Skip to main content

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 methodCheckout paymentType valueSDK 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:

  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 Android APK.
  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 Android app.
  4. The app builds CheckoutSessionCredentials from that token, calls SwirepaySDK.initialize(...), and launches the payment modal with a PaymentPayload.
  5. 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.
  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.

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:

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 (required by the Android checkout-session path today).
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.
acceptedDomainOrigin 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 keys c (checkout session id), k (server public key), i (key id), e (expiry epoch ms). Pass this string into CheckoutSessionCredentials.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:

AARUse when
swirepaysdk.aarKotlin / Compose (or any) host — primary SDK
swirepaysdk-java.aarOptional 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 applicationId registered 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.

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​

FieldRequiredDescription
amountYesDisplay / host amount string (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).
cardYes (structure)Prefill for the Card tab; shopper can edit in the modal.
addressYesBilling address fields.
paymentMethodYesDefault / first method (CARD, BANK, or GOOGLE_PAY).
enabledPaymentMethodsNoIf non-empty, controls which tabs/buttons appear. If empty, only paymentMethod is shown.
bankDetailsFor ACHe.g. BankDetails(businessType = "Individual"). Plaid token is filled in-modal.
googlePayFor Google PayGooglePayPresentation(...) — see §8.
checkoutStatementDescriptorYesStatement descriptor for card/bank session.
checkoutCaptureMethodYesTypically "AUTOMATIC".
checkoutConfirmMethodYesTypically "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)​

  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 §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 + 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 registered with PlaidOAuth / Link redirects use your applicationId.
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.

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 = true while integrating.
  • Never put your Swirepay secret key in the APK just to create Plaid tokens; the checkout-session + accountGid path 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​

RequirementWhy
accountGid on initializeFetches Google Pay US credentials for your account.
GooglePayPresentationCountry, merchant display name, capture/confirm methods.
Device / Play ServicesNative Google Pay sheet via Play Services Wallet.
DIRECT tokenization configured for the accountSDK 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​

  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 (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 = false with details / error in 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 in rawResponse.
  • 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​

  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), then combine.
  3. Walk Card end-to-end with Swirepay test cards.
  4. For ACH, set plaidSecureLinkIsTest = true and complete Plaid Link before Pay.
  5. For Google Pay, use a device/emulator with Play Services and a Google account that can complete the sheet in your test environment.
  6. Confirm your webhook receives the same paymentSessionGid you stored at session create.
  7. 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​

StepCardACHGoogle Pay
Backend POST /v3/checkout-sessionCARDACH_LEGACYGOOGLE_PAY_US
Persist paymentSessionGidYesYesYes
Pass CheckoutSessionCredentials into initializeRequiredRequiredPreferred
Pass accountGidOptionalRequiredRequired
Extra payloadCard / address / customerBankDetails + Plaid in UIGooglePayPresentation
Result APIPaymentFlowContractsamesame
Webhook for fulfillmentYesYesYes
Secret key in APKNoNoNo

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 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.
  • Google Pay without GooglePayPresentation fields — country and merchant display name must be non-blank.
  • Mismatch between session paymentType and enabledPaymentMethods — 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, not com.swirepay.swirepaysdk.
  • Trusting client-supplied amount for settlement — the amount charged is defined by the checkout session your backend created.

  • 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.md in the root of the Android SDK repository
  • Kotlin sample: sample-app
  • Java sample: sample-app-java