Реалізація єдиного платіжного модуля для всіх мініпрограм Super App
Super App—це оболонка, в якій живуть десятки мініпрограм. Кожна мініпрограма хоче приймати платежі, але робити окремену інтеграцію з платіжним провайдером в кожній—помилка з кількох причин: PCI scope розширюється на кожну мініпрограму, оновлення сертифікатів або ключів API вимагає синхронного деплоя всіх мініпрограм, й у користувача немає єдиної історії платежів.
Рішення—платіжний модуль як частина хост-додатку (Shell App), який мініпрограми викликають через bridge API.
Архітектура: Shell ↔ Mini-Program Bridge
Типова Super App будується на WebView (або React Native / Flutter WebView) для мініпрограм. Платіжний bridge:
// Android Shell App: реєстрація bridge-методу
webView.addJavascriptInterface(PaymentBridge(this), "NativePayment")
class PaymentBridge(private val activity: AppCompatActivity) {
@JavascriptInterface
fun initiatePayment(requestJson: String) {
val request = PaymentRequest.fromJson(requestJson)
activity.runOnUiThread {
PaymentBottomSheet.show(activity, request) { result ->
val js = "window.onPaymentResult(${result.toJson()})"
webView.evaluateJavascript(js, null)
}
}
}
@JavascriptInterface
fun getSavedPaymentMethods(): String {
return paymentRepository.getSavedMethods().toJson()
}
}
// Мініпрограма (JS/React): виклик платіжного модуля
async function checkout(amount, orderId) {
return new Promise((resolve, reject) => {
window.onPaymentResult = (result) => {
if (result.status === 'success') resolve(result);
else reject(result.error);
};
NativePayment.initiatePayment(JSON.stringify({
amount,
currency: 'RUB',
orderId,
miniProgramId: 'com.yourshop.miniapp'
}));
});
}
На iOS аналогічна схема через WKScriptMessageHandler:
class PaymentMessageHandler: NSObject, WKScriptMessageHandler {
func userContentController(
_ controller: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard message.name == "initiatePayment",
let body = message.body as? [String: Any] else { return }
let request = PaymentRequest(from: body)
PaymentCoordinator.shared.present(request: request, from: hostViewController) { result in
let js = "window.onPaymentResult(\(result.jsonString))"
webView.evaluateJavaScript(js)
}
}
}
// Реєстрація в WKWebViewConfiguration
configuration.userContentController.add(PaymentMessageHandler(), name: "initiatePayment")
Єдиний PaymentCoordinator
Ключовий компонент—PaymentCoordinator у Shell App. Знає:
- які методи оплати доступні (карти, Apple Pay / Google Pay, SBP, баланс Super App)
- які карти збережені для користувача
- який провайдер обслуговує платіж (один платіжний шлюз або кілька)
// Android: PaymentCoordinator як singleton у Shell
class PaymentCoordinator private constructor() {
companion object {
val shared = PaymentCoordinator()
}
private val activeProviders = mutableMapOf<String, PaymentProvider>()
fun registerProvider(id: String, provider: PaymentProvider) {
activeProviders[id] = provider
}
fun initiatePayment(request: PaymentRequest, callback: (PaymentResult) -> Unit) {
val provider = selectProvider(request)
provider.process(request, callback)
}
private fun selectProvider(request: PaymentRequest): PaymentProvider {
// Логіка маршрутизації: різні мініпрограми можуть використовувати різних провайдерів
return activeProviders[request.miniProgramId]
?: activeProviders["default"]
?: throw IllegalStateException("No payment provider registered")
}
}
Маршрутизація по miniProgramId дозволяє одному Super App працювати з кількома еквайєрами—один для маркетплейсу, другий для доставки, третій для фінансових послуг.
Збережені методи оплати (Saved Payment Methods)
Користувач додає картку один раз—вона доступна у всіх мініпрограмах. Хранити токени карт потрібно централізовано:
data class SavedPaymentMethod(
val id: String,
val type: PaymentMethodType, // CARD, SBP, APPLE_PAY
val displayName: String, // "Visa •••• 4242"
val providerToken: String, // токен конкретного провайдера (не PAN!)
val isDefault: Boolean
)
providerToken—це токен від Stripe (pm_xxx), CloudPayments або іншого провайдера. PAN ніколи не зберігається на пристрої.
Синхронізація між пристроями: токени зберігаються на серверу, прив'язані до userId. При першому відкритті Super App після встановлення—завантажуємо список методів для користувача.
UI єдиного платіжного екрану
Bottom Sheet з платіжними методами повинен бути консистентним для всього Super App. Різні мініпрограми не можуть змінювати його внутрішній вигляд—це важливо для довіри користувача.
Що повинен уміти єдиний платіжний екран:
- Показати список збережених карт з можливістю вибору
- Додати нову картку (через SDK провайдера або власний card input)
- Apple Pay / Google Pay в одне натиснення
- SBP з deeplink у банківський додаток
- Відобразити суму та назву мініпрограми (звідки прийшов запит)
// PaymentBottomSheet отримує PaymentRequest і показує потрібні методи
data class PaymentRequest(
val amount: Long, // копійках
val currency: String,
val orderId: String,
val miniProgramId: String,
val miniProgramName: String, // "Доставка YourShop"—для відображення користувачу
val allowedMethods: List<PaymentMethodType>? = null // null = всі доступні
)
Обробка помилок та retry
Платіж—критична операція. Shell App повинен правильно обробляти частичні відмови:
- Таймаут провайдера → показати «Статус платежу уточняється», запустити polling статусу
-
3DSредирект → відкрити WebView всередину bottom sheet, не виводити користувача з додатку - Дублювання запиту → idempotent
orderIdна стороні сервера
Ориентири по терміне
3–6 тижнів: проектування bridge API, реалізація PaymentCoordinator, інтеграція з провайдером(ами), єдиний UI bottom sheet, тестування в кількох мініпрограмах. Вартість розраховується індивідуально після аналізу архітектури Super App.







