Skip to content

Choosing a Gateway

Murai ships with three built-in gateway adapters: Midtrans Snap, Xendit Checkout, and Stripe Checkout. This guide helps you choose the right one for your app.

Feature comparison

FeatureMidtrans SnapXendit CheckoutStripe Checkout
Payment pageHosted popup (Snap)Hosted invoice pageHosted checkout page
GoPayYesNoNo
ShopeePayYesYesNo
OVONoYesNo
DANANoYesNo
QRISYesYesNo
Bank transferYesYes (virtual accounts)Yes (ACH, SEPA)
Credit cardYesYesYes (global)
Webhook verificationSHA512 signature in bodyCallback token in headerStripe-Signature header (HMAC)
SandboxYes (separate dashboard)Yes (same dashboard, test mode)Yes (test mode with test keys)
Minimum payoutIDR 10,000IDR 10,000Varies by currency
API styleREST with Snap tokenREST with invoice URLREST with Session URL
Global coverageIndonesiaIndonesia, Philippines, SEA195+ countries

When to choose Midtrans

  • You need GoPay support (the most popular e-wallet in Indonesia)
  • You prefer the Snap popup experience (payment stays on your domain)
  • Your users are primarily on desktop (Snap popup works well)

When to choose Xendit

  • You need OVO or DANA support
  • You prefer a redirect-based flow (simpler to implement)
  • You want a single dashboard for sandbox and production
  • Your users are primarily on mobile (redirect flow works better)

When to choose Stripe

  • You need global coverage beyond Southeast Asia
  • Your users pay with international credit cards, Apple Pay, or Google Pay
  • You want a single provider for subscriptions, invoicing, and one-time payments
  • You are building a SaaS product targeting US, EU, or global markets

Using multiple gateways

Murai is gateway-agnostic — you can use multiple gateways in the same app:

import {
createMidtransGateway,
createXenditGateway,
createStripeGateway,
createCheckoutManager,
createLedger,
} from '@murai-wallet/murai';
const midtrans = createMidtransGateway({ /* config */ });
const xendit = createXenditGateway({ /* config */ });
const stripe = createStripeGateway({ /* config */ });
const ledger = createLedger(storage);
// Separate checkout managers — same ledger and storage
const midtransCheckout = createCheckoutManager(midtrans, ledger, storage);
const xenditCheckout = createCheckoutManager(xendit, ledger, storage);
const stripeCheckout = createCheckoutManager(stripe, ledger, storage);
// Use the right one based on user preference or region
function pickCheckout(preference: string) {
if (preference === 'gopay') return midtransCheckout;
if (preference === 'ovo' || preference === 'dana') return xenditCheckout;
return stripeCheckout; // default to Stripe for international
}

Implementing a custom gateway

Stripe is now built-in as of v0.4.0. If you need a different payment provider (DOKU, PayMongo, etc.), implement the PaymentGatewayAdapter interface:

import type { PaymentGatewayAdapter } from '@murai-wallet/murai';
const myGateway: PaymentGatewayAdapter = {
async createCheckout(params) {
// Call your gateway's API to create a payment session
// Return a CheckoutSession with redirectUrl
},
async verifyWebhook(payload, signature) {
// Verify the webhook signature
// Return true if valid, false otherwise
},
parseWebhookPayload(payload) {
// Extract orderId, status, and grossAmount from the webhook body
// Return null if the payload is unrecognizable
},
// Optional: poll the gateway for payment status
async getPaymentStatus(id) {
// Return 'success' | 'failed' | 'pending' | 'expired'
},
};

See the Types reference for the full PaymentGatewayAdapter interface.