Skip to content

Domain Model

Payment owns 2 tables (WebhookConfig + per-merchant rows in shared Configuration). 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():

ColumnType
idtext (PK, Snowflake)
createdAt / modifiedAt / deletedAttimestamptz
createdBy / modifiedBytext
metadatajsonb

3. Entities

3.1 WebhookConfig

PropertyValue
TableWebhookConfig
Sourcecore/src/models/schemas/public/webhook-config/schema.ts
Soft-deleteyes
FieldTypeRequiredDescription
nametextDisplay name
urltextWebhook endpoint
eventTypestext[]Subscribed events (e.g., ['mq-pay:attempt.success'])
statustextACTIVATED (default) / DEACTIVATED
signingMethodtextNONE (default) / HMAC_SHA256
secrettextSigning secret (used when signingMethod ≠ NONE)
headersjsonbCustom HTTP headers, default {}
metadatajsonb{ timeoutMs: 30000, maxRetries: 3 } defaults

Indexes: (status).

3.2 Configuration (payment-relevant rows)

PropertyValue
TableConfiguration (shared)
Sourcecore/src/models/schemas/public/configuration/schema.ts
FieldTypeRequiredDescription
codetextVNPAY_QR_MMS / VNPAY_PHONE_POS / per-merchant credential code
grouptextINTEGRATION for payment configs
principalTypetextMERCHANT for per-merchant rows
principalIdtextMerchant id
environmenttextDEVELOPMENT / PRODUCTION
tValuetextEncrypted JSON config (provider config like appId, masterMerchantCode)
credentialtextEncrypted credential — hidden from CRUD reads; AES-256-GCM
dataTypetextTEXT / JSON / BOOLEAN
statustextACTIVATED (default)

Constraints:

  • Partial unique: (group, code, principalId, principalType, environment) WHERE deletedAt IS NULL
  • Index: (group, code, environment), (group, principalType), GIN metadata, (principalId), (principalType, principalId), (status)

3.3 Transaction (from @nx/mq-pay)

FieldTypeNotes
statusenumNEW (1xx) / PARTIAL (3xx) / SETTLED (3xx) / CANCELLED (5xx) / BLOCKED (5xx) / CLOSED (5xx)
total / paiddecimalAmount tracking
sourceType / sourceIdtextPolymorphic — typically SaleOrder / SaleCheck
metadata.merchant.source.idjsonb pathMerchant scope, used by PaymentSocketEventService

3.4 PaymentAttempt (from @nx/mq-pay)

FieldTypeNotes
transactionIdtextFK
statusenumNEW (100) / SENT (200) / SUCCESS (300) / FAIL (500) / EXPIRED (510)
paymentProvidertextVNPAY_QR_MMS / VNPAY_PHONE_POS / VNPAY_SMART_POS / SYSTEM
metadata.sourcejsonb{ id, uid, type } — the originating order/check

4. Status Enums

4.1 PaymentProviders (from @nx/mq-pay)

ValueStatus
SYSTEMCash / manual bank transfer (in-platform)
VNPAY_QR_MMSSupported
VNPAY_PHONE_POSSupported
VNPAY_SMART_POSSupported (placeholder)
MOMONOT SUPPORTED
ZALOPAYNOT SUPPORTED
VIETQRPlaceholder

4.2 Transaction lifecycle

CodeStatusNotes
100NEWCreated
300PARTIALSome payment received
300SETTLEDFully paid (immutable)
500CANCELLEDAuto-cancelled (no payments made)
500BLOCKEDAdmin block (reversible)
500CLOSEDAdmin close (irreversible)

4.3 PaymentAttempt lifecycle

CodeStatus
100NEW
200SENT (QR available)
300SUCCESS
500FAIL
510EXPIRED

4.4 Credential actions (per IMQPayOptions.credentialGetter)

ActionUse
CREATE_PAYMENTSign provider request
VERIFY_IPNValidate provider IPN
CHECK_TRANSACTIONQuery transaction state
CANCEL_PAYMENTCancel pending payment
REFUNDRefund payment

4.5 Credential types

TypePurpose
REQUESTSign request to provider
RESPONSEVerify response from provider
IPNVerify IPN
PLATFORM_UNIT_TOKENPer-unit token (e.g., terminal-level)

4.6 WebSocket topics (PaymentWebSocketTopics)

TopicSource
observation/payment/transactionTransaction lifecycle events
observation/payment/payment-attemptAttempt events

5. Cross-entity Invariants

InvariantEnforcement
Encrypted Configuration.credential is never read directly via repository CRUDSchema marks credential as hidden; raw access via drizzle connector only inside PaymentConfigurationService
WebhookConfig.eventTypes[] contains valid event namesService-layer zod validation
Transaction state transitions follow MQ-Pay state machineOwned by @nx/mq-pay
At-least-once webhook delivery to subscribersWebhookDispatcherService with retry + timeout
Encryption secret is consistent across podsSourced from APP_ENV_APPLICATION_SECRET (must be identical)

6. Soft-delete Behavior

EntitySoft-deleteNotes
WebhookConfigDeactivate via status rather than delete is preferred
Configuration (payment rows)Migration seed re-creates on bootstrap

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.