Docs index
Feature Reference

Ag Bundle — Customer-Portal Tier Upgrade Flow

Status: Planned Issue: #165Depends on: #156 (Ag Bundle 3-tier SKU + entitlement helpers — shipped)

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):

TierMonthlyAnnualStripe product
Lite$20 CAD$200 CADprod_UYQuITRZoGGz3s
Farmer$50 CAD$500 CADprod_UYQuxVMO8WytkP
Investor$100 CAD$1000 CADprod_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:

FeatureLiteFarmerInvestor
One-click parcel reports5/moUnlimitedUnlimited
PDF export of parcel reports
LSRS soil score (full overlay)Lookup
AAFC crop history overlayLookup
Land portfolio manager10 parcels250Unlimited
Lease renewal alerts
Territory & prospecting polygon tool
Land values panel (when shipped)RegionalFull
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) from server/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:

  1. A confirmation email to the customer
  2. A Telegram notification for the founder-led sales motion (existing pattern)
  3. 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 panel
  • app/components/billing/AgTierComparison.vue — the comparison table
  • app/components/billing/AgUpgradeDialog.vue — confirmation dialog before checkout
  • app/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)