Apple Pay via Widget
This guide walks you through integrating Apple Pay using the DEUNA Web‑SDK (@deuna/web-sdk v1.6). It covers the two integration paths the SDK exposes:
| Payment Widget | Payment Vault | |
|---|---|---|
| What it does** | Handles the full checkout: UI, payment processing, and confirmation | Tokenizes the card only — returns a card_id for you to use |
| Who processes the purchase | DEUNA (internally) | You (via the Purchase API from your backend) |
| Apple Pay button | Rendered inside DEUNA's iframe | Rendered by you, in your own UI |
| SDK method | initPaymentWidget | initElements({ types: ['APPLE_PAY'] }) |
| Use when | You want a drop-in checkout experience | You need control over the payment flow or want to store the card for later |
| Full integration reference | Payment Widget — Web SDK | Payment Vault - Web SDK |
1. Prerequisites
Before you start, make sure the following are in place:
| Requirement | Notes |
|---|---|
| DEUNA account | Active merchant account in the DEUNA Dashboard. |
publicApiKey | Public API key issued by DEUNA. Required for DeunaSDK.initialize. |
orderToken | Generated on your backend via the DEUNA Orders API. Required to start a payment. |
userToken (optional) | Required only when you want to tokenize the card against a known DEUNA user. |
| HTTPS domain | Apple Pay requires the page to be served over HTTPS. localhost is allowed only for development. |
| Browser support | Safari required for the native Apple Pay sheet. Other browsers fall back to QR-code flow (iOS 18+). |
| Web‑SDK package | @deuna/web-sdk@^1.6.0 |
Loading the SDK from the CDN
Add the SDK script to your HTML (ideally in <head> so it's ready before checkout renders):
<script src="https://cdn.deuna.io/web-sdk/v1.6/index.js"></script>
If you need to wait for the script to load before using it (e.g. when injecting it dynamically), listen for the `load` event:
```ts
const script = document.createElement("script");
script.src = "https://cdn.deuna.io/web-sdk/v1.6/index.js";
script.onload = () => {
// window.DeunaSDK is now available
};
document.head.appendChild(script);
Domain verification (required for both paths)
Every domain that renders an Apple Pay button — whether via the Payment Widget or the Vault — must be registered with Apple through DEUNA. This applies to production domains and to any staging or preview environments.
Step‑by‑step
- Obtain the domain association file from the DEUNA Dashboard (or from Apple Pay if using your own merchant ID). The file is typically named
apple-developer-merchantid-domain-association(no extension) or.txt. - Host the file unchanged at:
Make sure:https://<your-domain>/.well-known/apple-developer-merchantid-domain-association- The response is served over HTTPS.
Content-Typeistext/plain; charset=utf-8.- No redirect, no auth wall, no 404. Response must be
200 OK.
- Verify the domain in the DEUNA Dashboard (or Apple Pay Developer portal). Apple will fetch the file from your server; if it matches, the domain is registered.
- Repeat per domain. Every hostname that loads the widget (e.g.
checkout.mystore.com,staging.mystore.com) must serve the file and be registered separately.
Reference implementation (Next.js - Example)
This repository already implements the verification endpoint. Two things are wired up:
next.config.js — rewrites both the canonical and .txt paths to an API route:
async rewrites() {
return [
{
source: '/.well-known/apple-developer-merchantid-domain-association',
destination: '/api/apple-verification',
},
{
source: '/.well-known/apple-developer-merchantid-domain-association.txt',
destination: '/api/apple-verification',
},
];
}src/pages/api/apple-verification.ts — returns the file contents as text/plain:
import { NextApiRequest, NextApiResponse } from 'next';
export default function domainVerification(
_req: NextApiRequest,
res: NextApiResponse,
) {
const fileContents = process.env.NEXT_PUBLIC_APPLE_VALIDATION_CONTENT_FILE;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.status(200).send(fileContents);
}The file contents are stored in an environment variable (NEXT_PUBLIC_APPLE_VALIDATION_CONTENT_FILE) so they can be rotated per environment without a code change.
How to verify it works
curl -i https://<your-domain>/.well-known/apple-developer-merchantid-domain-associationExpect:
- HTTP
200 OK Content-Type: text/plain; charset=utf-8- A body starting with
7B22...(the Apple blob)
2. Path A — Payment Widget
Use this when: you want DEUNA to handle the full payment experience. Apple Pay appears inside DEUNA's iframe alongside other payment methods. You do not call the Purchase API.
For the full widget integration guide, including initialization, callbacks, and display options, refer to Payment Widget — Web SDK
Apple Pay particularities
- No extra configuration needed in code. Apple Pay is enabled or disabled from the DEUNA Dashboard. If the merchant has it active and the user's device supports it, the button appears automatically.
userTokenis optional — only needed when you want to associate the payment with a known DEUNA user account.- The standard widget callbacks (
onSuccess,onError,onClosed,onPaymentProcessing) work the same way as for other payment methods.
3. Path B — Payment Vault
Use this when: you want to render your own Apple Pay button and control the purchase flow. The Vault tokenizes the card and returns a card_id. You call the Purchase API from your backend.
const { DeunaSDK } = window; // from https://cdn.deuna.io/web-sdk/v1.6/index.js
await DeunaSDK.initialize({
env: "staging",
publicApiKey: "<YOUR_PUBLIC_API_KEY>",
});
// If Apple Pay is available, it should return it within an array, such as: ["APPLE_PAY"]
const available = await DeunaSDK.getWalletsAvailable()
await DeunaSDK.initElements({
types: [{ name: "APPLE_PAY" }],
callbacks: {
onSuccess: (payload) => console.log("Apple Pay success", payload),
onError: (err) => console.error("Apple Pay error", err),
onClosed: (action) => console.log("Sheet closed:", action),
},
});4. Gotchas
| Gotcha | Fix |
|---|---|
| Button does not render | Check getWalletsAvailable() result, browser support, and HTTPS. |
| Payment sheet opens then freezes | Merchant validation failed — check domain verification file and DEUNA credentials. |
session.begin() silently rejected | You lost the user gesture. Do not await long‑running work between click and session.begin(). Use the orderToken + userInfo SSR path to pre‑resolve transactionInfo. |
| Works in Safari, not Chrome | Expected — the native sheet requires Safari. Other browsers fall back to QR-code flow (iOS 18+). |
Quick reference
| API | Purpose |
|---|---|
DeunaSDK.initialize({ env, publicApiKey }) | One‑time SDK bootstrap. |
DeunaSDK.getWalletsAvailable() | Check which wallets are available on the device. |
DeunaSDK.initElements({ types, orderToken?, callbacks }) | Initialize Apple Pay. Render your own Apple Pay button |
DeunaSDK.initPaymentWidget({ orderToken, callbacks, ... }) | Open DEUNA's full Payment Widget (Apple Pay included). |
.well-known/apple-developer-merchantid-domain-association | Domain verification file you must host on every domain |
Updated about 4 hours ago