A production-grade Flutter application demonstrating financial transaction safety, idempotency, and crash recovery for MSB-regulated environments.
The system prevents duplicate debits through idempotent transaction IDs:
-
Single ID Generation: Each transaction receives a permanent
client_transaction_id(UUID) that is generated once before the first API call and stored in the local database immediately. -
ID Reuse for All Operations: The same transaction ID is reused for:
- All retry attempts
- OTP verification requests
- Recovery queries
- Status checks
-
Duplicate Prevention: Before creating a new transaction, the system checks for any existing transaction with status
pending,unknown, orrisk_required. If found, it reuses the same transaction ID (regardless of amount). A new transaction ID is only generated when no unresolved transaction exists. -
Backend Idempotency: The backend uses the
client_transaction_idto ensure idempotency - multiple requests with the same ID are treated as the same transaction, preventing duplicate debits.
Implementation:
// Transaction ID generated ONCE
final transactionId = uuid.v4(); // e.g., "550e8400-e29b-41d4-a716-446655440000"
// Saved to DB immediately (before any network call)
await database.insertTransaction(transaction);
// ALL subsequent operations use SAME ID
await apiClient.sendTransaction(clientTransactionId: transactionId, ...);
await apiClient.verifyOtp(clientTransactionId: transactionId, ...);
await apiClient.getTransactionStatus(clientTransactionId: transactionId, ...);Transaction ID Lifecycle:
CREATED → PENDING → (retry with same ID) → SUCCESS or FAILED
↓
Only after SUCCESS/FAILED
can a new transaction be created
Safety Guarantees:
- User taps "Send" multiple times → Same ID, backend rejects duplicate
- App crashes mid-transaction → Recovery uses same ID
- OTP verification retries → Same ID, backend knows it's the same transaction
- Network timeouts → Retry uses same ID, no duplicate debit
The system automatically recovers transaction state when it's unknown or uncertain:
-
Immediate Persistence: Every transaction is persisted to Sqflite database before any network call, ensuring transaction state is never lost.
-
Recovery Detection: On app launch, the system queries for transactions with uncertain status:
pending: Transaction sent but response not receivedunknown: Network timeout or connection error occurredrisk_required: OTP verification needed
-
Backend Status Query: For each uncertain transaction, the system queries the backend using the stored
client_transaction_id:GET /transactions/{id}/status -
State Synchronization: The backend returns the authoritative status (success, failed, or pending), and the local database is updated accordingly.
Recovery Flow:
// On app start
1. Load all transactions from local DB
2. Filter: status ∈ {pending, unknown, risk_required}
3. For each transaction:
- Call backend: GET /transactions/{id}/status
- Update local DB with backend response
- Update UI stateWhy it's Safe:
- No Data Loss: Transaction state persisted before network calls
- Backend as Source of Truth: Backend determines final status
- Automatic Recovery: No manual intervention needed
- No Stuck Transactions: All uncertain states are resolved on next app launch
Example Scenarios:
-
Network Timeout:
- Transaction sent → Network timeout (504) → Status set to
unknown - App restarts → Recovery queries backend → Backend returns
success(transaction actually completed) - Result: Status updated correctly, no duplicate send
- Transaction sent → Network timeout (504) → Status set to
-
App Crash Before Response:
- Transaction sent → App crashes → Status remains
pendingin DB - App restarts → Recovery queries backend → Backend returns actual status
- Result: Transaction state restored correctly
- Transaction sent → App crashes → Status remains
-
Connection Error:
- Transaction sent → Connection lost → Status set to
unknown - App restarts → Recovery queries backend → Backend returns final status
- Result: System self-recovers without user intervention
- Transaction sent → Connection lost → Status set to
Built with safety, correctness, and recoverability as first-class concerns.