Merchant Registration — Reject & Revision Flow
✅ AUDIT VERDICT (2026-06-05): Phase 1–5 SHIPPED end-to-end + deployed:
- Phase 1 Schema: mig
v1/066_merchant_registration_revision.sqlapplied (statusrevision_required+ kolomrevision_notes/revision_count/revision_phase).- Phase 2 Backend: handler
RequestRevisiondimerchant_core_api/internal/merchant/merchant.go+ endpoint/internal/merchant/registration/request-revision; FCM eventmerchant_status_revision_requiredregistered dimerchant_event_messages.go.- Phase 3 Dashboard:
RejectRevisionDialog+ status pillrevision_requiredrendering live, proxydashboard_api/internal/app/dashboard_merchant_registration.goroute ke core-api.- Phase 4 Mobile (locked, eksplisit per-file approve): 7 helper di
shared/merchant_status.dart+merchant_account_status_page.dart+push_notification_service.darthandle caserevision_requiredidentik denganrejected.- Phase 5 SQL smoke test: 5/5 skenario PASS (2026-06-05), binary
merchant_core_apideployed ke VM. UI smoke test manual via dashboard+mobile masih bisa dijalankan sebagai validasi akhir tapi tidak blocking.6 keputusan log (NMID retention, cap 3 rounds, free-text notes, dst.) sudah ter-implementasi. Plan boleh diarsipkan ke
docs/architecture/.
Konteks
Saat ini tombol "Reject" di dashboard merchant registration queue menjadi satu-satunya jalur penolakan, dengan beberapa keterbatasan yang merugikan operasional:
- Reject hanya valid di status
pending/pending_review— setelahpending_acquireratauacquirer_approved, admin tidak punya cara membatalkan/menolak registrasi tersebut tanpa intervensi SQL manual. - Tidak ada pembedaan eksplisit antara "kirim balik untuk perbaikan" vs "tolak permanen" — model existing (mig 033) pakai counter implicit
rejected_countvsmax_submission_attemptsyang membuat semantik admin tidak jelas saat menolak. - NMID/MID/TID dari bank yang sudah issued pasca-
acquirer_approvedtidak ada policy retention saat user mau revisi — pertanyaan kritis: kalau user ganti foto KTP, apakah NMID lama tetap valid? Belum ada keputusan kode.
Plan ini menargetkan Opsi B dari diskusi 2026-06-02: tambah status enum revision_required eksplisit, ekspos dua jalur outcome saat reject (Send Back vs Final Reject), plus extend gating agar reject valid sampai status acquirer_approved.
Konteks lengkap diskusi & keputusan ada di transkrip sesi 2026-06-02.
Aturan lock: Perubahan di
apps/mobile_user/masuk lock policy (lihatapps/mobile_user/CLAUDE.md). Phase 4 plan ini menyentuh mobile — eksekusi tiap file menunggu approval per-file. Dashboard (apps/merchant_dashboard/) tidak terlock tapi tetap punya mirror obligation kerepo_exports/kesles/merchant/merchant-dashboard/.
Decision log
Rangkuman 6 keputusan policy yang sudah disepakati di diskusi:
1. revision_notes free-text dulu (decided 2026-06-02)
Field tunggal untuk catatan reviewer. Checklist per-field (fields_to_fix JSONB) dipertimbangkan di fase berikutnya kalau ada kebutuhan nyata.
2. Cap 3 round revisi (decided 2026-06-02)
Setelah revision_count >= 3, reviewer dipaksa pilih Approve atau Reject Final — tidak boleh "Kirim balik untuk revisi" lagi. Counter dipisah dari rejected_count existing (yang akan tetap dipertahankan untuk audit).
3. NMID retention: SELALU tetap pada revisi (decided 2026-06-02)
NMID/MID/TID dari bank selalu tetap saat user submit revisi, terlepas dari field apa yang berubah. Tidak ada whitelist critical/non-critical di code.
Rasional: device-level guard sudah menutup risiko transaksi salah-merchant. Transaksi tidak bisa masuk kecuali 4 syarat semua terpenuhi: (1) merchant active di merchant.merchants (2) sales_order device exist (3) device paired ke merchant (4) device status active. Selama salah satu gate tidak terlewati, NMID hanya idle metadata.
Logic implementasi saat user submit revisi:
- Status sebelum revisi
pending_review→ balikpending_reviewsetelah submit - Status sebelum revisi
pending_acquirer→ balikpending_acquirer - Status sebelum revisi
acquirer_approved→ balikacquirer_approved(NMID tetap, admin tinggal Final Approve)
Kolom audit revision_phase tetap dipertahankan (pre_bank / post_submit / post_bank_approval) untuk batch audit query nanti — kalau bank/regulator butuh laporan "revisi yang terjadi setelah NMID issued".
Eskalasi future: kalau ternyata di production ada compliance issue (bank Mandiri/BNI flag merchant), bisa upgrade ke Pendekatan C (tombol manual "Reset NMID" di dialog admin) tanpa breaking change schema — tinggal tambah field UI + handler.
4. Suspend / Block / Terminate diluar scope (decided 2026-06-02)
Operasi untuk merchant yang sudah active (suspend, block device, terminate) jadi epic terpisah. Bukan target plan ini.
5. FCM + Notification Center (decided 2026-06-02)
Event type baru merchant_status_revision_required ditambah ke event registry merchant_event_messages.go. Mobile push notification + entry di notification center mengikuti pola FCM existing — tidak butuh infrastruktur baru.
6. Satu tombol Reject di dashboard → dialog 2-pilihan (decided 2026-06-02)
Wording final: "Kirim Balik untuk revisi" vs "Tolak Final".
Counter display: info read-only (Opsi A). Render text "Sudah di-revisi: X dari 3 round" di header dialog supaya admin sadar konteks. Cap 3 round hard-coded — tidak ada admin override per-registrasi. Setelah revision_count >= 3, opsi "Kirim Balik untuk revisi" disabled, admin wajib pilih Approve atau Tolak Final.
Rasional pilih Opsi A: konsistensi policy antar reviewer. Admin tidak bisa naik/turunkan cap individual — mencegah inkonsistensi treatment antar merchant. Kalau ternyata butuh fleksibilitas (mis. kasus borderline), upgrade ke Opsi B/C bisa dilakukan future dengan tambah kolom revision_max_rounds INT NOT NULL DEFAULT 3 tanpa breaking change.
7. Reject pasca-acquirer_approved BOLEH (decided 2026-06-02)
Admin boleh menolak/membatalkan registrasi setelah bank balas approve (NMID issued). Tidak ada compliance issue dari sisi Kesles karena transaksi tidak bisa masuk sebelum Final Approve + device aktif. Tambah field audit rejection_reason_post_acquirer TEXT di Phase 1 schema untuk catat alasan eksplisit kalau reject terjadi di fase ini — supaya kalau auditor regulator tanya "kenapa NMID issued tapi tidak aktif di Kesles", ada catatan.
Existing state audit (verified 2026-06-02)
Pondasi yang sudah live dan akan dimanfaatkan plan ini:
Schema (db_kesles_merchant, schema merchant)
| Komponen | Lokasi | Status |
|---|---|---|
| Status enum constraint | chk_merchant_registration_requests_status | ('draft', 'pending', 'pending_review', 'pending_acquirer', 'acquirer_approved', 'approved', 'rejected', 'cancelled') — TANPA revision_required |
| Acquirer ID format constraints | chk_acquirer_{nmid,mid,tid}_format, chk_acquirer_ids_complete | NMID = ID + 13 digit, MID = 15 digit, TID = 8 digit. Saat acquirer_response_status='approved' → ketiga NMID/MID/TID wajib non-empty. Implikasi reset NMID: harus juga reset acquirer_response_status ke NULL agar constraint tidak fail. |
| Counter audit trail | mig 033 migrations/v1/033_merchant_registration_audit_trail.sql | submission_count, rejected_count, max_submission_attempts=3, submitted_at, last_resubmitted_at, review_started_at, approved_at, rejected_at |
| Event log | merchant.merchant_registration_events (mig 033) | 5 event types: submitted, resubmitted, review_started, approved, rejected. Plan ini tambah 2: revision_requested, revision_submitted |
| Last migration | merchant_database/db_kesles_merchant/migrations/v1/065_*.sql | Plan ini mulai dari mig 066 |
Backend server (merchant_core_api)
| Komponen | Lokasi | Status |
|---|---|---|
| Resubmit logic | merchant.go:540-577 | Re-use row rejected → status pending, counter+1, cap 3 (existing max_submission_attempts) |
Audit event resubmitted | merchant.go:947-952 | Logged tiap user submit ulang |
| Reject handler | dashboard_merchant_registration.go:572-735 | POST /v1/dashboard/merchant/registration/{id}/reject. SQL gating: WHERE status IN ('pending', 'pending_review'). Perlu extend untuk plan ini. |
| Approve handler | internal_merchant_handlers.go:307 | POST /internal/merchant/registration/approve — mint MRC code, INSERT ke merchants, hapus row registrasi |
| Sub-routes inventory | dashboard_merchant_registration.go:50-110 | review, save-review, submit-acquirer, acquirer-response, approve, reject, address-update |
| Internal routes | routes_internal.go:12-16 | /internal/merchant/registration/{review,submit-acquirer,acquirer-response,approve,manual}. Plan ini tambah /request-revision. |
FCM event registry
| Komponen | Lokasi | Status |
|---|---|---|
| Event types registered | merchant_event_messages.go | merchant_status_pending_review, merchant_status_active, merchant_status_rejected. Plan ini tambah merchant_status_revision_required. |
| Emitter di reject path | internal_merchant_handlers.go:287 | merchant_status_rejected emit saat status=rejected. Plan ini tambah emit untuk revision_required. |
Dashboard UI
| Komponen | Lokasi | Status |
|---|---|---|
| Reject button + gating | dashboard_sections.dart:3483-3490 | _canDecide gating: status mengandung review + progress 100% |
| Reject performer | dashboard_sections.dart:3718 | _performReject → prompt textarea notes → API call |
| API call | dashboard_home_api.dart:2951 | rejectMerchantRegistration POST |
| Status pill | dashboard_sections.dart:3527+ | Render label berdasar status. Plan ini tambah label "Revisi" |
Mobile (apps/mobile_user)
| Komponen | Lokasi | Status |
|---|---|---|
| Status helpers | shared/merchant_status.dart | Sudah handle rejected dengan label "Perbaikan", banner, progress, headline. Plan ini tambah handler untuk revision_required (label & UX bisa identik dengan rejected sebagai jembatan UX) |
| Session field | auth_session_service.dart:1138-1143 | Persist merchantLastResubmittedAt, merchantSubmissionCount, merchantRejectedCount, merchantMaxSubmissionAttempts. Plan ini tambah merchantRevisionNotes, merchantRevisionCount. |
| Profile API service | profile_api_service.dart:219-282 | Parse counter fields dari response. Plan ini tambah revision fields. |
| Account status page | merchant_account_status_page.dart | Sudah render banner "rejected" dengan rejected_count display. Plan ini extend untuk revision_required. |
| Registration page | merchant_registration_page.dart:173 | Pre-fill data dari MRR existing row saat flow "Perbaikan". Plan ini reuse untuk revision flow. |
| FCM handler | push_notification_service.dart:223-239 | _isMerchantStatusOrOrderEvent + _inferMerchantStatusFromType. Plan ini tambah case merchant_status_revision_required. |
KYC parallel (informational, bukan modify target)
| Komponen | Lokasi | Status |
|---|---|---|
| KYC submissions | kyc.submissions (schema kyc, mig 025) | Punya status needs_more_docs + lifecycle under_review → needs_more_docs → submitted yang persis identik dengan revision_required. Linkage ke registration via kyc.submissions.source_registration_id. Plan ini tidak menyentuh kyc — pattern di-borrow saja sebagai referensi UX. |
Gap analysis (apa yang BELUM ada vs target)
| # | Gap | Phase yang menangani |
|---|---|---|
| 1 | Status enum revision_required belum ada di check constraint | Phase 1 (schema) |
| 2 | Kolom revision_notes, revision_requested_at, revision_requested_by_user_id, revision_count, revision_phase belum ada | Phase 1 (schema) |
| 3 | Event log revision_requested, revision_submitted belum ada di constraint event_type | Phase 1 (schema) |
| 4 | Endpoint POST /internal/merchant/registration/{id}/request-revision belum ada | Phase 2 (server) |
| 5 | Endpoint dashboard POST /v1/dashboard/merchant/registration/{id}/request-revision belum ada | Phase 2 (server) |
| 6 | Reject handler gating sempit (pending/pending_review saja) — perlu extend untuk pending_acquirer, acquirer_approved | Phase 2 (server) |
| 7 | NMID retention logic saat user submit revisi pasca-acquirer_approved (whitelist field critical) belum ada | Phase 2 (server) |
| 8 | FCM event merchant_status_revision_required belum register | Phase 2 (server) |
| 9 | Dashboard UI: tombol Reject masih single prompt textarea, belum dialog 2-pilihan | Phase 3 (dashboard) |
| 10 | Dashboard UI: gating button Reject masih _canDecide sempit (cuma pending_review 100%) — perlu extend | Phase 3 (dashboard) |
| 11 | Dashboard UI: status pill untuk revision_required belum ada (label, warna) | Phase 3 (dashboard) |
| 12 | Mobile UI: helper merchantStatusLabel/Headline/Description/StoreTitle/StoreSubtitle untuk revision_required belum ada | Phase 4 (mobile, locked) |
| 13 | Mobile session: field merchantRevisionNotes, merchantRevisionCount belum ada | Phase 4 (mobile, locked) |
| 14 | Mobile FCM handler merchant_status_revision_required belum ada | Phase 4 (mobile, locked) |
| 15 | Mobile flow: banner home + handle redirect ke registration page mode revision belum ada (eksplisit) | Phase 4 (mobile, locked) |
| 16 | E2E test scenario belum ada | Phase 5 (test) |
Phase plan
Setiap phase didahului audit pre-flight (read-only) yang memvalidasi state codebase di saat phase dieksekusi, bukan asumsi dari hari plan ditulis. Phase 4 (mobile, locked) didetailkan per-file dengan diskusi-detail wajib sebelum edit apa pun.
Phase 1 — Schema migration ✅ DONE 2026-06-02
Tujuan: tambah status enum, kolom audit, event types. Foundation untuk Phase 2-4.
Hasil eksekusi:
- File
migrations/v1/066_merchant_registration_revision.sqlapplied kedb_kesles_merchant(host 10.8.0.1, dev). - 6 kolom baru ditambah ke
merchant.merchant_registration_requests. - Status enum constraint extended dengan
revision_required. - Event type constraint extended dengan
revision_requested+revision_submitted. - Index
ix_mrr_revision_requested_atcreated. - Smoke test INSERT + event log + cleanup PASS.
1.1 Audit pre-flight
- Konfirmasi nomor migration terakhir di
merchant_database/db_kesles_merchant/migrations/v1/. Plan ini reserve mig 066. - Cek apakah ada migration in-flight yang belum di-apply (cek tabel
schema_migrationskalau ada, ataugit log). - Cek snapshot constraint
chk_merchant_registration_requests_statussaat ini, simpan ke audit doc untuk rollback reference. - Cek apakah ada row
merchant_registration_requestsyang status-nya non-standard atau corrupt (kalau ada, perlu cleanup sebelum constraint baru). - Inventaris konsumen field
rejected_count(grep dimerchant_core_api,services/dashboard_api,apps/merchant_dashboard,apps/mobile_user) — pastikan tidak ada yang break kalau counter dipertahankan tapi semantik bergeser.
1.2 Design
File baru: merchant_database/db_kesles_merchant/migrations/v1/066_merchant_registration_revision.sql
Konten:
-- 066_merchant_registration_revision.sql
--
-- Tambah kolom revision_* dan status enum 'revision_required' untuk
-- explicit Send-Back-for-Revision flow (lihat
-- docs/architecture/merchant-reject-revision-flow-plan.md).
-- ──────────────────────────────────────────────────────────────────
-- 1. Tambah kolom revision_*
-- ──────────────────────────────────────────────────────────────────
ALTER TABLE merchant.merchant_registration_requests
ADD COLUMN IF NOT EXISTS revision_notes TEXT,
ADD COLUMN IF NOT EXISTS revision_requested_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS revision_requested_by_user_id UUID,
ADD COLUMN IF NOT EXISTS revision_count INT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS revision_phase VARCHAR(32),
ADD COLUMN IF NOT EXISTS rejection_reason_post_acquirer TEXT;
COMMENT ON COLUMN merchant.merchant_registration_requests.revision_notes IS
'Catatan reviewer saat request revision; ditampilkan ke user di mobile sebagai instruksi perbaikan.';
COMMENT ON COLUMN merchant.merchant_registration_requests.revision_count IS
'Counter berapa kali sudah di-request-revision oleh reviewer. Cap hard-coded = 3.';
COMMENT ON COLUMN merchant.merchant_registration_requests.revision_phase IS
'Fase registrasi saat revision di-request: pre_bank (dari pending_review) | post_submit (dari pending_acquirer) | post_bank_approval (dari acquirer_approved). Audit only — tidak mempengaruhi logic NMID retention.';
COMMENT ON COLUMN merchant.merchant_registration_requests.rejection_reason_post_acquirer IS
'Catatan eksplisit saat admin reject setelah bank balas approve (NMID issued). Untuk audit kalau regulator/bank tanya kenapa NMID issued tidak aktif di Kesles.';
CREATE INDEX IF NOT EXISTS ix_mrr_revision_requested_at
ON merchant.merchant_registration_requests (revision_requested_at DESC)
WHERE revision_requested_at IS NOT NULL;
-- ──────────────────────────────────────────────────────────────────
-- 2. Update status check constraint — tambah 'revision_required'
-- ──────────────────────────────────────────────────────────────────
ALTER TABLE merchant.merchant_registration_requests
DROP CONSTRAINT IF EXISTS chk_merchant_registration_requests_status;
ALTER TABLE merchant.merchant_registration_requests
ADD CONSTRAINT chk_merchant_registration_requests_status CHECK (
status::text = ANY (ARRAY[
'draft', 'pending', 'pending_review',
'pending_acquirer', 'acquirer_approved',
'revision_required',
'approved', 'rejected', 'cancelled'
]::text[])
);
-- ──────────────────────────────────────────────────────────────────
-- 3. Update event log check constraint — tambah 2 event types
-- ──────────────────────────────────────────────────────────────────
ALTER TABLE merchant.merchant_registration_events
DROP CONSTRAINT IF EXISTS chk_registration_event_type;
ALTER TABLE merchant.merchant_registration_events
ADD CONSTRAINT chk_registration_event_type CHECK (
event_type IN (
'submitted', 'resubmitted',
'review_started',
'approved', 'rejected',
'revision_requested', 'revision_submitted'
)
);
1.3 Implementation
- Tulis file
066_merchant_registration_revision.sqldimerchant_database/db_kesles_merchant/migrations/v1/. - Sinkron ke mirror
repo_exports/kesles/merchant/database/db_kesles_merchant/migrations/v1/(kalau folder ini di-mirror). - Apply ke DB dev via
psql. - Re-verify constraint dengan query
pg_get_constraintdef.
1.4 Verification
-
pg_get_constraintdef('chk_merchant_registration_requests_status')mengandung'revision_required'. -
pg_get_constraintdef('chk_registration_event_type')mengandung'revision_requested'dan'revision_submitted'. -
\d+ merchant.merchant_registration_requestsmenampilkan 5 kolom baru. - Index
ix_mrr_revision_requested_atada. - Smoke INSERT row dengan status
revision_required+revision_count=0tidak error. - Smoke INSERT event
revision_requestedtidak error.
1.5 Risiko & rollback
- Risiko: ada row dengan status non-standard yang melanggar constraint baru saat DROP+RECREATE. Mitigasi: query audit row sebelum apply.
- Rollback:
ALTER TABLE ... DROP COLUMN revision_*+ restore constraint lama via DDL backup. Risiko rendah karena migration additive.
Phase 2 — Backend server (merchant_core_api + dashboard_api) ✅ DONE 2026-06-02
Tujuan: tambah endpoint request-revision, extend reject gating, register FCM event baru.
Hasil eksekusi:
- 6 file dimodifikasi:
server.go,merchant.go,internal_merchant_handlers.go,routes_internal.go,merchant_event_messages.go,dashboard_merchant_registration.go. - Method
RequestRevisiondimerchant.go— deriverevision_phasedari status, cap 3 round, simpan kerevision_count, log eventrevision_requested. - Resubmit flow di-extend: predecessor
revision_requireddi-handle (target status derive darirevision_phase), event logrevision_submitted. - Endpoint internal
POST /internal/merchant/registration/request-revisiondiroutes_internal.go. - Endpoint dashboard
POST /v1/dashboard/merchant/registration/{id}/request-revisiondidashboard_merchant_registration.go. - FCM event
merchant_status_revision_requireddimerchant_event_messages.go. - Reject SQL gating extended ke
pending_acquirer,acquirer_approved,revision_required. Fieldrejection_reason_post_acquirerditangkap dari body. go build+go vetkeduanya bersih untuk core_api + dashboard_api.- Smoke test SQL: RequestRevision flow + extended reject pasca-acquirer_approved keduanya PASS.
2.1 Audit pre-flight
- Baca ulang
merchant.go:540-577— pastikan logic resubmit existing tidak konflik dengan flow baru. - Konfirmasi format payload sub-route dispatcher di
dashboard_merchant_registration.go:50-110. - Verifikasi internal-key auth flow di
routes_internal.go. - Inventaris field handler
update merchant_registration_requests(pastikan fieldrevision_*baru di-handle di SELECT/UPDATE). - Cek pattern FCM emit di
internal_merchant_handlers.go:287sebagai template. - Audit konsumen
merchant_status_rejecteddi mobile sambil cari titik di manamerchant_status_revision_requiredperlu di-handle (saat Phase 4).
2.2 Design
Server: merchant_core_api/internal/merchant/merchant.go — tambah method:
// RequestRevision menset status registrasi ke 'revision_required',
// menyimpan catatan reviewer, dan mencatat event audit. Dipanggil
// oleh handler dashboard saat reviewer pilih "Kirim Balik untuk
// revisi" di dialog 2-pilihan.
//
// Cap: revision_count >= 3 → return error, handler wajib pilih
// Approve atau Tolak Final.
//
// Phase logic: revision_phase di-set berdasar status sebelumnya.
// pending_review → pre_bank
// pending_acquirer → post_submit
// acquirer_approved → post_bank_approval
//
// status_pre_revision disimpan di metadata event log supaya
// ResubmitFromRevision tahu harus kembalikan ke status apa.
func (s *Store) RequestRevision(ctx context.Context, input RequestRevisionInput) (Record, error)
// ResubmitFromRevision dipanggil dari endpoint mobile saat user submit
// data setelah dapat permintaan revisi. NMID/MID/TID SELALU TETAP —
// tidak ada whitelist critical/non-critical (per decision log #3).
// Status balik ke status_pre_revision yang disimpan saat
// RequestRevision dipanggil:
// - pre_bank → pending_review
// - post_submit → pending_acquirer
// - post_bank_approval → acquirer_approved (NMID tetap, admin tinggal Final Approve)
func (s *Store) ResubmitFromRevision(ctx context.Context, input ResubmitFromRevisionInput) (Record, error)
Catatan: Pendekatan A (NMID selalu tetap) dipilih per decision #3. Method
ResubmitFromRevisiontidak butuh helper whitelist atau diff comparator — lebih simpel dari proposal awal.
Server: merchant_core_api/internal/httpapi/internal_merchant_handlers.go — tambah:
// POST /internal/merchant/registration/request-revision
func (s *Server) handleRequestRevisionMerchantRegistration(w http.ResponseWriter, r *http.Request) { /* ... */ }
Server: route registration routes_internal.go:
s.mux.HandleFunc("/internal/merchant/registration/request-revision",
s.handleRequestRevisionMerchantRegistration)
Dashboard API: services/dashboard_api/internal/app/dashboard_merchant_registration.go — tambah:
// handleMerchantRegistrationRequestRevision forwards ke core-api
// internal endpoint /internal/merchant/registration/request-revision.
func (s *Server) handleMerchantRegistrationRequestRevision(w http.ResponseWriter, r *http.Request, id string) { /* ... */ }
Plus tambah case "request-revision": di sub-route dispatcher di file yang sama.
Reject handler extend: ubah SQL gating di handleMerchantRegistrationReject:
-- SEBELUM:
WHERE id = $1::uuid
AND deleted_at IS NULL
AND status IN ('pending', 'pending_review')
-- SESUDAH:
WHERE id = $1::uuid
AND deleted_at IS NULL
AND status IN (
'pending', 'pending_review',
'pending_acquirer', 'acquirer_approved',
'revision_required' -- user mungkin sedang revisi → bisa di-final-reject langsung
)
FCM event registry: tambah di merchant_event_messages.go:
case "merchant_status_revision_required":
return MerchantEventMessage{
Title: "Pengajuan perlu perbaikan",
Body: "Reviewer Kesles meminta perbaikan data registrasi Anda. Buka aplikasi untuk lihat detail.",
}
Plus emit dari handleRequestRevisionMerchantRegistration pakai pattern existing fireMerchantEventAsync.
2.3 Implementation
- Tulis service methods
RequestRevision+ResubmitFromRevision+ helperhasCriticalFieldChangedimerchant.go. - Tulis handler
handleRequestRevisionMerchantRegistrationdiinternal_merchant_handlers.go. - Register route di
routes_internal.go. - Tulis dashboard proxy handler + case sub-route di
dashboard_merchant_registration.go. - Extend SQL gating di
handleMerchantRegistrationRejectSAME file. - Tambah FCM event message + emit.
go build+go vetsemua harus pass.- Unit test untuk
hasCriticalFieldChangeminimum (10+ skenario). - Smoke test endpoint pakai
curl.
2.4 Verification
-
curl POST /v1/dashboard/merchant/registration/{id}/request-revisiondengan body{"notes":"foto buram"}→ 200, status DB jadirevision_required, counter+1, event tercatat denganstatus_pre_revisiondi metadata. - Repeat 3× → call ke-4 reject 409 "revision limit reached, please approve or final-reject".
-
curl POST /v1/dashboard/merchant/registration/{id}/rejectsaat statuspending_acquirer→ 200 (sebelum patch: 409). Sertakanrejection_reason_post_acquirerdi body. - Resubmit dari mobile saat status
revision_requiredpasca-acquirer_approved(ubah field apa pun) → NMID/MID/TID tetap, status balikacquirer_approved. - Resubmit saat status
revision_requiredpasca-pending_review→ status balikpending_review. - FCM event
merchant_status_revision_requiredter-dispatch (cek log atau monitor mobile end).
2.5 Risiko & rollback
- Risiko utama: NMID retention policy belum konfirmasi acquirer real. Mitigasi: deploy dulu di dev/staging dengan whitelist konservatif (default), konfirmasi acquirer sebelum cutover production.
- Risiko: reject pasca-
acquirer_approvedmungkin punya implikasi compliance/banking yang belum dipikirkan. Saran: diskusikan dengan stakeholder bank dulu sebelum implementasi. - Rollback: revert handler + route registration. Schema kolom revision_* sudah ada di Phase 1 tapi tidak digunakan — no data loss.
Phase 3 — Dashboard UI (apps/merchant_dashboard) ✅ DONE 2026-06-02
Tujuan: tombol Reject di queue → buka dialog 2-pilihan. Tambah status pill revision_required.
Hasil eksekusi (Phase 3.1, setelah Phase 3.0 data layer extension):
7 file dimodifikasi/dibuat di dashboard:
dashboard_home_api.dart— extendrejectMerchantRegistrationdenganrejectionReasonPostAcquirer+ add newrequestRevisionMerchantRegistration.home_remote_data_source.dart— passthrough kedua method.home_repository_contract.dart— abstract methods.home_repository.dart— impl methods.home_dashboard_usecase.dart— facade methods.- NEW
reject_revision_dialog.dart—RejectRevisionDialogwidget pakaiDashboardFormDialogShell: 2 radio button (Kirim Balik untuk revisi vs Tolak Final) denganRadioGroup<RejectRevisionOutcome>ancestor pattern (Flutter 3.32+), counter "Sudah X dari 3 round" di header, textarea catatan (min 5 char wajib), auto-disable "Kirim Balik" saatrevisionCount >= 3, dynamic confirm button color (kuning untuk revisi, merah untuk reject final). dashboard_sections.dart:- Import dialog baru
- Ganti
_canDecide→_canRejectOrRevise(extend gating untukpending_acquirer/acquirer_approved/revision_required) - Modify
_performReject— buka dialog dulu, route kerequestRevisionMerchantRegistrationataurejectMerchantRegistrationsesuai outcome. Pasca-acquirer rejection: passrejectionReasonPostAcquireruntuk audit. - Tambah branch status pill
revision_required(warna kuning, label "Revisi · X/3", icon edit_note) - Update tooltip Reject button
- Hapus
_promptRejectNotes+ kelas_RejectRegistrationDialog(sudah digantikan dialog baru).
Verifikasi:
- ✓
flutter analyzebersih untuk semua file Phase 3 yang disentuh - ✓
flutter analyze libtotal 12 issues — semua di file lain (jadwal_kirim_promo_panel,broadcast_pengumuman_panel), tidak terkait Phase 3.
Phase 3 — Dashboard UI ORIGINAL SPEC (kept for reference)
Tujuan: tombol Reject di queue → buka dialog 2-pilihan. Tambah status pill revision_required. Counter dashboard.
3.1 Audit pre-flight
- Baca ulang gating
_canDecidedidashboard_sections.dart:3598— cari pattern guard yang harus extend. - Inventaris widget shell yang sudah ada untuk dialog (per
feedback_dashboard_dialog_standard:DashboardFormDialogShell/DashboardDialogShell). Tidak boleh AlertDialog mentah. - Cek API service method
rejectMerchantRegistrationdidashboard_home_api.dart:2951sebagai template untukrequestRevisionMerchantRegistration. - Verifikasi
_kPendingMerchantActionsWidth = 200didashboard_sections.dart:2648— pastikan tidak overflow setelah perubahan (jumlah tombol tetap, hanya behavior). - Konfirmasi mirror obligation: ubahan harus disinkronkan ke
repo_exports/kesles/merchant/merchant-dashboard/.
3.2 Design
Dialog 2-pilihan — buat file baru:
apps/merchant_dashboard/lib/dashboard/features/home/presentation/widgets/panels/reject_revision_dialog.dart
class RejectRevisionDialogResult {
final RejectRevisionOutcome outcome; // sendBackForRevision | rejectFinal
final String notes;
}
class RejectRevisionDialog extends StatefulWidget {
final PendingMerchantItem item;
final int revisionCount; // dari item.revisionCount
final int revisionMaxRounds; // hardcoded 3 dulu
// ...
}
Body dialog:
┌───────────────────────────────────────┐
│ Tolak / Kirim balik registrasi │
├───────────────────────────────────────┤
│ Status: pending_review │
│ Sudah di-revisi: 1 dari 3 round │
├───────────────────────────────────────┤
│ Pilih outcome: │
│ ○ Kirim balik untuk revisi │
│ (user bisa edit data & submit ulang)│
│ │
│ ○ Tolak final │
│ (user harus daftar dari nol) │
├───────────────────────────────────────┤
│ Catatan untuk user (wajib): │
│ ┌───────────────── ──────────────────┐ │
│ │ Foto KTP buram, tolong upload │ │
│ │ ulang dengan pencahayaan jelas. │ │
│ └───────────────────────────────────┘ │
│ │
│ [Batal] [Kirim] │
└───────────────────────────────────────┘
Gating logic baru — modify _canDecide di dashboard_sections.dart:
// SEBELUM:
static bool _canDecide(PendingMerchantItem item) {
if (!item.status.toLowerCase().contains('review')) return false;
return item.reviewProgress >= 100;
}
// SESUDAH:
static bool _canRejectOrRevise(PendingMerchantItem item) {
final s = _statusKey(item);
if (s == 'pending_review') {
return item.reviewProgress >= 100; // unchanged
}
if (s == 'pending_acquirer' || s == 'acquirer_approved' || s == 'revision_required') {
return true; // boleh reject/revise tanpa progress check
}
return false;
}
Status pill — tambah branch untuk revision_required di _statusPill:
} else if (normalized == 'revision_required') {
color = const Color(0xFFE08A1C); // kuning warning
label = t('Revision', 'Revisi');
icon = Icons.edit_note_rounded;
}
API call — tambah method di dashboard_home_api.dart:
Future<Map<String, dynamic>> requestRevisionMerchantRegistration({
required String accessToken,
required String id,
required String notes,
}) async { /* POST /dashboard/merchant/registration/$id/request-revision */ }
Counter dashboard — di summary stats, tambah card "Revisi Diminta" dengan count WHERE status='revision_required'.
3.3 Implementation
- Buat file
reject_revision_dialog.dartdengan widget shellDashboardFormDialogShell. - Modify
_canDecide→ split jadi_canRejectOrRevisedidashboard_sections.dart. - Modify
_performReject→ buka dialog dulu, lalu route ke API method sesuai outcome. - Tambah
_statusPillbranchrevision_required. - Tambah API method
requestRevisionMerchantRegistrationdidashboard_home_api.dart. - Tambah counter card di summary stats.
- Mirror semua perubahan ke
repo_exports/kesles/merchant/merchant-dashboard/. flutter analyzeharus bersih.
3.4 Verification
- Klik Reject di row
pending_review100% → dialog muncul dengan 2 opsi. - Pilih "Kirim balik untuk revisi" + catatan → submit → status DB jadi
revision_required. - Pilih "Tolak final" + catatan → status DB jadi
rejected. - Row dengan
revision_count=3→ opsi "Kirim balik" disabled di dialog. - Row
pending_acquirer→ tombol Reject aktif (sebelumnya disabled). - Status pill untuk row
revision_requiredrender dengan label "Revisi" warna kuning. - Counter dashboard "Revisi Diminta" akurat.
- Mirror identik antara
apps/danrepo_exports/.
3.5 Risiko & rollback
- Risiko: dialog tidak terbuka di production karena widget shell version mismatch. Mitigasi: test di canary user dulu.
- Risiko: counter
revision_countdi payload UI belum di-expose oleh backend (perlu cek Phase 2 response payload). - Rollback: feature flag
dashboard.reject_dialog_v2(kalau infra mendukung); fallback ke single-prompt textarea lama.
Phase 4 — Mobile UI ✅ DONE 2026-06-02
Hasil eksekusi (Phase 4.0 server-side + 4.1 mobile per-file approval):
Phase 4.0 — server profile/store.go (1 file, no lock):
- Pattern A (single-line whitelist): 33 subquery extend
'revision_required' - Pattern B (multi-line whitelist): 8 subquery extend
'revision_required' - Pattern C (review_notes + review_payload subquery): extend
WHERE status IN ('rejected', 'revision_required')+CASE WHENper status untuk pilih kolom yang tepat (review_notesvsrevision_notes) - Tidak tambah field response baru —
merchant_review_notes+merchant_review_payloadcover kedua status (status-based source mapping di server)
Phase 4.1 — mobile (3 file, LOCKED — diskusi per-file):
shared/merchant_status.dart: 7 helper extendcase 'revision_required':paralel dengan'rejected'merchant_account_status_page.dart:_viewFor()extendcase 'revision_required':dengan body identik'rejected'push_notification_service.dart:_inferMerchantStatusFromType+_isMerchantStatusOrOrderEventextend FCM eventmerchant_status_revision_required
Yang TIDAK diubah (per instruksi user "pakai yang sudah ada"):
- Widget
_RejectionWarningCard— reuse 100% _StatusVariantenum — tidak tambah variant baruStoredProfile+ session service — tidak tambah field baruprofile_api_service.dart— tidak tambah parsinghome_merchant_activation_banner.dart— render via helper di file 1, tidak disentuh
Hasil yang user lihat di mobile saat status revision_required: persis sama dengan status rejected (hero ilustrasi, banner home, status page, kartu warning merah, checklist + notes + counter, tombol "Lihat Detail" + "Perbaiki Data"). Tidak ada visual baru.
Verifikasi:
- ✓
go buildbersih untuk core_api - ✓
flutter analyzebersih untuk 3 file mobile yang disentuh - ✓ FCM event chain end-to-end terkoneksi (server emit Phase 2 → mobile handle Phase 4.1)
Mirror
repo_exports/.../mobile-user/SKIPPED — folder tidak ada di session ini (sama dengan Phase 3 dashboard).
Phase 4 — Mobile UI ORIGINAL SPEC (kept for reference)
Tujuan: handle status revision_required di mobile_user app — banner, label, navigasi ke registration page mode revisi.
Lock policy: setiap file di
apps/mobile_user/butuh diskusi-detail dulu sebelum edit. Phase ini tidak boleh dimulai sebelum Phase 1+2 done dan user explicit approval.
4.1 Audit pre-flight
- Re-verify isi terkini
shared/merchant_status.dart— kemungkinan ada perubahan antara plan ditulis vs eksekusi. - Verify
auth_session_service.dart— pattern untuk tambah field session baru. - Verify
profile_api_service.dart— pattern parse field baru dari response. - Audit
merchant_account_status_page.dart— pastikan pattern banner status existing bisa di-extend tanpa breaking. - Audit
push_notification_service.dart— confirm_inferMerchantStatusFromTypeswitch case shape. - Cek apakah ada test (unit/widget) yang break — biasanya minim test di mobile_user.
4.2 Design — file by file
Setiap file di section ini akan didiskusikan terpisah dengan user sebelum edit. Sketsa di sini = proposal, bukan commitment.
File 1: shared/merchant_status.dart
Tambah 'revision_required' ke _pendingStatuses? Tidak — revision_required bukan "pending" semantically (user perlu action, bukan tunggu). Buat handling terpisah:
bool isMerchantRevisionRequiredStatus(String status) {
return normalizeMerchantStatus(status) == 'revision_required';
}
// merchantStatusLabel:
case 'revision_required':
return 'Perbaikan'; // reuse label existing (UX continuity)
// merchantActionButtonLabel:
if (normalized == 'revision_required') {
return 'Perbaikan';
}
// merchantActivationProgress:
case 'revision_required':
return 0.4; // 40% — sudah submit dulu, perlu revisi, baseline dari draft
// merchantActivationHeadline:
case 'revision_required':
return 'Pengajuan perlu diperbaiki';
// merchantActivationDescription:
case 'revision_required':
return 'Reviewer meminta perbaikan data registrasi Anda. Periksa catatan, perbaiki, lalu submit ulang.';
File 2: auth_session_service.dart
Tambah field di StoredProfile:
final String? merchantRevisionNotes;
final int? merchantRevisionCount;
final String? merchantRevisionPhase;
Plus parse di fromJson + serialize di toJson.
File 3: profile_api_service.dart
Parse 3 field dari response server di _buildResponse (atau equivalent).
File 4: merchant_account_status_page.dart
Tambah case render banner untuk revision_required:
- Variant
_StatusVariant.revision_required(atau reuserejected) - Show
revision_notessebagai pesan kuning di atas - Tombol "Perbaikan" → navigate ke
MerchantRegistrationPagedengan pre-fill mode
File 5: merchant_registration_page.dart
Sudah handle pre-fill flow (line 173). Kemungkinan minimal change — extend untuk pickup data dari revision_required row sama seperti rejected row.
File 6: push_notification_service.dart
case 'merchant_status_revision_required':
return 'revision_required';
Di _inferMerchantStatusFromType. Plus tambah ke _isMerchantStatusOrOrderEvent daftar.
File 7-8: mungkin perlu touch home banner widget + notification_copy untuk localized text. Audit pre-flight akan klarifikasi.
4.3 Implementation
Tahapan WAJIB per-file:
- Pre-edit: kirim "saya mau edit file X, ini diff yang saya rencana, alasannya Y. Risiko regresi Z. OK?"
- User approval → edit dengan Edit tool.
- Mirror ke
repo_exports/kesles/merchant/mobile-user/<path>. - Confirm
flutter analyzebersih.
4.4 Verification
- FCM
merchant_status_revision_requiredarrive → notif center entry muncul. - Home banner berubah ke "Pengajuan perlu diperbaiki" dengan tombol "Perbaikan".
- Tap tombol Perbaikan → form registrasi terbuka dengan data pre-filled + banner catatan reviewer.
- Submit revisi → status server berubah → mobile sync via profileRefreshTick.
- Backward compat: status
rejectedlama tetap render seperti sebelum (pattern handler old preserved). - Mirror byte-identical antara
apps/mobile_user/danrepo_exports/.../mobile-user/.
4.5 Risiko & rollback
- Risiko: locked folder berarti rollback per-file lebih lambat (perlu diskusi balik). Mitigasi: tiap edit kecil, tidak monolith.
- Risiko: localization arb file tidak terupdate → string keluar default English. Mitigasi: check
app_en.arb/app_id.arb. - Rollback: revert per file commit-by-commit. Mirror sync wajib.
Phase 5 — End-to-end test ✅ DONE 2026-06-05 (SQL smoke 5/5 PASS, binary deployed)
Tujuan: validasi lima skenario E2E pre-cutover production.
Status saat ini: SQL smoke test 5/5 PASS (dibungkus BEGIN/ROLLBACK, tidak meninggalkan state di DB dev). Binary merchant_core_api Phase 1-4 sudah deployed ke VM oleh user 2026-06-05. UI smoke test manual via dashboard+mobile masih disarankan sebagai validasi akhir tapi tidak blocking.
5.0 Hasil SQL smoke test (2026-06-05)
| # | Skenario | Status | Verifikasi |
|---|---|---|---|
| 1 | Revisi pre-bank pertama | PASS | status: pending_review → revision_required → pending_review, counter 0→1, revision_phase=pre_bank, event chain revision_requested → revision_submitted |
| 2 | Revisi pasca-acquirer_approved | PASS | NMID/MID/TID retained, status balik ke acquirer_approved, revision_phase=post_bank_approval |
| 3 | Revisi dengan field identitas | PASS | Pendekatan A confirmed — NMID retention konsisten regardless field |
| 4 | Cap 3 round | PASS | Guard revision_count >= 3 aktif — server tolak sebelum UPDATE |
| 5 | Reject Final + pasca-acquirer | PASS | rejected = terminal; rejection_reason_post_acquirer terisi terpisah, NMID dipertahankan utk audit |
Pra-syarat sebelum jalan UI smoke test manual (optional):
- VPN ke dev DB (
10.8.0.1:5432) aktif - Binary
merchant_core_apidi VMptikn3-vmdeployed dengan source Phase 1-4 — DONE 2026-06-05 - Dashboard build + deploy
- Mobile app build + install ke device test
Instruksi test manual lihat sub-section 5.2 di bawah.
5.1 Audit pre-flight
- State DB dev: clean slate (sudah dilakukan 2026-06-02), 0 merchants, 25 users.
- Backend service running + mobile app build terbaru dengan Phase 1-4 changes.
- Test data: minimum 3 user di iam.users (sudah ada 25), 7 device stok gudang (sudah ada).
5.2 Skenario test
Skenario 1: Revisi sederhana (pre-bank)
- User register dari mobile → status
pending. - Admin start review → submit checklist 100% → status
pending_review. - Admin klik Reject → dialog → pilih "Kirim balik untuk revisi" + catatan "Foto KTP buram" → submit.
- DB:
status='revision_required',revision_count=1,revision_notes='Foto KTP buram',revision_phase='pre_bank'. - FCM
merchant_status_revision_requiredter-dispatch ke user. - User di mobile lihat banner "Pengajuan perlu diperbaiki" + catatan.
- User tap "Perbaikan" → form pre-filled, upload foto KTP baru → submit.
- DB:
status='pending_review',revision_submittedevent logged. - Admin review → klik Approve → row registrasi DELETED, merchant baru di
merchant.merchants. ✅
Skenario 2: Revisi pasca-bank-approval (NMID retention)
- Lanjut dari sukses Skenario 1, mulai user baru → flow sampai
acquirer_approved(bank balas approve, NMID issued). - Admin pilih Kirim Balik untuk revisi dengan catatan "Update foto KTP dan rekening pencairan".
- DB:
status='revision_required',revision_phase='post_bank_approval', NMID/MID/TID tetap tersimpan. - User edit foto KTP +
account_number→ submit. - DB: status balik ke
acquirer_approved(sesuai decision #3 — NMID selalu tetap),revision_submittedevent tercatat. - Admin Final Approve → merchant aktif dengan NMID/MID/TID yang sama dengan yang bank issued di awal. ✅
Skenario 3: Revisi pasca-bank-approval, field identitas (verifikasi NMID retention tetap)
- Sama setup dengan Skenario 2 sampai
revision_required. - User edit
nik(field identitas yang signifikan) → submit. - DB: NMID/MID/TID tetap tersimpan (tidak reset), status balik
acquirer_approved. - Admin Final Approve → merchant aktif. Catat di
revision_phase='post_bank_approval'untuk audit batch nanti kalau bank/regulator tanya konsistensi data. ✅ - Catatan operasional: kalau di production ternyata bank flag inkonsistensi data, eskalasi ke Pendekatan C (tombol manual reset NMID di dialog admin) — schema tidak perlu ubah.
Skenario 4: Cap 3 round
- Register → admin minta revisi 1 → user submit.
- Admin minta revisi 2 → user submit.
- Admin minta revisi 3 → user submit.
- Admin coba minta revisi 4 → dialog dashboard: opsi "Kirim balik untuk revisi" DISABLED, hanya Approve atau Reject Final yang available.
- Admin pilih Approve → merchant aktif. ✅
Skenario 5: Reject Final
- Register → admin pilih Reject → dialog → pilih "Tolak final" + catatan "Dokumen palsu".
- DB:
status='rejected',rejected_count=1, FCMmerchant_status_rejectedter-dispatch. - User di mobile lihat banner status "Belum Aktif"/"Ditolak".
- User coba tap "Aktifkan" → navigasi ke flow registrasi baru (row registrasi lama dianggap done, mulai dari nol). ✅
5.3 Output dokumentasi
- Test run log per skenario (timestamp, screenshot mobile, snapshot DB).
- Update
docs/plans/audit/folder dengane2e-merchant-revision-YYYY-MM-DD.md. - Update plan ini: ubah status frontmatter
draft → approvedsetelah semua 5 pass.
Cross-cutting concerns
Testing strategy
- Unit test: minimum coverage untuk
hasCriticalFieldChange(Phase 2). Server transitions lain optional. - Integration test: tidak ada di codebase merchant_core_api saat ini — skip.
- Smoke manual: dilakukan di Phase 5 (E2E).
Backward compatibility
- Schema additive — kolom baru nullable atau dengan default. Tidak ada drop column.
- API additive — endpoint baru ditambah, endpoint lama tetap. Status enum tambah, tidak hapus.
- Mobile dual-handle: status
rejectedlama +revision_requiredbaru — keduanya jalan. - FCM: event baru ditambah. Mobile lama yang belum di-update akan ignore event tidak dikenal (no crash).
Deployment order
Phase 1 (schema) — apply ke prod ASAP, additive safe
↓
Phase 2 (server) — deploy, tapi endpoint masih dark (tidak ada caller)
↓
Phase 3 (dashboard) — deploy, mulai expose dialog
↓
Phase 4 (mobile, locked) — diskusi → release app version baru
↓
Phase 5 (E2E test) — validasi end-to-end di staging sebelum prod
Open questions (resolved)
NMID retention whitelist final— RESOLVED 2026-06-02: Pendekatan A (selalu tetap). Schema kolomrevision_phasedipertahankan untuk audit batch. Eskalasi ke Pendekatan C tanpa breaking change kalau production butuh.Reject pasca-— RESOLVED 2026-06-02: boleh. Kolom auditacquirer_approvedrejection_reason_post_acquirerditambah di Phase 1.Wording dialog— RESOLVED 2026-06-02: "Kirim Balik untuk revisi" + "Tolak Final".Counter display— RESOLVED 2026-06-02: Opsi A (info read-only). Cap 3 hard-coded.
Open questions (masih)
- Counter
rejected_count(mig 033) vsrevision_count(mig 066) — dual model: dua counter berbeda untuk dua jalur (rejectedvsrevision_required). Apakah perlu deprecationrejected_countdi future? Atau dipertahankan untuk audit? - i18n: Phase 4 mobile sudah pakai arb files? Cek saat audit pre-flight.
Changelog
- 2026-06-02: draft awal, lengkap dengan 5 phase + audit pre-flight per phase + 5 skenario E2E. Decision log untuk 6 keputusan policy dari diskusi 2026-06-02.
- 2026-06-02: decision finalisasi sesi diskusi:
- #1 Wording "Kirim Balik untuk revisi" + "Tolak Final" — confirmed
- #2 Counter display Opsi A (info read-only, cap 3 hard-coded)
- #3 NMID retention Pendekatan A (selalu tetap, no whitelist code)
- #4 Reject pasca-
acquirer_approvedBOLEH + audit kolomrejection_reason_post_acquirer - Phase 2 server design disederhanakan (hapus
hasCriticalFieldChangehelper) - Verifikasi & skenario E2E di-update sesuai final design
- 2026-06-02: Phase 1 EXECUTED. Mig 066 applied ke db_kesles_merchant dev. Semua verifikasi pass.
- 2026-06-02: Phase 2 EXECUTED. 6 file modified, go build + go vet bersih, smoke test SQL pass untuk RequestRevision dan extended reject pasca acquirer_approved.
- 2026-06-02: Phase 3.0 EXECUTED (data layer extension prerequisite). 4 file modified: (a)
db_internal_dashboard.go— SQLWHERE status INextended denganrevision_required, SELECT + struct + Scan extend 3 kolom revision_*. (b)dashboard_summary.go— response mapper extend 3 key. (c)home_dashboard_entities.dart—PendingMerchantItemextend 3 field + copyWith. (d)home_dashboard_mapper.dart— parse 3 field dari JSON (null-safe backward compat). Build & analyze keduanya bersih. Mirrorrepo_exports/SKIPPED (folder tidak ada lagi di session ini). - 2026-06-02: Phase 3.1 EXECUTED. 7 file modified/created di dashboard. New widget
RejectRevisionDialogpakaiDashboardFormDialogShell+RadioGroup<T>pattern Flutter 3.32+._performRejectroute ke endpoint sesuai outcome dialog. Status pillrevision_requiredrender dengan label "Revisi · X/3". Kelas_RejectRegistrationDialoglama dihapus (digantikan dialog baru).flutter analyzebersih untuk semua file Phase 3. - 2026-06-02: Phase 4.0 EXECUTED.
profile/store.goextend 41 subquery whitelist + 2 Pattern C subquery untuk includerevision_required. Tidak tambah field response baru (reusemerchant_review_notes+merchant_review_payloaddengan status-based source mapping).go buildbersih. - 2026-06-02: Phase 4.1 EXECUTED (locked, per-file approval). 3 file mobile_user disentuh:
shared/merchant_status.dart(7 helper extendrevision_requiredparalelrejected),merchant_account_status_page.dart(_viewForextend case identik rejected),push_notification_service.dart(2 switch FCM event handler extend). Tidak buat widget baru, tidak tambah variant enum, tidak tambah field session. Mobile rendering 100% reuse pattern existing.flutter analyzebersih. - 2026-06-05: Phase 5 SQL smoke test EXECUTED. 5/5 skenario PASS (Revisi pre-bank, Revisi pasca-acquirer NMID retention, Revisi field identitas, Cap 3 round, Reject Final + pasca-acquirer). Semua test wrap
BEGIN/ROLLBACK— tidak mengubah state DB dev. Result tabel ditulis di sub-section 5.0. - 2026-06-05: Binary
merchant_core_apiPhase 1-4 deployed ke VM oleh user. Proxydashboard_api/internal/app/dashboard_merchant_registration.go→/internal/merchant/registration/request-revisionconfirmed wired. Plan status frontmatter dinaikkan kecomplete. UI smoke test manual (dashboard tekan tombol → FCM → mobile render banner) tetap disarankan sebagai validasi akhir tapi tidak blocking untuk archive plan.