All challenges
Nodejunior

Authenticated Wallet Transactions

Build an authenticated Express wallet API with seeded wallets, ledger-backed deposits/withdrawals/transfers, validation, authorization, idempotency, and safe failure handling.

Product context

Our backend is expanding from ticket-only support tooling into a fintech wallet service. Wallet owners need to inspect balances and post money movements safely, while the system maintains an auditable transaction history and prevents access to other users’ wallets.

User story

As an authenticated wallet owner, I want to view my wallet and create deposit, withdrawal, and transfer transactions, so that balances are updated through a ledger without exposing or corrupting another user’s wallet.

Acceptance criteria

  • Add the wallet feature surface exactly at:
    • src/walletStore.js, exporting DEFAULT_WALLETS, createWalletStore(initialWallets), defaultWalletStore, and resetDefaultWalletStore().
    • src/wallets.js, exporting VALID_CURRENCIES, VALID_TRANSACTION_TYPES, applyWalletTransaction(store, params), and createWalletRouter(options).
    • Mount createWalletRouter() from src/app.js at /wallets without breaking existing routes.
  • Seed deterministic wallets using integer minor units:
    • wal_alice_usd, owner alice, currency USD, balance 10000.
    • wal_bob_usd, owner bob, currency USD, balance 2500.
    • wal_alice_eur, owner alice, currency EUR, balance 5000.
  • Expose the HTTP contract under /wallets:
    • GET /wallets/:walletId returns 200 { wallet }.
    • GET /wallets/:walletId/transactions returns 200 { items }.
    • POST /wallets/:walletId/transactions accepts { type, amount, currency, destinationWalletId?, idempotencyKey? } and returns 201 { transaction, wallet } for new successful deposits, withdrawals, and transfers.
    • Missing wallets return 404 { error: { code: 'WALLET_NOT_FOUND' } }.
  • Require x-user-id authentication for every /wallets route:
    • Missing authentication returns 401 { error: { code: 'AUTH_REQUIRED' } }.
    • Authenticated users may only read or transact against wallets they own; otherwise return 403 { error: { code: 'WALLET_FORBIDDEN' } }.
  • Validate transaction input before changing state:
    • type must be deposit, withdrawal, or transfer.
    • amount must be a positive safe integer.
    • currency must be one of USD, EUR, or GBP.
    • Request currency must match the source wallet currency.
    • Transfers must include destinationWalletId.
    • Return 400 { error: { code } } with stable codes such as INVALID_TRANSACTION_TYPE, INVALID_AMOUNT, INVALID_CURRENCY, CURRENCY_MISMATCH, or MISSING_DESTINATION_WALLET.
  • Model wallet history with append-only ledger entries. Each successful transaction should record id, walletId, type, amount, currency, balanceAfter, and createdAt; transfers should create linked debit and credit entries using a shared relationship field such as relatedTransactionId.
  • createWalletStore(initialWallets) must create isolated in-memory state so separate store instances do not leak mutations.
  • Support safe transaction failure handling:
    • Insufficient funds return 409 { error: { code: 'INSUFFICIENT_FUNDS' } }.
    • Unknown transfer destinations return 404 { error: { code: 'DESTINATION_WALLET_NOT_FOUND' } }.
    • Failed transactions must not leave partial balance or ledger changes.
  • Support idempotency on POST /wallets/:walletId/transactions when idempotencyKey is supplied. Repeating the same key for the same source wallet should return the original transaction with 200 and must not apply the balance change twice.

Constraints

  • Store and accept money amounts as integer minor units only.
  • Keep response shapes and error codes stable.
  • Preserve the existing app structure and ticket routes.
  • Use the existing Node/Jest/Express style in the codebase.

Deliverable

Implement the wallet store, transaction logic, router, and app mounting in src/walletStore.js, src/wallets.js, and src/app.js.

Time box

Plan to spend about 60–90 minutes on the implementation and verification.