Checkout SDK
Checkout flow and screens
Checkout flow, UI phases, mobile app checkout, deep links, and polling.
Checkout flow
The SDK loads the payment session and guides the customer through checkout. On phone (SDK 1.2.0+), checkout auto-starts Pay with Zippy unless you opt out. On desktop, customers use QR and/or manual entry per checkoutPolicy.
Key rules
| Name | Type | Description |
|---|---|---|
Method lock | behavior | After QR or manual checkout is committed, customers cannot switch methods. Exception: before payment is claimed in the app, failed Pay with Zippy handoff may offer QR/manual fallbacks. |
Amount source | behavior | Display amount from onReady / onSessionUpdate — amount is locked server-side. |
Deep link / QR payload | security | Only sessionId — never clientToken. |
Payment approval | behavior | Customer confirms in the Zippy mobile app (PIN / approval). |
Three time boundaries
| Name | Type | Description |
|---|---|---|
expiresAt | IDLE phase | Idle shelf — customer must start checkout before this time. |
paymentExpiryAt | PAYING phase | Pay window — countdown after checkout starts. |
absoluteExpiresAt | always | Hard maximum — session and token expire here. |
Session statuses
| Name | Type | Description |
|---|---|---|
CREATED | active | Session open — customer can choose method. |
AWAITING_PAYMENT | active | Customer must confirm in Zippy app. |
COMPLETED | terminal | Payment succeeded. |
EXPIRED | terminal | Session or pay window expired. |
CANCELLED | terminal | Payment cancelled. |
FAILED | terminal | Payment failed. |
Checkout methods
| Name | Type | Description |
|---|---|---|
QR | QR code | Customer scans QR in the Zippy app. Locked when QR flow starts. |
Manual | Zippy ID | Customer enters Zippy ID, verifies, and proceeds. Locked when payment starts. |
Pay with Zippy | Phone only | Opens the Zippy Pay app on phone (default in SDK 1.2.0+). Ignored on desktop. |
Mobile app checkout (phone)
On phone, the SDK can open the Zippy Pay app so customers complete payment there. Configure with mobileApp.policy in ZippyPay.create() or mobile-app-policy on <zippy-pay-checkout>. Requires SDK 1.1.0+ for mobile app features; 1.2.0+ for the new default.
On desktop, mobile app settings are ignored. Customers always use QR and/or manual entry per checkoutPolicy.
mobileApp.policy
| Name | Type | Description |
|---|---|---|
enforce | default (1.2.0+) | Recommended — lands on Pay with Zippy automatically. QR/manual hidden until app handoff fails. |
option | phone | Full chooser (QR/manual per checkoutPolicy) plus a Pay with Zippy button. |
off | phone | Web-only checkout on phone. QR/manual only, per checkoutPolicy. Restores pre-1.2.0 behavior. |
Default behavior (1.2.0+)
If you omit mobileApp, or omit policy inside it, the SDK uses enforce on phone:
Equivalent on phone (SDK 1.2.0+)
typescriptZippyPay.create({ environment, sessionId, clientToken });
ZippyPay.create({ environment, sessionId, clientToken, mobileApp: {} });
ZippyPay.create({ environment, sessionId, clientToken, mobileApp: { policy: 'enforce' } });Web-only on phone
typescriptZippyPay.create({
environment,
sessionId,
clientToken,
mobileApp: { policy: 'off' },
});checkoutPolicy × mobileApp.policy (phone)
| Name | Type | Description |
|---|---|---|
(omit) + enforce | default | Pay with Zippy auto-start. |
(omit) + option | chooser | QR + manual + Pay with Zippy. |
(omit) + off | web-only | Chooser: QR + manual. |
QR + enforce | app first | Pay with Zippy auto-start; QR fallback after failed handoff. |
QR + off | web-only | QR only. |
MANUAL + enforce | app first | Pay with Zippy auto-start; manual fallback after failed handoff. |
MANUAL + off | web-only | Manual only. |
Migration from 1.1.x
What changed
| Name | Type | Description |
|---|---|---|
≤ 1.1.x | before | Omitting mobileApp → web-only on phone. |
1.2.0+ | after | Omitting mobileApp → Pay with Zippy enforce on phone. |
- Want Pay with Zippy by default? Upgrade to
1.2.1(or1.2.0+) — no config change. - Want web-only on phone? Add
mobileApp: { policy: 'off' }. - Already set enforce or option? No change needed.
FAQ
Common questions
| Name | Type | Description |
|---|---|---|
Is mobileApp required? | — | No. Omitted mobileApp defaults to enforce on phone (SDK 1.2.0+). |
Does enforce affect desktop? | — | No. Desktop never shows Pay with Zippy. |
QR/manual with enforce? | — | Yes — after app handoff fails. Customer sees Try again, Or pay another way, and Install Zippy. |
Customer flow
- Customer sees Pay with Zippy (enforce) or taps it from the chooser (option).
- Button shows Opening Zippy… (disabled, with spinner) while the SDK attempts to open the app.
- SDK opens the Zippy Pay app when the customer taps (iOS and Android). Checkout stays on your page.
- If the app opens: Complete your payment in the Zippy app. (no Try again).
- If the app does not open: failed handoff screen (below). The App Store / Play Store is not opened automatically.
UI states
| Name | Type | Description |
|---|---|---|
Idle | Pay with Zippy | Pay with Zippy button. No inline countdown. Bottom pay progress bar visible during pay window. Footnote: Keep this page open until payment completes. |
Opening | Handoff | Disabled Opening Zippy… button with spinner. No inline timer. |
Opened | In app | Complete your payment in the Zippy app. Inline timer if pay window active. No Try again. |
Awaiting payment | Claimed | Waiting spinner after app claim. Payer details if available. |
Failed handoff | Fallback | See failed handoff section below. |
Failed app handoff
If the app does not open, the customer sees:
- We couldn't open the Zippy app on this device.
- Try again — retries opening the app (same as Pay with Zippy).
- Or pay another way — QR and/or manual cards per
checkoutPolicy. - Install Zippy (bottom) — opens the native store when tapped. iOS: App Store. Android: Play Store.
Timers (mobile app flow)
| Name | Type | Description |
|---|---|---|
Bottom pay progress bar | pay window | Shown whenever checkout is in the pay window, including idle Pay with Zippy. |
Inline countdown timer | conditional | Hidden on idle and Opening Zippy…; shown after app open, on failed handoff, and while awaiting payment. |
Amount | callbacks | Always from onReady, onSessionUpdate, onComplete. |
UI screens and phases
loading
Spinner while fetching session.
choose-method
QR, Zippy ID, and (on phone) Pay with Zippy per policy.
mobile-app
Pay with Zippy, Opening Zippy…, opened, failed handoff.
qr-active
Branded QR + step guide or awaiting confirmation.
manual-entry
Zippy ID input + Verify button.
manual-confirm
Verified identity card + Proceed button.
awaiting
Complete in Zippy app with identity card.
Terminal
success, expired, failed, cancelled, declined, invalid-session, error.
The header on all active screens shows the business name and formatted amount from the session. A pay progress bar appears when the pay window is active (phase === 'PAYING' with valid paymentExpiryAt).
Deep links and QR
Payment QR codes and mobile app handoff use your sessionId only — never the client token. Desktop customers scan the QR; phone customers may open the app via Pay with Zippy.
- Rendered as a branded QR with centred Zippy logo (desktop).
- Pay with Zippy opens the installed Zippy Pay app on phone.
- Error correction level: H (high) for QR.
- Never include
clientTokenin QR payloads or share links.
Zippy ID validation
Personal Zippy handles (payment IDs) are validated before verify and attach:
Validation rules
| Name | Type | Description |
|---|---|---|
Length | 3–20 characters | Minimum 3, maximum 20. |
Characters | a-z, 0-9 | Lowercase letters and digits only. |
Dot separator | at most one | e.g. john.doe12 |
Leading/trailing dot | invalid | .john and john. are rejected. |
Case | normalized | Input normalized to lowercase before validation. |
Valid: abc, user123, john.doe12. Invalid: ab (too short), a..b (double dot), .john.
Verify vs proceed
| Name | Type | Description |
|---|---|---|
Verify | Manual flow | Lookup only — does not start the pay timer. |
Proceed & pay | Manual flow | Confirms the payer and starts the pay window. |
Session polling
The SDK polls session status while the checkout is in a non-terminal state.
Polling settings
| Name | Type | Description |
|---|---|---|
Default interval | 3 seconds | When the browser tab is visible. |
Background interval | 10 seconds | When the tab is hidden. |
Pollable statuses | CREATED, AWAITING_PAYMENT | Polling continues while session is active. |
Terminal statuses | COMPLETED, EXPIRED, CANCELLED, FAILED | Polling stops when reached. |
Polling also stops on unrecoverable errors (401, bootstrap failure) or when destroy() is called.
Error handling
Errors are RFC 7807 application/problem+json, surfaced as ZippyPayError:
Error codes
| Name | Type | Description |
|---|---|---|
payment-session.origin-not-allowed | 403 | Checkout page origin not registered. Add the domain under Integrations → Allowed browser origins. |
payment-session.client-token-invalid | 401 | Bad or missing token. Terminal: session unavailable. |
payment-session.id-invalid | 400 | Bad UUID. Thrown at init. |
payment-id.not-found / payer.not-found | 404 | Unknown Zippy ID. Inline: Zippy ID not found. |
payment-session.not-attachable | 400 | Session closed or expired. Terminal: expired. |
payment-session.payer-attached | 409 | Payer already bound. SDK refreshes session state if the same payer. |
payment-session.checkout-method-conflict | 409 | Method already locked. Refreshes session, shows error. |
payment-session.failed | — | Payment failed. Terminal: failed. |
payment-session.declined | — | Payment declined. Terminal: declined. |
payment-session.cancelled | — | Payment cancelled. Terminal: cancelled. |
Troubleshooting
Common issues
| Name | Type | Description |
|---|---|---|
Session unavailable immediately | 401 / invalid token | Create a new session on your server. |
sessionId must be a UUID v4 | init error | Verify the session ID format. |
container not found | inline mode | Ensure the container element exists before create(). |
Checkout blocked on unregistered domain | origin | Register the page origin under Integrations → Allowed browser origins in the portal. |
Web component shows nothing | clientToken | Set el.clientToken property after mount. |
Amount wrong on page | display | Use session.amount from onReady, not page props. |
QR scan fails | environment mismatch | Session environment must match SDK environment. |
Pay with Zippy does nothing | mobile app | Use SDK 1.2.1 (recommended). On phone, Pay with Zippy is the default since 1.2.0 — set mobileApp.policy to off for web-only. |
Modal won't close | modal options | Enable closeOnBackdropClick / closeOnEscape or call close(). |
Browser requirements
Required features
| Name | Type | Description |
|---|---|---|
ES2020+ | JavaScript | Required. |
fetch | API | Required. |
Shadow DOM | Web platform | Required. |
Custom Elements | Web platform | Required. |
Intl.NumberFormat | API | Currency display. |
- Supported browsers: current Chrome, Firefox, Safari, Edge (evergreen).
- Serve the host application over HTTPS in production.
- Node.js ≥ 18 for local development and bundlers.