Implementing Unified Payment Module for All Mini-Programs in Super App
Super App is shell containing dozens of mini-apps. Each wants to accept payments, but building separate provider integration in each—mistake for several reasons: PCI scope expands per mini-app, certificate updates require simultaneous deploy all mini-apps, user lacks unified payment history.
Solution—payment module as part of host app (Shell App), which mini-programs call via bridge API.
Architecture: Shell ↔ Mini-Program Bridge
Typical Super App built on WebView (or React Native / Flutter WebView) for mini-programs. Payment bridge:
// Android Shell App: register bridge method
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()
}
}
// Mini-program (JS/React): call payment module
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'
}));
});
}
On iOS similar via 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)
}
}
}
// Register in WKWebViewConfiguration
configuration.userContentController.add(PaymentMessageHandler(), name: "initiatePayment")
Unified PaymentCoordinator
Key component—PaymentCoordinator in Shell App. Knows:
- available payment methods (cards, Apple Pay / Google Pay, SBP, Super App balance)
- saved cards for user
- which provider handles payment (one gateway or multiple)
// Android: PaymentCoordinator as singleton in 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 {
// Routing logic: different mini-programs may use different providers
return activeProviders[request.miniProgramId]
?: activeProviders["default"]
?: throw IllegalStateException("No payment provider registered")
}
}
Routing by miniProgramId allows one Super App work with multiple acquirers—one for marketplace, another for delivery, third for fintech.
Saved Payment Methods
User adds card once—available in all mini-programs. Store tokens centrally:
data class SavedPaymentMethod(
val id: String,
val type: PaymentMethodType, // CARD, SBP, APPLE_PAY
val displayName: String, // "Visa •••• 4242"
val providerToken: String, // provider token (not PAN!)
val isDefault: Boolean
)
providerToken—token from Stripe (pm_xxx), CloudPayments, or other provider. PAN never stored on device.
Cross-device sync: tokens stored on server, tied to userId. On first Super App open after install—load user's payment methods.
Unified Payment Screen UI
Bottom Sheet with payment methods must be consistent across Super App. Different mini-programs can't change its look—important for user trust.
Unified payment screen must handle:
- Show saved cards with selection option
- Add new card (via provider SDK or custom card input)
- Apple Pay / Google Pay in one tap
- SBP with deep-link to banking app
- Display amount and mini-program name (request origin)
// PaymentBottomSheet gets PaymentRequest and shows needed methods
data class PaymentRequest(
val amount: Long, // kopecks
val currency: String,
val orderId: String,
val miniProgramId: String,
val miniProgramName: String, // "Delivery YourShop"—for user display
val allowedMethods: List<PaymentMethodType>? = null // null = all available
)
Error Handling and Retry
Payment—critical operation. Shell App must handle partial failures correctly:
- Provider timeout → show "Payment status being verified", start polling
-
3DSredirect → open WebView inside bottom sheet, don't leave app - Request duplication → idempotent
orderIdon server side
Timeline Estimates
3–6 weeks: bridge API design, PaymentCoordinator implementation, provider integration, unified UI bottom sheet, testing across mini-programs. Pricing is calculated individually after architecture analysis.







