Ag Bundle — Customer-Portal Tier Upgrade Flow
Status: Planned Issue: #165Depends on: #156 (Ag Bundle 3-tier SKU + entitlement helpers — shipped)
Prices (live in Stripe; re-priced to round numbers 2026-05-21):
| Tier | Monthly | Annual | Stripe product |
|---|---|---|---|
| Lite | $20 CAD | $200 CAD | prod_UYQuITRZoGGz3s |
| Farmer | $50 CAD | $500 CAD | prod_UYQuxVMO8WytkP |
| Investor | $100 CAD | $1000 CAD | prod_UYQu5wHADrbE5e |
Annual = 10× monthly (2 months free). Resolve price IDs via getVerticalBundlePriceId(sku, interval) from server/utils/pricing.js.
Goal
Self-serve tier upgrade flow inside /app/account for Ag Bundle customers — Lite → Farmer → Investor. Comparison view, one-click upgrade through Stripe-hosted checkout, immediate entitlement update on success, and a promo-code path for the 8 existing ag customers' 90-day Investor trial.
CTA placement
A dedicated "Ag Bundle" panel on /app/account for customers whose subscription.plan is one of ag_lite / ag_farmer / ag_investor. The panel shows:
- Current tier (and current price/billing cycle)
- A tier-comparison table showing what each tier includes
- Upgrade buttons next to the tier(s) the customer doesn't yet have
- Downgrade option (with confirmation dialog) for users wanting to move down a tier at the end of the current billing period
For non-Ag customers, the panel doesn't appear. The general pricing page (/pricing) is the entry point for first-time Ag Bundle signup; the customer-portal flow is for existing Ag customers moving between Ag tiers.
Tier comparison view
A compact comparison table with checkmark-style cells:
| Feature | Lite | Farmer | Investor |
|---|---|---|---|
| One-click parcel reports | 5/mo | Unlimited | Unlimited |
| PDF export of parcel reports | — | ✓ | ✓ |
| LSRS soil score (full overlay) | Lookup | ✓ | ✓ |
| AAFC crop history overlay | Lookup | ✓ | ✓ |
| Land portfolio manager | 10 parcels | 250 | Unlimited |
| Lease renewal alerts | — | ✓ | ✓ |
| Territory & prospecting polygon tool | — | — | ✓ |
| Land values panel (when shipped) | — | Regional | Full |
| CSV export with CRM mapping | — | — | ✓ |
Table is sourced from app/config/pricing.js once the Ag tiers are added there (follow-up). Until then, the doc references the canonical entitlement boundaries listed in #156.
API
POST /api/billing/ag-bundle/upgrade
body: { targetTier: "ag_farmer" | "ag_investor" }
→ { url: "https://checkout.stripe.com/c/pay/..." }
Validation:
requireUser(event)- Verify current plan is an Ag plan (
isAgPlan(plan)fromserver/utils/pricing.js) - Verify the target tier is strictly higher than the current tier (no sideways or downward "upgrade")
- Build a Stripe Checkout Session that modifies the existing subscription to the new price ID
- Return the checkout URL
Downgrade endpoint is separate so the UI can treat them distinctly:
POST /api/billing/ag-bundle/downgrade
body: { targetTier: "ag_lite" | "ag_farmer" }
→ { effective_date: "2026-07-15", target_tier: "ag_farmer" }
Downgrades take effect at the end of the current billing period (no immediate proration credit). The response gives the customer-portal UI a date to display.
Trial promo path
The 8 existing ag customers get a 90-day Investor trial via a Stripe promo code. The "Upgrade to Investor" checkout session sets allow_promotion_codes: true. After the 90-day trial, the customer either stays on Investor (at the standard price) or downgrades to Farmer/Lite via the downgrade endpoint.
The promo code itself is configured in Stripe — no code change needed to support it. Operations runbook will document the code distribution.
Webhook handling
On customer.subscription.updated for an Ag plan, the webhook updates app.subscriptions.plan to the new Ag plan ID and emits:
- A confirmation email to the customer
- A Telegram notification for the founder-led sales motion (existing pattern)
- If the new tier unlocks features the customer didn't previously have, fire the standard "feature available" prompt in-app on next sign-in
UI components
app/components/billing/AgBundlePanel.vue— top-level panelapp/components/billing/AgTierComparison.vue— the comparison tableapp/components/billing/AgUpgradeDialog.vue— confirmation dialog before checkoutapp/components/billing/AgDowngradeDialog.vue— downgrade confirmation with effective-date display
All components follow existing customer-portal patterns from app/components/billing/.
Acceptance criteria progress
- Entitlement helpers shipped via #156
- Tier-comparison view component
- Upgrade endpoint + Stripe Checkout integration
- Downgrade endpoint + scheduled change
- Webhook handling for plan changes
- Trial promo code path for 8 existing ag customers
- Immediate feature unlock on upgrade (no re-login required)
Related features
- Ag Bundle Stripe SKU + entitlement helpers (#156)
- Land Portfolio Manager (tier-gated by Ag plan)
- One-Click Parcel Report (free-tier mechanics tied to Ag Lite quota)
- Territory & Prospecting (Investor-only)