doit.id payment gateway (QRIS, Virtual Account, bank transfer) for Medusa v2 — by Haus Kayu
Terima QRIS, VA, dan transfer di Medusa lewat doit.id. Dibuat dan dipakai sendiri oleh Haus Kayu.
Accept QRIS, Virtual Account and bank transfer in Medusa v2 through doit.id. Built and used in production by Haus Kayu.
Community plugin, not an official doit.id product. Independent open source (MIT), not affiliated with or endorsed by doit.id. Plugin komunitas, bukan produk resmi doit.id. Open source independen (MIT), tidak berafiliasi dengan doit.id.
| Admin dashboard | Storefront (Next.js Starter) |
|---|---|
More: settings · withdrawals · needs action · Bahasa Indonesia UI · VA on mobile · order confirmed
:
Then run the migrations and set the encryption key:
Finally enable doit.id () as a payment provider in your IDR region.
Open Settings → doit.id in the Admin:
Lookup order for credentials: Admin → provider (, ) → env (, ). The secret always comes from the same source as the key.
| Env var | Purpose |
|---|---|
| 64 hex chars (). Required to store credentials in the Admin. Same value on server and worker; back it up with the database. | |
| , | Fallback credentials when nothing is saved in the Admin. |
| Schedule of the reconciliation job (default ). |
If the encryption key is missing, malformed or different from the one used to save the credentials, the plugin keeps running: the Admin shows "Kunci enkripsi tidak valid, masukkan ulang kredensial." and payments fall back to options or env. After changing the key, re-enter the API key and the secret.
doit.id's payment list and webhooks cover everything on an API key. Use one doit.id app (key) per store, and a separate app with a key for testing and staging. Don't share a key between two shops, or between production and a staging copy.
The plugin still guards against sharing. Every payment it creates carries (this Medusa's store id) and a reference like . Reconciliation (webhooks, the 15-minute job, refunds) only touches payments with this store's marker. These are ignored: no ledger row, never listed under "Needs action":
Payments made by plugin 0.1.0/0.1.1 (no marker yet) are still reconciled while their Medusa session exists. Items that earlier versions raised for payments that are not this store's are dismissed automatically by the next job run.
Ledger rows that earlier versions wrote for such payments are cleaned up once, by the first job run after upgrading to 0.1.3. Rows of other apps or stores, of the other mode, or old rows whose Medusa session is gone are marked excluded, never deleted. Rows linked to an order are always kept. Excluded rows are left out of every dashboard figure; tick "Show excluded" in Transactions (or on the API and the CSV export) to see them with the reason. The optional reference prefix (same page) only makes your payments easy to recognise in the doit.id portal.
Requests with a missing or wrong , or a timestamp more than 5 minutes off, get . Redeliveries are answered and not processed twice. If a webhook is lost, the 15-minute job completes the order.
doit.id sets the fee payer per method in the doit.id portal (Payment Methods). The API has no parameter for it, so the plugin cannot change it. QRIS fees are always paid by the merchant (Bank Indonesia rule).
To keep this transparent, the plugin:
The provider works with any storefront: create a payment session for and optionally pass . The session data contains , , , , and . The order is created when the payment is paid, so the storefront waits for it with two endpoints (publishable key required):
| Endpoint | Returns |
|---|---|
| Active methods with fee and total for this cart, the Admin default. Cached 5 min. | |
| Payment status and : (poll again after ), (paid: complete the cart), , . when the QR image ran out before the invoice: send the shopper to . |
Access rules: the cart must belong to the publishable key's sales channel, and a registered customer's cart answers only to that customer (otherwise ). Only display fields are returned. Rate limits: 40/min per cart and 120/min per IP for the status, 20/min per cart and 30/min per IP for methods (in memory, per server process). doit.id is read at most once per 10 seconds per payment.
Medusa Next.js Starter: copy-in components and a patch in , also shipped in the npm package ().
Switching method creates a new invoice. Switching back to a method whose invoice is still open (same cart, same amount, VA with ≥5 minutes left, QRIS whose QR is still issued) reuses that invoice.
Sandbox payments stay pending. In the sandbox, payments are marked paid with the "Simulasikan lunas" button in the doit.id portal. There is no API for it.
Webhooks don't arrive on localhost. doit.id cannot reach your machine. The storefront polling endpoint and the 15-minute job complete orders anyway; use a tunnel if you want to test webhooks.
The shopper paid but the order is missing. Check doit.id → Needs action in the Admin. A payment whose cart or session no longer exists, or arrived after the order was canceled, is listed there for you to fulfil or refund.
The dashboard balance differs from doit.id. It is an estimate (net of successful payments − refunds − withdrawals). doit.id has no balance API; check the portal before withdrawing.
I see payments of another app or a staging store in doit.id, but not in the dashboard. That is intended: only payments carrying this store's marker are reconciled. Use one doit.id app per store (see above).
Can I use another currency? No. doit.id charges in IDR only, and sessions in other currencies are rejected.
Report vulnerabilities privately: see SECURITY.md.
Tambahkan plugin dan provider di (contoh lengkap di bagian English di atas): berisi dan provider Payment Module .
Lalu aktifkan doit.id () sebagai payment provider di region IDR.
Buka Pengaturan → doit.id di Admin:
Urutan sumber kredensial: Admin → provider (, ) → env (, ). Secret selalu diambil dari sumber yang sama dengan key.
| Env | Fungsi |
|---|---|
| 64 karakter hex (). Wajib untuk menyimpan kredensial di Admin. Sama di server dan worker; backup bersama database. | |
| , | Kredensial cadangan bila belum ada yang disimpan di Admin. |
| Jadwal job rekonsiliasi (default ). |
Kalau kunci enkripsi hilang, rusak, atau berbeda dari kunci saat kredensial disimpan, plugin tetap jalan: Admin menampilkan "Kunci enkripsi tidak valid, masukkan ulang kredensial." dan pembayaran memakai options atau env. Setelah mengganti kunci, isi ulang API key dan secret.
Daftar pembayaran dan webhook doit.id mencakup semua transaksi pada satu API key. Pakai satu aplikasi doit.id (key) per toko, dan aplikasi terpisah dengan key untuk testing dan staging. Jangan berbagi key antara dua toko, atau antara produksi dan salinan staging.
Plugin tetap menjaga kalau key terlanjur dipakai bersama. Setiap pembayaran yang dibuatnya membawa (id store Medusa ini) dan referensi seperti . Rekonsiliasi (webhook, job 15 menit, refund) hanya menyentuh pembayaran dengan penanda toko ini. Yang berikut diabaikan: tidak masuk ledger, tidak pernah masuk "Perlu tindakan":
Pembayaran dari plugin 0.1.0/0.1.1 (belum ada penanda) tetap direkonsiliasi selama session Medusa-nya masih ada. Item yang dulu dibuat versi lama untuk pembayaran yang bukan milik toko ini ditutup otomatis pada job berikutnya.
Baris ledger yang dulu ditulis versi lama untuk pembayaran seperti itu dibersihkan sekali, oleh job pertama setelah upgrade ke 0.1.3. Baris aplikasi/toko lain, mode lain, atau baris lama yang session Medusa-nya sudah tidak ada ditandai dikecualikan, tidak dihapus. Baris yang terhubung ke order selalu dipertahankan. Baris yang dikecualikan tidak dihitung di angka dashboard mana pun; centang "Tampilkan yang dikecualikan" di Transaksi (atau di API dan export CSV) untuk melihatnya beserta alasannya. Prefix referensi (opsional, halaman yang sama) hanya supaya pembayaranmu mudah dikenali di portal doit.id.
Request tanpa yang benar, atau dengan timestamp lebih dari 5 menit, dijawab . Kiriman ulang dijawab tanpa diproses dua kali. Kalau webhook hilang, job 15 menit menyelesaikan order-nya.
doit.id mengatur penanggung biaya per metode di portal doit.id (Metode Pembayaran). API tidak punya parameter untuk itu, jadi plugin tidak bisa mengubahnya. Biaya QRIS selalu ditanggung merchant (ketentuan Bank Indonesia).
Supaya transparan, plugin:
Provider bisa dipakai storefront apa pun: buat payment session dan (opsional) kirim . Data session berisi , , , , , dan . Order baru dibuat setelah lunas, jadi storefront menunggu lewat dua endpoint (wajib publishable key):
| Endpoint | Isi |
|---|---|
| Metode aktif beserta biaya dan total untuk cart ini, plus default Admin. Cache 5 menit. | |
| Status dan : (poll lagi setelah ), (lunas: selesaikan cart), , . bila gambar QR habis sebelum tagihan: arahkan pembeli ke . |
Aturan akses: cart harus milik sales channel publishable key, dan cart customer terdaftar hanya untuk customer itu (selain itu ). Hanya field tampilan yang dikembalikan. Rate limit: status 40/menit per cart dan 120/menit per IP, methods 20/menit per cart dan 30/menit per IP (di memori, per proses server). doit.id dibaca paling sering sekali per 10 detik per pembayaran.
Medusa Next.js Starter: komponen dan patch di , ikut di paket npm ().
Ganti metode membuat invoice baru. Kembali ke metode yang invoice-nya masih aktif (cart dan nominal sama, VA sisa ≥5 menit, QRIS yang QR-nya masih terbit) memakai ulang invoice itu.
Pembayaran sandbox tetap pending. Di sandbox, pembayaran dilunasi lewat tombol "Simulasikan lunas" di portal doit.id. Tidak ada API untuk itu.
Webhook tidak sampai ke localhost. doit.id tidak bisa menjangkau komputermu. Endpoint polling storefront dan job 15 menit tetap menyelesaikan order; pakai tunnel kalau mau mengetes webhook.
Pembeli sudah bayar tapi order tidak ada. Cek doit.id → Perlu tindakan di Admin. Pembayaran yang cart/session-nya sudah tidak ada, atau datang setelah order dibatalkan, tercatat di sana untuk dipenuhi atau direfund.
Saldo dashboard beda dengan doit.id. Itu estimasi (net pembayaran sukses − refund − withdraw). doit.id tidak punya API saldo; cek portal sebelum menarik dana.
Di doit.id ada pembayaran aplikasi lain atau toko staging, tapi tidak muncul di dashboard. Memang begitu: hanya pembayaran dengan penanda toko ini yang direkonsiliasi. Pakai satu aplikasi doit.id per toko (lihat di atas).
Bisa pakai mata uang lain? Tidak. doit.id hanya menerima IDR; session dengan mata uang lain ditolak.
Laporkan celah keamanan secara privat: lihat SECURITY.md.
See CONTRIBUTING.md and CHANGELOG.md.
Using this plugin in your store? Open a pull request to add it here.
Pakai plugin ini di tokomu? Kirim pull request untuk menambahkannya di sini.
Haus Kayu makes handcrafted teak kitchenware and furniture in Jepara, Indonesia. We built this plugin for our own Medusa store and share it so other Indonesian merchants don't have to start from scratch.
Haus Kayu membuat perkakas dapur dan furnitur jati buatan tangan dari Jepara. Plugin ini kami buat untuk toko Medusa kami sendiri dan kami bagikan supaya merchant Indonesia lain tidak perlu mulai dari nol.
For questions about your doit.id account, fees or settlement, contact doit.id directly at care@doit.id.
Untuk pertanyaan soal akun doit.id, biaya, atau pencairan dana, hubungi doit.id langsung di care@doit.id.
The plugin's own Admin pages show a small "PG Medusa Doit.id · by Haus Kayu" footer with a help link. It is plain links only: no tracking, no scripts, and nothing is shown on your storefront or checkout. Turn it off with:
The disclaimer stays visible. / Disclaimer tetap tampil.
PG Medusa Doit.id is a community plugin, not an official doit.id product, and is not affiliated with or endorsed by
doit.id. "doit.id" is used only to describe compatibility. Test with a sandbox key before going live.
PG Medusa Doit.id adalah plugin komunitas, bukan produk resmi doit.id, dan tidak berafiliasi dengan doit.id.
Tes dengan key sandbox sebelum dipakai live.
MIT © Haus Kayu
npm install medusa-payment-doitnpm install medusa-payment-doit1module.exports = defineConfig({2 // …3 plugins: [{ resolve: "medusa-payment-doit", options: {} }],4 modules: [5 {6 resolve: "@medusajs/medusa/payment",7 options: {8 providers: [9 {10 resolve: "medusa-payment-doit/providers/doit",11 id: "doit",12 options: {13 // all optional; the Admin settings take precedence14 // rail: "qris", va_bank: "bmri", expires_in: 86400, late_payment_policy: "accept",15 },16 },17 ],18 },19 },20 ],21})1npx medusa db:migrate2echo "DOIT_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env # same value on server and workernpm install medusa-payment-doit1npx medusa db:migrate2echo "DOIT_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env # nilai sama di server dan worker1npm ci2npm run lint && npm run typecheck && npm run test:unit && npm run build3# E2E (Postgres + Redis on the spike ports, see integration-tests/README.md)4DATABASE_URL=postgres://postgres:spike@127.0.0.1:55432/doit_e2e REDIS_URL=redis://127.0.0.1:56379 npm run test:e2e1// medusa-config.ts2plugins: [{ resolve: "medusa-payment-doit", options: { showCredit: false } }]