← Docs

Creating invoices

Two things must exist before an invoice can be paid: a merchant with a wallet address, and an invoice with a USDC amount. Both are created from the dashboard, or through the API if you want to wire Payrail into your own system.

1. Register as a merchant

Open the dashboard, connect a wallet, and enter your business name. That is all. The connected wallet becomes the receiving address for every invoice this merchant issues, and walletAddress is unique: one wallet, one merchant.

The name is display only. The contract uses the address, so check it twice before your first invoice: USDC sent to the wrong address cannot be pulled back.

2. Write the invoice

From New invoice, fill in:

FieldRequiredNotes
NetworkyesRobinhood Chain. Shown only when this deployment offers more than one network (for example the testnet); it cannot be changed after the link is created.
DescriptionyesUp to 500 characters. Shown on the payment link.
Amount (USDC)yesPositive number, up to 6 decimals. Stored in smallest units (250.00 becomes 250000000).
Customer namenoFor you only; never sent to chain.
Due datenoInformational. There is no automatic expiry yet.

The onchain key below does not include the network. The invoice's stored chainId is what pins the payment to Robinhood Chain: a matching event seen on any other network (the testnet, say) is rejected.

On save, the backend creates the invoice record and derives two values:

salt      = keccak256(invoice.id)
onchainId = keccak256(abi.encode(salt, merchant.walletAddress, amountUnits))

invoice.id is a random cuid, so salt is unguessable and unique. The buyer passes salt, the merchant address and the exact amount to pay(); the contract recomputes the same onchainId from those three terms. Because the key binds the merchant and the amount, a payment with different terms lands on a different key and can never be confused with, or block, this invoice.

The response from POST /api/invoices includes paymentLink:

https://<your-app>/pay/<invoice.id>

Send it however you like: email, chat, QR. The link is public to anyone holding it, and can be paid exactly once, for exactly the listed amount. Paying the same terms twice is rejected by the contract (InvoiceAlreadyPaid); paying a different amount is a different key and does not close this invoice.

Through the API

curl -X POST https://<your-app>/api/invoices \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "clx...",
    "description": "Logo design, milestone 2",
    "customerName": "Alex",
    "amount": "85.00"
  }'

A 201 response contains the full invoice plus paymentLink. See API for every endpoint.

Cancelling

DELETE /api/invoices/:id marks an invoice CANCELLED as long as it is not PAID. The link stops accepting payment in the UI. Note that the contract knows nothing about cancellation: if someone calls pay() directly with the ID of a cancelled invoice, the USDC still reaches the merchant and the indexer will record it. See Risks and limits.

Next: Payment flow.