Android SDK
The Paypercut Android SDK is a drop-in native payment sheet — card, Google Pay, and saved cards — that you present from your app with a single call. The customer pays inside a native Jetpack Compose sheet, and the SDK hands you back a payment method for your backend to charge.
Card details are captured directly into Paypercut's PCI-compliant vault on-device — they never pass through your app or your servers.
Why the native SDK?
- Native UX — a Jetpack Compose sheet that renders card, Google Pay, and saved cards, themed to your brand.
- Minimal PCI scope — the PAN is captured straight into Paypercut's vault; it never touches your app or servers.
- You stay in control of the charge — the SDK produces a payment method; your backend confirms the payment with your secret key.
- Localized — the sheet ships in 17 languages and follows the device locale (or a locale you pass).
How It Works
The SDK is checkout-less — you supply the transaction context directly, with no checkout to create first.
- Your app presents the payment sheet with your publishable key and the amount / currency.
- The customer enters card details (or taps Google Pay / picks a saved card) and confirms. 3-D Secure runs automatically when required.
- The SDK returns a payment method (
pm_…) viaonResult. - Your backend confirms the payment with that payment method id using your secret key.
The SDK does not move money. It collects payment details and returns a payment method; charging it is a server-to-server call from your backend.
Prerequisites
- A Paypercut merchant account — Sign up and get your publishable key (
pk_test_…/pk_live_…) from API Keys. - The transaction amount and currency — you pass these to the SDK directly. Optionally pass saved cards you've fetched server-to-server and a known customer email.
- A GitHub token with
read:packages— the SDK is distributed via GitHub Packages (see Installation).
Installation
The SDK is published to GitHub Packages. Add the repository and dependency to Gradle:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/paypercut/paypercut-android-sdk")
credentials {
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
password = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
}
}
}
}
// app/build.gradle.kts
dependencies {
implementation("com.paypercut:checkout-android:2.0.0")
}
Put your GitHub token (with read:packages) in local.properties (gitignored):
gpr.user=your-github-username
gpr.key=ghp_xxxxxxxxxxxxxxxxxxxx
Requirements: minSdkVersion 28, Jetpack Compose.
Basic Integration
import com.paypercut.checkout.Paypercut
import com.paypercut.checkout.PaymentSheet
import com.paypercut.checkout.core.PaypercutEnvironment
import com.paypercut.checkout.core.models.PaymentMethodResult
class CheckoutActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val paypercut = Paypercut(
publishableKey = "pk_test_…", // your publishable key
amount = 2500, // total in minor units (2500 = €25.00)
currency = "EUR",
environment = PaypercutEnvironment.PROD,
)
setContent {
paypercut.PaymentSheet { result ->
when (result) {
is PaymentMethodResult.Created -> confirmOnYourBackend(result.paymentMethod.paymentMethodId)
is PaymentMethodResult.Blik -> confirmBlikOnYourBackend(result.code)
is PaymentMethodResult.Cancelled -> finish()
is PaymentMethodResult.Failed -> showError(result.message)
}
}
}
}
}
The sheet renders the enabled methods, collects the details, and returns a payment method. Charging it is a call from your backend.
Configuration
Paypercut — only publishableKey, amount, and currency are required:
| Parameter | Type | Default | Description |
|---|---|---|---|
publishableKey |
String |
required | Your Paypercut publishable key (pk_test_… / pk_live_…) — resolves your account |
amount |
Long |
required | Transaction total in minor units (e.g. 2500 = €25.00; 0 for a setup-mode card save) |
currency |
String |
required | ISO 4217 code (e.g. "EUR", "PLN") |
currencyScale |
Int |
2 |
Minor-unit exponent for the currency (2 for EUR/USD, 0 for JPY) |
mode |
PaymentMode |
payment |
payment to charge now, setup to save a card for later |
paymentMethodTypes |
List<String> |
["card"] |
Which methods to render (e.g. listOf("card"), listOf("card", "blik")). Google Pay is gated by device + account, not this list |
savedPaymentMethods |
List<SavedPaymentMethodInput> |
[] |
Saved cards to offer for one-tap reuse (fetch them server-to-server). Rendered only when non-empty |
allowSaveCard |
Boolean |
true |
Show the "save card" checkbox on the card form. In setup mode it is always shown as required consent |
collectBillingAddress |
Boolean |
true |
Collect a billing address in the card form |
billingAddress |
BillingAddress? |
null |
Pre-fill the billing address. When set, the section renders collapsed to a summary (expandable); when absent, expanded |
customerEmail |
String? |
null |
A known buyer email. Hides the sheet's email field and feeds billing + 3-D Secure |
customer |
String? |
null |
A known customer id (01…), when you've selected one. Attributes the payment method to that customer |
environment |
PaypercutEnvironment |
PROD |
DEV / STAGE / PROD |
locale |
String? |
device locale | BCP-47 tag controlling the sheet's language (e.g. "pl", "de") |
appearance |
Appearance |
default | Branding overrides — see Appearance |
enableLogging |
Boolean |
false |
Verbose logging for development only — never enable in production |
proceedOnThreeDSUnavailable |
Boolean |
false |
When 3-D Secure comes back unavailable, proceed with the payment (no liability shift) instead of failing. Defaults to fail-closed |
You pass the amount and currency directly; saved cards are the ones you supply. Which methods render is driven by paymentMethodTypes plus device / account support for Google Pay.
Appearance
Match the sheet to your brand:
val paypercut = Paypercut(
publishableKey = "pk_test_…",
amount = 2500,
currency = "EUR",
appearance = Appearance(
theme = AppearanceTheme.SYSTEM, // SYSTEM | LIGHT | DARK
brandColor = 0xFF6366F1L, // ARGB — CTA / brand colour (Pay button, save checkbox)
brandColorContrast = 0xFF021A1BL, // ARGB — content on the brand (e.g. Pay button label)
accentColor = 0xFF10B981L, // ARGB — interactive accent (selected radio, link, cursor)
accentColorContrast = 0xFF021A1BL, // ARGB — content on the accent
borderRadius = 12f, // dp — inputs & buttons
fontFamily = "Inter", // any Google Font name; null = system
),
)
Re-running 3-D Secure
Confirmation is server-to-server: your backend confirms the payment against the Paypercut API with your secret key. Sometimes that confirm needs a fresh 3-D Secure authentication for the card — the API responds with payment_status: requires_action and a use_sdk next action (use_sdk.sdk == "three_d_secure"). Because the challenge runs on-device, your backend relays that action's metadata to the app, the app runs the challenge, and the resulting proof goes back for your backend to re-confirm with.
Plan for this from your first confirm — any card confirm can come back requires_action.
onEvent and the PaymentSheet result belong to the sheet and end once you have the pm_…. The retrigger is separate: you call handleNextAction(...), a suspend function that returns a ThreeDSProof (or throws) — nothing new arrives on onEvent.1. Your backend confirms and inspects the response. On requires_action, read the use_sdk next action and forward its metadata to the app:
{
"payment_status": "requires_action",
"next_action": {
"type": "use_sdk",
"use_sdk": {
"sdk": "three_d_secure",
"metadata": { "vault_token_ref": "tok_…" } // or "vault_token_intent_ref"
}
}
}
Trigger the app step only when next_action.type == "use_sdk" and use_sdk.sdk == "three_d_secure". (A top-level three_d_secure next action is the web browser flow — it does not apply to the native SDK.)
2. The app runs the challenge and returns a proof. Build the action straight from the forwarded metadata — the SDK resolves the vault key and card brand itself. The challenge is presented over the Activity you pass:
val action = PaypercutThreeDSAction.fromMetadata(serverResponse.useSdkMetadata)
try {
val proof = paypercut.handleNextAction(activity, action)
reConfirmOnYourBackend(proof) // step 3
} catch (e: Exception) {
showError(e.message ?: "Authentication failed.")
}
3. Your backend re-confirms, server-to-server, passing the proof as payment_method_options.card.three_d_secure. That call returns the terminal outcome (succeeded / failed).
handleNextAction presents the issuer's challenge if required and returns a ThreeDSProof (cryptogram, electronicCommerceIndicator, transactionId, version). It uses the amount, currency, and account the instance was created with, and throws if authentication fails, is cancelled, or yields no proof. The SDK only produces the proof — the confirm and re-confirm are both server-to-server calls your backend owns.
Paypercut instance for the sheet and handleNextAction — the retrigger builds on context the SDK retained while the sheet tokenized the card, so call it on the instance that presented the sheet. And don't swap screens while you wait: confirming, the challenge, and re-confirming are one "we're working" window — keep a single loading spinner mounted and change its label (e.g. Authenticating → Confirming payment), then move to your success / failure screen only on the terminal result.Payment Methods
You control which methods appear through the config you pass:
- Card — an inline card form, shown when
"card"is inpaymentMethodTypes(the default). WhenallowSaveCardis on, a "save card" checkbox lets the customer opt in to reuse the card later. A billing-address section appears whencollectBillingAddressis on. - Google Pay — the native Google Pay button appears when the device supports it and your account has it enabled. Google Pay also returns the buyer's billing address.
- Saved cards — pass one or more
savedPaymentMethods(fetched server-to-server for your customer) and they're offered for one-tap selection; selecting one returns itspm_…as-is. - BLIK (Poland / PLN) — add
"blik"topaymentMethodTypes. Returns the entered code (PaymentMethodResult.Blik), not a payment method. Your backend confirms the payment with the code and polls the result.
Handling the Result
onResult delivers one terminal outcome:
paypercut.PaymentSheet { result ->
when (result) {
is PaymentMethodResult.Created -> {
val pm = result.paymentMethod
// pm.paymentMethodId — pass this to your backend to confirm the payment
// pm.type — "card"
// pm.wallet — "google_pay", or null for a manual / saved card
// pm.saveForFutureUse — customer opted to save the card; pass as save_payment_method on confirm
// pm.billingDetails — name / email / billing address collected on the sheet (or from Google Pay)
confirmOnYourBackend(pm.paymentMethodId)
}
is PaymentMethodResult.Blik -> confirmBlikOnYourBackend(result.code) // single-use, expires ~2 min
is PaymentMethodResult.Cancelled -> { /* buyer dismissed the sheet */ }
is PaymentMethodResult.Failed -> showError(result.message)
}
}
When the customer ticks the save-card checkbox, pm.saveForFutureUse is true — forward it as save_payment_method when you confirm so the card is attached to the customer for future payments.
Lifecycle events (optional)
Pass onEvent for analytics / logging. It's observational — the sheet drives its own UI:
paypercut.PaymentSheet(
onEvent = { event ->
when (event) {
is PaypercutEvent.Loaded -> {}
is PaypercutEvent.Processing -> {}
is PaypercutEvent.PaymentMethodCreated -> { /* id, type, wallet, saveForFutureUse */ }
is PaypercutEvent.BlikCodeEntered -> { /* code */ }
is PaypercutEvent.Error -> { /* code, message */ }
is PaypercutEvent.Expired -> {}
}
},
) { result -> /* … */ }
These events cover the sheet only. The 3-D Secure retrigger is a separate, direct call that does not flow through onEvent — see Re-running 3-D Secure.
Error codes
The Error event (and PaymentMethodResult.Failed) carries a code and a buyer-safe message. Show the message; the code tells you which stage failed, so you can quote it when reporting an issue:
code |
Stage | Meaning |
|---|---|---|
tokenization_failed |
On-device tokenization | The card or wallet token couldn't be secured into the vault. |
threeds_authentication_failed |
3-D Secure | The issuer declined authentication, or the customer failed / cancelled the challenge. |
card_declined, session_expired, payment_method_unavailable, … |
Payment-method create | The Paypercut API rejected the create call; the code is the API's own error code. |
null |
Network / parsing | A transport or decode failure — the message is generic on purpose. |
The message is deliberately generic for tokenization and network failures — the SDK never surfaces vault or transport internals to the customer.
Google Pay
Google Pay appears automatically when the device supports it and your account has it enabled — no in-app certificate setup is required. Test on a real device signed in to a Google account with a saved card. In sandbox, Google Pay returns test cards.
Testing
Use sandbox keys (pk_test_…) and DEV / STAGE environments during development.
For test cards and sandbox scenarios, see the Testing Guide.
Support & Resources
Ready to add native checkout to your Android app? Create your free Paypercut account →

