Table of Contents
1) Goal & Preconditions
Goal (Outcome)
Provide a clear, scalable payment model using Stripe where:
- BoQ pricing is resolved per facility region and shown in one page grouped by region.
- If a tenant has facilities in multiple regions, show a section per region with its own subtotals and optional tax summary.
- Checkout charges OTC now and saves a payment method for later subscriptions.
- Assets onboarding is gated by OTC payment; certification starts per facility only after onboarding is complete and its subscription activates.
Preconditions
- Intake captures facility country/city → system resolves a Region.
- Super Admin maintains Price Lists, taxes and Subscription pricing per region.
- Tenant account exists (email/phone OTP). EN/AR localization.
- One IoT platform per tenant is hard‑locked (out of scope in this doc).
2) User Flow (overview)
flowchart LR A[Intake resolves regions] --> B[Compute BoQ snapshot] B --> C[View BoQ grouped by region] C --> D[Checkout] D --> E[OTC PaymentIntent success + SetupIntent] E --> F[Create facility subscriptions: pending_activation] F --> G[Onboarding (Admin completes)] G --> H[Tenant confirms received/installed] H --> I[Activate provider subscriptions] I --> J[Renewals & Billing states]
3) Functional Requirements
3.1 Region Resolution & Catalog
- Resolve Region from facility country/city (postal code optional) using admin‑configured mappings.
- Price Catalog per region stores SKUs, unit prices, tax policy, delivery, and subscription plans (monthly/annual).
- Snapshot prices/taxes into the BoQ when issued; validity = 30 days.
3.2 BoQ (Multi‑Facility / Multi‑Region)
- Per Facility: compute quantities by rules; select active regional Price List; price each line with regional taxes.
- One Page View groups by Region: header with currency code, tax policy badge, FX snapshot note if cross‑region.
- Facility rows: OTC subtotal + taxes; per‑facility plan selector (monthly/annual) showing recurring price.
- Region subtotal (OTC + tax), Region recurring (sum of facilities by chosen plans; show monthly and annual columns).
- Grand view: same currency → show grand totals; mixed currencies → per‑region totals plus optional tenant currency totals with FX snapshot (ECB daily rates).
3.3 Checkout (Stripe)
- Inputs: boqId, facilityPlans: [{ facilityId, plan:'monthly'|'annual' }]
- Create: PaymentIntent for OTC (charge now); SetupIntent to save payment method.
- Outputs: order created; FacilitySubscriptions in
pending_activationwith selected plan. - Webhooks: handle payment succeeded/failed; idempotent retries.
3.4 Onboarding → Activation (Per Facility)
- Access to Assets becomes available to Super Admin only after OTC is paid.
- A facility’s subscription activates when Admin completes onboarding AND Tenant confirms received/installed.
- On activation, create the provider Subscription/Invoice using the saved payment method. Status → active.
3.5 Billing & Renewals
- Status per facility:
pending_activation → active → payment_due (grace) → suspended → reactivation_pending. - Grace period: 7 days default (range 3–14 per tenant). During grace: view allowed; cert/QR hidden if suspended.
- Renewal cadence: monthly/annual per facility using saved method. Annual discount 10% default (per‑region override).
- Invoice grouping: grouped per region per cycle → one invoice per region, line items per facility.
- Co‑termination & proration: align next billing date within a region; mid‑cycle activations prorated.
- Alerts: billing alerts surface with Pay Now CTA routing to Billing/Checkout for that facility.
3.6 UI — Priced BoQ Page
- Sections per Region with currency/tax summary.
- Facility rows: name, city/country, OTC subtotal + taxes, recurring monthly/annual price, plan badge.
- Totals: per region and overall; FX note (snapshot date) for mixed currency.
- CTAs: Download Proforma PDF, Checkout.
4) Non‑Functional Requirements
- Security: RBAC on every endpoint; OTP throttles; hashed passwords; secure cookies; Stripe keys server‑side only.
- Reliability: Idempotency keys on compute/checkout/webhooks; retries with backoff.
- Performance: boq/compute ≤ 2s p95 (≤ 20 facilities); PDF render ≤ 3s p95; dashboard/billing list ≤ 500ms with caching.
- Localization & A11y: EN/AR incl. RTL; aria‑live for errors; amounts formatted per locale.
5) Data & Validation Cheatsheet
Region & Catalog
- region: enum { GCC, Europe, USA, UK, SE_Asia, Australia, RestOfWorld, … }
- priceList: { id, name, region, currency, effectiveFrom, effectiveTo?, isActive }
- priceItem: { sku, unitPrice, taxClass, tiering?, discountPct? }
- fxSnapshot: { provider:'ECB', takenAt, rates:{ 'USD':1.0, 'AED':3.6725, … } }
BoQ Snapshot
- Header: { boqId, tenantId, createdAt, validUntil, priceListRefs[], fxSnapshot }
- Items: [{ sku, label, region, facilityId, qty, unitPrice, taxPct, taxAmount, subtotal, type: 'otc'|'recurring', plan?: 'monthly'|'annual' }]
- Totals: { otcTotal, recurringMonthlyTotal, recurringAnnualTotal, taxTotal, grandTotal }
Checkout / Orders
- Request: { tenantId, boqId, facilityPlans:[{ facilityId, plan:'monthly'|'annual' }], paymentProvider:'stripe' }
- Response: { clientSecret, setupClientSecret, orderId, facilitySubscriptions:[{ id, facilityId, plan, status:'pending_activation' }] }
Subscriptions (per facility)
- { id, facilityId, plan, status:'active'|'pending_activation'|'grace'|'suspended'|'reactivation_pending', nextBillingDate, graceEndsAt? }
Validation
- validUntil must be ≥ today for checkout.
- Must have active PriceList per involved region; otherwise pricing is blocked.
- Currency per region required; FX snapshot timestamp stored if conversions shown.
6) API Stubs (Shapes) — /v1
BoQ
POST /v1/boq/compute → { boqId, priced:false, teaser:{ deviceCounts:[...], highlights:[...] } }
GET /v1/boq/{boqId} → { ...snapshot, items[], totals{}, validUntil }
Checkout
POST /v1/checkout → { clientSecret, setupClientSecret, orderId, facilitySubscriptions:[...] }
POST /v1/billing/webhooks/stripe → events: payment_succeeded, payment_failed (idempotent)
Onboarding / Activation
PATCH /v1/facilities/{id}/onboarding → { status:'complete' }
POST /v1/facilities/{id}/confirm-onboarding → { receivedInstalled:true }
POST /v1/facility-subscriptions/{id}/activate → { status:'active' }
Admin Pricing (reference)
GET /v1/admin/pricelists?region=...
POST /v1/admin/pricelists/{id}/items
POST /v1/admin/tax-policies
Billing Utility
GET /v1/facilities/{id}/billing-status → { status, graceEndsAt }
GET /v1/billing/checkout-link?facilityId=... → { checkoutUrl }
7) UX Copy & Errors (EN/AR)
- EN: “Prices are grouped by region. Taxes are applied per regional policy.”
AR: «الأسعار مجمّعة حسب المنطقة. الضرائب مطبّقة وفق سياسة كل منطقة.» - EN: “This BoQ is valid until {date}.”
AR: «هذا العرض صالح حتى {date}.» - EN: “We will charge the one‑time amount now and save your payment method for subscriptions.”
AR: «سنخصم المبلغ لمرة واحدة الآن ونحفظ طريقة الدفع للاشتراكات.» - EN Error: “No active price list for region {region}.”
AR: «لا توجد قائمة أسعار فعّالة لمنطقة {region}.» - EN Error: “Payment interrupted — you can safely retry.”
AR: «انقطع الدفع — يمكنك إعادة المحاولة بأمان.» - Activation prompt — EN: “Shipping and onboarding done. Did you receive and install the devices?”
AR: «تم الشحن وإنهاء التجهيز. هل استلمت الأجهزة وتم تركيبها؟» - Billing alerts — EN: “Renewal failed — {days} days left in grace. Resolve payment to avoid suspension.”
AR: «فشل التجديد — تبقّى {days} يومًا في فترة السماح. يرجى إتمام الدفع لتجنّب الإيقاف.»
8) Edge Cases
- Multi‑region BoQ with mixed currencies → show per‑region sections; overall in tenant currency via FX snapshot; note snapshot time.
- BoQ expired → block checkout; offer Redo BoQ.
- No active PriceList for a region → show teaser only; alert Super Admin.
- Payment 3‑D Secure fail → retry/change method; keep order idempotent.
- Tenant changes intake after BoQ issued → mark snapshot superseded; recompute new BoQ.
- Partial facility readiness → only facilities passing the AND‑gate activate; others remain pending_activation.
9) Acceptance Criteria
- Region pricing — Given facility country resolves to GCC, When BoQ is computed, Then items price from GCC Price List and taxes apply per GCC policy.
- Multi‑region rendering — Given facilities in GCC and UK, When viewing BoQ, Then two regional sections appear with correct currencies/subtotals, plus FX snapshot total if enabled.
- Per‑facility plan — Given multiple facilities, When tenant selects different plans per facility, Then recurring totals per region update and checkout payload contains facilityPlans.
- Checkout split — Given a priced BoQ, When user checks out, Then OTC is captured and a payment method is saved; facility subscriptions become pending_activation.
- Activation gate — Given pending_activation, When Admin completes onboarding and tenant confirms, Then subscription becomes active; facility shows Ready for certification.
- Invoice grouping — Given two active facilities in the same region with aligned dates, When renewal runs, Then a single regional invoice is issued with two line items.
- Grace handling — Given a failed renewal, When within 7 days, Then facility reflects payment_due (grace) and alerts show Pay Now; after 7 days, Then facility becomes suspended.
10) Analytics (Events)
boq_priced_viewed { boqId, regions[], otc, recurringAnnual }
checkout_started { boqId, plan }
checkout_succeeded { orderId, otcAmount, plan }
payment_failed { orderId, reason }
onboarding_status_updated{ facilityId, status }
tenant_onboarding_confirmed { facilityId }
facility_subscription_activated { facilityId, plan }
billing_status_changed { facilityId, from, to }