# GlomoPay Unified SDK (Web)

Official JavaScript SDK for integrating GlomoPay payment checkout flows into your web applications. The Unified SDK supports standard payments, LRS (Liberalised Remittance Scheme) payments, subscriptions, and payment sessions — automatically detecting the correct flow based on your configuration.

## Prerequisites

Before using this SDK, you need:

- API credentials (Public Key) from your GlomoPay [dashboard](https://app.glomopay.com/api-keys-and-webhooks/api-keys)
- A created order (`order_id`), subscription (`subscription_id`), or payment session (`paymentSessionId` + `paymentSessionToken`)
- A modern browser with ES module support


## Installation

Import the SDK directly from the hosted URL using an ES module import:


```html
<script type="module">
  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';
</script>
```

## Integration Guides

The SDK supports three integration journeys. Choose the one that matches your use case.

### Journey 1: Payment Session (Integrated LRS & KYC)

Use this when you have created a payment session via the GlomoPay API. Payment sessions bundle the LRS checkout flow with integrated KYC verification. When using a payment session, you do **not** need a `publicKey` or `orderId`.

**What you need:**

- `paymentSessionId` — the payment session ID returned when you [create a payment session](/api-documentation/apis/openapi/payment_session/createpaymentsession)
- `paymentSessionToken` — the JWT token returned alongside the payment session ID


HTML

```html
<button id="pay-button">Pay with Payment Session</button>
<script type="module">
  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';

  const checkout = new GlomoCheckoutApi({
    paymentSessionId: 'ps_679a16457aP6K',                  // Payment session ID from your server
    paymentSessionToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI...',  // JWT token from your server
  });

  checkout.on('payment.success', function (response) {
    console.log('Payment successful:', response);
    // response contains: { paymentId, orderId, signature, status }
  });

  checkout.on('payment.failure', function (response) {
    console.log('Payment failed:', response);
  });

  checkout.on('checkout.closed', function () {
    console.log('Checkout closed by user');
  });

  document.getElementById('pay-button').addEventListener('click', () => {
    checkout.open();
  });
</script>
```

React (TS)

```tsx
import { FC, useEffect, useRef } from "react";

interface PaymentSessionButtonProps {
  paymentSessionId: string;
  paymentSessionToken: string;
}

const PaymentSessionButton: FC<PaymentSessionButtonProps> = ({
  paymentSessionId,
  paymentSessionToken,
}) => {
  const buttonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    let checkout: any;

    const loadCheckout = async () => {
      try {
        const { GlomoCheckoutApi } = await import(
          'https://unified-checkout-sdk-prod.web.app/index.js'
        );

        checkout = new GlomoCheckoutApi({ paymentSessionId, paymentSessionToken });

        checkout.on('payment.success', (response: unknown) => {
          console.log('Payment successful:', response);
        });

        checkout.on('payment.failure', (response: unknown) => {
          console.error('Payment failed:', response);
        });

        checkout.on('checkout.closed', () => {
          console.log('Checkout closed by user');
        });

        if (buttonRef.current) {
          buttonRef.current.addEventListener('click', () => checkout.open());
        }
      } catch (error) {
        console.error('Error loading the checkout SDK:', error);
      }
    };

    loadCheckout();
  }, [paymentSessionId, paymentSessionToken]);

  return <button ref={buttonRef}>Pay now</button>;
};
```

### Journey 2: Order Checkout (Standard & LRS)

Use this when you have created an order via the GlomoPay API and have an `orderId`. The SDK automatically detects whether the order is a standard payment or an LRS payment — no additional configuration is needed.

**What you need:**

- `publicKey` — your public key from the GlomoPay [dashboard](https://app.glomopay.com/api-keys-and-webhooks/api-keys)
- `orderId` — the order ID returned when you [create an order](/product-guide/payin/order) on your server (starts with `order_`)


HTML

```html
<button id="buy-button">Buy Now</button>
<script type="module">
  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';

  const checkout = new GlomoCheckoutApi({
    publicKey: 'live_687b0151Bid24PAI', // Your public key from the dashboard
    orderId: 'order_679a16457aP6K',     // Order ID from your server
  });

  // Called when the payment completes successfully.
  // Send the response to your server for signature verification.
  checkout.on('payment.success', function (response) {
    console.log('Payment successful:', response);
    // response contains: { paymentId, orderId, signature, status }
  });

  // Called when the payment fails.
  checkout.on('payment.failure', function (response) {
    console.log('Payment failed:', response);
    // response contains: { paymentId, orderId, signature, status }
  });

  // Called when the user closes the checkout without completing payment.
  checkout.on('checkout.closed', function () {
    console.log('Checkout closed by user');
  });

  // For bank transfer payments only:
  // Emitted when the user submits their bank transfer details.
  // This does NOT mean the payment is complete — wait for a webhook to confirm.
  checkout.on('payment.bank_transfer_submitted', function (response) {
    console.log('Bank transfer submitted:', response);
    // response contains: { orderId, senderAccountNumber, transactionReference }
  });

  document.getElementById('buy-button').addEventListener('click', () => {
    checkout.open();
  });
</script>
```

React (TS)

```tsx
import { FC, useEffect, useRef } from "react";

interface CheckoutButtonProps {
  orderId: string;
  publicKey: string;
}

const CheckoutButton: FC<CheckoutButtonProps> = ({ orderId, publicKey }) => {
  const buttonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    let checkout: any;

    const loadCheckout = async () => {
      try {
        const { GlomoCheckoutApi } = await import(
          'https://unified-checkout-sdk-prod.web.app/index.js'
        );

        checkout = new GlomoCheckoutApi({ orderId, publicKey });

        checkout.on('payment.success', (response: unknown) => {
          console.log('Payment successful:', response);
        });

        checkout.on('payment.failure', (response: unknown) => {
          console.error('Payment failed:', response);
        });

        checkout.on('checkout.closed', () => {
          console.log('Checkout closed by user');
        });

        if (buttonRef.current) {
          buttonRef.current.addEventListener('click', () => checkout.open());
        }
      } catch (error) {
        console.error('Error loading the checkout SDK:', error);
      }
    };

    loadCheckout();
  }, [orderId, publicKey]);

  return <button ref={buttonRef}>Pay now</button>;
};
```

#### Order Checkout with `callbackUrl`

If you prefer to handle the payment result server-side via a redirect instead of in JavaScript, pass `callbackUrl`. The SDK will redirect the user to your URL with the payment result as query parameters. Terminal `.on()` events (`payment.success`, `payment.failure`, `payment.bank_transfer_submitted`) will not fire.

HTML

```html
<button id="buy-button">Buy Now</button>
<script type="module">
  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';

  const checkout = new GlomoCheckoutApi({
    publicKey: 'live_687b0151Bid24PAI',                      // Your public key from the dashboard
    orderId: 'order_679a16457aP6K',                          // Order ID from your server
    callbackUrl: 'https://yourwebsite.com/payment-result',   // Redirect URL for payment result
  });

  // No payment.success or payment.failure listeners needed —
  // when callbackUrl is set, the SDK redirects instead of emitting these events.

  // checkout.closed still fires, so you can handle abandonment.
  checkout.on('checkout.closed', function () {
    console.log('Checkout closed by user');
  });

  document.getElementById('buy-button').addEventListener('click', () => {
    checkout.open();
  });
</script>
```

React (TS)

```tsx
import { FC, useEffect, useRef } from "react";

interface CheckoutButtonProps {
  orderId: string;
  publicKey: string;
  callbackUrl: string;
}

const CheckoutButton: FC<CheckoutButtonProps> = ({ orderId, publicKey, callbackUrl }) => {
  const buttonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    let checkout: any;

    const loadCheckout = async () => {
      try {
        const { GlomoCheckoutApi } = await import(
          'https://unified-checkout-sdk-prod.web.app/index.js'
        );

        checkout = new GlomoCheckoutApi({ orderId, publicKey, callbackUrl });

        // No payment.success or payment.failure listeners needed —
        // when callbackUrl is set, the SDK redirects instead of emitting these events.

        // checkout.closed still fires, so you can handle abandonment.
        checkout.on('checkout.closed', () => {
          console.log('Checkout closed by user');
        });

        if (buttonRef.current) {
          buttonRef.current.addEventListener('click', () => checkout.open());
        }
      } catch (error) {
        console.error('Error loading the checkout SDK:', error);
      }
    };

    loadCheckout();
  }, [orderId, publicKey, callbackUrl]);

  return <button ref={buttonRef}>Pay now</button>;
};
```

### Journey 3: Subscription Checkout

Use this when you have created a subscription and want to collect the first payment or set up recurring billing.

**What you need:**

- `publicKey` — your public key from the GlomoPay [dashboard](https://app.glomopay.com/api-keys-and-webhooks/api-keys)
- `subscriptionId` — the subscription ID returned when you [create a subscription](/product-guide/payin/subscriptions) on your server


> **Important:** You cannot pass both `orderId` and `subscriptionId` — use one or the other.


HTML

```html
<button id="subscribe-button">Subscribe</button>
<script type="module">
  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';

  const checkout = new GlomoCheckoutApi({
    publicKey: 'live_687b0151Bid24PAI',    // Your public key from the dashboard
    subscriptionId: 'sub_679a16457aP6K',   // Subscription ID from your server
  });

  checkout.on('payment.success', function (response) {
    console.log('Subscription payment successful:', response);
    // response contains: { paymentId, orderId, signature, status }
  });

  checkout.on('payment.failure', function (response) {
    console.log('Subscription payment failed:', response);
  });

  checkout.on('checkout.closed', function () {
    console.log('Checkout closed by user');
  });

  document.getElementById('subscribe-button').addEventListener('click', () => {
    checkout.open();
  });
</script>
```

React (TS)

```tsx
import { FC, useEffect, useRef } from "react";

interface SubscribeButtonProps {
  subscriptionId: string;
  publicKey: string;
}

const SubscribeButton: FC<SubscribeButtonProps> = ({ subscriptionId, publicKey }) => {
  const buttonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    let checkout: any;

    const loadCheckout = async () => {
      try {
        const { GlomoCheckoutApi } = await import(
          'https://unified-checkout-sdk-prod.web.app/index.js'
        );

        checkout = new GlomoCheckoutApi({ publicKey, subscriptionId });

        checkout.on('payment.success', (response: unknown) => {
          console.log('Subscription payment successful:', response);
        });

        checkout.on('payment.failure', (response: unknown) => {
          console.error('Subscription payment failed:', response);
        });

        checkout.on('checkout.closed', () => {
          console.log('Checkout closed by user');
        });

        if (buttonRef.current) {
          buttonRef.current.addEventListener('click', () => checkout.open());
        }
      } catch (error) {
        console.error('Error loading the checkout SDK:', error);
      }
    };

    loadCheckout();
  }, [subscriptionId, publicKey]);

  return <button ref={buttonRef}>Subscribe</button>;
};
```

## Configuration Reference

The `GlomoCheckoutApi` constructor accepts a configuration object. The required parameters depend on which journey you are using:

| Parameter | Type | Journey 1 (Payment Session) | Journey 2 (Order) | Journey 3 (Subscription) | Description |
|  --- | --- | --- | --- | --- | --- |
| `paymentSessionId` | `string` | Required | Not used | Not used | Payment session identifier. |
| `paymentSessionToken` | `string` | Required | Not used | Not used | JWT token for the payment session. Required when `paymentSessionId` is provided. |
| `publicKey` | `string` | Not used | Required | Required | Your GlomoPay public key. If it starts with `test_`, mock mode is enabled automatically. |
| `orderId` | `string` | Not used | Required | Not used | The order ID for this transaction. Must start with `order_`. |
| `subscriptionId` | `string` | Not used | Not used | Required | Subscription ID for subscription payments. Cannot be used together with `orderId`. |
| `callbackUrl` | `string` | Optional | Optional | Optional | URL to redirect the user after a terminal payment event (`payment.success`, `payment.failure`, `payment.bank_transfer_submitted`). When set, `.on()` listeners for these events are **not invoked** — only the redirect fires. Must be an `http:` or `https:` URL. |


**Validation rules:**

- **Journey 1:** Both `paymentSessionId` and `paymentSessionToken` are required. `publicKey` and `orderId` are not needed
- **Journey 2:** Both `publicKey` and `orderId` are required
- **Journey 3:** Both `publicKey` and `subscriptionId` are required. Cannot be combined with `orderId`
- **callbackUrl (all journeys):** When provided, the SDK navigates the parent window to this URL on payment completion, failure, or bank transfer submission. The event payload fields are appended as query string parameters. Terminal event listeners (`.on('payment.success', ...)`, `.on('payment.failure', ...)`, `.on('payment.bank_transfer_submitted', ...)`) are **not invoked** — handle the result server-side via the redirect query parameters instead. Non-terminal events (`checkout.closed`) still fire normally. Only `http:` and `https:` URLs are accepted.


## API Reference

### Methods

#### `open(): Promise<void>`

Opens the checkout in a modal overlay. Automatically detects the correct checkout flow (standard, LRS, or payment session) based on your configuration. A loading spinner is shown while initializing.


```javascript
await checkout.open();
```

#### `close(): void`

Closes the checkout and cleans up all resources. Triggers the `checkout.closed` event.


```javascript
checkout.close();
```

#### `on(event, callback): () => void`

Registers an event listener. Returns an unsubscribe function to remove the listener.


```javascript
const unsubscribe = checkout.on('payment.success', (data) => {
  console.log('Payment succeeded:', data);
});

// Later: remove the listener
unsubscribe();
```

### Events

| Event | Description | Payload |
|  --- | --- | --- |
| `payment.success` | Payment completed successfully. **Not emitted when `callbackUrl` is set.** | `{ paymentId, orderId, signature, status }` |
| `payment.failure` | Payment failed. **Not emitted when `callbackUrl` is set.** | `{ paymentId, orderId, signature, status }` |
| `payment.bank_transfer_submitted` | User submitted bank transfer details. **Does NOT confirm payment** — await a webhook for confirmation. **Not emitted when `callbackUrl` is set.** | `{ orderId, senderAccountNumber, transactionReference }` |
| `checkout.closed` | Checkout was closed by the user or programmatically | `{}` |


### Using `callbackUrl` (Redirect Mode)

Instead of handling terminal payment events in JavaScript, you can pass a `callbackUrl` to redirect the user after payment completion. The SDK navigates the parent window to `callbackUrl` with the event payload fields appended as query string parameters. When `callbackUrl` is set:

- `.on()` listeners for `payment.success`, `payment.failure`, and `payment.bank_transfer_submitted` are **not invoked**. `callbackUrl` and JS callbacks are mutually exclusive for terminal payment events — this prevents merchants from double-handling the same event.
- Non-terminal events (`checkout.closed`) continue to fire `.on()` listeners normally.
- Only `http:` and `https:` callback URLs are accepted.


**Query parameters by event:**

| Event | Query parameters appended |
|  --- | --- |
| `payment.success` | `paymentId`, `orderId`, `status`, `signature` |
| `payment.failure` | `paymentId`, `orderId`, `status`, `signature`, `customerErrorMessage` (when available) |
| `payment.bank_transfer_submitted` | `orderId`, `status`, `senderAccountNumber`, `transactionReference` |


`status` reflects the payment lifecycle and varies by payment method (e.g. `success`, `pending`, `failed`, `bank_transfer_submitted`). Treat it as an opaque string and rely on the event type for branching logic.

**Example redirect after successful payment:**


```
https://yourwebsite.com/payment-result?paymentId=payment_abc123&orderId=order_abc123&status=success&signature=abc123def456
```

> **Important:** Verify the `signature` query parameter on your server exactly as you would for the `.on()` payload. See [Signature Verification](#signature-verification).


> **Note:** This differs from the checkout-sdk, which emits `.on()` events **and** redirects when `callbackUrl` is set. The Unified SDK only redirects — registering `.on()` listeners for terminal events alongside `callbackUrl` has no effect.


### Payload Types


```typescript
interface PaymentPayload {
  paymentId: string;   // Payment ID from GlomoPay
  orderId: string;     // Order identifier (e.g., "order_abc123")
  signature: string;   // Signature for server-side verification
  status: string;      // Payment status
}

interface BankTransferPayload {
  orderId: string;                // The order ID
  senderAccountNumber: string;    // Account number provided by the user
  transactionReference: string;   // Reference number provided by the user
}
```

## Signature Verification

After receiving a `payment.success` or `payment.failure` event, verify the `signature` on your server using your secret key. Send the response payload to your server and generate the signature using:


```
data = order_id + "|" + payment_id + "|" + status
signature = HMAC-SHA256(data, secret_key)
```

For detailed instructions and sample code in PHP, Ruby, Go, JavaScript, and Python, see [Verify checkout response and signature](/product-guide/payin/checkout#steps-to-integrate-checkout).

## Troubleshooting

### Mock Mode Testing

Mock mode is automatically enabled based on your `publicKey`:

- If your `publicKey` starts with `test_`, mock mode is enabled and the checkout URL will include `mode=mock`
- Otherwise, live mode is used


**Example:**


```javascript
// Mock mode (publicKey starts with "test_")
const checkout = new GlomoCheckoutApi({
  publicKey: 'test_abc123',
  orderId: 'order_xyz789',
});

// Live mode
const checkout = new GlomoCheckoutApi({
  publicKey: 'live_abc123',
  orderId: 'order_xyz789',
});
```

### Checkout Popup Blocked

If the checkout fails to open in production, your website's `Cross-Origin-Opener-Policy` header may be blocking popups. See [Checkout Popup Blocked in Production Environment](/product-guide/payin/checkout#checkout-popup-blocked-in-production-environment) for solutions.

### CORS Error on Checkout Load

If you see CORS errors in the browser console when the checkout loads, browser extensions may be interfering. See [CORS Error on Checkout Load](/product-guide/payin/checkout#cors-error-on-checkout-load-preferences-api-failure) for troubleshooting steps.

### Content Blocked by Content Security Policy

If the checkout iframe or SDK scripts fail to load due to CSP headers, you need to allow the Glomo domains in your CSP configuration. See [Content Blocked by CSP](/product-guide/payin/checkout#content-blocked-by-content-security-policy-csp) for setup instructions.

### Handling Abandoned Checkouts

To detect when a user closes the checkout without completing payment, listen for the `checkout.closed` event:


```javascript
let paymentCompleted = false;

checkout.on('payment.success', () => {
  paymentCompleted = true;
});

checkout.on('payment.failure', () => {
  paymentCompleted = true;
});

checkout.on('checkout.closed', () => {
  if (!paymentCompleted) {
    console.log('User closed checkout without completing payment');
    // Prompt the user to retry or log the abandonment
  }
});
```

> When using `callbackUrl`, the `checkout.closed` event still fires, so this pattern works regardless of whether you are using redirect mode or event-listener mode.