Skip to main content

Knowledge > Runbooks > Customer Ops > Cancel Subscription

Cancel a Church Subscription

Handle a subscription cancellation, whether triggered automatically via Stripe webhook or executed manually at a church's request.

Prerequisites

  • Stripe CLI logged in (test mode by default; live mode needs --api-key $STRIPE_LIVE_SECRET_KEY)
  • Supabase access
  • The church's Stripe subscription ID (sub_...) or customer ID (cus_...)

Automated Flow (what should happen)

When a church cancels via the Stripe customer portal or the subscription expires:

  1. Stripe fires customer.subscription.deleted (immediate cancel) or customer.subscription.updated (cancel at period end)
  2. Webhook handler updates premium_churches to reset plan/tier
  3. Church loses premium feature access at the appropriate time

Steps — Manual Cancellation at Church Request

  1. Find the church's subscription ID

    stripe customers list --email "[church-email]"

    Note the cus_ ID, then:

    stripe subscriptions list --customer cus_xxxxxxxxxxxx

    Note the sub_ ID and current_period_end date (Unix timestamp — tells you when access should end).

  2. Cancel the subscription in Stripe

    Cancel at period end (recommended — church retains access until they've paid for):

    stripe subscriptions update sub_xxxxxxxxxxxx --cancel-at-period-end=true

    Cancel immediately (use only if church requests immediate cancellation or for refund):

    stripe subscriptions cancel sub_xxxxxxxxxxxx

    For live mode, append: --api-key $STRIPE_LIVE_SECRET_KEY

  3. Verify the Stripe webhook fired (for immediate cancellation)

    stripe events list --limit 5

    Look for customer.subscription.deleted with status: delivered.

  4. Verify premium_churches was updated

    SELECT church_id, plan, tier, stripe_subscription_id, updated_at
    FROM premium_churches
    WHERE stripe_subscription_id = 'sub_xxxxxxxxxxxx';

    After immediate cancellation: plan should be 'free' (or the row removed, depending on webhook handler implementation). After cancel-at-period-end, the row remains active until the period ends.

  5. If webhook did not fire — update manually

    UPDATE premium_churches
    SET plan = 'free',
    tier = NULL,
    stripe_subscription_id = NULL,
    updated_at = now()
    WHERE stripe_subscription_id = 'sub_xxxxxxxxxxxx';

    Confirm with founder before running this in live mode.

  6. Send a cancellation confirmation email to the church (if not automated via Stripe/Resend)

Cancel-at-Period-End Behavior

When cancel-at-period-end=true:

  • Stripe does NOT immediately fire customer.subscription.deleted
  • The subscription stays active until current_period_end
  • Stripe fires customer.subscription.deleted at that date
  • The church retains full access until then

If you need to verify what date their access ends:

stripe subscriptions retrieve sub_xxxxxxxxxxxx

Look at current_period_end (convert Unix timestamp to date).

Refund

If the church is also requesting a refund, see refund.md after completing cancellation.

Verification

After cancellation:

  • Church cannot access premium features at churchwiseai.com/admin (if immediately cancelled)
  • Stripe subscription shows status: canceled
  • premium_churches row reflects the correct state

See Also

  • refund.md — process a refund after cancellation
  • onboard-new-church.md — if they resubscribe later
  • C:\dev\PRICING.md — plan definitions and cancellation policy references
  • C:\dev\knowledge\data\policies.yaml — cancellation and refund policies