Integrating payments with the Swirepay Flutter SDK
The Swirepay Flutter SDK opens a native payment modal (Android and iOS) that can collect Card, ACH (Pay by Bank), and a platform wallet in one flow. Which methods appear is controlled by the paymentType array on the checkout session you create server-side, and by the enabledPaymentMethods list you pass when launching the modal.
Swirepay delivers this SDK to merchants as a local folder (swirepay_payments_sdk). It is not published to pub.dev. You add it to your Flutter app with a path dependency. Native binaries ship inside that folder — you do not download a separate AAR or CocoaPod.
This guide mirrors the Android Payment Center guide, adapted for Flutter. Card, ACH, and Google Pay share the same backend session step and the same result pattern. Apple Pay is iOS-only and is covered in §9.
The same guide ships inside the local SDK folder you send to merchants:
packages/flutter/swirepay_payments_sdk/docs/INTEGRATION.md.
| Payment method | Platform | Checkout paymentType value | SDK PaymentMethodType |
|---|---|---|---|
| Card | Android, iOS | "CARD" | PaymentMethodType.card |
| ACH / bank | Android, iOS | "ACH_LEGACY" | PaymentMethodType.bank |
| Google Pay | Android only | "GOOGLE_PAY_US" | PaymentMethodType.googlePay |
| Apple Pay (hosted page) | iOS only | "APPLE_PAY_US" | PaymentMethodType.applePay |
| Apple Pay (native PassKit) | iOS only | Not the checkout-session modal | SwirepayNativeApplePay (separate API) |
1. The end-to-end integration flow
Every Card / ACH / Google Pay integration follows the same shape:
- Your backend calls Swirepay’s
POST /v3/checkout-sessionwith your secret key and the session config (scope,amount/currency,paymentType,acceptedDomain, etc.). This must happen server-side; the secret key must never ship inside your Flutter app. - Swirepay returns a compact encryption token plus a
paymentSessionGididentifying this charge attempt. - Your backend records
paymentSessionGid(you will reconcile final status against it) and sends the encryption token (and optionalpaymentSessionGid) to your Flutter app. - The app builds
CheckoutSessionCredentialsfrom that token, callsSwirepayPaymentsSdk.initialize(...), and launches the payment modal withSwirepayPaymentsSdk.startPayment(payload). - The shopper completes Card, ACH (via Plaid Link), and/or Google Pay inside the native SDK UI. Sensitive card and wallet data stay inside Swirepay’s encrypted pipeline / Google Pay tokenization — your app does not need to POST raw PANs to your own servers.
- Also configure a webhook in the Swirepay merchant dashboard. The in-app
PaymentFlowResultis a best-effort client signal — it can be missed if the app is killed right after a charge. The webhook is the authoritative source of truth for final transaction status; match it to thepaymentSessionGidyou stored in step 3.
Apple Pay (hosted page and native PassKit) uses the same initialize call, but those wallet paths still accept a secretKey / publishableKey on the client until they fully migrate to checkout session. Prefer checkout session for Card, ACH, and Google Pay.
2. Live vs. Test mode
Your Swirepay account has a Live / Test toggle in the merchant dashboard, each with its own secret key:
- Test / staging — no real money movement; use test cards, Plaid sandbox-style secure links, and staging Google Pay credentials where applicable.
- Live / production — real charges.
On Flutter, environment is selected when you initialize:
| Goal | Typical settings |
|---|---|
| Staging | test: true → SDK uses https://staging-backend.swirepay.com |
| Production | test: false → SDK uses https://api.swirepay.com |
| Custom gateway | Pass a non-blank baseUrl (overrides the test host mapping) |
ACH also uses plaidSecureLinkIsTest on initialize (?isTest= on the Plaid secure-link token API). Google Pay presentation can include an optional isTest flag for credential fetch.
The client integration shape is the same in both modes — only the secret key used to mint the checkout session (and the Live/Test dashboard mode) changes.
3. Step 1 — Get a secure token from your backend
Minting a session requires your secret key, so this call must happen on your server — never hard-code sk_* in a production app.
POST https://api.swirepay.com/v3/checkout-session
Content-Type: application/json
x-api-key: <your secret key>
{
"scope": "payment",
"amount": 2599,
"currency": "USD",
"paymentType": ["CARD", "ACH_LEGACY", "GOOGLE_PAY_US"],
"inventoryOrder": null,
"acceptedDomain": "https://your-site.example.com"
}
| Field | Notes |
|---|---|
scope | Use "payment" for a one-time / standalone charge. |
amount | Smallest currency unit (e.g. 2599 = $25.99). |
currency | ISO 4217, e.g. "USD". |
paymentType | Include only the methods you want for this session. Combine values to offer multiple tabs/buttons in one modal. |
acceptedDomain | HTTPS origin associated with your integration (no path, query, or fragment). |
Example paymentType combinations
- Card only:
["CARD"] - Card + ACH:
["CARD", "ACH_LEGACY"] - Card + ACH + Google Pay (Android):
["CARD", "ACH_LEGACY", "GOOGLE_PAY_US"] - Hosted Apple Pay (iOS): include
"APPLE_PAY_US"when that method is offered in the modal
Response (shape):
{
"entity": {
"scope": "payment",
"encryption": "eyJjIjoiLi4uIn0=...",
"paymentSessionGid": "payment-session-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
entity.encryption— Base64 UTF-8 JSON with keysc(checkout session id),k(server public key),i(key id),e(expiry epoch ms). Pass this string intoCheckoutSessionCredentials(encryptionBase64: ...).entity.paymentSessionGid— Record this in your database immediately. You will use it to reconcile webhook / app callback outcomes against your order.
// server.js — never expose SWIREPAY_SECRET_KEY to the mobile app
app.post('/api/swirepay/checkout-token', async (req, res) => {
const response = await fetch('https://api.swirepay.com/v3/checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.SWIREPAY_SECRET_KEY,
},
body: JSON.stringify({
scope: 'payment',
amount: req.body.amountInCents,
currency: 'USD',
paymentType: req.body.paymentTypes, // e.g. ["CARD","ACH_LEGACY","GOOGLE_PAY_US"]
inventoryOrder: null,
acceptedDomain: 'https://your-site.example.com',
}),
});
const { entity } = await response.json();
// Persist entity.paymentSessionGid against your order/customer here
res.json({
encryption: entity.encryption,
paymentSessionGid: entity.paymentSessionGid,
scope: entity.scope,
});
});
Your Flutter app only needs encryption, scope, and paymentSessionGid. It does not need the secret key to start a Card, ACH, or Google Pay checkout.
4. Step 2 — Add the SDK to your Flutter app
Swirepay sends you the plugin folder swirepay_payments_sdk. Copy that entire folder into your project (or keep it next to the app). Do not run flutter pub publish, and do not add this package from pub.dev.
What you must keep
Copy the folder as-is. Do not delete native sources — the plugin will not build without them.
| Path inside the folder | Why it must stay |
|---|---|
lib/ | Dart API (SwirepayPaymentsSdk, payloads, Apple Pay) |
android/ including android/repo/ | Flutter Android bridge + embedded Kotlin SDK |
ios/ including ios/SwirepayPaymentsSDK/ | Flutter iOS bridge + embedded Swift SDK |
pubspec.yaml | Plugin declaration (publish_to: none) |
You can ignore example/ — it is a reference app, not something you add as a dependency. Do not point your path at example/.
Typical layout:
your-app/
pubspec.yaml
lib/
android/
ios/
vendor/
swirepay_payments_sdk/ # the folder Swirepay sent you
pubspec.yaml
lib/
android/
ios/
docs/
INTEGRATION.md # this guide
pubspec.yaml
Use a path dependency. Paths are relative to your app’s pubspec.yaml. Use forward slashes even on Windows.
dependencies:
flutter:
sdk: flutter
swirepay_payments_sdk:
path: vendor/swirepay_payments_sdk
If the SDK sits next to the app instead of inside it:
swirepay_payments_sdk:
path: ../swirepay_payments_sdk
Then:
flutter pub get
Requirements
| Requirement | Value |
|---|---|
| Flutter | 3.10 or newer |
| Dart | 3.x (>=3.0.0 <4.0.0) |
Android minSdk | 24 or higher |
Android compileSdk | 34 or compatible |
| iOS | 15.0 or newer, Xcode on macOS |
| Google Pay | Google Play services on the device or emulator |
| ACH | Host app Android applicationId / iOS bundle id registered with Plaid |
Android host app
In android/app/build.gradle or android/app/build.gradle.kts, set minSdk to at least 24:
defaultConfig {
// Swirepay native SDK requires API 24+.
minSdk = maxOf(flutter.minSdkVersion, 24)
}
You do not add a Maven repository or an AAR by hand. The plugin already ships com.swirepay:swirepaysdk under android/repo/.
The SDK merges INTERNET and its internal activities (PaymentModalActivity, ACS challenge, and so on). You do not need to declare those activities yourself.
Plaid (ACH): register your app applicationId (for example com.yourcompany.yourapp) in the Plaid Dashboard. The SDK sends the host app package name — not the library namespace com.swirepay.swirepaysdk.
iOS host app
From your app (not from inside the plugin folder):
cd ios
pod install
cd ..
Always open ios/Runner.xcworkspace, not Runner.xcodeproj.
The plugin compiles the vendored Swift SDK through CocoaPods. You do not add the iOS SDK as a separate Swift package.
5. Step 3 — Initialize and launch the payment modal
Import:
import 'package:flutter/services.dart';
import 'package:swirepay_payments_sdk/swirepay_payments_sdk.dart';
Decode credentials from your backend
final credentials = CheckoutSessionCredentials(
encryptionBase64: response.encryption, // entity.encryption
scope: response.scope ?? 'payment',
paymentSessionGid: response.paymentSessionGid, // optional but recommended
);
You can also pass the decoded fields (checkoutSessionId, serverPublicKey, keyId, expiresAt) instead of encryptionBase64. Prefer passing the blob unchanged.
Initialize
Call once at startup, or immediately before opening the modal. initialize requires either a checkoutSession or a non-blank secretKey. For Card, ACH, and Google Pay, pass the checkout session and omit the secret key in production.
await SwirepayPaymentsSdk.initialize(
checkoutSession: credentials,
test: false, // true → staging host when baseUrl is blank
accountGid: 'account-…', // required for ACH + Google Pay credentials
plaidSecureLinkIsTest: false, // ACH secure-link ?isTest=
// secretKey / publishableKey: avoid in production builds for Card / ACH / Google Pay
);
| Argument | Required | Meaning |
|---|---|---|
checkoutSession | Card, ACH, Google Pay | Session minted by your backend |
secretKey | Legacy wallets only | x-api-key. Do not ship sk_* for the checkout-session path |
test | No (false) | Staging vs production when baseUrl is empty |
accountGid | ACH + Google Pay | Swirepay account id (account-…) |
publishableKey | Wallets | pk_… when a wallet path still needs it |
plaidSecureLinkIsTest | No (true) | Plaid Link isTest query |
baseUrl | No | Custom API root; overrides the test host mapping |
try {
await SwirepayPaymentsSdk.initialize(
checkoutSession: credentials,
test: false,
accountGid: accountGid,
plaidSecureLinkIsTest: false,
);
} on PlatformException catch (e) {
// e.code / e.message — show a recoverable error
}
Launch
startPayment presents the native modal and completes when the shopper finishes, cancels, or the SDK returns an error.
final result = await SwirepayPaymentsSdk.startPayment(payload);
if (result.success) {
// result.displayJson — pretty JSON for logs / support
// result.wrapperEvent?.paymentSessionGid
// Still reconcile via webhook + the paymentSessionGid you stored in step 1
} else {
// Recoverable failure — show result.displayJson and let the shopper retry
}
Shared PaymentPayload fields
| Field | Required | Description |
|---|---|---|
amount | Yes | Display / host amount in major units (e.g. "25.99"). The charged amount comes from the checkout session minted server-side. |
currencyCode | Yes | ISO 4217, e.g. "USD". |
customer | Yes | CustomerInfo(fullName, email, phoneCode, phoneNumber). |
card | Structure | Prefill for the Card tab; shopper can edit in the modal. Defaults to empty fields. |
address | Yes | Billing address fields. |
paymentMethod | Yes | Default / first method (card, bank, googlePay, or applePay). |
enabledPaymentMethods | No | If set, controls which tabs/buttons appear. If omitted, only paymentMethod is shown. |
bankDetails | For ACH | e.g. BankDetails(businessType: 'Individual'). Plaid token is filled in-modal. |
googlePay | For Google Pay | GooglePayPresentation(...) — see §8. |
applePay | For hosted Apple Pay | ApplePayPresentation(...) — see §9. |
checkoutStatementDescriptor | Yes for card/bank | Statement descriptor for the session. |
checkoutCaptureMethod | No | Defaults to "AUTOMATIC". |
checkoutConfirmMethod | No | Defaults to "AUTOMATIC". |
Map a method to the checkout-session string with paymentMethod.checkoutPaymentType (CARD, ACH_LEGACY, GOOGLE_PAY_US, APPLE_PAY_US).
6. Card payments
Enable "CARD" in the checkout session paymentType array, then include PaymentMethodType.card in enabledPaymentMethods (or set paymentMethod: PaymentMethodType.card alone).
What the shopper sees
The modal collects card number, expiry, CVV, cardholder name, customer contact, and billing address. Prefill via PaymentPayload.card / customer / address if you already know them. Prefer leaving PAN and CVV empty so the shopper types them inside the SDK.
What the SDK does (high level)
- Validates the checkout session and establishes the encrypted channel.
- Creates / attaches customer and a
CARDpayment method. - Updates and confirms the payment session.
- Returns a
PaymentFlowResultwith a JSON envelope (see §10).
Minimal Card-only example
final result = await SwirepayPaymentsSdk.startPayment(
PaymentPayload(
amount: '25.99',
currencyCode: 'USD',
customer: const CustomerInfo(
fullName: 'Jane Doe',
email: 'jane@example.com',
phoneCode: '+1',
phoneNumber: '4155552671',
),
card: const CardDetails(), // shopper enters PAN / expiry / CVV in the modal
address: const AddressDetails(
street: '1 Market St',
city: 'San Francisco',
state: 'CA',
country: 'US',
zipCode: '94105',
),
paymentMethod: PaymentMethodType.card,
enabledPaymentMethods: const [PaymentMethodType.card],
checkoutStatementDescriptor: 'Your Store',
checkoutCaptureMethod: 'AUTOMATIC',
checkoutConfirmMethod: 'AUTOMATIC',
),
);
Card tips for merchants
- Do not log or store full card numbers from your own forms if you collect them outside the SDK.
- Prefer letting the shopper enter PAN/CVV in the SDK modal.
- Declines and validation errors leave the modal usable for retry (recoverable failures).
- Always rely on webhooks +
paymentSessionGidbefore fulfilling goods.
7. ACH (Pay by Bank)
Enable "ACH_LEGACY" in the checkout session. In the SDK, ACH is PaymentMethodType.bank (UI label: ACH).
Extra requirements
| Requirement | Why |
|---|---|
accountGid on initialize | Used to call POST /v3/integration/plaid/{accountGid}/create-secure-link-token. |
plaidSecureLinkIsTest | Controls sandbox vs live-style Plaid secure link (?isTest=). |
| Host app package / bundle id registered with Plaid | OAuth / Link redirects use your Android applicationId or iOS bundle id. |
bankDetails on the payload | At least businessType: "Individual" or "Business" (sent as INDIVIDUAL / BUSINESS). |
Shopper flow
- Shopper opens the ACH tab and taps Add bank account.
- SDK requests a Plaid secure link token, then opens Plaid Link.
- On success, the modal stores a
plaidPublicTokenonBankDetails. - Shopper taps Pay → SDK creates an
ACH_LEGACYpayment method and confirms the payment session.
If Pay is tapped without completing Plaid Link, the SDK returns an error such as missing Plaid public token.
If your app already completed Plaid Link, you may pass plaidPublicToken on BankDetails. Otherwise leave it null and let the modal run Link.
ACH example
await SwirepayPaymentsSdk.initialize(
checkoutSession: credentials,
test: false,
accountGid: 'account-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
plaidSecureLinkIsTest: false,
);
final result = await SwirepayPaymentsSdk.startPayment(
PaymentPayload(
amount: '25.99',
currencyCode: 'USD',
customer: const CustomerInfo(
fullName: 'Jane Doe',
email: 'jane@example.com',
phoneCode: '+1',
phoneNumber: '4155552671',
),
address: const AddressDetails(
street: '1 Market St',
city: 'San Francisco',
state: 'CA',
country: 'US',
zipCode: '94105',
),
paymentMethod: PaymentMethodType.bank,
enabledPaymentMethods: const [PaymentMethodType.bank],
bankDetails: const BankDetails(businessType: 'Individual'),
checkoutStatementDescriptor: 'Your Store',
checkoutCaptureMethod: 'AUTOMATIC',
checkoutConfirmMethod: 'AUTOMATIC',
),
);
ACH tips for merchants
- ACH settlement can take longer than card authorization — treat webhook / bank status as source of truth for fulfillment.
- Use Test mode +
plaidSecureLinkIsTest: truewhile integrating. - Never put your Swirepay secret key in the app just to create Plaid tokens; the checkout-session +
accountGidpath is the supported production approach.
8. Google Pay (Android)
Enable "GOOGLE_PAY_US" in the checkout session. In the SDK, use PaymentMethodType.googlePay and supply GooglePayPresentation.
Google Pay is Android only. It opens the native Google Pay sheet and sends the encrypted wallet token to Swirepay (processingMode: API_ONLY). Do not pass PaymentMethodType.googlePay on iOS.
Extra requirements
| Requirement | Why |
|---|---|
accountGid on initialize | Fetches Google Pay US credentials for your account. |
GooglePayPresentation | Country and merchant display name must be non-blank. |
| Device / Play services | Native Google Pay sheet via Play services Wallet. |
| DIRECT tokenization configured for the account | SDK expects Swirepay credentials with tokenizationSpecification.type == "DIRECT". |
GooglePayPresentation
const GooglePayPresentation(
transactionCountryCode: 'US', // ISO 3166-1 alpha-2
merchantDisplayName: 'Your Store', // shown on the Google Pay sheet
isTest: null, // optional credential-environment override
paymentSessionCaptureMethod: 'AUTOMATIC',
paymentSessionConfirmMethod: 'AUTOMATIC',
);
Shopper flow
- Shopper taps Google Pay in the modal.
- SDK loads account credentials from Swirepay.
- Native Google Pay sheet opens; shopper confirms.
- SDK forwards only the encrypted wallet token to Swirepay (
WALLET/GOOGLE_PAY_US), then updates and confirms the payment session. - If the session requires 3-D Secure / ACS action, the SDK presents the challenge UI and completes the action APIs.
Your app never decrypts Google Pay payload data on-device.
Google Pay example
await SwirepayPaymentsSdk.initialize(
checkoutSession: credentials,
test: false,
accountGid: 'account-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
);
final result = await SwirepayPaymentsSdk.startPayment(
PaymentPayload(
amount: '25.99',
currencyCode: 'USD',
customer: const CustomerInfo(
fullName: 'Jane Doe',
email: 'jane@example.com',
phoneCode: '+1',
phoneNumber: '4155552671',
),
address: const AddressDetails(country: 'US'),
paymentMethod: PaymentMethodType.googlePay,
enabledPaymentMethods: const [PaymentMethodType.googlePay],
googlePay: const GooglePayPresentation(
transactionCountryCode: 'US',
merchantDisplayName: 'Your Store',
paymentSessionCaptureMethod: 'AUTOMATIC',
paymentSessionConfirmMethod: 'AUTOMATIC',
),
checkoutStatementDescriptor: 'Your Store',
checkoutCaptureMethod: 'AUTOMATIC',
checkoutConfirmMethod: 'AUTOMATIC',
),
);
Offering Card + ACH + Google Pay together
Mint the session with all three paymentType values, then:
PaymentPayload(
paymentMethod: PaymentMethodType.card, // default tab
enabledPaymentMethods: const [
PaymentMethodType.card,
PaymentMethodType.bank,
PaymentMethodType.googlePay, // omit on iOS
],
bankDetails: const BankDetails(businessType: 'Individual'),
googlePay: const GooglePayPresentation(
transactionCountryCode: 'US',
merchantDisplayName: 'Your Store',
),
// amount, currencyCode, customer, address, checkout fields...
);
9. Apple Pay (iOS)
There are two Apple Pay options. They are not interchangeable.
| Option | When to use | Dart entry |
|---|---|---|
| Hosted Apple Pay | Your checkout page already embeds PayPal JS components=applepay | startPayment + PaymentMethodType.applePay |
| Native Apple Pay | You want the system PassKit sheet | SwirepayNativeApplePay.presentNativeApplePay |
Do not pass PaymentMethodType.applePay on Android. Do not use startPayment for the native PassKit path.
Typical split:
import 'dart:io' show Platform;
final method = Platform.isIOS
? PaymentMethodType.applePay
: PaymentMethodType.googlePay;
9.1 Hosted Apple Pay (WKWebView)
Same modal as Card / ACH. The SDK loads your HTTPS page in WKWebView. That page should embed the PayPal JS SDK with components=applepay.
- Host an HTTPS page (not
example.com). - When checkout finishes, redirect to a URL that starts with
completionUrlPrefix(defaultswirepay-wallet://result). - Include
success=true(and preferablypaymentSessionGid,paymentMethodGid) on that redirect.
The plugin appends swirepay_sdk=1, completionPrefix, and swirepay_amount_cents to jsCheckoutUrl.
final wallet = ApplePayPresentation( // typedef of WalletCheckoutPresentation
jsCheckoutUrl: 'https://your-domain.com/wallet-checkout',
completionUrlPrefix: 'swirepay-wallet://result',
transactionCountryCode: 'US',
merchantDisplayName: 'Your Merchant',
paymentSessionDescription: 'Online payment',
paymentSessionStatementDescriptor: 'Apple Pay payment',
paymentSessionCaptureMethod: 'AUTOMATIC',
paymentSessionConfirmMethod: 'AUTOMATIC',
);
final result = await SwirepayPaymentsSdk.startPayment(
PaymentPayload(
amount: '25.99',
currencyCode: 'USD',
customer: const CustomerInfo(
fullName: 'Jane Doe',
email: 'jane@example.com',
phoneCode: '+1',
phoneNumber: '4155552671',
),
address: const AddressDetails(country: 'US'),
paymentMethod: PaymentMethodType.applePay,
applePay: wallet,
),
);
jsCheckoutUrl must be a real HTTPS URL. example.com is rejected.
9.2 Native Apple Pay (PassKit)
Presents the system Apple Pay sheet, then:
POST /v1/payment-method(WALLET/APPLE_PAY_US/API_ONLY/APPLE_PKTOKEN)POST /v2/payment-sessionwithpaymentMethodType: ["WALLET"]- ACS WebView +
/actionifREQUIRE_ACTION
Call SwirepayPaymentsSdk.initialize first. This path still uses the keys you passed to initialize (secretKey / publishableKey). It does not use startPayment.
Xcode — Apple Pay capability
In your app (Runner), not the plugin:
- Open
ios/Runner.xcworkspace. - Runner target → Signing & Capabilities.
- + Capability → Apple Pay.
- Add the merchant id Swirepay issued.
ios/Runner/Runner.entitlements:
<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.com.your.apple.pay.id</string>
</array>
The same id must match:
- This entitlement
NativeApplePayPresentation.merchantIdentifier- The Payment Processing Certificate / private key on Swirepay
Dart
import 'package:flutter/foundation.dart';
import 'package:swirepay_payments_sdk/swirepay_payments_sdk.dart';
Future<void> payWithNativeApplePay() async {
if (defaultTargetPlatform != TargetPlatform.iOS) return;
final canPay = await SwirepayNativeApplePay.canMakeNativePayments();
if (!canPay) {
// No PassKit / missing entitlement / Simulator limitation
return;
}
final result = await SwirepayNativeApplePay.presentNativeApplePay(
amountMinorUnits: parseAmountToCents('25.99'), // $25.99 → 2599
currencyCode: 'USD',
presentation: const NativeApplePayPresentation(
merchantIdentifier: 'merchant.com.your.apple.pay.id',
transactionCountryCode: 'US',
merchantDisplayName: 'Your Merchant',
paymentSessionCaptureMethod: 'AUTOMATIC',
paymentSessionConfirmMethod: 'AUTOMATIC',
),
);
if (result.success) {
// result.displayJson
// result.wrapperEvent?.paymentSessionGid
} else {
// cancel, decrypt mismatch, session error — result.displayJson
}
}
Throws PlatformException(code: unsupported_platform) on Android / web.
amountMinorUnits is cents. PaymentPayload.amount is major units ("25.99"). Use parseAmountToCents('25.99') only for native Apple Pay.
NativeApplePayPresentation
| Property | Required | Default | Meaning |
|---|---|---|---|
merchantIdentifier | Yes | — | PassKit merchant id (must match the entitlement) |
transactionCountryCode | No | "US" | ISO 3166-1 alpha-2 |
merchantDisplayName | Yes | — | Line on the Apple Pay sheet |
paymentSessionCaptureMethod | No | "AUTOMATIC" | POST /v2/payment-session |
paymentSessionConfirmMethod | No | "AUTOMATIC" | POST /v2/payment-session |
The SDK never decrypts PKPayment.token.paymentData on device.
Test native Apple Pay
- Use a physical iPhone. Simulator Simulated Cards are dummy tokens (
AEADBadTagException). - Staging (
test: true): device iCloud = App Store Connect sandbox tester, then add an Apple Pay test card in Wallet. Settings → App Store → Sandbox Account is only for IAP, not Apple Pay. - Production (
test: false): real Wallet card againsthttps://api.swirepay.com. Do not mix sandbox tokens with the production certificate (or the reverse).
10. Success and error results
PaymentFlowResult
Returned by SwirepayPaymentsSdk.startPayment:
| Field | Meaning |
|---|---|
success | true when the native flow reports success |
rawResponse | JSON body / envelope |
displayJson | Pretty-printed JSON for logs or a support screen |
wrapperEvent | Parsed wrapper when the body is a payment_completed envelope |
Native Apple Pay returns NativeApplePayFlowResult with the same fields.
Success / completion envelope (Card / ACH path)
On completion the modal typically returns a wrapper like:
{
"sdk_type": "wrapper",
"session_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"request_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"method": "processPaymentInternal",
"event": "payment_completed",
"success": true,
"payment_method_gid": "paymentmethod-…",
"payment_session_gid": "paymentsession-…",
"total_time_ms": 3010,
"details": {}
}
final sessionGid = result.wrapperEvent?.paymentSessionGid;
final methodGid = result.wrapperEvent?.paymentMethodGid;
Use payment_session_gid (and the paymentSessionGid you stored from step 1) to reconcile against your backend and webhooks. Do not mark an order paid from the client callback alone.
Failure shapes
- Recoverable payment failures (declined card, etc.):
success == falsewith details /errorin the JSON; the modal may remain usable for retry depending on the path. - Setup / missing config (no checkout session, missing
accountGid, incomplete Google Pay presentation, canceled Google Pay sheet, missing Plaid token): JSON error messages inrawResponse. - Channel failures throw
PlatformException(e.code/e.message) instead of aPaymentFlowResult.
Treat webhooks as authoritative for fulfillment.
Android-only debug log (secrets and wallet tokens redacted):
final log = await SwirepayPaymentsSdk.getDebugLog();
11. Which API do I call?
| Option | Platform | Dart entry |
|---|---|---|
| Card | Android + iOS | startPayment + PaymentMethodType.card |
| Bank | Android + iOS | startPayment + PaymentMethodType.bank |
| Google Pay | Android | startPayment + PaymentMethodType.googlePay + googlePay: |
| Apple Pay (hosted page) | iOS | startPayment + PaymentMethodType.applePay + applePay: |
| Apple Pay (native) | iOS | SwirepayNativeApplePay.presentNativeApplePay |
Initialize once, then call the matching option.
| Feature | Android | iOS |
|---|---|---|
| Card | Yes | Yes |
| Bank (ACH) | Yes | Yes |
| Google Pay (native sheet) | Yes | No |
| Apple Pay (WKWebView) | No | Yes |
| Apple Pay (PassKit) | No | Yes |
12. Testing your integration
- Use Test / staging secret keys on your backend to mint sessions.
- Enable only the method under test first (
CARD, thenACH_LEGACY, thenGOOGLE_PAY_USor Apple Pay), then combine. - Walk Card end-to-end with Swirepay test cards. Leave card fields empty and type them in the modal.
- For ACH, set
plaidSecureLinkIsTest: trueand complete Plaid Link before Pay. - For Google Pay, use an Android device or emulator with Play services and a Google account that can complete the sheet in your test environment.
- For hosted Apple Pay, use a real HTTPS page (not
example.com) and confirm the completion redirect. - For native Apple Pay, use a physical iPhone, matching merchant id, and the sandbox rules in §9.2.
- Confirm your webhook receives the same
paymentSessionGidyou stored at session create. - Only switch the backend to a live secret key after the full flow behaves as expected.
The folder you received may include an example/ app. It is a demo only. Sample code may create the checkout session in-process with sk_*. Production apps must create the session on your backend.
13. Merchant checklist
| Step | Card | ACH | Google Pay | Apple Pay |
|---|---|---|---|---|
Local path dependency on swirepay_payments_sdk | Yes | Yes | Yes | Yes |
Android minSdk ≥ 24 / iOS 15+ | Yes | Yes | Android | iOS |
Backend POST /v3/checkout-session | CARD | ACH_LEGACY | GOOGLE_PAY_US | APPLE_PAY_US for hosted page |
Persist paymentSessionGid | Yes | Yes | Yes | Yes |
Pass CheckoutSessionCredentials into initialize | Required | Required | Required | Hosted / native still use keys on initialize |
Pass accountGid | Optional | Required | Required | Wallet credential fetch |
| Extra payload | Card / address / customer | BankDetails + Plaid in UI | GooglePayPresentation | ApplePayPresentation or NativeApplePayPresentation |
| Result API | startPayment | same | same | startPayment or presentNativeApplePay |
| Webhook for fulfillment | Yes | Yes | Yes | Yes |
| Secret key in the app binary | No | No | No | Avoid; required today only for wallet paths that have not migrated |
14. Common pitfalls
- Adding the package from pub.dev — this SDK is local only. Use a
path:dependency on the folder Swirepay sent you. - Pointing
pathatexample/— depend on the plugin root (the folder that containspubspec.yamlandlib/). - Deleting
android/repoorios/SwirepayPaymentsSDK— those folders are the native SDKs. The plugin will not compile without them. - Minting the checkout session inside the app — only do this in local demos. Production must mint server-side with the secret key.
- Not recording
paymentSessionGidfrom step 1 — you will not be able to reconcile webhook / callback outcomes reliably. - Assuming
PaymentFlowResultalone is authoritative — configure a dashboard webhook; the app can be killed before you process the callback. - ACH without
accountGidor Plaid package registration — secure link / OAuth will fail. Register the host app id, notcom.swirepay.swirepaysdk. - Google Pay on iOS, or Apple Pay on Android — pick the wallet for the platform. Google Pay needs
GooglePayPresentation; hosted Apple Pay needs a realjsCheckoutUrl. - Using
startPaymentfor native Apple Pay — that is the hosted-page option. Native PassKit isSwirepayNativeApplePay.presentNativeApplePay. - Native Apple Pay on Simulator — Simulated Cards produce dummy tokens. Use a physical device.
- Mismatch between session
paymentTypeandenabledPaymentMethods— the session must allow a method for the charge to succeed even if the UI shows it. - Trusting client-supplied amount for settlement — the amount charged is defined by the checkout session your backend created.
PaymentPayload.amountis a display string in major units ("25.99"), not cents. - Opening
Runner.xcodeproj— always openios/Runner.xcworkspaceafterpod install.
15. Related docs
- Android Payment Center: Integrating payments with the Swirepay Android SDK
- The same guide ships inside the local SDK folder:
docs/INTEGRATION.md - Web Payment Center — Card: Integrating card payments via
<swirepay-checkout> - Sibling web guides: ACH, Google Pay (same checkout-session model; different client surfaces)