Key Takeaway
In Indonesia only linked cards and e-wallets can be charged automatically: Midtrans's Subscription API bills saved cards and GoPay, Xendit Subscriptions bills any channel that supports merchant-initiated transactions, and QRIS, which neither can charge automatically, needs a fresh payment link every cycle. Model the subscription as an explicit state machine of active, past due and canceled, and treat only signed, idempotent webhooks as proof that a cycle was paid.
Charging a card once is easy. Charging the same customer automatically every month, forever, without a human clicking anything, is a different problem. That is what a subscription is, and in Indonesia it is harder than the Stripe tutorials suggest.
Most Indonesian customers do not pay with a saved credit card. They pay with QRIS, GoPay, OVO, DANA, ShopeePay, or a bank transfer. Most of these rails are built for one-off payments, not for a merchant that silently pulls funds next month. Building SaaS billing here means knowing which local rails support recurring charges, and engineering around the ones that do not.
Why Recurring Billing Is Hard Here
Card penetration in Indonesia is low, while QRIS keeps growing: Bank Indonesia counted 65.77 million QRIS users and 44.86 million QRIS merchants by June 2026. Recurring billing assumes the opposite: a stored instrument the merchant can charge again without the customer present. When the method a customer reaches for is a QR code they scan fresh each time, the card-on-file model that Western SaaS relies on simply does not map.
The result is that you cannot just copy a Stripe subscription flow. You need a gateway that supports merchant-initiated transactions on local rails, a way to tokenize whatever instrument the customer does have, and a fallback plan for the large share of users whose only payment method is inherently one-time.
- Credit card ownership is a minority behavior, so a card-on-file default excludes most of your potential subscribers.
- E-wallets can be linked for merchant-initiated charges, but only through specific products: Midtrans's Subscription API takes GoPay alongside cards, and Xendit lists GoPay (through a separate recurring channel that needs partner activation), OVO, DANA and ShopeePay as supporting them.
- QRIS, the national QR standard, needs the customer to act in their own app for every payment, whether they scan the merchant's code or show their own, so it is not built for silent renewals.
Gateways and Their Recurring Features
Midtrans and Xendit both document recurring billing. Midtrans's Subscription API charges a stored token on a day, week or month interval and can retry a failed charge up to three times, but it accepts only cards and GoPay. Card recurring reuses the One Click token, since Midtrans says recurring and one click are the same feature and differ only in what triggers the charge, and it needs extra bank approval plus a successful 3DS first payment. Published fees shape the choice as much as the APIs do. On 17 October 2026 Midtrans listed QRIS at 0.7%, cards at 2.9% plus Rp 2.000, a flat Rp 4.000 for virtual accounts, GoPay and ShopeePay at 2%, and DANA and OVO at 1.5%, with different e-wallet and QRIS rates for gaming and digital-product companies. Bank Indonesia has set the QRIS MDR at 0% up to Rp 100.000 for all merchants since 1 October 2026, while Midtrans's page still showed 0.7% that day, so confirm which rate your account is billed. The flat virtual-account fee undercuts the card rate on any charge above roughly Rp 69.000.
Xendit offers a Subscriptions product for fixed-amount and usage-based plans on weekly, monthly or yearly schedules. You define the amount, schedule and recovery options for failed payments, including a payment link the customer can use when an attempt fails, and Xendit handles the deductions, emitting webhooks such as recurring.cycle.retrying when an attempt fails and recurring.cycle.succeeded when a cycle clears; it requires at least one payment channel that supports merchant-initiated transactions. QRIS, standardized by Bank Indonesia, is not one of them: neither Midtrans's Subscription API nor Xendit's QRIS channel supports a merchant-initiated charge, so a QRIS payer becomes a manual renewal touchpoint rather than a true recurring instrument.
Whatever gateway you pick, store the gateway's token and subscription id against your own user, never the raw card or wallet credential. Tokenization keeps sensitive data off your servers and is exactly what makes a later merchant-initiated charge possible.
The Subscription State Machine
A subscription is a small state machine, and getting the states and transitions right is most of the work. Model it explicitly rather than inferring status from scattered payment rows.
- Active: the current cycle is paid and the customer has access, with the next charge scheduled at the period end.
- Past due: a renewal charge failed, so access may continue during a grace period while dunning retries run on a backoff schedule.
- Canceled: the customer or an exhausted dunning sequence ended the subscription, and access stops at the end of the paid period, not immediately.
- Proration: when a plan changes mid-cycle, credit the unused portion of the old plan and charge the prorated remainder of the new one.
Reliability: Webhooks and Idempotency
Payments are asynchronous. The customer approves in a wallet app or scans a QR, and your server learns the outcome later through a webhook, not a synchronous API response. Treat the webhook as the source of truth for whether a cycle succeeded, and never mark a subscription active just because you sent a charge request.
Webhooks retry and can arrive more than once or out of order, so every handler must be idempotent: dedupe on the gateway's event or order id and make processing the same event twice a no-op. Midtrans supports an Idempotency-Key header when creating a subscription, so a create request retried with the same key within three minutes returns the same response instead of a duplicate. Reconcile daily by pulling the gateway's transaction list and comparing it against your own ledger to catch webhooks you missed.
Never trust a webhook without verifying its signature, and never flip a user to paid on the request alone. A spoofed or replayed webhook that you process blindly can grant free access or double-charge a customer.
Pitfalls to Plan For
The failure modes here are specific to the local market. Design for them from the start rather than patching after the first failed renewal.
- Failed renewals are normal, because e-wallet balances run dry and cards expire, so a dunning sequence with retries and reminders is mandatory, not optional.
- QRIS and unlinked e-wallet payments are one-time by design, so for those users you cannot silently re-bill: send a renewal notice with a fresh payment link each cycle, which can carry a QRIS code, since Bank Indonesia's remote QRIS lets a merchant share the code in an image or invoice.
- Tax and invoicing matter: if you are a VAT-registered business (PKP), Indonesian VAT, known as PPN, is 12% on a DPP of 11/12 for non-luxury services, and tax invoices have run through Coretax DJP and its connected channels since 1 January 2025, so bake invoice numbering and tax fields into your billing records and issue a proper tax invoice per charge.
SaaS recurring billing in Indonesia is less about the payment API and more about the reality that most customers cannot be silently re-billed. Pick a gateway that tokenizes the instruments your users actually have, model the subscription as an explicit state machine, and make every webhook idempotent and reconciled. Where card-on-file is impossible, turn renewal into a reliable notification-plus-link flow.
Sources and further reading
- Midtrans Docs — One Click, Two Clicks, and Recurring Transaction
- Xendit Docs — How Subscriptions Work
- Bank Indonesia — QRIS: payment modes, remote QRIS and MDR scheme
- Wikipedia — Subscription business model
- Bank Indonesia — 0% QRIS MDR from 1 October 2026 (No. 28/159/DKom)
- Midtrans — published transaction fees per payment method

