Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ NEXT_PUBLIC_NPM_PACKAGE_URL="https://www.npmjs.com/package/@requestnetwork/payme
NEXT_PUBLIC_DEMO_URL="https://calendly.com/mariana-rn/request-network-intro"
NEXT_PUBLIC_INTEGRATION_URL="https://docs.request.network/building-blocks/templates/request-checkout"
NEXT_PUBLIC_RN_API_CLIENT_ID="rn_f6mr53l2yfcdv4sych5adq7gez3avurq"
NEXT_PUBLIC_REQUEST_API_URL="https://api.stage.request.network"
NEXT_PUBLIC_REQUEST_API_URL="https://api.stage.request.network"
NEXT_PUBLIC_EASY_INVOICE_URL="https://easyinvoice.request.network"
4,827 changes: 2,378 additions & 2,449 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,28 @@
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@tanstack/react-query": "^5.89.0",
"@tanstack/react-query": "^5.90.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^4.1.0",
"embla-carousel-autoplay": "^8.3.1",
"embla-carousel-react": "^8.3.1",
"framer-motion": "^11.3.28",
"html2canvas-pro": "^1.5.11",
"html2pdf.js": "^0.12.1",
"jspdf": "^3.0.3",
"lucide-react": "^0.542.0",
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.62.0",
"react-hook-form": "^7.63.0",
"sharp": "^0.33.5",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"validator": "^13.12.0",
"viem": "^2.37.6",
"wagmi": "^2.16.9",
"viem": "^2.37.11",
"wagmi": "^2.17.5",
"zod": "^3.23.8",
"zustand": "^5.0.1"
},
Expand Down
47 changes: 36 additions & 11 deletions src/components/PaymentStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import { useTicketStore } from "@/store/ticketStore";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { PaymentWidget } from "./payment-widget/payment-widget";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { EASY_INVOICE_URL } from "@/lib/constants";
import { useRouter } from "next/navigation";

export function PaymentStep() {
const { tickets, clearTickets } = useTicketStore();
const [total, setTotal] = useState(0);
const router = useRouter();
const [customClientId, setCustomClientId] = useState("");
const router = useRouter()

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

const clientId = process.env.NEXT_PUBLIC_RN_API_CLIENT_ID;
const defaultClientId = process.env.NEXT_PUBLIC_RN_API_CLIENT_ID;
const clientId = customClientId || defaultClientId;

return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Expand Down Expand Up @@ -72,14 +76,37 @@ export function PaymentStep() {
</div>
</div>

{/* Payment Widget */}
<div role="region" aria-label="Payment Widget">
<h2 className="text-2xl font-semibold mb-6">Payment</h2>
<div className="mb-6 space-y-2">
<Label htmlFor="custom-client-id">Custom Client ID</Label>
<Input
id="custom-client-id"
type="text"
placeholder="Enter your custom client ID"
value={customClientId}
onChange={(e) => setCustomClientId(e.target.value)}
className="w-full"
/>
<p className="text-sm text-gray-600">
Get your Client ID on{" "}
<a
href={`${EASY_INVOICE_URL}/ecommerce/manage`}
target="_blank"
rel="noopener noreferrer"
className="text-green hover:text-dark-green underline"
>
EasyInvoice
</a>
</p>
</div>

{clientId && (
<PaymentWidget
amountInUsd={total.toString()}
recipientWallet="0xb07D2398d2004378cad234DA0EF14f1c94A530e4"
paymentConfig={{
reference: `ORDER-${Date.now()}`,
rnApiClientId: clientId,
supportedCurrencies: [
"ETH-sepolia-sepolia",
Expand Down Expand Up @@ -124,17 +151,15 @@ export function PaymentStep() {
totals: invoiceTotals,
receiptNumber: `REC-${Date.now()}`,
}}
onSuccess={() => {
onComplete={() => {
clearTickets();
setTimeout(() => {
router.push("/");
}, 10000);
router.push('/');
}}
onError={(error) => {
onPaymentError={(error) => {
console.error("Payment failed:", error);
}}
>
<div className="px-8 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">
<div className="px-10 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">
Pay with crypto
</div>
</PaymentWidget>
Expand Down
20 changes: 20 additions & 0 deletions src/components/Playground/blocks/customize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PlaygroundFormData } from "@/lib/validation";
import { Switch } from "../../ui/switch";
import { useEffect } from "react";
import { CurrencyCombobox } from "../../ui/combobox";
import { EASY_INVOICE_URL } from "@/lib/constants";

export const CustomizeForm = () => {
const {
Expand Down Expand Up @@ -126,6 +127,17 @@ export const CustomizeForm = () => {
{errors.paymentConfig?.rnApiClientId?.message && (
<Error>{errors.paymentConfig.rnApiClientId.message}</Error>
)}
<p className="text-sm text-gray-600">
Get your Client ID on{" "}
<a
href={`${EASY_INVOICE_URL}/ecommerce/manage`}
target="_blank"
rel="noopener noreferrer"
className="text-green hover:text-dark-green underline"
>
EasyInvoice
</a>
</p>
</div>

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

<div className="flex flex-col gap-2">
<Label>Custom payment reference (Optional)</Label>
<Input
placeholder="your-custom-payment-reference"
{...register("paymentConfig.reference")}
/>
</div>

<div className="flex flex-col gap-2">
<Label className="flex items-center">
Supported Currencies
Expand Down
15 changes: 10 additions & 5 deletions src/components/Playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const Playground = () => {
amountInUsd: "0",
recipientWallet: "",
paymentConfig: {
reference: undefined,
walletConnectProjectId: undefined,
rnApiClientId: "YOUR_CLIENT_ID_HERE",
supportedCurrencies: [],
Expand Down Expand Up @@ -141,12 +142,15 @@ export const Playground = () => {
paymentConfig={${formatObject(cleanedPaymentConfig, 2)}}${formValues.uiConfig ? `
uiConfig={${formatObject(formValues.uiConfig, 2)}}` : ''}
receiptInfo={${formatObject(cleanedreceiptInfo, 2)}}
onSuccess={() => {
console.log('Payment successful');
onPaymentSuccess={(requestId) => {
console.log('Payment successful', requestId);
}}
onError={(error) => {
onPaymentError={(error) => {
console.error('Payment failed:', error);
}}
onComplete={() => {
console.log('Payment process completed');
}}
>
{/* Custom button example */}
<div className="px-8 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">
Expand Down Expand Up @@ -192,8 +196,9 @@ export const Playground = () => {
paymentConfig={formValues.paymentConfig}
uiConfig={formValues.uiConfig}
receiptInfo={formValues.receiptInfo}
onSuccess={(requestId) => console.log('Payment successful:', requestId)}
onError={(error) => console.error('Payment failed:', error)}
onPaymentSuccess={(requestId) => console.log('Payment successful:', requestId)}
onPaymentError={(error) => console.error('Payment failed:', error)}
onComplete={() => console.log('Payment process completed')}
>
<div className="px-8 py-2 bg-[#099C77] text-white rounded-lg hover:bg-[#087f63] transition-colors text-center">Pay with crypto</div>
</PaymentWidget>
Expand Down
43 changes: 36 additions & 7 deletions src/components/payment-widget/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ function App() {
totalTax: 0.00,
},
}}
onSuccess={(requestId, transactionReceipts) => console.log("Payment successful:", requestId, transactionReceipts)}
onError={(error) => console.error("Payment failed:", error)}
onPaymentSuccess={(requestId, transactionReceipts) => console.log("Payment successful:", requestId, transactionReceipts)}
onPaymentError={(error) => console.error("Payment failed:", error)}
/>
);
}
Expand Down Expand Up @@ -212,11 +212,11 @@ function App() {

### Event Handlers

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

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

Expand All @@ -234,6 +234,35 @@ function App() {
</PaymentWidget>
```

## PaymentWidget Props

### onComplete
Optional callback that fires when the user closes the payment widget from the success screen. Use this to handle post-payment cleanup or navigation.

```tsx
<PaymentWidget
// ... other props
onComplete={() => {
// Close modal, redirect user, etc.
console.log("Payment flow completed");
}}
/>
```

### PaymentConfig.reference
Optional string to associate with the payment request for your own tracking purposes. This reference will be stored with the Request Network payment data.

```tsx
<PaymentWidget
paymentConfig={{
rnApiClientId: "your-client-id",
reference: "invoice-12345", // Your internal reference
supportedCurrencies: ["ETH-sepolia-sepolia"]
}}
// ... other props
/>
```

## Styling and Theming

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

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

## Browser Support

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type ConversionCurrency,
} from "../utils/currencies";
import { Check } from "lucide-react";
import { usePaymentWidgetContext } from "../context/payment-widget-context";
import { usePaymentWidgetContext } from "../context/payment-widget-context/use-payment-widget-context";

interface CurrencySelectProps {
onSubmit: (currency: ConversionCurrency) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
getSymbolOverride,
type ConversionCurrency,
} from "../utils/currencies";
import { usePaymentWidgetContext } from "../context/payment-widget-context";
import type { BuyerInfo, PaymentError } from "../types/index";
import { useState } from "react";
import type { TransactionReceipt } from "viem";
import { usePaymentWidgetContext } from "../context/payment-widget-context/use-payment-widget-context";

interface PaymentConfirmationProps {
selectedCurrency: ConversionCurrency;
Expand All @@ -32,9 +32,9 @@ export function PaymentConfirmation({
amountInUsd,
recipientWallet,
connectedWalletAddress,
paymentConfig: { rnApiClientId, feeInfo },
paymentConfig: { rnApiClientId, feeInfo, reference },
receiptInfo: { companyInfo: { name: companyName } = {} },
onError,
onPaymentError,
walletAccount,
} = usePaymentWidgetContext();
const { isExecuting, executePayment } = usePayment(
Expand All @@ -58,6 +58,7 @@ export function PaymentConfirmation({
amountInUsd,
recipientWallet,
paymentCurrency: selectedCurrency.id,
reference,
feeInfo,
customerInfo: {
email: buyerInfo.email,
Expand Down Expand Up @@ -97,7 +98,7 @@ export function PaymentConfirmation({
}
setLocalError(errorMessage);

onError?.(paymentError);
onPaymentError?.(paymentError);
}
};

Expand Down
7 changes: 4 additions & 3 deletions src/components/payment-widget/components/payment-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { BuyerInfoForm } from "./buyer-info-form";
import { PaymentConfirmation } from "./payment-confirmation";
import { PaymentSuccess } from "./payment-success";
import { DisconnectWallet } from "./disconnect-wallet";
import { usePaymentWidgetContext } from "../context/payment-widget-context";
import type { BuyerInfo } from "../types/index";
import type { ConversionCurrency } from "../utils/currencies";
import type { TransactionReceipt } from "viem";
import { usePaymentWidgetContext } from "../context/payment-widget-context/use-payment-widget-context";

interface PaymentModalProps {
isOpen: boolean;
Expand All @@ -27,7 +27,7 @@ export function PaymentModal({
isOpen,
handleModalOpenChange,
}: PaymentModalProps) {
const { isWalletOverride, receiptInfo, onSuccess } =
const { isWalletOverride, receiptInfo, onPaymentSuccess, onComplete } =
usePaymentWidgetContext();

const [activeStep, setActiveStep] = useState<
Expand Down Expand Up @@ -63,7 +63,7 @@ export function PaymentModal({
transactionReceipts[transactionReceipts.length - 1].transactionHash,
);
setActiveStep("payment-success");
await onSuccess?.(requestId, transactionReceipts);
await onPaymentSuccess?.(requestId, transactionReceipts);
};

const reset = () => {
Expand All @@ -80,6 +80,7 @@ export function PaymentModal({
// reset modal state when closing from success step
if (!isOpen && activeStep === "payment-success") {
reset();
onComplete?.();
}
handleModalOpenChange(isOpen);
}}
Expand Down
Loading