Root now contains only essential files: README.md, CLAUDE.md, CHANGELOG.md, CONTRIBUTING.md Reorganized into: docs/audits/ — all audit reports & checklists (71 files) docs/architecture/ — codebase overview, implementation plan docs/guides/ — auth guide, implementation checklist docs/load-testing/ — k6 load test guides & endpoints docs/security/ — payment & security reviews Also removed 5 untracked debug/investigation files and cleaned up playwright-report/ & test-results/ artifacts. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
487 lines
15 KiB
Markdown
487 lines
15 KiB
Markdown
# 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)
|