Sơ đồ cơ sở dữ liệu cho gói và quyền truy cập hỗ trợ nâng cấp và add-on
Schema cơ sở dữ liệu cho gói và quyền (entitlements) hỗ trợ nâng cấp, add-on, trial và thu hồi mà không cần quy tắc mã cứng, dùng bảng rõ ràng và kiểm tra thời gian.

Tại sao gói và tính năng nhanh chóng trở nên lộn xộn
Trên trang giá, các gói có vẻ đơn giản: Basic, Pro, Enterprise. Vấn đề bắt đầu khi bạn cố biến những tên đó thành quy tắc truy cập thực sự trong ứng dụng.
Kiểm tra tính năng được mã hóa cứng (như if plan = Pro then allow X) hoạt động cho phiên bản đầu tiên. Rồi giá thay đổi. Một tính năng chuyển từ Pro xuống Basic, xuất hiện một add-on mới, hoặc một thỏa thuận bán hàng bao gồm gói tuỳ chỉnh. Ngay lập tức bạn có cùng một quy tắc sao chép khắp API, UI, ứng dụng mobile và các job nền. Bạn sửa một chỗ và quên chỗ khác. Người dùng nhận ra.
Vấn đề thứ hai là thời gian. Đăng ký không phải là một nhãn tĩnh; chúng thay đổi giữa chu kỳ. Ai đó nâng cấp hôm nay, hạ cấp tháng sau, tạm ngưng, hoặc huỷ khi vẫn còn thời gian đã trả. Nếu database của bạn chỉ lưu “gói hiện tại”, bạn mất dòng thời gian và không thể trả lời các câu hỏi cơ bản sau này: Họ có quyền gì vào thứ Ba tuần trước? Tại sao hỗ trợ chấp thuận hoàn tiền?
Add-on làm mọi thứ tồi tệ hơn vì chúng cắt ngang các gói. Một add-on có thể mở thêm chỗ ngồi, bỏ giới hạn, hoặc kích hoạt một tính năng cụ thể. Người ta có thể mua nó trên bất kỳ gói nào, gỡ sau đó, hoặc giữ lại sau khi hạ cấp. Nếu quy tắc được nhúng trong mã, bạn sẽ có một đống các trường hợp đặc biệt ngày càng lớn.
Những tình huống thường phá vỡ các thiết kế ngây thơ gồm:
- Nâng cấp giữa chu kỳ: quyền truy cập nên thay đổi ngay lập tức, việc tính proration có thể theo quy tắc khác.
- Hạ cấp theo lịch: quyền truy cập có thể giữ ở mức "cao hơn" cho đến khi kỳ trả tiền kết thúc.
- Grandfathering: khách hàng cũ giữ một tính năng mà khách hàng mới không có.
- Thỏa thuận tuỳ chỉnh: một tài khoản có Feature A nhưng không có Feature B dù cùng tên gói.
- Nhu cầu kiểm toán: hỗ trợ, tài chính hoặc compliance hỏi “chính xác họ được bật cái gì và khi nào?”
Mục tiêu đơn giản: một mô hình kiểm soát truy cập linh hoạt thay đổi theo giá mà không phải viết lại logic nghiệp vụ mỗi lần. Bạn muốn một nơi để hỏi “họ có thể làm điều này không?” và một dấu vết trong database giải thích câu trả lời.
Cuối bài, bạn sẽ có một mẫu schema có thể sao chép: gói và add-on trở thành dữ liệu đầu vào, và entitlements trở thành nguồn duy nhất cho quyền truy cập tính năng. Cách tiếp cận này cũng phù hợp với các công cụ no-code như AppMaster, vì bạn có thể giữ quy tắc trong dữ liệu và truy vấn chúng nhất quán từ backend, web app và mobile.
Thuật ngữ chính: plan, add-on, entitlement và access
Nhiều vấn đề đăng ký bắt nguồn từ khác biệt về từ vựng. Nếu mọi người dùng cùng một từ để chỉ những thứ khác nhau, schema của bạn sẽ biến thành các trường hợp đặc biệt.
Các thuật ngữ cần tách riêng trong sơ đồ cơ sở dữ liệu gói và quyền truy cập:
- Plan: Gói mặc định người dùng nhận khi đăng ký (ví dụ Basic hoặc Pro). Plan thường đặt giới hạn cơ bản và các tính năng bao gồm.
- Add-on: Mua tuỳ chọn thay đổi baseline (ví dụ "extra seats" hoặc "advanced reporting"). Add-on nên gắn và gỡ được mà không thay đổi plan.
- Entitlement: Kết quả cuối cùng, đã tính toán "họ đang có gì ngay bây giờ", sau khi kết hợp plan + add-on + ghi đè. Đây là thứ ứng dụng của bạn nên truy vấn.
- Permission (hoặc capability): Hành động cụ thể người dùng có thể thực hiện (ví dụ "export data" hoặc "manage billing"). Permission thường phụ thuộc vào vai trò cộng với entitlements.
- Access: Kết quả thực tế khi ứng dụng thực thi quy tắc (màn hình hiện/ẩn tính năng, API được phép hoặc chặn, áp dụng giới hạn).
Feature flag liên quan nhưng khác. Một feature flag thường là công tắc sản phẩm bạn điều khiển (rollouts, experiments, tắt tính năng khi sự cố). Một entitlement là quyền truy cập theo khách hàng dựa trên họ trả tiền hay được cấp. Dùng flag khi bạn muốn thay đổi hành vi cho nhóm mà không động tới billing. Dùng entitlements khi quyền truy cập phải khớp với subscription, invoice, hoặc hợp đồng.
Phạm vi là nguồn nhầm lẫn khác. Giữ các khái niệm sau rõ ràng:
- User: Một người. Phù hợp cho vai trò (admin vs member) và giới hạn cá nhân.
- Account (customer): Thực thể trả tiền. Phù hợp cho thông tin thanh toán và quyền sở hữu subscription.
- Workspace (project/team): Nơi làm việc. Nhiều sản phẩm áp quyền truy cập ở đây (chỗ ngồi, lưu trữ, module được bật).
Thời gian quan trọng vì quyền truy cập thay đổi. Mô hình hoá nó trực tiếp:
- Start and end: Một entitlement có thể chỉ hoạt động trong một khoảng (trial, promo, hợp đồng hàng năm).
- Scheduled change: Nâng cấp có thể bắt đầu ngay; hạ cấp thường bắt đầu vào kỳ tái gia hạn tiếp theo.
- Grace and cancelation: Bạn có thể cho truy cập giới hạn sau khi thanh toán thất bại, nhưng chỉ đến một ngày kết thúc rõ ràng.
Ví dụ: Một công ty ở Pro, thêm "Advanced Reporting" giữa tháng, rồi lịch hạ cấp về Basic cho chu kỳ sau. Plan thay đổi sau, add-on bắt đầu ngay, và lớp entitlement vẫn là nơi duy nhất để hỏi: “Workspace này hôm nay có dùng báo cáo nâng cao được không?”
Một schema lõi đơn giản cho gói và tính năng
Một schema tốt bắt đầu nhỏ: tách rõ những gì bạn bán (plans và add-ons) khỏi những gì người dùng có thể làm (features). Nếu giữ hai ý này sạch, nâng cấp và add-on mới là thay đổi dữ liệu, không phải viết lại mã.
Một tập bảng lõi thực tế phù hợp với hầu hết sản phẩm đăng ký:
products: thứ bán được (Base plan, Team plan, Extra seats add-on, Priority support add-on).plans: tuỳ chọn, nếu bạn muốn plans là loại sản phẩm đặc biệt với các trường riêng (billing interval, public display order). Nhiều nhóm chỉ lưu plans trongproductsvà dùng cộtproduct_type.features: danh mục khả năng (API access, max projects, export, SSO, SMS credits).product_features(hoặcplan_featuresnếu bạn tách plans): bảng nối nói rõ feature nào đi kèm product nào, thường có giá trị.
Bảng nối này chứa phần lớn sức mạnh. Tính năng hiếm khi chỉ bật/tắt. Một plan có thể bao gồm max_projects = 10, trong khi một add-on có thể thêm +5. Vì vậy product_features nên hỗ trợ ít nhất:
feature_value(số, text, JSON hoặc cột riêng)value_type(boolean, integer, enum, json)grant_mode(replace vs add), để add-on có thể “thêm 5 chỗ ngồi” thay vì ghi đè giới hạn cơ bản
Mô hình hoá add-on cũng như product. Khác biệt duy nhất là cách chúng được mua. Một product plan cơ bản là “một tại một thời điểm”, trong khi add-on có thể cho phép quantity. Nhưng cả hai đều ánh xạ tới các feature theo cùng cách. Điều này tránh các trường hợp đặc biệt như “nếu add-on X thì bật Y” rải rác trong mã.
Feature nên là dữ liệu, không phải hằng số trong mã. Nếu bạn mã hoá kiểm tra tính năng trong nhiều dịch vụ, cuối cùng bạn sẽ có sự khác biệt khi phát hành (web nói có, mobile nói không, backend bất đồng). Khi features nằm trong database, ứng dụng có thể hỏi một câu nhất quán và bạn có thể triển khai thay đổi bằng cách sửa hàng.
Đặt tên quan trọng hơn bạn tưởng. Dùng định danh ổn định không đổi ngay cả khi tên marketing thay đổi:
feature_keynhưmax_projects,sso,priority_supportproduct_codenhưplan_starter_monthly,addon_extra_seats
Tách nhãn hiển thị (feature_name, product_name). Nếu dùng AppMaster’s Data Designer với PostgreSQL, xem các key này là trường duy nhất sẽ có lợi ngay: bạn có thể tái tạo an toàn trong khi giữ tích hợp và báo cáo ổn định.
Lớp entitlement: một nơi để hỏi “họ có thể?”
Hầu hết hệ thống đăng ký gặp rắc rối khi “những gì họ mua” lưu ở một nơi, nhưng “những gì họ có thể làm” được tính ở năm đường dẫn mã khác nhau. Sửa lỗi là tạo lớp entitlement: một bảng (hoặc view) đại diện cho truy cập hiệu dụng cho một subject tại một thời điểm.
Nếu bạn muốn một schema gói và quyền truy cập tồn tại qua nâng cấp, hạ cấp, trial và các cấp phát một-off, lớp này là phần làm mọi thứ dự đoán được.
Một bảng entitlements thực tế
Hãy nghĩ mỗi hàng như một khẳng định: “subject này có quyền với feature này với giá trị này, từ thời điểm này đến thời điểm kia, từ nguồn này.” Hình dạng phổ biến như sau:
subject_type(ví dụ "account", "user", "org") vàsubject_idfeature_idvalue(giá trị hiệu dụng cho feature đó)source(nguồn gốc: "direct", "plan", "addon", "default")starts_atvàends_at(ends_at nullable cho truy cập đang diễn ra)
Bạn có thể hiện giá trị theo một vài cách: một cột text/JSON cộng value_type, hoặc các cột riêng như value_bool, value_int, value_text. Giữ cho đơn giản và dễ truy vấn.
Các loại giá trị bao phủ hầu hết sản phẩm
Tính năng không phải lúc nào cũng bật/tắt. Những loại giá trị này thường đủ cho nhu cầu billing và kiểm soát truy cập:
- Boolean: bật/tắt ("can_export" = true)
- Quota số: giới hạn ("seats" = 10, "api_calls" = 100000)
- Tier level: hạng ("support_tier" = 2)
- String: chế độ hoặc biến thể ("data_retention" = "90_days")
Ưu tiên: cách giải quyết xung đột
Xung đột là bình thường. Một user có thể ở trên một plan cho 5 chỗ, mua add-on thêm 10, và còn được grant thủ công từ support.
Đặt quy tắc rõ ràng và áp dụng mọi nơi:
- Direct grant ghi đè plan
- Sau đó add-ons
- Cuối cùng defaults
Cách đơn giản là lưu tất cả hàng ứng viên (từ plan, add-on, direct) và tính “người thắng” cuối cùng theo subject_id + feature_id bằng cách sắp xếp theo độ ưu tiên nguồn, rồi theo starts_at mới nhất.
Ví dụ cụ thể: khách hàng hạ cấp plan hôm nay, nhưng họ đã trả cho add-on còn hiệu lực đến cuối tháng. Với starts_at/ends_at trên entitlements, hạ cấp có hiệu lực ngay cho tính năng dựa trên plan, trong khi add-on vẫn hoạt động đến ends_at. Ứng dụng của bạn trả lời “họ có thể không?” bằng một truy vấn thay vì logic đặc biệt.
Subscriptions, items và truy cập có giới hạn thời gian
Danh mục gói của bạn (plans, add-ons, features) là “cái gì.” Subscriptions là “ai có cái gì, và khi nào.” Nếu tách hai phần này, nâng cấp và huỷ bỏ sẽ không còn đáng sợ.
Một mẫu thực tế: một subscription cho mỗi account, và nhiều subscription items dưới nó (một cho base plan, cộng nhiều add-on). Trong schema, điều này cho bạn một nơi sạch để ghi lại thay đổi theo thời gian mà không sửa quy tắc truy cập.
Bảng lõi để mô hình hoá dòng thời gian mua
Bạn có thể giữ đơn giản với hai bảng dễ truy vấn:
subscriptions: id, account_id, status (active, trialing, canceled, past_due), started_at, current_period_start, current_period_end, canceled_at (nullable)subscription_items: id, subscription_id, item_type (plan, addon), plan_id/addon_id, quantity, started_at, ends_at (nullable), source (stripe, manual, promo)
Một chi tiết phổ biến: lưu mỗi item với ngày của riêng nó. Bằng cách đó bạn có thể cấp add-on chỉ 30 ngày, hoặc để một plan tiếp tục đến cuối kỳ đã trả ngay cả khi khách hàng huỷ tự động.
Giữ proration và billing ra khỏi logic truy cập
Proration, invoices và retry thanh toán là bài toán billing. Quyền truy cập là bài toán entitlement. Đừng cố “tính truy cập” từ các dòng hoá đơn.
Thay vào đó, để các sự kiện billing cập nhật bản ghi subscription (ví dụ, kéo dài current_period_end, tạo hàng subscription_item mới, hoặc đặt ends_at). Ứng dụng của bạn sau đó trả lời câu hỏi truy cập từ dòng thời gian subscription (và sau đó từ lớp entitlement), chứ không từ toán học hoá đơn.
Thay đổi theo lịch mà không bất ngờ
Nâng cấp và hạ cấp thường có thời điểm cụ thể:
- Thêm
pending_plan_idvàchange_attrên subscriptions cho một thay đổi plan đã lên lịch. - Hoặc dùng bảng
subscription_changes(subscription_id, effective_at, from_plan_id, to_plan_id, reason) nếu bạn cần lịch sử và nhiều thay đổi tương lai.
Điều này tránh việc mã hoá cứng các quy tắc như “hạ cấp diễn ra vào cuối kỳ” trong các phần lộn xộn của mã. Lịch là dữ liệu.
Trial nằm ở đâu
Trial đơn giản là truy cập có giới hạn thời gian với nguồn khác. Hai lựa chọn sạch:
- Xử lý trial như một trạng thái subscription (trialing) với
trial_start/trial_end. - Hoặc tạo các item/entitlement cấp trial với
started_at/ends_atvàsource = trial.
Nếu xây trong AppMaster, các bảng này khớp gọn với Data Designer trong PostgreSQL, và các ngày giúp truy vấn “cái gì đang active ngay bây giờ” mà không cần trường hợp đặc biệt.
Bước từng bước: triển khai mẫu
Một schema gói và quyền truy cập tốt bắt đầu với một lời hứa: logic tính năng nằm trong dữ liệu, không rải rác khắp các đường dẫn mã. Ứng dụng của bạn nên hỏi một câu - “quyền hiệu dụng hiện tại là gì?” - và nhận câu trả lời rõ ràng.
1) Định nghĩa features với key ổn định
Tạo bảng feature với key ổn định, dễ đọc mà bạn sẽ không đổi tên (ngay cả khi nhãn UI thay đổi). Key tốt trông như export_csv, api_calls_per_month, hoặc seats.
Thêm kiểu để hệ thống biết xử lý giá trị: boolean (on/off) so với numeric (giới hạn/quota). Giữ cho đơn giản và nhất quán.
2) Ánh xạ plans và add-ons tới entitlements
Giờ bạn cần hai nguồn chân lý: plan bao gồm gì, và mỗi add-on cấp gì.
Trình tự đơn giản thực tế:
- Đặt tất cả features trong một bảng
featurevới key ổn định và kiểu giá trị. - Tạo
planvàplan_entitlementnơi mỗi hàng cấp giá trị feature (ví dụseats = 5,export_csv = true). - Tạo
addonvàaddon_entitlementcấp các giá trị bổ sung (ví dụseats + 10,api_calls_per_month + 50000, hoặcpriority_support = true). - Quyết định cách kết hợp giá trị: boolean thường dùng OR, giới hạn số thường dùng MAX (cao hơn thắng), và các lượng chỗ ngồi thường dùng SUM.
- Ghi lại khi entitlements bắt đầu và kết thúc để nâng cấp, huỷ và proration không phá vỡ kiểm tra truy cập.
Nếu xây trong AppMaster, bạn có thể mô tả các bảng này trong Data Designer (PostgreSQL) và giữ quy tắc kết hợp như một bảng “policy” nhỏ hoặc enum dùng bởi Business Process.
3) Sinh “entitlements hiệu dụng”
Bạn có hai lựa chọn: tính khi đọc (query và gộp mỗi lần) hoặc sinh snapshot lưu vào khi có thay đổi (thay đổi plan, mua add-on, gia hạn, huỷ). Với hầu hết app, snapshot dễ suy luận và nhanh dưới tải.
Cách phổ biến là một bảng account_entitlement lưu kết quả cuối cùng cho mỗi feature, cộng valid_from và valid_to.
4) Thực thi truy cập bằng một kiểm tra duy nhất
Đừng rải quy tắc khắp các màn hình, endpoint và job nền. Đặt một hàm trong code ứng dụng đọc entitlements hiệu dụng và quyết định.
can(account_id, feature_key, needed_value=1):
ent = get_effective_entitlement(account_id, feature_key, now)
if ent.type == "bool": return ent.value == true
if ent.type == "number": return ent.value >= needed_value
Khi mọi thứ gọi can(...), nâng cấp và add-on chỉ là cập nhật dữ liệu, không phải viết lại.
Kịch bản ví dụ: nâng cấp cộng add-on không gây bất ngờ
Một đội support 6 người dùng Starter. Starter bao gồm 3 chỗ agent và 1.000 SMS/tháng. Giữa tháng họ lên 6 agent và muốn một gói SMS thêm 5.000. Bạn muốn điều này hoạt động mà không có mã điều kiện kiểu “nếu plan = Pro thì…”.
Ngày 1: họ bắt đầu với Starter
Bạn tạo một subscription cho account với kỳ thanh toán (ví dụ Jan 1 đến Jan 31). Rồi thêm subscription_item cho plan.
Khi checkout (hoặc qua job đêm), bạn viết các cấp entitlement cho kỳ đó:
entitlement_grant:agent_seats, value3, startJan 1, endJan 31entitlement_grant:sms_messages, value1000, startJan 1, endJan 31
Ứng dụng của bạn không hỏi “họ đang ở gói nào?” mà hỏi “entitlement hiệu dụng hiện tại là gì?” và nhận seats = 3, SMS = 1000.
Ngày 15: nâng cấp lên Pro, cùng ngày thêm gói SMS
Ngày Jan 15 họ nâng cấp lên Pro (bao gồm 10 seats và 2.000 SMS). Bạn không sửa các grants cũ. Bạn thêm bản ghi mới:
- Đóng item plan cũ: đặt
subscription_item(Starter) end làJan 15 - Tạo item plan mới:
subscription_item(Pro) startJan 15, endJan 31 - Thêm item add-on:
subscription_item(SMS Pack 5000) startJan 15, endJan 31
Rồi các grants cho cùng kỳ được thêm vào:
entitlement_grant:agent_seats, value10, startJan 15, endJan 31entitlement_grant:sms_messages, value2000, startJan 15, endJan 31entitlement_grant:sms_messages, value5000, startJan 15, endJan 31
Điều gì xảy ra ngay Jan 15?
- Seats: effective seats trở thành 10 (bạn chọn quy tắc như “lấy max cho seats”). Họ có thể thêm 3 agent ngay trong ngày.
- SMS: effective SMS là 7.000 cho phần còn lại của kỳ (bạn chọn “cộng các grants” cho gói tin nhắn).
Không cần di chuyển usage đã có. Bảng usage tiếp tục đếm số tin nhắn gửi; kiểm tra entitlement so sánh usage chu kỳ này với giới hạn hiệu dụng hiện tại.
Ngày 25: lên lịch hạ cấp, giữ truy cập tới cuối kỳ
Ngày Jan 25 họ lên lịch hạ cấp về Starter bắt đầu Feb 1. Bạn không động tới các grants của tháng 1. Bạn tạo items/gifts cho tương lai:
subscription_item(Starter) startFeb 1, endFeb 28- Không có item SMS pack bắt đầu Feb 1
Kết quả: họ giữ Pro seats và SMS pack đến hết Jan 31. Ngày Feb 1, seats hiệu dụng giảm về 3 và SMS trở lại giới hạn Starter cho chu kỳ mới. Điều này dễ lý giải, và phù hợp với luồng no-code trong AppMaster: thay đổi ngày tạo hàng mới, và truy vấn entitlement không đổi.
Sai lầm phổ biến và bẫy
Hầu hết lỗi đăng ký không phải lỗi billing. Chúng là lỗi truy cập do logic phân tán khắp sản phẩm. Cách nhanh nhất để phá vỡ schema là trả lời “họ có thể dùng không?” ở năm chỗ khác nhau.
Một thất bại kinh điển là mã hoá cứng quy tắc trong UI, API và job nền riêng biệt. UI ẩn nút, API quên chặn endpoint, job đêm vẫn chạy vì kiểm tra thứ khác. Bạn có các báo cáo “nó thỉnh thoảng hoạt động” khó tái tạo.
Bẫy khác là dùng plan_id để kiểm tra thay vì kiểm tra tính năng. Ban đầu có vẻ đơn giản (Plan A được export, Plan B không), nhưng tan vỡ khi bạn thêm add-on, khách hàng được grandfather, trial miễn phí, hoặc ngoại lệ enterprise. Nếu bạn từng nói “if plan is Pro then allow…”, bạn đang xây một mê cung phải duy trì mãi.
Các cạnh khó về thời gian và huỷ bỏ
Quyền truy cập cũng dễ bị “kẹt” khi bạn chỉ lưu một boolean như has_export = true mà không đính kèm ngày. Huỷ, hoàn tiền, chargeback và hạ cấp giữa chu kỳ đều cần khoảng thời gian. Không có starts_at và ends_at, bạn sẽ lỡ cấp quyền vĩnh viễn, hoặc thu quyền quá sớm.
Một kiểm tra đơn giản để tránh điều này:
- Mỗi entitlement grant phải có nguồn (plan, add-on, ghi đè thủ công) và khoảng thời gian.
- Mọi quyết định truy cập nên dùng “now giữa start và end” (với quy tắc rõ ràng cho end null).
- Job nền nên re-check entitlements khi chạy, không giả sử trạng thái hôm qua.
Đừng trộn billing và truy cập
Các đội cũng gặp rắc rối khi trộn records billing và quy tắc truy cập trong cùng một bảng. Billing cần invoices, thuế, proration, provider IDs và trạng thái retry. Truy cập cần key tính năng rõ ràng và cửa sổ thời gian. Khi trộn lẫn, một migration billing có thể trở thành outage sản phẩm.
Cuối cùng, nhiều hệ thống bỏ qua dấu vết kiểm toán. Khi người dùng hỏi “tại sao tôi có thể export?”, bạn cần trả lời như: “Được bật bởi Add-on X từ 2026-01-01 đến 2026-02-01” hoặc “Được grant thủ công bởi support, ticket 1842.” Không có điều đó, support và engineering chỉ đoán.
Nếu bạn xây trong AppMaster, giữ các trường audit trong Data Designer model và làm kiểm tra “họ có thể?” thành một Business Process dùng chung cho web, mobile và các luồng đã lên lịch.
Danh sách kiểm tra nhanh trước khi ship
Trước khi phát hành schema gói và quyền truy cập, làm một lượt với câu hỏi thực chứ không lý thuyết. Mục tiêu là truy cập có thể giải thích được, kiểm thử được và dễ thay đổi.
Câu hỏi kiểm tra thực tế
Chọn một user và một tính năng, rồi cố giải thích kết quả như bạn sẽ với support hoặc finance. Nếu bạn chỉ trả lời “họ ở Pro” (hoặc tệ hơn, “mã nói vậy”), bạn sẽ đau khi ai đó nâng cấp giữa chu kỳ hoặc có thỏa thuận một-off.
Dùng checklist nhanh này:
- Bạn có trả lời được “tại sao user này có quyền?” chỉ bằng dữ liệu (subscription items, add-ons, overrides và cửa sổ thời gian), không đọc code ứng dụng?
- Mọi kiểm tra truy cập dựa trên feature keys ổn định (như
feature.export_csv) thay vì tên gói (như “Starter” hay “Business”). Tên gói thay đổi; feature key không nên. - Entitlements có start và end rõ ràng, bao gồm trial, grace period và huỷ theo lịch không? Nếu thiếu thời gian, hạ cấp trở thành tranh cãi.
- Bạn có thể cấp hoặc thu quyền cho một khách hàng bằng một bản ghi override, không làm rẽ logic? Đây là cách xử lý “cho họ 10 chỗ ngồi thêm trong tháng này” mà không viết mã tuỳ chỉnh.
- Bạn có test được nâng cấp và hạ cấp với vài hàng mẫu và nhận kết quả dự đoán không? Nếu cần script phức tạp để mô phỏng, mô hình của bạn quá ẩn dụ.
Một bài test thực tế: tạo ba user (mới, nâng cấp giữa tháng, huỷ) và một add-on (ví dụ “extra seats” hoặc “advanced reports”). Rồi chạy truy vấn truy cập cho mỗi người. Nếu kết quả rõ ràng và dễ giải thích, bạn sẵn sàng.
Nếu dùng công cụ như AppMaster, giữ quy tắc: một nơi (một truy vấn hoặc một Business Process) chịu trách nhiệm cho “họ có thể?” để mọi màn hình web và mobile dùng cùng một câu trả lời.
Bước tiếp theo: làm cho nâng cấp dễ duy trì
Cách tốt nhất để giữ nâng cấp ở trạng thái dễ quản lý là bắt đầu nhỏ. Chọn vài tính năng thực sự tạo giá trị (5-10 là đủ) và xây một truy vấn/hàm entitlement trả lời một câu: “Tài khoản này có thể làm X ngay bây giờ không?” Nếu không trả lời được trong một nơi, nâng cấp sẽ luôn cảm thấy rủi ro.
Khi kiểm tra đó hoạt động, coi đường nâng cấp như hành vi sản phẩm, không chỉ billing. Cách nhanh nhất để bắt các edge case kỳ lạ là viết vài test truy cập dựa trên hành động khách hàng thực.
Các bước tiếp theo mang lại lợi tức nhanh:
- Định nghĩa danh mục feature tối thiểu và ánh xạ mỗi plan tới một tập entitlements rõ ràng.
- Thêm add-ons như các “item” riêng cấp hoặc kéo dài entitlements, thay vì nhúng chúng vào quy tắc plan.
- Viết 5-10 test truy cập cho các đường đi phổ biến (nâng cấp giữa chu kỳ, hạ cấp khi gia hạn, thêm rồi gỡ add-on, trial lên paid, grace period).
- Biến thay đổi giá thành cập nhật dữ liệu: sửa hàng plan, mapping feature và entitlement grants, không sửa mã ứng dụng.
- Thiết lập thói quen: mỗi plan hoặc add-on mới phải đi kèm ít nhất một test chứng minh truy cập hoạt động như mong muốn.
Nếu dùng backend no-code, bạn vẫn có thể mô hình hoá pattern này. Trong AppMaster, Data Designer phù hợp để xây các bảng lõi (plans, features, subscriptions, subscription items, entitlements). Rồi Business Process Editor chứa luồng quyết định truy cập (load entitlements active, áp cửa sổ thời gian, trả allow/deny) để bạn không viết kiểm tra rải rác trên endpoint.
Lợi ích hiện ra khi thay đổi giá. Thay vì viết lại quy tắc, bạn sửa dữ liệu: một feature chuyển từ “Pro” thành add-on, thời lượng entitlement thay đổi, hoặc plan legacy giữ grant cũ. Logic truy cập vẫn ổn định, và nâng cấp là cập nhật có kiểm soát, không phải sprint code.
Nếu muốn xác thực nhanh schema, thử mô hình hoá một nâng cấp cộng một add-on từ đầu đến cuối, rồi chạy các test truy cập đó trước khi thêm gì nữa.


