Integrating payments with the Swirepay Android SDK
The Swirepay Android SDK opens a native payment modal that can collect Card, ACH (Pay by Bank), and Google Pay 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.
This guide mirrors the structure of the web Payment Center docs (
<swirepay-checkout>), adapted for Android (Kotlin and Java). Card, ACH, and Google Pay share the same backend session step and the same result callback pattern.
| Payment method | Checkout paymentType value | SDK PaymentMethodType |
|---|---|---|
| Card | "CARD" | CARD |
| ACH / bank | "ACH_LEGACY" | BANK |
| Google Pay | "GOOGLE_PAY_US" | 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.). This must happen server-side; the secret key must never ship inside your Android APK. - 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 Android app. - The app builds
CheckoutSessionCredentialsfrom that token, callsSwirepaySDK.initialize(...), and launches the payment modal with aPaymentPayload. - The shopper completes Card, ACH (via Plaid Link), and/or Google Pay inside the 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.
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 Android, 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 (required by the Android checkout-session path today). |
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 | Origin associated with your integration (same concept as web). |
Example paymentType combinations
- Card only:
["CARD"] - Card + ACH:
["CARD", "ACH_LEGACY"] - All three:
["CARD", "ACH_LEGACY", "GOOGLE_PAY_US"]
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.fromEncryptionBase64(...).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,
});
});
4. Step 2 — Add the SDK to your Android app
Swirepay delivers the Android SDK to merchants as AAR file(s). Add the AAR your Swirepay contact provides into your app module, then wire it in Gradle.
Typical package contents:
| AAR | Use when |
|---|---|
swirepaysdk.aar | Kotlin / Compose (or any) host — primary SDK |
swirepaysdk-java.aar | Optional Java-friendly façade; still needs swirepaysdk.aar |
Place the AAR in your project
Copy the file(s) into your app module, for example:
your-app/
app/
libs/
swirepaysdk.aar
swirepaysdk-java.aar # only if you use the Java façade
build.gradle.kts
Gradle (Kotlin DSL)
android {
// ...
compileSdk = 34
defaultConfig {
minSdk = 24
// ...
}
}
dependencies {
// Primary Swirepay SDK AAR
implementation(files("libs/swirepaysdk.aar"))
// Optional: Java façade AAR (in addition to swirepaysdk.aar)
// implementation(files("libs/swirepaysdk-java.aar"))
// AAR files do not pull transitive Maven deps automatically.
// Declare the SDK runtime dependencies your Swirepay package notes list.
// At minimum you typically need (versions may be pinned in the release notes):
implementation(platform("androidx.compose:compose-bom:<bom-version>"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:<version>")
implementation("androidx.activity:activity-ktx:<version>")
implementation("com.squareup.okhttp3:okhttp:<version>")
implementation("com.google.android.gms:play-services-wallet:19.4.0")
implementation("com.plaid.link:sdk-core:<version>") // ACH / Plaid Link
// plus any other deps listed with your AAR release package
}
Gradle (Groovy)
dependencies {
implementation files('libs/swirepaysdk.aar')
// implementation files('libs/swirepaysdk-java.aar') // optional Java façade
// Same transitive deps as above — see the release notes shipped with the AAR
}
Alternative: flatDir repository
// settings.gradle.kts or the app module repositories block
repositories {
flatDir { dirs("libs") }
}
dependencies {
implementation(name = "swirepaysdk", ext = "aar")
}
Prefer files("libs/…") when you only have one or two AARs — it is explicit and avoids flatDir resolution surprises.
Requirements
- minSdk 24, compileSdk 34 (or compatible)
- Google Play Services available on device/emulator for Google Pay
- Host app
applicationIdregistered with Plaid for ACH (see below)
Exact transitive dependency versions are included in the release package / notes that accompany the AAR. Match those versions when adding the deps above.
Permissions / activities
The SDK merges INTERNET and its internal activities (PaymentModalActivity, ACS challenge, etc.). You do not need to declare those activities yourself.
Plaid (ACH): register your app applicationId (e.g. com.yourcompany.yourapp) in the Plaid Dashboard. The SDK sends the host app package name — not the library namespace com.swirepay.swirepaysdk.
5. Step 3 — Initialize and launch the payment modal
Decode credentials from your backend
val credentials = CheckoutSessionCredentials.fromEncryptionBase64(
encryptionBase64 = response.encryption, // entity.encryption
scope = response.scope ?: "payment",
paymentSessionGid = response.paymentSessionGid, // optional but recommended
)
Initialize (Kotlin)
SwirepaySDK.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 APKs; prefer checkout-session only
)
initialize requires either a non-blank checkoutSession or a legacy secretKey. For Card and ACH, checkout session is required. Google Pay prefers checkout session as well.
Launch with PaymentFlowContract (recommended)
val paymentLauncher = rememberLauncherForActivityResult(
SwirepaySDK.PaymentFlowContract(),
) { result: PaymentFlowResult? ->
if (result == null) return@rememberLauncherForActivityResult
if (result.success) {
// Parse result.rawResponse — see §9
// Still reconcile via webhook + paymentSessionGid
} else {
// Show recoverable error / let shopper retry
}
}
paymentLauncher.launch(payload)
View / Java equivalent:
ActivityResultLauncher<PaymentPayload> paymentLauncher =
registerForActivityResult(new SwirepaySDK.PaymentFlowContract(), this::onPaymentResult);
Shared PaymentPayload fields
| Field | Required | Description |
|---|---|---|
amount | Yes | Display / host amount string (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 | Yes (structure) | Prefill for the Card tab; shopper can edit in the modal. |
address | Yes | Billing address fields. |
paymentMethod | Yes | Default / first method (CARD, BANK, or GOOGLE_PAY). |
enabledPaymentMethods | No | If non-empty, controls which tabs/buttons appear. If empty, 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. |
checkoutStatementDescriptor | Yes | Statement descriptor for card/bank session. |
checkoutCaptureMethod | Yes | Typically "AUTOMATIC". |
checkoutConfirmMethod | Yes | Typically "AUTOMATIC". |
6. Card payments
Enable "CARD" in the checkout session paymentType array, then include PaymentMethodType.CARD in enabledPaymentMethods (or set paymentMethod = 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.
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 §9).
Minimal Card-only example (Kotlin)
val payload = PaymentPayload(
amount = "25.99",
currencyCode = "USD",
customer = CustomerInfo(
fullName = "Jane Doe",
email = "jane@example.com",
phoneCode = "+1",
phoneNumber = "4155552671",
),
card = CardDetails(
cardNumber = "",
expiryDate = "",
cvv = "",
cardHolderName = "",
),
address = AddressDetails(
street = "1 Market St",
city = "San Francisco",
state = "CA",
country = "US",
zipCode = "94105",
),
paymentMethod = PaymentMethodType.CARD,
enabledPaymentMethods = listOf(PaymentMethodType.CARD),
checkoutStatementDescriptor = "Your Store",
checkoutCaptureMethod = "AUTOMATIC",
checkoutConfirmMethod = "AUTOMATIC",
)
paymentLauncher.launch(payload)
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 registered with Plaid | OAuth / Link redirects use your applicationId. |
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.
ACH example (Kotlin)
SwirepaySDK.initialize(
checkoutSession = credentials,
test = false,
accountGid = "account-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
plaidSecureLinkIsTest = false,
)
val payload = PaymentPayload(
amount = "25.99",
currencyCode = "USD",
customer = CustomerInfo("Jane Doe", "jane@example.com", "+1", "4155552671"),
card = CardDetails("", "", "", ""),
address = AddressDetails("1 Market St", "San Francisco", "CA", "US", "94105"),
paymentMethod = PaymentMethodType.BANK,
enabledPaymentMethods = listOf(PaymentMethodType.BANK),
bankDetails = BankDetails(businessType = "Individual"),
checkoutStatementDescriptor = "Your Store",
checkoutCaptureMethod = "AUTOMATIC",
checkoutConfirmMethod = "AUTOMATIC",
)
paymentLauncher.launch(payload)
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 APK just to create Plaid tokens; the checkout-session +
accountGidpath is the supported production approach.
8. Google Pay
Enable "GOOGLE_PAY_US" in the checkout session. In the SDK, use PaymentMethodType.GOOGLE_PAY and supply GooglePayPresentation.
Extra requirements
| Requirement | Why |
|---|---|
accountGid on initialize | Fetches Google Pay US credentials for your account. |
GooglePayPresentation | Country, merchant display name, capture/confirm methods. |
| 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
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 (Kotlin)
SwirepaySDK.initialize(
checkoutSession = credentials,
test = false,
accountGid = "account-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
)
val payload = PaymentPayload(
amount = "25.99",
currencyCode = "USD",
customer = CustomerInfo("Jane Doe", "jane@example.com", "+1", "4155552671"),
card = CardDetails("", "", "", ""),
address = AddressDetails("1 Market St", "San Francisco", "CA", "US", "94105"),
paymentMethod = PaymentMethodType.GOOGLE_PAY,
enabledPaymentMethods = listOf(PaymentMethodType.GOOGLE_PAY),
googlePay = GooglePayPresentation(
transactionCountryCode = "US",
merchantDisplayName = "Your Store",
paymentSessionCaptureMethod = "AUTOMATIC",
paymentSessionConfirmMethod = "AUTOMATIC",
),
checkoutStatementDescriptor = "Your Store",
checkoutCaptureMethod = "AUTOMATIC",
checkoutConfirmMethod = "AUTOMATIC",
)
paymentLauncher.launch(payload)
Offering Card + ACH + Google Pay together
Mint the session with all three paymentType values, then:
enabledPaymentMethods = listOf(
PaymentMethodType.CARD,
PaymentMethodType.BANK,
PaymentMethodType.GOOGLE_PAY,
)
paymentMethod = PaymentMethodType.CARD, // default tab
bankDetails = BankDetails(businessType = "Individual"),
googlePay = GooglePayPresentation(
transactionCountryCode = "US",
merchantDisplayName = "Your Store",
),
9. Success and error results
PaymentFlowResult
data class PaymentFlowResult(
val success: Boolean, // true when Activity.RESULT_OK
val rawResponse: String, // JSON body / envelope
val httpStatusCode: Int? = null,
)
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": { }
}
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; 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. - HTTP failures may also populate
httpStatusCode.
Treat webhooks as authoritative for fulfillment.
10. Java façade (swirepaysdk-java)
If your release package includes swirepaysdk-java.aar, add it together with swirepaysdk.aar (see §4). Java apps can then use SwirepayPaymentsJava for init and builders while still launching with SwirepaySDK.PaymentFlowContract:
SwirepayPaymentsJava.initialize(
/* secretKey */ null,
/* baseUrl */ null,
/* test */ false,
SwirepayEncryptionConfig.stagingDefault(),
accountGid,
/* plaidSecureLinkIsTest */ false,
/* publishableKey */ null
);
// Prefer also setting checkout session via Kotlin SwirepaySDK.initialize when using encrypted Card/ACH.
PaymentPayload payload = SwirepayPaymentsJava.paymentPayload()
// ... builder fields ...
.build();
paymentLauncher.launch(payload);
See the sample-app-java module for a full XML / Java reference.
11. 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_US), then combine. - Walk Card end-to-end with Swirepay test cards.
- For ACH, set
plaidSecureLinkIsTest = trueand complete Plaid Link before Pay. - For Google Pay, use a device/emulator with Play Services and a Google account that can complete the sheet in your test environment.
- 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.
Reference implementations in this repo:
- Kotlin / Compose:
sample-app - Java / XML:
sample-app-java
Sample apps may create the checkout session in-process with
sk_*for demos. Production apps must create the session on your backend.
12. Merchant checklist
| Step | Card | ACH | Google Pay |
|---|---|---|---|
Backend POST /v3/checkout-session | CARD | ACH_LEGACY | GOOGLE_PAY_US |
Persist paymentSessionGid | Yes | Yes | Yes |
Pass CheckoutSessionCredentials into initialize | Required | Required | Preferred |
Pass accountGid | Optional | Required | Required |
| Extra payload | Card / address / customer | BankDetails + Plaid in UI | GooglePayPresentation |
| Result API | PaymentFlowContract | same | same |
| Webhook for fulfillment | Yes | Yes | Yes |
| Secret key in APK | No | No | No |
13. Common pitfalls
- Minting the checkout session inside the APK — 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. - Google Pay without
GooglePayPresentationfields — country and merchant display name must be non-blank. - Mismatch between session
paymentTypeandenabledPaymentMethods— the session must allow a method for the charge to succeed even if the UI shows it. - Using the SDK library namespace for Plaid — register your host app
applicationId, notcom.swirepay.swirepaysdk. - Trusting client-supplied amount for settlement — the amount charged is defined by the checkout session your backend created.
14. Related docs
- Web Payment Center — Card: Integrating card payments via
<swirepay-checkout> - Sibling web guides: ACH, Google Pay (same checkout-session model; different client surfaces)
- Android module README: see the
README.mdin the root of the Android SDK repository - Kotlin sample:
sample-app - Java sample:
sample-app-java