rl-docs-hub

Home · Apps · rl-bank-mvp


Bank MVP journeys & GraphQL schema (MVP contract)

Status: Draft MVP contract for rl-bank-api + first customer/staff clients.

This doc defines the minimum viable set of customer and staff journeys for the fake bank, and the corresponding GraphQL schema (MVP only) that rl-bank-api must implement. It is intentionally narrow: accounts, transactions, internal transfers, and basic staff approval flows only.

No cards, loans, or KYC flows are included here.


1. Customer journeys (MVP)

Assume authentication/session is already handled by an IdP + JWT. All customer flows below start from an authenticated customer context.

1.1 Login (happy path)

1.2 View accounts (list)

1.3 View account detail + transactions

1.4 Create an internal transfer (between own accounts)


2. Staff journeys (MVP)

Assume staff users log in via a staff portal (web/mobile) using the same IdP but different audience/roles. All flows start from an authenticated staff context.

2.1 Login (staff)

2.2 Search for a customer

2.3 View a customer’s accounts and recent transactions

2.4 View and approve/reject pending transfers


3. GraphQL schema (MVP only)

This section defines the MVP contract between rl-bank-api and clients. It is deliberately small; future slices can extend it via additional fields and mutations without breaking these basics.

Note: Names and shapes are written in GraphQL SDL. Implementation details (NestJS modules, resolvers, etc.) are out of scope here.

3.1 Core enums (MVP)

"Supported account types in the MVP."
enum AccountType {
  CHECKING
  SAVINGS
}

"High-level transaction categorisation."
enum TransactionType {
  DEBIT
  CREDIT
}

"Transaction posting state."
enum TransactionStatus {
  PENDING
  POSTED
  REVERSED
}

"Lifecycle of an internal transfer."
enum TransferStatus {
  PENDING
  APPROVED
  REJECTED
  COMPLETED
  FAILED
}

3.2 Core types (MVP)

"Authenticated customer user."
type Customer {
  id: ID!
  name: String
  email: String
}

"Authenticated staff user."
type StaffUser {
  id: ID!
  name: String
  email: String
  role: String
}

"Bank account owned by a customer."
type Account {
  id: ID!
  customerId: ID!
  name: String
  accountNumberMasked: String!
  type: AccountType!
  currency: String!
  status: String! # e.g. ACTIVE, FROZEN, CLOSED (string for now to avoid over-specifying)
  availableBalance: Float!
  currentBalance: Float!
}

"Ledger transaction for an account."
type Transaction {
  id: ID!
  accountId: ID!
  postedAt: String!
  description: String!
  amount: Float! # positive for credit, negative for debit in MVP
  type: TransactionType!
  status: TransactionStatus!
  runningBalance: Float
}

"Internal transfer between customer-owned accounts."
type Transfer {
  id: ID!
  fromAccountId: ID!
  toAccountId: ID!
  amount: Float!
  currency: String!
  status: TransferStatus!
  createdAt: String!
  approvedAt: String
  rejectedAt: String
  completedAt: String
  failureReason: String
  description: String
}

3.3 Query operations (MVP)

Customer-facing queries

"Returns the currently authenticated customer (self)."
me: Customer

"Returns accounts for the current customer.

When called in a customer context, `customerId` is derived from the token and
this field should be ignored. When called in a staff context, `customerId`
may be provided explicitly to view that customer's accounts."
customerAccounts(customerId: ID): [Account!]!

"Returns recent transactions for a specific account."
accountTransactions(accountId: ID!): [Transaction!]!

Staff-facing queries

"Returns the currently authenticated staff user (self)."
staffMe: StaffUser

"Staff search for customers by name/email/id."
searchCustomers(
  query: String!
  page: Int = 0
  pageSize: Int = 20
): [Customer!]!

"Transfers that are currently pending and require staff action."
pendingTransfersForApproval: [Transfer!]!

3.4 Mutations (MVP)

input InitiateTransferInput {
  fromAccountId: ID!
  toAccountId: ID!
  amount: Float!
  description: String
}

"Initiate an internal transfer between accounts owned by the same customer."
initiateTransfer(input: InitiateTransferInput!): Transfer!

"Approve a pending transfer as staff."
approveTransfer(transferId: ID!): Transfer!

"Reject a pending transfer as staff."
rejectTransfer(transferId: ID!, reason: String): Transfer!

4. MVP constraints & notes