Documentation Index
Fetch the complete documentation index at: https://docs-mstore.faisalaffan.com/llms.txt
Use this file to discover all available pages before exploring further.
Transaction Flow
Dokumentasi lengkap alur transaksi di MStore Backend, mencakup state machine, offline-first capability, inventory movement, dan journal accounting.
π― Overview
Sistem transaksi MStore dirancang dengan prinsip:
- β
State Machine untuk lifecycle management yang jelas
- β
Offline-First untuk operasional tanpa internet (CASH only)
- β
Multi-Payment support (CASH, QRIS, E-Wallet, VA, Credit Card)
- β
Inventory Integration dengan automatic stock movement
- β
Journal Accounting untuk setiap transaksi
- β
Idempotent untuk retry safety
ποΈ Transaction State Machine
State Definitions
| State | Description | Allowed Actions |
|---|
| draft | Transaksi baru dibuat, belum submit payment | Edit, Delete, Submit Payment |
| pending_payment | Menunggu konfirmasi pembayaran | Verify Payment, Cancel |
| paid | Pembayaran berhasil, belum sync accounting | Sync, Void |
| synced | Sudah sync ke accounting (journal posted) | Void (with reversal) |
| failed | Pembayaran gagal | - |
| expired | Pembayaran expired | - |
| voided | Transaksi dibatalkan | - |
π‘ Transaction API Endpoints
1. Create Transaction (Draft)
Membuat transaksi baru dalam status draft.
curl -X POST https://api.mushola-store.com/api/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"branch_code": "BRN-MST-OSK00001",
"cashier_id": 5,
"items": [
{
"product_id": 1238,
"quantity": 2,
"unit_price": 15000,
"discount": 0
}
],
"payment_method": "CASH",
"customer_id": null,
"notes": "Transaksi kasir"
}'
Response:
{
"code": 201,
"data": {
"id": 185,
"transaction_code": "TRX-MC01-BRN-MST-OSK00001-251013-5UJW-6SWG-7",
"status": "draft",
"subtotal": 30000,
"discount_total": 0,
"tax_total": 3000,
"grand_total": 33000,
"payment_method": "CASH",
"created_at": "2025-10-13T10:30:00Z"
}
}
2. Submit Payment
Mengubah status dari draft β pending_payment dan membuat payment request.
curl -X POST https://api.mushola-store.com/api/v1/transactions/185/submit-payment \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"payment_method": "QRIS",
"amount": 33000
}'
Response (QRIS):
{
"code": 200,
"data": {
"transaction_id": 185,
"transaction_code": "TRX-MC01-BRN-MST-OSK00001-251013-5UJW-6SWG-7",
"status": "pending_payment",
"payment": {
"qr_url": "https://xendit.co/qr/abc123.png",
"qr_string": "00020101021126...",
"reference_id": "qr_abc123xyz",
"expires_at": "2025-10-13T10:35:00Z"
}
}
}
Response (CASH):
{
"code": 200,
"data": {
"transaction_id": 185,
"transaction_code": "TRX-MC01-BRN-MST-OSK00001-251013-5UJW-6SWG-7",
"status": "paid",
"message": "CASH payment auto-verified"
}
}
3. Verify Payment
Verifikasi pembayaran dari payment gateway (webhook atau manual check).
curl -X POST https://api/v1/transactions/185/verify-payment \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"payment_gateway_ref": "qr_abc123xyz"
}'
Response:
{
"code": 200,
"data": {
"transaction_id": 185,
"status": "paid",
"paid_at": "2025-10-13T10:32:15Z",
"payment_method": "QRIS"
}
}
4. Sync Transaction (Accounting)
Sync transaksi ke accounting system (post journal entries).
curl -X POST https://api/v1/transactions/185/sync \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"code": 200,
"data": {
"transaction_id": 185,
"status": "synced",
"journal_code": "JV-2025-10-000123",
"journal_entries": [
{
"account_code": "1-10100",
"account_name": "Kas",
"debit": 33000,
"credit": 0
},
{
"account_code": "4-40100",
"account_name": "Penjualan",
"debit": 0,
"credit": 30000
},
{
"account_code": "2-20300",
"account_name": "PPN Keluaran",
"debit": 0,
"credit": 3000
}
],
"synced_at": "2025-10-13T10:33:00Z"
}
}
5. Void Transaction
Membatalkan transaksi (dengan reversal journal jika sudah synced).
curl -X POST https://api/v1/transactions/185/void \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"reason": "Customer request refund",
"voided_by": 5
}'
Response:
{
"code": 200,
"data": {
"transaction_id": 185,
"status": "voided",
"reversal_journal_code": "JV-2025-10-000124",
"voided_at": "2025-10-13T11:00:00Z"
}
}
π Offline-First Flow
Untuk transaksi CASH, sistem mendukung offline-first:
Flutter Implementation
Complete guide untuk Flutter + Isar DB
Backend Implementation
Complete backend implementation dengan Go
Lihat dokumentasi lengkap: Offline-First POS System
Lihat dokumentasi lengkap: Offline-First Architecture (coming soon)
π¦ Inventory Integration
Setiap transaksi yang paid atau synced akan otomatis membuat inventory movement:
Inventory Movement Flow
Movement Record Example
{
"movement_type": "SALES_OUT",
"warehouse_id": 1,
"inventory_id": 456,
"quantity": -2.0,
"unit": "PCS",
"reference_type": "transaction",
"reference_id": 185,
"reference_code": "TRX-MC01-BRN-MST-OSK00001-251013-5UJW-6SWG-7",
"notes": "Sales transaction",
"created_at": "2025-10-13T10:32:15Z"
}
π° Journal Accounting Integration
Setiap transaksi yang di-sync akan membuat journal entries:
Journal Mapping Rules
| Transaction Type | Debit Account | Credit Account |
|---|
| CASH Sales | 1-10100 (Kas) | 4-40100 (Penjualan) |
| QRIS Sales | 1-10200 (Bank) | 4-40100 (Penjualan) |
| Sales Tax | 1-10100/200 | 2-20300 (PPN Keluaran) |
| COGS | 5-50100 (HPP) | 1-13100 (Persediaan) |
Journal Entry Example
{
"journal_code": "JV-2025-10-000123",
"journal_date": "2025-10-13",
"description": "Sales Transaction TRX-MC01-BRN-MST-OSK00001-251013-5UJW-6SWG-7",
"entries": [
{
"account_code": "1-10100",
"account_name": "Kas",
"debit": 33000,
"credit": 0
},
{
"account_code": "4-40100",
"account_name": "Penjualan",
"debit": 0,
"credit": 30000
},
{
"account_code": "2-20300",
"account_name": "PPN Keluaran",
"debit": 0,
"credit": 3000
},
{
"account_code": "5-50100",
"account_name": "Harga Pokok Penjualan",
"debit": 20000,
"credit": 0
},
{
"account_code": "1-13100",
"account_name": "Persediaan Barang Dagang",
"debit": 0,
"credit": 20000
}
],
"total_debit": 53000,
"total_credit": 53000,
"status": "posted"
}
π§ͺ Testing Scenarios
Scenario 1: CASH Transaction (Happy Path)
test('should create and complete CASH transaction', async () => {
// 1. Create transaction
const tx = await createTransaction({
branch_code: 'BRN-MST-OSK00001',
payment_method: 'CASH',
items: [{ product_id: 1238, quantity: 2 }]
});
expect(tx.status).toBe('draft');
// 2. Submit payment (auto-verify for CASH)
const paid = await submitPayment(tx.id, { payment_method: 'CASH' });
expect(paid.status).toBe('paid');
// 3. Sync to accounting
const synced = await syncTransaction(tx.id);
expect(synced.status).toBe('synced');
expect(synced.journal_code).toBeDefined();
});
Scenario 2: QRIS Transaction with Webhook
test('should handle QRIS payment with webhook', async () => {
// 1. Create transaction
const tx = await createTransaction({
payment_method: 'QRIS',
items: [{ product_id: 1238, quantity: 2 }]
});
// 2. Submit payment (get QR code)
const payment = await submitPayment(tx.id, { payment_method: 'QRIS' });
expect(payment.status).toBe('pending_payment');
expect(payment.payment.qr_url).toBeDefined();
// 3. Simulate webhook from Xendit
const webhook = await handleWebhook({
id: payment.payment.reference_id,
external_id: tx.transaction_code,
status: 'PAID'
});
// 4. Verify transaction updated
const updated = await getTransaction(tx.id);
expect(updated.status).toBe('paid');
});
| Metric | Target | Notes |
|---|
| Create Transaction | < 200ms | Database insert |
| Submit Payment (CASH) | < 300ms | Auto-verify + inventory |
| Submit Payment (QRIS) | < 2s | External API call |
| Verify Payment | < 500ms | Update status + inventory |
| Sync Transaction | < 1s | Journal posting |
| Void Transaction | < 1s | Reversal journal |
π Security Considerations
1. Transaction Code Generation
// Format: TRX-{MERCHANT_CODE}-{BRANCH_CODE}-{YYMMDD}-{RANDOM_4}-{RANDOM_4}-{SEQ}
// Example: TRX-MC01-BRN-MST-OSK00001-251013-5UJW-6SWG-7
2. Idempotency
- Gunakan
idempotency_key untuk retry safety
- Cek duplicate
transaction_code sebelum insert
- Webhook handler harus idempotent
3. Authorization
- Cashier hanya bisa create/view transaksi di branch sendiri
- Manager bisa void transaksi
- Admin bisa view semua transaksi
π‘ Best Practices
- Selalu gunakan state machine untuk update status
- Simpan snapshot payment request/response di tabel
payments
- Buat inventory movement setelah payment verified
- Post journal entries setelah sync
- Handle webhook idempotent
- Log semua state transitions
DONβT β
- Jangan skip state machine (direct DB update)
- Jangan lupa create inventory movement
- Jangan post journal sebelum payment verified
- Jangan ignore webhook errors
- Jangan hardcode account codes
π Troubleshooting
Problem: Transaction Stuck in pending_payment
Symptoms: Status tidak update setelah payment success
Solution:
# Manual verify payment
curl -X POST /api/v1/transactions/{id}/verify-payment \
-d '{"payment_gateway_ref": "qr_abc123"}'
Problem: Journal Not Posted
Symptoms: Transaction status paid tapi belum ada journal
Solution:
# Manual sync
curl -X POST /api/v1/transactions/{id}/sync
Problem: Inventory Not Reduced
Symptoms: Stock tidak berkurang setelah transaksi
Solution:
- Cek
inventory_movements table
- Verify warehouse stock calculation
- Re-sync transaction jika perlu
Payment Gateway
Integrasi Xendit & Midtrans
Inventory Flow
Warehouse & stock management