Hosted Checkout
Create a checkout session, redirect the customer, and verify payment from your server.
Send the customer to a Tapaya-hosted payment page for cards, Apple Pay, and Google Pay, then verify the result on your server. Your page never handles card details, and you add no card fields to your shop.
- 1. Your backend to Tapaya: Create session.
- 2. Tapaya to Your backend: Session ID and URL.
- 3. Your backend to Your frontend: Redirect to Checkout.
- 4. Your frontend to Tapaya: Open Checkout and pay.
- 5. Tapaya to Your frontend: Return to your shop.
- 6. Your backend to Tapaya: Retrieve session.
- 7. Tapaya to Your backend: Payment status.
- 8. Your backend to Your frontend: Show verified result.
Before you start
Pick the key that matches who runs your backend. Keep either key on your server; Checkout Security lists the server-side rules.
One shop
Use a merchant checkout key. It selects its merchant, and the examples here use it.
Many merchants
Use the organization Secret key and send a merchant token with every request.
Then, in Checkout in the Tapaya Platform:
- Add your merchant and check that it shows Ready. See Checkout settings.
- Register your shop as an allowed website, with default success and cancel URLs, for the key you chose. The
examples use
https://shop.example.
Return URLs follow the key
Merchant-key sessions use the merchant's return URLs. Sessions created with the organization Secret key use the organization's return destinations instead. The two are never combined.
Accept a payment
All three options create the same Checkout sessions with the same credentials. Pick one; every step below follows your choice.
Set up your server
Set TAPAYA_CHECKOUT_API_URL to https://api.sandbox.tapaya.com and store your key in TAPAYA_CHECKOUT_API_KEY.
Call the API from your server runtime's fetch, never from the customer's browser. The examples read these values
through process.env; adapt that lookup if your runtime uses a different secrets interface.
Create a helper that checks HTTP errors before returning the response body:
const apiUrl = process.env.TAPAYA_CHECKOUT_API_URL;
const apiKey = process.env.TAPAYA_CHECKOUT_API_KEY;
async function checkoutRequest(path, options = {}) {
const response = await fetch(new URL(path, apiUrl), {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${apiKey}`,
},
redirect: 'error',
cache: 'no-store',
signal: AbortSignal.timeout(8000),
});
if (!response.ok) {
throw new Error(`Checkout request failed (${response.status})`);
}
return response.json();
}Create a session
Calculate the order total on your server, including tax and shipping. Persist the order and a unique
checkoutAttemptKey before making the request, and load order from that stored snapshot. merchantOrderId
is required.
const session = await checkoutRequest('/merchant/checkout-sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': order.checkoutAttemptKey,
},
body: JSON.stringify({
merchantOrderId: order.id,
amount: order.totalInMinorUnits,
currency: order.currency,
successUrl: 'https://shop.example/checkout/return',
cancelUrl: 'https://shop.example/checkout/cancel',
}),
});The response contains the session's id and hosted url. For item summaries, tax breakdowns, customer details,
and every response field, see Create a Checkout Session.
If the request fails or times out, retry with the same key and an unchanged body. Never generate a new key for an uncertain result: a changed body with the same key returns HTTP 409.
| Option | Retries automatically |
|---|---|
| HTTP (fetch), cURL | No. Repeat the request yourself. See Idempotency and errors. |
| Node.js SDK | Up to twice after a connection error, a timeout, or HTTP 429, 502, 503, or 504, with the same key and body. See Idempotency and retries. Across separate calls or restarts, pass your persisted key: omitting it generates a new one per call. |
Redirect the customer
Persist session.id with your order as its checkoutSessionId, then send the browser to session.url with HTTP
303. Use the URL unchanged rather than constructing it.
// First persist session.id as the order's checkoutSessionId.
return Response.redirect(session.url, 303);If your framework uses a different response API, send HTTP 303 with session.url in the Location header. With
cURL, open the returned url in your browser to test the payment by hand.
Verify the payment
In your return handler, resolve the order from your application's authenticated checkout context and retrieve its stored session from your server. Do this on the cancel URL too: a visit to either URL proves nothing.
const current = await checkoutRequest(
`/merchant/checkout-sessions/${encodeURIComponent(order.checkoutSessionId)}`,
);
if (
current.id !== order.checkoutSessionId ||
current.merchantOrderId !== order.id ||
current.amount !== order.totalInMinorUnits ||
current.currency !== order.currency
) {
throw new Error('Checkout session does not match the order');
}
const paid = current.paymentStatus === 'successful';Fulfill once, and only when paid
Record the payment and fulfill the order only when the session matches your stored order and paymentStatus is
successful, and record it atomically so it happens once. If retrieval fails or the payment is unresolved, keep
the session ID and check again. See Payment status.
Handle payment outcomes
Checkout Security covers what to verify and how to retry an unknown result without creating a second session.
| Outcome | What the session shows | What to do |
|---|---|---|
| Declined | open, with paymentStatus: failed | Nothing. The customer can try another card or wallet on the same page. |
| Not yet known | open and unpaid while Tapaya confirms the result | Retrieve the session again later. |
| New session for the same order | The previous unpaid session reports expired | Use the new session. A paid order, or one with a payment in progress, returns HTTP 409. See One payment per order. |
| Expired | Stops accepting new payments 30 minutes after creation | Create a new session if the order is still unpaid. A payment submitted before expiry still completes. |
No webhooks yet
There are no Checkout webhooks yet. Run a server-side reconciliation job that retrieves sessions for orders whose customers never return.