Custom Plan (Custom Contract)
Mô Tả
Tài liệu này mô tả flow custom plan (giá tùy chỉnh theo hợp đồng) sử dụng Stripe Checkout với price_data. Custom plan tách biệt khỏi base plan mapping để tránh ảnh hưởng đến catalog price và flow đổi gói tiêu chuẩn.
Data Model Chính
subscriptions
pricing_type:customcustom_contract_id: liên kết hợp đồngpayment_provider_subscription_id: Stripesub_...
custom_contracts
- Điều khoản:
amount,currency,billing_interval,starts_at,ends_at, các giới hạn - Stripe neo dữ liệu:
provider_checkout_session_id(Stripecs_...)provider_price_id(Stripeprice_...)provider_subscription_item_id(Stripesi_...)
subscription_histories
Snapshot theo kỳ thanh toán:
custom_contract_idprovider_price_idprovider_subscription_item_idinvoice_id/payment_intent_id
Flow Tổng Quan
1) Tạo Custom Contract (Admin)
API:
POST /api/v1/admin/custom-contracts
Logic:
- Tạo hợp đồng ở trạng thái
draft - Gắn
subscription_idnếu đã có, hoặc tạo subscription mới vớipricing_type = custom
2) Gửi Payment Link (Admin)
API:
POST /api/v1/admin/custom-contracts/{id}/send-payment-link
Logic:
- Tạo Stripe Checkout Session với
price_data - Gắn metadata:
custom_contract_idsubscription_slug
- Update
custom_contracts.provider_checkout_session_id - Chuyển trạng thái hợp đồng:
draft→offered
3) Stripe Webhook (Checkout + Subscription)
customer.subscription.created
- Map subscription với custom contract:
subscriptions.pricing_type = customsubscriptions.custom_contract_id = <id>
- Cập nhật
custom_contracts:provider_price_idprovider_subscription_item_idsubscription_id
invoice.paid
- Tạo hoặc cập nhật
subscription_historiescho kỳ thanh toán - Update
custom_contracts.provider_price_id/provider_subscription_item_id(fallback theo invoice line) - Nếu thanh toán thành công:
custom_contracts.status = active
customer.subscription.updated
- Đồng bộ trạng thái subscription
- Tạo history định kỳ (renew/change) cho custom contract nếu cần
customer.subscription.deleted
- Hủy subscription và cập nhật contract:
cancellednếu bị hủy sớmexpirednếuends_atđã qua
Guardrails
- Custom plan không dùng
package_plan_to_providers - Các thay đổi giá/phạm vi phải thao tác qua Custom Contract + webhook
- Stripe metadata là khóa để map đúng hợp đồng
Ghi Chú
- Custom plan có thể nhận webhook theo thứ tự khác nhau; logic đã thiết kế idempotent để xử lý out-of-order.
- Trường hợp miễn phí (
amount = 0) vẫn cần theo dõiinvoice.paidđể kích hoạt chính xác.