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, confirm that your merchant is configured for online payments. Open Settings > Checkout in the Tapaya Platform, then enable Checkout and configure its settings. Register your shop's return URLs and allowed origins. The examples use https://shop.example.

Use the Secret key from the API Keys page and keep it on your server. See Checkout Security for the full set of 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 for development and store your Secret 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. 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 or an in-progress request 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.

Use a server-side reconciliation job for orders whose customers never return. The current Checkout integration uses authenticated retrieval and does not expose a merchant checkout webhook API.

Check the integration

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