Accept a payment
The ordinary case: charge a card now, and find out when it worked.
The shape of it
| Step | What happens |
|---|---|
| You create a payment intent | We record what you intend to charge. |
| You create a checkout session | You get a URL. |
| Your customer pays | On a page hosted by the bank. You never see the card. |
| We verify with the bank | We re-read the payment rather than trusting a notification. |
| We send you a webhook | payment_intent.succeeded. Fulfil the order here. |
Creating the payment
ts
const intent = await sp.paymentIntents.create({
amount: 6000,
currency: "usd",
// Reaches the bank's own record, and is what you will see in their portal.
// Lead with your own reference so you can find it again.
description: "SCU-B0161 — Blue Hole Two-Tank",
// Your identifiers. The payments list can be filtered by these.
metadata: { shop_id: "scuba-sensation", booking_id: "SCU-B0161" },
// Optional: the bank emails a receipt to this address.
receipt_email: "diver@example.com",
});description is the only field that reaches the bank’s own records, and it is frozen when the payment is taken. Put the reference you would search for first — a bare noun like “Booking” makes a bank statement impossible to reconcile.Sending the customer
ts
const session = await sp.checkoutSessions.create({
payment_intent: intent.id,
success_url: "https://example.com/thanks",
cancel_url: "https://example.com/cart",
line_items: [{ name: "Blue Hole Two-Tank Dive", amount: 6000, quantity: 1 }],
});
redirect(session.url!);line_items are shown on the checkout page so the customer can see what they are paying for. The payment’s own amount is what is charged — the line items are never summed.
Knowing it worked
Fulfil on the webhook, not on the redirect. A customer can close the tab after paying, and a redirect can be replayed by anyone who sees the URL.
ts
if (event.type === "payment_intent.succeeded") {
const payment = event.data.object;
await fulfil(payment.metadata.order_id);
}If you also want to show the customer a result immediately, read the payment back by id when they land on your success_url. That is a display decision; the webhook is the record.