What you’ll be able to explain
- Trace invoice creation, payment detection, reconciliation and fulfillment without trusting the browser as payment evidence.
01 / Understand
A checkout is a conversation between systems
A customer buys an illustrated guide from a fictional shop. The shop creates an order for one copy, asks its payment service for an invoice, and shows the resulting payment request. The customer’s wallet then attempts the payment.
That visible screen hides several responsibilities. The shop knows what is being sold and to whom. The payment system tracks an expected payment and its state. The wallet arranges the payment. A fulfillment process grants access after the shop’s rules are satisfied.
An invoice identifier connects those responsibilities. It should be mapped to the intended order on the server, alongside expected currency, amount and relevant payment method. A client-supplied “paid” flag is not a substitute for this record.
A fictional successful sequence
- The shop creates order
demo-order-42, awaiting payment. - The server creates an invoice and stores the order/invoice mapping.
- The customer receives a payment request with its applicable terms.
- The provider observes payment and sends a notification.
- The server authenticates and reconciles the notification with current invoice state.
- The shop records eligibility for fulfillment, then grants access once.
The browser may return to a success page, but that redirect is a user-interface event. It is not the authoritative evidence that the payment completed. A customer can close a tab before the redirect, and an attacker can visit a success URL without paying.
The idea, at a glance
Request
Store a server-side order/invoice mapping.
Observe
Authenticate and reconcile payment evidence.
Fulfill
Apply business rules and deliver once.
02 / Explore
Expiration is a decision boundary, not an eraser
Consider an invoice that expires after a fictional ten-minute window. At minute eleven, an on-chain payment to its destination is detected. The business cannot make that transfer disappear by changing an invoice label.
Instead, it needs a late-payment policy and a reconciliation path. The order might need manual review, updated terms, fulfillment, or a refund process. Similarly, an underpayment is not equivalent to no payment, and an overpayment is not necessarily a second order.
BTCPay’s documented invoice states illustrate these distinctions, including payment exceptions and settlement settings. The exact names and event behavior belong to that provider and version. Our flow is an application design example, not a protocol-wide state machine.
Network and application failures complicate the happy path. An invoice-creation response can be lost after the provider creates the invoice. A webhook can arrive twice. A fulfillment call can time out after granting access. Each boundary needs a stable identity and a way to ask what actually happened.
03 / Build
Draw the failure paths
Add two branches to the successful sequence:
- The browser closes after paying. Expected behavior: the server’s provider reconciliation can still update the order; fulfillment does not depend on the customer’s tab remaining open.
- The payment arrives after expiry. Expected behavior: preserve the payment evidence, flag the applicable exception, and apply the business policy. Do not silently discard the money or immediately create a duplicate purchase.
For a developer, write the minimal mapping record:
order_id, provider, provider_store_id, invoice_id,
expected_amount, expected_currency, payment_method
These are conceptual fields. A production schema must define units, uniqueness constraints, versioning and which values come from trusted server state. Use provider documentation for authentication and verification; this publication does not implement a merchant checkout.
Pause & explain
If the customer closes the browser after paying, should the order be permanently stuck?
Try explaining it in your own words before opening the answer.
Compare your explanation
No. Server-side notification or reconciliation should discover the provider’s authoritative state independently of the browser. The application can then apply its fulfillment policy.
Sources & scope
Primary references behind this explanation. Worked examples and diagrams are original teaching material.
- 01BTCPay Server — Greenfield API example ↗
Creating invoices and registering and processing webhook notifications.
- 02BTCPay Server — Greenfield PHP example ↗
Provider integration example, including webhook validation. The lesson’s pseudocode is not a drop-in handler.
- 03BTCPay Server — Invoices ↗
Provider-specific invoice lifecycle and exception statuses. Business fulfillment is a separate application decision.
- 04BOLT 11 — Invoice protocol ↗
Payment-request encoding, amount and expiry fields. BOLT 11 is one Lightning request format, not every possible payment flow.
Where this explanation stops
- Fictional merchant example only; provider-specific API authentication and production checkout are not implemented.
Keep unfolding
Why a webhook can arrive more than once Payment received versus order fulfilled: designing clear statuses