Series MapLesson 53 / 64
Deepen PracticeOrdered learning track

Learn Java Payment Systems Part 053 Backoffice Operations Platform

17 min read3325 words
PrevNext
Lesson 5364 lesson track3653 Deepen Practice

title: Build From Scratch: Large Production Grade Java Payment Systems - Part 053 description: Backoffice operations platform for production-grade Java payment systems, including operational search, case management, manual adjustment, approvals, auditability, ledger-safe commands, and repair workflows. series: learn-java-payment-systems seriesTitle: Build From Scratch: Large Production Grade Java Payment Systems order: 53 partTitle: Backoffice Operations Platform tags:

  • java
  • payments
  • payment-systems
  • backoffice
  • operations
  • case-management
  • ledger
  • audit
  • enterprise-architecture date: 2026-07-02

Part 053 — Backoffice Operations Platform

Payment system yang production-grade tidak selesai ketika API pembayaran berhasil.

Justru setelah sistem live, sebagian besar masalah paling mahal muncul di ruang operasi:

  • payment sukses di provider tetapi merchant melihat pending,
  • webhook datang terlambat,
  • settlement report tidak cocok,
  • payout gagal tetapi saldo sudah reserved,
  • refund double-requested oleh support,
  • merchant meminta koreksi fee,
  • bank statement punya credit yang tidak bisa dimatch,
  • risk team perlu hold payout,
  • compliance perlu freeze merchant,
  • finance perlu adjustment yang bisa diaudit,
  • customer support butuh jawaban yang benar tanpa membuka data sensitif.

Backoffice adalah tempat realitas production bertemu manusia.

Karena itu backoffice payment system bukan dashboard admin biasa. Ia adalah operational control plane.

Kalau salah desain, backoffice bisa menjadi jalur bypass yang lebih berbahaya daripada bug API publik.

1. Mental Model: Backoffice Is a Controlled Repair Surface

API publik dibuat untuk happy path dan predictable path.

Backoffice dibuat untuk:

  • investigasi,
  • exception handling,
  • repair,
  • override,
  • approval,
  • evidence gathering,
  • manual intervention,
  • finance correction,
  • compliance action,
  • customer support answer.

Tetapi repair tidak berarti bebas mengubah data.

Payment backoffice yang benar tidak memberi tombol “edit payment status”. Ia memberi command yang legal, sempit, beralasan, diaudit, dan punya efek ledger yang eksplisit.

Rule utama:

Backoffice must never be a database editor. It must be a governed command interface over the same domain model as production flows.

2. Why Payment Backoffice Is Hard

Backoffice payment terlihat sederhana karena UI-nya sering hanya search, table, detail page, dan button.

Yang sulit bukan UI-nya.

Yang sulit adalah memastikan setiap tombol aman secara finansial.

Contoh tombol sederhana:

Mark payment as succeeded

Kelihatannya praktis.

Tetapi pertanyaan yang harus dijawab:

  • Apakah provider memang sukses?
  • Apakah payment sudah pernah captured?
  • Apakah ledger sudah diposting?
  • Apakah settlement nanti akan membawa transaksi ini?
  • Apakah merchant balance berubah?
  • Apakah customer boleh menerima fulfilment?
  • Apakah reconciliation akan match atau break?
  • Apakah operator punya wewenang?
  • Apakah action ini perlu approval?
  • Apakah alasan dan evidence cukup?
  • Apakah action bisa diulang secara idempotent?
  • Bagaimana rollback-nya?

Karena itu production backoffice harus didesain sebagai bagian dari domain architecture.

Bukan fitur tambahan.

3. Backoffice Capability Map

Payment backoffice minimal punya capability berikut.

Backoffice bukan satu service raksasa.

Ia adalah layer di atas domain services:

  • Payment Core,
  • Ledger,
  • Reconciliation,
  • Settlement,
  • Payout,
  • Risk,
  • Compliance,
  • Merchant,
  • Dispute,
  • Audit.

Backoffice sebaiknya tidak memiliki ownership atas data finansial utama. Ia mengorkestrasi command ke owner yang benar.

4. Boundary: What Backoffice May and May Not Do

Backoffice boleh:

  • membaca timeline lengkap,
  • membuka case,
  • mengusulkan action,
  • menjalankan command yang sudah didefinisikan domain,
  • meminta approval,
  • menambahkan evidence,
  • melakukan manual match reconciliation,
  • membuat adjustment melalui ledger posting rule,
  • memicu replay webhook,
  • memicu provider state inquiry,
  • hold/release sesuai policy,
  • mengubah metadata operasional yang tidak mengubah financial truth.

Backoffice tidak boleh:

  • update payment status langsung di database,
  • update ledger balance langsung,
  • menghapus ledger entry,
  • menghapus webhook raw event,
  • mengubah provider reference,
  • bypass idempotency,
  • mengubah amount transaksi historis,
  • mengedit settlement batch yang sudah finalized,
  • menjalankan payout tanpa balance reservation,
  • mengubah audit trail,
  • melihat PAN/CVC/raw secret,
  • mengekspor data sensitif tanpa policy.

Tabel batas ownership:

ObjectOwnerBackoffice access
Payment intentPayment CoreRead + controlled commands
Payment attemptPayment Core / OrchestrationRead + inquiry/retry commands
Raw webhookWebhook IngestionRead + replay/quarantine commands
Ledger journalLedgerRead + reversal/adjustment commands
BalanceLedger ProjectionRead only
Settlement batchSettlement EngineRead + approve/hold/release where legal
Reconciliation breakReconciliationRead + propose/manual match
Merchant restrictionRisk/Compliance/MerchantRead + controlled capability command
Audit eventAudit ServiceAppend only by system

5. Backoffice Object Graph

Operator jarang mencari satu object saja.

Mereka butuh melihat graph.

Satu transaksi bisa terkait dengan:

  • customer order,
  • merchant,
  • payment intent,
  • payment attempt,
  • provider operation,
  • provider webhook,
  • authorization,
  • capture,
  • ledger journal,
  • fee journal,
  • settlement item,
  • payout batch,
  • bank statement item,
  • reconciliation match/break,
  • dispute,
  • risk decision,
  • support case.

Backoffice detail page harus menjawab pertanyaan:

Apa yang terjadi, kapan, oleh siapa/apa, berdasarkan evidence apa, dan uangnya sekarang berada di mana?

6. Search Architecture

Payment backoffice butuh search yang kuat.

Tetapi search tidak boleh menjadi source of truth.

Pattern yang umum:

Search index dipakai untuk menemukan object.

Detail page harus membaca dari authoritative services/database read model.

Kenapa?

Karena search index bisa stale.

Kalau operator membuat keputusan finansial berdasarkan index stale, backoffice menjadi sumber bug.

6.1 Search Keys

Minimal searchable keys:

KeyContohCatatan
Payment IDpi_...Internal canonical ID
Attempt IDpa_...Per provider attempt
Provider referencePSP transaction IDHarus normalized dan indexed
Merchant referenceorder IDBisa tidak unik global
Customer email/phonemasked/hashHati-hati privacy
Amount + currency100000 IDRBiasanya dikombinasi tanggal
Bank referenceVA number / RRN / STANPayment rail specific
Ledger journal IDlj_...Untuk finance investigation
Settlement batch IDsb_...Untuk settlement issue
Payout IDpo_...Untuk outbound money movement
Dispute IDdp_...Untuk chargeback case
Reconciliation break IDrb_...Untuk finance ops

6.2 Search Result Must Be Safe

Search result jangan menampilkan data sensitif.

Contoh aman:

Payment pi_123
Merchant: m_456 / Example Store
Amount: IDR 150,000
Status: SUCCEEDED
Method: CARD •••• 4242
Created: 2026-07-02 10:12:22 WIB
Provider: provider_a

Contoh buruk:

Card: 4242424242424242
CVC: 123
Customer full address
Raw provider token

Backoffice user sering lebih luas daripada engineer. Jangan beri data sensitif hanya karena “internal”.

7. Timeline as the Primary Debugging View

Payment investigation harus timeline-first.

Bukan table-first.

Timeline menyatukan event dari banyak subsystem:

  • API command received,
  • idempotency hit/miss,
  • risk decision,
  • route selected,
  • provider request sent,
  • provider response received,
  • webhook received,
  • state transition,
  • ledger journal posted,
  • reconciliation item matched,
  • settlement included,
  • payout created,
  • operator action executed.

Timeline harus menampilkan:

  • timestamp business,
  • timestamp received,
  • source system,
  • actor,
  • event type,
  • correlation ID,
  • idempotency key,
  • provider reference,
  • before/after state,
  • ledger journal if any,
  • evidence link.

8. Case Management Model

Backoffice tanpa case management akan berubah menjadi Slack-driven operations.

Slack boleh untuk komunikasi, tetapi bukan system of record.

Case adalah container untuk:

  • masalah,
  • owner,
  • severity,
  • evidence,
  • timeline,
  • notes,
  • tasks,
  • approvals,
  • commands,
  • resolution,
  • postmortem link.

8.1 Case Types

Case typeTriggerOwner utama
Support caseCustomer/merchant complaintSupport Ops
Payment investigationUnknown/mismatch paymentPayment Ops
Reconciliation breakInternal vs external mismatchFinance Ops
Settlement exceptionPayout/settlement issueSettlement Ops
Risk reviewFraud/velocity/suspicious patternRisk Ops
Compliance reviewKYB/sanctions/AML issueCompliance
Dispute caseChargeback/retrievalDispute Ops
Incident caseBroad production degradationSRE/Incident Commander

8.2 Case State Machine

State case tidak sama dengan state payment.

Case bisa resolved walaupun payment tetap failed.

Case bisa reopened walaupun payment status tidak berubah.

9. Case Data Model

Contoh schema awal:

create table ops_case (
    id uuid primary key,
    case_number text not null unique,
    case_type text not null,
    severity text not null,
    status text not null,
    title text not null,
    description text,
    merchant_id uuid,
    customer_reference text,
    owner_user_id uuid,
    team text not null,
    created_by uuid not null,
    created_at timestamptz not null,
    updated_at timestamptz not null,
    closed_at timestamptz,
    resolution_code text,
    resolution_summary text,
    constraint ops_case_status_check check (
        status in ('OPEN','TRIAGED','IN_PROGRESS','WAITING_EXTERNAL','WAITING_APPROVAL','RESOLVED','CLOSED','REOPENED','CANCELLED')
    )
);

create table ops_case_link (
    id uuid primary key,
    case_id uuid not null references ops_case(id),
    object_type text not null,
    object_id text not null,
    relation_type text not null,
    created_at timestamptz not null,
    created_by uuid not null,
    unique (case_id, object_type, object_id, relation_type)
);

create table ops_case_note (
    id uuid primary key,
    case_id uuid not null references ops_case(id),
    note_type text not null,
    body text not null,
    created_by uuid not null,
    created_at timestamptz not null,
    redaction_level text not null default 'INTERNAL'
);

create table ops_case_evidence (
    id uuid primary key,
    case_id uuid not null references ops_case(id),
    evidence_type text not null,
    storage_key text not null,
    sha256_hex text not null,
    uploaded_by uuid not null,
    uploaded_at timestamptz not null,
    retention_class text not null,
    classification text not null
);

ops_case_link penting karena case bisa link ke banyak object.

Jangan menaruh semua foreign key langsung di ops_case. Payment platform akan punya terlalu banyak object type.

10. Controlled Action Model

Backoffice action harus dimodelkan sebagai command.

Contoh action:

  • REFRESH_PROVIDER_STATE,
  • REPLAY_WEBHOOK,
  • MOVE_WEBHOOK_TO_QUARANTINE,
  • RELEASE_WEBHOOK_FROM_QUARANTINE,
  • CREATE_LEDGER_ADJUSTMENT,
  • CREATE_REFUND_CORRECTION,
  • HOLD_PAYOUT,
  • RELEASE_PAYOUT_HOLD,
  • MANUAL_RECONCILIATION_MATCH,
  • MARK_RECONCILIATION_BREAK_AS_ACCEPTED,
  • FREEZE_MERCHANT_CAPABILITY,
  • UNFREEZE_MERCHANT_CAPABILITY,
  • REQUEST_SETTLEMENT_REBUILD,
  • REGENERATE_MERCHANT_STATEMENT.

Setiap action punya:

  • actor,
  • role/capability,
  • target object,
  • reason code,
  • free-text reason,
  • evidence requirement,
  • idempotency key,
  • approval requirement,
  • risk level,
  • expected domain effect,
  • expected ledger effect,
  • audit event.
create table ops_action_request (
    id uuid primary key,
    action_type text not null,
    target_type text not null,
    target_id text not null,
    case_id uuid references ops_case(id),
    status text not null,
    requested_by uuid not null,
    requested_at timestamptz not null,
    reason_code text not null,
    reason_text text not null,
    idempotency_key text not null,
    request_payload jsonb not null,
    risk_level text not null,
    executed_at timestamptz,
    executed_by uuid,
    result_payload jsonb,
    error_code text,
    error_message text,
    unique (action_type, target_type, target_id, idempotency_key),
    constraint ops_action_status_check check (
        status in ('DRAFT','REQUESTED','APPROVED','REJECTED','EXECUTING','SUCCEEDED','FAILED','CANCELLED')
    )
);

Backoffice action request adalah audit anchor.

Bahkan jika action gagal, kegagalan itu harus tercatat.

11. Command Execution Pattern

Jangan biarkan UI memanggil database domain langsung.

Pattern yang lebih aman:

Backoffice API bertanggung jawab untuk:

  • authentication,
  • authorization,
  • policy check,
  • action request creation,
  • approval workflow,
  • audit context,
  • command dispatch,
  • result recording.

Domain service bertanggung jawab untuk:

  • invariant,
  • state transition,
  • ledger posting,
  • idempotency,
  • consistency,
  • events.

12. Manual Adjustment Is Not Manual Balance Edit

Manual adjustment sering diperlukan.

Contoh:

  • fee correction,
  • settlement correction,
  • goodwill credit,
  • chargeback fee correction,
  • reconciliation correction,
  • rounding correction,
  • provider report correction,
  • merchant compensation,
  • operational loss booking.

Tetapi adjustment harus lewat ledger.

Jangan pernah:

update merchant_balance set available = available + 100000 where merchant_id = ...;

Yang benar:

Create ledger journal:
  Debit  Platform Operational Loss Expense  IDR 100,000
  Credit Merchant Payable                   IDR 100,000
Reason: SETTLEMENT_CORRECTION
Case: CASE-2026-000123
Evidence: provider settlement file + finance approval

12.1 Adjustment Request Schema

create table ledger_adjustment_request (
    id uuid primary key,
    case_id uuid not null references ops_case(id),
    merchant_id uuid,
    currency char(3) not null,
    amount_minor numeric(38, 0) not null,
    adjustment_type text not null,
    reason_code text not null,
    reason_text text not null,
    status text not null,
    requested_by uuid not null,
    requested_at timestamptz not null,
    approved_by uuid,
    approved_at timestamptz,
    ledger_journal_id uuid,
    idempotency_key text not null unique,
    constraint adjustment_amount_positive check (amount_minor > 0)
);

Direction jangan disimpan sebagai tanda negatif bebas.

Lebih aman memakai adjustment_type dan posting rule.

Contoh:

Adjustment typeDebitCredit
MERCHANT_CREDITPlatform Ops LossMerchant Payable
MERCHANT_DEBITMerchant PayablePlatform Ops Recovery
FEE_REFUNDPlatform RevenueMerchant Payable
RESERVE_RELEASE_CORRECTIONMerchant ReserveMerchant Available
RESERVE_HOLD_CORRECTIONMerchant AvailableMerchant Reserve

13. Reconciliation Operations

Reconciliation break sering butuh intervensi manusia.

Tetapi manual match harus tetap terkendali.

Action reconciliation:

  • accept exact match,
  • accept tolerance match,
  • manual many-to-one match,
  • manual one-to-many match,
  • classify as timing difference,
  • classify as provider fee difference,
  • classify as bank fee difference,
  • classify as duplicate external record,
  • create correction proposal,
  • send to provider investigation,
  • mark as unrecoverable after approval.

Manual match tidak boleh hanya mengubah status break.

Ia harus menyimpan:

  • records yang dimatch,
  • rule/version atau manual reason,
  • amount delta,
  • tolerance policy,
  • actor,
  • approver if needed,
  • resulting ledger correction if any.
create table reconciliation_manual_decision (
    id uuid primary key,
    break_id uuid not null,
    decision_type text not null,
    reason_code text not null,
    reason_text text not null,
    amount_delta_minor numeric(38, 0),
    currency char(3),
    decided_by uuid not null,
    decided_at timestamptz not null,
    approved_by uuid,
    approved_at timestamptz,
    correction_journal_id uuid,
    evidence_id uuid references ops_case_evidence(id)
);

14. Webhook Replay and Repair

Webhook repair adalah fitur backoffice yang sangat berguna dan sangat berbahaya.

Command yang aman:

  • replay raw webhook event yang sudah tersimpan,
  • re-run parser/mapping version tertentu,
  • re-run state transition idempotently,
  • move event from quarantine to retry queue,
  • mark poison after investigation,
  • fetch provider current state.

Command yang tidak aman:

  • create fake webhook tanpa evidence,
  • edit raw webhook payload,
  • delete duplicate webhook,
  • force apply webhook walaupun signature invalid,
  • skip state machine validation.

Webhook replay harus idempotent.

Replay harus melewati pipeline yang sama. Jangan buat shortcut khusus backoffice yang langsung mengubah state.

15. Provider State Inquiry

Untuk unknown payment state, operator sering butuh tombol:

Refresh from provider

Tombol ini tidak boleh hanya “call provider and update status”.

Harus menjadi command:

ProviderStateInquiryCommand
- target payment attempt
- provider account
- provider reference
- initiated by operator/system
- reason
- idempotency key
- inquiry timeout policy

Hasil inquiry harus dicatat sebagai evidence.

Jika provider mengatakan succeeded, domain service tetap harus cek:

  • apakah amount cocok,
  • currency cocok,
  • merchant/account cocok,
  • reference cocok,
  • state transition legal,
  • ledger posting belum ada,
  • operation idempotency belum pernah dipakai.

16. Merchant and Capability Operations

Backoffice sering punya tombol untuk merchant:

  • enable card payment,
  • disable payout,
  • freeze settlement,
  • change risk tier,
  • set reserve percentage,
  • change payout schedule,
  • update processing limit,
  • block refund,
  • require manual review.

Semua ini adalah control-plane change.

Harus ada:

  • effective time,
  • expiration time if temporary,
  • reason,
  • approver,
  • policy impact preview,
  • affected payment methods,
  • affected pending transactions,
  • audit trail.

Contoh command:

public record SetMerchantCapabilityRestrictionCommand(
    UUID merchantId,
    String capability,
    RestrictionMode mode,
    Instant effectiveAt,
    Optional<Instant> expiresAt,
    String reasonCode,
    String reasonText,
    UUID requestedBy,
    UUID caseId,
    String idempotencyKey
) {}

Jangan membuat boolean liar seperti:

merchant.payout_enabled = false

Lebih baik punya restriction model:

MERCHANT_CAPABILITY_RESTRICTION
- capability: PAYOUT
- mode: BLOCK
- reason: RISK_REVIEW
- source: BACKOFFICE
- effective_at
- expires_at
- status

Dengan begitu policy engine bisa menjawab:

Can merchant m_123 create payout now?
No. Blocked by restriction RISK_REVIEW until 2026-07-05.

17. Support View vs Finance View vs Risk View

Tidak semua operator perlu melihat hal yang sama.

ViewFokusSensitive access
SupportCustomer/merchant answerMinimum, masked
Payment OpsPayment lifecycle repairProvider refs, timeline
Finance OpsLedger, reconciliation, settlementAmount/accounting detail
Risk OpsFraud signals, holds, reviewRisk signals, device/customer linkage
ComplianceKYB, sanctions, freezeIdentity/evidence data
Engineering/SRETechnical event, trace, logsOperational metadata, not raw secrets

Backoffice perlu view-level authorization.

Jangan hanya ADMIN.

Payment platform dengan role ADMIN tunggal akan sulit lolos audit dan sulit dikendalikan saat organisasi tumbuh.

18. Authorization Model for Backoffice

Minimal model:

Subject: operator identity
Role: support_ops, payment_ops, finance_ops, risk_ops, compliance_ops, sre
Permission: action-level ability
Scope: merchant, region, currency, payment method, amount limit
Condition: time, approval, case required, evidence required

Contoh permission:

permission: payment.provider_state_inquiry
scope:
  region: ID
  payment_method: CARD
conditions:
  case_required: true
  max_actions_per_hour: 20

Contoh action yang lebih kuat:

permission: ledger.manual_adjustment.create
scope:
  currency: IDR
conditions:
  max_amount_minor: 100000000
  approval_required: true
  approver_role: finance_manager
  case_required: true
  evidence_required: true

Part 054 akan membahas operational safety controls ini lebih dalam.

19. Java Service Boundary

Backoffice service sebaiknya tidak langsung import repository dari Payment Core/Ledger.

Gunakan application-level command ports.

public interface PaymentOperationsPort {
    ProviderStateInquiryResult inquireProviderState(ProviderStateInquiryCommand command);
    WebhookReplayResult replayWebhook(WebhookReplayCommand command);
    PaymentTimeline getPaymentTimeline(PaymentTimelineQuery query);
}

public interface LedgerOperationsPort {
    LedgerAdjustmentResult createAdjustment(CreateLedgerAdjustmentCommand command);
    LedgerJournalView getJournal(UUID journalId);
    AccountStatementView getAccountStatement(AccountStatementQuery query);
}

public interface ReconciliationOperationsPort {
    ManualMatchResult createManualMatch(CreateManualMatchCommand command);
    BreakView getBreak(UUID breakId);
}

Backoffice API melakukan authorization dan workflow.

Domain port melakukan invariant.

20. Example: Safe Manual Fee Correction

Scenario:

Merchant dikenakan fee IDR 10,000 padahal harusnya IDR 7,500.

Bad solution:

update merchant_balance set available = available + 2500;

Safe flow:

Ledger posting:

Debit  Platform Fee Revenue        IDR 2,500
Credit Merchant Payable            IDR 2,500

Evidence:

  • original fee calculation snapshot,
  • pricing plan version,
  • merchant contract reference,
  • approval,
  • adjustment journal ID.

21. Example: Unknown Payment Repair

Scenario:

Client received timeout. Provider later shows payment successful. Webhook was lost or quarantined.

Safe flow:

  1. Operator opens payment timeline.
  2. Sees provider operation timeout.
  3. Sees no succeeded webhook.
  4. Opens case.
  5. Runs provider state inquiry.
  6. Inquiry returns SUCCEEDED with matching amount/currency/reference.
  7. Payment Core applies legal transition from UNKNOWN to SUCCEEDED.
  8. Ledger posts capture journal idempotently.
  9. Case resolves.

Unsafe flow:

Operator changes payment.status = SUCCEEDED in DB.

Why unsafe?

  • no provider evidence,
  • no ledger posting,
  • no idempotency,
  • no state machine validation,
  • no reconciliation link,
  • no audit event.

22. Backoffice API Shape

Example endpoints:

GET /ops/payments/{paymentId}/timeline
GET /ops/search?q=...
POST /ops/cases
POST /ops/cases/{caseId}/links
POST /ops/cases/{caseId}/notes
POST /ops/actions/provider-state-inquiry
POST /ops/actions/webhook-replay
POST /ops/actions/ledger-adjustment
POST /ops/actions/reconciliation-manual-match
POST /ops/actions/payout-hold
POST /ops/actions/payout-release
GET /ops/actions/{actionId}

Backoffice command response should not hide asynchronous workflow.

{
  "actionId": "act_01J...",
  "status": "WAITING_APPROVAL",
  "approvalRequired": true,
  "requiredApproverRole": "FINANCE_MANAGER",
  "caseId": "case_01J..."
}

Or:

{
  "actionId": "act_01J...",
  "status": "SUCCEEDED",
  "domainResult": {
    "ledgerJournalId": "lj_01J...",
    "paymentId": "pi_01J..."
  }
}

23. Audit Event for Backoffice Action

Audit event should be structured.

{
  "eventType": "OPS_ACTION_EXECUTED",
  "eventId": "audit_01J...",
  "occurredAt": "2026-07-02T10:12:22Z",
  "actor": {
    "userId": "usr_123",
    "role": "FINANCE_OPS",
    "sessionId": "sess_456",
    "ipAddressHash": "..."
  },
  "action": {
    "actionId": "act_789",
    "actionType": "CREATE_LEDGER_ADJUSTMENT",
    "targetType": "MERCHANT",
    "targetId": "m_123"
  },
  "reason": {
    "code": "FEE_CORRECTION",
    "textHash": "..."
  },
  "caseId": "case_123",
  "approvalId": "appr_123",
  "result": {
    "status": "SUCCEEDED",
    "ledgerJournalId": "lj_123"
  }
}

Free text reason boleh disimpan, tetapi hati-hati PII.

Untuk audit query, gunakan structured fields.

24. Operational Dashboards

Backoffice harus punya dashboard untuk operasi, bukan hanya reporting bisnis.

Minimal:

  • open cases by type/severity/age,
  • actions waiting approval,
  • failed ops actions,
  • unknown payments pending resolution,
  • quarantined webhooks,
  • reconciliation breaks by age/amount,
  • payout holds,
  • merchant freezes,
  • manual adjustments by amount/user/team,
  • high-risk operators/actions,
  • SLA breach,
  • break-glass sessions,
  • failed authorization attempts,
  • policy denials.

Metric penting:

ops_case_open_total{case_type,severity}
ops_case_age_seconds_bucket{case_type,severity}
ops_action_total{action_type,status}
ops_action_approval_wait_seconds_bucket{action_type}
ops_manual_adjustment_amount_minor_sum{currency,reason_code}
ops_webhook_replay_total{provider,result}
ops_provider_inquiry_total{provider,result}
ops_reconciliation_manual_match_total{decision_type}

25. Backoffice UX Principles

Backoffice UX bukan estetika saja. UX yang buruk bisa menyebabkan kerugian finansial.

Prinsip:

  1. Show money impact before action.
  2. Show irreversible warning for finalized financial actions.
  3. Require reason and evidence for high-risk action.
  4. Make stale data visible.
  5. Show current state and allowed actions only.
  6. Prefer command preview over blind submit.
  7. Use comparison view for before/after.
  8. Show policy denial reason.
  9. Mask sensitive data by default.
  10. Make audit trail visible to operator.

Example preview:

Action: Create Merchant Credit Adjustment
Merchant: Example Store
Amount: IDR 2,500
Reason: Fee correction
Ledger impact:
  Debit  Platform Fee Revenue  IDR 2,500
  Credit Merchant Payable      IDR 2,500
Requires approval: Yes, Finance Manager
Can be reversed: Yes, via reversal journal

26. Failure Modes

FailureBad design resultCorrect design response
Operator clicks twiceDouble adjustmentAction idempotency key
Browser timeoutUnknown whether action executedAction request status lookup
Approval after target changedStale dangerous actionRevalidate at execution time
Search index staleWrong decisionRead authoritative detail before action
Operator lacks contextWrong repairCase/evidence required
Manual match wrongHidden reconciliation errorManual decision audit + reversal path
Admin abuseUnauthorized money movementSoD, approval, limits, audit
Sensitive data copied to notesCompliance leakRedaction/scanning/classification
Emergency access overusedControl bypassBreak-glass expiry/review

27. Testing Strategy

Test backoffice like production money movement.

27.1 Permission Tests

  • support cannot create ledger adjustment,
  • finance ops cannot unfreeze sanctions block,
  • risk ops cannot see full bank account number,
  • SRE cannot create payout without approval,
  • approver cannot approve their own request.

27.2 Idempotency Tests

  • double submit action request,
  • browser retry after timeout,
  • repeated command dispatcher retry,
  • domain service duplicate command,
  • approval callback duplicate.

27.3 Ledger Safety Tests

  • adjustment must create balanced journal,
  • direct balance mutation impossible,
  • reversal creates new journal,
  • correction links to case and evidence,
  • amount sign cannot bypass posting rule.

27.4 Workflow Tests

  • approval required by amount,
  • approval rejected,
  • approval expires,
  • target state changes before approval,
  • command fails after approval,
  • case cannot close while action executing.

27.5 Audit Tests

  • every action has audit event,
  • actor/session captured,
  • reason captured,
  • approval captured,
  • before/after state captured,
  • sensitive fields redacted.

28. Anti-Patterns

28.1 One Big Admin Role

ROLE_ADMIN is not an operational model.

It is a risk acceptance statement disguised as authorization.

28.2 Direct Database Fixes as Normal Workflow

Emergency SQL might happen during incident.

But if it becomes normal operational workflow, the system architecture has failed.

28.3 Status Override Without Ledger Effect

Changing status without ledger posting creates split-brain financial truth.

28.4 Audit Logs as Text

Text logs are useful, but payment operations need structured audit events.

28.5 No Case Requirement for High-Risk Action

If an operator can create money-impacting adjustment without case/evidence, the platform is not defensible.

28.6 Backoffice Bypasses Domain Invariants

The domain model must protect against internal callers too.

Internal caller does not mean safe caller.

29. Build Order

Implement backoffice in this order:

  1. read-only search,
  2. object timeline,
  3. case management,
  4. action request model,
  5. audit event integration,
  6. low-risk command execution,
  7. approval workflow,
  8. ledger adjustment via posting rule,
  9. reconciliation manual decision,
  10. risk/compliance restrictions,
  11. break-glass and emergency workflow,
  12. dashboards and review reports.

Do not start with the dangerous buttons.

Start with visibility.

Then controlled action.

30. Production Readiness Checklist

Backoffice is production-ready only if:

  • every write action is a domain command,
  • every action is authorized at action-level,
  • high-risk actions require case/evidence/reason,
  • high-risk actions require approval,
  • maker cannot approve own request,
  • every action is idempotent,
  • ledger-impacting action posts balanced journal,
  • direct balance edit is impossible,
  • raw evidence is immutable,
  • audit trail is structured and searchable,
  • sensitive data is masked by default,
  • search index is not source of truth,
  • stale detail view blocks dangerous action,
  • break-glass is time-limited and reviewed,
  • dashboard shows pending/failing actions,
  • reconciliation/settlement/backoffice actions are linked.

31. Key Takeaways

Backoffice bukan admin UI.

Backoffice adalah operational safety layer untuk production payment system.

Desain yang benar:

  • memperlihatkan realitas sistem,
  • mengikat tindakan ke case dan evidence,
  • menjalankan command domain yang legal,
  • membuat efek ledger eksplisit,
  • menerapkan approval dan separation of duties,
  • menjaga audit trail defensible,
  • mencegah operator menjadi sumber hidden financial drift.

Kalau API publik adalah pintu depan payment platform, backoffice adalah ruang kontrol.

Ruang kontrol harus lebih aman daripada pintu depan, bukan sebaliknya.

References

Lesson Recap

You just completed lesson 53 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.