# 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 ```javascript 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 ```prisma 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 ```prisma 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 ```prisma 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 ```typescript // Signature: HMAC SHA-512 // Request via: URL parameters // Response Code: vnp_ResponseCode = '00' means success // Transaction ID: vnp_TransactionNo ``` #### MoMo ```typescript // Signature: HMAC SHA-256 // Request via: JSON POST body // Response Code: resultCode = 0 means success // Transaction ID: transId ``` #### ZaloPay ```typescript // 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)