Skip to content

Payments in mobile apps

An iOS or Android app takes payment through Flint in one of two ways:

  • Hosted checkout in a system browser view. Your server creates a checkout session, the app opens its url, and an HTTPS link brings the buyer back. Checkout takes cards, and Apple Pay and Google Pay on devices that support them.
  • A card field in your own screen. Stripe's mobile SDK collects the card, and your server pays the Flint order with it.

Either way, your server learns the order is paid from the order.paid webhook, and the app never calls the Flint API. An API key shipped in a mobile app counts as leaked, so keep keys on your server, as API key security explains.

Before you build, check that the app store lets your app take the payment outside its in-app purchase system.

Check what the app stores allow#

Apple and Google decide which purchases in an app must use their in-app purchase systems. This summary of their rules isn't legal advice. The stores change them, so read the current text before you ship, and ask your counsel when your case isn't clear.

What the buyer pays forApple App StoreGoogle Play
Physical goods, such as groceries, clothing, housewares, or electronics3.1.3(e): an app that sells "physical goods or services that will be consumed outside of the app" must use purchase methods other than in-app purchase, "such as Apple Pay or traditional credit card entry"Payments policy, Section 3(a): Google Play's billing system must not be used for physical goods
Physical services, such as transportation, cleaning, airfare, gym memberships, food delivery, or tickets for live eventsThe same rule, 3.1.3(e)Payments policy, Section 3(a): Google Play's billing system must not be used for physical services. These examples are Google's.
Digital content or features used in the app, such as subscriptions, premium content, in-game currency, or a full versionIn-app purchase required: 3.1.1, apart from the exceptions belowGoogle Play's billing system required, apart from the programs below: Payments policy

Flint fits the first two rows. Selling digital content or features used inside the app through Flint risks rejection or removal from the store. The exceptions are narrow:

  • Apple reader apps (3.1.3(a)) may let people use magazines, newspapers, books, audio, music, and video they bought elsewhere.
  • Apple multiplatform services (3.1.3(b)) may let people use in the app what they bought on your website, provided those items are also available as in-app purchases.
  • Apple external purchase links (3.1.1(a)): in specific storefronts, apps with a StoreKit External Purchase Link Entitlement may link to another way to buy. On the United States storefront, the guidelines let apps include buttons and links to other purchase methods without an entitlement.
  • Google alternative billing and external offers apply only in eligible countries and regions, and only after you enroll in the program: see user choice billing and programs for external content.

The reader-app and multiplatform rules let people use in the app what they bought elsewhere. They don't let the app sell that item through Flint, link to Flint checkout, or encourage another way to pay. Apple's 3.1.3 says apps that may use other purchase methods "cannot, within the app, encourage users to use a purchasing method other than in-app purchase", except on the United States storefront and as 3.1.1(a) and 3.1.3(a) set out. The 3.1.3(a) link is an informational link for creating or managing an account, not for buying. Google's Payments policy, Section 4, says that apart from Sections 3, 8, and 9, "apps may not lead users to a payment method other than Google Play's billing system", including through in-app webviews, buttons, and links. So for digital items, an app may point buyers to Flint checkout only on Apple's United States storefront, under a 3.1.1(a) entitlement, or in a Google program you've enrolled in under Sections 8 and 9.

Apple treats gift cards by what they buy: digital gift cards redeemable for digital goods or services need in-app purchase, and physical gift cards mailed to the buyer may use other payment methods (3.1.1).

Open checkout from the app#

Use this flow for physical goods and services used outside the app. For digital items, use it only where a rule above lets the app point buyers to another way to pay.

Create the session on your server#

When the buyer taps to pay, the app asks your server for a checkout URL. Your server creates the order, then a hosted checkout session for it, with return URLs on your domain:

cURL
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: app-checkout-ord-1042" \
  -d '{
    "type": "order",
    "order_id": "ord_1kmn0aExample",
    "redirects": {
      "success_url": "https://example.com/app/checkout/done",
      "cancel_url": "https://example.com/app/checkout/canceled"
    }
  }'
Response
{
  "data": {
    "checkout_session_id": "cs_1kmn0aExample",
    "status": "open",
    "surface": "hosted",
    "order_id": "ord_1kmn0aExample",
    "type": "order",
    "expires_at": "2026-07-04T17:04:05Z",
    "url": "https://checkout.withflintpay.com/checkout/cs_1kmn0aExample#checkout_token=cklt_v1...",
    "checkout_auth_token": "ckat_v1..."
  }
}

Return only url to the app, and keep checkout_session_id with your order on the server. A hosted integration doesn't use checkout_auth_token, so don't send it to the app.

The redirect URLs must be absolute HTTPS URLs of up to 2,048 characters. Flint rejects custom schemes such as myapp://checkout/done, and accepts HTTP only for loopback URLs in test mode. The same rule applies to payment_return_url on embedded sessions, which also can't contain a query string or a fragment. To get the buyer back into the app from an HTTPS URL, make your return URLs universal links on iOS and Android App Links on Android, and serve a page at each one for buyers who don't have the app.

Open the URL in a system browser view#

Open url in one of the system's browser views rather than a WKWebView or Android WebView. Your app can't read what the buyer types there, and the buyer's saved AutoFill details, such as cards and addresses, are available. On iOS, use SFSafariViewController by default for payments, since Apple Pay on the web works there, and ASWebAuthenticationSession when you need the HTTPS callback.

PlatformViewHow the buyer gets back
iOSSFSafariViewControllerThe universal link opens the app when the buyer taps the receipt's button, or when the automatic redirect opens the app. Otherwise the buyer closes the view.
iOS 17.4 and laterASWebAuthenticationSession with an HTTPS callback for the host and path of success_urlThe session closes when checkout navigates to success_url, and your completion handler gets the URL. The host must be one of your app's associated domains, under the webcredentials service.
AndroidCustom TabsThe app link opens the app when the buyer taps the receipt's button, or when the automatic redirect opens the app. Otherwise the buyer closes the tab.
Android, Chrome 137 and laterAuth Tab with an HTTPS redirect host and path, verified with Digital Asset LinksThe tab closes on success_url and your result callback gets the URL. Where Auth Tab isn't supported, a Custom Tab opens instead, and your callback gets a canceled result when it closes, so ask your server whether the order is paid.

After a payment, checkout shows its receipt, then a few seconds later sends the buyer to success_url with checkout_session_id and order_id query parameters added. A payment approved now and captured later redirects too, before order.paid arrives. The receipt also has a button that returns the buyer to your app or site. A bank payment that is still processing gets no redirect. If the buyer leaves checkout without paying, checkout sends them to cancel_url.

In SFSafariViewController and Custom Tabs, the automatic redirect is a script navigation, so it can load your success_url page in the browser view instead of opening the app. Apple opens a universal link in the app when the buyer taps it from a different domain, such as the receipt's button on Flint's checkout domain. If you serve checkout on a custom domain, test this on a device. Whichever way the buyer comes back, including closing the view, have the app ask your server for the order's state.

Confirm the payment on your server#

The redirect doesn't prove payment: a buyer can close the view before it loads, and anyone can open the URL. Fulfill from the order.paid webhook, or read the session with GET /v1/checkout-sessions/{checkout_session_id} and check that status is paid. The app then shows what your server reports, never what the redirect suggests.

Collect the card in your own screen#

Use this path only for physical goods and services used outside the app. To keep the card form inside the app, follow Embedded payments with Stripe's iOS, Android, or React Native SDK in place of Stripe.js:

  1. Your server creates the order and reads it. payment_collection.stripe holds the publishable_key and account_id the Stripe SDK needs, as step 2 shows.
  2. The app shows the SDK's card field and creates a payment method (pm_...). Card numbers go to Stripe, never through your server.
  3. Your server pays the order with credential_type: "token", as in step 5.
  4. If the bank asks for 3D Secure, the pay response carries a client secret. The app passes it only to the SDK's next-action call (handleNextAction in React Native), and your server resumes the payment, as in step 6.

The iOS, Android, and React Native pages have code for each step. For Apple Pay, use hosted checkout.

Wallets and 3D Secure#

Hosted checkout runs on Flint's domain, so it shows Apple Pay and Google Pay with no setup, as Apple Pay and Google Pay setup describes. In an app, the buttons appear when the device and the browser view support the wallet: Apple Pay in SFSafariViewController, and Google Pay in a Custom Tab on Chrome. If you open checkout in ASWebAuthenticationSession or Auth Tab, check on a real device that the buttons you need appear.

When a bank asks for 3D Secure, hosted checkout runs the challenge itself, so the app needs no code for it. With a card field in your own screen, the Stripe SDK runs it. Test it with the 3D Secure card in Testing.

Known limits#

These don't work in a native app today:

  • No Flint mobile SDK. Flint has no iOS, Android, or React Native library, and no publishable key of its own. The app calls your server, and your server calls Flint.
  • No native Apple Pay. Flint can't install the Apple Pay merchant certificate that Apple Pay in your app's own interface needs. Offer Apple Pay through hosted checkout in SFSafariViewController.
  • Stripe's PaymentSheet can't finish a Flint order payment. A successful payment returns no PaymentIntent client secret, which the sheet needs to complete. Use the SDK's card field instead.
  • No endpoint returns the publishable key. The Stripe publishable key and account ID appear only on an order's payment_collection and in pending payment actions. Set up the Stripe SDK from the order when checkout starts, not when the app launches.
  • Subscription charges that need 3D Secure can't be completed in the app. Creating a subscription with POST /v1/subscriptions returns no authentication step, and payment retries carry no client action. A renewal the bank asks to authenticate fails, and the buyer fixes it by saving a new card, whose setup runs 3D Secure. See Subscription billing.

Was this helpful?