# GoodGo Platform Payment Module - Security Review File Inventory ## Overview Comprehensive file listing for the Order & Escrow entities security review in the payments module. Location: `/Users/velikho/Desktop/WORKING/goodgo-platform-ai/apps/api/src/modules/payments/` --- ## 1. DOMAIN LAYER - ENTITIES ### Core Entities | File | Description | |------|-------------| | `domain/entities/order.entity.ts` | **ORDER ENTITY** - Manages order lifecycle with state machine (CREATED→PAYMENT_PENDING→PAYMENT_CONFIRMED→ESCROW_HELD→SHIPPED→DELIVERED→ESCROW_RELEASED→COMPLETED). Validates transitions. Emits events: OrderCreatedEvent, OrderPaidEvent, OrderCancelledEvent. Critical fields: buyerId, sellerId, listingId, amount (Money VO), platformFee, sellerPayout. | | `domain/entities/escrow.entity.ts` | **ESCROW ENTITY** - Manages escrow lifecycle (PENDING→HELD→RELEASED/DISPUTED/REFUNDED). Stores escrow amount, fee, and calculated netPayout. Emits: EscrowHeldEvent, EscrowReleasedEvent, EscrowDisputedEvent. Validates state transitions. | | `domain/entities/payment.entity.ts` | **PAYMENT ENTITY** - Manages payment transactions (PENDING→PROCESSING→COMPLETED/FAILED/REFUNDED). Stores userId, provider (VNPAY/MOMO/ZALOPAY), type, amount, callbackData, idempotencyKey. Emits: PaymentCreatedEvent, PaymentCompletedEvent, PaymentFailedEvent, PaymentRefundedEvent. | ### Value Objects | File | Description | |------|-------------| | `domain/value-objects/money.vo.ts` | **MONEY VALUE OBJECT** - Wraps amounts as bigint in VND. Validates: amount > 0, max limit 999_999_999_999. Used for all financial amounts. | | `domain/value-objects/platform-fee.vo.ts` | **PLATFORM FEE VALUE OBJECT** - Calculates 5% platform fee. Methods: `fromOrderAmount()` (auto-calc 5%), `create()` (explicit amount). Validates fee >= 0. | --- ## 2. DOMAIN LAYER - REPOSITORIES (Interfaces) | File | Description | |------|-------------| | `domain/repositories/order.repository.ts` | **ORDER REPOSITORY INTERFACE** - Defines CRUD + query methods: findById, findByIdempotencyKey, findByBuyerId, findBySellerId, save, update. Idempotency protection. | | `domain/repositories/escrow.repository.ts` | **ESCROW REPOSITORY INTERFACE** - Defines: findById, findByOrderId, save, update. One escrow per order relationship. | | `domain/repositories/payment.repository.ts` | **PAYMENT REPOSITORY INTERFACE** - Defines: findById, findByProviderTxId, findByIdempotencyKey, findByUserId, save, update, **updateIfStatus** (atomic conditional update for race condition handling). | --- ## 3. DOMAIN LAYER - EVENTS | File | Description | |------|-------------| | `domain/events/order-created.event.ts` | Emitted when order created - contains orderId, buyerId, sellerId, listingId, amount | | `domain/events/order-paid.event.ts` | Emitted when payment confirmed - contains orderId, buyerId, amount | | `domain/events/order-cancelled.event.ts` | Emitted when order cancelled - contains orderId, buyerId, sellerId | | `domain/events/escrow-held.event.ts` | Emitted when escrow held - contains escrowId, orderId, amount | | `domain/events/escrow-released.event.ts` | Emitted when escrow released - contains escrowId, orderId, netPayout | | `domain/events/escrow-disputed.event.ts` | Emitted when escrow disputed - contains escrowId, orderId, reason | | `domain/events/payment-created.event.ts` | Emitted when payment created - contains paymentId, userId, provider, amount | | `domain/events/payment-completed.event.ts` | Emitted when payment completes - contains paymentId, userId, provider | | `domain/events/payment-failed.event.ts` | Emitted when payment fails - contains paymentId, userId, provider | | `domain/events/payment-refunded.event.ts` | Emitted when payment refunded - contains paymentId, userId, provider, amount | --- ## 4. INFRASTRUCTURE LAYER - REPOSITORIES (Implementations) | File | Description | |------|-------------| | `infrastructure/repositories/prisma-order.repository.ts` | **ORDER REPOSITORY IMPL** - Prisma ORM implementation. Stores: id, buyerId, sellerId, listingId, status, amountVND, platformFeeVND, sellerPayoutVND, idempotencyKey, metadata. Handles order persistence. | | `infrastructure/repositories/prisma-escrow.repository.ts` | **ESCROW REPOSITORY IMPL** - Prisma ORM implementation. Stores: id, orderId, amountVND, feeVND, status, heldAt, releasedAt, disputeReason, disputedAt. Handles escrow persistence. | | `infrastructure/repositories/prisma-payment.repository.ts` | **PAYMENT REPOSITORY IMPL** - Prisma ORM. Stores: id, userId, transactionId, provider, type, amountVND, status, providerTxId, callbackData, idempotencyKey. **CRITICAL: `updateIfStatus()` uses conditional WHERE clause for atomic race condition prevention** (Line 84-109). | --- ## 5. INFRASTRUCTURE LAYER - PAYMENT GATEWAY SERVICES ### Payment Gateway Interface | File | Description | |------|-------------| | `infrastructure/services/payment-gateway.interface.ts` | **GATEWAY INTERFACE** - Defines IPaymentGateway contract: createPaymentUrl(), verifyCallback(), refund(). CallbackVerifyResult includes: isValid, orderId, providerTxId, isSuccess, rawData. Sensitive for security. | ### VNPay Service | File | Description | |------|-------------| | `infrastructure/services/vnpay.service.ts` | **VNPAY PAYMENT GATEWAY** - Implements IPaymentGateway. **CALLBACK VERIFICATION (Line 72-105)**: Extracts secure hash, removes it from data, sorts params, generates HMAC-SHA512, uses crypto.timingSafeEqual() for constant-time comparison. Amount multiplied by 100 for VND cents. Returns isValid, orderId (vnp_TxnRef), providerTxId (vnp_TransactionNo), isSuccess (responseCode === '00'). Refund support. | ### MoMo Service | File | Description | |------|-------------| | `infrastructure/services/momo.service.ts` | **MOMO PAYMENT GATEWAY** - Implements IPaymentGateway. **CALLBACK VERIFICATION (Line 102-147)**: Extracts signature from data, rebuilds raw signature with accessKey, amount, extraData, IPN/redirect URLs, orderId, etc. Uses HMAC-SHA256, constant-time comparison via crypto.timingSafeEqual(). Success check: resultCode === '0'. Refund support. Amount as Number (not bigint in API). | ### ZaloPay Service | File | Description | |------|-------------| | `infrastructure/services/zalopay.service.ts` | **ZALOPAY PAYMENT GATEWAY** - Implements IPaymentGateway. **CALLBACK VERIFICATION (Line 98-144)**: Data passed as JSON string in 'data' field. MAC verified via HMAC-SHA256 with key2. Parses JSON data to extract app_trans_id and zp_trans_id. **SECURITY NOTE**: Catches JSON parse errors gracefully. Uses constant-time comparison. Refund support (key1). | ### Payment Gateway Factory | File | Description | |------|-------------| | `infrastructure/services/payment-gateway.factory.ts` | **GATEWAY FACTORY** - Returns appropriate gateway instance (VNPay/MoMo/ZaloPay) based on provider enum. | --- ## 6. APPLICATION LAYER - COMMANDS ### Order Commands | File | Description | |------|-------------| | `application/commands/create-order/create-order.command.ts` | **CREATE ORDER COMMAND** - Input: buyerId, sellerId, listingId, amountVND, idempotencyKey. Payload object. | | `application/commands/create-order/create-order.handler.ts` | **CREATE ORDER HANDLER** - Idempotency check via findByIdempotencyKey. Validates amount (Money VO). Calculates platform fee (5%) and seller payout. Creates OrderEntity + EscrowEntity (PENDING status). Saves both. Emits events. | | `application/commands/cancel-order/cancel-order.command.ts` | **CANCEL ORDER COMMAND** - Input: orderId, userId, reason. | | `application/commands/cancel-order/cancel-order.handler.ts` | **CANCEL ORDER HANDLER** - Verifies user owns order, validates state transition via entity.markCancelled(), saves, emits events. | ### Escrow Commands | File | Description | |------|-------------| | `application/commands/hold-escrow/hold-escrow.command.ts` | **HOLD ESCROW COMMAND** - Input: orderId. Admin-only operation. | | `application/commands/hold-escrow/hold-escrow.handler.ts` | **HOLD ESCROW HANDLER (Line 23-67)** - Fetches order + escrow by orderId. Calls escrow.hold() state transition. Updates both entities. Emits EscrowHeldEvent. **SECURITY NOTE**: No Redis lock - potential race condition if multiple concurrent requests. | | `application/commands/release-escrow/release-escrow.command.ts` | **RELEASE ESCROW COMMAND** - Input: orderId. Admin-only operation. | | `application/commands/release-escrow/release-escrow.handler.ts` | **RELEASE ESCROW HANDLER (Line 24-45)** - Fetches order + escrow by orderId. Calls escrow.release() state transition. Updates both entities. Emits EscrowReleasedEvent with netPayout. **SECURITY NOTE**: No Redis lock - potential race condition. | ### Payment Commands | File | Description | |------|-------------| | `application/commands/create-payment/create-payment.command.ts` | **CREATE PAYMENT COMMAND** - Input: userId, provider, type, amountVND, description, returnUrl, ipAddress, transactionId, idempotencyKey. | | `application/commands/create-payment/create-payment.handler.ts` | **CREATE PAYMENT HANDLER** - Idempotency check. Validates amount (Money VO). Gets payment gateway. Calls createPaymentUrl(). Creates PaymentEntity (PENDING status). Saves. Emits PaymentCreatedEvent. Returns paymentUrl for frontend redirect. | | `application/commands/refund-payment/refund-payment.command.ts` | **REFUND PAYMENT COMMAND** - Input: paymentId, reason, userId. Admin command. | | `application/commands/refund-payment/refund-payment.handler.ts` | **REFUND PAYMENT HANDLER** - Verifies payment exists, calls gateway.refund() with provider-specific args, updates payment status to REFUNDED, emits PaymentRefundedEvent. | ### Callback Handler (CRITICAL) | File | Description | |------|-------------| | `application/commands/handle-callback/handle-callback.command.ts` | **HANDLE CALLBACK COMMAND** - Input: provider (PaymentProvider enum), callbackData (Record). | | `application/commands/handle-callback/handle-callback.handler.ts` | **HANDLE CALLBACK HANDLER (Line 32-110)** - **CRITICAL SECURITY FILE**. Gets gateway, calls verifyCallback() (validates signature). If invalid: throws ValidationException. If valid: **Uses `paymentRepo.updateIfStatus()` with conditional WHERE ['PENDING', 'PROCESSING']** (Line 48-55) - atomic update to prevent duplicate processing. If update returns null: checks if payment exists (already processed - idempotent response). If success: calls payment.emitCompleted(), else payment.emitFailed(). Publishes events. **STRONG RACE CONDITION PROTECTION via conditional update**. | --- ## 7. APPLICATION LAYER - QUERIES | File | Description | |------|-------------| | `application/queries/get-order-status/get-order-status.query.ts` | Query: Input orderId, userId (for authorization). | | `application/queries/get-order-status/get-order-status.handler.ts` | Fetches order, verifies ownership (buyer/seller), returns status + details. | | `application/queries/get-payment-status/get-payment-status.query.ts` | Query: Input paymentId, userId. | | `application/queries/get-payment-status/get-payment-status.handler.ts` | Fetches payment, verifies ownership, returns status + details. | | `application/queries/list-transactions/list-transactions.query.ts` | Query: Input userId, status (optional), limit, offset. | | `application/queries/list-transactions/list-transactions.handler.ts` | Lists payments for user with pagination, filters by status if provided. | --- ## 8. PRESENTATION LAYER - CONTROLLERS | File | Description | |------|-------------| | `presentation/controllers/orders.controller.ts` | **ORDERS CONTROLLER** - Routes: POST / (create order), GET /:id (status), POST /:id/cancel (cancel), POST /:id/escrow/hold (admin), POST /:id/escrow/release (admin). Auth: JwtAuthGuard, RolesGuard for admin ops. Converts DTO to commands. | | `presentation/controllers/payments.controller.ts` | **PAYMENTS CONTROLLER** - Routes: POST / (create payment), POST /callback/:provider (webhook - **Throttle + EndpointRateLimit**), GET /:id (status), GET (list), POST /:id/refund (admin refund). **CRITICAL: Callback endpoint has rate limiting (Throttle + EndpointRateLimitGuard)** - prevents callback flooding. | --- ## 9. PRESENTATION LAYER - DTOs | File | Description | |------|-------------| | `presentation/dto/create-order.dto.ts` | DTO: sellerId, listingId, amountVND (string), idempotencyKey (optional). | | `presentation/dto/cancel-order.dto.ts` | DTO: reason (string). | | `presentation/dto/create-payment.dto.ts` | DTO: provider (enum), type (enum), amountVND (string), description, returnUrl, transactionId (optional), idempotencyKey (optional). | | `presentation/dto/refund-payment.dto.ts` | DTO: reason (string). | | `presentation/dto/list-transactions.dto.ts` | DTO: status (optional), limit, offset. | --- ## 10. MODULE & TEST FILES | File | Description | |------|-------------| | `payments.module.ts` | **MODULE SETUP** - Registers repositories, services, handlers, controllers. | | `index.ts` (module level) | Exports public API. | | `infrastructure/repositories/index.ts` | Exports repository implementations. | | `infrastructure/services/index.ts` | Exports gateway services. | | `application/index.ts` | Exports command/query handlers. | | `domain/repositories/index.ts` | Exports repository interfaces. | | `domain/entities/index.ts` | Exports entities. | | `domain/value-objects/index.ts` | Exports VOs. | | `domain/events/index.ts` | Exports domain events. | | `presentation/controllers/index.ts` | Exports controllers. | | `presentation/dto/index.ts` | Exports DTOs. | ### Test Files | File | Description | |------|-------------| | `domain/__tests__/order.entity.spec.ts` | Order entity unit tests - state machine, transitions | | `domain/__tests__/escrow.entity.spec.ts` | Escrow entity unit tests - hold, release, dispute, refund | | `domain/__tests__/payment.entity.spec.ts` | Payment entity unit tests | | `domain/__tests__/money.vo.spec.ts` | Money VO validation tests | | `domain/__tests__/platform-fee.vo.spec.ts` | Platform fee calculation tests | | `domain/__tests__/payment-events.spec.ts` | Domain event emission tests | | `application/__tests__/create-order.handler.spec.ts` | Create order handler tests | | `application/__tests__/create-payment.handler.spec.ts` | Create payment handler tests | | `application/__tests__/handle-callback.handler.spec.ts` | Callback handling tests | | `application/__tests__/handle-callback-edge-cases.handler.spec.ts` | Callback edge cases (race conditions, idempotency) | | `application/__tests__/get-payment-status.handler.spec.ts` | Payment status query tests | | `application/__tests__/refund-payment.handler.spec.ts` | Refund command tests | | `application/__tests__/list-transactions.handler.spec.ts` | List transactions query tests | | `infrastructure/__tests__/vnpay.service.spec.ts` | VNPay gateway tests - signature verification | | `infrastructure/__tests__/momo.service.spec.ts` | MoMo gateway tests - HMAC-SHA256 verification | | `infrastructure/__tests__/zalopay.service.spec.ts` | ZaloPay gateway tests - JSON parsing + MAC verification | | `infrastructure/__tests__/payment-gateway.factory.spec.ts` | Factory pattern tests | --- ## SECURITY FINDINGS SUMMARY ### ✅ STRONG SECURITY MEASURES 1. **Callback Signature Verification**: All 3 providers (VNPay, MoMo, ZaloPay) verify HMAC signatures using `crypto.timingSafeEqual()` for constant-time comparison 2. **Atomic Race Condition Prevention**: `paymentRepo.updateIfStatus()` uses conditional WHERE clause to atomically update only if in PENDING/PROCESSING state 3. **Idempotency Protection**: Orders + Payments check idempotencyKey to prevent duplicate operations 4. **Rate Limiting**: Callback endpoint has Throttle + EndpointRateLimit decorators 5. **Authorization**: All endpoints require JwtAuthGuard; admin operations require RolesGuard 6. **Amount Validation**: Money VO validates: 0 < amount ≤ 999_999_999_999 VND 7. **State Machine Validation**: Order + Escrow enforce valid status transitions ### ⚠️ SECURITY CONCERNS (NEEDS REVIEW) 1. **Hold/Release Escrow Race Conditions**: No Redis lock on hold-escrow/release-escrow handlers - concurrent requests could cause state inconsistencies 2. **No Distributed Lock Mechanism**: Escrow operations not protected against simultaneous requests from different servers 3. **Callback Processing Idempotency**: While paymentRepo.updateIfStatus() prevents double-processing, idempotency check doesn't verify callback signature consistency 4. **Payment Provider Secrets**: Keys loaded from ConfigService - verify env variable encryption at rest 5. **Refund Authorization**: Only ADMIN role check - no business logic validation (e.g., refund window, max refund amount) 6. **Order/Escrow Update Race**: While holds are atomic for payments, order + escrow updates in handlers are done sequentially (2 DB calls), not atomically --- ## FILES NOT FOUND / NOT IN SCOPE - ❌ **Redis Lock Usage**: No Redis locks found in payments module. CONCERN: Critical for escrow hold/release. - ❌ **Shared Payment Utilities**: No external payment utility modules referenced - ❌ **Encryption for Payment Data**: No field-level encryption for sensitive payment data (though field-encryption service exists in shared module)