Skip to main content

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:

  1. Your backend calls Swirepay's POST /v3/checkout-session with 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.
  2. Swirepay's backend returns a secure token, plus a paymentSessionGid identifying this specific charge attempt.
  3. 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.).
  4. Your frontend mounts <swirepay-checkout>, sets secureToken (and the other props) as JS properties, and registers successCallback/errorCallback.
  5. 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.
  6. Also configure a webhook in the Swirepay merchant dashboard for this account. successCallback is 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 (not successCallback alone) to actually mark an order as paid in your own system, matched up against the paymentSessionGid you 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:

MethodWhat Test mode does
CardFull sandbox — card numbers are tokenized against Swirepay's test rails, no real charge occurs.
ACHBank-account linking goes through Plaid's own sandbox/test credentials, no real transfer occurs.
Apple Pay, Google PayRuns against the underlying sandbox that backs both wallet buttons — no real charge occurs, no separate wallet-specific sandbox configuration needed.
PayPalRuns 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 attributeTypeRequiredDescription
accountGidaccount-gidstringYesYour Swirepay account identifier.
secureTokensecure-tokenstringYesThe compact token from the checkout session. May be attached after initial mount.
customerGidcustomer-gidstringNo*Gid of an existing customer to attach the payment to. *Required when savePaymentMethod is true. See §7 for how this overrides prefill.
savePaymentMethodsave-payment-methodboolean (attribute presence)NoSaves 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).
payThroughSavedMethodpay-through-saved-methodJSON stringNoCharges 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.
customerDetailPrefillJsonStringcustomer-detail-prefill-json-stringJSON stringNoPre-fills the contact-info fields. See the shape in §7. Overridden entirely by customerGid.
shopNameshop-namestringApple Pay onlyDisplay name shown in the Apple Pay payment sheet. Not used by any other payment method.
themeJsonStringthemeJSON stringNoVisual theming — see §9 below.
successCallback(JS property only)(payload: CheckoutSuccessPayload) => voidNoCalled once the charge succeeds. See §6 for the exact payload shape.
errorCallback(JS property only)(payload: ErrorPayload) => voidNoCalled 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 reaches errorCallback; only name/message/stack survive.
  • 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-scope secure-token, a malformed pay-through-saved-method JSON payload, setting save-payment-method without customer-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-string JSON — logged to the console only, the form falls back to empty name/email/phone fields.
  • Malformed theme JSON, 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 separatelyphone 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.

KeyTypeAffects
componentBackgroundColorcolorOverall component background
inputBackgroundColorcolorInput/select field background
inputBorderRadiuslength (px/rem/em/%)Input/select corner radius
infoBackgroundColorcolorInfo banner background
infoBorderColorcolorInfo banner border
buttonBackgroundColorcolorPrimary button background
buttonBorderRadiuslengthPrimary button corner radius
buttonTextColorcolorPrimary button text color
spacingXs / spacingSm / spacingMd / spacingLglengthInternal spacing scale
fontFamilynon-empty stringFont family
fontColorcolorBody text color
fontColorErrorcolorValidation-error text color
fontSizeSm / fontSizeMd / fontSizeLglengthFont size scale
fontWeightRegular / fontWeightMedium / fontWeightBoldinteger 100–900Font 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 list amount/currencyCode/paymentOptionsJsonString on 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 paymentSessionGid from the checkout-session response — you'll need it later to reconcile the webhook/successCallback outcome against your own order records.
  • Assuming successCallback alone is authoritative — configure a dashboard webhook too (see §1); the callback can be missed if the browser tab closes right after a charge.
  • Expecting cause in the error payload to carry an HTTP status code — it doesn't; only name/message/stack survive for an error-based cause. Parse message instead 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 paymentType you include is simply dropped from the checkout for that session.
  • Passing a combined +1XXXXXXXXXX string as phone in the prefill JSONphone is 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.