# Wallet Foundation

## Scope

The wallet foundation provides one tenant-owned wallet per currency, an append-only double-entry ledger, atomic balance projections, operational adjustments and reversals, initialization, read services, and reconciliation. The initial currency is TZS and every amount is an integer minor-unit value.

This milestone does not add a wallet UI or public API, and it does not connect message acceptance, usage accounting, SMPP submission, pricing, billing, or payments to wallet debits. Operational mutations are available only through authenticated server access to Artisan commands because the application does not yet have an approved financial web authorization policy.

## Invariants

- A tenant has at most one wallet for a currency. Tenant ownership is enforced on every query.
- Wallet and ledger currencies must match. Floating-point amounts are never accepted or stored.
- Every accepted mutation creates one immutable transaction and a balanced pair of ledger entries.
- The wallet balance is a locked projection of the ledger. It is changed in the same database transaction as the transaction and entries.
- A debit cannot make the available balance negative. An unsigned database column provides an additional storage guard.
- Each mutation requires an idempotency key. Only an HMAC digest and semantic request fingerprint are stored; the raw key is not logged or serialized.
- Reusing a key with identical semantics returns the original result. Reusing it with different semantics is rejected.
- A transaction may be reversed once. A reversal is a new transaction with opposite entries; historical rows are not edited or deleted.
- Frozen wallets accept operational credits but reject debits. Unfreezing restores debit capability. The pre-existing closed state remains supported for compatibility and rejects mutations.
- Existing reservation operations remain part of the established double-entry subsystem, but this milestone does not add or activate any message-charging path.

## Concurrency model

Mutations run in a database transaction and acquire `SELECT ... FOR UPDATE` on the tenant wallet before checking state, available funds, idempotency, or writing. The unique wallet/currency and idempotency constraints provide final database enforcement. This serializes concurrent credits and debits for a wallet, prevents lost updates, and permits at most one competing debit when only one can be funded.

`WalletFoundationService::debitWithinTransaction` is the future integration seam for commercial message charging. It requires an already-open outer transaction so a later milestone can place the debit beside message, SMS, usage, event, and outbox persistence. It is intentionally unused today.

## Operations

- `gateway:wallet:initialize` creates zero-balance TZS wallets for active tenants in bounded chunks and is safe to rerun.
- `gateway:wallet:create` creates an individual tenant wallet idempotently.
- `gateway:wallet:credit` and `gateway:wallet:debit` create audited operational adjustments with a reason and idempotency key.
- `gateway:wallet:reverse` creates an audited compensating transaction.
- `gateway:wallet:freeze` and `gateway:wallet:unfreeze` create audit records for state changes.
- `gateway:wallet:reconcile` compares projections, ledger entry balance, transaction balance chain, currencies, reversals, reservations, and idempotency uniqueness. It reports discrepancies and does not silently repair them.

`WalletQueryService` exposes tenant-scoped summary, bounded history, transaction detail, bounded date totals, and reconciliation reads for future authorized adapters.

## Schema and indexes

The migration extends `wallet_ledger_transactions` with the resulting balance, generic reference, operational reason, original reversal transaction, and actor. It adds wallet/date, tenant/date, reference, and one-reversal indexes. MySQL check constraints limit operation types, foreign keys protect reversal and actor references, and identifiers remain below MySQL's 64-character limit.

## Deployment and rollback

1. Back up the database and put workers that can write wallet data into a controlled state.
2. Deploy application code and run `php artisan migrate --force`.
3. Run `php artisan gateway:wallet:initialize`, then rerun it to confirm an all-skipped idempotent result.
4. Run `php artisan gateway:wallet:reconcile` for representative tenants and monitor audit/error output.
5. Resume workers. No SMS charging behavior changes after deployment.

Rollback requires stopping wallet writers first. Reverse operational transactions when business history must be preserved. The schema migration may then be rolled back only after confirming that no new operation types or extension fields are required; rollback removes only the extension fields and indexes and restores the prior operation constraint. Reapply the migration and rerun reconciliation after any rollback/reapply exercise.
