Android SDK

Coming soon

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.

  1. Your app presents the payment sheet with your publishable key and the amount / currency.
  2. The customer enters card details (or taps Google Pay / picks a saved card) and confirms. 3-D Secure runs automatically when required.
  3. The SDK returns a payment method (pm_…) via onResult.
  4. 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

  1. A Paypercut merchant accountSign up and get your publishable key (pk_test_… / pk_live_…) from API Keys.
  2. 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.
  3. 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.

This step is a direct call, not an SDK event. 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.

Reuse the same 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. AuthenticatingConfirming 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 in paymentMethodTypes (the default). When allowSaveCard is on, a "save card" checkbox lets the customer opt in to reuse the card later. A billing-address section appears when collectBillingAddress is 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 its pm_… as-is.
  • BLIK (Poland / PLN) — add "blik" to paymentMethodTypes. 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 →