Реализация единого платёжного модуля для всех мини-программ 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, не уводить пользователя из приложения - Дублирование запроса → идемпотентный
orderIdна стороне сервера
Ориентиры по срокам
3–6 недель: проектирование bridge API, реализация PaymentCoordinator, интеграция с провайдером(ами), единый UI bottom sheet, тестирование в нескольких мини-программах. Стоимость рассчитывается индивидуально после анализа архитектуры Super App и количества мини-программ.







