Docs index
Feature Reference

Energy Bundle — Customer Portal "Add Energy Bundle" CTA

Status: Planned Issue: #155Depends on: #149 (Energy Bundle Stripe SKU + entitlement helpers — shipped)

Energy Bundle — Customer Portal "Add Energy Bundle" CTA

Status: Planned Issue: #155Depends on: #149 (Energy Bundle Stripe SKU + entitlement helpers — shipped)

Price (live in Stripe as of 2026-05-20, re-priced to round numbers 2026-05-21): $100 CAD/seat/month or $1,000 CAD/seat/year (2 months free). Product: prod_UYQu6Drc1obzVY. Resolve price IDs via getVerticalBundlePriceId("energy_bundle", interval) from server/utils/pricing.js.

Goal

Self-serve add-on upgrade flow inside the customer portal for Business-plan customers. A single "Add Energy Bundle" CTA card in the account page that opens a Stripe-hosted checkout for the add-on. On success, metadata.energy_bundle = true lands on the subscription and the energy-specific features unlock immediately.

CTA placement

The CTA card lives on /app/account (the customer portal page) in a new "Add-ons" section below the existing plan/invoice/payment sections. Visibility rules:

  • Hidden when the user is not on Business
  • Hidden when metadata.energy_bundle is already true
  • Visible — primary when the user is on Business and not yet on the bundle

For Starter and Pro customers, no CTA appears. Instead, the existing tier-upgrade path takes them to Business first. Once they're on Business, the Energy Bundle add-on becomes available — a one-click upgrade rather than a side-stream.

Component

app/components/billing/EnergyBundleCard.vue:

  • Card layout consistent with the existing billing/account UI (UCard + UButton from Nuxt UI)
  • Title: "Energy Bundle"
  • Subtitle: short benefits list (AER wells filters, CCS layers, asset history, Indigenous consultation overlay, Crown dispositions overlay — surfaces from the Energy Bundle docs)
  • Price line: pulled from STRIPE_ENERGY_BUNDLE_DISPLAY_PRICE so the dollar value isn't hardcoded
  • Primary action: "Add Energy Bundle" → POSTs to /api/billing/energy-bundle/checkout which returns a Stripe Checkout URL
  • Secondary action: "Learn more" → opens /use-cases/energy (once that route is wired — see follow-up task)

API

POST /api/billing/energy-bundle/checkout
  body: {}
→ { url: "https://checkout.stripe.com/c/pay/..." }

Implementation:

  1. requireUser(event) + check the user is on Business (requireBusiness(event))
  2. Verify they don't already have the bundle (idempotent — clicking twice doesn't open two checkout sessions)
  3. Create a Stripe Checkout session with the price ID from STRIPE_ENERGY_BUNDLE_PRICE_ID, client_reference_id set to the user ID, and the subscription set to modify the existing Business subscription (not create a parallel one)
  4. Return the checkout URL

Webhook handler (server/api/webhooks/stripe.post.js) on the checkout.session.completed event for mode: subscription with the energy bundle price ID:

  1. Update app.subscriptions.metadata.energy_bundle = true for the user
  2. Send a confirmation email
  3. Trigger a Telegram notification for the founder-led sales motion

Trial promo code

The 10 existing energy customers get a trial promo code (90-day or full-comp — TBD per operations runbook). The Stripe Checkout session includes allow_promotion_codes: true so they can paste the code at checkout. The promo code itself is configured in Stripe directly — no code change needed.

Acceptance criteria progress

  • Energy Bundle entitlement helpers shipped via #149
  • "Add Energy Bundle" CTA card component
  • CTA visible only to Business customers not yet on the bundle
  • /api/billing/energy-bundle/checkout endpoint
  • Stripe Checkout session creation with the bundle price ID
  • Webhook handler setting metadata.energy_bundle = true on success
  • Entitlement applied immediately (energy-gated features unlock without re-login)
  • Trial promo code path for 10 existing energy customers
  • Confirmation email + Telegram notification

UI consistency

The card follows the existing customer-portal design patterns. Reference components on /app/account:

  • app/components/billing/PlanCard.vue — current plan summary card
  • app/components/billing/PaymentMethodCard.vue — payment method card
  • app/components/billing/InvoiceList.vue — invoice history

The new EnergyBundleCard.vue matches their visual treatment (padding, border, header style) so the add-on doesn't read as a different surface.