Back
Climacert‑X — Payments, BoQ & Subscriptions (Multi‑Region) — Consolidated Spec v1 • 2025‑08‑27
Wireframe

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_activation with 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 }