Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
487 lines
17 KiB
Markdown
487 lines
17 KiB
Markdown
# Tóm Tắt Kiểm Tra Định Giá → Thanh Toán GoodGo
|
|
|
|
## 🎯 Tổng Quan Nhanh
|
|
|
|
| Khía cạnh | Trạng thái | Chi tiết chính |
|
|
|--------|--------|-------------|
|
|
| **Trang Định Giá** | ✅ Hoàn chỉnh | `/pricing` hiển thị 4 gói, chuyển đổi tháng/năm |
|
|
| **API Gói dịch vụ** | ✅ Hoàn chỉnh | `GET /subscriptions/plans` với dữ liệu dự phòng |
|
|
| **Backend Đăng ký** | ✅ Hoàn chỉnh | Mẫu CQRS, thực thể miền, kho lưu trữ |
|
|
| **Tích hợp Cổng Thanh toán** | ✅ Hoàn chỉnh | VNPay, MoMo, ZaloPay sẵn sàng sử dụng |
|
|
| **API Thanh toán** | ✅ Hoàn chỉnh | Tạo thanh toán, lấy trạng thái, xử lý callback |
|
|
| **Mô hình Cơ sở dữ liệu** | ✅ Hoàn chỉnh | Plan, Subscription, Payment, UsageRecord |
|
|
| **Luồng Thanh toán Frontend** | ❌ THIẾU | Không có modal/trang để khởi tạo thanh toán |
|
|
| **Trình xử lý Trả về Thanh toán** | ❌ THIẾU | Không có trang xử lý chuyển hướng từ cổng |
|
|
| **Tự động Tạo Đăng ký** | ❌ THIẾU | Quy trình thủ công sau khi thanh toán |
|
|
|
|
---
|
|
|
|
## 🏗️ Tổng Quan Kiến Trúc
|
|
|
|
### Stack Frontend
|
|
```
|
|
Trang Định Giá (/pricing)
|
|
↓ hook usePlans()
|
|
↓ React Query
|
|
API Client: subscriptionApi.getPlans()
|
|
↓ GET /subscriptions/plans
|
|
Backend (endpoint /subscriptions/plans)
|
|
```
|
|
|
|
### Luồng Thanh toán (Hiện bị Hỏng)
|
|
```
|
|
Trang Định Giá (Chọn Gói)
|
|
✅ Hiển thị gói, giá, tính năng
|
|
❌ Nút CTA trỏ đến /register thay vì thanh toán
|
|
|
|
[THIẾU] Modal/Trang Thanh toán
|
|
❌ Chưa triển khai
|
|
❌ Không có xác nhận gói
|
|
❌ Không có lựa chọn phương thức thanh toán
|
|
|
|
[THIẾU] Tạo Thanh toán
|
|
❌ Cần gọi POST /payments
|
|
❌ Cần chuyển hướng đến paymentUrl
|
|
|
|
Cổng Thanh toán (VNPay/MoMo/ZaloPay)
|
|
✅ Backend có triển khai createPaymentUrl
|
|
✅ Xác minh chữ ký đã sẵn sàng
|
|
❌ Chưa triển khai chuyển hướng frontend
|
|
|
|
[THIẾU] Trình xử lý Trả về
|
|
❌ Không có trang nhận callback từ cổng
|
|
❌ Không có cơ chế poll trạng thái thanh toán
|
|
❌ Không có tạo đăng ký
|
|
|
|
[THIẾU] Tạo Đăng ký
|
|
❌ Cần gọi POST /subscriptions
|
|
❌ Cần hiển thị thông báo thành công
|
|
|
|
Bảng điều khiển/Trang chủ
|
|
✅ Có trang thanh toán để xem lịch sử
|
|
❌ Không có giao diện quản lý đăng ký
|
|
```
|
|
|
|
---
|
|
|
|
## 📁 Cấu Trúc Tệp Frontend
|
|
|
|
```
|
|
apps/web/
|
|
├── app/[locale]/(public)/pricing/
|
|
│ └── page.tsx ✅ Trang định giá chính
|
|
│
|
|
├── lib/
|
|
│ ├── subscription-api.ts ✅ API client & types (PlanDto, CreateSubscriptionResult, v.v.)
|
|
│ ├── payment-api.ts ✅ API client & types (CreatePaymentResult, PaymentStatusDto, v.v.)
|
|
│ └── hooks/
|
|
│ ├── use-subscription.ts ✅ usePlans(), useBillingHistory(), useQuota()
|
|
│ └── use-payments.ts ✅ useTransactions(), usePaymentStatus()
|
|
│
|
|
├── app/[locale]/(dashboard)/dashboard/
|
|
│ └── payments/page.tsx ✅ Trình xem lịch sử giao dịch
|
|
│
|
|
└── components/
|
|
└── (cần thêm component mới cho thanh toán)
|
|
├── checkout-modal/ ❌ Thiếu
|
|
├── payment-provider-select/ ❌ Thiếu
|
|
└── subscription-status/ ❌ Thiếu
|
|
```
|
|
|
|
---
|
|
|
|
## 🔧 Cấu Trúc Tệp Backend
|
|
|
|
```
|
|
apps/api/src/modules/
|
|
│
|
|
├── subscriptions/
|
|
│ ├── presentation/
|
|
│ │ ├── controllers/subscriptions.controller.ts ✅ 8 endpoint
|
|
│ │ └── dto/
|
|
│ │ ├── create-subscription.dto.ts ✅ { planTier, billingCycle }
|
|
│ │ ├── upgrade-subscription.dto.ts ✅
|
|
│ │ ├── cancel-subscription.dto.ts ✅
|
|
│ │ └── meter-usage.dto.ts ✅
|
|
│ │
|
|
│ ├── application/
|
|
│ │ ├── commands/
|
|
│ │ │ ├── create-subscription/ ✅ Tạo đăng ký
|
|
│ │ │ ├── upgrade-subscription/ ✅
|
|
│ │ │ ├── cancel-subscription/ ✅
|
|
│ │ │ └── meter-usage/ ✅
|
|
│ │ └── queries/
|
|
│ │ ├── get-plan/ ✅ Trả về PlanDto[]
|
|
│ │ ├── check-quota/ ✅
|
|
│ │ └── get-billing-history/ ✅
|
|
│ │
|
|
│ ├── domain/
|
|
│ │ ├── entities/subscription.entity.ts ✅ Aggregate CQRS
|
|
│ │ ├── events/ ✅ 5 sự kiện miền
|
|
│ │ └── 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 endpoint
|
|
│ └── dto/
|
|
│ ├── create-payment.dto.ts ✅ { provider, type, amountVND, description, returnUrl }
|
|
│ ├── refund-payment.dto.ts ✅
|
|
│ └── list-transactions.dto.ts ✅
|
|
│
|
|
├── application/
|
|
│ ├── commands/
|
|
│ │ ├── create-payment/ ✅ Logic tạo thanh toán chính
|
|
│ │ ├── handle-callback/ ✅ Trình xử lý webhook
|
|
│ │ └── refund-payment/ ✅
|
|
│ └── queries/
|
|
│ ├── get-payment-status/ ✅ Poll trạng thái
|
|
│ └── list-transactions/ ✅
|
|
│
|
|
├── domain/
|
|
│ ├── entities/payment.entity.ts ✅ Aggregate CQRS
|
|
│ ├── events/ ✅ 4 sự kiện miền
|
|
│ ├── 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 ✅ Lấy cổng thanh toán phù hợp
|
|
├── vnpay.service.ts ✅ createPaymentUrl() + verifyCallback()
|
|
├── momo.service.ts ✅ createPaymentUrl() + verifyCallback()
|
|
└── zalopay.service.ts ✅ createPaymentUrl() + verifyCallback()
|
|
```
|
|
|
|
---
|
|
|
|
## 🔌 Tóm Tắt Endpoint API
|
|
|
|
### Endpoint Đăng ký
|
|
```
|
|
GET /subscriptions/plans → PlanDto[]
|
|
GET /subscriptions/plans/:tier → PlanDto
|
|
POST /subscriptions → CreateSubscriptionResult (yêu cầu xác thực)
|
|
PUT /subscriptions/upgrade → UpgradeSubscriptionResult (yêu cầu xác thực)
|
|
DELETE /subscriptions → CancelSubscriptionResult (yêu cầu xác thực)
|
|
POST /subscriptions/usage → MeterUsageResult (yêu cầu xác thực)
|
|
GET /subscriptions/quota/:metric → QuotaCheckResult (yêu cầu xác thực)
|
|
GET /subscriptions/billing → BillingHistoryDto (yêu cầu xác thực)
|
|
```
|
|
|
|
### Endpoint Thanh toán
|
|
```
|
|
POST /payments → CreatePaymentResult (yêu cầu xác thực)
|
|
POST /payments/callback/:provider → HandleCallbackResult (webhook)
|
|
GET /payments/:id → PaymentStatusDto (yêu cầu xác thực)
|
|
GET /payments → TransactionListDto (yêu cầu xác thực)
|
|
POST /payments/:id/refund → RefundPaymentResult (chỉ quản trị viên)
|
|
```
|
|
|
|
---
|
|
|
|
## 💰 Các Gói Định Giá
|
|
|
|
```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, // Không giới hạn
|
|
maxSearches: -1, // Không giới hạn
|
|
},
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Mô Hình Dữ Liệu (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? ← Ngăn chặn thanh toán trùng lặp
|
|
createdAt: DateTime
|
|
updatedAt: DateTime
|
|
```
|
|
|
|
---
|
|
|
|
## 🔑 Chi Tiết Triển Khai Quan Trọng
|
|
|
|
### Luồng Tạo Thanh toán (Backend)
|
|
```
|
|
Người dùng nhấn "Thanh toán ngay"
|
|
↓
|
|
Frontend: POST /payments {
|
|
provider: 'VNPAY',
|
|
type: 'SUBSCRIPTION',
|
|
amountVND: 499000,
|
|
description: 'Agent Pro - Monthly',
|
|
returnUrl: 'https://goodgo.vn/payment-return',
|
|
idempotencyKey: UUID ← Duy nhất cho mỗi lần thanh toán
|
|
}
|
|
↓
|
|
Backend CreatePaymentHandler:
|
|
1. Kiểm tra idempotencyKey (ngăn trùng lặp)
|
|
2. Xác thực số tiền (1 đến 100 tỷ VND)
|
|
3. Lấy cổng thanh toán (VNPay/MoMo/ZaloPay)
|
|
4. Gọi gateway.createPaymentUrl()
|
|
- Trả về paymentUrl: "https://gateway.com/pay?params..."
|
|
- Trả về providerTxId: "VNP-12345..."
|
|
5. Đánh dấu thanh toán là PROCESSING trong DB
|
|
6. Phát sự kiện PaymentCreatedEvent
|
|
7. Trả về cho client: { paymentId, paymentUrl, providerTxId }
|
|
↓
|
|
Frontend:
|
|
window.location = paymentUrl ← Chuyển hướng đến cổng
|
|
↓
|
|
Người dùng hoàn tất thanh toán tại cổng
|
|
↓
|
|
Cổng chuyển hướng đến returnUrl với tham số callback
|
|
↓
|
|
Backend webhook: POST /payments/callback/vnpay?params...
|
|
1. Xác minh chữ ký callback
|
|
2. Kiểm tra trạng thái thanh toán
|
|
3. Cập nhật trạng thái thanh toán trong DB
|
|
4. Phát sự kiện PaymentCompletedEvent
|
|
↓
|
|
PaymentCompletedEvent kích hoạt:
|
|
- Gửi thông báo qua email
|
|
- Cập nhật liên kết gói của người dùng (cuối cùng)
|
|
↓
|
|
Trình xử lý callback frontend (nếu được triển khai):
|
|
1. Lấy paymentId từ URL
|
|
2. Poll GET /payments/{paymentId}
|
|
3. Khi status = COMPLETED:
|
|
- POST /subscriptions { planTier, billingCycle }
|
|
- Hiển thị thông báo thành công
|
|
- Chuyển hướng đến bảng điều khiển
|
|
```
|
|
|
|
### Triển Khai Cổng Thanh toán
|
|
|
|
#### VNPay
|
|
```typescript
|
|
// Chữ ký: HMAC SHA-512
|
|
// Yêu cầu qua: Tham số URL
|
|
// Mã phản hồi: vnp_ResponseCode = '00' nghĩa là thành công
|
|
// Mã giao dịch: vnp_TransactionNo
|
|
```
|
|
|
|
#### MoMo
|
|
```typescript
|
|
// Chữ ký: HMAC SHA-256
|
|
// Yêu cầu qua: Body JSON POST
|
|
// Mã phản hồi: resultCode = 0 nghĩa là thành công
|
|
// Mã giao dịch: transId
|
|
```
|
|
|
|
#### ZaloPay
|
|
```typescript
|
|
// Chữ ký: HMAC SHA-256 (tương tự MoMo)
|
|
// Yêu cầu qua: Body JSON POST
|
|
// Mã phản hồi: return_code = 1 nghĩa là thành công
|
|
// Mã giao dịch: zp_trans_id
|
|
```
|
|
|
|
---
|
|
|
|
## 🚨 Những Thiếu Sót Nghiêm Trọng (Những Gì Còn Thiếu)
|
|
|
|
### 1. Modal/Trang Thanh toán ❌
|
|
**Chức năng cần có:**
|
|
- Hiển thị chi tiết gói đã chọn
|
|
- Hiện giá theo tháng và theo năm
|
|
- Cho phép chọn phương thức thanh toán (VNPay, MoMo, ZaloPay)
|
|
- Hiển thị điều khoản và điều kiện
|
|
- Xử lý tạo thanh toán và chuyển hướng
|
|
|
|
**Hiện tại:** Nút CTA trên trang định giá trỏ đến `/register` thay vì bắt đầu thanh toán
|
|
|
|
### 2. Trình xử lý Trả về Thanh toán ❌
|
|
**Chức năng cần có:**
|
|
- Nhận chuyển hướng từ cổng thanh toán
|
|
- Trích xuất trạng thái thanh toán từ URL/callback
|
|
- Poll trạng thái thanh toán qua GET /payments/:id
|
|
- Tạo đăng ký khi thanh toán thành công
|
|
- Hiển thị giao diện thành công/lỗi
|
|
|
|
**Hiện tại:** Không có trang nào cho luồng này
|
|
|
|
### 3. Tự động Tạo Đăng ký ❌
|
|
**Chức năng cần có:**
|
|
- Sau khi thanh toán thành công, gọi POST /subscriptions
|
|
- Truyền planTier và billingCycle
|
|
- Cập nhật trạng thái đăng ký của người dùng
|
|
- Chuyển hướng đến bảng điều khiển
|
|
|
|
**Hiện tại:** Quy trình thủ công, không có giao diện
|
|
|
|
### 4. Giao diện Quản lý Đăng ký ⚠️ Một phần
|
|
**Những gì đã có:**
|
|
- Trang thanh toán hiển thị lịch sử giao dịch
|
|
|
|
**Những gì còn thiếu:**
|
|
- Trang trạng thái/chi tiết đăng ký
|
|
- Giao diện nâng cấp/hạ cấp gói
|
|
- Giao diện hủy đăng ký
|
|
- Hiển thị mức sử dụng/hạn mức
|
|
|
|
---
|
|
|
|
## 📋 Lộ Trình Triển Khai
|
|
|
|
### Giai đoạn 1: Thanh toán Cơ bản (1-2 ngày)
|
|
```
|
|
✅ Trang định giá đã tồn tại
|
|
❌ Thêm component CheckoutModal
|
|
❌ Thêm bộ chọn nhà cung cấp thanh toán
|
|
❌ Tạo trang /payment-return
|
|
❌ Triển khai poll thanh toán
|
|
❌ Kết nối tạo đăng ký
|
|
```
|
|
|
|
### Giai đoạn 2: Tích hợp Đầy đủ (1-2 ngày)
|
|
```
|
|
✅ Tất cả endpoint backend đã sẵn sàng
|
|
❌ Xử lý các trường hợp ngoại lệ (hết thời gian, người dùng đóng cửa sổ, v.v.)
|
|
❌ Thêm luồng phục hồi lỗi
|
|
❌ Thêm giao diện đang tải/thành công
|
|
❌ Kiểm tra với cả 3 nhà cung cấp thanh toán
|
|
```
|
|
|
|
### Giai đoạn 3: Quản lý Đăng ký (1-2 ngày)
|
|
```
|
|
✅ Endpoint API nâng cấp/hạ cấp đã tồn tại
|
|
✅ API hủy đăng ký đã tồn tại
|
|
❌ Xây dựng trang chi tiết đăng ký
|
|
❌ Thêm giao diện nâng cấp/hạ cấp
|
|
❌ Thêm giao diện hủy với xác nhận
|
|
❌ Thêm hiển thị hạn mức sử dụng
|
|
```
|
|
|
|
### Giai đoạn 4: Kiểm tra & Hoàn thiện (1-2 ngày)
|
|
```
|
|
❌ Kiểm tra E2E cho tất cả nhà cung cấp thanh toán
|
|
❌ Xử lý lỗi & các trường hợp ngoại lệ
|
|
❌ Tối ưu hóa hiệu suất
|
|
❌ Tích hợp phân tích/theo dõi
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Các Bước Tiếp Theo
|
|
|
|
1. **Hiểu UX thanh toán mong muốn** - Thanh toán nên bắt đầu ở đâu/như thế nào?
|
|
- Modal từ trang định giá?
|
|
- Trang thanh toán riêng?
|
|
- Trực tiếp trên trang định giá?
|
|
|
|
2. **Tạo component CheckoutModal** - Thiết kế phù hợp với trang định giá
|
|
- Tóm tắt gói
|
|
- Phân tích giá
|
|
- Bộ chọn nhà cung cấp thanh toán
|
|
- Nút "Tiến hành Thanh toán"
|
|
|
|
3. **Triển khai mutation tạo thanh toán** - Kết nối với React Query
|
|
- Hook `useCreatePayment()`
|
|
- Xử lý trạng thái đang tải/lỗi
|
|
- Chuyển hướng đến paymentUrl
|
|
|
|
4. **Xây dựng trang /payment-return** - Xử lý chuyển hướng từ cổng
|
|
- Phân tích tham số URL
|
|
- Poll trạng thái thanh toán
|
|
- Tạo đăng ký khi thành công
|
|
|
|
5. **Kiểm tra với cả 3 nhà cung cấp** - Đảm bảo tất cả tích hợp hoạt động
|
|
- Sử dụng thông tin đăng nhập sandbox/kiểm tra
|
|
- Xác minh callback
|
|
|
|
6. **Thêm giao diện quản lý đăng ký** - Cho phép người dùng quản lý gói
|
|
- Xem đăng ký hiện tại
|
|
- Nâng cấp/hạ cấp
|
|
- Hủy với xác nhận
|
|
|
|
---
|
|
|
|
## 📚 Tài Liệu Tham Khảo
|
|
|
|
Tài liệu kiểm tra đầy đủ: `PRICING_CHECKOUT_AUDIT.md`
|
|
|
|
Các tệp cần xem xét:
|
|
- Frontend: `/apps/web/app/[locale]/(public)/pricing/page.tsx`
|
|
- Backend thanh toán: `/apps/api/src/modules/payments/`
|
|
- Backend đăng ký: `/apps/api/src/modules/subscriptions/`
|
|
- Prisma schema: `/prisma/schema.prisma` (dòng 451-514)
|
|
|
|
---
|
|
|
|
**Trạng thái:** Sẵn sàng triển khai thanh toán
|
|
**Ước tính công sức:** 4-6 ngày
|
|
**Độ phức tạp:** Trung bình (toàn bộ hạ tầng backend đã sẵn sàng)
|