Merchant MDR Classification Rule
Spec dokumen untuk klasifikasi MDR (Merchant Discount Rate) per merchant — dipakai oleh:
- mobile app saat user submit registrasi (Step Profil Usaha)
- backend
merchant_core_apisaat stampmdr_ratekemerchant_registration_requestsdan carry-over kemerchants - backend transaction handler saat hitung
mdr_fee_amountper transaksi
Status: draft v8 (revisi 2026-05-05). Update: (v5) Tabel MDR QRIS resmi dikonfirmasi dari Siaran Pers RDG Februari 2025 — berlaku efektif 15 Maret 2025. (v6) §3.2 dikoreksi ke PP Nomor 7 Tahun 2021; BESAR ditambah (migration 045); omzet buckets mobile diperluas. (v7) Scoring 3 faktor:
typeScaledariref_business_type.typical_scale_id— migration 046. (v8) MCC sebagai internal scoring engine:suggested_mcc_codedi ref_business_type (migration 047) → MCC check untuk deteksi Khusus, complianceis_allowed, pre-fill admin, QRIS payload tag 52. MCC tidak ditampilkan ke merchant. Algoritma §3.3 diperluas jadi 3 step: skala → MCC check → mdr_classification. Yang masih perlu sign-off: threshold karyawan UMI BI (Q4), mekanisme deteksi sektor khusus (Q3), audit trail (Q5), backfill historis (Q6).
1. Context
1.1 Masalah yang diselesaikan
Sebelum migration 107 (107_merchant_mdr_rate.sql):
merchant.merchantsdanmerchant.merchant_registration_requeststidak punya kolommdr_rate.- Handler INSERT di
merchant_core_api/internal/transactionstatus/store.go:272hardcodemdr_rate = 0,mdr_fee_amount = 0→ mayoritas transaksi tercatat dengan MDR 0% (loss revenue). - Halaman Laporan mobile menampilkan total MDR Rp 942 dari gross Rp 2,472,367,540 (seharusnya ~Rp 17.3 juta @ 0.7%) — confirmation symptom.
Migration 107 menambah kolom mdr_rate numeric(5,4) NOT NULL DEFAULT 0.0070 CHECK (mdr_rate BETWEEN 0 AND 1) di kedua tabel; dokumen ini menetapkan rule BAGAIMANA value tersebut diisi mengikuti aturan BI QRIS.
1.2 Sumber-of-truth
| Layer | Sumber | Catatan |
|---|---|---|
| Master skala usaha | db_reference.business_scales | Range omzet/modal (PP 7/2021) + karyawan (BPS Sensus Ekonomi 2016) |
| Master jenis usaha / sektor | db_reference.business_types (existing) | Diperlukan untuk identifikasi sektor khusus (Pendidikan/SPBU/Government) — belum ada flag sektor MDR khusus, perlu enrichment (lihat §6 Q3) |
| Klasifikasi MDR per merchant (registration) | merchant.merchant_registration_requests.mdr_rate | Stamped saat user submit Step "Profil Usaha" |
| Klasifikasi MDR per merchant (post-approval) | merchant.merchants.mdr_rate | Carry-over dari registration_requests; admin dapat override |
| MDR per transaksi (snapshot) | merchant.transactions.mdr_rate | Dibaca dari merchants.mdr_rate saat INSERT — untuk merchant UMI dihitung kondisional per-transaction-amount (§3.3) |
2. Aturan Resmi BI QRIS
2.1 Klasifikasi MDR menurut BI
Sumber resmi: Tabel MDR QRIS per Siaran Pers RDG Februari 2025, berlaku efektif 15 Maret 2025. MDR ditanggung merchant, tidak boleh dibebankan ke konsumen.
BI QRIS menetapkan dua Jenis Merchant utama: Reguler dan Khusus.
Jenis Merchant: REGULER
| Sub-kategori | Kondisi | MDR |
|---|---|---|
| UMI (Usaha Mikro) | Nominal transaksi ≤ Rp 500.000 | 0% |
| UMI (Usaha Mikro) | Nominal transaksi > Rp 500.000 | 0.3% |
| UKE / UME / UBE | Usaha Kecil, Menengah, Besar — semua nominal | 0.7% |
Jenis Merchant: KHUSUS
| Sub-kategori | Cakupan | MDR |
|---|---|---|
| Pendidikan | Sekolah, kampus, lembaga pendidikan | 0.6% |
| SPBU | Stasiun Pengisian Bahan Bakar Umum | 0.4% |
| BLU / PSO / G2P / P2G | Badan Layanan Umum (BLU), Public Service Obligation (PSO), Government to People / Bansos (G2P), People to Government: pajak, paspor, donasi sosial nirlaba (P2G) | 0% |
Tidak ada MDR di atas 0.7%. 0.7% adalah batas tertinggi skema standar QRIS BI.
Catatan: Kategori
TRANSPORTASI(0.4%) danRUMAH_SAKIT(0.4%) yang ada dimerchant.ref_mcc_code(migration 014) tidak tercantum secara eksplisit dalam tabel resmi BI ini. Kedua kategori tersebut adalah asumsi internal berdasarkan praktik industri — perlu konfirmasi ke acquirer Mandiri apakah RS/transportasi umum mendapat tarif khusus atau masuk Reguler UMI/UKE. Sementara ini data MCC tetap dipertahankan dengan rate 0.4% sebagai best-effort estimate.
2.2 Aturan kondisional UMI (Usaha Mikro)
Merchant berklasifikasi UMI dikenakan MDR berdasarkan nominal transaksi, bukan flat rate:
| Nominal transaksi | MDR |
|---|---|
| ≤ Rp 500,000 | 0% |
| > Rp 500,000 | 0.3% |
Implikasi desain: kolom tunggal
merchants.mdr_ratetidak cukup untuk UMI. Lihat §4.2 untuk skema kolom tambahan + logic per-transaksi.
2.2b Kategori GOVERNMENT — Cakupan Lengkap (Khusus, MDR 0%)
Berdasarkan tabel BI resmi, kategori 0% Khusus mencakup empat sub-tipe:
| Sub-tipe | Singkatan | Contoh |
|---|---|---|
| Badan Layanan Umum | BLU | RSUD, perguruan tinggi negeri, Perum Bulog |
| Public Service Obligation | PSO | Subsidi pemerintah — KAI ekonomi, Damri, Pelni |
| Government to People | G2P | Bantuan Sosial (Bansos) — PKH, BPNT, BLT |
| People to Government | P2G | Pajak (DJP), paspor (imigrasi), donasi sosial nirlaba (ZIS) |
Implikasi kode: kolom
mdr_classificationenum perlu diperluas atauGOVERNMENTdijadikan umbrella untuk keempat sub-tipe ini. Untuk audit trail, disarankan menyimpan sub-tipe (BLU/PSO/G2P/P2G) di field terpisah atau sebagaigovernment_sub_typeVARCHAR di tabelmerchants.
2.3 Skema bagi-hasil (revenue share) — assumed v1
Per konfirmasi business owner (2026-05-05), skema sementara:
| MDR yang dibayar merchant | Bagian Bank (acquirer Mandiri) | Bagian Kesles | Catatan |
|---|---|---|---|
| 0.7% (Reguler — UKE/UME/UBE) | 0.4% | 0.3% | Sumber Kesles share saat ini |
| 0.3% (UMI > Rp 500,000) | TBD | 0% (assumed) | Skema bagi-hasil belum tersedia, sementara asumsikan Kesles tidak dapat share |
| 0% (UMI ≤ Rp 500,000 / Government / bansos) | — | 0% | Tidak ada MDR di-collect, otomatis Kesles share 0 |
| 0.6% (Pendidikan) | TBD | 0% (assumed) | Sama dengan 0.3% — share belum dikonfirmasi |
| 0.4% (SPBU) | TBD | 0% (assumed) | Sama dengan 0.3% — share belum dikonfirmasi |
Implikasi: revenue MDR Kesles hanya non-zero untuk merchant Reguler (klasifikasi 0.7%) saat ini. Inilah alasan §2.4 menetapkan akuisisi target = Reguler 0.7%.
Angka 0.3% Kesles share di qrisplus-selling-strategy-analysis.md, qrisplus-variable-cost-analysis.md line 27, dan merchant-financial-analytics-spec.md:50 merujuk khusus ke kasus MDR 0.7% (Reguler) — Kesles dapat 0.3% dari 0.7% itu (sisanya 0.4% ke Mandiri). Bukan "merchant tier Kecil bayar 0.3%". Untuk tier non-Reguler, Kesles share = 0% sampai skema baru tersedia.
2.4 Target fokus produk Kesles
Saat ini target fokus akuisisi Kesles adalah merchant yang masuk klasifikasi MDR 0.7% (Reguler / UKE / UME / UBE). Tier UMI (Mikro) dan sektor khusus (Pendidikan/SPBU/Government) bukan prioritas akuisisi — onboarding tetap diizinkan tapi revenue share economics belum optimal sebelum skema bagi-hasil tier 0.3%/0.6%/0.4%/0% tersedia.
2.5 Threshold UMI menurut BI (PENTING — beda dari skala UU 20/2008)
Klasifikasi MDR UMI vs Reguler menggunakan threshold BI QRIS, bukan definisi UU 20/2008 yang dipakai master db_reference.business_scales. Indikator UMI per konfirmasi business owner:
| Indikator | Threshold UMI | Sumber |
|---|---|---|
| Omzet tahunan | < Rp 2,000,000,000 / tahun (~ Rp 166,666,667 / bulan) | BI QRIS rule |
| Jumlah karyawan | indikator pelengkap (threshold belum dikonfirmasi numerik) | BI QRIS rule |
Konsekuensi: master
business_scalesPP 7/2021 (Mikro≤ 166.6jt/bulan = 2M/tahun;Kecil166.6jt–1.25M/bulan = 2M–15M/tahun) tidak 1:1 dengan klasifikasi BI UMI. Threshold BI UMI adalah omzet < 2M/tahun — kebetulan sama persis dengan batas atas MIKRO PP 7/2021, sehingga MIKRO (PP 7/2021) ≈ UMI (BI) secara praktis. Merchant ber-skala PP 7/2021Kecil(omzet > 166.6jt/bulan) otomatis masuk Reguler BI (0.7%). Skala PP 7/2021 digunakan untuk display badge kategori usaha; klasifikasi MDR tetap pakai BI threshold terpisah.
Rule penentuan UMI vs Reguler (untuk dipakai di mobile registration + dashboard review):
omzetTahunan = omzetPerBulan × 12
isUMIByRevenue = omzetTahunan < 2_000_000_000
isUMIByEmployees = jumlahKaryawan < <threshold> // belum dikonfirmasi numerik (lihat §6 Q4)
mdrClassification =
isUMIByRevenue AND isUMIByEmployees ? UMI // strict (default rekomendasi)
: REGULAR // higher tier wins (konsisten §3.2)
Rule kombinasi
AND(strict) atauOR(lenient) atauhigher-tier-winsbelum final — lihat §6 Q4. Rekomendasi default: higher tier wins (konsisten dengan tie-break skala usaha) — kalau salah satu indikator menunjukkan Reguler, klasifikasi naik ke Reguler. Lebih konservatif untuk Kesles revenue (tarif 0.7% > 0%/0.3% UMI).
2.6 ⚠️ Diskrepansi — 012_cleanup_payment_cards_mdr_rates.sql (OUTDATED)
Migration 012 di db_reference berisi rate MDR yang sudah tidak sesuai dengan ketentuan BI per 15 Maret 2025. File ini menggunakan skema lama (sebelum update RDG Februari 2025):
| Kategori | Rate di 012 (LAMA) | Rate Resmi BI (15 Mar 2025) | Status |
|---|---|---|---|
Pendidikan (EDUCATION) | 0.30% | 0.60% | ❌ SALAH |
SPBU (FUEL) | 0.30% | 0.40% | ❌ SALAH |
PPOB / Bill Payment (PPOB) | 0.50% | Tidak ada (masuk P2G = 0%) | ❌ KATEGORI TIDAK ADA |
Government / Retribusi (GOVERNMENT) | 0.50% | 0% (P2G) | ❌ SALAH |
| Merchant Mikro (MUK) | Threshold "≤ Rp 500jt/tahun" | Skala UMI per BI QRIS | ⚠️ Definisi berbeda |
Tindakan yang diperlukan: Buat migration 044_fix_mdr_rates_bi_march_2025.sql di db_reference untuk:
- Update
merchant_tier = 'EDUCATION'→ setbiaya_mdr = 0.60 - Update
merchant_tier = 'FUEL'→ setbiaya_mdr = 0.40 - Update
merchant_tier = 'PPOB'→ reklasifikasi keGOVERNMENTdenganbiaya_mdr = 0.00 - Update
merchant_tier = 'GOVERNMENT'→ setbiaya_mdr = 0.00 - Update view
public.v_qris_mdr_matrixjika diperlukan
Catatan: Migration 012 hanya menyentuh tabel
ref_payment_jenis_kartu_psp(data PSP/bank acquirer), bukanmerchant.ref_mcc_code. Tabel MCC (014) sudah menggunakan rate yang benar (Pendidikan 0.60%, SPBU 0.40%). Perbaikan diprioritaskan pada tabel PSP.
3. Penentuan Klasifikasi MDR di Mobile Registration
3.1 Dua master yang BERBEDA — jangan ditukar
| Master | Tujuan | Sumber threshold |
|---|---|---|
db_reference.business_scales | Display "Skala Usaha" merchant | PP 7/2021 (modal & omzet/tahun) + BPS Sensus Ekonomi 2016 (karyawan) — Mikro/Kecil/Menengah/Besar |
| rule kode §2.5 | Penentuan mdr_classification (BI QRIS) | BI QRIS — UMI threshold omzet < 2M/tahun + indikator karyawan |
Mobile Step "Profil Usaha" pakai kedua-duanya untuk tujuan berbeda: skala PP 7/2021 untuk badge "Skala Usaha: Mikro/Kecil/dst" (transparansi), dan threshold BI untuk tentukan mdr_classification.
3.2 Master business_scales (PP 7/2021 + BPS 2016 — display only)
Sumber: PP Nomor 7 Tahun 2021 untuk modal usaha & omzet/tahun. BPS Sensus Ekonomi 2016 untuk range jumlah karyawan (sebagai indikator pelengkap tie-break). Bukan UU 20/2008 (sudah dicabut).
code | name | omzet_per_bulan_min..max (Rp) | jumlah_karyawan_min..max | sort_order |
|---|---|---|---|---|
MIKRO | Mikro | 0 – 166,666,667 | 1 – 4 | 1 |
KECIL | Kecil | 166,666,668 – 1,250,000,000 | 5 – 19 | 2 |
MENENGAH | Menengah | 1,250,000,001 – 4,166,666,667 | 20 – 99 | 3 |
BESAR | Besar | 4,166,666,668+ | 100+ | 4 |
Algoritma resolve finalScale — 3 faktor, higher-tier-wins:
revenueScale= row diref_business_scaleyang rangeomzet_per_bulan_min..maxmatch omzet inputemployeeScale= row yang rangejumlah_karyawan_min..maxmatch karyawan inputtypeScale=ref_business_type.typical_scale_iddari jenis usaha yang dipilih (added migration 046)- Higher-tier-wins:
finalScale = argmax(revenueScale.sort_order, employeeScale.sort_order, typeScale.sort_order) - Output
_BusinessScaleDecision { finalScale, revenueScale, employeeScale, typeScale }ditampilkan di Step Profil Usaha + Ringkasan sebagai kategori usaha, bukan sebagai sumber MDR.
Contoh: merchant melaporkan omzet Rp 100jt/bulan (→ MIKRO), 3 karyawan (→ MIKRO), tapi memilih jenis usaha MINIMARKET (typeScale = KECIL, benchmark Rp 245jt/bulan). Higher-tier-wins → finalScale = KECIL → MDR 0.7% Reguler. Ini mencegah merchant minimarket mendapat MDR UMI 0%/0.3%.
3.2b ref_business_type.typical_scale_id — sumber typeScale
Ditambahkan oleh migration 046_add_typical_scale_to_business_type.sql. Setiap jenis usaha mendapat typical_scale_id berdasarkan benchmark_omzet_per_bulan = benchmark_transactions × benchmark_ticket_size × 30:
| Kategori | Jenis Usaha | Benchmark omzet/bulan | Typical scale |
|---|---|---|---|
| Perdagangan | Minimarket / Convenience Store | Rp 245 jt | KECIL |
| Perdagangan | Toko Pakaian & Aksesoris | Rp 315 jt | KECIL |
| Perdagangan | Apotek / Toko Obat | Rp 228 jt | KECIL |
| Perdagangan | Toko Kosmetik & Perawatan | Rp 222 jt | KECIL |
| Perdagangan | Toko Elektronik & Aksesoris Gadget | Rp 561 jt | KECIL |
| Perdagangan | Toko Kelontong / Sembako | Rp 144 jt | Mikro |
| Perdagangan | Toko Buku / ATK / Fotokopi | Rp 101 jt | Mikro |
| Industri Pengolahan | Restoran / Rumah Makan | Rp 226 jt | KECIL |
| Industri Pengolahan | Toko Kue / Dessert | Rp 188 jt | KECIL |
| Industri Pengolahan | Katering / Pesanan Harian | Rp 258 jt | KECIL |
| Industri Pengolahan | Kedai Kopi / Coffee Shop | Rp 125 jt | Mikro |
| Industri Pengolahan | Bakery / Toko Roti | Rp 134 jt | Mikro |
| Industri Pengolahan | Minuman Kekinian / Juice / Tea | Rp 118 jt | Mikro |
| Industri Pengolahan | Gerai Jajanan / Street Food | Rp 90 jt | Mikro |
| Jasa | Bengkel Motor / Mobil | Rp 144 jt | Mikro |
| Jasa | Servis Gadget / Elektronik | Rp 132 jt | Mikro |
| Jasa | Salon / Barbershop | Rp 89 jt | Mikro |
| Jasa | Studio Foto / Printing | Rp 83 jt | Mikro |
| Jasa | Counter Pulsa / PPOB | Rp 71 jt | Mikro |
| Jasa | Cuci Motor / Mobil | Rp 74 jt | Mikro |
| Jasa | Agen Pengiriman / Paket | Rp 65 jt | Mikro |
| Jasa | Laundry Kiloan | Rp 46 jt | Mikro |
Benchmark adalah estimasi dari data seed — bukan data aktual.
typeScaleberfungsi sebagai floor (batas bawah), bukan ceiling. Merchant minimarket dengan omzet aktual Rp 50jt/bulan tetap di-score KECIL karena typeScale = KECIL. Admin dapat override di Registration Queue (§4.5).
3.2c ref_business_type.suggested_mcc_code — MCC sebagai scoring engine internal
Ditambahkan oleh migration 047_add_suggested_mcc_to_business_type.sql. MCC tidak ditampilkan ke merchant di Step 1, namun dipakai internal untuk tiga fungsi scoring:
| Fungsi | Mekanisme | Layer |
|---|---|---|
| Konfirmasi mdr_category | JOIN suggested_mcc_code → merchant.ref_mcc_code.mdr_category | Backend (auto) |
| Deteksi sektor Khusus | Jika mdr_category IN ('PENDIDIKAN','SPBU','DONASI','BANSOS') → flag mdr_classification_hint = KHUSUS | Backend (auto) |
| Pre-fill admin | Suggested MCC ditampilkan di Registration Queue sebagai rekomendasi awal | Admin review |
| Compliance check | is_allowed = FALSE di ref_mcc_code → block onboarding otomatis | Backend (auto) |
| QRIS payload tag 52 | MCC final (setelah admin konfirmasi) di-stamp ke merchants.mcc_code | Post-approve |
Mapping 22 jenis usaha → MCC primer (semua REGULER untuk tipe usaha saat ini):
| Jenis Usaha | MCC | Deskripsi | mdr_category |
|---|---|---|---|
| Toko Kelontong / Sembako | 5499 | Toko kelontong / makanan lainnya | REGULER |
| Minimarket / Convenience Store | 5411 | Supermarket / Grocery Stores | REGULER |
| Toko Pakaian & Aksesoris | 5651 | Toko pakaian keluarga | REGULER |
| Apotek / Toko Obat | 5912 | Apotek | REGULER |
| Toko Kosmetik & Perawatan | 5977 | Toko kosmetik | REGULER |
| Toko Elektronik & Aksesoris Gadget | 5732 | Toko elektronik | REGULER |
| Toko Buku / ATK / Fotokopi | 5943 | Toko ATK / alat kantor | REGULER |
| Kedai Kopi / Coffee Shop | 5812 | Restoran / rumah makan | REGULER |
| Restoran / Rumah Makan | 5812 | Restoran / rumah makan | REGULER |
| Bakery / Toko Roti | 5462 | Toko roti / bakery | REGULER |
| Minuman Kekinian / Juice / Tea | 5814 | Fast Food Restaurant | REGULER |
| Gerai Jajanan / Street Food | 5812 | Restoran / rumah makan | REGULER |
| Toko Kue / Dessert | 5441 | Toko permen, kacang, confectionery | REGULER |
| Katering / Pesanan Harian | 5811 | Catering | REGULER |
| Laundry Kiloan | 7211 | Laundry keluarga & komersial | REGULER |
| Salon / Barbershop | 7230 | Salon & barbershop | REGULER |
| Bengkel Motor / Mobil | 7538 | Bengkel servis mobil | REGULER |
| Cuci Motor / Mobil | 7542 | Cuci mobil / Car Wash | REGULER |
| Agen Pengiriman / Paket | 4789 | Jasa transportasi lainnya | REGULER |
| Counter Pulsa / PPOB | 4812 | Konter pulsa / telekomunikasi | REGULER |
| Servis Gadget / Elektronik | 7379 | Jasa maintenance komputer | REGULER |
| Studio Foto / Printing | 7221 | Studio foto | REGULER |
Semua 22 jenis usaha saat ini REGULER — tidak ada yang auto-trigger KHUSUS dari MCC. Deteksi Khusus (PENDIDIKAN/SPBU/GOVERNMENT) dilakukan melalui field "Jenis Merchant" yang merchant deklarasi sendiri di Step 1 (Q3 §6). Jika business_type baru ditambahkan untuk sekolah/SPBU/instansi,
suggested_mcc_codeakan otomatis trigger Khusus melaluimdr_categorycheck.
MCC 5122 dilarang (
is_allowed = FALSE) meski terdengar mirip "apotek". Apotek yang benar menggunakan MCC 5912. Backend wajib checkis_alloweddariref_mcc_codesaat onboarding.
3.3 Penentuan mdr_classification (BI threshold)
Algoritma terpisah, hasil di-stamp ke merchants.mdr_classification + merchants.mdr_rate:
// ── Step 1 — Skala usaha (3 faktor, higher-tier-wins) ───────────────────────
revenueScale = lookup ref_business_scale WHERE omzet_per_bulan_min ≤ monthlyRevenue ≤ max
employeeScale = lookup ref_business_scale WHERE jumlah_karyawan_min ≤ employeeCount ≤ max
typeScale = lookup ref_business_scale via ref_business_type.typical_scale_id // mig 046
finalScale = argmax(revenueScale.sort_order, employeeScale.sort_order, typeScale.sort_order)
// ── Step 2 — MCC internal check (tidak terlihat merchant) ───────────────────
mccRow = lookup merchant.ref_mcc_code WHERE mcc_code = businessType.suggested_mcc_code
if mccRow.is_allowed == FALSE:
BLOCK onboarding → return error "Jenis usaha tidak dapat menggunakan QRIS"
mccCategory = mccRow.mdr_category // REGULER | PENDIDIKAN | SPBU | DONASI | BANSOS | FORBIDDEN
// ── Step 3 — MDR classification ─────────────────────────────────────────────
// Merchant deklarasi "Jenis Merchant" di Step 1 (Reguler / Khusus-Pendidikan / dst)
// Backend mengkonfirmasi via mccCategory; admin dapat override di Review Queue
if merchantDeclaredJenisMerchant == "KHUSUS_PENDIDIKAN"
OR mccCategory == "PENDIDIKAN":
mdr_classification = "PENDIDIKAN" // MDR 0.6%
elif merchantDeclaredJenisMerchant == "KHUSUS_SPBU"
OR mccCategory == "SPBU":
mdr_classification = "SPBU" // MDR 0.4%
elif merchantDeclaredJenisMerchant == "KHUSUS_GOVERNMENT"
OR mccCategory IN ("DONASI", "BANSOS"):
mdr_classification = "GOVERNMENT" // MDR 0%
elif finalScale.business_scale_name == "Mikro":
mdr_classification = "UMI" // MDR 0%/0.3% conditional
else: // Kecil / Menengah / Besar
mdr_classification = "REGULAR" // MDR 0.7%
typeScaleadalah floor — hanya bisa menaikkan skala.mccCategoryberfungsi sebagai konfirmator sektor Khusus — jika business_type baru ditambahkan dengan MCC PENDIDIKAN/SPBU, akan otomatis terdeteksi Khusus. Prioritas: deklarasi merchant → konfirmasi MCC → override admin.
3.4 Mapping mdr_classification → merchants.mdr_rate value
mdr_classification | merchants.mdr_rate (snapshot) | Runtime behavior | UI display |
|---|---|---|---|
UMI | 0.0030 (representative) | conditional per transaction (≤500k → 0%, >500k → 0.3%) — lihat §3.5 | "0% transaksi ≤ Rp 500rb, 0.3% transaksi > Rp 500rb (UMI)" |
REGULAR | 0.0070 | flat × gross_amount | "0.7% (Reguler)" |
PENDIDIKAN | 0.0060 | flat × gross_amount | "0.6% (Sektor Pendidikan / Khusus)" |
SPBU | 0.0040 | flat × gross_amount | "0.4% (SPBU / Khusus)" |
GOVERNMENT | 0.0000 | 0 | "0% (BLU / PSO / G2P / P2G / Khusus)" |
| (fallback) | 0.0070 | flat × gross_amount | "0.7% (Reguler)" |
Catatan UI: merchant kategori Khusus sebaiknya menampilkan label "Jenis Merchant: Khusus" sesuai terminologi BI resmi, bukan hanya nama sektor. Ini penting agar merchant memahami konteks regulasi.
Kolom
merchants.mdr_rateuntuk UMI disimpan0.0030sebagai representative rate — angka yang ditampilkan saat audit/admin. Runtime handler tetap menerapkan logic kondisional §3.5 (gross ≤ 500k → 0%).
3.5 Kolom & logic tambahan untuk klasifikasi UMI (PENDING design)
Karena merchants.mdr_rate sebagai single numeric field tidak menangkap rule kondisional UMI, perlu migration 108 (DRAFT) menambah:
ALTER TABLE merchant.merchants
ADD COLUMN IF NOT EXISTS mdr_classification varchar(32) NOT NULL DEFAULT 'REGULAR'
CHECK (mdr_classification IN ('UMI','REGULAR','PENDIDIKAN','SPBU','GOVERNMENT'));
ALTER TABLE merchant.merchant_registration_requests
ADD COLUMN IF NOT EXISTS mdr_classification varchar(32) NOT NULL DEFAULT 'REGULAR'
CHECK (mdr_classification IN ('UMI','REGULAR','PENDIDIKAN','SPBU','GOVERNMENT'));
Transaction handler logic (replacing hardcode mdr_rate = 0 di transactionstatus/store.go:272):
// computeTransactionMdr menghitung tarif MDR dan bagian Kesles per
// transaksi sesuai BI rule + skema bagi-hasil §2.3.
//
// Return:
// - rate : MDR yang dibebankan ke merchant (snapshot ke transactions.mdr_rate)
// - feeTotal : MDR total nominal Rp (snapshot ke transactions.mdr_fee_amount)
// - feeKeslesShare : bagian Kesles dalam Rp (snapshot ke transactions.mdr_fee_kesles)
func computeTransactionMdr(merchant Merchant, grossAmount int64) (rate float64, feeTotal int64, feeKeslesShare int64) {
switch merchant.MDRClassification {
case "UMI":
if grossAmount <= 500_000 {
return 0, 0, 0 // ≤ Rp500k gratis untuk UMI
}
rate = 0.0030
feeTotal = roundIDR(float64(grossAmount) * rate)
return rate, feeTotal, 0 // Kesles share = 0% (assumed)
case "PENDIDIKAN":
rate = 0.0060
feeTotal = roundIDR(float64(grossAmount) * rate)
return rate, feeTotal, 0 // Kesles share = 0% (assumed)
case "SPBU":
rate = 0.0040
feeTotal = roundIDR(float64(grossAmount) * rate)
return rate, feeTotal, 0 // Kesles share = 0% (assumed)
case "GOVERNMENT":
return 0, 0, 0 // 0% MDR
case "REGULAR":
fallthrough
default:
rate = merchant.MdrRate // default 0.0070, admin overridable
feeTotal = roundIDR(float64(grossAmount) * rate)
feeKeslesShare = roundIDR(float64(grossAmount) * 0.0030) // Kesles 0.3% dari 0.7%
return rate, feeTotal, feeKeslesShare
}
}
Catatan implementasi:
feeKeslesShareuntuk Reguler dihitung sebagaigross × 0.003(flat 0.3%), bukanfeeTotal × (0.3/0.7)— tetap konsisten secara aritmatika tapi lebih jelas mengikuti contract: "Kesles dapat 0.3% dari gross". Kalau admin overridemerchants.mdr_ratedi luar 0.7% (mis. promo 0.5%),feeKeslesSharetetap 0.3% kecuali ada kebijakan baru — lihat §6 Q9.
Sektor khusus (PENDIDIKAN/SPBU/GOVERNMENT) butuh mekanisme deteksi — lihat §6 Q3.
4. Implementasi
4.1 Mobile — Step Profil Usaha & Ringkasan
apps/mobile_user/lib/features/merchant/presentation/pages/merchant_registration_page.dart:
static double _mdrRateForScale(String scaleName) {
switch (scaleName.trim().toLowerCase()) {
case 'mikro': return 0.0030; // representative — runtime conditional ≤500k=0%
case 'kecil': return 0.0070; // Reguler UKE
case 'menengah': return 0.0070; // Reguler UME
case 'besar': return 0.0070; // Reguler UBE
default: return 0.0070;
}
}
Status (2026-06-23):
Kecil/Menengah/Besardi kode mobile sudah0.0070(Reguler). Yang masih PENDING:Mikromasih0.0000— perlu update ke0.0030(representative) dengan UI note kondisional. Lihat_mdrRateForScaledimerchant_registration_page.dart.
Ringkasan menampilkan:
- Skala Usaha:
<name> - Klasifikasi MDR:
0.7%(Reguler) /0%–0.3% (UMI)(Mikro) — note dynamic perlu ditambahkan
4.2 Backend — Intake & Validation
merchant_core_api/internal/merchant/merchant.go helper mdrRateForBusinessScale:
func mdrRateForBusinessScale(name string) float64 {
switch strings.ToLower(strings.TrimSpace(name)) {
case "mikro": return 0.0030 // representative — runtime kondisional di transaksi
case "kecil": return 0.0070
case "menengah": return 0.0070
case "besar": return 0.0070
default: return 0.0070
}
}
Status (2026-06-23): nilai backend sudah sesuai BI Rule —
Mikro=0.0030(representative, per-tx kondisional),Kecil/Menengah/Besar=0.0070. LihatmdrRateForBusinessScaledimerchant_core_api/internal/merchant/merchant.go.
4.3 Backend — Persistence (sudah berjalan)
| Path | Tindakan |
|---|---|
CreateMerchantRegistration UPDATE-draft path | SET mdr_rate = $46::numeric |
CreateMerchantRegistration INSERT-new path | column mdr_rate di INSERT |
CreateManualMerchantRegistration (dashboard) | column mdr_rate di INSERT |
loadMerchantRegistrationForUpdateByStatuses | SELECT COALESCE(mrr.mdr_rate, 0.0070) |
ApproveMerchantRegistration INSERT INTO merchant.merchants | column mdr_rate |
4.4 Backend — Per-transaction stamping (Layer 3, PENDING)
Saat ini
transactionstatus/store.go:272masih hardcodemdr_rate = 0. Fix yang akan datang:
- Read
merchants.mdr_rate+merchants.mdr_classification(setelah migration 108).- Apply
computeTransactionMdr(merchant, grossAmount)(§3.5) untuk hasilkan rate + fee per transaksi.- INSERT ke
merchant.transactionsdenganmdr_rate+mdr_fee_amountactual (bukan hardcode 0).- Race condition antara
transactionstatus/store.go(ON CONFLICT DO UPDATEexcludes mdr fields) danpsp_event_receiver.go(ON CONFLICT DO NOTHING) — keduanya harus jalankan logic compute yang sama dan tidak saling menimpa setelah snapshot pertama.
4.5 Dashboard — Registration Queue / Review (PENDING)
Wajib: pengecekan MDR di dashboard Registration Queue saat admin Review (sebelum approve / reject) — agar mismatch antara klasifikasi yang user pilih di mobile vs aturan BI ketahuan sebelum merchant aktif. Skenario contoh: user "Mikro" tapi omzet/tahun > 2M → harus naik ke Reguler 0.7%.
Lokasi UI: panel detail registrasi di Registration Queue. Implementasi saat ini di apps/merchant_dashboard/lib/dashboard/features/home/presentation/widgets/forms/manual_registration_wizard_dialog.dart; saat refactor section ini, ekstrak ke folder dedicated apps/merchant_dashboard/lib/dashboard/features/registration/ (planned, belum dibuat) — tambahkan section "Klasifikasi MDR" sebelum tombol Approve.
Konten section:
-
Submitted values (read-only, dari
merchant_registration_requests):- Skala Usaha (UU 20/2008):
business_scale_name - Omzet/bulan:
average_monthly_revenue - Omzet/tahun (computed):
average_monthly_revenue × 12 - Jumlah karyawan:
employee_count - MDR yang merchant submit:
mdr_rate+mdr_classification
- Skala Usaha (UU 20/2008):
-
Computed-by-rule (dari §3.3 algorithm, server-side recompute):
- Klasifikasi rekomendasi:
UMI/REGULAR/PENDIDIKAN/SPBU/GOVERNMENT - MDR rate rekomendasi:
0.0030/0.0070/0.0060/0.0040/0.0000
- Klasifikasi rekomendasi:
-
Comparison + flag:
- Hijau ✅ kalau submitted value match server compute
- Kuning ⚠️ kalau mismatch ringan (mis. user pilih Mikro tapi server compute UMI — sebenarnya konsisten, hanya beda label)
- Merah ❌ kalau mismatch signifikan (mis. user submit
mdr_rate=0.0030UMI tapi omzet/tahun = 5M = di atas threshold UMI → harusnya REGULAR 0.7%)
-
Override control (admin-only):
- Dropdown final
mdr_classification(UMI/REGULAR/PENDIDIKAN/SPBU/GOVERNMENT) - Input
mdr_ratefinal (numeric, 0..1, validated server-side terhadap CHECK constraint) - Required field:
override_reason(text, min 10 char) — disimpan ke audit history (§6 Q5) - Default value mengikuti server compute, bukan submitted value — admin harus aktif konfirmasi sebelum lanjut.
- Dropdown final
Backend endpoint baru: PATCH /api/dashboard/merchant-registrations/{id}/mdr-classification — body { mdr_classification, mdr_rate, override_reason, reviewed_by_user_id }. Validate: status registrasi pending / pending_review, mdr_rate ∈ [0, 1], override_reason non-empty.
Lifecycle:
- Saat admin click "Review" → fetch detail registrasi → server hitung & return rekomendasi → UI render comparison.
- Admin boleh accept rekomendasi (klik "Pakai Rekomendasi") atau override manual.
- Saat admin click "Approve", values yang aktif (override jika ada, else rekomendasi) di-stamp ke
merchant_registration_requests.mdr_*lalu carry-over kemerchants.mdr_*.
Pencegahan double-stamp: kalau approve dijalankan tanpa Review terlebih dahulu, server tetap apply rekomendasi otomatis (server-side recompute) supaya tidak ada path yang menulis hardcoded value tanpa cek aturan BI.
5. Backfill historis
Setelah Layer 3 + migration 108 deploy, jalankan one-shot backfill:
-- DRAFT — JANGAN dijalankan tanpa review legal/finance
UPDATE merchant.transactions t
SET mdr_rate = CASE
WHEN m.mdr_classification = 'UMI' AND t.gross_amount <= 500000 THEN 0
WHEN m.mdr_classification = 'UMI' THEN 0.0030
WHEN m.mdr_classification = 'PENDIDIKAN' THEN 0.0060
WHEN m.mdr_classification = 'SPBU' THEN 0.0040
WHEN m.mdr_classification = 'GOVERNMENT' THEN 0
ELSE m.mdr_rate
END,
mdr_fee_amount = ROUND(t.gross_amount * (CASE ... same expression ... END), 0)
FROM merchant.merchants m
WHERE t.merchant_id = m.id
AND t.status = 'success'
AND t.mdr_rate = 0
AND t.transaction_at >= '<cut-off date>';
Cut-off date ditentukan saat sign-off (lihat §6 Q5).
6. Open Questions (perlu sign-off)
Angka tier final→ CLOSED: BI rule dikonfirmasi via Siaran Pers RDG Februari 2025, efektif 15 Maret 2025. Struktur Reguler (UMI + UKE/UME/UBE) dan Khusus (Pendidikan 0.6%, SPBU 0.4%, BLU/PSO/G2P/P2G 0%). Tidak ada tier > 0.7%.Tier Besar→ CLOSED: Besar masuk Reguler (UBE) → 0.7%, sama dengan Kecil/Menengah.Mekanisme deteksi sektor khusus→ PARTIALLY CLOSED (2026-05-05): Sektor Khusus per BI adalah: Pendidikan, SPBU, dan BLU/PSO/G2P/P2G (Government). Pilihan mekanisme yang direkomendasikan: (b) manual pick saat registrasi — tambahkan step/field "Jenis Merchant" dengan opsi dropdownReguler/Khusus - Pendidikan/Khusus - SPBU/Khusus - BLU/PSO/G2P/P2G. Default = Reguler. Admin dapat override di Registration Queue (§4.5). Auto-derive daribusiness_typesterlalu kompleks dan rawan salah — lebih aman merchant deklarasi sendiri dengan admin review.- Yang masih open: apakah field "Jenis Merchant Khusus" perlu sub-tipe BLU/PSO/G2P/P2G (untuk audit), atau cukup satu label
GOVERNMENT?
- Yang masih open: apakah field "Jenis Merchant Khusus" perlu sub-tipe BLU/PSO/G2P/P2G (untuk audit), atau cukup satu label
- Threshold karyawan untuk UMI + rule kombinasi — angka karyawan max untuk klasifikasi UMI BI belum dikonfirmasi numerik. Rule kombinasi
(omzet < 2M/thn AND karyawan < threshold)(strict-AND) vs higher-tier-wins (rekomendasi). Rekomendasi: higher-tier-wins — paling conservative untuk Kesles revenue. - Audit trail override —
merchant_mdr_rate_historytable untuk track perubahanmdr_rate+mdr_classificationper merchant + timestamp + actor + reason? - Backfill cut-off — backfill
merchant.transactions.mdr_ratehistoris dari kapan? Dari awal data, atau dari tanggal go-live policy? Skema bagi-hasil tier non-0.7%→ CLOSED (assumed v1): Kesles share = 0% untuk tier 0.3% / 0.6% / 0.4% / 0%. Update saat skema bank tersedia.Fokus akuisisi→ CLOSED: target = merchant Reguler UKE/UME/UBE (MDR 0.7%). UMI & Khusus boleh onboard, revenue Kesles 0% sampai skema baru.- Override
merchants.mdr_ratenon-0.7% — jika admin override ke nilai non-standar (promo, partner agreement), bagaimana skema bagi-hasil Kesles? Tetap 0.3% flat, proporsional(rate − 0.4%), atau kolommdr_kesles_share_rateterpisah? - NEW — Fix migration 012 — Buat
044_fix_mdr_rates_bi_march_2025.sqluntuk koreksi rate Pendidikan (0.3% → 0.6%), SPBU (0.3% → 0.4%), PPOB/Government (0.5% → 0%) di tabelref_payment_jenis_kartu_psp. Lihat §2.6. - NEW — Konfirmasi TRANSPORTASI & RUMAH_SAKIT — Rate 0.4% di
merchant.ref_mcc_codetidak tercantum eksplisit di tabel BI resmi. Perlu konfirmasi ke Mandiri/BI apakah RS dan transportasi umum mendapat tarif Khusus atau Reguler.
Setelah Q4–Q6 + Q9–Q11 dijawab, dokumen di-promote dari draft v5 → v1.0 dan §2.1 + §2.5 + §3.3 + §3.4 + §3.5 + §4.5 di-freeze sebagai contract; migration 108 + migration 032 + update kode mobile/backend/dashboard dieksekusi.
7. Action items pasca-sign-off
- [URGENT] Migration 044 (db_reference): buat
044_fix_mdr_rates_bi_march_2025.sql— koreksi rate diref_payment_jenis_kartu_pspsesuai BI 15 Maret 2025 (lihat §2.6). Ini independent dari migration 108 dan bisa dijalankan segera. - Migration 108: tambah
mdr_classificationdimerchants+merchant_registration_requests(enum checkUMI/REGULAR/PENDIDIKAN/SPBU/GOVERNMENT). - Update mobile (
merchant_registration_page.dart):- Replace
_mdrRateForScale(scale)dengan_resolveMdrClassification(omzetPerBulan, employeeCount, businessTypeCode)yang return tuple(mdrClassification, mdrRate)per §3.3 + §3.4. - Step Profil Usaha + Ringkasan: tampilkan klasifikasi (UMI/Reguler/dst) dengan note kondisional khusus UMI.
- Body POST tambahkan field
mdr_classification.
- Replace
- Update backend (
merchant.go):- Helper
resolveMdrClassification(omzetPerBulan, employeeCount, businessTypeCode)server-side — single source of truth (mobile only mengusulkan, backend selalu recompute & overwrite). - INSERT/UPDATE registration_requests + INSERT merchants tambahkan kolom
mdr_classification.
- Helper
- Dashboard Registration Queue Review (
apps/merchant_dashboard/lib/dashboard/features/registration/, planned folder — current code difeatures/home/.../forms/manual_registration_wizard_dialog.dart) — implement §4.5:- Section "Klasifikasi MDR" di panel detail registrasi.
- Endpoint baru
PATCH /api/dashboard/merchant-registrations/{id}/mdr-classification. - Comparison submitted vs server-compute + flag warna + override control.
- Approve flow stamp values aktif (rekomendasi atau override) ke
merchants.mdr_*.
- Layer 3 fix: implement
computeTransactionMdrdi transaction INSERT path (§3.5). - Backfill historis (§5) post Layer 3.
- Update merchant-financial-analytics-spec.md §3:
mdr_fee_keslesformula dependent kemdr_classification— saat ini hardcode0.3% × gross, hanya valid untuk merchant Reguler (klasifikasi 0.7%). Untuk UMI/Pendidikan/SPBU/Government, formula harus return 0 sesuai assumption v1 §2.3. - Update qrisplus-variable-cost-analysis.md:27 dan qrisplus-device-landed-cost-estimation.md:213-214: tegaskan bahwa "MDR Kesles 0.3%" = pass-through dari merchant Reguler 0.7%, tidak berlaku untuk tier lain. Tambahkan footnote ke §2.3 dokumen ini.
8. Referensi
- [UTAMA] Tabel MDR QRIS Resmi BI: Siaran Pers RDG Februari 2025 — berlaku efektif 15 Maret 2025 — sumber: qris.id / bi.go.id
- Migration 107:
merchant_database/db_kesles_merchant/migrations/legacy/107_merchant_mdr_rate.sql - Migration 014 (MCC):
merchant_database/db_reference/sql/014_create_ref_mcc_code.sql - Migration 012 (OUTDATED rates):
merchant_database/db_reference/sql/012_cleanup_payment_cards_mdr_rates.sql⚠️ Rate Pendidikan & SPBU salah — lihat §2.6 - Master skala:
db_reference.ref_business_scale(UU 20/2008 — display only, bukan penentu MDR) - Master jenis usaha:
db_reference.ref_business_type(perlu enrichment flag sektor Khusus) - Master MCC:
merchant.ref_mcc_code(ISO 18245 — rate sudah benar di 014) - Kesles share economics: qrisplus-selling-strategy-analysis.md, qrisplus-variable-cost-analysis.md, merchant-financial-analytics-spec.md
- Mobile UI:
apps/mobile_user/lib/features/merchant/presentation/pages/merchant_registration_page.dart - Backend:
merchant_core_api/internal/merchant/merchant.go(resolveRegistrationMdrRate,mdrRateForBusinessScale)