@zippypay/checkout
v1.2.2
Published
Zippy Pay checkout SDK — embed inline or in a modal on any merchant site
Maintainers
Readme
@zippypay/checkout
Browser checkout SDK for Zippy Pay merchants. Embed payment UI inline on a checkout page or open it in a modal. Works with vanilla JavaScript, React, Vue, Angular, and any stack that runs modern JavaScript.
Styles are scoped inside Shadow DOM — no extra CSS import is required.
Install
npm install @zippypay/checkoutyarn add @zippypay/checkoutpnpm add @zippypay/checkoutBefore the SDK runs
Your server creates a payment session and passes credentials to the checkout page:
| Field | Description |
|---|---|
| sessionId | Payment session identifier — safe in URLs |
| clientToken | Short-lived token from your server — browser only, never public |
Never expose merchant API credentials in browser code. Never put clientToken in QR codes, query strings, logs, or analytics.
Quick start
JavaScript / TypeScript
import { ZippyPay } from '@zippypay/checkout';
const checkout = ZippyPay.create({
environment: 'production',
sessionId: '11111111-1111-4111-8111-111111111111',
clientToken: 'zps_…',
mode: 'inline',
container: '#zippy-checkout',
theme: { primary: '#2563eb' },
onComplete: (session) => console.log('Paid', session),
});Modal
ZippyPay.create({
environment: 'production',
sessionId,
clientToken,
mode: 'modal',
closeOnBackdropClick: true,
closeOnEscape: true,
onClose: () => console.log('Modal closed'),
});Web Component
<script type="module">
import '@zippypay/checkout';
</script>
<zippy-pay-checkout
environment="production"
session-id="11111111-1111-4111-8111-111111111111"
mode="inline"
theme-primary="#2563eb"
></zippy-pay-checkout>
<script type="module">
const el = document.querySelector('zippy-pay-checkout');
el.clientToken = 'zps_…'; // property, not HTML attribute
el.addEventListener('zippy-complete', (e) => console.log(e.detail));
</script>Script tag (UMD)
Load from a public npm CDN:
<script src="https://unpkg.com/@zippypay/checkout@1/dist/index.min.js"></script>
<script>
ZippyPay.create({
environment: 'production',
sessionId: '…',
clientToken: 'zps_…',
mode: 'inline',
container: '#zippy-checkout',
});
</script>Pin the version in production (e.g. @1.2.0 instead of @1).
Environments
| Environment | Use when |
|---|---|
| production | Live merchant checkout |
| staging | Pre-production testing |
| development | Local development with a proxy |
Use the same environment where your server created the payment session.
Configuration
type ZippyPayConfig = {
environment: 'production' | 'staging' | 'development';
sessionId: string;
clientToken: string;
mode?: 'inline' | 'modal';
container?: HTMLElement | string; // required for inline
theme?: ZippyTheme;
/** Restrict web checkout methods. Omit for full chooser (QR + manual). */
checkoutPolicy?: 'QR' | 'MANUAL';
/**
* Phone-only app handoff. Ignored on desktop.
* Default policy: `enforce` (SDK 1.2.0+). Pass `{ policy: 'off' }` for web-only on phone.
*/
mobileApp?: { policy?: 'off' | 'option' | 'enforce' };
redirectOnComplete?: boolean;
redirectOnFailure?: boolean;
closeOnBackdropClick?: boolean;
closeOnEscape?: boolean;
onReady?: (session) => void;
onSessionUpdate?: (session) => void;
onComplete?: (session) => void;
onExpired?: (session) => void;
onError?: (error: ZippyPayError) => void;
onClose?: () => void;
};Instance methods: open(), close() (modal), destroy()
Checkout policy
Control which payment methods customers can use:
| Config | Customer sees |
|---|---|
| (omit checkoutPolicy) | Full chooser — QR + manual |
| checkoutPolicy: 'QR' | QR only |
| checkoutPolicy: 'MANUAL' | Manual Zippy ID only |
Mobile app (phone)
On phone, customers can pay in the Zippy app via Pay with Zippy. On desktop, mobile app settings are ignored — customers use QR and/or manual per checkoutPolicy.
| mobileApp.policy | Phone behavior |
|---|---|
| enforce (default) | Auto-starts Pay with Zippy; QR/manual hidden until app handoff fails |
| option | Full chooser plus a “Pay with Zippy” button |
| off | Web checkout only (QR / manual per checkoutPolicy) |
Omitting mobileApp (or policy) is the same as enforce (SDK 1.2.0+). To keep web-only checkout on phone (behavior before 1.2.0):
mobileApp: { policy: 'off' }Pay with Zippy flow
- Customer taps Pay with Zippy (or checkout auto-starts it when
enforce). - The button becomes disabled and shows Opening Zippy… for up to 8 seconds.
- The SDK opens the app from that tap — checkout stays on your page:
- iOS:
https://zippypay.io/app/merchant-payment/{sessionId} - Android: intent URL for the same HTTPS link targeting
com.zippy.pay
- iOS:
- If the app opens → Complete your payment in the Zippy app.
- If the app does not open → failed handoff UI:
- Try again
- Or pay another way (QR and/or manual per
checkoutPolicy) - Install Zippy (bottom) — opens the native store; not opened automatically
Install Zippy store links (fixed by Zippy, not merchant-configurable):
| Platform | Web / QR | Install button (native) |
|---|---|---|
| iOS | App Store | itms-apps://apps.apple.com/in/app/zippypay/id6806799311 |
| Android | Google Play | market://details?id=com.zippy.pay |
Deep links and QR payloads use sessionId only — never include clientToken.
Switching from mobile app to QR or manual
If checkout started as MOBILE_APP and the customer has not yet claimed payment in the app, they may switch from the failed handoff screen:
| Customer picks | SDK behavior |
|---|---|
| QR code | Starts QR checkout (pay timer is not reset) |
| Zippy account (manual) | Opens manual entry; backend switches to MANUAL when the payer is verified and attached |
After payment is claimed in the app or a payer is attached on manual, method switching is no longer available.
Theming
| Token | CSS variable | Default |
|---|---|---|
| primary | --zippy-primary | #3f63f3 |
| primaryForeground | --zippy-primary-foreground | #ffffff |
| background | --zippy-background | #ffffff |
| surface | --zippy-surface | #f8fafc |
| text | --zippy-text | #0f172a |
| textMuted | --zippy-text-muted | #64748b |
| border | --zippy-border | #e2e8f0 |
| borderRadius | --zippy-radius | 20px |
| fontFamily | --zippy-font-family | DM Sans stack |
| mode | — | light | dark | auto |
Web component: use theme-primary, theme-mode, etc.
Checkout experience
The SDK guides customers through payment and keeps your page updated until the session reaches a final state.
Payment methods
| Method | Customer experience | |---|---| | QR | Scan a QR code in the Zippy app, then confirm payment in the app | | Manual | Enter a Zippy ID, verify identity, then confirm payment in the app | | Mobile app (phone) | Tap Pay with Zippy → pay in the app; fallback to QR/manual if the app cannot open |
After QR or manual checkout is committed, customers generally cannot switch methods. Exception: MOBILE_APP → QR/MANUAL is allowed before claim, via the failed handoff fallbacks.
Timers and amount
- A bottom pay progress bar shows during the pay window (including idle Pay with Zippy).
- The inline countdown timer is hidden on idle and Opening Zippy…; it appears after the app opens, on failed handoff, and while awaiting payment.
- Display the amount from the session passed to your callbacks (
onReady,onSessionUpdate,onComplete) — do not rely on amount props from your own page markup alone. - Sessions also have idle and absolute expiry boundaries handled by the SDK UI.
Web component
Attributes
| Attribute | Values |
|---|---|
| environment | production, staging, development |
| session-id | Session UUID |
| mode | inline, modal |
| checkout-policy | QR, MANUAL |
| mobile-app-policy | off, option, enforce (default: enforce when omitted) |
| theme-* | See theming table above |
Set clientToken via the property (not an HTML attribute).
Events
| Event | When |
|---|---|
| zippy-ready | Checkout loaded and ready |
| zippy-session-update | Session status updated |
| zippy-complete | Payment succeeded |
| zippy-expired | Session expired |
| zippy-error | Checkout error |
| zippy-close | Modal closed |
Framework notes
React
'use client';
import { useEffect, useRef } from 'react';
import '@zippypay/checkout';
export function ZippyCheckout({ sessionId, clientToken }: Props) {
const ref = useRef<HTMLElement & { clientToken: string }>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
el.clientToken = clientToken;
const onComplete = (e: Event) => console.log((e as CustomEvent).detail);
el.addEventListener('zippy-complete', onComplete);
return () => el.removeEventListener('zippy-complete', onComplete);
}, [clientToken]);
return (
<zippy-pay-checkout
ref={ref}
environment="production"
session-id={sessionId}
mode="inline"
/>
);
}Or imperative: ZippyPay.create({ container: ref.current, … }) and destroy() on unmount.
Vue 3
Import @zippypay/checkout, set el.clientToken in onMounted, listen for @zippy-complete.
Angular
Import the package, use CUSTOM_ELEMENTS_SCHEMA, set nativeElement.clientToken in ngAfterViewInit.
Error handling
Use onError to receive a ZippyPayError with code, status, and detail. If checkout cannot continue (for example, an invalid or expired token), show your own recovery flow — typically creating a new session on your server and re-initializing the SDK.
Security checklist
- Never bundle merchant API credentials in browser code.
- Keep
clientTokenin memory only. - Put only
sessionIdin QR codes and app deep links — never the client token. - Use the session amount from SDK callbacks, not from unchecked page props.
Requirements
- Modern browsers with ES2020,
fetch, Shadow DOM, and Custom Elements - Node.js ≥ 18 for local development / bundlers
More documentation
Full integration guide and framework examples:
github.com/IMAGINE-INNOVATION-LIMITED/zippy-pay-web-sdk
License
MIT © Imagine Innovation Limited
