- Given a center has just been created through onboarding
- When creation completes
- Then a
trialingsubscription is created on the trial plan (limits of the highest non-Enterprise plan, 1 branch, 5 staff users) with expiry = creation date + 30 days; the top bar shows "Dùng thử, còn N ngày"; no payment details are required; FLYER's sales team is notified.
PRD 19 · Service plans and subscription
The Trường Số plan catalogue with per-plan limits (active students, branches, staff users, storage, pooled ZNS messages, modules), the tenant subscription lifecycle from trial to termination, a shared limit-enforcement point, upgrades recalculated on a 12-month cycle, subscription invoices collected by bank transfer with SePay matching, and FLYER's internal console. No plan or subscription entity exists at tenant level in the backend today.
Key facts, origin and limits
Drafted by an AI assistant in a single session on 16/09/2026, extending the PRD set under the original request quoted verbatim on the hub page. A proposal for team discussion, not a decision of the product team.
Sources
- Public competitor research (PSE ONE, pse4.one), the
center-crm-frontendcodebase and the staging/v3OpenAPI specification (182 paths), as of 10/09/2026. - The existing / extend / new tags on this page come from a comparison with the staging
/v3specification at that date. - A second scan of PSE ONE's public sources on 16/09/2026 (features page, pricing, user-guide articles).
- Competitor reference: PSE ONE's public price list (pse4.one, surveyed 16/09/2026) has Basic at 199,000 VND/month (billed yearly, up to 100 students, 1 GB), Pro at 400,000 VND/month (300 students, 2 GB), Business at 700,000 VND/month (500 students, 4 GB) and Enterprise at 1,000,000 VND/month (unlimited students, 5 GB, adds online curriculum, courses, test prep, student accounts, games); extra storage costs 2,500,000 VND/year per 40 GB. Every tier includes the full operations suite (mobile app, HR, leads, trial, schedule, attendance, finance, inventory, reports, daily backup, 24/7 support). Its published upgrade rule, verbatim: "hợp đồng sẽ được tính lại 12 tháng mới theo gói đã chọn", "phần chi phí còn lại của gói cũ sẽ được khấu trừ trực tiếp vào gói mới", "Hợp đồng cũ chấm dứt ngay tại thời điểm nâng cấp"; published example: Basic used 7 of 12 months, remaining value 199,000 × 5 = 995,000 VND, upgrade to Pro at 4,800,000 VND, amount due 3,805,000 VND. Downgrade, trial length, renewal and data retention are not published. These figures are for positioning only; Trường Số prices are a business decision.
Limits
- Verification pending in-app survey, because automated login to the reference product was not authorised in the drafting session:
- The competitor's trial length, whether payment details are required, and what happens when the trial ends.
- Behaviour at plan expiry: full lock, read-only or warning only; whether a grace period exists and how long.
- Downgrade rules and whether the difference is refunded.
- Whether "students" in the plan limit means active students or all records; whether withdrawn records count.
- Which file types count toward storage, whether the tenant can see it; whether extra storage is prorated or always a full year.
- Whether a self-service page exists to upgrade and see invoices in-app, or sales must be contacted.
Proposed design
Eight screens in the FLYER Cloud design system: the Plan page in Settings with usage bars that change colour from 90% and a plan comparison with prices left as "To be decided", the 12-month upgrade quote, subscription invoices, the banner and the 402 block dialog on the student list, and FLYER's internal console; the key point is that at every limit touchpoint the user sees used / limit and the upgrade path.








1Purpose
Trường Số is sold as a prepaid service plan per center (tenant). Each plan sets the number of active students, branches, staff users, digital-resource storage, pooled ZNS messages per month and which modules are open. This module is the single source for "which plan is this center on, how much headroom is left, when does it expire and what does an upgrade cost". Every other module asks one shared enforcement point before creating more records. The center owner sees usage against limits on one page in Settings, is warned before hitting a ceiling, and upgrades without contacting FLYER's sales team.
In scope
- Plan catalogue: name, monthly and yearly price (actual values decided by the business), per-metric limits, entitlement to P2 modules (digital curriculum PRD 14, e-learning PRD 18).
- Subscription lifecycle: 30-day trial, active, expiring, past due with a 14-day grace period, read-only suspension, termination and data deletion after 90 days per PRD 12.
- Shared enforcement point: soft warning at 90%, hard block on creation beyond a limit with an upgrade prompt, read-only mode when suspended.
- Upgrade recalculated on a 12-month cycle; downgrade at term end when usage fits the new limits; add-ons (storage, ZNS, branch, users).
- Subscription invoices: bank transfer with SePay matching as in PRD 05, VNPay gateway in P2, e-invoice issued by FLYER to the tenant.
- FLYER internal console: set plan, extend trial, apply credit, suspend and reactivate.
- Usage metering, a "Gói dịch vụ" page in Settings (PRD 10), T-30, T-7, T-1, T+0 notifications, audit of every subscription change.
Out of scope
- Plans sold to guardians and students (fee plans belong to PRD 05; FLYER learning packages belong to the learning apps).
- Agent commissions and channel discounts.
- Multi-currency; v1 is VND only.
- Automatic recurring card charges (P3 candidate once VNPay exists).
2Today
The backend already has the tenant (CRM Center) and branch (FLYER School) per decision D1 in PRD 00, plus the onboarding flow that creates a center. Nothing records a tenant's plan, limits or term; every center is currently open without limits. The teacher app has a licence package for individual teachers; it is a reference for data shape, not a tenant plan.
| Existing capability | Endpoint | Keep? |
|---|---|---|
| List and detail of the user's centers (tenants) | GET /v3/crm/centers existing | Keep; extend the response with a subscription_summary block. |
| Onboarding: centers created, create a new center | GET /v3/crm/onboarding/centers, POST /v3/crm/onboarding/create-center existing | Keep; create-center starts a 30-day trial subscription as the center is created. |
| Branches the user can access | GET /v3/crm/me/accessible-schools existing | Keep; the source for the branch count and the place the read_only flag is carried when a tenant is suspended. |
| Personal licence package of a FLYER teacher | GET /v3/teacher/package/my-subscriptions existing | Unchanged for the teacher app; shape reference only (package, start, end, status). |
| SePay bank-transfer matching on the center's own account | SePay webhook (PRD 05) extend | Extend to receive transfers into FLYER's account and match subscription invoices by payment_code. |
| Plans, limits, subscription, usage counters, subscription invoices, credit, internal console | none new | Build under /v3/crm/subscription/* (tenant self-service) and /v3/admin/tenants/* (FLYER internal). |
3User stories and acceptance criteria
- Given I hold
subscription.view - When I open Cài đặt → Gói dịch vụ
- Then the page shows plan name, status, expiry date and one bar per metric as "used / limit" with a percentage; metrics at 90% or above switch to the warning colour with text; figures are at most 15 minutes old and a "Tính lại ngay" button exists.
- Given active students reach 90% of the plan limit
- When any user creates or converts another student (PRD 01, PRD 02)
- Then the action succeeds; a banner "Đã dùng 92/100 học viên của gói Tiêu chuẩn" appears on the student list and the dashboard; the owner gets one in-app notification and email per metric, repeated at most every 7 days.
- Given active students equal the limit
- When an academic admin enrols one more student or moves a paused student back to active
- Then the API returns 402
PLAN_LIMIT_REACHEDwith metric, limit and usage; a dialog "Gói Tiêu chuẩn cho phép 100 học viên đang học. Nâng cấp lên gói Nâng cao để tiếp tục." offers Xem gói (owner) or Báo cho chủ trung tâm (other roles); the record is not created; viewing, editing, attendance and payments on existing records keep working.
- Given a yearly subscription on Tiêu chuẩn, 7 of 12 months used
- When I pick Nâng cao on the plan page
- Then the quote shows the new plan's 12-month price, minus the old plan's remaining value (monthly rate × 5 unused months), minus credit, equals the amount due; new expiry = confirmation date + 12 months; on confirmation a subscription invoice with a QR code is created; the new limits apply once the invoice is fully paid, or immediately if the internal console has enabled "trust payment" for the tenant.
- Given usage fits the lower plan's limits
- When I choose "Hạ xuống gói Khởi đầu từ kỳ tiếp theo"
- Then the request is stored with effective date = current expiry; the page shows "Sẽ chuyển sang gói Khởi đầu vào dd/MM/yyyy" with Huỷ yêu cầu; at T-1 the system re-checks usage: if it exceeds the new limits the request is cancelled and the owner is told which metrics to reduce; otherwise the renewal invoice is issued on the new plan.
- Given usage exceeds the lower plan's limits
- When I choose to downgrade
- Then the button is disabled with "Đang có 180 học viên, gói Khởi đầu cho phép 100".
- Given an unpaid subscription invoice with a
payment_codein its QR and transfer description - When a transfer reaches FLYER's account with the right code and full amount
- Then within 5 minutes the invoice becomes paid, the subscription becomes
activewith the plan and expiry on the invoice, a PDF receipt is emailed to the payer and the owner; transfers with a wrong code or short amount land in the internal console's Chưa đối soát tab.
- Given an active subscription nearing expiry
- When T-30, T-7, T-1 and T+0 arrive
- Then the owner and accountant get an in-app notification and email at each milestone, with the renewal invoice pre-created at T-30; at T+0 the subscription becomes
past_due, a red banner "Gói dịch vụ đã hết hạn, còn 14 ngày ân hạn" shows for every user and features keep working; at T+14 unpaid it becomessuspended: write endpoints return 423TENANT_READ_ONLY, users can still sign in, view, search, export Excel and download receipts; guardians still see the parent portal; automated messages stop. - Given the subscription is
suspended - When the renewal invoice is fully paid
- Then it returns to
activewithin 5 minutes; the new term starts from the old expiry date (grace and suspension days belong to the new term and are not added).
- Given an internal user with
tenant.admin - When I pick a center in the internal console, set Doanh nghiệp for 12 months from the signing date, enter the contract number and a reason
- Then the subscription updates at once, a subscription invoice is created as paid outside the system (method
offline, with the contract number), the owner receives a confirmation email, and the audit log records actor, before and after. - Given a trial center about to expire while negotiating
- When I choose "Gia hạn dùng thử" for 7, 14 or 30 days with a reason
- Then the expiry is extended; total trial time cannot exceed 90 days without
tenant.admin.override. - Given a service incident to compensate
- When I grant credit with a reason
- Then the credit is added to the tenant's balance, deducted automatically from the next invoice, and shown on the plan page.
4Screens
Header: plan name, status chip (Đang hoạt động, Sắp hết hạn, Quá hạn, Dùng thử), expiry date, buttons Nâng cấp, Hoá đơn, Hạ gói từ kỳ sau. Body: one row per metric with a progress bar and percentage, a warning mark with text from 90%, a module row per plan. Footer: remaining credit, last computed time, Tính lại ngay. Below, a plan comparison table (yearly price, monthly price, each limit, modules) with the current plan highlighted. There is no empty state because every tenant always has a subscription; during trial the header reads "Dùng thử, còn N ngày" and the comparison table is expanded. Nâng cấp opens the quote dialog (/settings/plan/upgrade?plan=advanced): new plan 12-month price, minus the old plan's remaining value (monthly rate × unused months), minus credit, amount due, new expiry, the fixed note "Kỳ hiện tại kết thúc tại thời điểm nâng cấp; kỳ mới 12 tháng bắt đầu từ hôm nay", and a Xác nhận và tạo hoá đơn button.
List: invoice number (TS-INV-yyyy-nnnnnn), type (renewal, upgrade, add-on, adjustment), period, amount, credit applied, balance due, status (Chưa thanh toán, Đã thanh toán, Quá hạn, Đã huỷ), due date, actions (Xem, Tải PDF, Tải HĐĐT). Detail: line items, transfer QR with FLYER's account and the payment_code, Thanh toán qua VNPay button (P2), e-invoice block (number, lookup code, PDF), buyer taken from the legal profile (PRD 10). Empty state: "Chưa có hoá đơn. Hoá đơn đầu tiên được tạo khi chọn gói."
Top-bar banner by status: trial (blue, "Dùng thử, còn N ngày. Chọn gói"), expiring (amber, from T-30), past due (red, grace-day countdown), suspended (red, "Chỉ xem. Thanh toán để tiếp tục"). The action button shows only for roles with subscription.manage or subscription.pay; other roles see "Liên hệ chủ trung tâm". Block dialog (402): limit, current usage, the next plan that fits, Xem gói or Báo cho chủ trung tâm. Read-only dialog (423): "Trung tâm đang ở chế độ chỉ xem do gói dịch vụ chưa được gia hạn."
FLYER users with tenant.admin only, outside tenant navigation. List: filters status, plan, expiring within N days, account manager, search by name or center_id; columns center, owner, plan, status, expiry, active students / limit, branches, normalised MRR; tabs Dùng thử sắp hết · Quá hạn · Chưa đối soát. Detail: tabs Thuê bao (set plan and term, extend trial, suspend / reactivate, terminate, "trust payment"), Mức sử dụng (90 days), Hoá đơn (adjustment, mark paid offline, void), Tín dụng, Nhật ký. Every write requires a reason. Empty state per tab, for example "Không có trung tâm nào quá hạn."
5Business rules
- Each tenant (Center) has exactly one subscription, created automatically as
trialingwhen the center is created; a subscription is never deleted, only transitioned. - Valid transitions:
trialing→active(payment or internal plan set) or →past_due(30 days elapsed);active→expiring(T-30, display flag only) →past_due(T+0) →suspended(T+14) →terminated(T+14+90 or on the owner's written request) → data purged 90 days after termination per PRD 12. Full payment in any state beforeterminatedreturns the subscription toactive. Other transitions return 409INVALID_TRANSITION. Milestones are end of day in Asia/Ho_Chi_Minh, stored in UTC. - Counting: active students = students in status Đang học across all branches (Bảo lưu, Chờ xếp lớp, Học thử excluded); branches = non-archived Schools of the Center; staff users = non-deactivated login accounts (staff without accounts and guardian accounts excluded); storage = total size of files uploaded by the tenant (curriculum materials PRD 14, lessons PRD 18, attachments, receipt PDFs), FLYER learning content excluded; ZNS = messages delivered in the calendar month, reset on the 1st.
- The shared
enforce_limit(metric)runs before every action that raises a metric: create student, convert lead, set status to Đang học, Excel import (PRD 11, whole file pre-checked), create branch, invite user, upload file, send ZNS. From 90%: allow and warn. From 100%: refuse with 402PLAN_LIMIT_REACHED. ZNS at quota falls back through the PRD 07 channel order (Zalo OA, in-app, email) with reason "Hết hạn mức ZNS"; the tenant may enable "Cho phép vượt hạn mức ZNS" and pay the overage on the next invoice (P2). - Viewing, editing existing records, attendance, payments and exports are never blocked by a count limit. While
suspended, write endpoints return 423TENANT_READ_ONLY, except paying a subscription invoice, sign-in, password change, guardian consent management and data-export requests. - Upgrade fee = new plan price (12 months) − remaining value of the old plan, where remaining value = old plan monthly rate × unused months; the month in progress counts as used; the old term ends at the moment of upgrade and a new 12-month term starts on the confirmation date. Credit is deducted last; a negative result becomes credit, never a refund. The new plan takes effect when the upgrade invoice is fully paid. Upgrading during trial ends the trial at once; remaining trial days are not converted to money.
- Downgrade takes effect only on the current term's expiry date and only if every metric at T-1 fits the new plan; no refund of the difference; at most one pending downgrade per subscription.
- Add-ons (40 GB blocks, 1,000 ZNS/month blocks, extra branch, 5 extra users) share the subscription term; the first charge is prorated to the remaining months rounded up; the limit rises on payment; they roll into the renewal invoice at full-year price unless removed before T-1; purchasable only while
activeorexpiring. - Subscription invoices are numbered TS-INV-yyyy-nnnnnn from a system-wide sequence; due in 7 days for upgrade and add-on invoices, on the expiry date for renewal invoices. Partial payment does not activate a plan. The renewal invoice is created at T-30 on the current plan (or the requested downgrade), plus add-ons, minus credit; the new term always follows the old expiry date, even when paid during grace or suspension; an unpaid renewal invoice is voided when the subscription terminates.
- The subscription e-invoice is issued by FLYER to the tenant through FLYER's provider; the buyer comes from the center's legal profile (
SchoolCompany); issued automatically within 24 hours of full payment when the tenant has enabled "Nhận hoá đơn điện tử", otherwise on request. - Pilot centers and bespoke contracts use internal plans
pilotorcustomset from the internal console, with their own limits and an explicit expiry; they never appear in the tenant's comparison table. - Every change of subscription state, plan, limits, credit or invoice and every internal-console action is audited (PRD 12) with actor, reason, before and after; the tenant sees its own history in Cài đặt → Nhật ký.
6Data model
| Entity | Fields | Notes |
|---|---|---|
| plan (system-wide) | id uuid; code enum(trial, starter, standard, advanced, enterprise, pilot, custom); name_vi; name_en; price_month_vnd; price_year_vnd; billing_cycles [year | month]; is_public bool; sort_order; modules jsonb {curriculum bool, elearning bool}; retired_at null | new; prices are "to be decided" placeholders; retired plans are kept so old subscriptions still resolve |
| plan_limit | id; plan_id; metric enum(active_students, branches, staff_users, storage_gb, zns_month); limit_value int null (null = unlimited) | new; unique (plan_id, metric) |
| subscription (tenant level) | id uuid; center_id (unique); plan_id; status enum(trialing, active, expiring, past_due, suspended, terminated); billing_cycle; term_start; term_end; trial_end null; grace_end null; suspended_at null; terminated_at null; purge_after null; pending_downgrade_plan_id null; trust_payment bool; contract_no null; account_manager_id null; version int; updated_at | new; every notification milestone derives from term_end |
| subscription_addon | id; subscription_id; type enum(storage_40gb, zns_1000, branch, staff_5); quantity int; unit_price_year_vnd; starts_at; ends_at (= term_end); auto_renew bool; invoice_id | new |
| subscription_event | id; subscription_id; type enum(trial_started, trial_extended, activated, plan_changed, downgrade_scheduled, downgrade_cancelled, addon_changed, state_changed, credit_applied); from jsonb; to jsonb; reason; actor_type enum(tenant_user, flyer_admin, system); actor_id; created_at | new; audit source |
| usage_counter | id; center_id; metric; value numeric; limit_snapshot int null; computed_at; source enum(incremental, nightly, manual); history_daily jsonb (90 days) | new; unique (center_id, metric); incremented on writes, fully recomputed nightly at 02:00 |
| subscription_invoice | id uuid; number (TS-INV-yyyy-nnnnnn, unique); center_id; subscription_id; type enum(renewal, upgrade, addon, adjustment); status enum(unpaid, paid, overdue, void); lines jsonb [{description, plan_id | addon_type, qty, unit_price, amount}]; subtotal; credit_applied; total_due; paid_amount; payment_code (unique); due_at; paid_at null; payment_method enum(bank_transfer, vnpay, offline) null; payments jsonb [{amount, method, sepay_transaction_id, received_at}]; einvoice {provider, number, lookup_code, pdf_url} null; buyer jsonb | new; FLYER's invoice to the tenant, separate from Tuition in PRD 05 |
| credit | id; center_id; amount_vnd; remaining_vnd; reason; granted_by; expires_at null; created_at | new; consumed earliest-expiry first |
| Center (extended) | + subscription_id; + read_only bool (derived) | extend; read_only is returned inside accessible-schools |
Scoping: plan and plan_limit are system-wide data managed by FLYER. subscription, usage_counter, subscription_invoice and credit are tenant-level (center_id), never branch-level; the branch metric aggregates the Center's Schools. A tenant reads only records carrying its own center_id; another tenant's id returns 404 per PRD 12.
7API contract
| Method | Path | Purpose | Status |
|---|---|---|---|
| GET | /v3/crm/centers | Center list; adds subscription_summary {plan_code, status, term_end, read_only} | extend |
| GET | /v3/crm/onboarding/centers | Centers created in onboarding | existing |
| POST | /v3/crm/onboarding/create-center | Create center; afterwards start a 30-day trialing subscription | extend |
| GET | /v3/crm/me/accessible-schools | User's branches; adds read_only and subscription_status | extend |
| GET | /v3/teacher/package/my-subscriptions | FLYER teacher's personal package; unchanged, shape reference only | existing |
| GET | /v3/crm/subscription | → {subscription, plan, limits, usage: [{metric, value, limit, percent}], addons, credit_remaining, pending_downgrade} | new |
| GET | /v3/crm/subscription/plans | Public plan catalogue with limits and modules, per X-Locale | new |
| GET | /v3/crm/subscription/usage | metric, from, to → daily history; ?recompute=true recomputes now (once per 5 minutes) | new |
| POST | /v3/crm/subscription/upgrade-quote | {plan_code, billing_cycle} → {new_price, remaining_value, months_unused, credit_applied, total_due, new_term_end, quote_hash} | new |
| POST | /v3/crm/subscription/upgrade | {plan_code, billing_cycle, quote_hash} → subscription_invoice; 409 STALE_QUOTE when the quote is older than 24 h; Idempotency-Key | new |
| POST · DELETE | /v3/crm/subscription/downgrade | {plan_code} schedules a downgrade at term_end; 422 USAGE_EXCEEDS_PLAN with the offending metrics; DELETE cancels | new |
| GET · POST · DELETE | /v3/crm/subscription/addons | {type, quantity} → prorated quote and invoice; DELETE turns off auto-renew | new |
| GET | /v3/crm/subscription/invoices · /{id} · /{id}/pdf · /{id}/qr | List, detail, PDF and transfer QR of subscription invoices | new |
| POST | /v3/crm/subscription/invoices/{id}/vnpay · /einvoice | → {redirect_url}, callback records payment (P2); {buyer} → issue e-invoice, returns number and lookup code | new |
| GET · POST | /v3/crm/subscription/events · /notify-owner | Tenant's subscription history, paginated; notify owners that a limit was hit (once per hour per user) | new |
| POST | /v3/internal/sepay/webhook | Existing SePay webhook (PRD 05) also receives FLYER's account; matches TS-INV payment_code | extend |
| GET | /v3/admin/tenants · /{center_id} | status, plan_code, expiring_within_days, account_manager_id, q → list with usage; detail → subscription, usage, invoices, credits, events; tenant.admin only | new |
| PATCH | /v3/admin/tenants/{center_id}/subscription | {plan_code, term_start, term_end, billing_cycle, contract_no, trust_payment, reason, version}; optionally creates a paid offline invoice | new |
| POST | /v3/admin/tenants/{center_id}/trial-extend · /credits · /suspend · /reactivate · /terminate | {days ∈ 7|14|30, reason} (over 90 days total needs tenant.admin.override); {amount_vnd, reason, expires_at?}; {reason}; terminate sets purge_after = now + 90 days | new |
| GET · POST · PATCH | /v3/admin/plans · /{id} · /v3/admin/subscription-transactions/unmatched · /{id}/match | Plan catalogue and limits (a PATCH to limits of a plan with live subscriptions applies to new terms only); unmatched SePay transfers on FLYER's account (table unmatched_subscription_transaction, same pattern as PRD 05) and assignment to an invoice | new |
| middleware | every write endpoint under /v3/crm and /v3/school | 402 PLAN_LIMIT_REACHED {metric, limit, usage, suggested_plan}; 423 TENANT_READ_ONLY {term_end, grace_end}; header X-Plan-Warning: metric=active_students;percent=92 from 90% | new |
8Permissions
| Permission | owner | branch_manager | admissions | academic_admin | accountant | teacher | parent |
|---|---|---|---|---|---|---|---|
subscription.view (plan page, usage) | all | all (read) | no | no | all (read) | no | no |
subscription.manage (upgrade, downgrade, add-ons, enable e-invoice) | yes | no | no | no | no | no | no |
subscription.pay (subscription invoices, QR, VNPay, e-invoice download) | yes | no | no | no | yes | no | no |
subscription.notify_owner (alert the owner when blocked) | not needed | yes | yes | yes | yes | yes | no |
| Subscription status banner | yes, with action button | yes | yes | yes | yes, with pay button | yes | read-only notice on the parent portal only while suspended |
tenant.admin, tenant.admin.override (internal console) | Granted only to FLYER internal users through a system role; absent from the tenant's permissions-catalog; every action requires a reason and is audited. | ||||||
9Analytics events
| Event | Properties |
|---|---|
| trial_started | center_id, source (onboarding|admin) |
| trial_converted · trial_expired | days_in_trial, plan_code, billing_cycle, actor_type, max_active_students |
| plan_page_viewed | status, highest_percent_metric, highest_percent |
| limit_warning_shown | metric, percent, surface (banner|email|in_app) |
| limit_block_shown | metric, limit, usage, action (view_plans|notify_owner|dismiss), by_role |
| upgrade_quote_viewed · upgrade_completed | from_plan, to_plan, months_unused, total_due, trigger (limit_block|plan_page|admin) |
| downgrade_scheduled · downgrade_cancelled | from_plan, to_plan, cancelled_by (user|usage_check) |
| addon_purchased | type, quantity, prorated_months, total_due |
| subscription_invoice_paid | type, method, hours_to_pay, matched (auto|manual) |
| expiry_notification_sent | milestone (T-30|T-7|T-1|T+0|T+14), channel |
| subscription_state_changed | from, to, actor_type |
| admin_tenant_action | action (set_plan|trial_extend|credit|suspend|reactivate|terminate), admin_id (no tenant personal data) |
10Edge cases
- An Excel import (PRD 11) would push students over the limit: the pre-check lists the rows beyond the ceiling and rejects the whole file; no partial import.
- A paused student returns to active when seats are full: blocked like a creation; the UI suggests upgrading or ending another enrollment.
- Usage above the limit after FLYER changes a plan's limits or after an internal downgrade: no data is removed, only creation is blocked; the plan page shows 118% and the reason.
- Soft-deleted files in their 30-day window (PRD 12) still count toward storage until purged; the UI says so.
- Two SePay transactions with the same
sepay_transaction_id(webhook retry): deduplicated. Right code but overpaid: the surplus becomes credit; underpaid: recorded as partial, no activation. - Upgrade while a renewal invoice is unpaid: the renewal invoice is voided and replaced by the upgrade invoice. Upgrade in the last month of the term: unused months equal 0, the formula still applies.
- Owner requests termination mid-term:
terminatedon the requested date, no refund of the remainder under the terms of service; data kept 90 days and fully exportable per PRD 12. - Payment arrives after data has been purged: the transaction goes to Chưa đối soát for a manual refund by FLYER; the tenant cannot be restored.
- Read-only tenant with a session in progress: the teacher can still open the attendance sheet in view mode; offline-saved sheets stay queued on the device and sync once the subscription is active again (PRD 04).
11Open questions
- Plan names and monthly and yearly prices per plan (business decision); proposed four public plans Khởi đầu, Tiêu chuẩn, Nâng cao, Doanh nghiệp, with Doanh nghiệp unlimited on students.
- Sell monthly terms in P1 or yearly only? Proposed: yearly only in P1, monthly opens with VNPay in P2.
- How long do the three P1 pilot centers stay on the free
pilotplan and under what condition do they move to a paid plan? - PRD 12 section 7 currently states a 30-day grace before tenant deletion; this PRD proposes 90 days of retention after termination. One figure must be agreed and PRD 12 updated.
- Is the e-invoice provider for FLYER's own invoices the same one tenants use in PRD 05?
- Does FLYER's sales team need "sign in as tenant" for support? Left out for security and personal-data reasons.
Trường Số (truongso.net) · FLYER internal