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

# Render Payment Methods

> Render only the payment methods that AvraAPI has made public, configured, and usable for the project.

`renderMethods()` turns safe server-generated availability data into selectable payment-method cards. It never calls AvraAPI from the browser and it never receives Project credentials or Gateway Vault secrets.

The safest default is to omit `methods`. Payment Elements then reads the availability bootstrap your backend placed in the page and shows only methods that are currently configured and available.

## Default: server-discovered methods

On your backend, call `availability()` with the same trusted merchant domain that you use when creating the order. Place the resulting browser-safe availability payload next to the mount target. Select the backend implementation that renders your checkout page.

<Tabs>
  <Tab title="PHP">
    ```php theme={null}
    <?php

    // AvraAPI SDK call — runs only on your backend.
    $availability = $apix->payment()
        ->availability('shop.example.com')
        ->toElementsPayload();
    ?>

    <div id="payment-methods"></div>
    <script
      id="payment-methods--avraapi-payment-availability"
      type="application/json"
      data-avraapi-payment-availability-for="payment-methods"
    ><?= htmlspecialchars(json_encode(
        $availability,
        JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR,
    ), ENT_NOQUOTES, 'UTF-8') ?></script>
    ```
  </Tab>

  <Tab title="Laravel">
    **Your checkout controller**

    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use Avraapi\Apix\ApixClient;
    use Illuminate\Contracts\View\View;

    final class CheckoutController
    {
        public function show(ApixClient $apix): View
        {
            // AvraAPI SDK call — runs only on your backend.
            $availability = $apix->payment()
                ->availability('shop.example.com')
                ->toElementsPayload();

            return view('checkout.show', compact('availability'));
        }
    }
    ```

    **Your Blade view**

    ```blade theme={null}
    <div id="payment-methods"></div>
    <script
      id="payment-methods--avraapi-payment-availability"
      type="application/json"
      data-avraapi-payment-availability-for="payment-methods"
    >{!! json_encode(
      $availability,
      JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR,
    ) !!}</script>
    ```
  </Tab>

  <Tab title="Node.js">
    **Your Node.js checkout route**

    ```ts theme={null}
    import express from 'express';

    const app = express();

    app.get('/checkout', async (_request, response, next) => {
      try {
        // AvraAPI request — runs only on your backend.
        const url = new URL('https://avraapi.com/api/v1/payments/availability');
        url.searchParams.set('merchant_domain', 'shop.example.com');

        const availabilityResponse = await fetch(url, {
          headers: {
            Accept: 'application/json',
            'X-API-KEY': process.env.AVRAAPI_PROJECT_CLIENT_ID!,
            'X-API-SECRET': process.env.AVRAAPI_PROJECT_CLIENT_SECRET!,
            'X-ENV': process.env.AVRAAPI_ENV ?? 'dev',
          },
        });

        const envelope = await availabilityResponse.json();
        if (!availabilityResponse.ok || envelope.success !== true) {
          throw new Error('Payment availability could not be loaded.');
        }

        // The API data is already the browser-safe Elements availability payload.
        const availabilityJson = JSON.stringify(envelope.data)
          .replace(/</g, '\\u003c')
          .replace(/>/g, '\\u003e')
          .replace(/&/g, '\\u0026')
          .replace(/\u2028/g, '\\u2028')
          .replace(/\u2029/g, '\\u2029');

        response.render('checkout', { availabilityJson });
      } catch (error) {
        next(error);
      }
    });
    ```

    **Your EJS view**

    ```ejs theme={null}
    <div id="payment-methods"></div>
    <script
      id="payment-methods--avraapi-payment-availability"
      type="application/json"
      data-avraapi-payment-availability-for="payment-methods"
    ><%- availabilityJson %></script>
    ```
  </Tab>
</Tabs>

Then mount the methods without a `methods` array:

```js theme={null}
elements.renderMethods('#payment-methods', {
  title: 'Choose a secure payment method',
  theme: 'dark',
  accentColor: '#3fcef1',
  backgroundColor: '#0c1631',
  borderColor: '#3a485f',
});
```

The browser receives only safe public method metadata. It does not receive Vault profile credentials, the selected provider environment as a trusted input, or a completion context.

<Note>
  The PHP SDK also has `renderMethods()`, which can render a standalone secret-free mount for a simple server-rendered page. For a full checkout that calls `elements.checkout()`, use one browser-created Elements instance and the availability bootstrap pattern above so that the form, selection, and checkout share the same instance.
</Note>

## Production priority and environment safety

When a gateway has both active Production and Sandbox Vault profiles, AvraAPI resolves Production by default. A trusted SDK environment override or a trusted explicit server-side selection can override that resolution.

Elements does not choose the profile. An `environment` value on a method only controls the visible **Testing mode** badge, and it is deliberately omitted from `selected()` and from the `createOrder` callback. Your backend must resolve and bind the actual gateway environment during `createOrder()`.

## All `renderMethods()` options

| Option | Type | Default | Behaviour |
| - | - | - | - |
| `title` | string | `Choose payment method` | Heading above method cards. |
| `theme` | `light`, `dark`, `auto` | `auto` | Shared appearance option. Use explicit `light` or `dark` for a fixed surface. |
| `accentColor` | CSS colour | `#2563eb` | Selected-card, focus, and radio-control accent. |
| `backgroundColor` | CSS colour | Theme default | Component and selected-radio inner background. |
| `textColor` | CSS colour | Theme default | Primary card text. |
| `borderColor` | CSS colour | Theme default | Component and method-card borders. |
| `padding` | CSS length | `20px` | Outer selector padding. |
| `availability` | Public availability object | Server bootstrap when present | Explicit availability takes precedence over the bootstrap. Its `methods` are used when no `methods` array is supplied. |
| `methods` | method array | `availability.methods` | Explicit cards. An empty or unsupported list produces the safe unavailable state. |
| `defaultGateway` | supported gateway code | First available card | Chooses a preferred available card. If it is unavailable or absent, the first available card is selected. |

The `auto` theme option uses the package's default visual tokens. It does not dynamically switch between light and dark according to the buyer's operating-system preference.

## Explicit methods

Use explicit methods only when your server has already determined that the cards are safe to show. The supported gateway codes are `payhere`, `marxpay`, `directpay`, `payplus`, `webxpay`, `koko`, `onepay`, and `stripe`.

```js theme={null}
elements.renderMethods('#payment-methods', {
  title: 'Pay securely with',
  defaultGateway: 'payhere',
  methods: [
    {
      gateway: 'payhere',
      mode: 'overlay',
      label: 'Credit or debit card — PayHere',
      description: 'Secure payment opens in a provider overlay.',
      available: true,
      providerOptions: {},
    },
    {
      gateway: 'directpay',
      mode: 'embedded',
      label: 'Credit or debit card — DirectPay',
      description: 'Secure checkout opens below the method list.',
      available: false,
      unavailableReason: 'DirectPay is not configured for this checkout.',
    },
  ],
});
```

### Explicit method fields

| Field | Required | Description |
| - | - | - |
| `gateway` | Yes | One of the supported gateway codes above. Unsupported gateway codes are not rendered. |
| `mode` | Recommended | The checkout mode to send to your backend endpoint, such as `redirect`, `redirect_form`, `overlay`, or `embedded`. The server still validates it against the selected gateway. |
| `modes` | Alternative | If `mode` is absent, the first entry is used. Prefer a single explicit `mode` for predictable buyer behaviour. |
| `label` | No | Card title. Defaults to the gateway's display name. |
| `description` | No | Buyer-facing card text when the method is available. Defaults to `Secure checkout`. |
| `available` | No | Set to `false` to render a disabled card. Defaults to available. |
| `unavailableReason` | No | Buyer-facing explanation for a disabled card. Defaults to `Unavailable`. Never expose a secret or raw provider error here. |
| `providerOptions` or `provider_options` | No | Public, gateway-specific UI options passed back in `selected()` and `createOrder`. Validate and allow-list them on your server. |
| `environment` or `gatewayEnvironment` | No | Shows the display-only **Testing mode** badge for `sandbox`, `test`, or `testing`. It does not select or authorize the gateway environment. |

`hosted_session` is accepted only for legacy compatibility and is normalized to `redirect`. New integrations should use `redirect`.

## MarxPay payment-method variants

MarxPay is the current gateway whose public availability can contain more than one payment-method card for the same gateway. Each card uses `gateway: 'marxpay'` and `mode: 'redirect'`; its `providerOptions.payment_method` value tells your backend which MarxPay option the buyer selected.

There are **five** supported MarxPay values:

| `payment_method` | Buyer-facing purpose | Supported currency |
| - | - | - |
| `OTHER` | Visa, Mastercard, or UnionPay cards | LKR |
| `AMEX` | American Express, Discover, or Diners Club | LKR |
| `OTHER_USD` | Visa, Mastercard, or UnionPay cards | USD |
| `AMEX_USD` | American Express, Discover, or Diners Club | USD |
| `PAY_BY_BANK_ACCOUNT` | Bank-account payment, when enabled for your MarxPay account | Provider/account dependent |

### Let AvraAPI choose the safe set

The usual choice is still the server-discovered pattern shown above. When the MarxPay Vault profile enables several variants, `availability()` returns them as separate safe cards with the correct `provider_options.payment_method` value. It also omits a variant that the project has not enabled.

If you create a MarxPay order without a `payment_method`, AvraAPI uses `OTHER` for an LKR order and `OTHER_USD` for a USD order. Those defaults do not make a disabled Vault option available.

### Show only the variants you want

You can deliberately render one MarxPay card, or several separate cards. For example, this selector offers LKR cards, USD cards, and bank-account payment as distinct buyer choices:

```js theme={null}
elements.renderMethods('#payment-methods', {
  title: 'Choose a payment method',
  theme: 'dark',
  accentColor: '#3fcef1',
  methods: [
    {
      gateway: 'marxpay',
      mode: 'redirect',
      label: 'Cards — LKR',
      description: 'Visa, Mastercard, or UnionPay in LKR.',
      available: true,
      providerOptions: { payment_method: 'OTHER' },
    },
    {
      gateway: 'marxpay',
      mode: 'redirect',
      label: 'American Express — LKR',
      description: 'American Express, Discover, or Diners Club in LKR.',
      available: true,
      providerOptions: { payment_method: 'AMEX' },
    },
    {
      gateway: 'marxpay',
      mode: 'redirect',
      label: 'Cards — USD',
      description: 'Visa, Mastercard, or UnionPay in USD.',
      available: true,
      providerOptions: { payment_method: 'OTHER_USD' },
    },
    {
      gateway: 'marxpay',
      mode: 'redirect',
      label: 'American Express — USD',
      description: 'American Express, Discover, or Diners Club in USD.',
      available: true,
      providerOptions: { payment_method: 'AMEX_USD' },
    },
    {
      gateway: 'marxpay',
      mode: 'redirect',
      label: 'Pay by bank account',
      description: 'Available for eligible MarxPay merchant accounts.',
      available: true,
      providerOptions: { payment_method: 'PAY_BY_BANK_ACCOUNT' },
    },
  ],
});
```

To show only USD card checkout, supply only the `OTHER_USD` object. To offer a smaller list, remove the variants that do not fit the order currency or your checkout design. With multiple MarxPay cards, the first available card is selected by default; `defaultGateway` cannot distinguish variants because they all use the same `marxpay` gateway code.

<Warning>
  These cards are a buyer-interface choice, not authorization. Your backend must pass the selected `providerOptions` into `createOrder()` and AvraAPI validates the value, the order currency, and the enabled payment methods in the project's MarxPay Vault profile. Do not mark a method available merely because it looks suitable in the browser.
</Warning>

### Other gateway-specific behavior

No other currently released gateway exposes selectable payment-method variants through `renderMethods()` in the same way. DirectPay can be rendered as separate **overlay** or **embedded** cards, but that is a checkout-mode choice rather than a provider payment-method option. PayHere accepts selected optional checkout fields, and DirectPay supports an opt-in customer prefill; neither creates multiple gateway-specific payment-method cards. Other gateways currently return one provider-managed method list or one checkout card.

## Read and manage the selector

`renderMethods()` returns a controller. Use it for UI state; do not use it as payment confirmation.

```js theme={null}
const methods = elements.renderMethods('#payment-methods');

console.log(methods.selected());
// {
//   gateway: 'payhere',
//   mode: 'overlay',
//   label: 'PayHere',
//   providerOptions: {}
// }

console.log(methods.availability());

// Mark every card unavailable after a trusted server response.
methods.markUnavailable({
  reason: 'upg_entitlement_inactive',
  message: 'Payment methods are no longer available. Refresh the checkout.',
});
```

The controller provides `selected()`, `availability()`, `markUnavailable({ reason, message })`, `mount()`, and `destroy()`.

## Unavailable and stale-page behaviour

Payment Elements disables any explicit method with `available: false`. If no selectable method remains, it renders an accessible unavailable panel and emits `payment_methods_unavailable`.

It also converts these fresh server availability failures from your `createOrder` callback into a safe disabled state:

| Server error code | Browser error code | Buyer-safe message |
| - | - | - |
| `upg_entitlement_inactive` | `elements_upg_entitlement_inactive` | Payment methods are no longer available. Refresh the checkout. |
| `upg_gateway_not_entitled` | `elements_upg_gateway_not_entitled` | Payment methods are no longer available. Refresh the checkout. |
| `payment_configuration_not_available` | `elements_payment_configuration_not_available` | Payment methods are no longer available. Refresh the checkout. |
| `project_paused` | `elements_project_paused` | Payment methods are no longer available. Refresh the checkout. |

This covers a page that was rendered before a UPG slot was released, an entitlement changed, a configuration was removed, or a project was paused. Refresh server-generated availability before allowing another checkout attempt.

Credential failures are intentionally collapsed into the generic browser error `elements_checkout_failed`. Diagnose the actual credential problem only on your backend.

Continue with [Checkout lifecycle, events & errors](/universal-payment-gateway/payment-elements/checkout-lifecycle).

## Visual & Samples

[Open the Visual Playground — Payment Methods](/universal-payment-gateway/payment-elements/visual-playground#payment-methods) to explore the real method-card UI without API keys. It lets you switch the documentation theme, preview Sandbox versus Live badges, inspect the safe server-discovered example, define gateway cards, choose supported checkout modes, and copy the generated `renderMethods()` code. The playground uses static browser-safe example data only; it does not inspect a real project's UPG slot, Vault profile, or gateway health.


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