Integrating a payment gateway in an Android app
Adding checkout to an Android app means connecting a native payment interface to a backend that creates and verifies transactions. A gateway such as Stripe or PayPal handles sensitive card details, while your app manages the basket and order state. Secret gateway keys must stay on the server, and a purchase is not final until that server confirms it.
Choose a provider based on the methods Australian customers expect. Cards, digital wallets and PayPal are common, and buy-now-pay-later services such as Zip, formerly Afterpay, are also familiar. Compare fees, settlement times, recurring-payment support and AUD payouts before integrating an SDK.
This guide uses Stripe PaymentSheet, a prebuilt Android checkout for cards and eligible wallets. The implementation covers project setup, server-side payment creation, Kotlin code, webhook fulfilment and testing. Other providers have different APIs, but the security model remains the same.
Choosing the right checkout for Australia
Payment methods should match how people actually shop. A card-first flow suits many Australian merchants, while PayPal remains useful for customers who prefer a separate account. Digital-wallet support also matters for shoppers checking out quickly on a phone.
| Option | Android checkout | Best fit | Main trade-off |
|---|---|---|---|
| Stripe PaymentSheet | Native SDK plus your backend | Cards, eligible wallets and subscriptions | Provider account and server work required |
| PayPal | Native SDK or browser redirect | Buyers with a PayPal account | Additional sign-in and redirect flow |
| Google Pay | Push provisioning plus gateway support | Wallet-led purchases | Device, bank and merchant eligibility |
Stripe PaymentSheet is the example used here because it gives Android developers a consistent interface for collecting payment details without handling raw card numbers. PayPal and Google Pay can still be options, but their eligibility, fees and reconciliation differ. Assess any buy-now-pay-later integration separately, including instalment disclosures and refunds, rather than treating it as a card feature.
Preparing the Android project and backend
Create a Stripe test account, enable the Android SDK and add the current stable com.stripe:stripe-android dependency. Check the library’s supported minimum Android version against your app’s minSdk, and add the INTERNET permission. A checkout needs network access, but it does not need camera, SMS or contacts permissions.
Your backend creates the payment intent using the secret key loaded from a secret manager or environment variable. Give the app only the publishable key and the payment intent’s client secret. Do not put sk_test_... or sk_live_... in BuildConfig, source control or an APK.
Here is a minimal Java endpoint for a server framework that supports Stripe’s Java SDK:
PaymentIntentCreateParams params = PaymentIntentCreateParams.builder()
.setAmount(1800)
.setCurrency("aud")
.setAutomaticPaymentMethodsEnabled(true)
.addMetadata("order_id", orderId)
.build();
PaymentIntent intent = PaymentIntent.create(
params, System.getenv("STRIPE_SECRET_KEY"));
return Map.of("paymentIntentId", intent.getId(),
"clientSecret", intent.getClientSecret());
The amount is in cents, so 1800 means A$18.00. Derive it from the server’s own price record, not a value supplied by the app. The order_id metadata links the payment to your database and makes webhook handling more reliable.
Building a secure checkout flow
Show an order summary and a clearly labelled pay button before opening the gateway. Never build your own form that asks for card numbers, CVV or a bank password. PaymentSheet collects those details in a provider-controlled interface and can present supported wallets and authentication steps.
Create the sheet with PaymentSheet.Builder(::onPaymentSheetReady).build(this), then launch it with ActivityResultContracts.StartPaymentFlow. When the sheet is ready, call presentWithPaymentIntent(clientSecret) using the secret returned by your backend.
private val paymentLauncher =
registerForActivityResult(ActivityResultContracts.StartPaymentFlow()) { result ->
when (result) {
is PaymentSheetResult.Completed ->
viewModel.awaitingConfirmation(result.paymentIntent?.id)
is PaymentSheetResult.Canceled ->
viewModel.showCancelled()
is PaymentSheetResult.Failed ->
viewModel.showError(result.error.message)
}
}
Handle the result without claiming success too early. Completed means PaymentSheet finished; the backend still needs to verify the payment. Disable the pay button while processing to prevent duplicate submissions, and offer a retry that reuses the same pending order rather than charging the customer twice.
Verifying orders and handling failures
Create a webhook endpoint for payment_intent.succeeded and verify its signature with Stripe’s SDK. Use the order_id metadata to locate the order, mark it paid and fulfil it exactly once. Webhooks can be delivered more than once, so make the handler idempotent. During local testing, the Stripe CLI can forward events without exposing your development machine to the internet.
The app may close, lose mobile data or be switched off after the customer returns from the bank’s authentication screen. In that case, reopen the order and ask the server for its status. A shopper in Melbourne or regional New South Wales should not see a completed charge become an unexplained pending payment because a connection dropped.
Use friendly, direct wording when something goes wrong: “No worries, that payment did not go through. Please try again.” This suits the informal tone many Australians appreciate without hiding useful details. Issue refunds through the provider and send receipts from the backend. For taxable consumer sales, display AUD prices with GST included, and keep the transaction records needed for your accounting.
Provider-hosted card collection can reduce PCI DSS scope, but it does not remove privacy duties. If the Australian Privacy Act applies, follow the Australian Privacy Principles, collect only what the order needs, and keep card details out of logs and analytics. Decide how long payment and order records are retained before launch.
Testing payments before release
Use Stripe’s test cards and authentication flows to cover successful payments, declines, expiry errors and cancelled checkouts. Test cards work only in test mode. For Google Pay, use a supported Android device, wallet setup and eligible card; an emulator may not reproduce every wallet condition.
Also test a slow connection, app restart, duplicate taps and a purchase that returns after a timeout. Check TalkBack labels, large-font settings and smaller screens. Make small live payments in AUD, then refund them, because test mode will not reveal every issue with real bank authentication, settlement or accounting.
Before release, confirm provider fees, payout timing, refund rules and webhook monitoring. Keep the order, provider payment ID and amount together in your records, but never store a card number. If GST applies, reconcile gateway fees and tax treatment with your bookkeeping rather than assuming every provider fee follows the same rule.