Skip to main content

Wallet Data Model

Database Schema

Core Tables

wallet_account

Primary wallet entity linking member to prepaid balance.

ColumnTypeDescription
idUUIDPrimary key
member_idUUIDFK to member
tenant_idUUIDMulti-tenant isolation
currencyVARCHAR(3)Currency code (e.g., "ZAR")
balanceINTEGERCurrent balance in cents
statusENUMPENDING, ACTIVE, SUSPENDED, CLOSED
created_atTIMESTAMPCreation time
updated_atTIMESTAMPLast modification

Indexes:

  • idx_wallet_member on (tenant_id, member_id) - unique
  • idx_wallet_status on (tenant_id, status)

wallet_transaction

Immutable ledger of all balance-affecting operations.

ColumnTypeDescription
idUUIDPrimary key
wallet_idUUIDFK to wallet_account
typeENUMCREDIT, DEBIT, ADJUSTMENT
amountINTEGERAmount in cents (positive=credit)
balance_afterINTEGERRunning balance after this txn
referenceVARCHARExternal reference (payment ID, etc)
descriptionVARCHARHuman-readable description
merchant_codeVARCHARPOS/merchant identifier
source_typeVARCHAROrigin (TOP_UP, PURCHASE, REFUND, etc)
posted_atTIMESTAMPWhen transaction was recorded

Indexes:

  • idx_txn_wallet_posted on (wallet_id, posted_at DESC)
  • idx_txn_reference on (reference) - unique when not null
  • idx_txn_merchant on (tenant_id, merchant_code, posted_at)

auto_topup_config

Configuration for automatic balance replenishment.

ColumnTypeDescription
idUUIDPrimary key
wallet_idUUIDFK to wallet_account (unique)
enabledBOOLEANWhether auto top-up is active
typeENUMTHRESHOLD, SCHEDULED
threshold_centsINTEGERTrigger when balance below this
amount_centsINTEGERAmount to add each time
schedule_cronVARCHARCron expression for scheduled type
mandate_refVARCHARStored payment method reference
last_run_atTIMESTAMPLast successful execution
next_run_atTIMESTAMPNext scheduled execution
failure_countINTEGERConsecutive failures

wallet_pass

Digital card tracking for Apple/Google Wallet.

ColumnTypeDescription
idUUIDPrimary key
wallet_idUUIDFK to wallet_account
providerENUMAPPLE, GOOGLE
pass_type_idVARCHARProvider-specific type ID
serial_numberVARCHARUnique pass identifier
push_tokenVARCHARPush notification token
device_idVARCHARDevice identifier
last_synced_atTIMESTAMPLast balance sync
installed_atTIMESTAMPWhen added to phone
uninstalled_atTIMESTAMPWhen removed (soft delete)

Indexes:

  • idx_pass_serial on (provider, pass_type_id, serial_number) - unique

Event Tables

wallet_outbox

Transactional outbox for reliable event publishing.

ColumnTypeDescription
idUUIDPrimary key
tenant_idUUIDMulti-tenant isolation
event_typeVARCHAREvent category
event_keyVARCHARSpecific event name
payloadJSONBEvent data
statusENUMPENDING, CLAIMED, COMPLETED, FAILED
claimed_byVARCHARWorker instance ID
claimed_atTIMESTAMPWhen claimed for processing
completed_atTIMESTAMPWhen processing finished
errorTEXTError message if failed
retry_countINTEGERNumber of retries
created_atTIMESTAMPCreation time

Indexes:

  • idx_outbox_pending on (tenant_id, status, created_at) where status = 'PENDING'
  • idx_outbox_claimed on (claimed_by, claimed_at) where status = 'CLAIMED'

Preference Tables

member_notification_preference

Member preferences for wallet notifications.

ColumnTypeDescription
idUUIDPrimary key
member_idUUIDFK to member
tenant_idUUIDMulti-tenant isolation
wallet_low_balance_enabledBOOLEANEnable low balance alerts
wallet_credit_enabledBOOLEANEnable top-up notifications
wallet_debit_enabledBOOLEANEnable purchase notifications
wallet_auto_topup_enabledBOOLEANEnable auto top-up alerts
email_enabledBOOLEANEmail channel enabled
whatsapp_enabledBOOLEANWhatsApp channel enabled
sms_enabledBOOLEANSMS channel enabled
push_enabledBOOLEANPush channel enabled
low_balance_thresholdINTEGERAlert threshold in cents

Data Relationships

Constraints & Rules

Balance Integrity

  • Balance is always recalculated from transaction history on reconciliation
  • Transactions are immutable (corrections via new adjustment transactions)
  • Balance cannot go negative (enforced at application layer)

Transaction Idempotency

  • External references must be unique
  • Duplicate transaction attempts are rejected

Multi-Tenancy

  • All queries include tenant_id filter
  • Cross-tenant data access is prevented at application layer