-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/payment widget #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… wagmi for wallet connection
…t payment confirmation step
…nect to the RN API
…cies, use payer address when creating a payout
…wice in strict mode
…l scope to avoid conflicts with other ShadCN libs
… and show it in confirmation step
928b459
to
4242270
Compare
Caution Review failedThe pull request is closed. WalkthroughAdds a Next.js app scaffold, global styles, shadcn UI primitives, demo wallet/connect components, a comprehensive Payment Widget package (UI, contexts, hooks, utils, types, receipt templates), registry manifests, tooling configs, CI workflows, and replaces Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant PW as PaymentWidget
participant CH as ConnectionHandler
participant WM as WalletConnectModal
participant PM as PaymentModal
participant HP as usePayment
participant API as Request API
participant BC as Blockchain
U->>PW: Click "Pay with crypto"
PW->>CH: Open flow
alt not connected & no walletAccount
CH->>WM: Open wallet modal
U->>WM: Select connector
WM-->>CH: Connected
else connected or walletAccount provided
CH-->>PW: Proceed
end
PW->>PM: Open payment modal
PM->>PM: Steps: currency -> buyer info -> confirmation
PM->>HP: Confirm -> executePayment(network, params)
HP->>API: POST /v2/payouts
alt API returns transactions
loop for each tx
HP->>BC: sendTransaction / walletClient.sendTransaction
BC-->>HP: txHash
HP->>BC: waitForReceipt
BC-->>HP: receipt
end
HP-->>PM: requestId + receipts
PM-->>U: Success + receipt download
else API error
API-->>HP: error
HP-->>PM: PaymentError
PM-->>U: Show error
end
sequenceDiagram
autonumber
actor U as User
participant PW as PaymentWidget (walletAccount provided)
participant PM as PaymentModal
participant HP as usePayment (custom WalletClient)
participant API as Request API
participant WC as WalletClient
U->>PW: Click "Pay with crypto"
PW->>PM: Open modal (connection skipped)
PM->>HP: Confirm -> executePayment(network, params)
HP->>API: Create payout
API-->>HP: Transactions
loop for each tx
HP->>WC: walletAccount.sendTransaction(tx)
WC-->>HP: txHash + receipt
end
HP-->>PM: requestId + receipts
PM-->>U: Success + receipt download
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
🧹 Nitpick comments (74)
biome.json (2)
8-11
: Useexcludes
instead of negatedincludes
and harden globs.Negated patterns inside
includes
are brittle. Prefer explicitexcludes
with recursive globs; keepincludes
as["**"]
."files": { "ignoreUnknown": true, - "includes": ["**", "!node_modules", "!.next", "!dist", "!build"] + "includes": ["**"], + "excludes": ["**/node_modules/**", ".next/**", "dist/**", "build/**"] },
17-29
: Broaden domain presets to match this codebase (TypeScript, a11y).Given a Next.js + React + TS widget, enable TypeScript and a11y recommended presets alongside React/Next. Keep the Tailwind-friendly
noUnknownAtRules
override."linter": { "enabled": true, "rules": { "recommended": true, "suspicious": { "noUnknownAtRules": "off" } }, "domains": { - "next": "recommended", - "react": "recommended" + "next": "recommended", + "react": "recommended", + "typescript": "recommended", + "a11y": "recommended" } },next.config.ts (2)
3-6
: Use “satisfies NextConfig” for safer typing.Prevents accidental widening and catches unknown keys at compile time.
-import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { +import type { NextConfig } from "next"; + +const nextConfig = { /* config options here */ - reactStrictMode: true, // @TODO this is an issue -}; + reactStrictMode: true, +} satisfies NextConfig;
5-5
: Clarify/resolve the Strict Mode TODO.React Strict Mode double-invokes effects in dev and can disturb wallet/init flows. Either keep it on and make effects idempotent (preferred), or gate it by env during local dev.
Example gating (if you really need it):
- reactStrictMode: true, + reactStrictMode: process.env.NEXT_PUBLIC_DISABLE_STRICT === "1" ? false : true,tsconfig.json (1)
2-24
: Enable verbatimModuleSyntax to avoid synthetic default import pitfalls with ESM (wagmi/viem).Aligns with TS 5 best practices and Next 15.
"jsx": "preserve", + "verbatimModuleSyntax": true, "incremental": true,
README.md (1)
1-36
: Augment README with Payment Widget setup and env vars.Helps users run the demos and avoid leaking secrets.
+## Payment Widget Setup + +Set the following environment variables (do not commit secrets): + +- `NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID` – WalletConnect Cloud Project ID. +- `REQUEST_API_CLIENT_ID` – Request Network API client ID (keep any secrets server-side). +- `RECIPIENT_WALLET` – Destination wallet address for payments. + +Then run `npm run dev` and open http://localhost:3000.package.json (1)
32-41
: Add Node engine and a type-check script.Locks CI/runtime expectations and surfaces TS errors early.
"devDependencies": { "@biomejs/biome": "2.2.0", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "shadcn": "^3.1.0", "tailwindcss": "^4", "typescript": "^5" - } + }, + "engines": { + "node": ">=18.18.0" + }And add:
"scripts": { "dev": "next dev --turbopack", "build": "next build --turbopack", "start": "next start", "lint": "biome check", "format": "biome format --write", - "registry:build": "shadcn build" + "registry:build": "shadcn build", + "typecheck": "tsc --noEmit" },components.json (1)
6-12
: Verify shadcn Tailwind config path.
"config": ""
is unusual; ensureshadcn build
doesn’t expect a file path with Tailwind v4. If needed, set it totailwind.config.ts
or omit when optional.public/r/registry.json (2)
18-25
: Align published dependency ranges with what the app actually uses.Prevents consumers from installing older, untested versions.
- "wagmi@^2.12.29", - "viem@^2.21.53", - "@tanstack/react-query@^5.64.1", - "react-hook-form@^7.0.0", - "lucide-react@^0.542.0", - "html2pdf.js@^0.12.0" + "wagmi@^2.16.9", + "viem@^2.37.3", + "@tanstack/react-query@^5.86.0", + "react-hook-form@^7.62.0", + "lucide-react@^0.542.0", + "html2pdf.js@^0.12.0"
138-141
: Publish the README with the registry item.Gives consumers inline usage docs.
{ "path": "lib/utils.ts", "type": "registry:lib", "target": "lib/utils.ts" - } + }, + { + "path": "registry/default/payment-widget/README.md", + "type": "registry:lib", + "target": "components/payment-widget/README.md" + }registry/default/payment-widget/utils/wagmi.ts (3)
18-27
: Type-safety: avoidas any
and build connectors inline.Infer connector types by composing the array inline; removes the
any
cast and keeps the list immutable.Apply:
-export const getWagmiConfig = (walletConnectProjectId?: string) => { - const connectors = [ - injected(), - coinbaseWallet({ - appName: "Request Network Payment", - }), - metaMask(), - safe(), - ]; +export const getWagmiConfig = (walletConnectProjectId?: string) => {…and below (see next hunk) compose
connectors: [...]
directly.
28-45
: WalletConnect creation: inline compose and dropas any
.Also, keep metadata but create the connector only when
walletConnectProjectId
is truthy.Apply:
- if (walletConnectProjectId && walletConnectProjectId.length > 0) { - try { - const connector = walletConnect({ - projectId: walletConnectProjectId, - metadata: { - name: "Request Network Payment", - description: "Pay with cryptocurrency using Request Network", - url: "https://request.network", - icons: ["https://request.network/favicon.ico"], - }, - showQrModal: true, - }); - - connectors.push(connector as any); - } catch (error) { - console.error("WalletConnect creation failed:", error); - } - } + const wc = + walletConnectProjectId && walletConnectProjectId.length > 0 + ? walletConnect({ + projectId: walletConnectProjectId, + metadata: { + name: "Request Network Payment", + description: "Pay with cryptocurrency using Request Network", + url: "https://request.network", + icons: ["https://request.network/favicon.ico"], + }, + showQrModal: true, // supported option + }) + : undefined;Notes:
metadata
andshowQrModal
are valid WalletConnect connector options. (wagmi.sh)
47-58
: Production RPCs and SSR flags.
- Using bare
http()
falls back to public RPCs; expect rate‑limits. Provide authenticated RPC URLs via env. (wagmi.sh)- Consider
autoConnect: true
andssr: true
(when wiring cookie/initial state in the Provider) for better Next.js App Router UX. Validate SSR implications in your layout. (github.com)Apply:
- const config = createConfig({ - chains: [mainnet, sepolia, arbitrum, optimism, polygon, base], - connectors, - transports: { - [mainnet.id]: http(), - [sepolia.id]: http(), - [arbitrum.id]: http(), - [optimism.id]: http(), - [polygon.id]: http(), - [base.id]: http(), - }, - }); + const config = createConfig({ + chains: [mainnet, sepolia, arbitrum, optimism, polygon, base], + // Compose connectors inline to preserve types. + connectors: [ + injected(), + coinbaseWallet({ appName: "Request Network Payment" }), + metaMask(), + safe(), + ...(wc ? [wc] : []), + ], + transports: { + [mainnet.id]: http(process.env.NEXT_PUBLIC_RPC_MAINNET), + [sepolia.id]: http(process.env.NEXT_PUBLIC_RPC_SEPOLIA), + [arbitrum.id]: http(process.env.NEXT_PUBLIC_RPC_ARBITRUM), + [optimism.id]: http(process.env.NEXT_PUBLIC_RPC_OPTIMISM), + [polygon.id]: http(process.env.NEXT_PUBLIC_RPC_POLYGON), + [base.id]: http(process.env.NEXT_PUBLIC_RPC_BASE), + }, + // autoConnect: true, + // ssr: true, // if you also pass cookie initialState in your Next.js layout/provider + });Optional: If keeping both
injected()
andmetaMask()
, document why (MetaMask SDK vs generic injected). (wagmi.sh)app/globals.css (2)
34-56
: Improve native form/control theming withcolor-scheme
.Advertise theme intent to the UA for better default widgets in light mode.
Apply:
:root { --radius: 0.625rem; --background: #ffffff; --foreground: #000502; + color-scheme: light;
57-77
: Advertise dark theme to the UA.Helps inputs/scrollbars match dark palette.
Apply:
.dark { --background: #000502; --foreground: #ffffff; + color-scheme: dark;
.gitignore (1)
39-41
: Do not ignorenext-env.d.ts
(usually committed).Next.js recommends committing this file so TS types are available on fresh clones.
Apply:
-*.tsbuildinfo -next-env.d.ts +*.tsbuildinfo +# keep next-env.d.ts tracked (generated by Next.js and safe to commit)registry.json (1)
18-25
: Version consistency and bundle weight.
- README lists
lucide-react ^0.263.1
while registry declares^0.542.0
. Align to one version.html2pdf.js
is heavy; ensure it’s dynamically imported in the widget to avoid bloating host apps that do not use receipts.Would you like me to add a lazy
import('html2pdf.js')
in the receipt utility and update docs accordingly?registry/default/payment-widget/README.md (2)
347-356
: Dependency version drift (lucide-react
).README says
^0.263.1
; registry uses^0.542.0
. Align to the same version to avoid consumer conflicts.
215-221
:onSuccess
signature mismatch.README shows
(requestId)
, butPaymentWidgetProps
defines(requestId, receipts)
. Update docs to show both params.registry/default/payment-widget/types/index.ts (4)
11-15
: AlignTransaction
with Viem types.Use Viem
Hex
and make fields optional to reflect typical transaction requests.Apply:
-export interface Transaction { - to: string; - data: string; - value: { hex: string }; -} +export interface Transaction { + to: string; + data?: `0x${string}`; // viem Hex + value?: `0x${string}`; // viem Hex +}Add (outside this hunk) if you prefer explicit type:
// at top of file // import type { Hex } from "viem";
17-26
: Currency amounts: prefer consistent string representation.To avoid FP rounding, use strings across receipt items.
Apply:
export interface ReceiptItem { id: string; description: string; quantity: number; - unitPrice: number; + unitPrice: string; discount?: number; tax?: number; - total: number; + total: string; currency?: string; }
28-41
: Makecountry
optional to match README and real‑world data.Apply:
export interface CompanyInfo { name: string; address?: { street: string; city: string; state: string; postalCode: string; - country: string; + country?: string; };
58-63
: Totals as strings for consistency with items.Keeps a single representation across the receipt model.
Apply:
export interface ReceiptTotals { - totalDiscount: number; - totalTax: number; - total: number; - totalUSD: number; + totalDiscount: string; + totalTax: string; + total: string; + totalUSD: string; }registry/default/payment-widget/payment-widget.types.ts (2)
10-11
: Minor: tidy comment and clarify currency IDs.Apply:
- supportedCurrencies?: string[]; // an array of currency ids + supportedCurrencies?: string[]; // currency IDs (e.g., "ethereum-mainnet", "usdc-ethereum")
63-69
: Docs parity: mention both params inonSuccess
JSDoc.The README shows a single param; align docs or README to include
receipts
.registry/default/payment-widget/types/html2pdf.d.ts (3)
41-51
: Type surface likely diverges from html2pdf.js (to/outputPdf).The upstream worker exposes toPdf()/toCanvas()/toImg() and output(type), not to()/outputPdf(). Please verify your runtime usage and align to avoid false-positive typings and runtime surprises.
Apply this diff to better match common API names:
interface Html2PdfWorker { from(element: HTMLElement | string): Html2PdfWorker; - to(target: string): Html2PdfWorker; + toPdf(): Html2PdfWorker; + // optionally: + // toCanvas(): Html2PdfWorker; + // toImg(): Html2PdfWorker; set(options: Html2PdfOptions): Html2PdfWorker; save(filename?: string): Promise<void>; - outputPdf(type?: string): Promise<any>; + output( + type?: "arraybuffer" | "blob" | "datauristring" | "dataurlnewwindow" + ): Promise<ArrayBuffer | Blob | string | void>;
53-58
: ESM/CJS interop: prefer adding “export as namespace” (and consider a default export alias).Using export = is fine for CJS, but many apps import html2pdf.js with a default import or via UMD. Add namespace export (and optionally a default alias) to reduce friction.
- export = html2pdf; + export = html2pdf; + export as namespace html2pdf; + // If your tsconfig uses NodeNext/bundler and you import default: + // declare const _default: typeof html2pdf; + // export default _default;Also applies to: 75-76
41-43
: Broaden element typing to avoid narrowing errors.html2pdf.from accepts generic Element/Node in practice. Widening helps when refs are typed as Element.
- from(element: HTMLElement | string): Html2PdfWorker; + from(element: Element | HTMLElement | string): Html2PdfWorker; - function html2pdf( - element: HTMLElement, + function html2pdf( + element: Element | HTMLElement,Also applies to: 55-57
registry/default/payment-widget/components/disconnect-wallet.tsx (2)
16-18
: Guard against undefined address and add a title for full address.Avoid rendering “undefined…undefined” during brief states; improve UX with tooltip.
export function DisconnectWallet() { const { address } = useAccount(); const { disconnect } = useDisconnect(); + if (!address) return null; @@ - <span className="text-sm font-mono text-muted-foreground"> - {`${address?.slice(0, 6)}...${address?.slice(-4)}`} + <span + className="text-sm font-mono text-muted-foreground" + title={address} + > + {`${address.slice(0, 6)}...${address.slice(-4)}`} </span>
19-25
: Small a11y improvement on the button.Add an aria-label; optionally handle async errors.
- <Button + <Button variant="outline" onClick={handleDisconnect} - className="text-sm ml-auto" + className="text-sm ml-auto" + aria-label="Disconnect wallet" > Disconnect </Button>components/ui/input.tsx (1)
5-12
: Default the input type to “text”.Prevents passing undefined and matches native default.
-({ className, type, ...props }, ref) => { +({ className, type = "text", ...props }, ref) => {registry/default/payment-widget/constants.ts (2)
1-1
: Don’t hardcode the API base URL; use an env override.Enable staging/self-hosted environments and testing without code changes.
-export const RN_API_URL = "https://api.request.network"; +export const RN_API_URL = + process.env.NEXT_PUBLIC_REQUEST_API_URL ?? "https://api.request.network";
2-14
: Consider bundle size: large base64 data URIs inflate JS.Inlining multiple ~KB icons into JS increases parse time. Prefer tiny SVGs, static assets, or lazy import.
- Option A: keep only wallet type → asset key here; load images from /public or CDN.
- Option B: switch to lightweight SVG for all icons; compress and dedupe.
registry/default/payment-widget/components/connection-handler.tsx (1)
18-26
: Handle reconnecting state for smoother UX.Wagmi can report isReconnecting; treat it as loading to avoid flicker.
- const { isConnected, isConnecting } = useAccount(); + const { isConnected, isConnecting, isReconnecting } = useAccount(); @@ - isLoading={isConnecting} + isLoading={isConnecting || isReconnecting}registry/default/payment-widget/context/web3-context.tsx (2)
10-16
: Import React types explicitly to avoid “Cannot find name ‘React’” in TS.Use ReactNode instead of React.ReactNode to avoid namespace dependency.
-import { useMemo, useRef } from "react"; +import { useMemo, useRef, type ReactNode } from "react"; @@ -}: { - children: React.ReactNode; +}: { + children: ReactNode; walletConnectProjectId?: string; }) {
17-25
: Config never updates if walletConnectProjectId changes.The ref guards against strict‑mode double init, but it also blocks updates. Rebuild when the project ID changes.
- const wagmiConfig = useMemo(() => { - if (!configRef.current) { - configRef.current = getWagmiConfig(walletConnectProjectId); - } - return configRef.current; - }, [walletConnectProjectId]); + const wagmiConfig = useMemo(() => { + // Recreate only when the project ID actually changes + if (!configRef.current) { + configRef.current = getWagmiConfig(walletConnectProjectId); + } + return configRef.current; + }, []); + + // Update on projectId change without reinitializing on strict-mode double mount + const prevIdRef = useRef<string | undefined>(undefined); + if (prevIdRef.current !== walletConnectProjectId) { + configRef.current = getWagmiConfig(walletConnectProjectId); + prevIdRef.current = walletConnectProjectId; + }registry/default/payment-widget/components/currency-select.tsx (4)
31-33
: Guard query execution and retries; avoid hammering API with missing config.Only fetch when rnApiClientId and network are set; also disable default retries to align with your manual Retry button and set a sane cache window.
} = useQuery({ queryKey: ["conversionCurrencies", rnApiClientId, network], - queryFn: () => getConversionCurrencies(rnApiClientId, network), + queryFn: () => getConversionCurrencies(rnApiClientId, network), + enabled: Boolean(rnApiClientId && network), + staleTime: 300_000, + retry: false, + refetchOnWindowFocus: false, });
59-66
: Show a helpful message when rnApiClientId is missing.Failing silently leads to a confusing UX. Add an explicit notice if the client ID isn’t configured.
- if (!conversionCurrencies || conversionCurrencies.length === 0) { + if (!rnApiClientId) { + return <div>Missing Request API client id. Set NEXT_PUBLIC_REQUEST_API_CLIENT_ID.</div>; + } + + if (!conversionCurrencies || conversionCurrencies.length === 0) { return <div>No conversion currencies available.</div>; }
67-74
: Auto‑select the only eligible currency to reduce friction.When exactly one option is available, preselect it.
-import { useState } from "react"; +import { useEffect, useState } from "react"; ... - const eligibleCurrencies = + const eligibleCurrencies = lowerCaseSupportedCurrencies.length > 0 ? conversionCurrencies.filter((currency) => lowerCaseSupportedCurrencies.includes(currency.id.toLowerCase()), ) : conversionCurrencies; + + useEffect(() => { + if (!selectedCurrency && eligibleCurrencies.length === 1) { + setSelectedCurrency(eligibleCurrencies[0].id); + } + }, [eligibleCurrencies, selectedCurrency]);
74-80
: Gate noisy console output to dev only.Avoid logging whole currency lists in production.
- if (eligibleCurrencies.length === 0) { - console.warn( - "Your supportedCurrencies do not match available currencies.", - { supportedCurrencies, conversionCurrencies }, - ); + if (eligibleCurrencies.length === 0) { + if (process.env.NODE_ENV === "development") { + console.warn( + "Your supportedCurrencies do not match available currencies.", + { supportedCurrencies, conversionCurrencies }, + ); + } return <div>No supported currencies available.</div>; }app/page.tsx (1)
34-39
: Make the install hint environment-agnostic.Hardcoding localhost can mislead in non-local environments. Consider deriving the base URL or adding a note.
registry/default/payment-widget/components/wallet-connect-modal.tsx (2)
28-30
: Handle connect errors explicitly.Wrap connect in try/catch to surface failures (e.g., user rejects, provider errors).
- const handleConnect = (connector: Connector) => { - connect({ connector }); - }; + const handleConnect = async (connector: Connector) => { + try { + await connect({ connector }); + } catch (err) { + // Optional: surface via toast or inline message + console.error("Wallet connect failed:", err); + } + };
68-95
: Empty state when no connectors are available.Provide feedback if nothing is listable after filtering.
- <div className="space-y-3 py-4"> - {displayConnectors.map((connector) => ( + <div className="space-y-3 py-4"> + {displayConnectors.length === 0 && ( + <div className="text-center text-sm text-muted-foreground py-2"> + No wallets available. Install a wallet or check your Wagmi config. + </div> + )} + {displayConnectors.map((connector) => ( <Buttonregistry/default/payment-widget/components/receipt/styles.css (1)
236-244
: Improve PDF/print fidelity and pagination.Add print rules to preserve colors and avoid orphaned breaks in tables/sections with html2pdf.
.notes-section { border-top: 1px solid #e2e8f0; font-size: 11px; color: #4a5568; background-color: #f7fafc; padding: 8px 12px; border-radius: 4px; } + +/* Print optimizations for html2pdf */ +@media print { + .receipt-container, + .items-table th, + .items-table td, + .payment-info, + .totals-box, + .notes-section { + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + .info-box, + .items-table, + .totals-section, + .notes-section { + break-inside: avoid; + page-break-inside: avoid; + } +}app/components/payment-widget-wrapper.tsx (2)
57-75
: Use reserved example domains for sample contact details.Avoid real-looking emails/websites in templates to prevent accidental outreach.
- email: "[email protected]", - phone: "+1 (555) 123-4567", - website: "https://request.network", + email: "[email protected]", + phone: "+1 (555) 123-4567", + website: "https://example.com",
43-56
: Consider making payment config overridable.Hardcoded network, currencies, and fees limit reuse. Allow overrides via props or env for demos vs. production.
registry/default/payment-widget/components/payment-confirmation.tsx (2)
45-47
: Surface a helpful error when no wallet address is present.Currently it silently returns. Notify the user and keep them on the screen.
- if (!connectedWalletAddress) return; + if (!connectedWalletAddress) { + setLocalError("Connect a wallet before proceeding."); + return; + }
164-181
: Disable Pay until prerequisites are met.Also guard the action if a wallet isn’t connected to prevent avoidable errors.
- <Button + <Button type="button" onClick={handleExecutePayment} className="flex-1" - disabled={isExecuting} + disabled={isExecuting || !connectedWalletAddress} > {isExecuting ? "Processing..." : "Pay"} </Button>components/ui/radio-group.tsx (2)
23-33
: Tighten unchecked vs. checked visuals (use data-state).Defaulting to
border-primary
makes all items look selected. Prefer neutral border by default and promote ondata-[state=checked]
.- className={cn( - "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", - className - )} + className={cn( + "aspect-square h-4 w-4 rounded-full border border-input ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary", + className + )}
36-38
: Avoid icon dependency for the dot.A tiny div is cheaper and more controllable than importing
lucide-react
just for a filled circle.- <RadioGroupPrimitive.Indicator className="flex items-center justify-center"> - <Circle className="h-2.5 w-2.5 fill-current text-current" /> - </RadioGroupPrimitive.Indicator> + <RadioGroupPrimitive.Indicator className="flex items-center justify-center"> + <div className="h-2.5 w-2.5 rounded-full bg-current" /> + </RadioGroupPrimitive.Indicator>registry/default/payment-widget/components/receipt/receipt-template.tsx (2)
38-41
: Typo: “REC” → “RECEIPT”.- <h2 className="receipt-title">REC</h2> + <h2 className="receipt-title">RECEIPT</h2>
124-136
: Crypto amount formatting is too naive for end‑users.
formatCryptoAmount
currently returns a raw${amount} ${currency}
. Consider fixed decimals per token and trimming insignificant zeros.I can add a formatter that uses token decimals (from your currency config) and
Intl.NumberFormat
.Also applies to: 150-176, 179-184
registry/default/payment-widget/components/buyer-info-form.tsx (7)
42-59
: Trim and normalize address fields on submit (including country uppercasing).const onFormSubmit = (data: BuyerInfo) => { - const cleanValue = (value: string | undefined) => { + const cleanValue = (value: string | undefined) => { if (typeof value === "string") { const trimmed = value.trim(); return trimmed === "" ? undefined : trimmed; } return value; }; - // we want to send undefined for empty optional fields - const cleanData: BuyerInfo = { + const cleanedAddress = + hasAnyAddressField + ? { + street: cleanValue(data.address?.street) ?? "", + city: cleanValue(data.address?.city) ?? "", + state: cleanValue(data.address?.state) ?? "", + country: (cleanValue(data.address?.country) || "")?.toUpperCase(), + postalCode: cleanValue(data.address?.postalCode) ?? "", + } + : undefined; + + // we want to send undefined for empty optional fields + const cleanData: BuyerInfo = { email: data.email, firstName: cleanValue(data.firstName), lastName: cleanValue(data.lastName), businessName: cleanValue(data.businessName), phone: cleanValue(data.phone), - address: data.address && hasAnyAddressField ? data.address : undefined, + address: cleanedAddress, }; onSubmit(cleanData); };
73-83
: Enable browser autofill for email.<Input id="email" type="email" + autoComplete="email" placeholder="[email protected]"
95-103
: Autofill for name fields.<Input id="firstName" + autoComplete="given-name" placeholder="John" {...register("firstName")} /> ... - <Input id="lastName" placeholder="Doe" {...register("lastName")} /> + <Input id="lastName" autoComplete="family-name" placeholder="Doe" {...register("lastName")} />
110-122
: Autofill for business and phone.<Input id="businessName" + autoComplete="organization" placeholder="Acme Inc." {...register("businessName")} /> ... <Input id="phone" type="tel" + inputMode="tel" + autoComplete="tel" placeholder="+1 (555) 123-4567" {...register("phone")} />
133-141
: Autofill for street.<Input id="address.street" + autoComplete="address-line1" placeholder="123 Main St, Apt 4B"
155-163
: Autofill for city/state/postal code.- <Input id="address.city" placeholder="San Francisco" + <Input id="address.city" autoComplete="address-level2" placeholder="San Francisco" ... - <Input id="address.state" placeholder="CA" + <Input id="address.state" autoComplete="address-level1" placeholder="CA" ... - <Input id="address.postalCode" placeholder="94105" + <Input id="address.postalCode" autoComplete="postal-code" placeholder="94105"Also applies to: 175-183, 225-233
196-213
: Allow lowercase country input; limit to 2 chars.Make the regex case-insensitive and cap input length at 2.
<Input id="address.country" placeholder="US" + maxLength={2} {...register("address.country", { required: hasAnyAddressField ? "Country is required when address is provided" : false, - pattern: hasAnyAddressField + pattern: hasAnyAddressField ? { - value: /^[A-Z]{2}$/, + value: /^[A-Z]{2}$/i, message: "Use a 2-letter ISO country code (e.g. US, CA, GB)", } : undefined, })} />registry/default/payment-widget/payment-widget.tsx (1)
25-31
: Add dialog a11y to the trigger button.Expose modal state to assistive tech.
<Button onClick={() => setIsModalOpen(true)} variant="ghost" className="p-0 h-auto bg-transparent hover:bg-transparent" + aria-haspopup="dialog" + aria-expanded={isModalOpen} >app/components/viem-account-demo.tsx (2)
50-53
: Close the connect dialog only on success; surface errors.Currently the dialog closes even if
connect
fails. UseconnectAsync
andtry/catch
to avoid misleading UX.- const handleConnect = (connector: any) => { - connect({ connector }); - setIsDialogOpen(false); - }; + const handleConnect = async (connector: any) => { + try { + await connectAsync({ connector }); + setIsDialogOpen(false); + } catch (err) { + console.error("Wallet connect failed:", err); + } + };
27-34
: Avoid duplicate connectors (Injected + MetaMask).Including both often shows two essentially identical options. Trim to one to simplify UX.
- connectors: [injected(), metaMask()], + // Choose one to avoid duplicates in the modal: + connectors: [metaMask()],registry/default/payment-widget/utils/currencies.ts (2)
25-33
: Improve fetch resilience and error detail (status, body); disable caching for fresh routes.The generic error hides server details; also, these routes should not be cached by default in the browser.
const response = await fetch( `${RN_API_URL}/v2/currencies/${DEFAULT_CURRENCY}/conversion-routes?network=${network}`, { + cache: "no-store", headers: { "x-client-id": rnApiClientId, "Content-Type": "application/json", }, }, ); - if (!response.ok) { - throw new Error("Network response was not ok"); - } + if (!response.ok) { + let detail = ""; + try { + detail = await response.text(); + } catch {} + throw new Error( + `Failed to fetch conversion currencies: HTTP ${response.status} ${response.statusText}${detail ? ` - ${detail}` : ""}`, + ); + }Also applies to: 35-37
21-24
: Constrain the network type to the known union.Align
network
to the same union as inPaymentWidgetContextValue["paymentConfig"]["network"]
to catch typos at compile time.registry/default/payment-widget/components/payment-success.tsx (1)
13-15
: Use configured network and normalized symbol in receipts.Hardcoding
chain: "ethereum"
and using rawselectedCurrency.symbol
can produce incorrect receipts on testnets and verbose symbols (e.g., "ETH-sepolia"). Use context network andgetSymbolOverride
.-import type { BuyerInfo } from "../types/index"; -import type { ConversionCurrency } from "../utils/currencies"; +import type { BuyerInfo } from "../types/index"; +import { getSymbolOverride, type ConversionCurrency } from "../utils/currencies"; ... const { amountInUsd, recipientWallet, connectedWalletAddress, receiptInfo, uiConfig, + paymentConfig, } = usePaymentWidgetContext(); ... payment: { - chain: "ethereum", - currency: selectedCurrency.symbol, + chain: paymentConfig.network, + currency: getSymbolOverride(selectedCurrency.symbol), exchangeRate: 1, transactionHash: "", },Also applies to: 27-34, 45-51
registry/default/payment-widget/context/payment-widget-context.tsx (1)
3-3
: Stabilize context value withuseMemo
to reduce re-renders.This context is consumed widely; memoize its value.
-import { createContext, useContext, type ReactNode } from "react"; +import { createContext, useContext, useMemo, type ReactNode } from "react"; ... - const contextValue: PaymentWidgetContextValue = { - amountInUsd, - recipientWallet, - walletAccount, - connectedWalletAddress, - isWalletOverride, - paymentConfig: { - rnApiClientId: paymentConfig.rnApiClientId, - network: paymentConfig.network, - feeInfo: paymentConfig.feeInfo, - supportedCurrencies: paymentConfig.supportedCurrencies, - }, - uiConfig: { - showReceiptDownload: uiConfig?.showReceiptDownload ?? true, - showRequestScanUrl: uiConfig?.showRequestScanUrl ?? true, - }, - receiptInfo, - onSuccess, - onError, - }; + const contextValue: PaymentWidgetContextValue = useMemo( + () => ({ + amountInUsd, + recipientWallet, + walletAccount, + connectedWalletAddress, + isWalletOverride, + paymentConfig: { + rnApiClientId: paymentConfig.rnApiClientId, + network: paymentConfig.network, + feeInfo: paymentConfig.feeInfo, + supportedCurrencies: paymentConfig.supportedCurrencies, + }, + uiConfig: { + showReceiptDownload: uiConfig?.showReceiptDownload ?? true, + showRequestScanUrl: uiConfig?.showRequestScanUrl ?? true, + }, + receiptInfo, + onSuccess, + onError, + }), + [ + amountInUsd, + recipientWallet, + walletAccount, + connectedWalletAddress, + isWalletOverride, + paymentConfig.rnApiClientId, + paymentConfig.network, + paymentConfig.feeInfo, + paymentConfig.supportedCurrencies, + uiConfig?.showReceiptDownload, + uiConfig?.showRequestScanUrl, + receiptInfo, + onSuccess, + onError, + ], + );Also applies to: 84-103
registry/default/payment-widget/components/payment-modal.tsx (2)
56-63
: Guard against exceptions in user-provided onSuccess callback.Avoid unhandled rejections if
onSuccess
throws.setRequestId(requestId); setActiveStep("payment-success"); - await onSuccess?.(requestId, transactionReceipts); + try { + await onSuccess?.(requestId, transactionReceipts); + } catch (err) { + console.error("onSuccess callback failed:", err); + }
73-81
: Consider resetting state on any close, not only after success.Prevents stale selections when the user cancels mid‑flow. If you want current behavior, ignore.
- onOpenChange={(isOpen) => { - // reset modal state when closing from success step - if (!isOpen && activeStep === "payment-success") { - reset(); - } - handleModalOpenChange(isOpen); - }} + onOpenChange={(open) => { + if (!open) reset(); + handleModalOpenChange(open); +}}registry/default/payment-widget/utils/receipt.ts (2)
84-90
: Prevent callers from overriding issueDate.Spread user metadata first, then set
issueDate
.- const metadata: ReceiptMetadata = { - issueDate: new Date(), - ...params.metadata, - }; + const metadata: ReceiptMetadata = { + ...params.metadata, + issueDate: new Date(), + };
55-60
: Optional: format crypto amounts consistently.Apply locale formatting and cap fraction digits for readability.
-export const formatCryptoAmount = ( - amount: number, - currency: string, -): string => { - return `${amount} ${currency}`; -}; +export const formatCryptoAmount = (amount: number, currency: string): string => + `${Number(amount).toLocaleString("en-US", { maximumFractionDigits: 8 })} ${currency}`;registry/default/payment-widget/utils/payment.ts (3)
108-139
: Add timeout/abort to payout request to avoid hanging UI.Client fetches should fail fast and be cancelable.
export const createPayout = async ( rnApiClientId: string, params: PaymentParams, ): Promise<Response> => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 30_000); const { amountInUsd, payerWallet, recipientWallet, paymentCurrency, feeInfo, } = params; - const response = await fetch(`${RN_API_URL}/v2/payouts`, { + const response = await fetch(`${RN_API_URL}/v2/payouts`, { method: "POST", headers: { "x-client-id": rnApiClientId, "Content-Type": "application/json", + Accept: "application/json", }, body: JSON.stringify({ amount: amountInUsd, payerWallet: payerWallet, payee: recipientWallet, invoiceCurrency: "USD", paymentCurrency: paymentCurrency, feePercentage: feeInfo?.feePercentage || undefined, feeAddress: feeInfo?.feeAddress || undefined, customerInfo: params.customerInfo, }), - }); + signal: controller.signal, + }).finally(() => clearTimeout(timeout)); return response; };
61-80
: Number-to-BigInt conversion can be lossy for large values.If backend ever sends wei as a JS number > 2^53−1, precision is already lost.
Consider rejecting unsafe number inputs or requiring string/hex:
- Warn when
!Number.isSafeInteger(value)
.- Prefer string/hex in API.
82-106
: Optional: validate tx fields before sending.Validate
to
/data
have 0x prefix; fail fast with atransaction
error.public/r/payment-widget.json (1)
120-136
: Minor: tighten PaymentConfirmation error typing.Consider using the exported
isPaymentError
guard from utils/payment in the embedded component to avoid misclassification of unknown errors.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
app/favicon.ico
is excluded by!**/*.ico
package-lock.json
is excluded by!**/package-lock.json
public/file.svg
is excluded by!**/*.svg
public/globe.svg
is excluded by!**/*.svg
public/next.svg
is excluded by!**/*.svg
public/vercel.svg
is excluded by!**/*.svg
public/window.svg
is excluded by!**/*.svg
📒 Files selected for processing (46)
.gitignore
(1 hunks)README.md
(1 hunks)app/components/payment-widget-wrapper.tsx
(1 hunks)app/components/viem-account-demo.tsx
(1 hunks)app/globals.css
(1 hunks)app/layout.tsx
(1 hunks)app/page.tsx
(1 hunks)biome.json
(1 hunks)components.json
(1 hunks)components/ui/button.tsx
(1 hunks)components/ui/dialog.tsx
(1 hunks)components/ui/input.tsx
(1 hunks)components/ui/label.tsx
(1 hunks)components/ui/radio-group.tsx
(1 hunks)lib/utils.ts
(1 hunks)next.config.ts
(1 hunks)package.json
(1 hunks)postcss.config.mjs
(1 hunks)public/r/payment-widget.json
(1 hunks)public/r/registry.json
(1 hunks)public/r/requestnetwork.json
(1 hunks)registry.json
(1 hunks)registry/default/payment-widget/README.md
(1 hunks)registry/default/payment-widget/components/buyer-info-form.tsx
(1 hunks)registry/default/payment-widget/components/connection-handler.tsx
(1 hunks)registry/default/payment-widget/components/currency-select.tsx
(1 hunks)registry/default/payment-widget/components/disconnect-wallet.tsx
(1 hunks)registry/default/payment-widget/components/payment-confirmation.tsx
(1 hunks)registry/default/payment-widget/components/payment-modal.tsx
(1 hunks)registry/default/payment-widget/components/payment-success.tsx
(1 hunks)registry/default/payment-widget/components/receipt/receipt-template.tsx
(1 hunks)registry/default/payment-widget/components/receipt/styles.css
(1 hunks)registry/default/payment-widget/components/wallet-connect-modal.tsx
(1 hunks)registry/default/payment-widget/constants.ts
(1 hunks)registry/default/payment-widget/context/payment-widget-context.tsx
(1 hunks)registry/default/payment-widget/context/web3-context.tsx
(1 hunks)registry/default/payment-widget/hooks/use-payment.ts
(1 hunks)registry/default/payment-widget/payment-widget.tsx
(1 hunks)registry/default/payment-widget/payment-widget.types.ts
(1 hunks)registry/default/payment-widget/types/html2pdf.d.ts
(1 hunks)registry/default/payment-widget/types/index.ts
(1 hunks)registry/default/payment-widget/utils/currencies.ts
(1 hunks)registry/default/payment-widget/utils/payment.ts
(1 hunks)registry/default/payment-widget/utils/receipt.ts
(1 hunks)registry/default/payment-widget/utils/wagmi.ts
(1 hunks)tsconfig.json
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (24)
registry/default/payment-widget/components/connection-handler.tsx (1)
registry/default/payment-widget/components/wallet-connect-modal.tsx (1)
WalletConnectModal
(21-106)
app/page.tsx (2)
app/components/payment-widget-wrapper.tsx (1)
PaymentWidgetWrapper
(13-101)app/components/viem-account-demo.tsx (1)
ViemAccountDemo
(149-157)
components/ui/input.tsx (1)
lib/utils.ts (1)
cn
(4-6)
registry/default/payment-widget/utils/receipt.ts (1)
registry/default/payment-widget/types/index.ts (3)
BuyerInfo
(43-56)CompanyInfo
(28-41)ReceiptItem
(17-26)
registry/default/payment-widget/utils/currencies.ts (1)
registry/default/payment-widget/constants.ts (1)
RN_API_URL
(1-1)
registry/default/payment-widget/context/payment-widget-context.tsx (2)
registry/default/payment-widget/types/index.ts (3)
FeeInfo
(1-4)ReceiptInfo
(65-71)PaymentError
(6-9)registry/default/payment-widget/payment-widget.types.ts (1)
PaymentWidgetProps
(49-69)
app/components/payment-widget-wrapper.tsx (1)
registry/default/payment-widget/payment-widget.tsx (1)
PaymentWidget
(66-93)
registry/default/payment-widget/components/currency-select.tsx (2)
registry/default/payment-widget/utils/currencies.ts (3)
ConversionCurrency
(3-11)getConversionCurrencies
(21-42)getSymbolOverride
(44-51)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(112-122)
registry/default/payment-widget/components/buyer-info-form.tsx (1)
registry/default/payment-widget/types/index.ts (1)
BuyerInfo
(43-56)
registry/default/payment-widget/components/wallet-connect-modal.tsx (3)
registry/default/payment-widget/constants.ts (1)
ICONS
(2-14)components/ui/dialog.tsx (5)
Dialog
(112-112)DialogContent
(117-117)DialogHeader
(118-118)DialogTitle
(120-120)DialogDescription
(121-121)components/ui/button.tsx (1)
Button
(56-56)
components/ui/radio-group.tsx (1)
lib/utils.ts (1)
cn
(4-6)
registry/default/payment-widget/components/payment-confirmation.tsx (5)
registry/default/payment-widget/utils/currencies.ts (2)
ConversionCurrency
(3-11)getSymbolOverride
(44-51)registry/default/payment-widget/types/index.ts (2)
BuyerInfo
(43-56)PaymentError
(6-9)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(112-122)registry/default/payment-widget/hooks/use-payment.ts (1)
usePayment
(16-105)registry/default/payment-widget/utils/payment.ts (1)
executePayment
(146-197)
registry/default/payment-widget/payment-widget.tsx (6)
registry/default/payment-widget/context/payment-widget-context.tsx (2)
usePaymentWidgetContext
(112-122)PaymentWidgetProvider
(66-110)registry/default/payment-widget/constants.ts (1)
ICONS
(2-14)registry/default/payment-widget/components/payment-modal.tsx (1)
PaymentModal
(26-128)registry/default/payment-widget/components/connection-handler.tsx (1)
ConnectionHandler
(13-30)registry/default/payment-widget/payment-widget.types.ts (1)
PaymentWidgetProps
(49-69)registry/default/payment-widget/context/web3-context.tsx (1)
Web3Provider
(10-32)
registry/default/payment-widget/utils/payment.ts (2)
registry/default/payment-widget/types/index.ts (2)
FeeInfo
(1-4)PaymentError
(6-9)registry/default/payment-widget/constants.ts (1)
RN_API_URL
(1-1)
app/components/viem-account-demo.tsx (1)
app/components/payment-widget-wrapper.tsx (1)
PaymentWidgetWrapper
(13-101)
registry/default/payment-widget/components/payment-modal.tsx (8)
registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(112-122)registry/default/payment-widget/utils/currencies.ts (1)
ConversionCurrency
(3-11)registry/default/payment-widget/types/index.ts (1)
BuyerInfo
(43-56)registry/default/payment-widget/components/disconnect-wallet.tsx (1)
DisconnectWallet
(5-28)registry/default/payment-widget/components/currency-select.tsx (1)
CurrencySelect
(19-134)registry/default/payment-widget/components/buyer-info-form.tsx (1)
BuyerInfoForm
(15-265)registry/default/payment-widget/components/payment-confirmation.tsx (1)
PaymentConfirmation
(25-185)registry/default/payment-widget/components/payment-success.tsx (1)
PaymentSuccess
(22-141)
registry/default/payment-widget/hooks/use-payment.ts (2)
registry/default/payment-widget/utils/payment.ts (6)
SendTransactionFunction
(49-49)TxParams
(43-47)WaitForTransactionFunction
(51-53)PaymentParams
(5-24)PaymentResponse
(141-144)executePayment
(146-197)registry/default/payment-widget/types/index.ts (1)
PaymentError
(6-9)
registry/default/payment-widget/context/web3-context.tsx (1)
registry/default/payment-widget/utils/wagmi.ts (1)
getWagmiConfig
(18-61)
registry/default/payment-widget/payment-widget.types.ts (1)
registry/default/payment-widget/types/index.ts (3)
FeeInfo
(1-4)ReceiptInfo
(65-71)PaymentError
(6-9)
registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
registry/default/payment-widget/utils/receipt.ts (4)
ReceiptData
(24-36)formatReceiptDate
(62-68)formatCryptoAmount
(55-60)formatUSDAmount
(46-53)
components/ui/label.tsx (1)
lib/utils.ts (1)
cn
(4-6)
components/ui/button.tsx (1)
lib/utils.ts (1)
cn
(4-6)
registry/default/payment-widget/components/payment-success.tsx (5)
registry/default/payment-widget/utils/currencies.ts (1)
ConversionCurrency
(3-11)registry/default/payment-widget/types/index.ts (1)
BuyerInfo
(43-56)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(112-122)registry/default/payment-widget/utils/receipt.ts (3)
CreateReceiptParams
(70-82)generateReceiptNumber
(38-44)createReceipt
(84-100)registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
ReceiptPDFTemplate
(10-195)
components/ui/dialog.tsx (1)
lib/utils.ts (1)
cn
(4-6)
🔇 Additional comments (14)
biome.json (4)
3-7
: VCS settings look good.Enables Git integration and respects ignore files. No changes needed.
12-16
: Formatter config is sensible.2‑space indentation with spaces is consistent with common JS/TS style.
2-2
: Schema matches Biome version — no action required. biome.json $schema = 2.2.0 and package.json @biomejs/biome = 2.2.0.
30-36
: Confirmassist.actions.source.organizeImports
support; add top‑level fallback.Found @biomejs/biome v2.2.0 in package.json; the
npx ... --write=false
run failed because--write
is a flag — re-run with the corrected check command below. (biomejs.dev)assist.actions.source.organizeImports is supported; add a top‑level fallback
organizeImports.enabled
so import-sorting still applies if theassist
block is not picked up. (biomejs.dev)Recommended diff (unchanged):
+ "organizeImports": { "enabled": true }, "assist": { "actions": { "source": { "organizeImports": "on" } } }
Run this to validate (omit
--write=false
— use formatter/linter flags to run only assists):#!/bin/bash set -euo pipefail # Show installed Biome version jq -r '.devDependencies["@biomejs/biome"], .dependencies["@biomejs/biome"]' package.json 2>/dev/null | grep -v null || true # Validate assist config (run checks without formatting/linting) npx -y @biomejs/biome@latest check --config-path biome.json --formatter-enabled=false --linter-enabled=false . || truepostcss.config.mjs (1)
1-3
: LGTM.Tailwind v4 PostCSS plugin is correctly declared.
public/r/requestnetwork.json (1)
1-5
: LGTM.Minimal registry item looks correct against shadcn schema.
package.json (1)
5-12
: Confirm Turbopack build stability or fall back tonext build
.Production Turbopack can still surface edge cases with some plugins/libraries.
If you hit issues, switch to:
- "build": "next build --turbopack", + "build": "next build",app/layout.tsx (1)
20-34
: LGTM — clean Next.js root layout.Fonts, metadata, and global styles are wired correctly. No issues spotted.
lib/utils.ts (1)
4-6
: LGTM — standard shadcn cn helper.components/ui/label.tsx (1)
9-24
: LGTM — idiomatic shadcn label wrapper.registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
43-44
: Date type safety.Ensure
receipt.metadata.issueDate
is aDate
. If it’s a string (API), parse toDate
before callingformatReceiptDate
.registry/default/payment-widget/hooks/use-payment.ts (1)
40-44
: No change required — Wagmi v2 supports both APIs. useConnect returns a status ('idle'|'pending'|'error'|'success') with derived booleans (isPending, etc.) and pendingConnector; useSendTransaction exposes both sendTransaction and sendTransactionAsync, so keeping sendTransactionAsync here is compatible with wagmi v2.components/ui/dialog.tsx (1)
32-54
: LGTM – accessible, well‑structured Radix wrappers.Good focus handling, overlay, and close button with sr‑only label.
public/r/payment-widget.json (1)
8-14
: Verify/update dependency pins in public/r/payment-widget.json (lines 8–14)Checked latest stable versions as of Sep 17, 2025 — update pins or confirm compatibility before publishing.
- wagmi: ^2.12.29 → latest v2 2.16.9 — update/test.
- viem: ^2.21.53 → latest v2 2.37.3 — update/test.
- @tanstack/react-query: ^5.64.1 → latest v5 5.85.9 — update/test.
- lucide-react: ^0.542.0 → latest 0.542.0 — OK.
- html2pdf.js: ^0.12.0 in repo vs npm latest 0.10.2 — confirm intended version (typo/private fork) and fix.
- react-hook-form: ^7.0.0 — verify current stable and compatibility.
registry/default/payment-widget/context/payment-widget-context.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (3)
registry/default/payment-widget/components/payment-confirmation.tsx (1)
38-41
: Good fix: walletAccount is now forwarded to usePayment.This unblocks the external pre-connected wallet path. Thanks for addressing it.
registry/default/payment-widget/utils/payment.ts (1)
177-188
: Good fix: require non‑empty transactions array.
Prevents false positives when backend returns[]
. This addresses the earlier review note.app/components/viem-account-demo.tsx (1)
83-102
: LGTM: isPending and connector.uid usage is correct in Wagmi v2.Both patterns are documented; no change needed.
Refs: useConnect includes isPending; Connect Wallet guide keys by connector.uid. (wagmi.sh)
Also applies to: 104-108
🧹 Nitpick comments (18)
registry/default/payment-widget/components/receipt/styles.module.css (6)
1-11
: Prevent width overflow by using border-box on the containerCurrent max-width 800px + 20px padding on each side yields an 840px footprint. Use border-box so padding is included within the max-width.
.receipt-container { background-color: #ffffff; padding: 20px; max-width: 800px; margin: 0 auto; + box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 12px; line-height: 1.4; color: #2d3748; }
179-182
: Align numbers consistently with tabular numeralsEnsure amounts line up vertically and render consistently across platforms.
.items-table td.right { text-align: right; - font-family: "Monaco", "Courier New", monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-variant-numeric: tabular-nums lining-nums; + font-feature-settings: "tnum" 1, "lnum" 1; } .items-table .amount { font-weight: 600; + font-variant-numeric: tabular-nums lining-nums; + font-feature-settings: "tnum" 1, "lnum" 1; } .total-amount { - font-family: "Monaco", "Courier New", monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: inherit; + font-variant-numeric: tabular-nums lining-nums; + font-feature-settings: "tnum" 1, "lnum" 1; }Also applies to: 192-194, 231-234
72-78
: Avoid awkward PDF page splits (html2pdf) with page-break guardsAdd break-inside/page-break-inside to keep boxes and table rows together across pages.
.info-box { flex: 1; border: 1px solid #e2e8f0; border-radius: 6px; padding: 12px; background-color: #f7fafc; + break-inside: avoid; + page-break-inside: avoid; } .items-table { width: 100%; border-collapse: collapse; border: 1px solid #e2e8f0; margin-bottom: 16px; font-size: 11px; } .items-table td { border: 1px solid #e2e8f0; padding: 6px; vertical-align: top; + break-inside: avoid; + page-break-inside: avoid; } .totals-box { background-color: #f7fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 12px; min-width: 240px; + break-inside: avoid; + page-break-inside: avoid; }Also applies to: 142-148, 169-173, 202-208
184-190
: Simplify row striping via nth-child selectors (no extra classes needed)Removes the need to compute row-even/row-odd in markup.
-.items-table .row-even { - background-color: #ffffff; -} - -.items-table .row-odd { - background-color: #f7fafc; -} +.items-table tr:nth-child(even) { + background-color: #ffffff; +} +.items-table tr:nth-child(odd) { + background-color: #f7fafc; +}
96-102
: Improve long-hash wrapping and monospace fallbackBroaden the monospace stack and enable modern wrapping for extreme lengths.
.wallet-address { font-size: 10px; color: #718096; - font-family: "Monaco", "Courier New", monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; margin-bottom: 6px; - word-break: break-all; + word-break: break-all; + overflow-wrap: anywhere; } .transaction-hash { - font-family: "Monaco", "Courier New", monospace; - word-break: break-all; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + word-break: break-all; + overflow-wrap: anywhere; }Also applies to: 137-140
236-243
: Preserve note formattingKeep author-entered line breaks in notes.
.notes-section { border-top: 1px solid #e2e8f0; font-size: 11px; color: #4a5568; background-color: #f7fafc; padding: 8px 12px; border-radius: 4px; + white-space: pre-wrap; }
registry/default/payment-widget/components/payment-confirmation.tsx (4)
43-45
: Event typing and preventDefault are unnecessary for onClick.handleExecutePayment is wired to a button onClick; no default form submit occurs, and the React.FormEvent type is incorrect for a click handler.
- const handleExecutePayment = async (e: React.FormEvent) => { - e.preventDefault(); + const handleExecutePayment = async () => {
41-41
: Format USD amount for readability.Render 2 decimals and thousands separators.
const [localError, setLocalError] = useState<string | null>(null); + const formattedUsd = Number(amountInUsd ?? 0).toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); @@ - <div className="text-sm font-medium text-foreground"> - ${amountInUsd} - </div> + <div className="text-sm font-medium text-foreground"> + ${formattedUsd} + </div>Also applies to: 126-129
151-163
: A11y: mark error region as an alert.Assistive tech won’t announce updates without live region semantics.
- {localError && ( - <div className="p-4 bg-destructive/10 border border-destructive/20 rounded-lg"> + {localError && ( + <div + className="p-4 bg-destructive/10 border border-destructive/20 rounded-lg" + role="alert" + aria-live="assertive" + aria-atomic="true" + >
121-124
: Theme consistency: avoid hard-coded blue; use design tokens.Align with the “From” badge styling for better theming and dark mode support.
- <div className="w-16 h-16 bg-blue-500 rounded-full flex items-center justify-center text-white font-semibold text-lg"> + <div className="w-16 h-16 bg-primary rounded-full flex items-center justify-center text-primary-foreground font-semibold text-lg">registry/default/payment-widget/utils/payment.ts (3)
55-59
: Harden the type guard to ensureerror.error
is an Error instance.
This prevents misclassification of arbitrary objects asPaymentError
.-export const isPaymentError = (error: any): error is PaymentError => { - return ( - error && typeof error === "object" && "type" in error && "error" in error - ); -}; +export const isPaymentError = (error: unknown): error is PaymentError => { + return ( + !!error && + typeof error === "object" && + "type" in error && + "error" in error && + // @ts-expect-error runtime check + (error as any).error instanceof Error + ); +};
120-136
: AddAccept: application/json
header; consider idempotency key and abort/timeout.
Improves content negotiation; idempotency prevents duplicate payouts on retries; abort/timeout avoids hung UX.const response = await fetch(`${RN_API_URL}/v2/payouts`, { method: "POST", headers: { "x-client-id": rnApiClientId, "Content-Type": "application/json", + "Accept": "application/json", + // "Idempotency-Key": "<stable-unique-key>", // if API supports it }, body: JSON.stringify({ amount: amountInUsd, payerWallet: payerWallet, payee: recipientWallet, invoiceCurrency: "USD", paymentCurrency: paymentCurrency, feePercentage: feeInfo?.feePercentage || undefined, feeAddress: feeInfo?.feeAddress || undefined, customerInfo: params.customerInfo, }), });
- Follow-up: pass an AbortSignal (via optional param) and use a controller to enforce a reasonable timeout (e.g., 30s).
141-144
: ExposepaymentReference
inPaymentResponse
for downstream UX (receipts/links).
Many UIs need it for confirmations and PDFs.export interface PaymentResponse { requestId: string; + paymentReference: string; transactionReceipts: TransactionReceipt[]; }
- return { requestId: data.requestId, transactionReceipts }; + return { + requestId: data.requestId, + paymentReference: data.paymentReference, + transactionReceipts, + };Please confirm no consumers rely on the current shape before changing the contract.
Also applies to: 184-184
app/components/viem-account-demo.tsx (3)
7-7
: Type the connector and close the dialog only after a successful async connect.Use the exported Connector type and connectAsync to await the result, then handle errors. This avoids prematurely closing the dialog and improves UX.
-import { useAccount, useConnect, useDisconnect, useWalletClient } from "wagmi"; +import { useAccount, useConnect, useDisconnect, useWalletClient, type Connector } from "wagmi"; @@ - const { connectors, connect, isPending } = useConnect(); + const { connectors, connect, connectAsync, isPending } = useConnect(); @@ - const handleConnect = (connector: any) => { - connect({ connector }); - setIsDialogOpen(false); - }; + const handleConnect = async (connector: Connector) => { + try { + await connectAsync({ connector }); + setIsDialogOpen(false); + } catch (error) { + console.error("Wallet connection failed:", error); + } + };Reference: useConnect exposes connectAsync; Connector type is re-exported. (wagmi.sh)
Also applies to: 39-47
83-91
: Optionally gate buttons by connector readiness to avoid listing unsupported wallets.Consider disabling per-connector when provider isn’t available (e.g., via connector.getProvider()) so users don’t click wallets they don’t have installed.
Example pattern (from docs): compute a ready boolean per connector and disable accordingly. (wagmi.sh)
25-32
: Optional: include WalletConnect for broader coverage (mobile, QR).If you want this demo to work without an injected wallet, add walletConnect with a projectId.
-import { injected, metaMask } from "wagmi/connectors"; +import { injected, metaMask, walletConnect } from "wagmi/connectors"; @@ - connectors: [injected(), metaMask()], + connectors: [ + injected(), + // requires NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID + walletConnect({ projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID! }), + metaMask(), + ],Docs show this setup in the Connect Wallet guide. (wagmi.sh)
registry/default/payment-widget/components/payment-success.tsx (2)
35-36
: Stabilize receipt number across re-renders.
generateReceiptNumber()
is called during render; the number can change between renders. Generate it once.const receiptRef = useRef<HTMLDivElement>(null); + const receiptNumberRef = useRef( + receiptInfo?.receiptNumber ?? generateReceiptNumber(), + );
55-60
: Use stable receipt number and format USD in notes.Ensures consistent filename/content and better UX for currency formatting.
- receiptNumber: receiptInfo?.receiptNumber - ? receiptInfo.receiptNumber - : generateReceiptNumber(), - notes: `Payment processed through Request Network for ${amountInUsd} USD`, + receiptNumber: receiptNumberRef.current, + notes: `Payment processed through Request Network for ${typeof amountInUsd === "number" ? amountInUsd.toLocaleString("en-US", { style: "currency", currency: "USD" }) : amountInUsd}`,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
app/components/payment-widget-wrapper.tsx
(1 hunks)app/components/viem-account-demo.tsx
(1 hunks)app/page.tsx
(1 hunks)package.json
(1 hunks)public/r/registry.json
(1 hunks)registry.json
(1 hunks)registry/default/payment-widget/README.md
(1 hunks)registry/default/payment-widget/components/payment-confirmation.tsx
(1 hunks)registry/default/payment-widget/components/payment-modal.tsx
(1 hunks)registry/default/payment-widget/components/payment-success.tsx
(1 hunks)registry/default/payment-widget/components/receipt/receipt-template.tsx
(1 hunks)registry/default/payment-widget/components/receipt/styles.module.css
(1 hunks)registry/default/payment-widget/constants.ts
(1 hunks)registry/default/payment-widget/context/payment-widget-context.tsx
(1 hunks)registry/default/payment-widget/context/web3-context.tsx
(1 hunks)registry/default/payment-widget/hooks/use-payment.ts
(1 hunks)registry/default/payment-widget/payment-widget.tsx
(1 hunks)registry/default/payment-widget/utils/payment.ts
(1 hunks)registry/default/payment-widget/utils/receipt.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- registry/default/payment-widget/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
- registry/default/payment-widget/hooks/use-payment.ts
- registry/default/payment-widget/constants.ts
- package.json
- registry/default/payment-widget/context/payment-widget-context.tsx
- app/page.tsx
- registry/default/payment-widget/components/receipt/receipt-template.tsx
- registry/default/payment-widget/context/web3-context.tsx
- registry/default/payment-widget/components/payment-modal.tsx
- registry/default/payment-widget/payment-widget.tsx
- app/components/payment-widget-wrapper.tsx
- registry/default/payment-widget/utils/receipt.ts
- public/r/registry.json
- registry.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-17T09:30:27.483Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: app/components/viem-account-demo.tsx:45-46
Timestamp: 2025-09-17T09:30:27.483Z
Learning: Wagmi v2 useConnect hook returns isPending (derived from TanStack Query mutation status) and connectors have .uid property as shown in official documentation at https://wagmi.sh/react/guides/connect-wallet
Applied to files:
app/components/viem-account-demo.tsx
🧬 Code graph analysis (4)
registry/default/payment-widget/components/payment-success.tsx (5)
registry/default/payment-widget/utils/currencies.ts (1)
ConversionCurrency
(3-11)registry/default/payment-widget/types/index.ts (1)
BuyerInfo
(43-56)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(130-140)registry/default/payment-widget/utils/receipt.ts (3)
CreateReceiptParams
(70-82)generateReceiptNumber
(38-44)createReceipt
(84-100)registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
ReceiptPDFTemplate
(10-195)
app/components/viem-account-demo.tsx (1)
app/components/payment-widget-wrapper.tsx (1)
PaymentWidgetWrapper
(11-90)
registry/default/payment-widget/utils/payment.ts (2)
registry/default/payment-widget/types/index.ts (2)
FeeInfo
(1-4)PaymentError
(6-9)registry/default/payment-widget/constants.ts (1)
RN_API_URL
(1-2)
registry/default/payment-widget/components/payment-confirmation.tsx (5)
registry/default/payment-widget/utils/currencies.ts (2)
ConversionCurrency
(3-11)getSymbolOverride
(44-51)registry/default/payment-widget/types/index.ts (2)
BuyerInfo
(43-56)PaymentError
(6-9)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(130-140)registry/default/payment-widget/hooks/use-payment.ts (1)
usePayment
(16-111)registry/default/payment-widget/utils/payment.ts (1)
executePayment
(146-197)
🔇 Additional comments (3)
registry/default/payment-widget/components/receipt/styles.module.css (1)
150-167
: Sanity-check header column widths for real amounts80px for the right header can truncate long crypto amounts/symbols at common DPI scales in PDFs. Consider 96–112px or let the TDs control width.
app/components/viem-account-demo.tsx (1)
141-149
: Provider order matches docs (WagmiProvider wrapping QueryClientProvider).Setup is consistent with the official guide. LGTM.
Reference: Connect Wallet guide, step 2. (wagmi.sh)
registry/default/payment-widget/components/payment-success.tsx (1)
90-141
: Success UI looks clean and clear.Good use of conditional actions (download + Request Scan) and accessible external link with rel="noopener noreferrer".
If you plan to show on‑screen tx details as well, consider echoing the tx hash here for quick copy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
registry/default/payment-widget/components/payment-success.tsx (2)
113-126
: Good fix: off‑screen render prevents 0×0 html2canvas layout.Switching to an absolutely positioned, off‑screen container (with aria-hidden) addresses blank/truncated PDFs. Nice.
37-39
: Populate real exchangeRate (remove placeholder 1).Receipts should reflect the conversion actually used. Compute it from totals (USD/crypto) or plumb the rate used during quote/confirmation.
Apply this diff to derive a sane default from totals:
@@ - const receiptParams: CreateReceiptParams = { + const computedExchangeRate = + (receiptInfo?.totals?.total ?? 0) > 0 + ? (receiptInfo?.totals?.totalUSD ?? 0) / (receiptInfo?.totals?.total ?? 0) + : 0; + + const receiptParams: CreateReceiptParams = { @@ - payment: { + payment: { chain: network, currency: selectedCurrency.symbol, - exchangeRate: 1, + exchangeRate: computedExchangeRate, transactionHash: txHash, },If you already have a quoted FX rate in state (e.g., from pricing/quote APIs), prefer wiring that exact rate into payment.exchangeRate.
Also applies to: 48-53
🧹 Nitpick comments (21)
registry/default/payment-widget/components/receipt/styles.css (6)
96-102
: Use a broader, modern monospace stack.Improve cross‑platform rendering for hashes/amounts.
-.wallet-address { +.wallet-address { font-size: 10px; color: #718096; - font-family: "Monaco", "Courier New", monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; margin-bottom: 6px; word-break: break-all; } -.items-table td.right { +.items-table td.right { text-align: right; - font-family: "Monaco", "Courier New", monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } -.total-amount { +.total-amount { - font-family: "Monaco", "Courier New", monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: inherit; }Also applies to: 179-182, 231-234
96-102
: Preferoverflow-wrap:anywhere
overword-break:break-all
for long hashes/addresses.Preserves readability while still preventing overflow.
.wallet-address { font-size: 10px; color: #718096; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; margin-bottom: 6px; - word-break: break-all; + overflow-wrap: anywhere; } .transaction-hash { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - word-break: break-all; + overflow-wrap: anywhere; }Also applies to: 137-140
159-167
: Avoid fixed column widths for table headers.
width: 40px/80px
risks truncation with localization/long labels. Usemin-width
or let content drive width.-.items-table th.center { +.items-table th.center { text-align: center; - width: 40px; + min-width: 40px; } -.items-table th.right { +.items-table th.right { text-align: right; - width: 80px; + min-width: 80px; }
184-190
: Leverage selectors instead of row classes for zebra striping.Reduces coupling to markup logic.
-.items-table .row-even { - background-color: #ffffff; -} -.items-table .row-odd { - background-color: #f7fafc; -} +.items-table tbody tr:nth-child(even) { background-color: #ffffff; } +.items-table tbody tr:nth-child(odd) { background-color: #f7fafc; }
236-244
: Add PDF/print safeguards: page breaks and color fidelity.Helps html2pdf/jsPDF avoid splitting rows/boxes and preserves background colors.
.notes-section { border-top: 1px solid #e2e8f0; font-size: 11px; color: #4a5568; background-color: #f7fafc; padding: 8px 12px; border-radius: 4px; } + +/* PDF/print tweaks */ +@media print { + .receipt-container { -webkit-print-color-adjust: exact; print-color-adjust: exact; } + .items-table { page-break-inside: auto; } + .items-table tr { page-break-inside: avoid; page-break-after: auto; } + .totals-box, .info-box { break-inside: avoid; } + .receipt-header { break-inside: avoid; } +}
1-11
: Consider extracting a color palette to CSS variables.Eases theming and consistency across the widget.
Example:
:root { --rcp-bg: #ffffff; --rcp-text: #2d3748; --rcp-muted: #4a5568; --rcp-subtle: #718096; --rcp-border: #e2e8f0; --rcp-soft: #f7fafc; --rcp-soft-2: #edf2f7; } .receipt-container { background-color: var(--rcp-bg); color: var(--rcp-text); }registry/default/payment-widget/components/payment-success.tsx (5)
74-83
: Await the html2pdf promise to surface errors to catch().Without awaiting, failures in .save() won’t be caught reliably.
Apply this diff:
- html2pdf() + await html2pdf() .set({ margin: 1, filename: `receipt-${receiptParams.metadata?.receiptNumber || "payment"}.pdf`, image: { type: "jpeg", quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: "in", format: "a4", orientation: "portrait" }, }) .from(element) .save();
10-10
: Prevent double-clicks; show progress while generating PDF.Minor UX: disable the button during generation to avoid duplicate downloads.
Apply this diff:
@@ -import { useRef } from "react"; +import { useRef, useState } from "react"; @@ - const receiptRef = useRef<HTMLDivElement>(null); + const receiptRef = useRef<HTMLDivElement>(null); + const [downloading, setDownloading] = useState(false); @@ - const handleDownloadReceipt = async () => { + const handleDownloadReceipt = async () => { try { + if (downloading) return; + setDownloading(true); const element = receiptRef.current; if (!element) { console.error("Receipt element not found"); - return; + return; } @@ - await html2pdf() + await html2pdf() .set({ margin: 1, filename: `receipt-${receiptParams.metadata?.receiptNumber || "payment"}.pdf`, image: { type: "jpeg", quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: "in", format: "a4", orientation: "portrait" }, }) .from(element) .save(); } catch (error) { console.error("Failed to download receipt:", error); alert("Failed to download receipt. Please try again."); } + finally { + setDownloading(false); + } }; @@ - <Button onClick={handleDownloadReceipt} className="w-full"> + <Button onClick={handleDownloadReceipt} className="w-full" disabled={downloading}> <Download className="w-4 h-4 mr-2" /> - Download Receipt PDF + {downloading ? "Generating PDF..." : "Download Receipt PDF"} </Button>Also applies to: 37-38, 64-88, 109-113
60-60
: Format USD consistently in notes.Use 2‑dp formatting to avoid “floating” decimals in the receipt note.
Apply this diff:
- notes: `Payment processed through Request Network for ${amountInUsd} USD`, + notes: `Payment processed through Request Network for ${Number(amountInUsd).toFixed(2)} USD`,
93-104
: Announce success to assistive tech.Add an ARIA status so screen readers announce completion.
Apply this diff:
- <div className="flex flex-col items-center space-y-6 p-6"> + <div className="flex flex-col items-center space-y-6 p-6" role="status" aria-live="polite">
16-21
: Validate txHash format and ensure currency ↔ chain alignment
- txHash is taken from transactionReceipts[last].transactionHash (registry/default/payment-widget/components/payment-modal.tsx ≈ lines 61–64). Add a guard so only a non-empty value matching /^0x[a-fA-F0-9]{64}$/ is passed into PaymentSuccess/createReceipt; otherwise omit the transactionHash or log/report the invalid value.
- PaymentSuccess sets receipt.payment.chain = paymentConfig.network and receipt.payment.currency = selectedCurrency.symbol (registry/default/payment-widget/components/payment-success.tsx ≈ lines 48–53). Ensure selectedCurrency originates from the same network (currency-select fetches conversionCurrencies with network in its query key) — either assert selectedCurrency.network === paymentConfig.network at selection time or prefer using selectedCurrency.network when present to avoid mismatched “Currency on Chain” lines in the PDF.
registry/default/payment-widget/utils/chains.ts (1)
10-27
: Type the return and centralize mapping for clearer errors and safer inputsSwap the switch for a typed map and declare the return type. This tightens DX, avoids duplicated cases, and produces a helpful supported list in errors.
Apply this diff:
+import type { Chain } from "viem"; export const getChainFromNetwork = (network: string) => { - switch (network.toLowerCase()) { - case "mainnet": - return mainnet; - case "arbitrum": - return arbitrum; - case "base": - return base; - case "optimism": - return optimism; - case "polygon": - return polygon; - case "sepolia": - return sepolia; - default: - throw new Error(`Unsupported network: ${network}`); - } + const map: Record<string, Chain> = { + mainnet, + arbitrum, + base, + optimism, + polygon, + sepolia, + }; + const key = network.toLowerCase(); + const chain = map[key]; + if (!chain) { + throw new Error(`Unsupported network: ${network}. Supported: ${Object.keys(map).join(", ")}`); + } + return chain; };registry/default/payment-widget/components/payment-confirmation.tsx (5)
46-48
: Don’t silently no-op when wallet is missing; show a clear errorEarly return gives no feedback. Set an explicit error so users know why Pay didn’t proceed.
- if (!connectedWalletAddress) return; + if (!connectedWalletAddress) { + setLocalError("Wallet not connected."); + return; + }
175-183
: Disable Pay when not connectedPrevents repeated clicks and aligns UI state with requirements.
- <Button + <Button type="button" onClick={handleExecutePayment} className="flex-1" - disabled={isExecuting} + disabled={isExecuting || !connectedWalletAddress} >
50-74
: Avoid duplicating source of truth for payerWalletThe hook ignores the component-supplied payerWallet and uses its own address. Remove the field here and change the hook’s execute signature to accept Omit<PaymentParams, "payerWallet"> to avoid confusion.
In this file:
const { requestId, transactionReceipts } = await executePayment( rnApiClientId, { - payerWallet: connectedWalletAddress, amountInUsd, recipientWallet, paymentCurrency: selectedCurrency.id, feeInfo, customerInfo: {
And in hooks/use-payment.ts, update the execute signature and its call-site to construct payerWallet internally (see my comment on that file).
151-163
: A11y: mark error region as an alertAnnounce errors to screen readers.
- {localError && ( - <div className="p-4 bg-destructive/10 border border-destructive/20 rounded-lg"> + {localError && ( + <div + role="alert" + aria-live="polite" + className="p-4 bg-destructive/10 border border-destructive/20 rounded-lg" + >
43-45
: Remove unnecessary preventDefaultThe handler is bound to a button type="button"; no form submit occurs.
- const handleExecutePayment = async (e: React.FormEvent) => { - e.preventDefault(); + const handleExecutePayment = async () => {registry/default/payment-widget/hooks/use-payment.ts (2)
81-85
: Make execute accept params without payerWallet; set it internallyThis eliminates duplicate inputs and enforces a single source of truth.
- const execute = async ( - rnApiClientId: string, - params: PaymentParams, - ): Promise<PaymentResponse> => { + const execute = async ( + rnApiClientId: string, + params: Omit<PaymentParams, "payerWallet">, + ): Promise<PaymentResponse> => { @@ return await executePayment({ rnApiClientId, - paymentParams: { + paymentParams: { amountInUsd: params.amountInUsd, payerWallet: address, recipientWallet: params.recipientWallet, paymentCurrency: params.paymentCurrency, feeInfo: params.feeInfo, customerInfo: params.customerInfo, },Also applies to: 100-111
22-35
: Optional: narrow the network type for compile-time safetyConsider typing network as a union (e.g., type SupportedNetwork = "mainnet" | "arbitrum" | "base" | "optimism" | "polygon" | "sepolia") and reusing it from utils/chains to prevent invalid inputs.
registry/default/payment-widget/utils/payment.ts (2)
82-84
: Fix log message; don’t warn about “defaulting to 0” when throwingThe message is misleading now that we fail fast.
- console.warn("Unknown value format, defaulting to 0:", value); - throw new Error("Unknown value format"); + throw new Error(`Unknown tx.value format: ${String(value)}`);
1-1
: Type-only import for TransactionReceiptAvoids pulling runtime code into bundles.
-import { TransactionReceipt } from "viem"; +import type { TransactionReceipt } from "viem";
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
public/r/registry.json
(1 hunks)registry.json
(1 hunks)registry/default/payment-widget/components/payment-confirmation.tsx
(1 hunks)registry/default/payment-widget/components/payment-modal.tsx
(1 hunks)registry/default/payment-widget/components/payment-success.tsx
(1 hunks)registry/default/payment-widget/components/receipt/receipt-template.tsx
(1 hunks)registry/default/payment-widget/components/receipt/styles.css
(1 hunks)registry/default/payment-widget/hooks/use-payment.ts
(1 hunks)registry/default/payment-widget/utils/chains.ts
(1 hunks)registry/default/payment-widget/utils/payment.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- public/r/registry.json
- registry/default/payment-widget/components/payment-modal.tsx
- registry.json
- registry/default/payment-widget/components/receipt/receipt-template.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-17T12:08:58.186Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/components/payment-confirmation.tsx:31-41
Timestamp: 2025-09-17T12:08:58.186Z
Learning: In the payment widget context (registry/default/payment-widget/context/payment-widget-context.tsx), connectedWalletAddress is properly derived as `walletAccount ? walletAccount.account?.address : address` which handles both custom WalletClient scenarios and wagmi-managed wallets, so components should use connectedWalletAddress from context rather than address from usePayment hook.
Applied to files:
registry/default/payment-widget/hooks/use-payment.ts
registry/default/payment-widget/components/payment-confirmation.tsx
📚 Learning: 2025-09-17T12:08:58.186Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/components/payment-confirmation.tsx:31-41
Timestamp: 2025-09-17T12:08:58.186Z
Learning: In the payment widget context (registry/default/payment-widget/context/payment-widget-context.tsx), connectedWalletAddress is properly derived as `walletAccount?.account?.address || address` which handles both custom WalletClient scenarios and wagmi-managed wallets, so components should use connectedWalletAddress from context rather than address from usePayment hook.
Applied to files:
registry/default/payment-widget/hooks/use-payment.ts
registry/default/payment-widget/components/payment-confirmation.tsx
📚 Learning: 2025-09-17T12:11:36.723Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/utils/payment.ts:82-106
Timestamp: 2025-09-17T12:11:36.723Z
Learning: The Request Network API handles transaction input validation (to/data fields), so client-side validation in the payment widget is not needed.
Applied to files:
registry/default/payment-widget/utils/payment.ts
🧬 Code graph analysis (4)
registry/default/payment-widget/hooks/use-payment.ts (3)
registry/default/payment-widget/utils/chains.ts (1)
getChainFromNetwork
(10-27)registry/default/payment-widget/utils/payment.ts (6)
SendTransactionFunction
(49-49)TxParams
(43-47)WaitForTransactionFunction
(51-53)PaymentParams
(5-24)PaymentResponse
(149-152)executePayment
(154-205)registry/default/payment-widget/types/index.ts (1)
PaymentError
(6-9)
registry/default/payment-widget/components/payment-confirmation.tsx (5)
registry/default/payment-widget/utils/currencies.ts (2)
ConversionCurrency
(3-11)getSymbolOverride
(44-51)registry/default/payment-widget/types/index.ts (2)
BuyerInfo
(43-56)PaymentError
(6-9)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(130-140)registry/default/payment-widget/hooks/use-payment.ts (1)
usePayment
(22-124)registry/default/payment-widget/utils/payment.ts (1)
executePayment
(154-205)
registry/default/payment-widget/components/payment-success.tsx (5)
registry/default/payment-widget/utils/currencies.ts (1)
ConversionCurrency
(3-11)registry/default/payment-widget/types/index.ts (1)
BuyerInfo
(43-56)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(130-140)registry/default/payment-widget/utils/receipt.ts (3)
CreateReceiptParams
(70-82)generateReceiptNumber
(38-44)createReceipt
(84-100)registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
ReceiptPDFTemplate
(11-196)
registry/default/payment-widget/utils/payment.ts (2)
registry/default/payment-widget/types/index.ts (2)
FeeInfo
(1-4)PaymentError
(6-9)registry/default/payment-widget/constants.ts (1)
RN_API_URL
(1-2)
🔇 Additional comments (2)
registry/default/payment-widget/components/receipt/styles.css (1)
120-127
: Contrast check — .payment-info OK; a muted color fails WCAGregistry/default/payment-widget/components/receipt/styles.css (lines 120–127): .payment-info (#2d3748 on #edf2f7) contrast 10.64:1 — passes (>=4.5:1 for 11px).
Failing pair found: #718096 on #f7fafc → 3.83:1 (<4.5). If that color is used for normal/small text on soft backgrounds, darken it (e.g. #4a5568) or increase font-size/weight.
registry/default/payment-widget/utils/payment.ts (1)
183-196
: Good: require at least one transaction and surface API “no data” clearlyThe non-empty array check and explicit API error improve reliability.
ee8ca54
to
70ce7e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
8-9
: Embed styles; remove non‑module CSS import (Next app‑router + html2pdf compatibility).Next.js forbids importing global CSS from components; html2pdf also needs real CSS on the DOM. Inline the stylesheet via a
<style>
tag fed by a string constant and drop thestyles.css
import.Apply:
-// a module.css file doesn't work with html2pdf.js -import "./styles.css"; +// Embed CSS for html2pdf + Next.js app-router compatibility +import { receiptStyles } from "./styles.inline";And inject the CSS:
return ( - <div className="receipt-container"> + <div className="receipt-container"> + <style dangerouslySetInnerHTML={{ __html: receiptStyles }} />Create a new file:
// registry/default/payment-widget/components/receipt/styles.inline.ts export const receiptStyles = ` /* Move content from styles.css here */ `;
🧹 Nitpick comments (6)
registry/default/payment-widget/components/receipt/receipt-template.tsx (2)
170-186
: “Subtotal” semantics may be misleading.You display “Subtotal” using
receipt.payment.amount
while discounts/taxes are shown fromreceipt.totals
. Confirm thatpayment.amount
equals the intended subtotal; otherwise rename the label to “Amount” or render crypto total fromreceipt.totals.total
.Example (optional) to also show crypto total:
<div className="total-line final"> - <span>TOTAL (USD):</span> + <span>TOTAL:</span> + <span className="total-amount"> + {formatCryptoAmount(receipt.totals.total.toString(), receipt.payment.currency)} + </span> + </div> + <div className="total-line final"> + <span>TOTAL (USD):</span> <span className="total-amount"> {formatUSDAmount(receipt.totals.totalUSD)} </span> </div>
117-121
: Nit: CSS can handle zebra striping.Instead of
index % 2
, consider.items-table tbody tr:nth-child(even)
in the embedded CSS to reduce JS-driven class logic. Safe with html2pdf.registry/default/payment-widget/utils/receipt.ts (4)
39-45
: Strengthen receipt number uniqueness.
Date.now()
+ 3-digit random risks collisions. Use ISO timestamp plus cryptographic randomness with broad runtime support.-export const generateReceiptNumber = (prefix: string = "REC"): string => { - const timestamp = Date.now(); - const random = Math.floor(Math.random() * 1000) - .toString() - .padStart(3, "0"); - return `${prefix}-${timestamp}-${random}`; -}; +export const generateReceiptNumber = (prefix: string = "REC"): string => { + const ts = new Date().toISOString().replace(/[-:.TZ]/g, ""); + const rand = + typeof crypto !== "undefined" && "getRandomValues" in crypto + ? Array.from(crypto.getRandomValues(new Uint32Array(1)))[0] + .toString(36) + : Math.random().toString(36).slice(2, 10); + return `${prefix}-${ts}-${rand}`; +};
56-61
: Crypto amount formatting: clamp fraction digits and normalize symbol.Consider capping fractional digits and uppercasing currency without losing precision.
-export const formatCryptoAmount = ( - amount: string, - currency: string, -): string => { - return `${amount} ${currency}`; -}; +export const formatCryptoAmount = ( + amount: string, + currency: string, + opts: { maxFractionDigits?: number } = {}, +): string => { + const { maxFractionDigits = 6 } = opts; + const [i, f = ""] = amount.split("."); + const clipped = f ? `${i}.${f.slice(0, maxFractionDigits)}` : i; + return `${clipped} ${currency.toUpperCase()}`; +};
63-69
: Stabilize date formatting across environments.Specify timezone (UTC) to avoid server/client drift.
-export const formatReceiptDate = (date: Date): string => { - return date.toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - }); -}; +export const formatReceiptDate = (date: Date, timeZone: string = "UTC"): string => { + return new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "short", + day: "numeric", + timeZone, + }).format(date); +};
71-83
: Allow injectingissueDate
; default tonew Date()
for backward compat.This ensures reproducible receipts and lets callers set merchant/UTC time explicitly.
export interface CreateReceiptParams { company: ReceiptCompanyInfo; buyer: ReceiptBuyerInfo; payment: PaymentInfo; items: ReceiptItem[]; totals: { totalDiscount: number; totalTax: number; total: number; totalUSD: number; }; - metadata: Omit<ReceiptMetadata, "issueDate">; + metadata: Omit<ReceiptMetadata, "issueDate"> & { issueDate?: Date }; } export const createReceipt = (params: CreateReceiptParams): ReceiptData => { - const metadata: ReceiptMetadata = { - ...params.metadata, - issueDate: new Date(), - }; + const metadata: ReceiptMetadata = { + ...params.metadata, + issueDate: params.metadata.issueDate ?? new Date(), + };Also applies to: 85-90
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
registry/default/payment-widget/components/payment-confirmation.tsx
(1 hunks)registry/default/payment-widget/components/payment-success.tsx
(1 hunks)registry/default/payment-widget/components/receipt/receipt-template.tsx
(1 hunks)registry/default/payment-widget/components/receipt/styles.css
(1 hunks)registry/default/payment-widget/hooks/use-payment.ts
(1 hunks)registry/default/payment-widget/types/index.ts
(1 hunks)registry/default/payment-widget/utils/receipt.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- registry/default/payment-widget/hooks/use-payment.ts
- registry/default/payment-widget/types/index.ts
- registry/default/payment-widget/components/payment-confirmation.tsx
- registry/default/payment-widget/components/receipt/styles.css
- registry/default/payment-widget/components/payment-success.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-17T12:21:36.704Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/components/receipt/receipt-template.tsx:0-0
Timestamp: 2025-09-17T12:21:36.704Z
Learning: When using html2pdf.js for PDF generation, CSS modules and scoped CSS won't work properly because html2pdf needs actual CSS styles applied to DOM elements. Use inline styles, embedded <style> blocks, or CSS-in-JS solutions instead.
Applied to files:
registry/default/payment-widget/components/receipt/receipt-template.tsx
🧬 Code graph analysis (2)
registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
registry/default/payment-widget/utils/receipt.ts (4)
ReceiptData
(25-37)formatReceiptDate
(63-69)formatCryptoAmount
(56-61)formatUSDAmount
(47-54)
registry/default/payment-widget/utils/receipt.ts (1)
registry/default/payment-widget/types/index.ts (3)
BuyerInfo
(45-58)CompanyInfo
(30-43)ReceiptItem
(19-28)
🔇 Additional comments (1)
registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
43-45
: Date rendering consistency.
formatReceiptDate(receipt.metadata.issueDate)
will reflect the local timezone of whereverissueDate
was created. EnsureissueDate
is captured in a consistent timezone (suggest UTC) and formatted accordingly. See suggested utils refactor.
8148436
to
3e505ea
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
registry/default/payment-widget/components/payment-success.tsx (1)
48-53
: Receipt shows the wrong amount and a placeholder rate. Must use actual crypto amount and real exchange rate.
payment.amount
is set to USD andexchangeRate
is1
, so the PDF misrepresents what was paid on-chain.Plumb the true values from the payment flow (backend payout response or the executed tx list) and use them here. Example shape:
-interface PaymentSuccessProps { +interface PaymentSuccessProps { requestId: string; txHash: string; selectedCurrency: ConversionCurrency; buyerInfo: BuyerInfo; + cryptoAmount: string; // e.g., "0.02345" + exchangeRateUsdPerUnit: number; // e.g., 3200.12 (USD per 1 ETH) }- payment: { - amount: amountInUsd, // TODO connect to actual payout and exchange rate - chain: selectedCurrency.network, - currency: selectedCurrency.symbol, - exchangeRate: 1, - transactionHash: txHash, - }, + payment: { + amount: cryptoAmount, + chain: selectedCurrency.network, + currency: getSymbolOverride(selectedCurrency.symbol), + exchangeRate: exchangeRateUsdPerUnit, + transactionHash: txHash, + },If these values aren’t available today, extend the return type of the payment execution to include them before rendering the success view.
🧹 Nitpick comments (8)
registry/default/payment-widget/utils/chains.ts (1)
10-27
: Consider adding type annotations and polygon alias consistency.The function logic is sound and provides good error handling. However, there are a few minor improvements that could enhance maintainability:
- Add explicit return type annotation for better TypeScript support
- Consider adding "polygon" as an alias alongside "matic" for consistency
Apply this diff to improve the function:
-export const getChainFromNetwork = (network: string) => { +export const getChainFromNetwork = (network: string): Chain => { switch (network.toLowerCase()) { case "mainnet": return mainnet; case "arbitrum": return arbitrum; case "base": return base; case "optimism": return optimism; case "matic": + case "polygon": return polygon; case "sepolia": return sepolia; default: throw new Error(`Unsupported network: ${network}`); } };You'll also need to import the
Chain
type:import { mainnet, arbitrum, base, optimism, polygon, sepolia, + type Chain, } from "viem/chains";
registry/default/payment-widget/utils/currencies.ts (3)
21-33
: Harden fetch: add timeout, richer HTTP errors, and optional?networks
filter.
- Presently no timeout and generic error text; hard to diagnose failures.
- You already noted the
?networks
param; wire it now to cut payload size.Apply this diff:
-export const getConversionCurrencies = async ( - rnApiClientId: string, -): Promise<ConversionCurrency[]> => { - const response = await fetch( - `${RN_API_URL}/v2/currencies/${DEFAULT_CURRENCY}/conversion-routes`, - { - headers: { - "x-client-id": rnApiClientId, - "Content-Type": "application/json", - }, - }, - ); +export const getConversionCurrencies = async ( + rnApiClientId: string, + networks?: string[], +): Promise<ConversionCurrency[]> => { + const url = new URL( + `${RN_API_URL}/v2/currencies/${DEFAULT_CURRENCY}/conversion-routes`, + ); + if (networks?.length) url.searchParams.set("networks", networks.join(",")); + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 10_000); + let response: Response; + try { + response = await fetch(url.toString(), { + headers: { + "x-client-id": rnApiClientId, + "Content-Type": "application/json", + }, + signal: controller.signal, + }); + } finally { + clearTimeout(timeout); + }- if (!response.ok) { - throw new Error("Network response was not ok"); - } + if (!response.ok) { + let message = response.statusText; + try { + const err = await response.json(); + message = err?.error || err?.message || message; + } catch {} + throw new Error(`HTTP ${response.status}: ${message}`); + }Also applies to: 35-37
39-41
: Be defensive parsing the API response.Avoid crashes if the payload shape changes; default to empty array.
Apply this diff:
- const data: GetConversionCurrenciesResponse = await response.json(); - - return data.conversionRoutes; + const data = (await response.json()) as Partial<GetConversionCurrenciesResponse>; + return Array.isArray(data?.conversionRoutes) ? data!.conversionRoutes! : [];
44-51
: Unify symbol presentation across the app.
getSymbolOverride
is used in the confirmation screen but not in the receipt; consider documenting this mapping here and reusing it in receipt generation to avoid “eth-sepolia” leaking into user-facing docs.registry/default/payment-widget/components/payment-confirmation.tsx (3)
46-53
: Don’t silently no-op when wallet is missing; surface an error and gate the Pay button.Early-return provides no feedback. Also, Back should not be disabled by wallet state.
Apply this diff:
- const handleExecutePayment = async (e: React.FormEvent) => { + const handleExecutePayment = async (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault(); - - if (!connectedWalletAddress) return; + if (!connectedWalletAddress) { + setLocalError("Wallet not connected."); + return; + }- <Button - type="button" - variant="outline" - onClick={onBack} - className="flex-1" - disabled={isExecuting || !connectedWalletAddress} - > + <Button + type="button" + variant="outline" + onClick={onBack} + className="flex-1" + disabled={isExecuting} + > Back </Button>- <Button + <Button type="button" onClick={handleExecutePayment} className="flex-1" - disabled={isExecuting} + disabled={isExecuting || !connectedWalletAddress} >Also applies to: 168-186
53-77
: Avoid passingpayerWallet
here; the hook overwrites it with its own address.
usePayment(...).executePayment
ignoresparams.payerWallet
and uses the hook-derived address, which can cause drift/confusion.Consider changing the hook’s
executePayment
input type to excludepayerWallet
and build it internally, then removepayerWallet
from this payload.
131-133
: Format USD consistently.Render USD with locale formatting to avoid “$12.5” style outputs.
Apply this diff:
- ${amountInUsd} + {new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: 2, + }).format(Number(amountInUsd))}registry/default/payment-widget/components/payment-success.tsx (1)
49-51
: Normalize currency symbol for the receipt.Mirror the confirmation screen by applying
getSymbolOverride
to avoid network-suffixed symbols in PDFs.Apply this diff:
- currency: selectedCurrency.symbol, + currency: getSymbolOverride(selectedCurrency.symbol),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
app/components/payment-widget-wrapper.tsx
(1 hunks)registry/default/payment-widget/components/currency-select.tsx
(1 hunks)registry/default/payment-widget/components/payment-confirmation.tsx
(1 hunks)registry/default/payment-widget/components/payment-success.tsx
(1 hunks)registry/default/payment-widget/context/payment-widget-context.tsx
(1 hunks)registry/default/payment-widget/payment-widget.tsx
(1 hunks)registry/default/payment-widget/payment-widget.types.ts
(1 hunks)registry/default/payment-widget/utils/chains.ts
(1 hunks)registry/default/payment-widget/utils/currencies.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- registry/default/payment-widget/context/payment-widget-context.tsx
- registry/default/payment-widget/payment-widget.types.ts
- registry/default/payment-widget/payment-widget.tsx
- app/components/payment-widget-wrapper.tsx
- registry/default/payment-widget/components/currency-select.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-17T12:08:58.186Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/components/payment-confirmation.tsx:31-41
Timestamp: 2025-09-17T12:08:58.186Z
Learning: In the payment widget context (registry/default/payment-widget/context/payment-widget-context.tsx), connectedWalletAddress is properly derived as `walletAccount ? walletAccount.account?.address : address` which handles both custom WalletClient scenarios and wagmi-managed wallets, so components should use connectedWalletAddress from context rather than address from usePayment hook.
Applied to files:
registry/default/payment-widget/components/payment-confirmation.tsx
📚 Learning: 2025-09-17T12:08:58.186Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/components/payment-confirmation.tsx:31-41
Timestamp: 2025-09-17T12:08:58.186Z
Learning: In the payment widget context (registry/default/payment-widget/context/payment-widget-context.tsx), connectedWalletAddress is properly derived as `walletAccount?.account?.address || address` which handles both custom WalletClient scenarios and wagmi-managed wallets, so components should use connectedWalletAddress from context rather than address from usePayment hook.
Applied to files:
registry/default/payment-widget/components/payment-confirmation.tsx
🧬 Code graph analysis (3)
registry/default/payment-widget/utils/currencies.ts (1)
registry/default/payment-widget/constants.ts (1)
RN_API_URL
(1-2)
registry/default/payment-widget/components/payment-success.tsx (5)
registry/default/payment-widget/utils/currencies.ts (1)
ConversionCurrency
(3-11)registry/default/payment-widget/types/index.ts (1)
BuyerInfo
(45-58)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(121-131)registry/default/payment-widget/utils/receipt.ts (3)
CreateReceiptParams
(71-83)generateReceiptNumber
(39-45)createReceipt
(85-101)registry/default/payment-widget/components/receipt/receipt-template.tsx (1)
ReceiptPDFTemplate
(11-196)
registry/default/payment-widget/components/payment-confirmation.tsx (5)
registry/default/payment-widget/utils/currencies.ts (2)
ConversionCurrency
(3-11)getSymbolOverride
(44-51)registry/default/payment-widget/types/index.ts (2)
BuyerInfo
(45-58)PaymentError
(8-11)registry/default/payment-widget/context/payment-widget-context.tsx (1)
usePaymentWidgetContext
(121-131)registry/default/payment-widget/hooks/use-payment.ts (1)
usePayment
(22-116)registry/default/payment-widget/utils/payment.ts (1)
executePayment
(154-205)
🔇 Additional comments (3)
registry/default/payment-widget/utils/chains.ts (1)
1-8
: LGTM! Clean and focused imports.The imports are well-organized and include all the major EVM chains commonly used in DeFi applications.
registry/default/payment-widget/components/payment-success.tsx (1)
113-121
: Good fix on off‑screen rendering for html2pdf.Moving the template off-screen (not 0×0) avoids blank PDFs. LGTM.
registry/default/payment-widget/utils/currencies.ts (1)
3-11
: Make ConversionCurrency.address optional or confirm the API always provides itNo code in registry/default/payment-widget reads ConversionCurrency.address. Either change
address: string
→address?: string
in registry/default/payment-widget/utils/currencies.ts if the API may return null/"" or confirm the API guarantees a non-empty address for ETH/ISO4217 and add runtime validation/normalization if it does not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (10)
registry/default/payment-widget/types/index.ts (10)
1-4
: Type safety for addresses + clarify fee unitsConsider branding EVM addresses and documenting the fee unit (bps vs decimal fraction) to avoid ambiguity.
-export interface FeeInfo { - feePercentage: string; - feeAddress: string; -} +export interface FeeInfo { + /** Decimal fraction string (e.g., "0.015" for 1.5%) or document if using bps. */ + feePercentage: string; + feeAddress: Address; +}Add near top of file:
export type Address = `0x${string}`;
6-6
: Resolve TODO: define money/percent string semanticsPick a canonical format and document it (e.g., decimal strings in display units with '.' separator; rounding mode; max decimals). Otherwise downstream math/formatting will diverge.
8-11
: Make errors UI-friendly and serializableRaw Error objects are not reliably serializable; include message/code and keep original as optional.
-export interface PaymentError { - type: "wallet" | "transaction" | "api" | "unknown"; - error: Error; -} +export interface PaymentError { + type: "wallet" | "transaction" | "api" | "unknown"; + error?: Error; + message?: string; + code?: string; + cause?: unknown; +}
13-17
: Align transaction shape with viem/ethers typesUse branded hex/address types; make data/value optional; accept common representations.
-export interface Transaction { - to: string; - data: string; - value: { hex: string }; -} +export interface Transaction { + to: Address; + data?: Hex; + /** Accept viem/ethers styles; normalize at call site. */ + value?: { hex: string } | Hex | bigint; +}Add near top of file:
export type Hex = `0x${string}`;
19-28
: Avoid duplicating derived totals; mark as optional/derivedKeeping both inputs and computed totals invites drift. Prefer computing
total
from inputs.export interface ReceiptItem { id: string; description: string; quantity: number; unitPrice: string; discount?: string; tax?: string; - total: string; + /** Derived: quantity * unitPrice - discount + tax */ + total?: string; currency?: string; }
32-38
: DRY the address shape across CompanyInfo and BuyerInfoExtract a shared Address type to reduce duplication and keep fields consistent.
export interface PostalAddress { street: string; city: string; state: string; postalCode: string; country: string; }Then:
- address?: { - street: string; - city: string; - state: string; - postalCode: string; - country: string; - }; + address?: PostalAddress;Also applies to: 51-57
30-43
: PII heads‑up: annotate intended retention/logging policyCompany fields may include PII. Document handling expectations (e.g., not persisted client-side; no logging) to guide implementers.
45-58
: BuyerInfo email required: confirm this constraintIf invoices can be downloaded without email, consider making
60-65
: Add subtotal and currency context for totalsTotals often need
subtotal
and a fiat currency tag rather than hardcoding USD.export interface ReceiptTotals { + subtotal?: string; totalDiscount: string; totalTax: string; total: string; - totalUSD: string; + /** Fiat-converted total and currency code (e.g., "USD"). */ + totalFiat?: { amount: string; currency: string }; }
67-73
: Immutability and stronger typing for itemsMake collections readonly to prevent accidental mutation; consider readonly on nested fields if you aim for value objects.
export interface ReceiptInfo { buyerInfo?: BuyerInfo; companyInfo: CompanyInfo; - items: ReceiptItem[]; + readonly items: readonly ReceiptItem[]; totals: ReceiptTotals; receiptNumber?: string; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/components/payment-widget-wrapper.tsx
(1 hunks)registry/default/payment-widget/components/payment-success.tsx
(1 hunks)registry/default/payment-widget/components/receipt/receipt-template.tsx
(1 hunks)registry/default/payment-widget/types/index.ts
(1 hunks)registry/default/payment-widget/utils/receipt.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- registry/default/payment-widget/utils/receipt.ts
- app/components/payment-widget-wrapper.tsx
- registry/default/payment-widget/components/payment-success.tsx
- registry/default/payment-widget/components/receipt/receipt-template.tsx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
app/components/payment-widget-wrapper.tsx (2)
23-25
: Avoidany
for error parameter.Use
unknown
to prevent accidental unsafe access and keep logs.- const handleError = async (error: any) => { + const handleError = async (error: unknown) => { console.error("Payment failed:", error); };
85-87
: Use a semantic button for the trigger (keyboard/a11y).The trigger is a div; switch to a button for focus/keyboard support.
- <div className="flex p-3 bg-blue-500 items-center rounded-2xl text-white"> - Pay using crypto - </div> + <button + type="button" + className="flex p-3 bg-blue-500 items-center rounded-2xl text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" + aria-label="Pay using crypto" + > + Pay using crypto + </button>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/components/payment-widget-wrapper.tsx
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-17T12:08:58.186Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/components/payment-confirmation.tsx:31-41
Timestamp: 2025-09-17T12:08:58.186Z
Learning: In the payment widget context (registry/default/payment-widget/context/payment-widget-context.tsx), connectedWalletAddress is properly derived as `walletAccount ? walletAccount.account?.address : address` which handles both custom WalletClient scenarios and wagmi-managed wallets, so components should use connectedWalletAddress from context rather than address from usePayment hook.
Applied to files:
app/components/payment-widget-wrapper.tsx
📚 Learning: 2025-09-17T12:08:58.186Z
Learnt from: bassgeta
PR: RequestNetwork/ui-registry#1
File: registry/default/payment-widget/components/payment-confirmation.tsx:31-41
Timestamp: 2025-09-17T12:08:58.186Z
Learning: In the payment widget context (registry/default/payment-widget/context/payment-widget-context.tsx), connectedWalletAddress is properly derived as `walletAccount?.account?.address || address` which handles both custom WalletClient scenarios and wagmi-managed wallets, so components should use connectedWalletAddress from context rather than address from usePayment hook.
Applied to files:
app/components/payment-widget-wrapper.tsx
🧬 Code graph analysis (1)
app/components/payment-widget-wrapper.tsx (1)
registry/default/payment-widget/payment-widget.tsx (1)
PaymentWidget
(89-116)
🔇 Additional comments (2)
app/components/payment-widget-wrapper.tsx (2)
31-31
: PassingwalletAccount
through is correct.This lets the PaymentWidget context derive a unified
connectedWalletAddress
(handles custom WalletClient and wagmi-managed wallets).
35-40
: Validate supportedCurrencies against the canonical currency registrySupported IDs are defined in app/components/payment-widget-wrapper.tsx (lines 35–40): "ETH-sepolia-sepolia", "fUSDT-sepolia", "FAU-sepolia", "USDCn-matic". No currency catalog/registry was found in the repo to cross-check — confirm these exact IDs against the widget’s currency catalog or add a repo-side registry + tests to prevent “unsupported currency” runtime errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (3)
.github/deploy-to-staging.yml (1)
3-6
: Trigger strategy: confirm intent to deploy staging on every push to main.If you want staging previews earlier, consider
pull_request
to main or specific labels; if not, current trigger is fine..github/deploy-to-production.yml (2)
3-6
: Manual dispatch bypass risk—confirm this is intended.
workflow_dispatch
allows ad‑hoc production deploys; rely on environment reviewers if you keep it. Otherwise, remove it and deploy only on releases.Possible change:
on: - workflow_dispatch: release: types: [published]
12-14
: Add a basic post‑deploy smoke check (optional).Consider a follow-up step to poll a health endpoint and fail if unhealthy.
Example:
- name: Webhook request … + - name: Smoke check + run: | + curl -fsS https://prod.request.network/healthz
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved 👍
Nice work! 🎉
Add Payment Widget Component
Overview
This PR introduces a comprehensive Payment Widget component for accepting cryptocurrency checkout.
Architecture
This entire codebase is a NextJS server, which serves the registry.json file and compiled components.
The main app has 2 demos, one without an existing wallet, the other with a prompt for you to connect your wallet which we pass to the widget.
The parts which are going to be distributed are all in the
registry
directory, so your review should be focused on those.Context-Driven Design
The widget uses React Context to manage state across all components, eliminating prop drilling and providing centralized state management. The
PaymentWidgetProvider
handles all business logic and data transformation.Modal Flow
The payment modal follows a step-by-step flow:
PDF Generation
Invoice PDF generation uses
html2pdf.js
with invisible DOM elements rendered off-screen to avoid visual disruption while maintaining proper styling and layout.Files Added
Core Component
payment-widget.types.ts
- TypeScript interfaces and type definitionsconstants.ts
- Icon constants and configuration valuesContext & State Management
context/payment-widget-context.tsx
- React Context provider for centralized state managementcontext/web3-context.tsx
- Wagmi/Web3 provider wrapperModal Components
components/payment-modal.tsx
- Main modal container with step managementcomponents/currency-select.tsx
- Cryptocurrency selection interfacecomponents/buyer-info-form.tsx
- Billing information form with validationcomponents/payment-confirmation.tsx
- Payment review and executioncomponents/payment-success.tsx
- Success screen with invoice downloadcomponents/connection-handler.tsx
- Wallet connection orchestrationcomponents/wallet-connect-modal.tsx
- Wallet selection modalcomponents/disconnect-wallet.tsx
- Wallet disconnection utilityUtilities & Hooks
hooks/use-payment.ts
- Payment execution and transaction handlinglib/currencies.ts
- Currency conversion and formatting utilitieslib/payment.ts
- Request Network payment processinglib/invoice.ts
- Invoice generation and PDF creationlib/wagmi.ts
- Wagmi configuration utilitiesInvoice System
components/invoice/invoice-template.tsx
- PDF-ready invoice templatecomponents/invoice/styles.css
- Invoice-specific stylingtypes/html2pdf.d.ts
- TypeScript definitions for html2pdf libraryDemo & Documentation
viem-account-demo.tsx
- Demo showing existing wallet integrationTechnical Notes
PDF Generation Implementation
The invoice PDF generation uses invisible DOM elements (
height: 0, width: 0, overflow: hidden
) to render the invoice template off-screen. This approach ensures:Context Architecture
The
PaymentWidgetProvider
receives raw props and transforms them into a structured context value, handling all business logic internally. Components consume only the specific values they need through destructuring, maintaining clean separation of concerns.Wallet Integration
The widget can operate in two modes:
Resolves #59
Summary by CodeRabbit
New Features
Documentation
Chores