Files
goodgo-platform/PRICING_AUDIT_SUMMARY.md
Ho Ngoc Hai db7147a95d feat: add pricing checkout flow, MFA type fixes, and Wave 13 audit docs
- Pricing page: enhanced with checkout modal integration, plan
  comparison table, and subscription funnel
- Payment return page: new VNPay/MoMo callback handler
- Subscription components: new checkout-modal with payment method
  selection (VNPay, MoMo, ZaloPay)
- API modules: type-safe PII encryption, improved error handling in
  MFA/auth/payments/analytics/search/notifications modules
- Audit docs: comprehensive Wave 13 platform assessment, pricing
  audit, production readiness checklist
- Updated PROJECT_TRACKER with Wave 13 status

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-12 20:17:11 +07:00

15 KiB

GoodGo Pricing → Checkout Audit Summary

🎯 Quick Overview

Aspect Status Key Details
Pricing Page Complete /pricing displays 4 tiers, monthly/yearly toggle
Plan API Complete GET /subscriptions/plans with fallback data
Subscription Backend Complete CQRS pattern, domain entities, repositories
Payment Gateway Integration Complete VNPay, MoMo, ZaloPay ready to use
Payment API Complete Create payment, get status, handle callbacks
Database Models Complete Plan, Subscription, Payment, UsageRecord
Frontend Checkout Flow MISSING No modal/page to initiate payment
Payment Return Handler MISSING No page to handle gateway redirect
Subscription Auto-Creation MISSING Manual process after payment

🏗️ Architecture Overview

Frontend Stack

Pricing Page (/pricing)
    ↓ usePlans() hook
    ↓ React Query
API Client: subscriptionApi.getPlans()
    ↓ GET /subscriptions/plans
Backend (/subscriptions/plans endpoint)

Payment Flow (Currently Broken)

Pricing Page (Select Plan)
    ✅ Displays plans, prices, features
    ❌ CTAs link to /register instead of checkout

[MISSING] Checkout Modal/Page
    ❌ Not implemented
    ❌ No plan confirmation
    ❌ No payment method selection

[MISSING] Payment Creation
    ❌ Should call POST /payments
    ❌ Should redirect to paymentUrl

Payment Gateway (VNPay/MoMo/ZaloPay)
    ✅ Backend has createPaymentUrl implementations
    ✅ Signature verification ready
    ❌ Frontend redirect not implemented

[MISSING] Return Handler
    ❌ No page for gateway callback
    ❌ No payment status polling
    ❌ No subscription creation

[MISSING] Subscription Creation
    ❌ Should call POST /subscriptions
    ❌ Should show success message

Dashboard/Home
    ✅ Has payments page to view history
    ❌ No subscription management UI

📁 Frontend File Structure

apps/web/
├── app/[locale]/(public)/pricing/
│   └── page.tsx                          ✅ Main pricing page
│
├── lib/
│   ├── subscription-api.ts               ✅ API client & types (PlanDto, CreateSubscriptionResult, etc.)
│   ├── payment-api.ts                    ✅ API client & types (CreatePaymentResult, PaymentStatusDto, etc.)
│   └── hooks/
│       ├── use-subscription.ts           ✅ usePlans(), useBillingHistory(), useQuota()
│       └── use-payments.ts               ✅ useTransactions(), usePaymentStatus()
│
├── app/[locale]/(dashboard)/dashboard/
│   └── payments/page.tsx                 ✅ Transaction history viewer
│
└── components/
    └── (needs new components for checkout)
        ├── checkout-modal/               ❌ Missing
        ├── payment-provider-select/      ❌ Missing
        └── subscription-status/          ❌ Missing

🔧 Backend File Structure

apps/api/src/modules/
│
├── subscriptions/
│   ├── presentation/
│   │   ├── controllers/subscriptions.controller.ts       ✅ 8 endpoints
│   │   └── dto/
│   │       ├── create-subscription.dto.ts                ✅ { planTier, billingCycle }
│   │       ├── upgrade-subscription.dto.ts               ✅
│   │       ├── cancel-subscription.dto.ts                ✅
│   │       └── meter-usage.dto.ts                        ✅
│   │
│   ├── application/
│   │   ├── commands/
│   │   │   ├── create-subscription/                      ✅ Creates subscription
│   │   │   ├── upgrade-subscription/                     ✅
│   │   │   ├── cancel-subscription/                      ✅
│   │   │   └── meter-usage/                              ✅
│   │   └── queries/
│   │       ├── get-plan/                                 ✅ Returns PlanDto[]
│   │       ├── check-quota/                              ✅
│   │       └── get-billing-history/                      ✅
│   │
│   ├── domain/
│   │   ├── entities/subscription.entity.ts               ✅ CQRS aggregate
│   │   ├── events/                                       ✅ 5 domain events
│   │   └── repositories/subscription.repository.ts       ✅ Interface
│   │
│   └── infrastructure/
│       ├── repositories/prisma-subscription.repository.ts ✅
│       └── event-handlers/listing-created-usage.handler.ts ✅
│
└── payments/
    ├── presentation/
    │   ├── controllers/payments.controller.ts            ✅ 5 endpoints
    │   └── dto/
    │       ├── create-payment.dto.ts                     ✅ { provider, type, amountVND, description, returnUrl }
    │       ├── refund-payment.dto.ts                     ✅
    │       └── list-transactions.dto.ts                  ✅
    │
    ├── application/
    │   ├── commands/
    │   │   ├── create-payment/                           ✅ Main payment creation logic
    │   │   ├── handle-callback/                          ✅ Webhook handler
    │   │   └── refund-payment/                           ✅
    │   └── queries/
    │       ├── get-payment-status/                       ✅ Poll status
    │       └── list-transactions/                        ✅
    │
    ├── domain/
    │   ├── entities/payment.entity.ts                    ✅ CQRS aggregate
    │   ├── events/                                       ✅ 4 domain events
    │   ├── value-objects/money.vo.ts                     ✅
    │   └── repositories/payment.repository.ts            ✅ Interface
    │
    └── infrastructure/
        ├── repositories/prisma-payment.repository.ts     ✅
        └── services/
            ├── payment-gateway.interface.ts              ✅ IPaymentGateway
            ├── payment-gateway.factory.ts                ✅ Gets correct gateway
            ├── vnpay.service.ts                          ✅ createPaymentUrl() + verifyCallback()
            ├── momo.service.ts                           ✅ createPaymentUrl() + verifyCallback()
            └── zalopay.service.ts                        ✅ createPaymentUrl() + verifyCallback()

🔌 API Endpoints Summary

Subscription Endpoints

GET    /subscriptions/plans                    → PlanDto[]
GET    /subscriptions/plans/:tier              → PlanDto
POST   /subscriptions                          → CreateSubscriptionResult (requires auth)
PUT    /subscriptions/upgrade                  → UpgradeSubscriptionResult (requires auth)
DELETE /subscriptions                          → CancelSubscriptionResult (requires auth)
POST   /subscriptions/usage                    → MeterUsageResult (requires auth)
GET    /subscriptions/quota/:metric            → QuotaCheckResult (requires auth)
GET    /subscriptions/billing                  → BillingHistoryDto (requires auth)

Payment Endpoints

POST   /payments                               → CreatePaymentResult (requires auth)
POST   /payments/callback/:provider            → HandleCallbackResult (webhook)
GET    /payments/:id                           → PaymentStatusDto (requires auth)
GET    /payments                               → TransactionListDto (requires auth)
POST   /payments/:id/refund                    → RefundPaymentResult (admin only)

💰 Pricing Tiers

const TIERS = [
  {
    tier: 'FREE',
    monthlyVND: '0',
    yearlyVND: '0',
    maxListings: 3,
    maxSearches: 5,
  },
  {
    tier: 'AGENT_PRO',
    monthlyVND: '499,000',
    yearlyVND: '4,990,000',
    maxListings: 50,
    maxSearches: 30,
    popular: true,
  },
  {
    tier: 'INVESTOR',
    monthlyVND: '999,000',
    yearlyVND: '9,990,000',
    maxListings: 20,
    maxSearches: 100,
  },
  {
    tier: 'ENTERPRISE',
    monthlyVND: '4,990,000',
    yearlyVND: '49,900,000',
    maxListings: -1,      // Unlimited
    maxSearches: -1,      // Unlimited
  },
];

📊 Data Models (Prisma)

Plan

id: String @id
tier: PlanTier @unique (FREE, AGENT_PRO, INVESTOR, ENTERPRISE)
name: String
priceMonthlyVND: BigInt
priceYearlyVND: BigInt
maxListings: Int?
maxSavedSearches: Int?
maxAnalyticsQueries: Int?
maxMediaUploads: Int?
features: Json           // { analytics: true, aiValuation: false, ... }
isActive: Boolean

Subscription

id: String @id
userId: String @unique
user: User
planId: String
plan: Plan
status: SubscriptionStatus (ACTIVE, PAST_DUE, CANCELLED, EXPIRED)
currentPeriodStart: DateTime
currentPeriodEnd: DateTime
cancelledAt: DateTime?
createdAt: DateTime
updatedAt: DateTime

Payment

id: String @id
userId: String
provider: PaymentProvider (VNPAY, MOMO, ZALOPAY, BANK_TRANSFER)
type: PaymentType (SUBSCRIPTION, LISTING_FEE, DEPOSIT, FEATURED_LISTING)
amountVND: BigInt
status: PaymentStatus (PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED)
providerTxId: String?
callbackData: Json?
idempotencyKey: String?  ← Prevents duplicate payments
createdAt: DateTime
updatedAt: DateTime

🔑 Key Implementation Details

Payment Creation Flow (Backend)

User clicks "Pay Now"
  ↓
Frontend: POST /payments {
  provider: 'VNPAY',
  type: 'SUBSCRIPTION',
  amountVND: 499000,
  description: 'Agent Pro - Monthly',
  returnUrl: 'https://goodgo.vn/payment-return',
  idempotencyKey: UUID  ← Unique per payment attempt
}
  ↓
Backend CreatePaymentHandler:
  1. Check idempotencyKey (prevent duplicates)
  2. Validate amount (1 to 100 billion VND)
  3. Get payment gateway (VNPay/MoMo/ZaloPay)
  4. Call gateway.createPaymentUrl()
     - Returns paymentUrl: "https://gateway.com/pay?params..."
     - Returns providerTxId: "VNP-12345..."
  5. Mark payment as PROCESSING in DB
  6. Publish PaymentCreatedEvent
  7. Return to client: { paymentId, paymentUrl, providerTxId }
  ↓
Frontend:
  window.location = paymentUrl  ← Redirect to gateway
  ↓
User completes payment at gateway
  ↓
Gateway redirects to returnUrl with callback params
  ↓
Backend webhook: POST /payments/callback/vnpay?params...
  1. Verify callback signature
  2. Check payment status
  3. Update payment status in DB
  4. Publish PaymentCompletedEvent
  ↓
PaymentCompletedEvent triggers:
  - Send email notification
  - Update user's plan association (eventually)
  ↓
Frontend callback handler (if implemented):
  1. Get paymentId from URL
  2. Poll GET /payments/{paymentId}
  3. When status = COMPLETED:
     - POST /subscriptions { planTier, billingCycle }
     - Show success message
     - Redirect to dashboard

Payment Gateway Implementations

VNPay

// Signature: HMAC SHA-512
// Request via: URL parameters
// Response Code: vnp_ResponseCode = '00' means success
// Transaction ID: vnp_TransactionNo

MoMo

// Signature: HMAC SHA-256
// Request via: JSON POST body
// Response Code: resultCode = 0 means success
// Transaction ID: transId

ZaloPay

// Signature: HMAC SHA-256 (similar to MoMo)
// Request via: JSON POST body
// Response Code: return_code = 1 means success
// Transaction ID: zp_trans_id

🚨 Critical Gaps (What's Missing)

1. Checkout Modal/Page

What it should do:

  • Display selected plan details
  • Show monthly vs yearly price
  • Allow payment method selection (VNPay, MoMo, ZaloPay)
  • Show terms & conditions
  • Handle payment creation and redirect

Current: CTAs on pricing page link to /register instead of starting checkout

2. Payment Return Handler

What it should do:

  • Receive redirect from payment gateway
  • Extract payment status from URL/callback
  • Poll payment status via GET /payments/:id
  • Create subscription when payment succeeds
  • Show success/error UI

Current: No page exists for this flow

3. Subscription Auto-Creation

What it should do:

  • After successful payment, call POST /subscriptions
  • Pass planTier and billingCycle
  • Update user's subscription status
  • Redirect to dashboard

Current: Manual process, no UI

4. Subscription Management UI ⚠️ Partial

What exists:

  • Payments page shows transaction history

What's missing:

  • Subscription status/details page
  • Upgrade/downgrade plan UI
  • Cancel subscription UI
  • Usage/quota display

📋 Implementation Roadmap

Phase 1: Basic Checkout (1-2 days)

✅ Pricing page exists
❌ Add CheckoutModal component
❌ Add payment provider selector
❌ Create /payment-return page
❌ Implement payment polling
❌ Wire subscription creation

Phase 2: Full Integration (1-2 days)

✅ All backend endpoints ready
❌ Handle edge cases (timeout, user closes window, etc.)
❌ Add error recovery flows
❌ Add loading/success UI
❌ Test with all 3 payment providers

Phase 3: Subscription Management (1-2 days)

✅ Upgrade/downgrade API endpoints exist
✅ Cancel subscription API exists
❌ Build subscription detail page
❌ Add upgrade/downgrade UI
❌ Add cancel UI with confirmation
❌ Add usage quota display

Phase 4: Testing & Polish (1-2 days)

❌ E2E tests for all payment providers
❌ Error handling & edge cases
❌ Performance optimization
❌ Analytics/tracking integration

🎯 Next Steps

  1. Understand the desired checkout UX - Where/how should checkout start?

    • Modal from pricing page?
    • Separate checkout page?
    • Inline on pricing page?
  2. Create CheckoutModal component - Design it to match pricing page

    • Plan summary
    • Price breakdown
    • Payment provider selector
    • "Proceed to Payment" button
  3. Implement payment creation mutation - Hook into React Query

    • useCreatePayment() hook
    • Handle loading/error states
    • Redirect to paymentUrl
  4. Build /payment-return page - Handle gateway redirect

    • Parse URL params
    • Poll payment status
    • Create subscription on success
  5. Test with all 3 providers - Ensure all integrations work

    • Use sandbox/test credentials
    • Verify callbacks
  6. Add subscription management UI - Allow users to manage plans

    • View current subscription
    • Upgrade/downgrade
    • Cancel with confirmation

📚 Reference

Full audit document: PRICING_CHECKOUT_AUDIT.md

Key files to review:

  • Frontend: /apps/web/app/[locale]/(public)/pricing/page.tsx
  • Backend payments: /apps/api/src/modules/payments/
  • Backend subscriptions: /apps/api/src/modules/subscriptions/
  • Prisma schema: /prisma/schema.prisma (lines 451-514)

Status: Ready for checkout implementation
Estimated effort: 4-6 days
Complexity: Medium (all backend infrastructure is ready)