Tapaya
Online PaymentsIntegration GuideHosted Checkout

Hosted Checkout

Create a checkout session, redirect the customer, and verify payment from your server.

Your frontend
Your backend
Tapaya
  1. 1. Your backend to Tapaya: Create session.
  2. 2. Tapaya to Your backend: Session ID and URL.
  3. 3. Your backend to Your frontend: Redirect to Checkout.
  4. 4. Your frontend to Tapaya: Open Checkout and pay.
  5. 5. Tapaya to Your frontend: Return to your shop.
  6. 6. Your backend to Tapaya: Retrieve session.
  7. 7. Tapaya to Your backend: Payment status.
  8. 8. Your backend to Your frontend: Show verified result.
Keep the Secret key on your backend. A browser return does not prove payment. Your backend checks the order, amount, currency, and successful status before fulfillment. Retrieve the status even if the customer never returns.

Choose your server integration below. The Node.js SDK, HTTP, and cURL create the same Checkout sessions and use the same merchant credentials.

Before you start, open Checkout in the Tapaya Platform, add your merchant, and configure its settings. Register your shop's allowed website and return URLs. The examples use https://shop.example.

The examples use a merchant checkout key, which needs no other header, so they use the merchant's return URLs. If one backend serves many merchants, use the organization Secret key and add X-Tapaya-Merchant-Token to every request (merchantToken in the SDK); those sessions use the organization's return destinations instead. See Checkout authentication. Keep either key on your server; Checkout Security lists the server-side rules.

Set up server-side fetch

Use your server runtime's fetch to call the merchant Checkout API. Do not run these requests in the customer's browser.

Set TAPAYA_CHECKOUT_API_URL to https://api.sandbox.tapaya.com and store your key in TAPAYA_CHECKOUT_API_KEY. The examples read these values through process.env; adapt that lookup if your server 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 with fetch

Calculate the order total on your server. Persist the order and a unique checkoutAttemptKey before making the request. merchantOrderId is required. Load order from that stored snapshot, including its ID, currency, and total in minor units.

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',
	}),
});

Supply the final total, including tax and shipping. For item summaries, tax breakdowns, and customer details, see Create a Checkout Session.

This helper does not retry automatically. If creation times out or its response is lost, retry with the same persisted key and unchanged body. Do not generate a new key for an uncertain result. A changed body with the same key returns HTTP 409. See Idempotency and errors.

Redirect using the HTTP response

Persist session.id with your order before returning a redirect. Use session.url unchanged rather than constructing the Checkout URL.

For a server handler that returns a Web Response, return:

// 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.

Verify payment with fetch

In your return handler, resolve the order from your application's authenticated checkout context. Load its stored checkoutSessionId and retrieve the session from your server, including when the customer visits the cancel URL.

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';

Only when paid is true, record the payment and fulfill the order once in your server-side storage. If retrieval fails or the payment remains unresolved, retain the session ID for another status check. See Payment status for every state.

Handle payment outcomes

Fulfill an order only after your server has verified the session, and record it atomically so it happens once. Checkout Security covers what to verify and how to retry an unknown result without creating a second session.

  • Decline: the session stays open with paymentStatus: failed. The customer can try another card or wallet on the same page.
  • Outcome not yet known: the session stays open and unpaid while Tapaya confirms the result in the background. Retrieve it again later.
  • New session for the same order: replaces the previous unpaid session, whose URL then reports expired. An order that is paid or has a payment in progress returns HTTP 409. See One payment per order.
  • Expiry: a session accepts new payments for 30 minutes. A payment submitted before then still completes.

There are no Checkout webhooks yet. Run a server-side reconciliation job that retrieves sessions for orders whose customers never return.

Check the integration

Test the flow against the sandbox environment with the test cards in Testing. Work through the QA Checklist, which covers payment outcomes, retries, interrupted checkouts, and expiry. Use Payment status to handle each returned state.