Skip to main content

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 methodPlatformCheckout paymentType valueSDK PaymentMethodType
CardAndroid, iOS"CARD"PaymentMethodType.card
ACH / bankAndroid, iOS"ACH_LEGACY"PaymentMethodType.bank
Google PayAndroid only"GOOGLE_PAY_US"PaymentMethodType.googlePay
Apple Pay (hosted page)iOS only"APPLE_PAY_US"PaymentMethodType.applePay
Apple Pay (native PassKit)iOS onlyNot the checkout-session modalSwirepayNativeApplePay (separate API)

1. The end-to-end integration flow​

Every Card / ACH / Google Pay integration follows the same shape:

  1. Your backend calls Swirepay’s POST /v3/checkout-session with 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.
  2. Swirepay returns a compact encryption token plus a paymentSessionGid identifying this charge attempt.
  3. Your backend records paymentSessionGid (you will reconcile final status against it) and sends the encryption token (and optional paymentSessionGid) to your Flutter app.
  4. The app builds CheckoutSessionCredentials from that token, calls SwirepayPaymentsSdk.initialize(...), and launches the payment modal with SwirepayPaymentsSdk.startPayment(payload).
  5. 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.
  6. Also configure a webhook in the Swirepay merchant dashboard. The in-app PaymentFlowResult is 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 the paymentSessionGid you 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:

GoalTypical settings
Stagingtest: true → SDK uses https://staging-backend.swirepay.com
Productiontest: false → SDK uses https://api.swirepay.com
Custom gatewayPass 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"
}
FieldNotes
scopeUse "payment" for a one-time / standalone charge.
amountSmallest currency unit (e.g. 2599 = $25.99).
currencyISO 4217, e.g. "USD".
paymentTypeInclude only the methods you want for this session. Combine values to offer multiple tabs/buttons in one modal.
acceptedDomainHTTPS 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 keys c (checkout session id), k (server public key), i (key id), e (expiry epoch ms). Pass this string into CheckoutSessionCredentials(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 folderWhy 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.yamlPlugin 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​

RequirementValue
Flutter3.10 or newer
Dart3.x (>=3.0.0 <4.0.0)
Android minSdk24 or higher
Android compileSdk34 or compatible
iOS15.0 or newer, Xcode on macOS
Google PayGoogle Play services on the device or emulator
ACHHost 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
);
ArgumentRequiredMeaning
checkoutSessionCard, ACH, Google PaySession minted by your backend
secretKeyLegacy wallets onlyx-api-key. Do not ship sk_* for the checkout-session path
testNo (false)Staging vs production when baseUrl is empty
accountGidACH + Google PaySwirepay account id (account-…)
publishableKeyWalletspk_… when a wallet path still needs it
plaidSecureLinkIsTestNo (true)Plaid Link isTest query
baseUrlNoCustom 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​

FieldRequiredDescription
amountYesDisplay / host amount in major units (e.g. "25.99"). The charged amount comes from the checkout session minted server-side.
currencyCodeYesISO 4217, e.g. "USD".
customerYesCustomerInfo(fullName, email, phoneCode, phoneNumber).
cardStructurePrefill for the Card tab; shopper can edit in the modal. Defaults to empty fields.
addressYesBilling address fields.
paymentMethodYesDefault / first method (card, bank, googlePay, or applePay).
enabledPaymentMethodsNoIf set, controls which tabs/buttons appear. If omitted, only paymentMethod is shown.
bankDetailsFor ACHe.g. BankDetails(businessType: 'Individual'). Plaid token is filled in-modal.
googlePayFor Google PayGooglePayPresentation(...) — see §8.
applePayFor hosted Apple PayApplePayPresentation(...) — see §9.
checkoutStatementDescriptorYes for card/bankStatement descriptor for the session.
checkoutCaptureMethodNoDefaults to "AUTOMATIC".
checkoutConfirmMethodNoDefaults 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)​

  1. Validates the checkout session and establishes the encrypted channel.
  2. Creates / attaches customer and a CARD payment method.
  3. Updates and confirms the payment session.
  4. Returns a PaymentFlowResult with 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 + paymentSessionGid before 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​

RequirementWhy
accountGid on initializeUsed to call POST /v3/integration/plaid/{accountGid}/create-secure-link-token.
plaidSecureLinkIsTestControls sandbox vs live-style Plaid secure link (?isTest=).
Host app package / bundle id registered with PlaidOAuth / Link redirects use your Android applicationId or iOS bundle id.
bankDetails on the payloadAt least businessType: "Individual" or "Business" (sent as INDIVIDUAL / BUSINESS).

Shopper flow​

  1. Shopper opens the ACH tab and taps Add bank account.
  2. SDK requests a Plaid secure link token, then opens Plaid Link.
  3. On success, the modal stores a plaidPublicToken on BankDetails.
  4. Shopper taps Pay → SDK creates an ACH_LEGACY payment 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: true while integrating.
  • Never put your Swirepay secret key in the app just to create Plaid tokens; the checkout-session + accountGid path 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​

RequirementWhy
accountGid on initializeFetches Google Pay US credentials for your account.
GooglePayPresentationCountry and merchant display name must be non-blank.
Device / Play servicesNative Google Pay sheet via Play services Wallet.
DIRECT tokenization configured for the accountSDK 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​

  1. Shopper taps Google Pay in the modal.
  2. SDK loads account credentials from Swirepay.
  3. Native Google Pay sheet opens; shopper confirms.
  4. SDK forwards only the encrypted wallet token to Swirepay (WALLET / GOOGLE_PAY_US), then updates and confirms the payment session.
  5. 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.

OptionWhen to useDart entry
Hosted Apple PayYour checkout page already embeds PayPal JS components=applepaystartPayment + PaymentMethodType.applePay
Native Apple PayYou want the system PassKit sheetSwirepayNativeApplePay.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.

  1. Host an HTTPS page (not example.com).
  2. When checkout finishes, redirect to a URL that starts with completionUrlPrefix (default swirepay-wallet://result).
  3. Include success=true (and preferably paymentSessionGid, 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:

  1. POST /v1/payment-method (WALLET / APPLE_PAY_US / API_ONLY / APPLE_PKTOKEN)
  2. POST /v2/payment-session with paymentMethodType: ["WALLET"]
  3. ACS WebView + /action if REQUIRE_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:

  1. Open ios/Runner.xcworkspace.
  2. Runner target → Signing & Capabilities.
  3. + Capability → Apple Pay.
  4. 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:

  1. This entitlement
  2. NativeApplePayPresentation.merchantIdentifier
  3. 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​

PropertyRequiredDefaultMeaning
merchantIdentifierYes—PassKit merchant id (must match the entitlement)
transactionCountryCodeNo"US"ISO 3166-1 alpha-2
merchantDisplayNameYes—Line on the Apple Pay sheet
paymentSessionCaptureMethodNo"AUTOMATIC"POST /v2/payment-session
paymentSessionConfirmMethodNo"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 against https://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:

FieldMeaning
successtrue when the native flow reports success
rawResponseJSON body / envelope
displayJsonPretty-printed JSON for logs or a support screen
wrapperEventParsed 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 == false with details / error in 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 in rawResponse.
  • Channel failures throw PlatformException (e.code / e.message) instead of a PaymentFlowResult.

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?​

OptionPlatformDart entry
CardAndroid + iOSstartPayment + PaymentMethodType.card
BankAndroid + iOSstartPayment + PaymentMethodType.bank
Google PayAndroidstartPayment + PaymentMethodType.googlePay + googlePay:
Apple Pay (hosted page)iOSstartPayment + PaymentMethodType.applePay + applePay:
Apple Pay (native)iOSSwirepayNativeApplePay.presentNativeApplePay

Initialize once, then call the matching option.

FeatureAndroidiOS
CardYesYes
Bank (ACH)YesYes
Google Pay (native sheet)YesNo
Apple Pay (WKWebView)NoYes
Apple Pay (PassKit)NoYes

12. Testing your integration​

  1. Use Test / staging secret keys on your backend to mint sessions.
  2. Enable only the method under test first (CARD, then ACH_LEGACY, then GOOGLE_PAY_US or Apple Pay), then combine.
  3. Walk Card end-to-end with Swirepay test cards. Leave card fields empty and type them in the modal.
  4. For ACH, set plaidSecureLinkIsTest: true and complete Plaid Link before Pay.
  5. 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.
  6. For hosted Apple Pay, use a real HTTPS page (not example.com) and confirm the completion redirect.
  7. For native Apple Pay, use a physical iPhone, matching merchant id, and the sandbox rules in §9.2.
  8. Confirm your webhook receives the same paymentSessionGid you stored at session create.
  9. 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​

StepCardACHGoogle PayApple Pay
Local path dependency on swirepay_payments_sdkYesYesYesYes
Android minSdk ≥ 24 / iOS 15+YesYesAndroidiOS
Backend POST /v3/checkout-sessionCARDACH_LEGACYGOOGLE_PAY_USAPPLE_PAY_US for hosted page
Persist paymentSessionGidYesYesYesYes
Pass CheckoutSessionCredentials into initializeRequiredRequiredRequiredHosted / native still use keys on initialize
Pass accountGidOptionalRequiredRequiredWallet credential fetch
Extra payloadCard / address / customerBankDetails + Plaid in UIGooglePayPresentationApplePayPresentation or NativeApplePayPresentation
Result APIstartPaymentsamesamestartPayment or presentNativeApplePay
Webhook for fulfillmentYesYesYesYes
Secret key in the app binaryNoNoNoAvoid; 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 path at example/ — depend on the plugin root (the folder that contains pubspec.yaml and lib/).
  • Deleting android/repo or ios/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 paymentSessionGid from step 1 — you will not be able to reconcile webhook / callback outcomes reliably.
  • Assuming PaymentFlowResult alone is authoritative — configure a dashboard webhook; the app can be killed before you process the callback.
  • ACH without accountGid or Plaid package registration — secure link / OAuth will fail. Register the host app id, not com.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 real jsCheckoutUrl.
  • Using startPayment for native Apple Pay — that is the hosted-page option. Native PassKit is SwirepayNativeApplePay.presentNativeApplePay.
  • Native Apple Pay on Simulator — Simulated Cards produce dummy tokens. Use a physical device.
  • Mismatch between session paymentType and enabledPaymentMethods — 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.amount is a display string in major units ("25.99"), not cents.
  • Opening Runner.xcodeproj — always open ios/Runner.xcworkspace after pod install.