Manual Bank Transfer
Bank transfers with payment-proof upload
PG Medusa Transfer Manual by Haus Kayu
Manual bank transfer with payment-proof upload and admin verification for Medusa v2.
Terima transfer bank dengan upload bukti dan verifikasi di Admin Medusa. Dibuat dan dipakai sendiri oleh Haus Kayu.
Status: 0.1.0, ready for a first release; tested on Medusa 2.19 and 2.22. Coming soon to hauskayu.com.
Payment page (storefront) Verification in the Admin
English · Bahasa Indonesia
English
What it does
- Bank accounts managed in the Admin (Settings → Manual transfer): bank name, account holder, account number, SWIFT/BIC, bank address, currency, regions, active/inactive, order. Several accounts can be active; the first active one matching the cart's currency and region is used.
- Placing the order authorizes the payment: Medusa creates the order and reserves stock, the payment waits for the buyer's transfer (Copy to clipboard
awaiting_payment). - Unique payment reference per order, e.g. Copy to clipboard
MT-7K3QX-9PA2B, guaranteed unique by a database index, with a check character that catches typos. - Payment history on the order page (Admin widget), including a warning when the account chosen at checkout was deactivated before the order was placed and another account was given to the buyer.
- Payment deadline in business days (default 3), counted in your time zone and skipping your holidays.
- Capture only through admin confirmation: Medusa's own "Capture" button (Payments section of the order) is not used for bank transfers and is refused. An admin confirms the transfer (amount received, date, bank reference) after checking the bank statement, and only then is the payment captured.
- The account details are copied into each order when it is placed, so later edits only apply to new orders.
- Payment-proof upload: JPG, PNG or PDF up to 5 MB, recognised by the file's content (not its name). Logged-in customers upload from their order; guests use a secret upload token. Proofs are stored privately in the database and admins open them through 5-minute signed links. After a rejection the buyer can upload again. A file already sent for another order is flagged. Uploads are rate limited per IP and per order.
- Verification queue (sidebar → Manual transfers): tabs per status (needs verification, awaiting payment, underpaid, rejected, paid, expired, canceled, all) with counts, search by reference, e-mail or Copy to clipboard
#order number. - Actions on the order page (widget): Confirm paid (amount received, date, bank reference, note; a red warning and an explicit "the store absorbs the difference" tick when less than the amount due arrived), Underpaid (records a partial transfer and shows what is left to pay; the buyer can upload a proof for the rest) and Reject for a pending proof (reason required, shown to the buyer). Every action is in the payment history with the admin's name, time and note. Each form carries the warning check the bank statement, never rely on the proof alone.
- Statuses: awaiting payment, awaiting verification, paid, underpaid, rejected, expired (and canceled).
- Reminder 24 hours before the deadline (once per payment) and auto-cancel when the deadline passes: the order is canceled with Medusa's own workflow, so the stock reservation is released. Payments with a proof waiting for review or with part of the money received are left for an admin. Both run every 15 minutes in the worker.
- Proof retention (optional): delete proof files some days after the order was paid or canceled, keeping the records.
- Late badge and admin digest: a proof waiting for verification more than 1 business day, or an underpayment without follow-up for more than 3 business days, shows a red "Late" badge in the queue (per row and per tab) and is listed once in a digest e-mail to the admins. Both thresholds are options.
- E-mails for every status change, to the buyer and to the admins, through Medusa's Notification Module (any e-mail provider): order placed with the payment instructions and the pro-forma invoice attached, proof received, proof rejected (with the reason), underpaid (what is left), paid, reminder, expired, canceled. Default templates in English and Indonesian; replace any of them with your own HTML files.
- Pro-forma invoice PDF: store name, logo and address, order number, reference, items and totals, bank details and deadline. Attached to the order e-mail, downloadable by the buyer and from the order page; the layout can be replaced. Set in an embedded Noto Sans (open license, Latin subset), so names like "José Müller-Ñúñez" keep their accents; Copy to clipboard
invoice.fontPathuses your store's font instead. - The e-mails and the invoice carry your store's identity only (Settings → Manual transfer → Store details).
- Storefront pages for the Medusa DTC Starter and the Medusa Next.js Starter (in the npm package): payment instructions with copy buttons, exact amount, reference, countdown, the "fees: OUR" note and the invoice download, plus the proof upload with its status. English and Indonesian. A starter-independent core (React + Fetch) makes other storefronts easy. See Storefront.
Install
1npm install medusa-payment-manual-transfer
Copy to clipboardmedusa-config.ts:
1234567891011121314151617181920module.exports = defineConfig({// …plugins: [{resolve: "medusa-payment-manual-transfer",options: {// secret: "…", // signs guest upload tokens and proof links; default MANUAL_TRANSFER_SECRET, then http.jwtSecret// trustedProxySecret: "…", // the storefront sends the buyer's IP with it; default MANUAL_TRANSFER_PROXY_SECRETshowCredit: true, // the small plugin credit under the plugin's Admin pagesproofRetentionDays: null, // e.g. 365: delete proof files that long after the order was paid or canceledreminderHours: 24, // reminder event this many hours before the deadlineautoCancelExpired: true, // cancel orders whose deadline passed without paymentadminReminders: {verificationBusinessDays: 1, // "Late" when a proof waits longer than thisunderpaidBusinessDays: 3, // "Late" when an underpayment has no follow-up for longer than thisemail: true, // digest e-mail to the admins (the badge is always shown)},notifications: {adminEmails: ["orders@your-store.com"], // recommended; unset = every Admin user (logged, shown in Settings)locale: "en", // buyer e-mails: "en" or "id"; an order's metadata.locale wins
Then run Copy to clipboardnpx medusa db:migrate, enable the Copy to clipboardpp_manual-transfer_manual-transfer provider on your regions, and add your bank account in Settings → Manual transfer. For e-mails, configure Medusa's Notification Module with a provider on the Copy to clipboardemail channel (see below).
E-mails and the pro-forma invoice
The plugin sends through Medusa's Notification Module, so any provider on the Copy to clipboardemail channel works (SendGrid, Resend, SES, SMTP plugins, …). Without a Notification Module nothing is sent and nothing fails.
Change Buyer e-mail (template) Admin e-mail Order placed Copy to clipboardorder_placed: instructions, exact amount, reference, deadline, invoice PDF attached Copy to clipboardadmin_status_changed Proof uploaded Copy to clipboardproof_received Copy to clipboardadmin_status_changed, with the bank-statement warning Proof rejected Copy to clipboardproof_rejected, with the reason and the instructions Copy to clipboardadmin_status_changed Underpaid Copy to clipboardpayment_underpaid, with what is left Copy to clipboardadmin_status_changed Paid Copy to clipboardpayment_confirmed Copy to clipboardadmin_status_changed Deadline near Copy to clipboardpayment_reminder – Expired Copy to clipboardpayment_expired Copy to clipboardadmin_status_changed Canceled Copy to clipboardpayment_canceled Copy to clipboardadmin_status_changed Waiting too long for an admin – Copy to clipboardadmin_overdue (one digest)
Every notification has Copy to clipboardcontent: { subject, html, text } rendered by the plugin, Copy to clipboardtemplate: "manual-transfer.<name>" (or your Copy to clipboardtemplateIds), all values in Copy to clipboarddata, and an idempotency key, so a retried event never sends the same e-mail twice. Attachments are base64.
Store identity. The e-mails and the invoice show the store's name (Settings → Store) and the store metadata Copy to clipboardlogo_url, Copy to clipboardaddress, Copy to clipboardsupport_email, Copy to clipboardphone and Copy to clipboardwebsite. Edit them in Settings → Manual transfer → Store details; the Preview menu there opens every e-mail and the invoice rendered with a made-up order.
Your own templates. Put HTML files in Copy to clipboardnotifications.templatesDir: Copy to clipboard<name>.<locale>.html or Copy to clipboard<name>.html (names in the table above, plus Copy to clipboardlayout.html with Copy to clipboard{{{content}}} for the frame). The subject is the file's Copy to clipboard<title>. A file containing Copy to clipboard<html is sent as it is, without the layout. Syntax: Copy to clipboard{{value}} (escaped), Copy to clipboard{{{value}}} (raw), Copy to clipboard{{#value}}…{{/value}} (if / for each), Copy to clipboard{{^value}}…{{/value}} (if not). Values: Copy to clipboardstore.*, Copy to clipboardorder.number, Copy to clipboardorder.customer_name, Copy to clipboardpayment.reference, Copy to clipboardpayment.amount, Copy to clipboardpayment.amount_received, Copy to clipboardpayment.amount_remaining, Copy to clipboardpayment.deadline, Copy to clipboardpayment.account.*, Copy to clipboardpayment.status_label, Copy to clipboardamount_to_pay, Copy to clipboardpayment_url, Copy to clipboardrejection_reason, Copy to clipboardconfirmation.*, Copy to clipboardadmin_order_url, and Copy to clipboardt.* (the default wording).
Admin recipients. Set Copy to clipboardnotifications.adminEmails to the addresses that should get the admin e-mails (new order, proof uploaded, paid, digest, …). Without it they go to every Admin user: the plugin logs a warning once, and Settings → Manual transfer → E-mail notifications lists who receives them right now, plus the Copy to clipboardpaymentUrl the buyer e-mails link to.
Invoice font. The default layout embeds Noto Sans (SIL Open Font License, subset to the Latin scripts, see Copy to clipboardassets/fonts), so accented names and addresses come out intact; only the glyphs used end up in the PDF. For your brand font, or for other scripts, set Copy to clipboardinvoice.fontPath to a TTF/OTF file (relative to the app's working directory) or Copy to clipboard{ regular, bold?, mono? }; bold falls back to regular, mono to the bundled one. An unreadable file logs a warning and the bundled fonts are used. Characters a font has no glyph for lose their accent or become "?".
Your own invoice. Copy to clipboardinvoice.render is a function, or a module path whose default export is one: Copy to clipboard(context, pdfLib) => Uint8Array with the labels, store, items, totals, account and the loaded order in Copy to clipboardcontext and pdf-lib in Copy to clipboardpdfLib. Copy to clipboardawait context.embedFonts(pdf) embeds the same fonts as the default layout.
Testing without a real provider. The package includes a notification provider that writes every e-mail to a folder (Copy to clipboard.html, Copy to clipboard.txt, Copy to clipboard.json and the attachments) instead of sending it. For development only:
123456789101112{resolve: "@medusajs/medusa/notification",options: {providers: [{resolve: "medusa-payment-manual-transfer/providers/email-preview",id: "email-preview",options: { channels: ["email"], dir: "./email-previews" },},],},},
Storefront
The npm package holds the buyer's pages for both Medusa starters, in English and Indonesian:
Starter Install Medusa DTC Starter (current) Copy to clipboardnode node_modules/medusa-payment-manual-transfer/examples/dtc-starter/install.mjs <repo>/apps/storefront Medusa Next.js Starter (deprecated upstream) Copy to clipboardnode node_modules/medusa-payment-manual-transfer/examples/nextjs-starter/install.mjs <storefront>
Each installer copies the core (Copy to clipboardsrc/storefront of the package: React components, Fetch API helpers, labels, no dependency on a starter) to Copy to clipboardsrc/modules/manual-transfer/core/, adds the example's thin, starter-specific layer (page, two route handlers, the place-order button) and makes four small, checked edits (payment method name, place-order button, order-page link, no "paid at" for unpaid transfers). Placing an order leads to Copy to clipboard/{countryCode}/transfer/{orderId}: status, exact amount, bank details and reference with copy buttons, deadline with countdown, the "choose OUR" note, the invoice download and the proof upload. Guests get there with the upload token, logged-in customers with their session. Set Copy to clipboardnotifications.paymentUrl to Copy to clipboardhttps://<storefront>/transfer/{order_id}?token={token} so the e-mails link to the same page.
Another storefront? Use the core directly: src/storefront/README.md. Details per starter: examples/dtc-starter/README.md, examples/nextjs-starter/README.md.
The buyer's IP behind a storefront
Uploads, status reads and token exchanges are limited per IP. Behind a storefront every request comes from the storefront's server, so the examples send the buyer's IP in Copy to clipboardx-mt-client-ip together with a shared secret in Copy to clipboardx-mt-proxy-secret. The backend uses that header only when the secret matches; otherwise it counts the IP of the connection. Copy to clipboardX-Forwarded-For is never read: Medusa sets Express' Copy to clipboardtrust proxy to 1, so anyone reaching the backend directly could pick their own IP with it.
123openssl rand -hex 32# Medusa .env # storefront .env (server-side only, never NEXT_PUBLIC_*)MANUAL_TRANSFER_PROXY_SECRET=<value> MANUAL_TRANSFER_PROXY_SECRET=<same value>
(or the plugin option Copy to clipboardtrustedProxySecret). The storefront takes the buyer's IP from Copy to clipboardX-Real-IP, then the first Copy to clipboardX-Forwarded-For entry, so the proxy in front of the storefront must set them (Vercel does; nginx: Copy to clipboardproxy_set_header X-Real-IP $remote_addr;). Without the secret all storefront traffic shares one IP budget: raise Copy to clipboardproofUploads.perIp then. A reverse proxy in front of Medusa can send the two headers itself, too.
Signing secret (production)
Guest upload tokens and the signed proof links are HMACs. In production set a secret of its own, so rotating the JWT secret does not break the links in e-mails already sent:
123openssl rand -hex 32# .envMANUAL_TRANSFER_SECRET=<the 64 hex characters>
The plugin option Copy to clipboardsecret wins over Copy to clipboardMANUAL_TRANSFER_SECRET, which wins over Copy to clipboardhttp.jwtSecret (the fallback, fine for development). Changing the secret invalidates every upload token and proof link issued before.
Locks (server + worker)
Uploads, rejections, confirmations, underpayments, reminders and the expiry job lock the payment they change. The plugin uses Medusa's Locking Module from the container, which Medusa loads by default. Its default provider is in-memory, which only protects one process: when you run a separate server and worker (or several instances), use a shared provider, e.g.
12345678910modules: [{resolve: "@medusajs/medusa/locking",options: {providers: [{ resolve: "@medusajs/medusa/locking-redis", id: "locking-redis", is_default: true, options: { redisUrl: process.env.REDIS_URL } },],},},]
(Copy to clipboard@medusajs/medusa/locking-postgres works too.) Only if an app disabled the Locking Module altogether does the plugin fall back to a lock kept in its own process, and it logs a warning once.
Proof retention
Proofs are kept forever by default. With Copy to clipboardproofRetentionDays: N, a daily job (03:30) deletes the file contents of proofs whose payment was captured (paid) or canceled more than N days ago. The proof record stays: file name, size, SHA-256, who uploaded it, review and history, so duplicate detection keeps working. The Admin shows "file deleted" instead of the View button, and old signed links answer Copy to clipboard410 Gone.
Admin API
Method Path GET / POST Copy to clipboard/admin/manual-transfer/accounts list / create accounts GET / POST / DELETE Copy to clipboard/admin/manual-transfer/accounts/:id read / update / delete an account GET Copy to clipboard/admin/manual-transfer/orders/:id the order's manual-transfer payment, history and proofs GET Copy to clipboard/admin/manual-transfer/orders/:id/invoice the pro-forma invoice PDF (Copy to clipboard?locale=en|id) GET Copy to clipboard/admin/manual-transfer/payments the verification queue: Copy to clipboard?status=&q=&limit=&offset=, with Copy to clipboardcounts and Copy to clipboardoverdue_counts per status, Copy to clipboardoverdue per row GET Copy to clipboard/admin/manual-transfer/previews/:name an e-mail (template name) or Copy to clipboardinvoice, rendered with a made-up order POST Copy to clipboard/admin/manual-transfer/payments/:id/confirm confirm a transfer and capture the payment POST Copy to clipboard/admin/manual-transfer/payments/:id/underpaid record a partial transfer (status Copy to clipboardunderpaid) GET Copy to clipboard/admin/manual-transfer/config Copy to clipboard{ show_credit, proof_retention_days, notifications: { active, enabled, admin_recipients: { source, emails }, payment_url } } for the Admin pages GET Copy to clipboard/admin/manual-transfer/proofs/:id/url a signed link to view a proof, valid 5 minutes POST Copy to clipboard/admin/manual-transfer/proofs/:id/reject reject a pending proof, Copy to clipboard{ reason } required
Copy to clipboardconfirm takes Copy to clipboard{ amount_received, received_at: "YYYY-MM-DD", bank_reference?, note?, accept_shortfall? }. Partial transfers recorded with Copy to clipboardunderpaid count towards the amount due; it is refused when less than that arrived in total, unless Copy to clipboardaccept_shortfall is Copy to clipboardtrue. Copy to clipboardunderpaid takes the same fields without Copy to clipboardaccept_shortfall and is refused when the transfer covers the whole amount (confirm instead).
Always check the bank statement in your banking app before confirming. Never rely on a proof image alone.
Store API (payment proofs)
All need the publishable API key. The buyer is either the logged-in customer who placed the order (session or bearer) or anyone holding the order's upload token (Copy to clipboardx-manual-transfer-token header, or Copy to clipboard?token=).
Method Path GET Copy to clipboard/store/manual-transfer/orders/:id instructions (reference, amount, account, deadline), status, Copy to clipboardamount_received / Copy to clipboardamount_remaining, Copy to clipboardcan_upload, proofs GET Copy to clipboard/store/manual-transfer/orders/:id/invoice the pro-forma invoice PDF (Copy to clipboard?locale=en|id) POST Copy to clipboard/store/manual-transfer/orders/:id/proofs multipart: Copy to clipboardfile (JPG/PNG/PDF ≤ 5 MB), optional Copy to clipboardnote GET Copy to clipboard/store/manual-transfer/carts/:id/upload-token right after checkout (24 h): trade the completed cart id for Copy to clipboard{ order_id, token }
Anyone else gets Copy to clipboard404. Too many requests get Copy to clipboard429 with Copy to clipboardRetry-After. Upload tokens are HMAC-derived from the order id, so the e-mails can always rebuild the link (Copy to clipboard{token} in Copy to clipboardpaymentUrl); they only work while the payment accepts proofs.
The per-IP limit is kept in memory per server process; the per-order limit is counted in the database. Which IP counts: see The buyer's IP behind a storefront. Run several server instances? Use a shared locking provider (e.g. Copy to clipboard@medusajs/medusa/locking-redis), since proof uploads, rejections and confirmations lock the payment.
Payment data
Copy to clipboardpayment.data (and the payment session's data) holds Copy to clipboardreference, Copy to clipboardaccount (the copied account details), Copy to clipboardamount, Copy to clipboardcurrency_code, Copy to clipboardstatus (Copy to clipboardawaiting_payment, Copy to clipboardawaiting_verification, Copy to clipboardpaid, Copy to clipboardunderpaid, Copy to clipboardrejected, Copy to clipboardexpired, Copy to clipboardcanceled), Copy to clipboardauthorized_at, Copy to clipboardexpires_at, Copy to clipboardhistory, Copy to clipboardreceipts (partial transfers), Copy to clipboardreminded_at, Copy to clipboardadmin_reminded_at and, once confirmed, Copy to clipboardconfirmation. The provider rebuilds it from these keys at every step, so data a storefront sends when creating the session is ignored.
Events for notifications: Copy to clipboardmanual_transfer.proof_uploaded and Copy to clipboardmanual_transfer.proof_rejected (Copy to clipboard{ id, order_id }), Copy to clipboardmanual_transfer.payment_confirmed and Copy to clipboardmanual_transfer.payment_underpaid (Copy to clipboard{ id } of the payment), Copy to clipboardmanual_transfer.payment_reminder (Copy to clipboard{ id, order_id, expires_at }), Copy to clipboardmanual_transfer.payment_expired (Copy to clipboard{ id, order_id }) and Copy to clipboardmanual_transfer.admin_overdue (Copy to clipboard{ payment_ids }). The plugin's own subscriber turns them (and Copy to clipboardorder.placed / Copy to clipboardorder.canceled) into the e-mails above.
Development
Copy to clipboardintegration-tests/ holds the E2E suite, the test app and the storefront run (Copy to clipboardstorefront.mjs: both examples installed in pinned starter checkouts, checkout to "paid" in Chromium): see integration-tests/README.md and CONTRIBUTING.md. Copy to clipboardnpm run email:preview -- <dir> renders every e-mail and the invoice with a made-up order, without Medusa. Changes: CHANGELOG.md. Security reports: SECURITY.md. More screenshots: docs/screenshots.
Roadmap
- Storage adapter for proofs (not built yet): proofs live in the database today, which is private by construction and needs no setup. A pluggable adapter for a private bucket (S3, Cloudflare R2, …) with short-lived signed URLs is planned for stores with many or large proofs. Medusa's File Module is deliberately not used: with Copy to clipboard
file-local, "private" files land under the public Copy to clipboardstatic/folder.
Bahasa Indonesia
Fitur
- Rekening bank dikelola dari Admin (Settings → Manual transfer): nama bank, nama penerima, nomor rekening, SWIFT/BIC, alamat bank, mata uang, region, aktif/nonaktif, urutan. Rekening aktif pertama yang cocok dengan mata uang dan region keranjang yang dipakai.
- Order dibuat saat checkout: pembayaran di-authorize sehingga order tercipta dan stok ditahan, status menunggu pembayaran (Copy to clipboard
awaiting_payment). - Kode referensi unik per order, mis. Copy to clipboard
MT-7K3QX-9PA2B, dijamin unik oleh index database, dengan karakter cek untuk menangkap salah ketik. - Riwayat pembayaran di halaman order (widget Admin), termasuk peringatan kalau rekening pilihan saat checkout dinonaktifkan sebelum order dibuat dan pembeli diberi rekening lain.
- Batas waktu pembayaran dalam hari kerja (default 3), dihitung di zona waktu toko dan melewati hari libur.
- Capture hanya lewat konfirmasi admin: tombol "Capture" bawaan Medusa ditolak. Admin mengonfirmasi transfer (nominal diterima, tanggal, ref bank) setelah mengecek mutasi, baru pembayaran di-capture.
- Detail rekening disalin ke setiap order, jadi perubahan rekening hanya berlaku untuk order baru.
- Upload bukti transfer: JPG, PNG, atau PDF maksimal 5 MB, dicek dari isi file (bukan namanya). Pelanggan yang login mengunggah dari halaman order; tamu memakai token rahasia. Bukti disimpan privat di database dan admin melihatnya lewat link bertanda tangan yang berlaku 5 menit. Setelah ditolak, pembeli bisa upload ulang. File yang sama yang pernah dikirim untuk order lain ditandai. Upload dibatasi per IP dan per order.
- Antrean verifikasi (sidebar → Transfer manual): tab per status (perlu verifikasi, menunggu pembayaran, kurang bayar, ditolak, lunas, kedaluwarsa, dibatalkan, semua) dengan jumlahnya, cari lewat referensi, email, atau Copy to clipboard
#nomor order. - Aksi di halaman order (widget): Konfirmasi lunas (nominal diterima, tanggal, ref bank, catatan; peringatan merah dan centang "toko menanggung selisih" kalau kurang dari tagihan), Kurang bayar (mencatat transfer sebagian dan menghitung sisa; pembeli bisa upload bukti untuk sisanya), dan Tolak bukti (alasan wajib, dilihat pembeli). Semua aksi tercatat di riwayat dengan nama admin, waktu, dan catatan, dan setiap form membawa peringatan cek mutasi di aplikasi bank, jangan hanya percaya bukti.
- Tombol "Capture" bawaan Medusa tidak dipakai untuk transfer bank dan akan ditolak; capture terjadi lewat Konfirmasi lunas.
- Pengingat 24 jam sebelum batas waktu dan auto-cancel saat kedaluwarsa: order dibatalkan dengan workflow Medusa sehingga stok kembali. Pembayaran yang buktinya menunggu dicek atau yang sudah dibayar sebagian dibiarkan untuk admin. Keduanya jalan tiap 15 menit di worker.
- Masa simpan bukti (opsional, Copy to clipboard
proofRetentionDays): isi file bukti dihapus N hari setelah order lunas atau dibatalkan; data buktinya (nama, ukuran, hash, riwayat) tetap disimpan. Default Copy to clipboardnull= simpan selamanya. - Badge "Terlambat" dan email ringkasan ke admin: bukti yang menunggu verifikasi lebih dari 1 hari kerja, atau kurang bayar tanpa tindak lanjut lebih dari 3 hari kerja, diberi badge merah di antrean (per baris dan per tab) dan dikirim sekali dalam email ringkasan ke admin. Ambangnya bisa diatur (Copy to clipboard
adminReminders). - Email untuk setiap perubahan status, ke pembeli dan admin, lewat Notification Module Medusa (provider email apa pun): order dibuat (instruksi + invoice PDF terlampir), bukti diterima, bukti ditolak (dengan alasan), kurang bayar (sisa tagihan), lunas, pengingat, kedaluwarsa, dibatalkan. Template default bahasa Inggris dan Indonesia (Copy to clipboard
notifications.locale: "id", atau Copy to clipboardmetadata.localeorder), bisa diganti dengan file HTML sendiri (Copy to clipboardnotifications.templatesDir). - Invoice PDF pro-forma: nama, logo, alamat toko, nomor order, referensi, barang dan total, detail rekening, batas waktu. Terlampir di email order, bisa diunduh pembeli dan dari halaman order; layout-nya bisa diganti (Copy to clipboard
invoice.render). Font Noto Sans (lisensi terbuka, subset Latin) disematkan sehingga nama seperti "José Müller-Ñúñez" tetap utuh; Copy to clipboardinvoice.fontPathuntuk memakai font toko sendiri. - Penerima email admin: isi Copy to clipboard
notifications.adminEmails(disarankan). Kalau kosong, semua user Admin menerimanya; plugin menulis peringatan di log dan Settings → Manual transfer → Notifikasi email menampilkan siapa saja penerimanya saat ini serta Copy to clipboardpaymentUrlyang dipakai di email pembeli. - Halaman storefront untuk Medusa DTC Starter dan Medusa Next.js Starter (ikut paket npm): halaman instruksi (detail rekening dengan tombol salin, nominal persis, kode referensi, hitung mundur, catatan biaya OUR, unduh invoice) dan upload bukti dengan statusnya, teks EN + ID. Intinya (Copy to clipboard
src/storefront: komponen React, fungsi API, label) tidak bergantung starter. Pasang dengan Copy to clipboardnode node_modules/medusa-payment-manual-transfer/examples/dtc-starter/install.mjs <repo>/apps/storefront(atau Copy to clipboardexamples/nextjs-starter/install.mjs), lalu isi Copy to clipboardnotifications.paymentUrldengan Copy to clipboardhttps://<storefront>/transfer/{order_id}?token={token}. Detail: examples/dtc-starter/README.md. - Email dan invoice hanya memakai identitas toko Anda (Settings → Manual transfer → Detail toko). Menu Pratinjau di sana membuka setiap email dan invoice dengan order contoh.
- Tes email tanpa provider asli: provider Copy to clipboard
medusa-payment-manual-transfer/providers/email-previewmenulis setiap email ke folder (HTML, teks, JSON, lampiran) alih-alih mengirimnya. Hanya untuk development.
Secret produksi: buat dengan Copy to clipboardopenssl rand -hex 32 lalu isi Copy to clipboardMANUAL_TRANSFER_SECRET di Copy to clipboard.env. Tanpa itu plugin memakai Copy to clipboardhttp.jwtSecret (cukup untuk development). Mengganti secret membatalkan semua token upload dan link bukti yang sudah terkirim.
IP pembeli di balik storefront: batas per IP memakai header Copy to clipboardx-mt-client-ip dari storefront hanya kalau Copy to clipboardx-mt-proxy-secret cocok dengan Copy to clipboardMANUAL_TRANSFER_PROXY_SECRET (atau opsi Copy to clipboardtrustedProxySecret); selain itu IP koneksi. Copy to clipboardX-Forwarded-For tidak pernah dibaca. Isi secret yang sama di Copy to clipboard.env server storefront (jangan Copy to clipboardNEXT_PUBLIC_*), dan pastikan proxy di depan storefront mengisi Copy to clipboardX-Real-IP.
Lock: plugin memakai Locking Module Medusa dari container. Provider default-nya in-memory (hanya satu proses); kalau server dan worker dipisah, pakai Copy to clipboard@medusajs/medusa/locking-redis atau Copy to clipboardlocking-postgres (contoh di bagian English). Hanya kalau Locking Module dimatikan, plugin memakai lock di prosesnya sendiri dan menulis peringatan.
Roadmap: adapter penyimpanan bukti ke bucket privat (S3, Cloudflare R2, …) dengan signed URL, belum dikerjakan.
Konfigurasi lengkap ada di bagian English di atas.
Selalu cek mutasi di aplikasi bank sebelum konfirmasi, jangan hanya percaya bukti transfer.
Kenapa kami membuatnya
Haus Kayu menjual perkakas dan furnitur jati dari Jepara ke pembeli di AS. Untuk custom furniture dan order besar, pembeli lebih nyaman transfer langsung ke rekening USD kami daripada membayar dengan kartu.
Plugin lain dari Haus Kayu
- PG Medusa Doit.id: github.com/haustex/pg-medusa-doit, npm Copy to clipboard
medusa-payment-doit
About Haus Kayu
Haus Kayu: teak tools and furniture from Jepara, Indonesia.
Support
- E-mail: dev@hauskayu.com
- WhatsApp: chat with us
License
MIT © Haus Kayu
