Skip to content

Commit b111571

Browse files
authored
feat: link to easy invoice for client id generation, add option for custom client id in checkout (#35)
1 parent c8babce commit b111571

23 files changed

+2631
-2573
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ NEXT_PUBLIC_NPM_PACKAGE_URL="https://www.npmjs.com/package/@requestnetwork/payme
66
NEXT_PUBLIC_DEMO_URL="https://calendly.com/mariana-rn/request-network-intro"
77
NEXT_PUBLIC_INTEGRATION_URL="https://docs.request.network/building-blocks/templates/request-checkout"
88
NEXT_PUBLIC_RN_API_CLIENT_ID="rn_f6mr53l2yfcdv4sych5adq7gez3avurq"
9-
NEXT_PUBLIC_REQUEST_API_URL="https://api.stage.request.network"
9+
NEXT_PUBLIC_REQUEST_API_URL="https://api.stage.request.network"
10+
NEXT_PUBLIC_EASY_INVOICE_URL="https://easyinvoice.request.network"

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: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
import { useTicketStore } from "@/store/ticketStore";
44
import { useEffect, useState } from "react";
5-
import { useRouter } from "next/navigation";
65
import { PaymentWidget } from "./payment-widget/payment-widget";
6+
import { Input } from "./ui/input";
7+
import { Label } from "./ui/label";
8+
import { EASY_INVOICE_URL } from "@/lib/constants";
9+
import { useRouter } from "next/navigation";
710

811
export function PaymentStep() {
912
const { tickets, clearTickets } = useTicketStore();
1013
const [total, setTotal] = useState(0);
11-
const router = useRouter();
14+
const [customClientId, setCustomClientId] = useState("");
15+
const router = useRouter()
1216

1317
useEffect(() => {
1418
const newTotal = Object.values(tickets).reduce(
@@ -33,9 +37,9 @@ export function PaymentStep() {
3337
total: total.toString(),
3438
totalUSD: total.toString(),
3539
};
36-
console.log("ma kaj mona", total, invoiceTotals)
3740

38-
const clientId = process.env.NEXT_PUBLIC_RN_API_CLIENT_ID;
41+
const defaultClientId = process.env.NEXT_PUBLIC_RN_API_CLIENT_ID;
42+
const clientId = customClientId || defaultClientId;
3943

4044
return (
4145
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
@@ -72,14 +76,37 @@ export function PaymentStep() {
7276
</div>
7377
</div>
7478

75-
{/* Payment Widget */}
7679
<div role="region" aria-label="Payment Widget">
7780
<h2 className="text-2xl font-semibold mb-6">Payment</h2>
81+
<div className="mb-6 space-y-2">
82+
<Label htmlFor="custom-client-id">Custom Client ID</Label>
83+
<Input
84+
id="custom-client-id"
85+
type="text"
86+
placeholder="Enter your custom client ID"
87+
value={customClientId}
88+
onChange={(e) => setCustomClientId(e.target.value)}
89+
className="w-full"
90+
/>
91+
<p className="text-sm text-gray-600">
92+
Get your Client ID on{" "}
93+
<a
94+
href={`${EASY_INVOICE_URL}/ecommerce/manage`}
95+
target="_blank"
96+
rel="noopener noreferrer"
97+
className="text-green hover:text-dark-green underline"
98+
>
99+
EasyInvoice
100+
</a>
101+
</p>
102+
</div>
103+
78104
{clientId && (
79105
<PaymentWidget
80106
amountInUsd={total.toString()}
81107
recipientWallet="0xb07D2398d2004378cad234DA0EF14f1c94A530e4"
82108
paymentConfig={{
109+
reference: `ORDER-${Date.now()}`,
83110
rnApiClientId: clientId,
84111
supportedCurrencies: [
85112
"ETH-sepolia-sepolia",
@@ -124,17 +151,15 @@ export function PaymentStep() {
124151
totals: invoiceTotals,
125152
receiptNumber: `REC-${Date.now()}`,
126153
}}
127-
onSuccess={() => {
154+
onComplete={() => {
128155
clearTickets();
129-
setTimeout(() => {
130-
router.push("/");
131-
}, 10000);
156+
router.push('/');
132157
}}
133-
onError={(error) => {
158+
onPaymentError={(error) => {
134159
console.error("Payment failed:", error);
135160
}}
136161
>
137-
<div className="px-8 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">
162+
<div className="px-10 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">
138163
Pay with crypto
139164
</div>
140165
</PaymentWidget>

src/components/Playground/blocks/customize.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { PlaygroundFormData } from "@/lib/validation";
1111
import { Switch } from "../../ui/switch";
1212
import { useEffect } from "react";
1313
import { CurrencyCombobox } from "../../ui/combobox";
14+
import { EASY_INVOICE_URL } from "@/lib/constants";
1415

1516
export const CustomizeForm = () => {
1617
const {
@@ -126,6 +127,17 @@ export const CustomizeForm = () => {
126127
{errors.paymentConfig?.rnApiClientId?.message && (
127128
<Error>{errors.paymentConfig.rnApiClientId.message}</Error>
128129
)}
130+
<p className="text-sm text-gray-600">
131+
Get your Client ID on{" "}
132+
<a
133+
href={`${EASY_INVOICE_URL}/ecommerce/manage`}
134+
target="_blank"
135+
rel="noopener noreferrer"
136+
className="text-green hover:text-dark-green underline"
137+
>
138+
EasyInvoice
139+
</a>
140+
</p>
129141
</div>
130142

131143
<div className="flex flex-col gap-2">
@@ -136,6 +148,14 @@ export const CustomizeForm = () => {
136148
/>
137149
</div>
138150

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+
139159
<div className="flex flex-col gap-2">
140160
<Label className="flex items-center">
141161
Supported Currencies

src/components/Playground/index.tsx

Lines changed: 10 additions & 5 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: [],
@@ -141,12 +142,15 @@ export const Playground = () => {
141142
paymentConfig={${formatObject(cleanedPaymentConfig, 2)}}${formValues.uiConfig ? `
142143
uiConfig={${formatObject(formValues.uiConfig, 2)}}` : ''}
143144
receiptInfo={${formatObject(cleanedreceiptInfo, 2)}}
144-
onSuccess={() => {
145-
console.log('Payment successful');
145+
onPaymentSuccess={(requestId) => {
146+
console.log('Payment successful', requestId);
146147
}}
147-
onError={(error) => {
148+
onPaymentError={(error) => {
148149
console.error('Payment failed:', error);
149150
}}
151+
onComplete={() => {
152+
console.log('Payment process completed');
153+
}}
150154
>
151155
{/* Custom button example */}
152156
<div className="px-8 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">
@@ -192,8 +196,9 @@ export const Playground = () => {
192196
paymentConfig={formValues.paymentConfig}
193197
uiConfig={formValues.uiConfig}
194198
receiptInfo={formValues.receiptInfo}
195-
onSuccess={(requestId) => console.log('Payment successful:', requestId)}
196-
onError={(error) => console.error('Payment failed:', error)}
199+
onPaymentSuccess={(requestId) => console.log('Payment successful:', requestId)}
200+
onPaymentError={(error) => console.error('Payment failed:', error)}
201+
onComplete={() => console.log('Payment process completed')}
197202
>
198203
<div className="px-8 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">Pay with crypto</div>
199204
</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
}}

0 commit comments

Comments
 (0)