Purchase Course › Product Selection
🎬 Simulate:

Product Selection

Course
📘
Introduction to Digital Tools for TVET
Instructor: Dr. Somchai Phongsavanh
Select product *
Select product
Name Type Quantity Unit price Total (preview)
period 20,000 LAK 20,000 LAK
lifetime — 150,000 LAK 150,000 LAK
📝 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-only
  • row_product_id "Product ID" — text · Product.id · lookup · always · read-only
  • row_product_name "Name" — text · Product.name · lookup · always · read-only
  • row_product_type "Type" — enum [lifetime, period] · Product.type · always · read-only
  • row_unit_price "Unit price" — number · Product.price · lookup · always · read-only
  • row_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-only
  • selected_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-01
  • cancel "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
  1. Supports concurrent users per NFR-01 (100–200 peak) [refs: NFR-01]
  2. Works on connection ≤1 Mbps — query Products + DiscountRule once on load; total preview computed client-side (no round-trip) [refs: NFR-02]
  3. TLS 1.2+; do not display raw price from untrusted source [refs: NFR-03]
  4. Responsive (desktop/laptop/tablet/smartphone) — product list collapses to vertical cards on mobile (column priority: name, price, qty, total) [refs: NFR-08]
  5. 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]
  6. 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]
  7. 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]
  8. Race condition — re-check active Subscription before proceed; if covering sub found show notice immediately (AF-3C); do not invoke SUC-BIL-01 [refs: —]
  9. "Checkout" button disabled after click until SUC returns or error — prevents double-invoke (idempotent payment processing) [refs: SS-BIL.02]
  10. 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: —]
  11. 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: —]