Payments
Purpose
The bridge to external payment providers. Two gateways are supported, LiqPay and Mono. For each company, one config row in Payment settings picks the platform and binds credentials; the gateway sibling table holds the platform-specific configuration.
Every money movement that touches a gateway gets a Payment row — booking pay, pass purchase, wallet top-up, subscription payment. The polymorphic source_id + source_type columns identify what was being paid for.
Boundaries
- In: gateway settings, gateway-level payment records, webhook idempotency.
- Out:
- In-app money flow (balance changes, refund workflow) — Wallet. Wallet transaction and gateway Payment are two perspectives on the same movement.
- What is being paid for — referenced polymorphically. The Payment row does not own its
source. - SaaS subscription billing state — Billing. A subscription payment may produce a gateway Payment row here.
Entities
| ID | Entity | Role |
|---|---|---|
| ENT-036 | Payment settings | Per-company config row — picks platform, binds credentials |
| ENT-033 | LiqPay payment | LiqPay-specific config tied to a payment_settings row |
| ENT-034 | Mono settings | Mono-specific config tied to a payment_settings row |
| ENT-035 | Payment | Gateway-level money movement — polymorphic source |
Backend implementation
- Module:
libs/features/payments/ - Surface routing:
payments-client.module.ts(user-facing checkout + status),payments-admin.module.ts(admin reads + config). - Drizzle schema:
libs/shared/data-access-db/src/lib/schema/payments.schema.ts(pgSchema('payments')).
Cross-context relationships
- This context owns the
payments.*Postgres schema. - This context references:
- This context is referenced by:
Open questions
- OPEN:
paymentSettings (company_id, platform)lacks UNIQUE — see Payment settings. - OPEN:
payment.external_idlacks a partial UNIQUE — webhook idempotency relies on the handler; see Payment. - The gateway-level Payment row and the in-app Wallet transaction are two records of the same money movement and may drift if a webhook is missed — there is no DB-level cross-check.