chore: organize docs — move 37 files from root into docs/ subfolders
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>
This commit is contained in:
486
docs/audits/PRICING_AUDIT_SUMMARY.md
Normal file
486
docs/audits/PRICING_AUDIT_SUMMARY.md
Normal file
@@ -0,0 +1,486 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user