Skip to content

Payments for Indonesia

Configure Midtrans, Xendit, and kode unik as explicit deferred-payment methods with verified callbacks and operator-safe settlement.

Indonesian channels are deferred payments: checkout creates an order and returns provider instructions while the order remains pending_payment. A verified provider callback—or an authorized kode-unik operator—settles the payment later.

Pick a provider by channel and operation

Provider Supported checkout channels Settlement signal Refund boundary
Midtrans Core API v2 QRIS, BCA/BNI/BRI VA, Mandiri bill-key, Permata VA, GoPay, Indomaret, Alfamart SHA-512 notification plus a fresh transaction-status query Automatic for QRIS and GoPay; other channels require a recorded manual transfer
Xendit Payment Requests 2024-11-11 QRIS, BCA/BNI/BRI/Mandiri VA, OVO, DANA-compatible redirects Constant-time x-callback-token verification Confirm the current provider/channel contract before enabling refunds
Kode unik IDR bank transfer with a visible 001999 adjustment Authorized operator confirmation Manual bank transfer with an operator-recorded reference

Expose each channel as a separate payment method. This keeps eligibility, labels, instructions, and operational reporting explicit.

Configure Midtrans

pnpm add @adocommercekit/midtrans
node ace configure @adocommercekit/midtrans

Set MIDTRANS_SERVER_KEY, MIDTRANS_CLIENT_KEY, and MIDTRANS_ENVIRONMENT, then register each channel:

import { midtrans } from "@adocommercekit/midtrans"

payments: {
  default: "midtrans_qris",
  methods: {
    midtrans_qris: midtrans({
      serverKey: env.get("MIDTRANS_SERVER_KEY"),
      clientKey: env.get("MIDTRANS_CLIENT_KEY"),
      environment: env.get("MIDTRANS_ENVIRONMENT"),
      channel: "qris",
    }),
    midtrans_bca_va: midtrans({
      serverKey: env.get("MIDTRANS_SERVER_KEY"),
      clientKey: env.get("MIDTRANS_CLIENT_KEY"),
      environment: env.get("MIDTRANS_ENVIRONMENT"),
      channel: "bca_va",
    }),
  },
}

Point Midtrans notifications at <APP_URL>/commerce/webhooks/midtrans. Do not reproduce the signature check in a controller: the driver verifies the notification and re-queries Midtrans before producing a normalized event.

Configure Xendit

pnpm add @adocommercekit/xendit
node ace configure @adocommercekit/xendit

Set XENDIT_SECRET_KEY and XENDIT_WEBHOOK_TOKEN:

import { xendit } from "@adocommercekit/xendit"

payments: {
  default: "xendit_qris",
  methods: {
    xendit_qris: xendit({
      secretKey: env.get("XENDIT_SECRET_KEY"),
      webhookToken: env.get("XENDIT_WEBHOOK_TOKEN"),
      channel: "qris",
    }),
  },
}

Point callbacks at <APP_URL>/commerce/webhooks/xendit. Xendit uses Basic secret-key authentication and the callback token; Midtrans signature rules do not apply.

Configure kode unik

Kode unik adds a small positive adjustment so incoming bank transfers can be distinguished on one receiving account.

import { kodeUnik } from "@adocommercekit/id"

payments: {
  methods: {
    kode_unik: kodeUnik({
      receivingAccount: {
        id: "bca-main",
        bankName: "BCA",
        accountNumber: env.get("COMMERCE_BANK_ACCOUNT_NUMBER"),
        accountName: "PT Example",
      },
      expiresAfterMinutes: 60,
    }),
  },
}

The driver requires IDR, allocates one active code per receiving account, adds that code to the amount due, and returns bank-transfer instructions. Settlement is never inferred from browser input. An operator must pass host authorization to KodeUnikAdminService.confirm() and record the bank statement reference. Rejection and manual refunds also require auditable operator actions.

Checkout and operations contract

  • Return every instruction and expiry supplied by the payment intent. QR payloads, VA numbers, redirect URLs, and exact transfer amounts are not interchangeable.
  • Make pending, paid, expired, rejected, and review-required states visible to the customer.
  • Dispatch expiry and asynchronous work through a durable queue in production.
  • Treat callback bodies, payment instructions, account numbers, tokens, and provider payloads as sensitive. Keep them out of general logs and issue trackers.
  • Make callback handling idempotent. Retries and out-of-order delivery are normal provider behavior.
  • Test one controlled production purchase per enabled channel and every claimed refund path before launch.

Continue

Navigation

Type to search…

↑↓ navigate↵ selectEsc close