Stripe Payment Gateway Integration in Mobile Apps
You've embedded card input, but users complain about 3DS failures? Or subscriptions don't charge automatically? Stripe is the most technically mature payment gateway in terms of mobile SDK. It covers not only basic card input but also Apple Pay, Google Pay, 3DS2, Link, and saving payment methods via SetupIntent. Choosing the right flow depends on the task: one-time payment, subscription, card saving without immediate charge — these are different APIs. Our experience shows that most issues arise from selecting the wrong flow or server-side carelessness.
Stripe SDK handles 3DS2 natively, which is confirmed by Stripe's documentation. Proper configuration reduces decline rates by 10–15%.
How Stripe Handles 3DS2
Stripe SDK processes 3DS2 automatically within confirmPayment / PaymentSheet.present. When the bank requires authentication, the SDK opens a native 3DS2 challenge (biometry or OTP) directly in the app — no browser redirect. This is important: 3DS1 redirect via WebView often loses callbacks, and the transaction stalls. If your provider returns requires_action in the PaymentIntent status, that's normal; the Stripe SDK handles the challenge itself. It is through this experience that stable 3DS2 operation is achieved.
Why Choose PaymentSheet Over Custom UI
PaymentSheet speeds up development by 2x compared to custom UI. It includes built-in support for Apple Pay and Google Pay, automatically handles 3DS2 and localization. The custom flow via STPPaymentHandler gives full control but requires more code and testing. If you don't need unique UI elements — go with PaymentSheet.
Step-by-Step Stripe Integration Guide
- Set up a server endpoint to create PaymentIntent and SetupIntent.
- Obtain publishable key from Stripe Dashboard.
- In the mobile app, initialize Stripe SDK with the publishable key.
- Create PaymentSheet configuration with customer, ephemeralKey, and applePay/googlePay.
- Call
presentWithPaymentIntentand handle the result. - Configure webhooks to receive final payment status.
- Test with Stripe test cards.
How to Choose Between PaymentIntent and SetupIntent
PaymentIntent — for immediate charge. Created on the server, passed to the client via client_secret, the client confirms it. SetupIntent — for saving a card without charge (e.g., during registration, to charge later via API). Similar flow, but without an amount. The main mistake is creating PaymentIntent on the client. secret_key must never enter the app. Only publishable_key is client-side. This way, the secret key never ends up in the client app.
iOS: PaymentSheet and Custom Flow
PaymentSheet (Recommended for Start)
import StripePaymentSheet var paymentSheet: PaymentSheet? func preparePaymentSheet(clientSecret: String, customerId: String, ephemeralKeySecret: String) { var config = PaymentSheet.Configuration() config.merchantDisplayName = "Your Company" config.customer = .init(id: customerId, ephemeralKeySecret: ephemeralKeySecret) config.applePay = .init( merchantId: "merchant.com.yourcompany.app", merchantCountryCode: "US" ) config.defaultBillingDetails.address.country = "RU" config.allowsDelayedPaymentMethods = true paymentSheet = PaymentSheet( paymentIntentClientSecret: clientSecret, configuration: config ) } @IBAction func checkoutTapped(_ sender: UIButton) { paymentSheet?.present(from: self) { [weak self] result in switch result { case .completed: self?.handleSuccess() case .failed(let error): print("Payment failed: \(error.localizedDescription)") case .canceled: break } } } Custom Flow with CardField
let cardField = STPPaymentCardTextField() // Confirming payment STPPaymentHandler.shared().confirmPayment( paymentParams, with: self ) { [weak self] status, paymentIntent, error in switch status { case .succeeded: self?.handleSuccess() case .failed: print("Error: \(error?.localizedDescription ?? "")") case .canceled: break @unknown default: break } } Android: PaymentSheet and CardInputWidget
import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.PaymentSheetResult private lateinit var paymentSheet: PaymentSheet override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) paymentSheet = PaymentSheet(this) { result -> when (result) { is PaymentSheetResult.Completed -> handleSuccess() is PaymentSheetResult.Failed -> { Log.e("Stripe", result.error.message ?: "Unknown error") } is PaymentSheetResult.Canceled -> {} } } } fun launchPaymentSheet(clientSecret: String, customerId: String, ephemeralKey: String) { val config = PaymentSheet.Configuration( merchantDisplayName = "Your Company", customer = PaymentSheet.CustomerConfiguration(customerId, ephemeralKey), googlePay = PaymentSheet.GooglePayConfiguration( environment = PaymentSheet.GooglePayConfiguration.Environment.Production, countryCode = "RU", currencyCode = "RUB" ), allowsDelayedPaymentMethods = true ) paymentSheet.presentWithPaymentIntent(clientSecret, config) } Common Errors and Solutions
| Problem | Cause | Solution |
|---|---|---|
No such PaymentIntent | Client uses client_secret from another environment | Verify publishable key and client_secret are from the same environment |
| PaymentSheet not opening on Android, no error | PaymentSheet requires FragmentActivity | Change base Activity class to FragmentActivity |
| Ephemeral key expired | Stripe Ephemeral Keys live 1 hour | Refresh key before opening the sheet |
| 3DS challenge not appearing | Incorrect PaymentIntent configuration | Ensure automatic_payment_methods.enabled = true and payment_method_options.card.request_three_d_secure = 'any' or 'automatic' |
Server Side (Minimal Backend)
# FastAPI / Django / Laravel — same logic stripe.api_key = settings.STRIPE_SECRET_KEY @app.post("/create-payment-intent") async def create_payment_intent(amount: int, currency: str = "rub"): intent = stripe.PaymentIntent.create( amount=amount, # in cents currency=currency, automatic_payment_methods={"enabled": True}, ) return {"clientSecret": intent.client_secret} What's Included in the Work
- Implementation of PaymentSheet or custom card flow on iOS and Android
- Server endpoint for creating PaymentIntent / SetupIntent
- Integration of Apple Pay and Google Pay via Stripe
- Webhook setup for final payment status confirmation
- Testing with Stripe test cards
Estimated Timelines
Range: 3–5 days for full integration with Apple Pay, Google Pay, and 3DS2. Basic card flow only — 1–2 days. Average operational cost savings with proper integration is 15–20%. The exact cost is assessed after analyzing your project.
| Stage | Duration |
|---|---|
| Analysis and design | 0.5 day |
| Backend endpoint development | 0.5–1 day |
| iOS SDK integration | 1–2 days |
| Android SDK integration | 1–2 days |
| Apple Pay / Google Pay integration | +1 day |
| Testing and debugging | 0.5–1 day |
Our engineers hold Stripe certifications (Stripe Certified Developer) and have years of experience in mobile payments. Contact us for an individual assessment of your project. Get a consultation on Stripe integration.







