# P2 — Subscriptions & Commercials (M7a) — Implementation Plan

**Demo:** `Navagoo_MI` v0.14 (`src/lib/{entitlements,subscriptions,offers}.ts` + tests, `src/portals/shop/finance/*`, `src/portals/admin/Subscriptions.tsx`) · **Target:** `common/` + `frontend/` + `console/` + a flagged `api/`/Paymob branch.
**Master-plan phase:** P2 of `DEMO_SYNC_V14_V20_MASTER_PLAN.md` (XL). **Money-critical** — every charge amount is cent-pinned; the billing rails need MI decisions (below).

## 0. What already exists (do NOT rebuild)

Earlier waves shipped the **data model + storefront**: models `NavagooSubscriptionPlan`, `ShopSubscription`, `NavagooOffer`, `ShopOfferEnrollment`, `SubscriptionPackage` (+ tables), the shop **Navagoo Plans** page (`79e5c64`/`8dc3a45`), admin **Offers/Payment-methods** tabs (`67b7720`), and the shop **Finance 5-tab hub** (E3, Ahmad's wave). The `invoice` billing-lifecycle columns (`m260628_230000`) are live. So P2's remainder is the **engines + lifecycle + rails**, not the schema.

## 1. Waves (safe → decision-gated)

| Wave | Scope | Money? | Gate |
|---|---|---|---|
| **1 · Entitlement engine** | `EntitlementService` — port `entitlements.ts`: tier feature catalogue (starter/growth/pro deltas), `featuresForTier`, `TIER_RANK`, `hasFeature(plan,f)`, `canAccess = hasFeature && hasRole` (role STUB = owner-true until P3), `paymentMethodEnabled`, `minTierFor`, `exceedsBand`, `suggestedTierForCount`. + `EntitlementServiceTest` (21 demo pins). | no | none — **source-authoritative, safe** |
| **2 · Offers + pricing math** | port `offers.ts` + the pricing fns (`planPriceFor`/`applyDiscount`/`derivePlanPricing`) → `OfferPricingService` + cent-locked tests (10+ pins). Offers change charge amounts → pin to the cent. | yes (pure math) | none — cent-lockable like the wage engine |
| **3 · Feature gating wiring** | wire `canAccess`/`paymentMethodEnabled` into the shop nav + booking payment-timing gate. **Additive default-allow when unconfigured** (mobile booking unaffected — flag). | no | API-flag (booking paths) |
| **4 · Subscription lifecycle cron + dunning** | `reconcileSubscriptions` (trial→charge→renewal→dunning retries→grace→lock) on `ShopSubscription` + Invoice; idempotency key = subscription\|period\|attempt. | **YES** | ⛔ **DECISION** — pricing, dunning cadence, **Arabic** wording |
| **5 · Paymob MIT card rail** | saved-card tokenization + recurring charge + `charge_to_card` dunning recovery (extend `PaymobPaymentHelper` + additive webhook branch). | **YES** | ⛔ **DECISION + api/ FLAG** (mobile shares Paymob webhook) |
| **6 · Bank-transfer slip rail** | reuse the Invoice verification lifecycle for bank-slip subscription payment. | yes | reuses existing |
| **7 · Prorated upgrades / downgrades / grandfathering** | `(newNet − current_term_price) × remaining` proration; term-end downgrade; surplus-specialist lock; **fix the demo's parked bank-rail `plan_cap_locked` bug** (D10). | **YES** | ⛔ **DECISION** (proration policy) |
| **8 · i18n + verify** | ar+en (~250 keys — largest of the plan; MI reviews plan/dunning/invoice Arabic) + browser verify + mobile curl-smoke. | — | ⛔ **Arabic review** |

**Execution order:** Waves 1–3 are safe + source-authoritative → build now. Waves 4–7 are money-critical with product gates → **surface the decisions, then build with cent-pinned tests** (never blind). Wave 8 closes it.

## 2. Decision gates for MI (must confirm before Waves 4–7)

- **D-pricing:** the real SAR prices for Starter / Growth / Pro (monthly, and 6-/12-month if offered) + VAT treatment.
- **D-dunning:** retry cadence + grace window before lock (demo: charge → N retries → grace → locked).
- **D-Paymob-MIT:** confirm we use Paymob saved-card recurring (token infra exists) — and that the additive webhook branch is safe for the shared mobile app.
- **D-Arabic:** MI review of plan/tier/dunning/invoice wording (money+legal strings).
- **D-band:** specialist-count bands per tier (the `exceedsBand` limits).

## 3. Cross-cutting (same guarantees as Wages)

- **Money pinned to the cent** — port the demo `finance.offers.test.ts` seed numbers; a P2 identity test in CI.
- **API impact: ADDITIVE + FLAGGED** — the payment-timing gate touches shared booking paths; Paymob webhook gains a branch → mobile book+pay curl-smoke mandatory before merge.
- **RBAC stub:** `hasRole` returns owner-true until P3 swaps it (one function).
- **Off-rail where possible; cent-locked where not.** One phase = one branch = review packet (disposition + migration list + API-impact + tests + screenshots).

## 4. Status

| Wave | State |
|---|---|
| 0 · existing infra | ✅ (models + storefront + finance hub) |
| **1 · Entitlement engine** | ✅ **DONE** — `EntitlementService` + 8 pins green |
| **2 · Offers/pricing math** | ✅ **DONE** — `OfferPricingService` + 6 pins green (cent/integer-locked) |
| **3a · gating resolver** | ✅ **DONE** — `EntitlementService::{subscriptionAccessState, shopFeatures, shopCanAccess}` bound to `ShopSubscription`/`NavagooSubscriptionPlan`; **live-rollout posture: default-allow when a shop has no subscription** (existing shops aren't locked out); verified against real data (a cancelled sub → locked → 0 features) + state-mapping unit-pinned (9 tests). |
| 3b · gating **wiring** (destructive) | ⛔ **flagged** — hiding tier-locked nav items / gating booking payment-timing changes what shops SEE + touches the shared api/ booking path. Needs (a) the subscription data cleaned (e.g. shop 15 has a stale *cancelled* sub → would lock everything), and (b) a **hide-vs-lock-banner** product call. Do deliberately, not blind. |
| 4–7 · lifecycle / rails / proration | ⛔ **blocked on MI decisions (§2)** — pricing, dunning, Paymob-MIT, Arabic, bands |
| 8 · i18n + verify | ⬜ |

**Safe engines done (Waves 1–2):** the feature/tier gating + the offer/plan pricing arithmetic are ported and pinned to the demo (14 assertions-groups). No money decision was taken — the billing lifecycle that *charges* those prices is Wave 4+, gated on §2.
