Інтеграція платіжного шлюзу Stripe в мобільному додатку
Stripe — технічно найбільш зрілий платіжний шлюз з точки зору мобільного SDK. SDK для iOS та Android охоплюють не лише базовий card input, а й Apple Pay, Google Pay, 3DS2, Link, а також збереження способів оплати через SetupIntent. Вибір правильного потоку залежить від завдання: одноразовий платіж, підписка, збереження карти без негайного списання — це різні API.
PaymentIntent проти SetupIntent: що використовувати
PaymentIntent — для негайного списання. Створюється на сервері, передається клієнту через client_secret, клієнт підтверджує.
SetupIntent — для збереження карти без списання (наприклад, при реєстрації, щоб потім списувати через API). Аналогічний потік, але без суми.
Основна помилка — створювання PaymentIntent на клієнті. secret_key ніколи не повинна потрапляти в додаток. Лише publishable_key — клієнтський.
iOS: PaymentSheet та користувацький потік
Stripe пропонує два підходи: готовий PaymentSheet (нативний UI від Stripe) та поелементний STPPaymentHandler.
PaymentSheet (рекомендується для старту)
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
}
}
}
Користувацький потік з CardField
Якщо потрібен повний контроль над UI:
let cardField = STPPaymentCardTextField()
// Підтвердження платежу
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 та 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)
}
3DS2: що відбувається під капотом
Stripe SDK обробляє 3DS2 автоматично в рамках confirmPayment / PaymentSheet.present. Коли банк вимагає підтвердження, SDK відкриває нативне 3DS2 завдання (біометрія або OTP) прямо в додатку — без перенаправлення браузера. Це важливо: перенаправлення 3DS1 через WebView часто втрачає callback, і транзакція зависає.
Якщо ваш постачальник повертає requires_action у статусі PaymentIntent — це нормально, Stripe SDK сам обробить завдання.
Типові проблеми
No such PaymentIntent — клієнт використовує client_secret з іншого середовища (test key з live secret або навпаки). Publishable key і client_secret повинні бути з одного середовища.
PaymentSheet не відкривається на Android, немає помилки. Stripe PaymentSheet вимагає FragmentActivity, а не простої Activity. Якщо запускаєте звичайної Activity — отримуєте тихий відмову.
Ephemeral key видалений. Stripe Ephemeral Keys живуть 1 годину. Якщо користувач довго сидить на екрані оплати — ключ протухає, PaymentSheet падає при спробі завантажити збережені способи оплати. Потрібно оновлювати ключ перед відкриттям sheet.
Серверна частина (мінімальний бекенд)
# FastAPI / Django / Laravel — логіка однакова
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, # у копійках
currency=currency,
automatic_payment_methods={"enabled": True},
)
return {"clientSecret": intent.client_secret}
Обсяг робіт
- Реалізація PaymentSheet або користувацького card flow на iOS та Android
- Серверний endpoint для створення PaymentIntent / SetupIntent
- Інтеграція Apple Pay та Google Pay через Stripe
- Налаштування Webhooks для остаточного підтвердження статусу платежу
- Тестування з тестовими картами Stripe (4242 4242 4242 4242 та сценарії 3DS)
Терміни
3–5 днів для повної інтеграції з Apple Pay, Google Pay та 3DS2. Лише базовий card flow — 1–2 дні. Вартість розраховується індивідуально.







