Checkout: setup and integration flow
swirepay-checkout is a single custom element that renders every payment method your checkout
session allows (Card, Pay by Bank/ACH, and wallet buttons) inside a PCI-isolated iframe. This page
covers the parts of the integration that are identical no matter which payment method(s) you
enable — the end-to-end flow, loading and mounting the element, the full props reference, the
error/success payload shapes, theming, and testing.
Which methods actually appear is controlled by the paymentType array you pass when creating the
checkout session — see Checkout session.
Once you've read this page, go to the method guide for whichever of these you're enabling:
Card,
ACH,
Apple Pay,
PayPal,
Google Pay.
1. The end-to-end integration flow
Every integration follows the same shape, regardless of which payment method(s) you enable:
- Your backend calls Swirepay's
POST /v3/checkout-sessionwith your secret key and the session config (scope,amount/currency,paymentType,acceptedDomain, etc.) — see Checkout session. This must happen server-side; the secret key must never reach the browser. - Swirepay's backend returns a secure token, plus a
paymentSessionGididentifying this specific charge attempt. - Your backend records that
paymentSessionGid(you'll want it to reconcile payment status in your own system later) and hands the secure token to your frontend, along with whatever prop values you want to pre-fill (customer details, theme, etc.). - Your frontend mounts
<swirepay-checkout>, setssecureToken(and the other props) as JS properties, and registerssuccessCallback/errorCallback. - The shopper completes their chosen payment method within the widget's PCI-isolated iframe (or, for a wallet button, via that wallet's own payment sheet/popup); your page never touches raw card or bank data.
- Also configure a webhook in the Swirepay merchant dashboard for this account.
successCallbackis a best-effort, client-side signal only — it can be missed entirely if the shopper's tab closes, the network drops, or the browser is killed right after a charge succeeds. The webhook is the authoritative, server-to-server source of truth for final transaction status; use it (notsuccessCallbackalone) to actually mark an order as paid in your own system, matched up against thepaymentSessionGidyou recorded in step 2. Configuring the webhook is done in the dashboard, not in this SDK.
2. Live vs. Test mode
Your Swirepay account has a Live / Test toggle in the merchant dashboard, each with its own secret key. The client-side integration is identical either way — same endpoint, same element, same props. Only the secret key your backend uses to mint the checkout session changes.
What "Test mode" actually exercises underneath differs by method:
| Method | What Test mode does |
|---|---|
| Card | Full sandbox — card numbers are tokenized against Swirepay's test rails, no real charge occurs. |
| ACH | Bank-account linking goes through Plaid's own sandbox/test credentials, no real transfer occurs. |
| Apple Pay, Google Pay | Runs against the underlying sandbox that backs both wallet buttons — no real charge occurs, no separate wallet-specific sandbox configuration needed. |
| PayPal | Runs against PayPal's own sandbox environment — log in with a PayPal sandbox test account to exercise the full approval flow. |
Live mode issues a live secret key and creates real, chargeable payment methods / real transfers / real wallet charges.
3. Load the SDK and mount the element
Install the package from npm:
npm install @swirepay-developer/swirepay-frontend-payment-sdk@latest
// Registers <swirepay-checkout>/<swirepay-contact-onboarding> as a side effect of the import.
import '@swirepay-developer/swirepay-frontend-payment-sdk';
Or, if you don't use a bundler, load it directly from a CDN with a plain <script> tag (pin an
exact version rather than a floating tag, so a future release can't silently change behavior on
your page):
<!doctype html>
<html>
<body>
<swirepay-checkout
id="checkout"
account-gid="account-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
></swirepay-checkout>
<script src="https://unpkg.com/@swirepay-developer/swirepay-frontend-payment-sdk@latest/dist/prod/swirepay-sdk.iife.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
customElements.whenDefined('swirepay-checkout').then(async () => {
const el = document.getElementById('checkout');
const res = await fetch('/api/swirepay/checkout-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amountInCents: 2599 })
});
const { secureToken } = await res.json();
el.secureToken = secureToken;
el.successCallback = (payload) => console.log('payment succeeded', payload);
el.errorCallback = (payload) => console.error('payment failed', payload);
});
});
</script>
</body>
</html>
If you're enabling Apple Pay, also set shop-name on the element — see the
Apple Pay guide.
The amount/currency actually charged is read back server-side from the checkout session itself
(not trusted from any client-side attribute), so there is no settable amount/currency prop on
the element — those only ever get set when you create the session. See the pitfall about
sdk.d.ts in §10 — the SDK's type declarations list amount/currencyCode
on the element, but they are not real, settable integration points.
4. Props reference
| Prop (JS property) | HTML attribute | Type | Required | Description |
|---|---|---|---|---|
accountGid | account-gid | string | Yes | Your Swirepay account identifier. |
secureToken | secure-token | string | Yes | The compact token from the checkout session. May be attached after initial mount. |
customerGid | customer-gid | string | No* | Gid of an existing customer to attach the payment to. *Required when savePaymentMethod is true. See §7 for how this overrides prefill. |
savePaymentMethod | save-payment-method | boolean (attribute presence) | No | Saves the payment method for future reuse via payThroughSavedMethod. Requires customerGid. Supported for Card and ACH only. When set, wallet methods are hidden — only Card/ACH tabs remain available (unless the session scope is "order", in which case wallets stay available too). |
payThroughSavedMethod | pay-through-saved-method | JSON string | No | Charges a previously-saved card/bank instantly instead of collecting fresh details. Card and ACH only. See §8. A malformed/incomplete value here is a fatal setup error — see §5. |
customerDetailPrefillJsonString | customer-detail-prefill-json-string | JSON string | No | Pre-fills the contact-info fields. See the shape in §7. Overridden entirely by customerGid. |
shopName | shop-name | string | Apple Pay only | Display name shown in the Apple Pay payment sheet. Not used by any other payment method. |
themeJsonString | theme | JSON string | No | Visual theming — see §9 below. |
successCallback | (JS property only) | (payload: CheckoutSuccessPayload) => void | No | Called once the charge succeeds. See §6 for the exact payload shape. |
errorCallback | (JS property only) | (payload: ErrorPayload) => void | No | Called on a fatal setup error or a recoverable payment-attempt failure. See §5. |
save-payment-method is a standard HTML boolean attribute — its mere presence enables it
(save-payment-method or save-payment-method=""); to control it dynamically from JS, set the
savePaymentMethod property directly to true/false instead of toggling the attribute.
Name, email, and phone are collected once (shared across whichever payment method the shopper picks) and are required for every payment method — see §7 for the prefill shape and phone-number format.
5. Error payload
errorCallback receives an object shaped like:
type ErrorPayload = {
message: string;
cause?: string | number | boolean | { name: string; message: string; stack?: string } | Record<string, unknown> | unknown[] | undefined;
fatal: boolean;
};
message is always a human-readable string. cause is whatever additional detail was available,
normalized so it survives crossing the widget's internal iframe boundary (the widget runs the
payment form inside a sandboxed iframe and relays events to the outer custom element via
postMessage, which can only carry "structured-cloneable" values):
- A thrown error becomes
{ name, message, stack }— note that if the underlying error carried an HTTP status code, that status code is not preserved in what reacheserrorCallback; onlyname/message/stacksurvive. - A plain string/number/boolean cause passes through unchanged — this is how an application-level
rejection reason from Swirepay's backend usually arrives, as a string like
"[REJECTED]: reason". - A plain object/array cause is JSON round-tripped (so it must itself be JSON-serializable).
- Anything else (a class instance that isn't an error, a function, etc.) becomes
undefined.
Fatal vs. recoverable errors
Not every failure has the same severity, and the widget behaves differently for each:
- Fatal setup errors unmount the entire Card/ACH/wallet UI — there is no form left for the
shopper to retry from, only whatever fallback content your page shows around the (now broken)
element. These include: a missing
account-gid, a malformed or wrong-scopesecure-token, a malformedpay-through-saved-methodJSON payload, settingsave-payment-methodwithoutcustomer-gid, and failure to load required scripts or fetch the payment session on mount. - Recoverable payment-attempt failures (e.g. a declined card) leave the form mounted so the shopper can correct details and try again — this is the normal case for a failed submission.
errorCallback's fatal field tells you which one just happened: true for a fatal setup error,
false for a recoverable payment-attempt failure. Use it to decide how to react — e.g. show a
persistent "something's wrong, contact support" state for fatal: true, vs. just letting the
shopper retry for fatal: false — rather than trying to infer severity from message/cause
content.
Which specific conditions count as fatal, recoverable, or entirely silent (no errorCallback
call at all) varies by payment method — this is one of the sharpest differences between methods,
especially around wallet-button eligibility checks. See each method's own guide for its exact
list.
When errorCallback does NOT fire (silent failures, common to all methods)
- Malformed
customer-detail-prefill-json-stringJSON — logged to the console only, the form falls back to empty name/email/phone fields. - Malformed
themeJSON, or JSON that doesn't match the theme schema — logged to the console only, default theming is used (see §9). - Ordinary client-side field validation (invalid card number, expired date, empty required field,
etc.) — shown inline under the field, never sent to
errorCallback.
save-payment-method/pay-through-saved-method (Card and ACH only): a malformed or incomplete
pay-through-saved-method payload, or a declined saved-method charge, are both treated as
fatal — see §8.
6. Success payload
successCallback receives a shaped, fixed set of fields — not the full raw internal record (which
carries far more detail than a merchant needs, e.g. card fingerprints, dispute internals, etc.):
type CheckoutSuccessPayload = {
paymentSessionGid: string | null;
paymentMethodGid: string | null;
customerGid: string | null;
amount: number | null;
currencyCode: string | null;
status: string | null;
};
This is identical regardless of which payment method was used (Card, ACH, or a wallet) or whether
it was a one-time payment or inventory order — every path funnels through the same shaping step.
No method-specific data (Plaid tokens, bank routing/account details, Apple/Google Pay device
tokens, PayPal order ids, etc.) ever appears here. paymentSessionGid here should match the value
you recorded from the checkout-session mint response — use it to look up and reconcile the order
in your own system. customerGid in this payload falls back to whatever customer-gid you
supplied as a prop if it isn't otherwise available. For an inventory-order charge,
amount/currencyCode/paymentMethodGid can occasionally come back null if that response
doesn't carry those fields in quite the same shape as a one-time payment does.
7. Prop interactions
customer-gid overrides prefill
Setting customer-gid overrides customer-detail-prefill-json-string entirely: if a
customerGid is present, the widget ignores the prefill JSON altogether, loads that customer's
saved details itself, and permanently locks name/email/phone as read-only — this happens
regardless of any disableInput flag inside the prefill JSON (that flag only matters when there's
no customerGid at all).
customer-detail-prefill-json-string shape
{
"name": "Jane Doe",
"email": "jane@example.com",
"countryCallingCode": "+1",
"phone": "5555550100",
"disableInput": true
}
All fields are optional. countryCallingCode and phone are supplied separately — phone is
just the national-number digits (no country code, no leading +), and the widget concatenates
them itself (countryCallingCode + phone) to validate the result as a full E.164 number. For the
example above, that's +1 + 5555550100 → +15555550100. Passing a combined string like
"+15555550100" directly in phone will fail validation — keep the calling code and the local
number as two separate fields.
disableInput: true locks all three of name/email/phone together as read-only once they're
pre-filled — it's a single switch, not a per-field setting. Omit it (or set it false) to let the
shopper edit the pre-filled values.
Malformed JSON here is silently ignored — see the silent-failure list in §5.
8. pay-through-saved-method shape
Supported for Card and ACH only — wallets (Google Pay, Apple Pay, PayPal) have no saved-method flow.
{
"paymentMethodGid": "payment-method-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
paymentMethodGid is the only field, and it's required. Unlike the prefill JSON props above, a
malformed or incomplete value here is treated as a fatal setup error (missing or empty
paymentMethodGid) — the widget will not silently fall back to the normal entry form, since that
could mean an expected charge silently never happens. Get the paymentMethodGid from a prior
charge made with save-payment-method set — it's the paymentMethodGid field on that charge's
successCallback payload (see §6).
This flow also has no form to fall back to on a payment-attempt failure (a declined saved
card/bank account), so — unlike a normal Card/ACH-tab submission — that failure is also treated as
fatal (errorCallback fires and the widget UI does not remain usable for a retry).
This also requires the customerGid prop to be populated and present.
9. Theming
theme/themeJsonString accepts a flat JSON object, identical for both <swirepay-checkout> and
<swirepay-contact-onboarding> — every key is optional; anything you omit falls back to the SDK's
defaults, and if the JSON itself is malformed or fails validation the entire theme is discarded
(fails closed to defaults) rather than partially applied.
| Key | Type | Affects |
|---|---|---|
componentBackgroundColor | color | Overall component background |
inputBackgroundColor | color | Input/select field background |
inputBorderRadius | length (px/rem/em/%) | Input/select corner radius |
infoBackgroundColor | color | Info banner background |
infoBorderColor | color | Info banner border |
buttonBackgroundColor | color | Primary button background |
buttonBorderRadius | length | Primary button corner radius |
buttonTextColor | color | Primary button text color |
spacingXs / spacingSm / spacingMd / spacingLg | length | Internal spacing scale |
fontFamily | non-empty string | Font family |
fontColor | color | Body text color |
fontColorError | color | Validation-error text color |
fontSizeSm / fontSizeMd / fontSizeLg | length | Font size scale |
fontWeightRegular / fontWeightMedium / fontWeightBold | integer 100–900 | Font weight scale |
"color" accepts hex (#rgb, #rgba, #rrggbb, #rrggbbaa), rgb()/rgba(), or
hsl()/hsla(). Theming does not restyle the native Google Pay, Apple Pay, or PayPal buttons —
only the rest of the widget.
Example:
{
"buttonBackgroundColor": "#3b5bfd",
"buttonTextColor": "#ffffff",
"inputBorderRadius": "8px",
"fontFamily": "Inter, sans-serif"
}
Note: if the theme JSON is invalid, the console warning the SDK logs is worded for
swirepay-contact-onboarding even when it's actually the checkout element that failed to parse
the theme — a cosmetic quirk shared by both elements (they use the same theming code internally).
10. Common pitfalls
- Minting the session token in browser JS — only ever do this server-side in production.
- Reading amount/currency off the element — there isn't a real one; those live entirely in the
checkout session you created server-side.
sdk.d.ts(the SDK's hand-written type declarations) does listamount/currencyCode/paymentOptionsJsonStringon the element's type, but these are stale leftovers from an internal implementation detail — the actual custom element does not accept them as attributes, and setting them from JS has no effect on what gets charged. - Not recording
paymentSessionGidfrom the checkout-session response — you'll need it later to reconcile the webhook/successCallbackoutcome against your own order records. - Assuming
successCallbackalone is authoritative — configure a dashboard webhook too (see §1); the callback can be missed if the browser tab closes right after a charge. - Expecting
causein the error payload to carry an HTTP status code — it doesn't; onlyname/message/stacksurvive for an error-based cause. Parsemessageinstead if you need to distinguish failure reasons. - Combining a wallet button with an inventory-order checkout expecting it to work — inventory
order currently only supports Card and ACH; any wallet
paymentTypeyou include is simply dropped from the checkout for that session. - Passing a combined
+1XXXXXXXXXXstring asphonein the prefill JSON —phoneis just the national digits; the country calling code is a separate field (see §7).
See each method's own guide for pitfalls specific to that payment method: Card, ACH, Apple Pay, PayPal, Google Pay.