# Apple Pay on the Web — TruePay sample A complete, runnable merchant integration for Apple Pay on your own website, matching **§3.3.1** of the TruePay API Integration Guide. Four files, no dependencies, Node 18+. ``` server.js your backend — the two endpoints that matter public/index.html the checkout page public/checkout.js the Apple Pay JS: onvalidatemerchant + onpaymentauthorized config.js your API key, your domain — server-side only ``` ## The one thing that makes web different from in-app Apple Pay in a browser needs a step in-app Apple Pay does not: **merchant validation**. Before the sheet will release a payment token, your page must call `completeMerchantValidation()` with a session obtained from Apple over mutual TLS. Until that happens `onpaymentauthorized` never fires and there is no token to send. **If your sheet opens and then closes with nothing, this is almost always why.** TruePay makes that call for you, with your Apple merchant identifier and your Merchant Identity certificate. Neither leaves TruePay, and nothing about them appears in this sample. The payment itself then goes to the *same* endpoint an iOS app uses. ``` browser your backend TruePay Apple ─────── ──────────── ─────── ───── onvalidatemerchant ──────> /apple-pay/validate ──────> validate-merchant ──> mTLS completeMerchantValidation <───────── Apple's session, verbatim ──────────────┘ onpaymentauthorized ──────> /apple-pay/pay ──────> wallet-pay ──> decrypt + charge completePayment <───────── SUCCESSFUL / FAILED ─────────────────────┘ ``` ## Before you start You need three things from Apple and TruePay. The sample cannot substitute for any of them. **1. Your own Apple Pay merchant identifier, with your domain registered and verified.** In your Apple Developer account, register the exact host serving the checkout page and host the verification file Apple gives you at `https:///.well-known/apple-developer-merchantid-domain-association`. Drop Apple's file into this sample's `.well-known/` folder and `server.js` will serve it. Apple enforces this. TruePay does not check domains and cannot register one for you. **2. A Merchant Identity certificate**, set up in the merchant portal under **Apple Pay → Merchant Identity**: generate the CSR, enroll it with Apple, upload the `.cer`. TruePay holds the key and makes the call — it is never downloaded. Without it the validate endpoint returns `NO_IDENTITY_CERT`. **3. A Payment Processing certificate.** Reuse the one your iOS app uses, or enroll a separate one for the web and add it in the portal under *Additional Payment Processing certificates*. TruePay matches each payment to the right certificate by the token's public key hash, so both keep working at once. ## Running it ```bash cp .env.example .env # then fill it in node server.js ``` `APPLE_PAY_DOMAIN` must be the fully-qualified host serving the page — no scheme, no path, no port — and it must match the address bar **exactly**. `www.example.om` and `example.om` are different domains to Apple. ### localhost will not work Apple Pay JS refuses to run on an insecure origin, and unlike service workers there is no localhost exemption. To try this you need the page served over **HTTPS on the real registered domain**, opened in **Safari**, on a device with a card in Wallet. A tunnel (ngrok, Cloudflare Tunnel) works only if you register *that* hostname with Apple and point `APPLE_PAY_DOMAIN` at it. ## What the sample does deliberately **The amount lives on the server.** A page that posts its own amount can be edited in the console, and the customer pays 0.001 for the laptop. `server.js` keeps a small cart and charges its own figure; the page only displays it. **Your API key never reaches the browser.** That is the only reason both endpoints exist. Anyone with your key can charge your account. **The `domain` sent to TruePay is server configuration, not a request field.** Letting the page choose it would be both useless — Apple only issues a session for a domain registered to your merchant identifier — and unsafe. **`validationURL` is pinned to `apple.com` before being forwarded.** TruePay checks this too, but a server that will POST wherever a browser tells it to is worth not building. **The outcome is confirmed server-to-server.** After `wallet-pay`, the sample calls `POST /api/v1/transactions/inquiry`. The browser's word is not proof that money moved. **The whole token object is sent**, exactly as `event.payment.token` gives it — not just `paymentData`, not a JSON string of part of it. ## When it does not work | What you see | What it usually is | |---|---| | No Apple Pay button at all | Not Safari, no card in Wallet, or not HTTPS. The page says which. | | The sheet opens, then closes with no token | Merchant validation failed. Check your server log and the response from step 1. | | `NO_IDENTITY_CERT` | The Merchant Identity certificate is not set up in the merchant portal. | | `NO_MERCHANT_ID` | Your Apple merchant identifier is not set in the merchant portal. | | Apple returns a non-2xx to validation | Nearly always the domain: not registered against *your* merchant identifier, or `APPLE_PAY_DOMAIN` does not match the address bar. | | `DECRYPT_FAILED` on the payment | The token was encrypted to a Payment Processing certificate TruePay does not hold — usually a separate web certificate that was never added. The error carries the token's public key hash; compare it with the hashes under *Active certificates* in the portal. | | `MERCHANT_CAPABILITY_DISABLED` | Apple Pay is not enabled on your account. Ask OMPAY. | ## Don't want to build this? TruePay's bank-hosted pay page already has Apple Pay in the browser, on TruePay's own domain — you redirect and TruePay handles validation and the sheet. No certificates, no merchant identifier, no domain registration. Ask OMPAY to enable bank-hosted checkout. ## Going live Point `TRUEPAY_BASE_URL` at the production API, use your production API key, and register your production domain with Apple. Everything else is the same.