Skip to main content

Store Credit Module

Overview​

The store credit module implements a wallet + immutable ledger system for customer store credit. Each customer has at most one store credit account per business. All balance mutations are recorded as immutable ledger transactions for full auditability.

Store credit is a financial liability — the business owes the customer the credit amount until redeemed.

Domain Concepts​

ConceptDescription
AccountOne wallet per customer per business. Holds a denormalized balance and status (active/suspended).
TransactionImmutable ledger entry. Never updated or deleted. Types: issue, use, adjust, reverse.
ConfigPer-business policy settings (manual issuance cap, layaway refund policy).

Architecture​

store-credit/
├── domain/
│ └── store-credit-repository.domain.ts # IStoreCreditRepository port + input interfaces
├── application/
│ ├── store-credit.service.ts # Business logic orchestration
│ └── events/
│ ├── on-exchange-completed.handler.ts
│ ├── on-layaway-cancelled.handler.ts
│ ├── on-return-completed.handler.ts
│ └── on-sale-voided.handler.ts
├── infrastructure/
│ └── store-credit.repository.ts # Kysely adapter (implements IStoreCreditRepository)
└── interfaces/
├── store-credit.controller.ts # REST endpoints
├── dtos/ # Request validation
└── query/ # Query parameter validation

Dependency flow: Controller → Service → IStoreCreditRepository (port) ← Repository (adapter)

Key Design Decisions​

  1. Lazy account creation — Accounts are created on first issuance, not upfront.
  2. Immutable ledger — Transactions are never updated/deleted. Reversals create new reverse entries.
  3. Pessimistic locking — SELECT FOR UPDATE on debit operations prevents race conditions.
  4. Denormalized balance — Stored on the account for fast reads; ledger is the audit source of truth.
  5. Fail-safe event handlers — Store credit events never rethrow; they must never break core flows (returns, exchanges, layaways, sale voids).
  6. Feature flag gated — All endpoints require STORE_CREDIT_ENABLED business parameter.

API Endpoints​

All endpoints require authentication and the STORE_CREDIT_ENABLED feature flag.

MethodPathPermissionDescription
GET/store-credit/balance?customerIdReadGet account balance (returns 0.00 if no account)
GET/store-credit/history/:accountIdReadPaginated transaction history
POST/store-credit/issueCreateIssue credit (manual, return, exchange, layaway)
POST/store-credit/redeemCreateRedeem credit during a sale
POST/store-credit/reverse/:transactionId?locationIdUpdateReverse a redemption
POST/store-credit/accounts/:accountId/adjustUpdateManager balance adjustment
PUT/store-credit/accounts/:accountId/suspendUpdateSuspend account
PUT/store-credit/accounts/:accountId/reactivateUpdateReactivate account
GET/store-credit/reports/liabilityReadLiability report (JSON or CSV)
GET/store-credit/configReadGet business configuration
PUT/store-credit/configUpdateUpsert business configuration

Event Integration​

The module listens to events from other modules:

EventSourceAction
store-credit.issue-from-returnReturns moduleAuto-issue credit from return
store-credit.issue-from-exchangeExchanges moduleAuto-issue credit from exchange
store-credit.issue-from-layaway-cancellationLayaway moduleAuto-issue credit from layaway cancel
sale.updatedSales moduleAuto-reverse redemptions when sale is voided

Example Requests​

Issue Store Credit​

POST /store-credit/issue
{
"customerId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"locationId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"amount": 25.00,
"sourceType": "manual",
"referenceNote": "Customer loyalty reward"
}

Redeem Store Credit​

POST /store-credit/redeem
{
"customerId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"locationId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"amount": 10.00,
"sourceId": "sale-uuid-here",
"referenceNote": "Applied to sale"
}

Adjust Balance​

POST /store-credit/accounts/:accountId/adjust
{
"locationId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"delta": -5.00,
"referenceNote": "Manager correction for pricing error"
}

Liability Report​

GET /store-credit/reports/liability?from=2026-01-01&to=2026-12-31&format=json

Response:

{
"totalOutstandingBalance": "1500.00",
"totalIssued": "2000.00",
"totalUsed": "500.00",
"netChange": "1500.00",
"bySourceType": [
{ "sourceType": "manual", "totalIssued": "1200.00", "count": 15 },
{ "sourceType": "return", "totalIssued": "800.00", "count": 8 }
],
"byEmployee": [
{ "employeeId": "uuid", "employeeName": "John Doe", "totalIssued": "1000.00", "count": 10 }
]
}

Database Tables​

  • store_credit_account — One per customer per business (unique constraint)
  • store_credit_transaction — Immutable ledger with CHECK constraints
  • store_credit_config — One per business (upsert pattern)