Skip to content
flint

Android payment gateway

Accept payments in your Android app.

Collect the card in your Kotlin or Compose app with Stripe's Android SDK, and pay a Flint order from your backend with one call. The order arrives with line items, US sales tax when you turn it on, an emailed receipt, refunds by line item, a buyer account page, and a dashboard, so your backend never builds any of them.

Your app's checkout. Stripe's field takes the card; your backend pays the order.
Your backend pays the order
curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/pay \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: pay-ord_1kmn0aExample-pm_1kmn0aExample" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "pay",
    "payment_source": {
      "credential_type": "token",
      "token": "pm_1kmn0aExample"
    },
    "expected_outstanding_money": {
      "amount": 18000,
      "currency": "USD"
    },
    "expected_version": 1790760000,
    "buyer_contact": { "email": "sam@example.com" }
  }'

The PaymentMethod id from the app, plus the balance and version the buyer approved. Flint creates and confirms the payment in this request.

And gets the outcome in the response
{
  "order": {
    "order_id": "ord_1kmn0aExample",
    "status": "closed",
    "payment_status": "paid"
  },
  "order_payment_attempt": {
    "order_payment_attempt_id": "opat_1kmn0aExample",
    "status": "succeeded",
    "pending_actions": []
  }
}

A 200 carries every outcome. Act on the attempt's status, not the HTTP status.

No key in the APK

Your Flint key stays on your backend. The app talks to Stripe's SDK and to your own server, never to Flint.

One call to pay

Your backend sends the card's PaymentMethod id. Flint charges the order's balance, pauses for 3D Secure when the bank asks, and marks the order paid.

No monthly fee

3.79% + 35¢ per card payment. The sandbox is free, with no credit card.

01Four steps

Order on the backend. Card in the app. One call to pay.

Your backend owns the sale and your key. The app owns the screen and the card field. Neither confirms a payment by hand: Flint does that when your backend pays the order.

  1. Create the order on your backend

    POST /v1/orders

    Send catalog variant ids and quantities, and Flint prices the order, with tax when you turn it on. Then read the order: its payment_collection names the Stripe publishable key and account the app initializes Stripe's SDK with. It holds no secrets, so send it to the app as returned.

    One line item from your catalog
    curl -X POST https://api.withflintpay.com/v1/orders \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Idempotency-Key: order-app-7f3c" \
      -H "Content-Type: application/json" \
      -d '{
        "line_items": [
          {
            "type": "variant",
            "variant_id": "var_1kmn0aExample",
            "quantity": 1
          }
        ]
      }'

    The app sends what the buyer picked. The price comes from your catalog, never from the app.

    GET /v1/orders/{order_id}
    {
      "order_id": "ord_1kmn0aExample",
      "version": 1790760000,
      "settlement_amounts": {
        "outstanding_money": { "amount": 18000, "currency": "USD" }
      },
      "payment_collection": {
        "stripe": {
          "account_id": "acct_1AbcPlaceholder",
          "publishable_key": "pk_test_51AbcPlaceholder",
          "elements": {
            "next_step": "collect_payment_source",
            "submit_to": "pay_order",
            "amount_money": { "amount": 18000, "currency": "USD" },
            "payment_method_types": ["card"]
          }
        }
      }
    }

    Keep the balance and version on your backend. They are what the buyer approves, and the pay call sends them back.

  2. Collect the card in the app

    Stripe.createPaymentMethod

    Initialize Stripe’s Android SDK with the key and account from the order, take the card in its card field, and call createPaymentMethod. That creates a single-use credential and charges nothing.

    Kotlin, in the app
    // publishableKey and accountId come from the order's
    // payment_collection, which your backend passed to the app
    val stripe = Stripe(context, publishableKey, stripeAccountId = accountId)
    
    val params = cardInputWidget.paymentMethodCreateParams ?: return
    val paymentMethod = stripe.createPaymentMethod(params)
    
    // your own endpoint, which calls Flint with your key
    val result = backend.payOrder(orderId, paymentMethod.id)

    The card field is an Android View. In a Jetpack Compose screen, host it with AndroidView.

    Card numbers go from Stripe's field to Stripe. They never reach your servers or Flint's.

    pm_…

    The app never creates or confirms a PaymentIntent, so there is no client secret to fetch before the buyer pays.

    next_step: collect_payment_source

    The publishable key and account come from the order on each checkout, so the app holds no Stripe configuration of its own.

    payment_collection.stripe
  3. Pay the order from your backend

    POST /v1/orders/{order_id}/pay

    Send the PaymentMethod id as the payment source, with the balance and version the buyer approved. Flint creates a payment for the full balance and confirms it in the same request. If the order changed since the buyer saw it, Flint refuses before charging, so the app can show the new total.

    From your backend, with your key
    curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/pay \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Idempotency-Key: pay-ord_1kmn0aExample-pm_1kmn0aExample" \
      -H "Content-Type: application/json" \
      -d '{
        "action": "pay",
        "payment_source": {
          "credential_type": "token",
          "token": "pm_1kmn0aExample"
        },
        "expected_outstanding_money": {
          "amount": 18000,
          "currency": "USD"
        },
        "expected_version": 1790760000,
        "buyer_contact": { "email": "sam@example.com" }
      }'

    An Idempotency-Key built from the order and the single-use card makes a network retry safe: Flint returns the first result instead of charging twice.

    The response
    {
      "order": {
        "order_id": "ord_1kmn0aExample",
        "status": "closed",
        "payment_status": "paid"
      },
      "order_payment_attempt": {
        "order_payment_attempt_id": "opat_1kmn0aExample",
        "status": "succeeded",
        "pending_actions": []
      }
    }
    • succeededShow the confirmation. Fulfill from the order.paid webhook.
    • requires_actionRun the client action in the app, then resume.
    • failedNothing was charged. Show the decline message and take a new card.
    • processingDon't start another payment. Resume or read the attempt again.
  4. Run 3D Secure in the app when the bank asks

    handleNextActionForPayment

    When the issuer wants the buyer to authenticate, the attempt pauses with a pending action. Your backend passes its Stripe client secret to the app, the app runs Stripe's handleNextActionForPayment, and your backend resumes the attempt. That client secret is the only one Flint returns, and it goes to that one call.

    Kotlin, in the app
    // action is the pending action's client action,
    // which your backend passed back to the app
    val stripe = Stripe(context, action.publishableKey, stripeAccountId = action.accountId)
    stripe.handleNextActionForPayment(activity, action.clientSecret)
    
    // when the result comes back to the activity, call your
    // backend, which resumes the attempt (below). Resume even
    // after an error: Flint reads the real outcome from Stripe.
    Then your backend resumes
    curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/pay \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Idempotency-Key: resume-opat_1kmn0aExample-1" \
      -H "Content-Type: application/json" \
      -d '{
        "action": "resume",
        "order_payment_attempt_id": "opat_1kmn0aExample",
        "expected_outstanding_money": {
          "amount": 18000,
          "currency": "USD"
        }
      }'

    A new Idempotency-Key, and no payment source: the resume continues the same attempt.

    What the pay call returns instead
    {
      "order_payment_attempt": {
        "order_payment_attempt_id": "opat_1kmn0aExample",
        "status": "requires_action",
        "is_resumable": true,
        "pending_actions": [
          {
            "action_type": "payment_authentication",
            "client_action": {
              "stripe": {
                "account_id": "acct_1AbcPlaceholder",
                "publishable_key": "pk_test_51AbcPlaceholder",
                "payment_intent": {
                  "stripe_js_call": "handle_next_action",
                  "client_secret": "pi_3AbcPlaceholder_secret_XyzPlaceholder"
                }
              }
            }
          }
        ]
      }
    }

    An attempt still waiting for authentication 30 minutes after it started is canceled, and the buyer pays again with a new card.

Fulfill from the order.paid webhook or a fresh read of the order on your backend, not from what the app shows: a buyer can close the app before it hears the result. The declines and payment attempts guide covers retrying with a new card after a decline.

reviewed 2026-10-30 against the embedded payments guide and Stripe's Android docs · corrections: Flint Help

02What you skip

Your backend sends a card. The store is already there.

A charge API records an amount and a currency. What was sold, the tax on it, the receipt, and what went back on a refund are tables and jobs your backend builds and keeps in step with every payment. On Flint, all of it is the order your app's backend already created.

Around a charge API, your backend builds

amount + currencywhat a charge records
  1. The sale

    An orders table your backend writes

    kept in step with every charge

  2. Prices

    Totals computed in your backend

    and sent to the processor as one amount

  3. Sales tax

    A tax lookup, stored per line item

    recomputed on every edit and refund

  4. Refunds

    Code that maps a charge back to items

    plus each item's share of the tax

  5. Receipts

    An itemized email you template and send

    and resend when a refund lands

  6. Buyer account

    Order history screens in your app

    plus the API behind them

6 systems to build behind the app and keep in sync

On Flint, your backend reads the order

ord_1kmn0aExampleGET /v1/orders/{order_id}
  1. The sale

    order_idPOST /v1/orders
  2. Prices

    pricing_amountsvariant_id
  3. Sales tax

    tax_moneypricing_amounts.tax_money
  4. Refunds

    line_item_allocationsPOST /v1/refunds
  5. Receipts

    order_receiptsPOST /v1/orders/{order_id}/send-receipt
  6. Buyer account

    account.withflintpay.comlinked from every receipt

1 order · 1 API key · 1 webhook stream

Flint calculates US sales tax on the order when you turn tax on, and a line-item refund works out each item’s share of the discount and tax for you.

app.withflintpay.com
Flint dashboard order detail: line items, totals with a line-item refund, the payment attempt and an activity timeline on one $19.43 order record
One order in the Flint dashboard: what sold, the totals, a refund against a single line item, the payment attempt, and its timeline.
The buyer's account at account.withflintpay.com: orders, subscriptions, and payment methods. Every receipt links to it.

A dashboard from the first sale

Orders, payments, refunds, analytics, and payouts, for whoever runs the business. Refunding one item happens there, with no admin screens to build in your app.

Subscriptions for real-world things

Sell a membership or a subscription box with a subscription plan. Flint charges each cycle, writes every renewal as its own order, and emails the buyer when a payment fails.

Gift cards and promotions

Sell your own gift cards and redeem them at Flint's hosted checkout, alongside promotion codes, on the same order record as every other sale.

03Security

Nothing in the APK can charge a card.

An Android app ships to every buyer's phone, and anything in it can be read. With Flint, the app holds a Stripe publishable key it gets from your backend at checkout, and nothing that can charge a card, change a price, or issue a refund.

Every Flint key is a secret key, and Flint has no publishable key, so no Flint credential is meant for the app. It talks to Stripe's SDK and your backend.

flint_test_ · flint_live_

The app never sends an amount. Your backend sends catalog ids, Flint prices the order, and the pay call refuses a balance the buyer didn't approve.

expected_outstanding_money

The credential the app creates is single-use and pays one payment. Send it to your backend over HTTPS and nowhere else.

credential_type: token

The 3D Secure client secret is the only one Flint returns. It goes to handleNextActionForPayment, and the result comes from your backend.

client_action.stripe

The only scope your backend’s key needs

  • commerce.orders.write

One scope creates and pays orders. It can’t issue refunds, read customers, or create a broader key. Key security covers scopes and rotation.

The same backend for every app

The backend calls don’t change by platform, so one server pays orders for your Android app, your iOS app, and your React Native app. Only the card field and the 3D Secure call come from each platform’s Stripe SDK.

04Google Play

Physical goods and real-world services pay outside Play billing.

Google Play's Payments policy decides which payments in your app go through Play's billing system. For physical goods and physical services, the policy says Play's billing must not be used, and that is where Flint fits.

Take payments with Flint

Physical goods, such as groceries, clothing, housewares, and electronics. Physical services, such as transportation, cleaning, airfare, gym memberships, food delivery, and tickets to live events. Google's policy says these must not use Play's billing system.

Use Google Play's billing

In-app features, digital content and goods (such as virtual currencies, extra lives, and an ad-free version), and subscriptions to digital services. Google's policy requires Play's billing for these, apart from its alternative billing programs in eligible countries.

These categories are Google’s, quoted from the Google Play Payments policy, checked 2026-10-30. Google decides how they apply to your app, so check the policy against what you sell. This isn’t legal advice.

05No card form

Or open Flint's hosted checkout in a Custom Tab.

If you'd rather not build a payment screen at all, your backend creates a checkout session for the order and the app opens its URL in a Chrome Custom Tab. Flint hosts the page with cards, Google Pay where the buyer's device supports it, promotion codes, gift cards, and the receipt.

Your backend creates the session
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: checkout-ord_1kmn0aExample" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "order",
    "order_id": "ord_1kmn0aExample",
    "redirects": {
      "success_url": "https://studio.example.com/app/paid"
    }
  }'

The response's url is the hosted page for this order. Flint adds checkout_session_id and order_id to the success URL.

Kotlin, in the app
// url is the checkout session's url, from your backend
CustomTabsIntent.Builder().build()
    .launchUrl(context, Uri.parse(url))

CustomTabsIntent comes from androidx.browser.

Scopes for the hosted path

  • commerce.orders.write
  • checkouts.checkout_sessions.write
Flint's hosted checkout on a phone, shown with a sample order. Wallet buttons appear where the buyer's browser supports them.

Google Pay works on the hosted page with no setup, because the page runs on Flint's domain. It appears on devices and browsers that support it.

checkout.withflintpay.com

Redirect URLs are absolute HTTPS, so the success URL is a page on your domain, not a custom scheme like myapp://. When the buyer returns to the app, ask your backend for the order's status.

redirects.success_url

The redirect doesn't prove payment. Your backend confirms with order.paid or by reading the session.

order.paid

The checkout sessions guide covers every redirect and session setting. For a fixed product, a payment link is a hosted checkout your app can open with no backend call at all.

06Who Flint serves

US businesses, selling online in USD.

Businesses in the United States

Flint onboards US businesses and pays out in USD to the business's own bank account. Each one is verified before it takes live payments.

Prices and payouts in USD

Orders, payments, and payouts are in US dollars. Buyers can pay with cards issued anywhere.

Online payments

Flint takes online and in-app payments: your app's card field, hosted checkout, payment links, and invoices. Card readers and tap to pay aren't available.

Start

From a sandbox order to a paid test payment.

The call your backend makes
curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/pay \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: pay-ord_1kmn0aExample-pm_1kmn0aExample" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "pay",
    "payment_source": {
      "credential_type": "token",
      "token": "pm_1kmn0aExample"
    },
    "expected_outstanding_money": {
      "amount": 18000,
      "currency": "USD"
    },
    "expected_version": 1790760000,
    "buyer_contact": { "email": "sam@example.com" }
  }'

Swap in your order and the PaymentMethod id your app sends.

Create a product in your sandbox, have your backend create an order for it, and pay with 4242 4242 4242 4242, any future expiry, and any CVC. Use 4000 0025 0000 3155 to see the 3D Secure step. The paid order appears in your dashboard. Going live means verifying your business, creating your products and webhook endpoints again in live mode, and switching your backend to a live key. When the app gets product ids from your backend, it needs no new build.

3.79% + 35¢ on cards · no monthly fee · free sandbox · no credit card

Then read these

  1. 01
    Embedded paymentsCollection guidance, the pay call, and 3D Secure, field by field.
  2. 02
    Declines and payment attemptsEvery attempt status, and retrying with a new card.
  3. 03
    Checkout sessionsThe hosted page for a Custom Tab.
  4. 04
    WebhooksFulfill from order.paid.
  5. 05
    Customer accountsThe hosted buyer account, and the API to build your own.
  6. 06
    Going liveFrom the sandbox to real payments.

Or run npm install -g @flintpay/cli && flint signup to create your account and a sandbox key from the terminal. CLI guide

Sources

Where each outside fact comes from.

  • Apps that sell in-app features, digital content or goods, and subscriptions to them must use Google Play's billing system, and Play's billing system must not be used when payment is mainly for physical goods (such as groceries, clothing, housewares, electronics) or physical services (such as transportation, cleaning, airfare, gym memberships, food delivery, and tickets for live events). Developers in eligible countries can enroll in alternative billing programs. Google Play: Payments policy

    support.google.com
  • Stripe's Android SDK collects a card in CardInputWidget, creates a PaymentMethod with createPaymentMethod, and runs 3D Secure for a PaymentIntent confirmed on the server with handleNextActionForPayment. Stripe: Accept card payments without webhooks (Android)

    docs.stripe.com
  • The Stripe constructor takes a publishable key and an optional connected account id, and createPaymentMethod and handleNextActionForPayment use that account. Stripe Android SDK: Stripe class

    stripe.dev
  • CustomTabsIntent from androidx.browser opens a URL in a browser-powered tab inside your app, and falls back to the default browser when none supports Custom Tabs. Chrome: Custom Tabs, get started

    developer.chrome.com

last reviewed 2026-10-30 · corrections: Flint Help

FAQ

Questions Android developers ask first.

How do I accept payments in my Android app?

Have your backend create a Flint order and read its payment collection guidance. In the app, collect the card with Stripe’s Android SDK and createPaymentMethod, then send the PaymentMethod id to your backend. Your backend pays the order with one call to POST /v1/orders/{order_id}/pay. If the bank asks for 3D Secure, the app runs handleNextActionForPayment and your backend resumes the attempt. Fulfill from the order.paid webhook. The embedded payments guide has every field.

Does Flint have an Android SDK?

No. Your app uses Stripe's Android SDK for card entry, and your backend calls Flint's API with your secret key. Use the SDK's card field with createPaymentMethod rather than PaymentSheet, because Flint confirms the payment from your backend and only returns a client secret when the buyer has to authenticate.

Can I use Stripe in my Android app with Flint?

Yes. Flint processes cards through Stripe, and the order tells your app which publishable key and account to initialize Stripe's Android SDK with. Card numbers go from Stripe's card field to Stripe and never reach your servers or Flint's. What changes is your backend: it pays a Flint order instead of creating and confirming PaymentIntents, and the order carries the line items, tax, receipt, and refunds.

Do I have to use Google Play Billing?

It depends on what you sell. Google Play’s Payments policy requires Play’s billing system for in-app features, digital content and goods, and subscriptions to them, and says it must not be used when payment is mainly for physical goods or physical services, such as clothing, groceries, transportation, gym memberships, food delivery, and tickets to live events. Flint is for that second group. Read the policy for your own app; this is not legal advice.

Can buyers pay with Google Pay?

Yes, through Flint's hosted checkout. Your backend creates a checkout session for the order and the app opens its URL in a Chrome Custom Tab. The hosted page runs on Flint's domain, so Google Pay works there with no setup on your side, on devices and browsers that support it. The native flow in your own payment screen takes cards.

How does 3D Secure work in an Android app?

When the bank asks the buyer to authenticate, the pay call returns the attempt with status requires_action and a pending action carrying a Stripe client secret. Your backend passes it to the app, the app calls Stripe’s handleNextActionForPayment with it, and your backend resumes the attempt with action: "resume". Flint reads the outcome from Stripe. Pass that client secret only to handleNextActionForPayment and take the result from your backend. Finish 3D Secure covers every outcome.

Can I take payments without building a card form in the app?

Yes. Have your backend create a checkout session for the order and open its URL in a Chrome Custom Tab. Flint hosts the payment page with cards, and Google Pay on devices and browsers that support it. Redirect URLs are absolute HTTPS URLs, so the success URL is a page on your domain rather than a custom scheme like myapp://. When the buyer comes back to the app, ask your backend for the order's payment status, and fulfill from the order.paid webhook rather than the redirect.

What does Flint cost for an Android app?

Card payments are 3.79% + 35¢, with no monthly fee and no setup fee. On the $180 class pack in the examples, Flint's fee is $7.17. Subscription payments add 0.40%, and automatic sales tax adds 0.50% on payments where Flint collects tax. The sandbox is free and needs no credit card.

Who can use Flint?

Businesses in the United States, selling online in USD. Buyers can pay with cards issued anywhere. Each business is verified before it takes live payments, and the sandbox works before that.