Skip to content

feat(backend+frontend): saved bank accounts under user profile for recurring offramp #214

@0xDeon

Description

@0xDeon

Problem

Every time a user initiates an offramp settlement they have to re-enter their bank details from scratch (bank name, account number, account name). There is no way to save a bank account and reuse it. This friction is intentional for a first send but becomes a serious usability problem for the core use-case: regular monthly withdrawals of yield to a Nigerian bank account.

The settlement domain currently has no concept of a saved bank account — the settlement payload accepts raw bank details inline and that is the only path.

What's Needed

Backend

New domain: apps/api/internal/domain/bankaccount/

model.go       // BankAccount { id, user_id, bank_name, account_number, account_name, currency, country, is_default, verified_at }
service.go     // Add, List, SetDefault, Remove
repository.go  // Interface

New DB migration:

CREATE TABLE bank_accounts (
  id             UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id        UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  bank_name      TEXT NOT NULL,
  bank_code      TEXT,              -- e.g. GTB = "058" in Nigeria
  account_number TEXT NOT NULL,     -- encrypted at rest
  account_name   TEXT NOT NULL,
  currency       TEXT NOT NULL,     -- NGN | GHS | KES
  country        TEXT NOT NULL,
  is_default     BOOLEAN NOT NULL DEFAULT false,
  created_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE (user_id, account_number, bank_code)
);

Endpoints:

GET    /api/v1/users/{id}/bank-accounts           — list saved accounts
POST   /api/v1/users/{id}/bank-accounts           — add a new account
PATCH  /api/v1/users/{id}/bank-accounts/{acctId}  — set as default / update label
DELETE /api/v1/users/{id}/bank-accounts/{acctId}  — remove

Settlement endpoint update:

  • Accept either inline bank details (existing behaviour) or a bank_account_id pointing to a saved account
  • If bank_account_id is provided, pull the bank details from the saved account rather than requiring them inline

Frontend

Settings page — "Bank Accounts" section:

  • List saved accounts with default badge
  • "Add account" flow: bank selector (dropdown), account number input, confirm account name via Paystack/Flutterwave name enquiry API
  • Set default, remove account actions
  • Country/currency flag shown per account

Settlement flow update:

  • If the user has saved accounts, show them as quick-select options at the top of the settlement form
  • "Use a different account" option falls back to the manual entry form
  • After a manual entry succeeds, prompt: "Save this account for next time?"

Acceptance Criteria

  • User can save, list, and delete bank accounts via API
  • Account number stored encrypted at rest
  • One account can be marked default per user per currency
  • Settlement endpoint accepts bank_account_id as an alternative to inline bank details
  • Frontend settings page shows saved accounts with add/remove/default actions
  • Settlement flow shows saved accounts as quick-select, falls back to manual entry
  • "Save for next time?" prompt shown after first manual entry per account
  • Unit tests for bank account service (add, default toggling, remove)

Notes

  • Account name verification ("name enquiry") is a standard API call available via Paystack and Flutterwave before saving — this prevents users from adding wrong account numbers
  • Bank code lookup tables for NG/GH/KE should be stored in the DB or a config file, not hardcoded in the frontend

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave programbackendGo backend infrastructurefeatureNew feature or functionalityfrontendDApp frontend (Next.js)

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions