Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
6 changes: 4 additions & 2 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 @@ -192,8 +193,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