🎬 Simulate:
Product Selection
Course
📘
Introduction to Digital Tools for TVET
Instructor: Dr. Somchai Phongsavanh
Select product *
Uses sub-flows:
Screen Flow (uc-bil-01-purchase-course.flow.puml):
📝 Spec notes — UC-BIL.01 / SCR-BIL.04 (fields, actions, requirements)
Identity
- Screen: SCR-BIL.04 — Product Selection
- Use case: UC-BIL.01 — Purchase Course
Brief description
Product Selection screen for UC-BIL.01 — entry point from LMS course detail when the learner clicks "Buy". System queries active Products linked to the course (direct/group/tag) and displays: lifetime products (name, fixed qty=1, no dropdown) and period products (name, base price, qty dropdown 1/3/6/12 months + live per-row total preview computed client-side). This screen shows the per-row total preview only — no summary/grand-total block; the final net amount is calculated and confirmed at checkout (SUC-BIL-01). Alternate states: no products available (AF-1A), already covered (AF-1B, AF-3C), payment-failed retry (AF-3A).
Fields
course_name"Course" — text · Course.name · default: param.course_name · always · read-onlyrow_product_id"Product ID" — text · Product.id · lookup · always · read-onlyrow_product_name"Name" — text · Product.name · lookup · always · read-onlyrow_product_type"Type" — enum [lifetime, period] · Product.type · always · read-onlyrow_unit_price"Unit price" — number · Product.price · lookup · always · read-onlyrow_qty"Quantity (months)" — enum · required and visible when row_product_type == period · default fallback "1" · options loaded from SystemConfig.period_duration_options (configured by the Super Administrator) · option label: "{qty} months (-{discount_pct}%)" when discount_pct > 0, "{qty} month(s)" when 0 · discount_pct resolved from DiscountRule.qty_tier (mock: 1m=0%, 3m=-10%, 6m=-15%, 12m=-20%)row_total_preview"Total (preview)" — number · computed client-side (default 0) · always · read-onlyselected_product_id"Select product" — text · required · radio selection state · fallback ""covering_subscription_label"Already-has-access message" — text · computed · visible when has_covering_subscription == true · read-only (AF-1B)gateway_error_message"Gateway error message" — text · fallback "" · visible when gateway_error_message != "" · read-only (AF-3A)
Actions
proceed"Checkout" — enabled when selected_product_id != "" · triggers UC-BIL.01-M-2 → invokes SUC-BIL-01cancel"Cancel" — always enabled · triggers UC-BIL.01-A-2A-1 (exit to course page)go_to_course"Start" — enabled when has_covering_subscription == true · triggers UC-BIL.01-A-1B-1 (AF-1B)contact_admin"Contact Admin" — enabled when products.count == 0 · triggers UC-BIL.01-A-1A-1 (AF-1A)retry_after_failure"Retry" — enabled when gateway_error_message != "" · triggers UC-BIL.01-A-3A-2 → re-invoke SUC-BIL-01 (AF-3A)
Additional requirements
- Supports concurrent users per NFR-01 (100–200 peak) [refs: NFR-01]
- Works on connection ≤1 Mbps — query Products + DiscountRule once on load; total preview computed client-side (no round-trip) [refs: NFR-02]
- TLS 1.2+; do not display raw price from untrusted source [refs: NFR-03]
- Responsive (desktop/laptop/tablet/smartphone) — product list collapses to vertical cards on mobile (column priority: name, price, qty, total) [refs: NFR-08]
- WCAG — product radio/button group needs <fieldset>+<legend> "Select product"; qty dropdown needs label-input pair; total preview updates via aria-live="polite" [refs: NFR-09]
- Empty state when course has no Product link → "not available for purchase, contact admin" + back link (AF-1A); not an error state [refs: NFR-04]
- Already-covered notice when user has active Subscription covering this course → replaces product list with "you already have access via [Subscription X]" + course button (AF-1B) [refs: NFR-04]
- Race condition — re-check active Subscription before proceed; if covering sub found show notice immediately (AF-3C); do not invoke SUC-BIL-01 [refs: —]
- "Checkout" button disabled after click until SUC returns or error — prevents double-invoke (idempotent payment processing) [refs: SS-BIL.02]
- Product list query only Products with status=active linked to the selected course (direct/group/tag) — displayed as a single list; inactive products excluded; every shown row selectable via radio (selected_product_id) — no per-row select step [refs: —]
- Screen shows per-row "Total (preview)" (row_total_preview) only — no summary/grand-total block below the list; final net amount calculated and confirmed at checkout (SUC-BIL-01) [refs: —]