This README is a living guide for the app structure and payment form behavior. We will keep updating it as the code evolves.
CreloPay is a payment-link creation app built with Next.js App Router. Merchants create a payment link by filling a dynamic form, signing typed data with a wallet, and submitting the signed payload to server actions backed by Supabase. Payers open the generated link, complete payment on-chain, and the app stores a receipt record.
- Next.js (App Router)
- React + TypeScript
- Tailwind CSS + shadcn/ui components
- TanStack React Query
- wagmi (wallet connection and signing)
- Supabase client/actions
- GSAP (light UI animation)
- Solidity (backend smart contract)
- Install dependencies:
npm install- Run development server:
npm run dev- Open:
http://localhost:3000
npm run dev- Start local dev servernpm run build- Production buildnpm run start- Run production servernpm run lint- Run eslint checks
app/
layout.tsx # Global providers, navbar, toaster
page.tsx # Renders CreatePaymentForm
pay/[paymentId]/page.tsx # Resolves payment id and loads payment record
ReactQueryProvider/ # Query client provider setup
components/
Navbar.tsx
forms/
CreatePaymentForm.tsx # Main form and submit flow
PayWithPaymentIdForm.tsx # Payer checkout flow
TypeBasedInputs/
Donation.tsx # Donation-only fields
Ticket.tsx # Ticket-only fields
ui/ # Shared shadcn-style UI primitives
lib/
types.ts # Shared form/value types
HelperFunctions.ts # Nonce generation and helpers
constants.ts # Contract ABI/address
contract/action.ts # On-chain pay simulation + write flow
ReactQueries/ContractQueries.tsx
ReactQueries/SupabaseQueries.tsx
supabase/scripts/
action.ts # Supabase actions (create payment link)
signature.ts # Metadata hash and bytes32 helpers
flowchart TD
A[Merchant opens /] --> B[CreatePaymentForm]
B --> C[validateForm]
C --> D[buildMetadata]
D --> E[buildMetadataHash]
E --> F[generateNonce]
F --> G[wagmi signTypedDataAsync]
G --> H[useCreatePaymentLink]
H --> I[createPaymentLink server action]
I --> J[Verify hash and recover signer]
J --> K[Insert payments_details row]
K --> L[Return /pay/{paymentId} link]
flowchart TD
A[Payer opens /pay/[paymentId]] --> B[getPaymentDetailsById]
B --> C[PayWithPaymentIdForm]
C --> D[Validate payer inputs]
D --> E[useCallContractPay]
E --> F[pay contract action]
F --> G[Check allowance]
G --> H[Approve USDC if needed]
H --> I[Simulate pay]
I --> J[Send pay tx]
J --> K[Read PaymentMade event]
K --> L[useCreateReceipt]
L --> M[createReceipt server action]
M --> N[Store receipt in Supabase]
- Route
app/page.tsxrendersCreatePaymentForm. validateFormchecks common and type-specific fields.buildMetadataconverts form values into canonical metadata.buildMetadataHashcreates deterministic bytes32 hash of metadata.generateNoncecreates nonce used in signature payload.- Wallet signs typed data (
PaymentRequest) via wagmisignTypedDataAsync. useCreatePaymentLinkcalls server actioncreatePaymentLink.- Server recomputes metadata hash, recovers signer, validates signature.
- Server inserts
payments_detailsrow and returns/pay/{paymentId}link.
- Route
app/pay/[paymentId]/page.tsxresolves param and callsgetPaymentDetailsById. PayWithPaymentIdFormrenders payer inputs based on stored payment type/metadata.- Form validates payer data and computes final payable amount.
useCallContractPaytriggerspayinlib/contract/action.ts.paychecks/sets USDC allowance, simulatespay, then sends transaction.extractPaymentRefparsesPaymentMadeevent from receipt.useCreateReceiptcallscreateReceiptto persist payer receipt details.
config- Central wagmi config (chains, connectors, RPC transports).
- Used by wallet hooks and contract actions.
CONTRACT_ABI- ABI for CreloPay contract (constructor, events, pay function, helpers).
- Used for simulate/read/write and event log parsing.
CONTRACT_ADDRESS- Contract address loaded from
NEXT_PUBLIC_CONTRACT_ADDRESS.
- Contract address loaded from
ERC20_ABI- Minimal ERC20 ABI subset (
allowance,approve) for USDC approval flow.
- Minimal ERC20 ABI subset (
getQueryClient- Returns request-scoped QueryClient on server and singleton on browser.
generateNonce(length = 32)- Generates random hex nonce for replay protection.
buildMetadata(formValues)- Builds normalized metadata object per payment type.
parseNumber(value)- Parses numeric string to number, returns
NaNfor invalid values.
- Parses numeric string to number, returns
parseCommaSeparatedNumbers(input)- Converts comma-separated input to positive number array.
parseCommaSeparatedStrings(input)- Converts comma-separated input to trimmed string array.
validateForm(formValues)- Validates merchant form fields before signing/submitting.
mapDbToSignature(dbData)- Maps app/db payload to exact EIP-712
PaymentRequestmessage shape.
- Maps app/db payload to exact EIP-712
simulatePay({ request, signature, account })- Simulates contract
paycall before sending transaction.
- Simulates contract
pay({ request, signature, account })- End-to-end payer transaction flow:
- checks USDC allowance,
- sends approval if needed,
- simulates payment,
- writes
paytransaction, - extracts
paymentReffromPaymentMadeevent.
useCallContractPay()- React Query mutation wrapper around
paycontract action.
- React Query mutation wrapper around
useCreatePaymentLink()- React Query mutation wrapper around server action
createPaymentLink.
- React Query mutation wrapper around server action
useCreateReceipt()- React Query mutation wrapper around server action
createReceipt.
- React Query mutation wrapper around server action
getSupabaseAdminClient()- Returns cached server-only Supabase service-role client.
isBytes32Hex(value)- Type guard for 32-byte hex strings.
ensureBytes32Hex(value, label)- Validates and returns bytes32 hex string or throws.
buildMetadataHash(metadata)- Canonically serializes metadata and computes deterministic keccak256 hash.
createPaymentLink(paymentInfo)- Validates signature payload, verifies signer, inserts payment record,
- returns payment link and payment id.
getPaymentDetailsById(paymentId)- Fetches a single payment record for payer route.
createReceipt(receiptInfo)- Persists receipt details after successful payment transaction.
cn(...inputs)- Utility to merge Tailwind/class names (
clsx+tailwind-merge).
- Utility to merge Tailwind/class names (
PaymentType- Supported payment categories:
DONATION,TICKET,PRODUCT,SERVICE,INVOICE.
- Supported payment categories:
PaymentFormValues- Merchant form state shape used by
CreatePaymentForm.
- Merchant form state shape used by
DonationValues- Donation-only subset of merchant fields.
TicketValues- Ticket-only subset of merchant fields.
PaymentSignaturePayload- Canonical merchant signature payload contract.
CreatePaymentLinkInput- Input shape expected by
createPaymentLinkserver action.
- Input shape expected by
PaymentDetailsRecord- Persisted payment details record returned for payer flow.
PayerInputValues- Client-side payer form state in
PayWithPaymentIdForm.
- Client-side payer form state in
CreateReceiptInput- Input shape for receipt creation action.
PaymentRequestInput- Contract
PaymentRequeststruct shape forpaycall.
- Contract
PayInput- Input for contract pay helper (
request, signature, payer account).
- Input for contract pay helper (
PayResult- Return shape from pay helper (
txHash,paymentRef).
- Return shape from pay helper (
Bytes32Hex- Type alias for bytes32-prefixed hex string.
titleis required.emailmust be a valid email format.unitPricemust be greater than zero for all types.shippingFeemust be greater than zero when shipping is enabled.- Donation requires
goalAmount > 0and at least one valid suggested amount. - Ticket requires
eventDate,location, andtotalSupply > 0.
- Keep this README updated whenever exported symbols, payload shape, or route flow changes.
- If a new payment type is added, update:
PaymentTypeinlib/types.ts,buildMetadataandvalidateForm,- payer rendering logic in
PayWithPaymentIdForm, - this README flow and symbol map.
- If signature fields/domain change, update both frontend signing and backend recovery sections here.