Skip to content

Commit 3dab4bd

Browse files
committed
feat: add custom reference to checkout and update code for the PaymentWidget component
1 parent 98721e8 commit 3dab4bd

File tree

21 files changed

+2579
-2559
lines changed

21 files changed

+2579
-2559
lines changed

package-lock.json

Lines changed: 2378 additions & 2449 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,28 @@
2121
"@radix-ui/react-switch": "^1.1.0",
2222
"@radix-ui/react-tabs": "^1.1.1",
2323
"@radix-ui/react-tooltip": "^1.1.2",
24-
"@tanstack/react-query": "^5.89.0",
24+
"@tanstack/react-query": "^5.90.2",
2525
"class-variance-authority": "^0.7.0",
2626
"clsx": "^2.1.1",
2727
"cmdk": "^1.0.0",
2828
"date-fns": "^4.1.0",
2929
"embla-carousel-autoplay": "^8.3.1",
3030
"embla-carousel-react": "^8.3.1",
3131
"framer-motion": "^11.3.28",
32+
"html2canvas-pro": "^1.5.11",
3233
"html2pdf.js": "^0.12.1",
34+
"jspdf": "^3.0.3",
3335
"lucide-react": "^0.542.0",
3436
"next": "14.2.5",
3537
"react": "^18",
3638
"react-dom": "^18",
37-
"react-hook-form": "^7.62.0",
39+
"react-hook-form": "^7.63.0",
3840
"sharp": "^0.33.5",
3941
"tailwind-merge": "^2.5.2",
4042
"tailwindcss-animate": "^1.0.7",
4143
"validator": "^13.12.0",
42-
"viem": "^2.37.6",
43-
"wagmi": "^2.16.9",
44+
"viem": "^2.37.11",
45+
"wagmi": "^2.17.5",
4446
"zod": "^3.23.8",
4547
"zustand": "^5.0.1"
4648
},

src/components/PaymentStep.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { PaymentWidget } from "./payment-widget/payment-widget";
66
import { Input } from "./ui/input";
77
import { Label } from "./ui/label";
88
import { EASY_INVOICE_URL } from "@/lib/constants";
9+
import { useRouter } from "next/navigation";
910

1011
export function PaymentStep() {
1112
const { tickets, clearTickets } = useTicketStore();
1213
const [total, setTotal] = useState(0);
1314
const [customClientId, setCustomClientId] = useState("");
15+
const router = useRouter()
1416

1517
useEffect(() => {
1618
const newTotal = Object.values(tickets).reduce(
@@ -104,6 +106,7 @@ export function PaymentStep() {
104106
amountInUsd={total.toString()}
105107
recipientWallet="0xb07D2398d2004378cad234DA0EF14f1c94A530e4"
106108
paymentConfig={{
109+
reference: `ORDER-${Date.now()}`,
107110
rnApiClientId: clientId,
108111
supportedCurrencies: [
109112
"ETH-sepolia-sepolia",
@@ -148,10 +151,11 @@ export function PaymentStep() {
148151
totals: invoiceTotals,
149152
receiptNumber: `REC-${Date.now()}`,
150153
}}
151-
onSuccess={() => {
154+
onComplete={() => {
152155
clearTickets();
156+
router.push('/');
153157
}}
154-
onError={(error) => {
158+
onPaymentError={(error) => {
155159
console.error("Payment failed:", error);
156160
}}
157161
>

src/components/Playground/blocks/customize.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ export const CustomizeForm = () => {
148148
/>
149149
</div>
150150

151+
<div className="flex flex-col gap-2">
152+
<Label>Custom payment reference (Optional)</Label>
153+
<Input
154+
placeholder="your-custom-payment-reference"
155+
{...register("paymentConfig.reference")}
156+
/>
157+
</div>
158+
151159
<div className="flex flex-col gap-2">
152160
<Label className="flex items-center">
153161
Supported Currencies

src/components/Playground/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const Playground = () => {
2727
amountInUsd: "0",
2828
recipientWallet: "",
2929
paymentConfig: {
30+
reference: undefined,
3031
walletConnectProjectId: undefined,
3132
rnApiClientId: "YOUR_CLIENT_ID_HERE",
3233
supportedCurrencies: [],
@@ -192,8 +193,9 @@ export const Playground = () => {
192193
paymentConfig={formValues.paymentConfig}
193194
uiConfig={formValues.uiConfig}
194195
receiptInfo={formValues.receiptInfo}
195-
onSuccess={(requestId) => console.log('Payment successful:', requestId)}
196-
onError={(error) => console.error('Payment failed:', error)}
196+
onPaymentSuccess={(requestId) => console.log('Payment successful:', requestId)}
197+
onPaymentError={(error) => console.error('Payment failed:', error)}
198+
onComplete={() => console.log('Payment process completed')}
197199
>
198200
<div className="px-8 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">Pay with crypto</div>
199201
</PaymentWidget>

src/components/payment-widget/README.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ function App() {
7474
totalTax: 0.00,
7575
},
7676
}}
77-
onSuccess={(requestId, transactionReceipts) => console.log("Payment successful:", requestId, transactionReceipts)}
78-
onError={(error) => console.error("Payment failed:", error)}
77+
onPaymentSuccess={(requestId, transactionReceipts) => console.log("Payment successful:", requestId, transactionReceipts)}
78+
onPaymentError={(error) => console.error("Payment failed:", error)}
7979
/>
8080
);
8181
}
@@ -212,11 +212,11 @@ function App() {
212212

213213
### Event Handlers
214214

215-
#### `onSuccess` (optional)
215+
#### `onPaymentSuccess` (optional)
216216
- **Type**: `(requestId: string) => void | Promise<void>`
217217
- **Description**: Callback function called when payment is successfully completed. Receives the Request Network request ID.
218218

219-
#### `onError` (optional)
219+
#### `onPaymentError` (optional)
220220
- **Type**: `(error: PaymentError) => void | Promise<void>`
221221
- **Description**: Callback function called when payment fails. Receives detailed error information.
222222

@@ -234,6 +234,35 @@ function App() {
234234
</PaymentWidget>
235235
```
236236

237+
## PaymentWidget Props
238+
239+
### onComplete
240+
Optional callback that fires when the user closes the payment widget from the success screen. Use this to handle post-payment cleanup or navigation.
241+
242+
```tsx
243+
<PaymentWidget
244+
// ... other props
245+
onComplete={() => {
246+
// Close modal, redirect user, etc.
247+
console.log("Payment flow completed");
248+
}}
249+
/>
250+
```
251+
252+
### PaymentConfig.reference
253+
Optional string to associate with the payment request for your own tracking purposes. This reference will be stored with the Request Network payment data.
254+
255+
```tsx
256+
<PaymentWidget
257+
paymentConfig={{
258+
rnApiClientId: "your-client-id",
259+
reference: "invoice-12345", // Your internal reference
260+
supportedCurrencies: ["ETH-sepolia-sepolia"]
261+
}}
262+
// ... other props
263+
/>
264+
```
265+
237266
## Styling and Theming
238267

239268
The Payment Widget uses Tailwind CSS and respects your application's design system through CSS custom properties. The following variables can be customized:
@@ -280,8 +309,8 @@ function PaymentWithExistingWallet() {
280309
receiptInfo={{
281310
// ... your receipt info
282311
}}
283-
onSuccess={(requestId) => console.log("Payment successful:", requestId)}
284-
onError={(error) => console.error("Payment failed:", error)}
312+
onPaymentSuccess={(requestId) => console.log("Payment successful:", requestId)}
313+
onPaymentError={(error) => console.error("Payment failed:", error)}
285314
>
286315
Pay with My Connected Wallet
287316
</PaymentWidget>
@@ -333,7 +362,7 @@ The widget includes comprehensive error handling for common scenarios:
333362
- **Invalid wallet addresses**
334363
- **API rate limiting**
335364

336-
All errors are passed to the `onError` callback with detailed error information for debugging and user feedback.
365+
All errors are passed to the `onPaymentError` callback with detailed error information for debugging and user feedback.
337366

338367
## Browser Support
339368

src/components/payment-widget/components/currency-select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
type ConversionCurrency,
1111
} from "../utils/currencies";
1212
import { Check } from "lucide-react";
13-
import { usePaymentWidgetContext } from "../context/payment-widget-context";
13+
import { usePaymentWidgetContext } from "../context/payment-widget-context/use-payment-widget-context";
1414

1515
interface CurrencySelectProps {
1616
onSubmit: (currency: ConversionCurrency) => void;

src/components/payment-widget/components/payment-confirmation.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import {
77
getSymbolOverride,
88
type ConversionCurrency,
99
} from "../utils/currencies";
10-
import { usePaymentWidgetContext } from "../context/payment-widget-context";
1110
import type { BuyerInfo, PaymentError } from "../types/index";
1211
import { useState } from "react";
1312
import type { TransactionReceipt } from "viem";
13+
import { usePaymentWidgetContext } from "../context/payment-widget-context/use-payment-widget-context";
1414

1515
interface PaymentConfirmationProps {
1616
selectedCurrency: ConversionCurrency;
@@ -32,9 +32,9 @@ export function PaymentConfirmation({
3232
amountInUsd,
3333
recipientWallet,
3434
connectedWalletAddress,
35-
paymentConfig: { rnApiClientId, feeInfo },
35+
paymentConfig: { rnApiClientId, feeInfo, reference },
3636
receiptInfo: { companyInfo: { name: companyName } = {} },
37-
onError,
37+
onPaymentError,
3838
walletAccount,
3939
} = usePaymentWidgetContext();
4040
const { isExecuting, executePayment } = usePayment(
@@ -58,6 +58,7 @@ export function PaymentConfirmation({
5858
amountInUsd,
5959
recipientWallet,
6060
paymentCurrency: selectedCurrency.id,
61+
reference,
6162
feeInfo,
6263
customerInfo: {
6364
email: buyerInfo.email,
@@ -97,7 +98,7 @@ export function PaymentConfirmation({
9798
}
9899
setLocalError(errorMessage);
99100

100-
onError?.(paymentError);
101+
onPaymentError?.(paymentError);
101102
}
102103
};
103104

src/components/payment-widget/components/payment-modal.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import { BuyerInfoForm } from "./buyer-info-form";
1313
import { PaymentConfirmation } from "./payment-confirmation";
1414
import { PaymentSuccess } from "./payment-success";
1515
import { DisconnectWallet } from "./disconnect-wallet";
16-
import { usePaymentWidgetContext } from "../context/payment-widget-context";
1716
import type { BuyerInfo } from "../types/index";
1817
import type { ConversionCurrency } from "../utils/currencies";
1918
import type { TransactionReceipt } from "viem";
19+
import { usePaymentWidgetContext } from "../context/payment-widget-context/use-payment-widget-context";
2020

2121
interface PaymentModalProps {
2222
isOpen: boolean;
@@ -27,7 +27,7 @@ export function PaymentModal({
2727
isOpen,
2828
handleModalOpenChange,
2929
}: PaymentModalProps) {
30-
const { isWalletOverride, receiptInfo, onSuccess } =
30+
const { isWalletOverride, receiptInfo, onPaymentSuccess, onComplete } =
3131
usePaymentWidgetContext();
3232

3333
const [activeStep, setActiveStep] = useState<
@@ -63,7 +63,7 @@ export function PaymentModal({
6363
transactionReceipts[transactionReceipts.length - 1].transactionHash,
6464
);
6565
setActiveStep("payment-success");
66-
await onSuccess?.(requestId, transactionReceipts);
66+
await onPaymentSuccess?.(requestId, transactionReceipts);
6767
};
6868

6969
const reset = () => {
@@ -80,6 +80,7 @@ export function PaymentModal({
8080
// reset modal state when closing from success step
8181
if (!isOpen && activeStep === "payment-success") {
8282
reset();
83+
onComplete?.();
8384
}
8485
handleModalOpenChange(isOpen);
8586
}}

src/components/payment-widget/components/payment-success.tsx

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import {
99
} from "../utils/receipt";
1010
import { useRef } from "react";
1111
import { ReceiptPDFTemplate } from "../components/receipt/receipt-template";
12-
import { usePaymentWidgetContext } from "../context/payment-widget-context";
1312
import type { BuyerInfo } from "../types/index";
1413
import type { ConversionCurrency } from "../utils/currencies";
14+
import { usePaymentWidgetContext } from "../context/payment-widget-context/use-payment-widget-context";
1515

1616
interface PaymentSuccessProps {
1717
requestId: string;
@@ -45,7 +45,7 @@ export function PaymentSuccess({
4545
walletAddress: connectedWalletAddress || "",
4646
},
4747
payment: {
48-
amount: amountInUsd, // TODO connect to actual payout and exchange rate
48+
amount: amountInUsd,
4949
chain: selectedCurrency.network,
5050
currency: selectedCurrency.symbol,
5151
exchangeRate: "1",
@@ -69,18 +69,37 @@ export function PaymentSuccess({
6969
return;
7070
}
7171

72-
const html2pdf = (await import("html2pdf.js")).default;
72+
const html2canvas = (await import("html2canvas-pro")).default;
73+
const jsPDF = (await import("jspdf")).default;
7374

74-
html2pdf()
75-
.set({
76-
margin: 1,
77-
filename: `receipt-${receiptParams.metadata?.receiptNumber || "payment"}.pdf`,
78-
image: { type: "jpeg", quality: 0.98 },
79-
html2canvas: { scale: 2 },
80-
jsPDF: { unit: "in", format: "a4", orientation: "portrait" },
81-
})
82-
.from(element)
83-
.save();
75+
const canvas = await html2canvas(element, {
76+
scale: 2,
77+
useCORS: true,
78+
backgroundColor: "#ffffff",
79+
width: element.scrollWidth,
80+
height: element.scrollHeight,
81+
windowWidth: element.scrollWidth,
82+
windowHeight: element.scrollHeight,
83+
});
84+
85+
const pdf = new jsPDF("p", "mm", "a4");
86+
const imgData = canvas.toDataURL("image/png");
87+
88+
const pageWidth = 210;
89+
const pageHeight = 297;
90+
const margin = 10;
91+
let imgWidth = pageWidth - margin * 2;
92+
let imgHeight = (canvas.height * imgWidth) / canvas.width;
93+
const maxHeight = pageHeight - margin * 2;
94+
if (imgHeight > maxHeight) {
95+
const ratio = maxHeight / imgHeight;
96+
imgWidth = imgWidth * ratio;
97+
imgHeight = imgHeight * ratio;
98+
}
99+
pdf.addImage(imgData, "PNG", margin, margin, imgWidth, imgHeight);
100+
pdf.save(
101+
`receipt-${receiptParams.metadata?.receiptNumber || "payment"}.pdf`,
102+
);
84103
} catch (error) {
85104
console.error("Failed to download receipt:", error);
86105
alert("Failed to download receipt. Please try again.");
@@ -118,6 +137,8 @@ export function PaymentSuccess({
118137
top: 0,
119138
opacity: 0,
120139
pointerEvents: "none",
140+
width: "800px",
141+
backgroundColor: "white",
121142
}}
122143
>
123144
<div ref={receiptRef}>

0 commit comments

Comments
 (0)