> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avraapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Redirect Checkout

> Create and hand off provider-hosted checkout sessions with the Universal Payment Gateway SDK.

Redirect Checkout sends the buyer from your site to a provider-hosted payment page. It is a **server-side SDK flow**: your server prepares a `PaymentSession`, stores its server-only completion context, then sends the browser to the checkout instruction returned by that session.

<Warning>
  A buyer reaching `return_url` does not prove payment. Verify the provider return or callback on your backend through [Payment Response & Completion](/universal-payment-gateway/advanced-setup/webhooks-and-completion) before fulfilment.
</Warning>

## Redirect capability reference

The **Supported SDK mode** is the value supplied to `CreateOrderOptions`. The **Public checkout type** is the `PaymentSession::$checkout['type']` value returned after the session is prepared. It tells your browser handoff code what to do; it is not an input you choose.

| Gateway | Supported SDK mode | Public checkout type | SDK-only browser handoff |
| - | - | - | - |
| PayHere | `redirect` | `redirect_form` | Render the signed POST form with `RedirectFormRenderer` |
| MarxPay | `redirect` | `redirect` | Redirect the browser to the validated `redirect_url` |
| PayPlus | `redirect` | `redirect` | Redirect the browser to the validated `redirect_url` |
| WebXPay | `redirect` | `redirect` | Redirect the browser to the validated `redirect_url` |
| KOKO | `redirect` | `redirect_form` | Render the signed POST form with `RedirectFormRenderer` |
| OnePay | `redirect` | `redirect` | Redirect the browser to the validated `redirect_url` |
| Stripe | `redirect` | `redirect` | Redirect the browser to the validated `redirect_url` |
| DirectPay | Not supported | — | Use [In-page Checkout](/universal-payment-gateway/advanced-setup/hosted-checkout) instead |

The table describes platform capabilities. A gateway is usable only when it is currently returned by your backend's `availability()` call for the configured domain and selected environment.

## 1. Prepare the session on your backend

Use an available method and request `CheckoutMode::Redirect`. Store the `completionContext` with the pending merchant order before you return any session data to the browser.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    use Avraapi\Apix\Payments\CheckoutMode;
    use Avraapi\Apix\Payments\CreateOrderOptions;
    use Avraapi\Apix\Payments\GatewayCode;

    $session = $apix->payment()->createOrder(new CreateOrderOptions(
        gateway: GatewayCode::from($selectedMethod['gateway']),
        mode: CheckoutMode::Redirect,
        orderId: $orderId,
        items: $items,
        amount: $amount,
        currency: $currency,
        customer: $customer,
        urls: $urls,
        merchantDomain: $merchantDomain, // Optional
        providerOptions: $providerOptions, // Optional
    ));

    // Persist these values with your own pending order record.
    $completionContext = $session->completionContext;
    $gateway = $session->gateway->value;
    $expiresAt = $session->expiresAt;
    $avraApiRequestId = $session->requestId;
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    use Avraapi\Laravel\Facades\AvraAPI;

    use Avraapi\Apix\Payments\CheckoutMode;
    use Avraapi\Apix\Payments\CreateOrderOptions;
    use Avraapi\Apix\Payments\GatewayCode;

    $session = AvraAPI::payment()->createOrder(new CreateOrderOptions(
        gateway: GatewayCode::from($selectedMethod['gateway']),
        mode: CheckoutMode::Redirect,
        orderId: $orderId,
        items: $items,
        amount: $amount,
        currency: $currency,
        customer: $customer,
        urls: $urls,
        merchantDomain: $merchantDomain, // Optional
        providerOptions: $providerOptions, // Optional
    ));

    // Persist these values with your own pending order record.
    $completionContext = $session->completionContext;
    $gateway = $session->gateway->value;
    $expiresAt = $session->expiresAt;
    $avraApiRequestId = $session->requestId;
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    import { CheckoutMode, CreateOrderOptions, GatewayCode } from '@avraapi/node-sdk';

    const session = await client.payment().createOrder(new CreateOrderOptions({
      gateway: selectedMethod.gateway as GatewayCode,
      mode: CheckoutMode.Redirect,
      orderId, items, amount, currency, customer, urls,
      merchantDomain, providerOptions,
    }));

    await orders.savePending({ completionContext: session.completionContext, gateway: session.gateway });
    ```
  </Tab>
</Tabs>

`completionContext` is signed server-only evidence. Never return it to JavaScript, include it in a URL, or store it in browser state.

## 2. Hand off the browser from the session response

The SDK returns the provider instruction inside `$session->checkout`. Do not recreate signatures, fields, or provider URLs yourself.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    use Avraapi\Apix\Payments\RedirectFormRenderer;

    $checkout = $session->checkout;

    if (($checkout['type'] ?? null) === 'redirect_form') {
        // PayHere and KOKO: signed provider form fields are already in $session.
        echo RedirectFormRenderer::render($session, 'Continue to secure payment');
        exit;
    }

    if (($checkout['type'] ?? null) === 'redirect') {
        $url = (string) ($checkout['redirect_url'] ?? '');

        if (! filter_var($url, FILTER_VALIDATE_URL)
            || parse_url($url, PHP_URL_SCHEME) !== 'https') {
            throw new RuntimeException('The payment session did not contain a valid redirect URL.');
        }

        header('Location: '.$url, true, 303);
        exit;
    }

    throw new RuntimeException('The prepared session is not a redirect checkout.');
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    use Avraapi\Apix\Payments\RedirectFormRenderer;

    $checkout = $session->checkout;

    if (($checkout['type'] ?? null) === 'redirect_form') {
        // PayHere and KOKO: signed provider form fields are already in $session.
        echo RedirectFormRenderer::render($session, 'Continue to secure payment');
        exit;
    }

    if (($checkout['type'] ?? null) === 'redirect') {
        $url = (string) ($checkout['redirect_url'] ?? '');

        if (! filter_var($url, FILTER_VALIDATE_URL)
            || parse_url($url, PHP_URL_SCHEME) !== 'https') {
            throw new RuntimeException('The payment session did not contain a valid redirect URL.');
        }

        header('Location: '.$url, true, 303);
        exit;
    }

    throw new RuntimeException('The prepared session is not a redirect checkout.');
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const checkout = session.checkout;

    if (checkout.type === 'redirect') {
      const url = String(checkout.redirect_url ?? '');
      if (new URL(url).protocol !== 'https:') throw new Error('Invalid payment redirect URL.');
      return response.redirect(303, url);
    }

    if (checkout.type === 'redirect_form') {
      // Render an HTML form from the returned action and fields using your escaping helper.
      return response.send(renderEscapedRedirectForm(checkout));
    }

    throw new Error('The prepared session is not a redirect checkout.');
    ```
  </Tab>
</Tabs>

`RedirectFormRenderer` is valid only for a `redirect_form` session. It safely escapes the provider action URL, field names, field values, and submit label. For a `redirect` session, use the exact HTTPS `redirect_url` supplied by the SDK.

## 3. Verify after the provider returns

Your provider callback, webhook, or return handler must load the matching pending order and its stored `completionContext`, preserve the gateway-required payload, then call `completePayment()`. See [Payment Response & Completion](/universal-payment-gateway/advanced-setup/webhooks-and-completion).

## Optional: Payment Elements

[Payment Elements](/universal-payment-gateway/payment-elements/overview) is optional. It can perform the same public browser handoff for `redirect_form` and `redirect` sessions after your backend has completed the SDK flow above. It does not replace `availability()`, `createOrder()`, server-side order storage, or `completePayment()`.

For setup options, safe availability rendering, and browser events, see [Payment Elements](/universal-payment-gateway/payment-elements/overview).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.