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

# Payment Initiation

> Create a provider-neutral checkout session safely from your backend.

`createOrder()` is the server-side operation used to start a Universal Payment Gateway checkout. It creates a provider-neutral payment session from an **available** payment method and returns the checkout instructions for that gateway.

The integration rules on this page apply to every AvraAPI SDK. Laravel 1.2.0 uses `AvraAPI::payment()` and Node.js SDK 1.2.0 uses `client.payment()` with the same typed payment options and server-only safety boundary.

<Warning>
  Create a pending order in your own database before starting checkout. A UPG session is not proof that a customer paid. Confirm payment later through [Payment Response & Completion](/universal-payment-gateway/advanced-setup/webhooks-and-completion).
</Warning>

## The safe initiation flow

1. Your backend creates a unique pending order.
2. Your server checks `availability()` and selects one returned method.
3. Your server calls `createOrder()`.
4. Your server stores the returned `completionContext` against that pending order.
5. Your browser receives only the public checkout instructions and follows the supported checkout mode.

## 1. Choose a `GatewayCode` from availability

Do not accept a gateway name from an untrusted browser request without checking it against the current server-side availability result. A method can become unavailable when the project is paused, its UPG slot is released, its configuration is disabled, or the selected origin is not allowed.

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

    $availability = $apix->payment()->availability('shop.example.com');

    if (! $availability->isReady()) {
        throw new RuntimeException($availability->message ?? 'Payments are not available.');
    }

    // Choose a method returned by availability() for this configured domain.
    // A real application can match this against its own allowed checkout choices.
    $selectedMethod = null;
    foreach ($availability->methods() as $method) {
        if (($method['gateway'] ?? null) === 'payhere'
            && ($method['mode'] ?? null) === 'redirect') {
            $selectedMethod = $method;
            break;
        }
    }

    if ($selectedMethod === null) {
        throw new RuntimeException('PayHere redirect checkout is not currently available.');
    }

    $gateway = GatewayCode::from($selectedMethod['gateway']);
    ```
  </Tab>

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

    use Avraapi\Apix\Payments\GatewayCode;

    $availability = AvraAPI::payment()->availability('shop.example.com');

    if (! $availability->isReady()) {
        throw new RuntimeException($availability->message ?? 'Payments are not available.');
    }

    // Choose a method returned by availability() for this configured domain.
    // A real application can match this against its own allowed checkout choices.
    $selectedMethod = null;
    foreach ($availability->methods() as $method) {
        if (($method['gateway'] ?? null) === 'payhere'
            && ($method['mode'] ?? null) === 'redirect') {
            $selectedMethod = $method;
            break;
        }
    }

    if ($selectedMethod === null) {
        throw new RuntimeException('PayHere redirect checkout is not currently available.');
    }

    $gateway = GatewayCode::from($selectedMethod['gateway']);
    ```
  </Tab>

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

    const gateway = GatewayCode.PayHere; // Select only from fresh server-side availability.
    ```
  </Tab>
</Tabs>

The SDK recognises `payhere`, `marxpay`, `directpay`, `payplus`, `webxpay`, `koko`, `onepay`, and `stripe`. Recognition is not entitlement: `availability()` is the source of truth for what this project can use now.

## 2. Choose the `CheckoutMode`

Use the mode returned by availability for the chosen gateway. Do not invent a mode because a provider supports another style outside AvraAPI.

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

    $mode = CheckoutMode::from($selectedMethod['mode']);
    ```
  </Tab>

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

    $mode = CheckoutMode::from($selectedMethod['mode']);
    ```
  </Tab>

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

    const mode = CheckoutMode.Redirect; // Must match the available server-side method.
    ```
  </Tab>
</Tabs>

| Mode | Meaning |
| - | - |
| `redirect` | The buyer is sent to the provider checkout URL or redirect form. |
| `overlay` | The provider presents a supported overlay checkout. |
| `embedded` | DirectPay renders its official checkout in a merchant container. |
| `hosted_session` | Deprecated compatibility alias. New integrations must use `redirect`. |

## 3. Set order details

`orderId`, `items`, `amount`, and `currency` are required. Use an order ID that is unique in your merchant system. Pass money as a positive decimal **string** with at most two decimal places; never calculate or serialize money as a floating-point value.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    $orderId = 'ORDER-2026-000184';
    $items = 'Annual membership';
    $amount = '12000.00';
    $currency = 'LKR';
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    $orderId = 'ORDER-2026-000184';
    $items = 'Annual membership';
    $amount = '12000.00';
    $currency = 'LKR';
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const orderId = 'ORDER-2026-000184';
    const items = 'Annual membership';
    const amount = '12000.00';
    const currency = 'LKR';
    ```
  </Tab>
</Tabs>

<Tip>
  Calculate the final amount on your backend from trusted product and pricing data. Never accept the order total from browser JavaScript.
</Tip>

## 4. Provide the customer object

The SDK requires `first_name`, `last_name`, `email`, `phone`, `address`, `city`, and `country`. You may provide `address_line_1` and `address_line_2` instead of `address`; the SDK combines the non-empty lines into the required address value.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    $customer = [
        'first_name' => 'Asha',
        'last_name' => 'Perera',
        'email' => 'asha@example.com',
        'phone' => '+94771234567',
        'address_line_1' => '12 Lake Road',
        'address_line_2' => 'Apartment 4B', // Optional
        'city' => 'Colombo',
        'country' => 'Sri Lanka',
    ];
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    $customer = [
        'first_name' => 'Asha',
        'last_name' => 'Perera',
        'email' => 'asha@example.com',
        'phone' => '+94771234567',
        'address_line_1' => '12 Lake Road',
        'address_line_2' => 'Apartment 4B', // Optional
        'city' => 'Colombo',
        'country' => 'Sri Lanka',
    ];
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const customer = {
      first_name: 'Asha', last_name: 'Perera', email: 'asha@example.com',
      phone: '+94771234567', address_line_1: '12 Lake Road',
      address_line_2: 'Apartment 4B', city: 'Colombo', country: 'Sri Lanka',
    };
    ```
  </Tab>
</Tabs>

Collect and validate this data in your application. Payment Elements can help render a customer form, but it does not replace your server-side validation.

## 5. Set trusted return, cancel, and webhook URLs

Build URLs in your backend from registered configuration. One shared completion handler or controller can serve every gateway when its gateway-scoped callback and return routes dispatch safely. Do not copy a return URL, webhook URL, or host name from a buyer request.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    $gatewayCode = $selectedMethod['gateway']; // Validated server-side from availability().

    $urls = [
        'return_url' => "https://shop.example.com/payments/return/{$gatewayCode}",
        'cancel_url' => "https://shop.example.com/payments/cancel/{$gatewayCode}",
        'notify_url' => "https://shop.example.com/payments/callback/{$gatewayCode}",
    ];
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    $gatewayCode = $selectedMethod['gateway']; // Validated server-side from availability().

    $urls = [
        'return_url' => "https://shop.example.com/payments/return/{$gatewayCode}",
        'cancel_url' => "https://shop.example.com/payments/cancel/{$gatewayCode}",
        'notify_url' => "https://shop.example.com/payments/callback/{$gatewayCode}",
    ];
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const gatewayCode = selectedMethod.gateway; // Validated server-side from availability().
    const urls = {
      return_url: `https://shop.example.com/payments/return/${gatewayCode}`,
      cancel_url: `https://shop.example.com/payments/cancel/${gatewayCode}`,
      notify_url: `https://shop.example.com/payments/callback/${gatewayCode}`,
    };
    ```
  </Tab>
</Tabs>

A return URL improves the buyer experience; it does not prove payment. Your gateway-scoped callback or return route can enter one shared completion handler, which must still call `completePayment()` in your backend. Use the exact configured URL where a gateway requires it; for example, Stripe requires the configured Success Return URL and Dashboard webhook URL.

## 6. Optional: set `merchantDomain`

`merchantDomain` identifies the configured domain for method discovery and origin policy checks. It is optional. When you omit it, AvraAPI resolves the configured project default. Supply it when your checkout uses a specific registered host.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    // Optional. Omit this named argument to use the configured project default.
    $merchantDomain = 'shop.example.com';
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    // Optional. Omit this named argument to use the configured project default.
    $merchantDomain = 'shop.example.com';
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const merchantDomain = 'shop.example.com'; // Optional; otherwise use the project default.
    ```
  </Tab>
</Tabs>

<Note>
  Origin rules are provider-specific. Do not assume that an allowed domain rule for one gateway automatically applies to every other gateway.
</Note>

## 7. Optional: pass `providerOptions`

`providerOptions` holds gateway-specific, non-secret checkout options. It is optional; keep it empty for the common flow. Add only values documented for the selected gateway in [Gateway-wise Functions](/universal-payment-gateway/gateway-wise-functions); never put API keys, gateway secrets, signing secrets, or buyer-controlled arbitrary data here.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    // Optional. Omit this named argument when no gateway-specific option is needed.
    $providerOptions = [];
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    // Optional. Omit this named argument when no gateway-specific option is needed.
    $providerOptions = [];
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const providerOptions = {}; // Optional non-secret fields for the selected gateway.
    ```
  </Tab>
</Tabs>

## 8. Optional: select the gateway environment

Gateway environment selects the vaulted Sandbox or Production profile. It is separate from your AvraAPI client environment and must stay server-side.

### Configure a default environment in PHP or Laravel

For one common environment across all gateways, this is enough:

<Tabs>
  <Tab title="PHP">
    ```ini theme={null}
    APIX_PAYMENT_GATEWAY_ENV=sandbox
    ```
  </Tab>

  <Tab title="Laravel">
    ```ini theme={null}
    APIX_PAYMENT_GATEWAY_ENV=sandbox
    ```
  </Tab>

  <Tab title="Node.js">
    ```ini theme={null}
    APIX_PAYMENT_GATEWAY_ENV=sandbox
    ```
  </Tab>
</Tabs>

### Override selected gateways in PHP or Laravel

Use `APIX_PAYMENT_GATEWAY_ENV_OVERRIDES` only when one or more gateways need a different environment from the default. The format is a comma-separated `gateway:sandbox` or `gateway:production` list.

<Tabs>
  <Tab title="PHP">
    ```ini theme={null}
    APIX_PAYMENT_GATEWAY_ENV=sandbox
    APIX_PAYMENT_GATEWAY_ENV_OVERRIDES=payhere:sandbox,marxpay:sandbox,directpay:sandbox,payplus:sandbox,webxpay:sandbox,koko:sandbox,onepay:sandbox,stripe:sandbox
    ```
  </Tab>

  <Tab title="Laravel">
    ```ini theme={null}
    APIX_PAYMENT_GATEWAY_ENV=sandbox
    APIX_PAYMENT_GATEWAY_ENV_OVERRIDES=payhere:sandbox,marxpay:sandbox,directpay:sandbox,payplus:sandbox,webxpay:sandbox,koko:sandbox,onepay:sandbox,stripe:sandbox
    ```
  </Tab>

  <Tab title="Node.js">
    ```ini theme={null}
    APIX_PAYMENT_GATEWAY_ENV=sandbox
    APIX_PAYMENT_GATEWAY_ENV_OVERRIDES=payhere:sandbox,marxpay:sandbox,directpay:sandbox,payplus:sandbox,webxpay:sandbox,koko:sandbox,onepay:sandbox,stripe:sandbox
    ```
  </Tab>
</Tabs>

The all-sandbox override example is explicit but redundant because the default already selects Sandbox. A practical mixed deployment could set a default to Production and override only the gateways still being tested.

### Override an environment for one order

You can also select a gateway environment for one server-side order. This is optional and takes priority over the configured default and override list. Never let browser input select it.

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

    $gatewayEnvironment = GatewayEnvironment::Sandbox;
    ```
  </Tab>

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

    $gatewayEnvironment = GatewayEnvironment::Sandbox;
    ```
  </Tab>

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

    const gatewayEnvironment = GatewayEnvironment.Sandbox;
    ```
  </Tab>
</Tabs>

## 9. Call `createOrder()`

Bring the required and optional options together with one named-argument call. Omit optional named arguments when you do not need them.

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

    $session = $apix->payment()->createOrder(new CreateOrderOptions(
        gateway: GatewayCode::from($selectedMethod['gateway']),
        mode: CheckoutMode::from($selectedMethod['mode']),
        orderId: $orderId,
        items: $items,
        amount: $amount,
        currency: $currency,
        customer: $customer,
        urls: $urls,
        merchantDomain: $merchantDomain, // Optional
        providerOptions: $providerOptions, // Optional
        gatewayEnvironment: GatewayEnvironment::Sandbox, // Optional
    ));
    ```
  </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;
    use Avraapi\Apix\Payments\GatewayEnvironment;

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

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

    const session = await client.payment().createOrder(new CreateOrderOptions({
      gateway, mode, orderId, items, amount, currency, customer, urls,
      merchantDomain, providerOptions, gatewayEnvironment,
    }));
    ```
  </Tab>
</Tabs>

## Store the `PaymentSession` correctly

`PaymentSession` contains `gateway`, `mode`, `status`, `expiresAt`, `checkout`, `requestId`, `flow`, `binding`, `verification`, and `completionContext`.

Store the `completionContext` with the pending merchant order before responding to the browser. It binds future provider evidence to this payment session.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    // Persist these values using your application's pending-order storage.
    $completionContext = $session->completionContext;
    $gateway = $session->gateway->value;
    $expiresAt = $session->expiresAt;
    $avraApiRequestId = $session->requestId;
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    // Persist these values using your application's pending-order storage.
    $completionContext = $session->completionContext;
    $gateway = $session->gateway->value;
    $expiresAt = $session->expiresAt;
    $avraApiRequestId = $session->requestId;
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    await orders.savePending({
      id: orderId, gateway: session.gateway, completionContext: session.completionContext,
      expiresAt: session.expiresAt, requestId: session.requestId,
    }); // completionContext remains server-only.
    ```
  </Tab>
</Tabs>

<Warning>
  Never send `completionContext` to a browser. Never put it in a redirect URL, frontend state store, analytics event, application log, cache entry, queue payload, or support screenshot.
</Warning>

## Return only public checkout instructions

Your frontend normally needs the gateway, mode, expiry, and `checkout` object. Keep the server-side completion data in your own order record.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    http_response_code(200);
    header('Content-Type: application/json; charset=UTF-8');

    echo json_encode([
        'gateway' => $session->gateway->value,
        'mode' => $session->mode->value,
        'status' => $session->status,
        'expires_at' => $session->expiresAt,
        'checkout' => $session->checkout,
        'request_id' => $session->requestId,
    ], JSON_THROW_ON_ERROR);
    ```
  </Tab>

  <Tab title="Laravel">
    ```php theme={null}
    http_response_code(200);
    header('Content-Type: application/json; charset=UTF-8');

    echo json_encode([
        'gateway' => $session->gateway->value,
        'mode' => $session->mode->value,
        'status' => $session->status,
        'expires_at' => $session->expiresAt,
        'checkout' => $session->checkout,
        'request_id' => $session->requestId,
    ], JSON_THROW_ON_ERROR);
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    return response.json({
      gateway: session.gateway, mode: session.mode, status: session.status,
      expires_at: session.expiresAt, checkout: session.checkout, request_id: session.requestId,
    }); // Never return session.completionContext.
    ```
  </Tab>
</Tabs>

`checkout` is gateway- and mode-specific. Pass it unchanged to the supported browser handoff; do not attempt to construct provider checkout instructions yourself.

Continue with [Payment Response & Completion](/universal-payment-gateway/advanced-setup/webhooks-and-completion).


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