Payment Methods
System-level catalog of payment methods (cash, card, digital wallets, etc.) that businesses can enable for their operations.
Architecture
Hexagonal architecture with strict layer boundaries:
payment-methods/
├── domain/
│ └── payment-methods-repository.domain.ts # IPaymentMethodsRepository port + DI token
├── application/
│ └── payment-methods.service.ts # Use cases (depends on domain port only)
├── infrastructure/
│ └── payment-methods.repository.ts # Kysely implementation of the port
└── interfaces/
├── payment-methods.controller.ts # HTTP routes + Swagger + RBAC guards
├── dtos/
│ ├── create-payment-method.dto.ts
│ ├── update-payment-method.dto.ts
│ └── payment-method-with-relation-response.dto.ts
└── query/
└── paginate-payment-methods.query.ts
Domain Concepts
Payment Method
A named payment type (e.g. "Credit Card", "Cash", "PayPal") with:
paymentGroup— classification bucket (see groups below)businessId— if set, the method is business-specific; if null, it's a global/system methodisActive— soft-delete flaggeneratesAccountsReceivable/generatesAccountsPayable— financial ledger flagsmetadata(jsonb) — POS tile appearance (posButtonColor,posTileImageSize,posTileButtonSize; same keys as restaurant menus)imageUrl— optional tile image on the catalog row (business-scoped methods only; global catalog rows cannot setimageUrlvia API)
POS tile appearance
Restaurant bill-pay and admin Payment Methods → Config use the same POS metadata model as menus:
| Layer | metadata / imageUrl |
|---|---|
payment_method | Catalog defaults (global or business-owned method) |
business_payment_method | Per-business overrides (Config tab) |
| Document / bill-pay | Effective merge: BPM wins per metadata key; imageUrl = BPM if non-empty, else catalog |
Admin UI: Config tab edits BPM overrides; Document tab shows read-only effective preview. Saving appearance on Config bumps a refresh key so the Document tab refetches.
API: PATCH /business-payment-methods/:id accepts metadata and imageUrl. PATCH /payment-methods/:id accepts metadata for business-owned methods; imageUrl is rejected when businessId is null.
Payment Groups
| Group | Description |
|---|---|
electronicAndCardPayments | Credit/debit cards, POS terminals |
digitalWalletsAndMobilePayments | Apple Pay, Google Pay, mobile wallets |
cashAndManual | Cash, checks, manual entries |
bankRelated | Wire transfers, ACH, bank deposits |
cryptocurrency | Bitcoin, stablecoins |
buyNowPayLater | Afterpay, Klarna, installment plans |
otherSpecialized | Gift cards, store credit, vouchers |
Business Payment Method (related module)
When a payment method is created with a businessId, a businessPaymentMethod junction record is auto-created to link it. Businesses can also independently enable/disable global payment methods via the business-payment-methods module.
API Endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
POST | /payment-methods | Create a payment method | Create |
GET | /payment-methods | List all (paginated, searchable) | Read |
GET | /payment-methods/:id | Get by ID | Read |
PATCH | /payment-methods/:id | Partial update | Update |
DELETE | /payment-methods/:id | Delete | Delete |
GET | /payment-methods/business/:businessId | List active methods for a business | Read |
GET | /payment-methods/with-relation/:businessId | All methods + business link status | Read |
With-Relation Endpoint
The with-relation endpoint returns all active payment methods (global + business-specific) with a LEFT JOIN to businessPaymentMethod. This tells the caller which methods the business has enabled. Results are sorted: linked methods first (bpmOrder: 1), then unlinked (bpmOrder: 2), alphabetically by name within each group.
Design Decisions
-
Global vs. business-specific methods: Methods with
businessId = nullare global (available to all businesses). Methods with abusinessIdare custom to that business. -
Auto-link on creation: When creating a method with a
businessId, the repository automatically inserts abusinessPaymentMethodrecord. This keeps the two tables consistent without requiring two API calls. -
DI via Symbol token: The service depends on the
IPaymentMethodsRepositorydomain interface viaPAYMENT_METHODS_REPOSITORYSymbol token, not the concrete Kysely implementation. This enables testing with mock repositories. -
RBAC: All endpoints are protected by
RolesGuardwithPolicyResource.PaymentMethod+ per-endpointPolicyAction.