Pastor upgrades from Voice to Pro Bundle
Persona
A pastor whose church has grown to 150+ attendees. They're happy with the voice agent and now want a chat widget, FAQ management, and analytics. Pro Bundle ($119.95/mo) costs less than buying Voice + Chat separately. Decision cycle is short — they just need the upgrade to be seamless.
Entry points
- Admin dashboard Billing section: Settings → Billing card → "Upgrade Plan" →
/pricing?upgrade=true¤t_plan=starter_voice. - Email campaign: "Ready to add chat? Pro Bundle saves $14.95/mo" — CTA links to
/pricing?plan=pro_both&upgrade=true. - Stripe Customer Portal: Plan change from portal redirects through webhook.
Click-through flow
Steps
-
Access Billing section — Settings tab → Billing shows "Starter Voice — $49.95/mo", next billing date, and "Upgrade Plan" link.
-
Pricing page shows upgrade options — Current plan has "Current Plan" badge. Pro Both card shows "Upgrade — saves $14.95/mo" and the full feature list. Annual toggle is HIDDEN (bundles are monthly only).
-
Proration confirmation — Modal shows amount due for remaining days in current cycle. Proration math is displayed for transparency (Stripe calculates; client displays without modification).
-
Multi-item subscription guard — Handler checks: Starter Voice → Pro Both is a safe same-family upgrade. Invalid path example: trying to hold Starter Voice + Starter Chat as two separate subscriptions — returns 400 "Your plan cannot be merged with Chat right now." Valid ladders: Starter Voice → Pro Voice, Pro Voice → Pro Both, Starter Voice → Pro Both.
-
Stripe checkout — Product "ChurchWiseAI Pro Bundle – $119.95/mo", prorated amount due, saved card pre-filled. On success, redirects to
/admin/[token]?upgraded=true. -
Webhook processes subscription update —
customer.subscription.updated→ webhook inbox → cron.updateSubscriptionFromStripe()reads subscription items array, updatespremium_churches.plan = 'cwa_pro_both'(raw Stripe key, never the collapsed tier fromnormalizePlanTier()). Setschatbot_enabled = true. -
Dashboard updates and confirmation email — Chat, Analytics, and FAQ tabs appear immediately. Welcome email with Pro Bundle setup next-steps (chat widget embed, first FAQ, analytics tour).
Acceptance spec
Canonical: knowledge/acceptance/pro-both.md (62 touchpoints, COMPLETE).
| Feature | Starter Voice | Pro Voice | Pro Both |
|---|---|---|---|
| Price | $49.95/mo | $99.95/mo | $119.95/mo (saves $29.90/mo) |
| Chat agents | — | — | 4 |
| FAQs | — | — | 50 limit |
| Analytics | — | — | 30-day view |
Plan column contract: premium_churches.plan holds canonical Stripe tier key (cwa_pro_both). Never write normalizePlanTier() output back to this column. See knowledge/architecture/db/plan-column-contract.md.
Success criteria
- "Upgrade Plan" is easy to find in Settings → Billing.
- Proration math is transparent (exact amount due for remainder of cycle).
- Checkout completes in under 2 minutes.
- Dashboard immediately shows Chat, FAQ, and Analytics tabs.
- Welcome email arrives within 5 minutes.
- Existing voice data (phone number, greeting, calls, prayer requests) is NOT lost.
- Voice agent continues answering calls during and after the upgrade.
Known failure modes
-
Multi-item subscription created by accident. If the guard misses, pastor ends up with two Stripe price items. Symptom: both Voice and Chat tabs accessible, but tier gating breaks. Verify
updateSubscriptionFromStripe()checkssubscription.items.length === 1after upgrade. -
Plan column not updated. If webhook doesn't write
plan = 'cwa_pro_both', dashboard gates as Starter. Pastor pays for Pro but sees Starter UI. Regression test: upgrade, then checkSELECT plan FROM premium_churches WHERE id = '[church_id]'. -
Chat not auto-enabled. Webhook must set
chatbot_enabled = truewhenchannel LIKE '%both%'. -
Proration display wrong. Never compute proration in JavaScript — Stripe is the source of truth. Display Stripe's calculation unchanged.