Founder & Lead Engineer, RAITHub
Build one payment interface in your backend and plug in a local gateway per country, because no single provider covers them all: Stripe's availability page lists India and Indonesia only as previews and does not list Bangladesh or Pakistan. Then price for flat fees. On Midtrans, a IDR 4,000 virtual-account fee is 8% of a IDR 50,000 course (Midtrans pricing).
If you would rather have it built for you, see how RAITHub would build this below.
This post is for EdTech founders and product teams selling courses, tuition or exam prep to learners in Bangladesh, India, Pakistan, Nigeria, Kenya and Indonesia. It covers the payment layer only. The wider platform, from content delivery to assessments, is in the EdTech software development guide.
Why can't an EdTech platform just use Stripe everywhere?
Because in these markets the learner usually pays with a mobile wallet, a bank transfer, a QR code or a local card scheme, and the gateways that reach them are local. Stripe's own global availability page, checked on 2 October 2026, marks India and Indonesia as "Preview" with a contact-sales link, routes Nigeria, Kenya, Ghana, South Africa and Côte d'Ivoire through Paystack, Stripe's extended network, and does not list Bangladesh or Pakistan at all.
So the realistic setup is one checkout in your product, backed by a different gateway per country. The table shows the typical rails. It is scope, not a claim of shipped integrations: the only ones RAITHub has shipped in production are bKash, Nagad and SSLCommerz, in TheSkinProof, the founder's own verified-skincare marketplace (not a client project).
| Country | How learners commonly pay | Example gateways to evaluate | Shipped by RAITHub? |
|---|---|---|---|
| Bangladesh | bKash and Nagad wallets, cards, bank, cash | bKash, Nagad, SSLCommerz | Yes, in TheSkinProof (the founder's own venture) |
| India | UPI, cards, netbanking | Razorpay | No: engineering scope only |
| Pakistan | Mobile wallets, cards, bank transfer | Local wallets and aggregators; confirm current merchant terms | No: engineering scope only |
| Nigeria | Cards, bank transfer, USSD | Paystack | No: engineering scope only |
| Kenya | M-Pesa, cards | Safaricom M-Pesa via Daraja, Paystack | No: engineering scope only |
| Indonesia | QRIS, GoPay, ShopeePay, virtual accounts, convenience stores | Midtrans and similar aggregators | No: engineering scope only |
Buy, build or hire: which EdTech payment setup should you choose?
Decide by how many countries you sell in and how much of your margin fees can take. A course platform is the fastest start; a custom build pays off when you need local rails in several countries or your own learning product around them.
| Option | Example and cited price | Choose this when | Watch out for |
|---|---|---|---|
| Off-the-shelf course platform | Teachable: Starter $39 a month with a 7.5% transaction fee; Builder $89 a month with 0%; international cards 3.9% + 30¢ (Teachable pricing) | You sell video courses mainly to card-holding learners and want to launch this week | Card-first checkout; per-sale fees add up on low-priced courses; local wallets may be missing |
| No-code payment pages on a local gateway | Razorpay: 2% per successful domestic transaction, no setup fee or annual charge, plus 18% GST on fees (Razorpay pricing) | One country, a handful of courses, and you can grant access by hand or with a simple automation | Enrolment and access are not linked to payment state; manual work grows with every learner |
| Custom build | Your own checkout with one gateway per country behind a provider interface | Several countries, instalments, cohorts or subscriptions, and payments that must unlock access in your own product automatically | Weeks of engineering and ongoing upkeep; each gateway needs its own merchant onboarding |
What do gateway fees do to a low-priced course?
They take a far larger share than on a high-priced one, because many local methods charge a flat fee per transaction. The arithmetic below uses list prices from each provider's pricing page, checked on 2 October 2026. Your negotiated rates may differ.
| Course price | Payment method and list fee | Fee on this sale | Share of price |
|---|---|---|---|
| IDR 50,000 | Midtrans QRIS, 0.7% | IDR 350 | 0.7% |
| IDR 50,000 | Midtrans GoPay, 2% | IDR 1,000 | 2% |
| IDR 50,000 | Midtrans virtual account, IDR 4,000 flat | IDR 4,000 | 8% |
| IDR 50,000 | Midtrans card, 2.9% + IDR 2,000 | IDR 3,450 | 6.9% |
| ₹499 | Razorpay domestic, 2% plus 18% GST on the fee | About ₹11.78 | About 2.4% |
| $10 | Teachable Starter 7.5% plus international card 3.9% + 30¢, if both apply | About $1.44 | About 14% |
Midtrans states its fees exclude VAT except for QRIS, GoPay and ShopeePay (Midtrans pricing). Three design choices follow from the table:
- Show low-fee methods first for small tickets, such as QR or wallet before card, without hiding the others.
- Bundle small purchases. A term pass or a bundle of five lessons pays one flat fee instead of five.
- Keep fee rates in configuration, per gateway and method, so finance can see margin per course and you can change the order of methods without a deploy.
How do you put many payment gateways behind one checkout?
With a small provider interface that every gateway implements, and a router that picks a provider by country and method. The enrolment code only ever talks to the interface. This is a sketch, not production code from any project; the helper functions are yours.
type Country = 'BD' | 'IN' | 'PK' | 'NG' | 'KE' | 'ID'
type Method = 'wallet' | 'card' | 'bank' | 'qr' | 'ussd'
type Money = { minor: number; currency: string } // integer, in the unit your gateway expects
type Status = 'paid' | 'pending' | 'failed'
export interface PaymentProvider {
id: string
supports(country: Country, method: Method): boolean
/** Starts a payment. Returns a redirect URL, or nothing for a phone push such as STK. */
start(a: { attemptId: string; amount: Money; phone?: string }): Promise<{ ref: string; redirectUrl?: string }>
/** Asks the provider, server to server. Never trust the browser redirect. */
status(ref: string): Promise<{ status: Status; amount?: Money; providerTxnId?: string }>
/** Some rails have no refund API; 'manual' sends it to a finance queue. */
refund(providerTxnId: string, amount: Money): Promise<{ refundRef: string } | 'manual'>
}
declare const providers: PaymentProvider[] // registered in priority order per country
declare function saveAttempt(row: Record<string, unknown>): Promise<void>
declare function lastAttempt(invoiceId: string): Promise<{ providerId: string; ref: string } | null>
export function pickProvider(country: Country, method: Method): PaymentProvider {
const p = providers.find((x) => x.supports(country, method))
if (!p) throw new Error('No provider for ' + country + '/' + method)
return p
}
/** Safe retry: check the last attempt first, so a slow wallet push is never charged twice. */
export async function payInvoice(invoiceId: string, amount: Money, country: Country, method: Method, phone?: string) {
const prev = await lastAttempt(invoiceId)
if (prev) {
const old = providers.find((x) => x.id === prev.providerId)
const s = old ? await old.status(prev.ref) : null
if (s && s.status !== 'failed') return { already: s.status } // paid or still pending: do not start another
}
const provider = pickProvider(country, method)
const attemptId = invoiceId + ':' + Date.now()
const started = await provider.start({ attemptId, amount, phone })
await saveAttempt({ invoiceId, attemptId, providerId: provider.id, ref: started.ref, amount })
return started
}
Two rules make this safe. Mark an invoice paid only after a server-to-server status check that matches the amount and currency you recorded, and make that update safe to run twice, as described in idempotency in API design. The bKash and SSLCommerz specifics, including token reuse and the validation API, are in integrating bKash, Nagad and SSLCommerz in one checkout.
How should course instalments and tuition payment plans work?
Treat each instalment as its own invoice tied to one enrolment, and let access follow the invoices. Card mandates and wallet auto-debit are not available everywhere, so most plans in these markets are "we remind, the learner pays" rather than automatic charges.
- Create the schedule at enrolment. For example, three invoices due on day 0, day 30 and day 60, with the course split into modules that unlock as each is paid.
- Use automatic recurring charges where the rail supports them. Razorpay's subscriptions documentation lists cards, UPI AutoPay and e-mandates, and says its "automatic retry logic maximises successful collections" (Razorpay Subscriptions). Elsewhere, send a payment link before each due date.
- Define a grace period in the product, not in someone's head. For example, access continues for seven days after a missed instalment, then pauses without deleting progress.
- Keep enrolled, paid and active as separate states. A learner can be enrolled with one instalment overdue; reports and support need to see exactly that.
Recurring-payment and consumer-credit rules differ by country. This is general information; confirm with your adviser before offering instalments.
How do you recover failed mobile money and wallet payments?
Mostly by making the retry easy and safe, because many failures are ordinary: the learner's balance is short, the phone prompt timed out, or the network dropped during the redirect.
- Query before you retry. A wallet push can be approved after your timeout. The code above checks the last attempt's status first, so a second prompt never doubles the charge.
- Reconcile pending attempts on a schedule. A job that asks each provider about attempts still pending after a few minutes catches payments whose callback never arrived. Testing payments and webhooks covers how to test those paths in CI.
- Send one retry link, on the channel the learner uses. SMS, email or a messaging app, with the same invoice and a fresh attempt. Offer a different method on the second try.
- Measure by method. Track success rate per gateway and method per country. If one method fails far more often, move it down the list.
How should refunds work for courses and tuition?
Write the refund policy into the product as rules, then route each refund back through the provider that took the money. Common rules are a full refund within a set number of days if less than a set share of the course was viewed, and pro-rated refunds for unused months of tuition.
- Refund to the original method. The provider interface above returns either a refund reference or "manual". Not every rail exposes a refund API to every merchant, so check each gateway's documentation and your merchant agreement, and send manual cases to a finance queue with an audit trail.
- Revoke access in the same transaction as recording the refund, so a refunded learner does not keep the course.
- Record fees separately. Some gateways do not return their fee on refund; finance needs the net figure per course.
Consumer refund rights depend on the country. This is general information; confirm with your adviser.
What does a multi-gateway EdTech checkout take to build yourself?
For an experienced backend developer, roughly 1 to 2 weeks per gateway for start, confirm, reconcile and refund paths with sandbox tests, plus 1 to 2 weeks for the shared interface, invoices and instalments. The main risk is not the happy path: it is double charges and unpaid access from late callbacks, which only appear under real traffic unless you test them deliberately.
Card data should stay on each gateway's hosted page or SDK, never on your servers; confirm your card-security obligations with your acquirer.
Why RAITHub for this
- Nine gateways in one EdTech product. PadhAI, the AI tutoring platform RAITHub built, supports 9 payment gateways behind one platform of 11 services. Its cost side, the 70/20/10 model router, is explained in LLM model routing.
- Bangladeshi rails in production. TheSkinProof, the founder's own venture, runs bKash, Nagad, SSLCommerz and cash on delivery, with 217 API endpoints and 750+ automated tests.
- QA-first money paths. Late callbacks, double notifications and amount mismatches are covered by automated tests gated in CI.
- Your controls, your accounts. We sign NDAs and DPAs and work inside your controls; production and financial data stay in your own cloud account, and development uses synthetic data.
When you don't need us
- You sell a few video courses to card holders. A course platform such as Teachable is faster and cheaper to start.
- You sell in one country with one gateway. The gateway's own payment pages or a maintained plugin, plus a developer for access control, may be enough.
- You need a merchant account, licence or tax advice. That is between you, the gateway and your adviser.
How RAITHub would build this
- Scope: a provider interface with the gateways for your launch countries; invoices, instalment schedules and access rules tied to enrolments; a reconciliation job for pending attempts with safe retries; refunds by policy, with a manual queue where a rail has no API; per-method fee and success reporting for finance.
- Timeline: a single-country checkout inside a new MVP or SaaS build fits the 4 to 6 week fixed-scope range; a multi-country payment layer for an existing platform is backend and API work, 6 to 12 weeks depending on the number of gateways.
- You receive: automated tests and CI covering every rail's success, failure, cancel and late-callback paths; handover docs and runbooks for reconciliation and refunds; and full IP under NDA.
- Next step: a free 15-minute technical audit, then a written fixed quote.
See what RAITHub builds for education on the EdTech industry page, past work on our work page, and the SaaS development service. To plan yours, book the free 15-minute technical audit with your countries, price points and payment methods.
Last reviewed: 2 October 2026. Stripe, Razorpay, Midtrans and Teachable pages checked on 2 October 2026.
Frequently asked questions
How should I accept course payments in Bangladesh?
Offer bKash and Nagad wallets plus an aggregator such as SSLCommerz for cards, and confirm every payment server to server before granting access. Stripe does not list Bangladesh on its availability page.
Can one checkout handle bKash, Razorpay, Paystack and M-Pesa?
Yes, if each gateway sits behind the same start, status and refund interface and a router picks one by country and method. RAITHub has shipped bKash, Nagad and SSLCommerz; the others are scope we would build to each provider's documentation.
Why are payment fees so high on cheap courses?
Many local methods charge a flat fee. On Midtrans list prices, a IDR 4,000 virtual-account fee is 8% of a IDR 50,000 course, while QRIS at 0.7% costs IDR 350.
How do I offer instalments for online courses?
Create one invoice per instalment at enrolment, unlock modules as each is paid, and set a written grace period. Use automatic recurring charges only where the rail supports them, and payment links elsewhere.
How do I stop double charges when a wallet payment times out?
Query the provider for the previous attempt's status before starting a new one, and only retry if it failed. A scheduled job should also reconcile attempts still marked pending.
How long does it take to build a multi-gateway EdTech checkout?
Doing it yourself, roughly 1 to 2 weeks per gateway plus 1 to 2 weeks of shared work. RAITHub scopes a multi-country payment layer as backend work of 6 to 12 weeks, with a fixed written quote after a free audit.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.