Automatic payment verification for bKash, Nagad & Rocket
A merchant configures payout wallets in the Android app, creates payment intents from their own server, and the SPV backend confirms payments after matching the customer's SMS confirmation.
https://spv-payment-api.pages.dev/api/v1
How a payment flows
Authentication
Two auth models, depending on who is calling the API.
Merchant server API key
Authorization: Bearer SPV-XXXXXXXX-XXXXXXXX
Content-Type: application/json
Keep this key on the merchant's own server only. Never embed it in browser JavaScript, Android source, website HTML, or a public repository.
Firebase ID token
Authorization: Bearer <FIREBASE_ID_TOKEN>
Used by the Android app. The backend validates the Firebase signature, project ID, expiry, and user ID.
API health
curl https://spv-payment-api.pages.dev/api/v1/health
{
"ok": true,
"service": "SPV Payment API",
"version": "v1",
"databaseReady": true,
"timestamp": "2026-07-10T00:00:00.000Z"
}
Merchant payout-wallet sync
Android app only. Syncs enabled payout wallets from the authenticated user's Firestore record.
POST /merchants/sync
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json
{}
- Merchant ID:
SPV-+ first 8 upper-case Firebase UID characters - Only enabled wallet fields sync:
walletBkash,walletNagad,walletRocket - Requires
kycStatus == approvedand at least one enabled wallet
{
"ok": true,
"merchantId": "SPV-ABCDEFGH",
"enabledMethods": ["bKash", "Nagad"],
"message": "Merchant wallet configuration synced."
}
Create payment intent
Called from the merchant's own server — never from browser JavaScript.
POST /payment-intents
Authorization: Bearer SPV-XXXXXXXX-XXXXXXXX
Content-Type: application/json
{
"amount": 1250,
"orderReference": "ORDER-1001",
"description": "Premium package purchase"
}
| Field | Rule |
|---|---|
amount | BDT number greater than 0 |
orderReference | Merchant's own unique order string |
description | Optional, customer-facing |
checkout link | Fixed 30-minute validation window |
{
"ok": true,
"paymentId": "pi_...",
"status": "pending",
"amount": 1250,
"expiresAt": 1760000000000,
"checkoutUrl": "https://spv-payment-api.pages.dev/payment-portal/checkout.html?payment_id=pi_...&token=pct_..."
}
checkoutUrl.Server example — Node.js
const result = await fetch('https://spv-payment-api.pages.dev/api/v1/payment-intents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SPV_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 1250,
orderReference: `ORDER-${order.id}`,
description: 'Premium package purchase'
})
}).then(r => r.json());
if (result.ok) {
res.redirect(result.checkoutUrl);
}
Read payment status
Customer checkout polling. Merchant server polls the same path without a token, using its API key instead.
GET /payment-intents/{paymentId}
Authorization: Bearer SPV-XXXXXXXX-XXXXXXXX
{
"ok": true,
"paymentId": "pi_...",
"merchant": { "id": "SPV-ABCDEFGH", "name": "Merchant Brand" },
"orderReference": "ORDER-1001",
"description": "Premium package purchase",
"amount": 1250,
"currency": "BDT",
"status": "pending",
"expiresAt": 1760000000000,
"updatedAt": 1759990000000,
"payoutNumbers": {
"bKash": "01XXXXXXXXX",
"Nagad": "01XXXXXXXXX"
}
}
| State | Meaning |
|---|---|
pending | Checkout created, no submission yet |
submitted | Transaction ID entered, verifying |
verified | Payment confirmation matched |
rejected | Verification window expired |
cancelled | Merchant/backend cancelled the intent |
expired | Link expired; may be renewed for legacy links |
Customer payment detail submission
POST /payment-intents/{paymentId}/submit
Content-Type: application/json
{
"token": "pct_...",
"provider": "bKash",
"transactionId": "5AB12CD34E"
}
Supported providers: bKash, Nagad, Rocket. Only a Transaction ID is requested — never a sender number. A submitted customer may resubmit a corrected Transaction ID while verification is pending.
{
"ok": true,
"paymentId": "pi_...",
"status": "submitted",
"message": "Payment details received. Verification in progress."
}
Merchant app SMS event
Generic merchant payments. Matches a pending/submitted intent by amount and expiry for the authenticated merchant.
POST /mobile/sms-events
Authorization: Bearer SPV-XXXXXXXX-XXXXXXXX
Content-Type: application/json
{
"transactionId": "5AB12CD34E",
"provider": "bKash",
"amount": "1250"
}
Subscription checkout
Android app. Requires an authenticated Firebase user and approved KYC. Subscription checkout always uses the platform owner's configured wallet, never the subscriber's payout wallet.
POST /subscriptions/checkout
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json
{
"planId": "30d"
}
| Plan ID | Amount | Duration |
|---|---|---|
30d | ৳99 | 30 days |
90d | ৳280 | 90 days |
365d | ৳999 | 365 days |
The app opens the returned browser checkout URL.
Subscription SMS verification
POST /subscriptions/sms-events
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json
{
"transactionId": "5AB12CD34E",
"provider": "bKash",
"amount": "999"
}
The device can submit payment evidence only after the confirmation SMS names the platform owner's wallet. Evidence enters the secure verification flow; client-provided SMS never activates a subscription directly.
Checkout UI specification
Rules for the payment-portal checkout page.
Dynamic data source
GET /api/v1/payment-intents/{paymentId}?token={checkoutToken}
Provider visibility
const enabled = Object.entries(data.payoutNumbers)
.filter(([provider, number]) => number);
If exactly one method is enabled, skip method-selection and open its instruction panel directly.
- Customer fields: only
Transaction ID. Never request sender phone number. - After submit, show a professional full-screen verification animation. Verification runs in the background for up to 30 seconds with no visible countdown.
- No website navbar, hamburger menu, avatar, or account navigation on the payment portal — payment content only.
| Backend state | UI |
|---|---|
verified | Dedicated receipt page with “You can leave this page” and PDF download |
rejected | Rejected screen only, with PDF receipt download and no navigation link |
submitted | Resume verifying overlay immediately on reopen |
Related links
Public entry points for the SPV product.