- 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>
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
-
Understand the desired checkout UX - Where/how should checkout start?
- Modal from pricing page?
- Separate checkout page?
- Inline on pricing page?
-
Create CheckoutModal component - Design it to match pricing page
- Plan summary
- Price breakdown
- Payment provider selector
- "Proceed to Payment" button
-
Implement payment creation mutation - Hook into React Query
useCreatePayment()hook- Handle loading/error states
- Redirect to paymentUrl
-
Build /payment-return page - Handle gateway redirect
- Parse URL params
- Poll payment status
- Create subscription on success
-
Test with all 3 providers - Ensure all integrations work
- Use sandbox/test credentials
- Verify callbacks
-
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)