# Embedded wallet

> Offer Apple Pay, Google Pay and cards on your own checkout through a secure Ozow iframe, so card data never touches your page. Approved merchants only.

Source: https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet/

> ⚠️ **Limited availability**: Embedded wallets are available to approved merchants only. Contact
> your account manager or [support@ozow.com](mailto:support@ozow.com) to enquire about eligibility
> and onboarding.

**This guide is a Payments API integration.** You create the payment request with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md), authenticate with
your API key, and sign the hash with your private key: the One API Client ID and Client Secret
are not used here. [Prerequisites and
onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md) says which credential
belongs to which API.

## What is an embedded wallet?

The embedded wallet lets you offer digital wallets, Apple Pay and Google Pay, and optionally card
payments, directly on your checkout page. The payment UI renders inside a secure Ozow iframe on your
domain. Your customers never leave your site and sensitive payment data never touches your page.

> ℹ️ **This is the only way to offer Apple Pay and Google Pay as standalone payment options.**
> Digital wallets cannot be triggered via the standalone button method used for other payment
> methods. If you want to offer Apple Pay or Google Pay at checkout, you must use the Wallet SDK.

Not sure which embedded option is right for you? See [Overview: how to choose](https://hub.ozow.com/integration-methods.md).

## Before you start

Before integrating, confirm the following with Ozow:

- **Wallet enablement**: Apple Pay, Google Pay, and/or card must be enabled on your merchant profile
  by Ozow before they will appear
- **Origin allowlist**: every checkout origin (scheme + host + port) where you will load the SDK
  must be registered with Ozow. Requests from unlisted origins will receive `403 Forbidden`
- **Apple Pay domain verification**: if you are offering Apple Pay, each top-level checkout domain
  must be verified (see [Apple Pay setup](#apple-pay-setup) below)
- **Settlement currency**: the amount and currency in your payment request must match your Ozow
  settlement configuration

You will also need:

- An active Ozow merchant account with the relevant payment methods enabled
- A server-side integration to create payment requests
- HTTPS on your checkout domain with a valid TLS certificate

## How the Wallet SDK works

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Your checkout page
    participant S as Wallet SDK
    participant O as Ozow API
    participant I as Ozow iframe

    C->>M: Reaches checkout
    M->>O: Create payment request (server-side)
    O-->>M: Returns requestId
    M->>S: Ozow.init({ requestId }).mount('#container')
    S->>I: Loads wallet buttons inside secure iframe
    C->>I: Taps wallet button or enters card details
    I->>O: Encrypts and submits payment
    O-->>S: Fires authorizationResult event
    S-->>M: Merchant handles outcome
```

## Environments

| Environment | API base URL | iframe origin | Dashboard |
|---|---|---|---|
| Production | `https://pay.ozow.com` | `https://pay.ozow.com` | [dash.ozow.com](https://dash.ozow.com) |
| Staging | `https://stagingpay.ozow.com` | `https://stagingpay.ozow.com` | [stagingdash.ozow.com](https://stagingdash.ozow.com) |

---

## Core integration

### Step 1: Create a payment request (server-side)

Your server creates the payment request with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md). The response returns
`paymentRequestId`, a GUID, which you pass to the SDK as its `requestId` option.

> ⚠️ **Important**: The payment request must be created server-side. Never build the hash, expose
> your private key, or call the payment request endpoint from browser code.

Pass the `requestId` to your checkout page, typically embedded in the page HTML, returned from your
own API, or passed as a query parameter.

> ℹ️ **One request per checkout attempt**: pass a fresh `requestId` for each new payment session. If
> a payment fails and the customer retries, call `destroy()` and create a new payment request
> server-side before reinitialising the SDK.

---

### Step 2: Load the SDK script

Load the SDK from Ozow's CDN. Contact Ozow for the current recommended version and matching SRI hash
for your environment.

```html
<script
  src="https://cdn.ozow.com/sdk/v1/1.7.0/ozow.js"
  integrity="sha384-YOUR_SRI_HASH"
  crossorigin="anonymous"
></script>
```

> ℹ️ **Pinned version**: the SDK URL includes a specific version number. There is no `latest`
> channel, version bumps are an explicit change on your side. This ensures predictable behaviour and
> no silent CDN drift.

> ⚠️ **Single script tag only**: include the SDK script once per page. It exposes a global
> `window.Ozow` object.

---

### Step 3: Add the mount container

Add a container element where the SDK will render the wallet buttons:

```html
<div id="ozow-payment"></div>
```

No additional markup is required. The SDK injects the iframe automatically.

---

### Step 4: Initialise and mount the SDK

```javascript
const requestId = "YOUR_REQUEST_ID"; // From your server

const ozow = Ozow.init({
  requestId: requestId,
  environment: "production", // 'test' | 'production'
  apiBaseUrl: "https://pay.ozow.com",
  iframeBaseUrl: "https://pay.ozow.com",
});

// Listen for events before mounting
ozow.on("ready", (payload) => {
  console.log("SDK ready", payload.availableWallets);
  if (payload.availableWallets.length === 0) {
    // No wallets available on this device - show fallback payment methods
    showFallbackPaymentMethods();
  }
});

ozow.on("authorizationResult", (payload) => {
  switch (payload.status) {
    case "approved":
      // Payment authorised - fulfil order using payload.transactionId
      completeOrder(payload.transactionId);
      break;
    case "cancelled":
      // Customer cancelled - re-enable checkout
      unlockCheckout();
      break;
    case "declined":
    case "error":
    default:
      // Payment failed - show message and allow retry
      showPaymentFailed(payload.reason);
      break;
  }
});

ozow.on("error", (payload) => {
  console.error(payload.error.code, payload.error.message);
  showPaymentFailed(payload.error.message);
});

// Mount the SDK
ozow.mount("#ozow-payment", {
  wallets: ["applePay", "googlePay"],
  // To include card when enabled on your merchant profile:
  // paymentMethods: ['card', 'applePay', 'googlePay'],
});
```

> ⚠️ **Amount lock**: once the customer opens a wallet sheet, the payment amount must not change.
> Listen for the `walletSheetOpened` event and lock your cart at that point.

---

### Step 5: Handle lifecycle events

Subscribe to events using `ozow.on(eventName, handler)` and unsubscribe using `ozow.off`.

**Event catalogue**

| Event | When it fires | Recommended action |
|---|---|---|
| `ready` | iframe loaded and wallet availability known | Enable checkout UI; check `availableWallets` |
| `availability` | Per-provider availability resolved | Telemetry; explain why a button is hidden |
| `walletSheetOpened` | Customer tapped a wallet button | Lock cart amount |
| `walletTokenIssued` | Wallet authorised; token submitted internally | Show "Processing..." indicator |
| `authorizationResult` | Terminal payment outcome | Show the outcome. Fulfil on the notification, not on this |
| `walletSheetClosed` | Wallet sheet dismissed | Re-enable UI if not a terminal outcome |
| `error` | Recoverable or fatal SDK error | Show error message; check `error.code` |

> ℹ️ **Late subscribers**: the SDK replays `ready` and terminal `error` events to handlers
> registered after `mount`. You can safely attach listeners immediately after `init` or defer until
> your SPA hydrates.

**`authorizationResult` is not the confirmation.**

The SDK reaches that outcome by polling from the browser, and it gives up after 60 seconds. It tells
you what the customer's browser saw, which is not the same as what the bank did, and a customer who
closes the tab produces no event at all.

The payment is confirmed the same way every other Ozow payin is: by the notification Ozow sends to
your server. Because this guide creates the payment with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md), that is the Payments API
[transaction notification](https://hub.ozow.com/api-reference/payments-api/webhooks/transaction-notification.md), posted to the
`NotifyUrl` on the request and verified by its `Hash`. Handle it exactly as [Step 4: Handle the
notification response](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/redirect-to-ozow.md#step-4-handle-the-notification-response)
describes: same format, same hash check.

> ⚠️ **Important**: Update the order from the notification. Use `authorizationResult` to move the
> customer's screen on, and nothing else.

**Recommended event handler pattern**

```javascript
ozow.on("walletSheetOpened", () => {
  lockCartAmount();
});

ozow.on("walletTokenIssued", () => {
  showProcessingOverlay();
});

ozow.on("authorizationResult", ({ status, transactionId, reason }) => {
  hideProcessingOverlay();
  switch (status) {
    case "approved":
      return completeOrder(transactionId);
    case "cancelled":
      return unlockCheckout();
    default:
      return showPaymentFailed(reason);
  }
});

ozow.on("error", ({ error }) => {
  hideProcessingOverlay();
  showPaymentFailed(error.message);
});
```

---

### Step 6: Handle retries and cleanup

Call `destroy()` whenever the customer leaves checkout or retries a failed payment. Always create a
new payment request server-side before reinitialising the SDK with a new `requestId`.

```javascript
// Tear down listeners, iframe, and in-flight work
ozow.destroy();

// For a retry - create a new payment request server-side first
const retry = Ozow.init({
  requestId: newRequestId,
  apiBaseUrl: "https://pay.ozow.com",
  iframeBaseUrl: "https://pay.ozow.com",
});
retry.mount("#ozow-payment", { wallets: ["applePay", "googlePay"] });
```

| Scenario | Action |
|---|---|
| Payment failed / customer retry | `destroy()`, create new payment request server-side, reinitialise with new `requestId` |
| SPA route change away from checkout | Call `destroy()` in your route teardown |
| Remounting on same page | Call `destroy()` before remounting, only one active instance per page |

---

## Apple Pay setup

Apple Pay in the Wallet SDK validates your top-level checkout domain, not the Ozow iframe origin.
Ozow handles certificates and server-side merchant validation. You are responsible for domain
verification.

**What you must do**

| Step | Detail |
|---|---|
| Register each checkout domain with Ozow | Include production, staging, and localhost origins |
| Host the domain association file | Ozow supplies the file. Serve it at exactly: `https://YOUR_DOMAIN/.well-known/apple-developer-merchantid-domain-association` |
| No redirects on the well-known path | Apple must fetch the file directly, no 301/302 redirects |
| HTTPS only | Valid TLS certificate required on your checkout domain |
| Update your CSP | Add `https://applepay.cdn-apple.com` to `script-src` and `connect-src` |
| Do not load Apple Pay JS yourself | The SDK lazy-loads Apple's script: do not add a duplicate script tag |
| Do not mock `window.ApplePaySession` | Test doubles that replace the constructor break the payment sheet in all browsers |

**Cross-browser behaviour**

| Browser | Behaviour |
|---|---|
| Safari (macOS / iOS) | Native Apple Pay sheet |
| Chrome, Edge, Firefox (including Windows) | Apple Pay button opens a QR code sheet; customer completes on an iOS 18+ device |
| Unsupported browsers | Button hidden after availability check |

**Common Apple Pay failures**

| Symptom | Likely cause |
|---|---|
| Apple Pay button never appears | Domain not verified, merchant not Apple-enabled, or device unavailable |
| `apple_pay_validation_failed` | Domain not on Ozow allowlist, or association file missing or misconfigured |
| `wallet_sheet_failed` | Another script overwrote `window.ApplePaySession` before Apple's SDK loaded |

---

## Google Pay setup

No Google-side merchant onboarding is required per checkout domain. Google validates the Ozow iframe
origin (`pay.ozow.com`), which is already approved by Google.

You only need to:

- Enable Google Pay on your Ozow merchant profile
- Ensure your CSP allows the Ozow iframe origin (see [Security and CSP](#security-and-csp) below)

---

## Card payments

When card is enabled on your merchant profile, the Wallet SDK can show a card payment form alongside
wallet buttons. Card fields render only inside the Ozow iframe; card data never enters your DOM,
keeping your checkout page out of PCI cardholder data scope. 3DS is handled entirely inside the Ozow
iframe.

To include card, pass it in `paymentMethods` when mounting:

```javascript
ozow.mount("#ozow-payment", {
  paymentMethods: ["card", "applePay", "googlePay"],
});
```

---

## Security and CSP

Add these directives to your checkout page Content Security Policy:

```http
Content-Security-Policy: script-src 'self' https://cdn.ozow.com https://applepay.cdn-apple.com; frame-src 'self' https://pay.ozow.com;
```

Adjust the hostnames for staging (`stagingpay.ozow.com`).

> ℹ️ **iframe attributes**: the SDK creates `<iframe allow="payment">` automatically. Do not strip
> payment permissions on the mount container.

---

## Error codes

| Code | Typical cause |
|---|---|
| `request_invalid` | Missing or malformed `requestId` |
| `request_load_failed` | Payment request not found or API unreachable |
| `mount_failed` | Mount operation failed |
| `invalid_mount_target` | Container selector not found |
| `not_initialized` | `mount` called before `init` |
| `invalid_config` | Unsupported configuration |
| `apple_pay_unavailable` | Device or browser cannot offer Apple Pay |
| `apple_pay_validation_failed` | Domain verification or merchant validation failed |
| `apple_pay_submit_failed` | Apple Pay token submission failed |
| `google_pay_unavailable` | Google Pay not ready on device |
| `google_pay_submit_failed` | Google Pay submission failed |
| `transaction_initiate_failed` | Could not create transaction |
| `polling_failed` | Polling timed out or errored |
| `authorization_failed` | Backend declined or errored |
| `three_ds_failed` | 3DS step-up failed |
| `wallet_sheet_failed` | Wallet sheet could not open |
| `network_error` | Network request failed |
| `destroyed` | Operation called after `destroy()` |
| `internal_error` | Unexpected SDK error |

---

## Go-live checklist

Use this checklist before going live.

**Backend**

- [ ] Payment request creation is server-side only, private key never in browser code
- [ ] Checkout page receives a valid `requestId` per payment attempt
- [ ] Order fulfilment is keyed on `transactionId` from `authorizationResult`

**Frontend**

- [ ] Pinned CDN URL with SRI hash on the script tag
- [ ] `Ozow.init` and `mount` use the correct environment URLs
- [ ] Handlers registered for `ready`, `authorizationResult`, and `error`
- [ ] `destroy()` called on checkout exit and before retry
- [ ] Cart amount locked after wallet sheet opens

**Apple Pay**

- [ ] Every checkout domain hosts `.well-known/apple-developer-merchantid-domain-association`
- [ ] Domain registered with Ozow and verified with Apple
- [ ] CSP allows `applepay.cdn-apple.com`
- [ ] No duplicate Apple Pay script or `ApplePaySession` mock on page

**Security**

- [ ] Checkout origin(s) registered on Ozow merchant allowlist
- [ ] CSP allows Ozow CDN and pay iframe origin(s)
- [ ] HTTPS on checkout domain

**Card (if applicable)**

- [ ] Card enabled on merchant profile
- [ ] `paymentMethods` includes `'card'` when mounting

---

## Next steps

- Review the [Building a secure
  integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md) checklist before going
  live
- Contact [support@ozow.com](mailto:support@ozow.com) to enable wallet and card payment methods on
  your merchant profile
- Contact Ozow to obtain the current SDK version and SRI hash for your environment
- See the [Payments API reference](https://hub.ozow.com/api-reference/payments-api.md) for the full technical specification