Hosted checkout

Redirect the customer to a Teyra Pay checkout page and bring them back to your site when they finish.

With hosted checkout, the customer enters payment details on a page hosted by Teyra Pay, not on your site. You never see or store card data, and every payment method your account supports (cards, Apple Pay and Google Pay, plus KNET, mada, Benefit or STC Pay on accounts that support them) works with the same integration. See Payment methods and your account.

The redirect flow

 Your server            Customer's browser              Teyra Pay
     │  POST /v1/payments                                   │
     │─────────────────────────────────────────────────────▶│
     │◀──────────────────── Payment + checkout_url ─────────│
     │  302 → checkout_url │                                │
     │────────────────────▶│  GET checkout_url              │
     │                     │───────────────────────────────▶│  customer pays
     │                     │◀── 302 → return_url?payment_id=…&status=…
     │  GET return_url     │                                │
     │◀────────────────────│                                │
     │  GET /v1/payments/:id  (confirm)                     │
     │─────────────────────────────────────────────────────▶│
     │◀─────────────── POST webhook payment.succeeded ──────│

Create the payment on your server

When the customer clicks Pay, your server calls POST /v1/payments with the amount, currency and a return_url. Save the returned id with your order.

Redirect to checkout_url

Respond with an HTTP redirect (303 or 302) to checkout_url. It's always a Teyra Pay URL. Open it in the same tab; don't put it in an iframe.

The customer pays

Teyra Pay shows the payment page in the customer's language (language: en or ar), with the methods available for the currency and device. The payment moves to processing and then to succeeded or failed.

The customer returns

Teyra Pay redirects the browser to your return_url with two query parameters added:

https://shop.example.com/thanks?payment_id=pay_01JBX4T9W2&status=succeeded

status is succeeded, failed or processing. If your return_url already has a query string, the parameters are appended to it.

Confirm on your server

Fetch the payment with GET /v1/payments/:id or wait for the webhook, and fulfil only when status is succeeded. See Return URL handling.

Server example

// Express: start checkout for an order
app.post('/orders/:id/pay', async (req, res) => {
  const order = await db.orders.find(req.params.id);

  const response = await fetch('https://api.teyra.co/v1/payments', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.TEYRA_SECRET_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `order-${order.id}-attempt-${order.attempt}`,
    },
    body: JSON.stringify({
      amount: order.totalMinor, // e.g. 12500 for 12.500 KWD
      currency: 'KWD',
      reference: String(order.id),
      description: `Order #${order.id}`,
      customer: { name: order.name, email: order.email, phone: order.phone },
      language: req.acceptsLanguages('ar', 'en') || 'en',
      return_url: `https://shop.example.com/orders/${order.id}/return`,
    }),
  });
  const payment = await response.json();
  if (!response.ok) return res.status(502).send(payment.error.message);

  await db.orders.update(order.id, { paymentId: payment.id });
  res.redirect(303, payment.checkout_url);
});

What the customer sees

The checkout page is a Teyra Pay page. It shows your business name, the payment description and reference, the amount, and the payment methods available for the currency and device. A TEST badge marks test-mode payments. The customer can switch the page between English and Arabic.

  • Cards (Visa, Mastercard, mada, AMEX): the customer types the card into secure fields on the checkout page. The card details go straight to the card processor; neither you nor Teyra Pay ever receive them. If the bank asks for 3-D Secure, the customer completes it either in a window on top of the checkout page or on the bank's page, depending on your account, and then comes back to your return_url automatically.
  • Apple Pay, Google Pay, STC Pay: completed on the checkout page. Apple Pay and Google Pay appear only on devices and browsers that support them.
  • KNET, Benefit and other bank methods (on accounts that support them): the customer is sent to the bank's own payment page and comes back automatically.
  • Declined cards: the page shows the reason with a Try again button. The payment becomes failed but can still be paid at the same checkout_url.
  • Cancel: the page has a Cancel and return to merchant button. The payment becomes canceled (you get payment.canceled) and the customer is sent to your return_url with status=failed. The payment can still be paid at the same checkout_url until it expires.

If you pass payment_method when creating the payment, only that method is offered. It must be a method your account supports, or the payment isn't created (422 validation_error).

Expiry and retries

  • A payment expires expires_in_minutes after creation (default 60, from 5 to 43200). Opening checkout_url after that shows "This payment has expired" (HTTP 410), and the payment becomes expired.
  • Opening checkout_url for a payment that is already paid shows "Already paid" instead of charging twice.
  • A failed or canceled payment can be paid again at the same checkout_url until it expires. To let the customer retry, send them back to it: this keeps one payment per order.
  • If you create a new payment for the order instead (with a new Idempotency-Key), the customer could still complete the old one in another tab. Treat any payment.succeeded for the order as paid, and refund a duplicate.
  • After expires_at, create a new payment.
  • If the card form can't load or the payment can't start, the page shows the error with a Try again button.

Checkout language

Pass "language": "ar" to show checkout in Arabic (right to left) or "language": "en" for English. The customer can still switch language on the page.