Domain Model
Payment owns 2 tables (
WebhookConfig+ per-merchant rows in sharedConfiguration). Transaction / PaymentAttempt / PaymentResult schemas are owned by@nx/mq-pay(cross-package read for payment).
1. Full ERD
2. Common Columns
Every entity adds these via generateCommonColumnDefs():
| Column | Type |
|---|---|
id | text (PK, Snowflake) |
createdAt / modifiedAt / deletedAt | timestamptz |
createdBy / modifiedBy | text |
metadata | jsonb |
3. Entities
3.1 WebhookConfig
| Property | Value |
|---|---|
| Table | WebhookConfig |
| Source | core/src/models/schemas/public/webhook-config/schema.ts |
| Soft-delete | yes |
| Field | Type | Required | Description |
|---|---|---|---|
name | text | ✓ | Display name |
url | text | ✓ | Webhook endpoint |
eventTypes | text[] | ✓ | Subscribed events (e.g., ['mq-pay:attempt.success']) |
status | text | ✓ | ACTIVATED (default) / DEACTIVATED |
signingMethod | text | ✓ | NONE (default) / HMAC_SHA256 |
secret | text | Signing secret (used when signingMethod ≠ NONE) | |
headers | jsonb | Custom HTTP headers, default {} | |
metadata | jsonb | { timeoutMs: 30000, maxRetries: 3 } defaults |
Indexes: (status).
3.2 Configuration (payment-relevant rows)
| Property | Value |
|---|---|
| Table | Configuration (shared) |
| Source | core/src/models/schemas/public/configuration/schema.ts |
| Field | Type | Required | Description |
|---|---|---|---|
code | text | ✓ | VNPAY_QR_MMS / VNPAY_PHONE_POS / per-merchant credential code |
group | text | ✓ | INTEGRATION for payment configs |
principalType | text | MERCHANT for per-merchant rows | |
principalId | text | Merchant id | |
environment | text | ✓ | DEVELOPMENT / PRODUCTION |
tValue | text | Encrypted JSON config (provider config like appId, masterMerchantCode) | |
credential | text | Encrypted credential — hidden from CRUD reads; AES-256-GCM | |
dataType | text | ✓ | TEXT / JSON / BOOLEAN |
status | text | ✓ | ACTIVATED (default) |
Constraints:
- Partial unique:
(group, code, principalId, principalType, environment) WHERE deletedAt IS NULL - Index:
(group, code, environment),(group, principalType), GINmetadata,(principalId),(principalType, principalId),(status)
3.3 Transaction (from @nx/mq-pay)
| Field | Type | Notes |
|---|---|---|
status | enum | NEW (1xx) / PARTIAL (3xx) / SETTLED (3xx) / CANCELLED (5xx) / BLOCKED (5xx) / CLOSED (5xx) |
total / paid | decimal | Amount tracking |
sourceType / sourceId | text | Polymorphic — typically SaleOrder / SaleCheck |
metadata.merchant.source.id | jsonb path | Merchant scope, used by PaymentSocketEventService |
3.4 PaymentAttempt (from @nx/mq-pay)
| Field | Type | Notes |
|---|---|---|
transactionId | text | FK |
status | enum | NEW (100) / SENT (200) / SUCCESS (300) / FAIL (500) / EXPIRED (510) |
paymentProvider | text | VNPAY_QR_MMS / VNPAY_PHONE_POS / VNPAY_SMART_POS / SYSTEM |
metadata.source | jsonb | { id, uid, type } — the originating order/check |
4. Status Enums
4.1 PaymentProviders (from @nx/mq-pay)
| Value | Status |
|---|---|
SYSTEM | Cash / manual bank transfer (in-platform) |
VNPAY_QR_MMS | Supported |
VNPAY_PHONE_POS | Supported |
VNPAY_SMART_POS | Supported (placeholder) |
MOMO | NOT SUPPORTED |
ZALOPAY | NOT SUPPORTED |
VIETQR | Placeholder |
4.2 Transaction lifecycle
| Code | Status | Notes |
|---|---|---|
100 | NEW | Created |
300 | PARTIAL | Some payment received |
300 | SETTLED | Fully paid (immutable) |
500 | CANCELLED | Auto-cancelled (no payments made) |
500 | BLOCKED | Admin block (reversible) |
500 | CLOSED | Admin close (irreversible) |
4.3 PaymentAttempt lifecycle
| Code | Status |
|---|---|
100 | NEW |
200 | SENT (QR available) |
300 | SUCCESS |
500 | FAIL |
510 | EXPIRED |
4.4 Credential actions (per IMQPayOptions.credentialGetter)
| Action | Use |
|---|---|
CREATE_PAYMENT | Sign provider request |
VERIFY_IPN | Validate provider IPN |
CHECK_TRANSACTION | Query transaction state |
CANCEL_PAYMENT | Cancel pending payment |
REFUND | Refund payment |
4.5 Credential types
| Type | Purpose |
|---|---|
REQUEST | Sign request to provider |
RESPONSE | Verify response from provider |
IPN | Verify IPN |
PLATFORM_UNIT_TOKEN | Per-unit token (e.g., terminal-level) |
4.6 WebSocket topics (PaymentWebSocketTopics)
| Topic | Source |
|---|---|
observation/payment/transaction | Transaction lifecycle events |
observation/payment/payment-attempt | Attempt events |
5. Cross-entity Invariants
| Invariant | Enforcement |
|---|---|
Encrypted Configuration.credential is never read directly via repository CRUD | Schema marks credential as hidden; raw access via drizzle connector only inside PaymentConfigurationService |
WebhookConfig.eventTypes[] contains valid event names | Service-layer zod validation |
| Transaction state transitions follow MQ-Pay state machine | Owned by @nx/mq-pay |
| At-least-once webhook delivery to subscribers | WebhookDispatcherService with retry + timeout |
| Encryption secret is consistent across pods | Sourced from APP_ENV_APPLICATION_SECRET (must be identical) |
6. Soft-delete Behavior
| Entity | Soft-delete | Notes |
|---|---|---|
WebhookConfig | ✓ | Deactivate via status rather than delete is preferred |
Configuration (payment rows) | ✓ | Migration seed re-creates on bootstrap |