Files
dictia-public/config/env.stripe.example
Allison f1a5ad565f feat(billing): B-2.7 Stripe Checkout 3 plans CAD/TVQ + Apple/Google Pay
Adds the customer-facing checkout flow under /checkout/<plan>:
- src/billing/plans.py — Plan dataclass + 3 plans (DictIA 8 / 16 / Cloud),
  monthly + yearly Price IDs resolved from STRIPE_DICTIA_*_{SETUP,MONTHLY,YEARLY} env.
- src/billing/stripe_client.py — lazy stripe.api_key init, get_or_create_customer
  (persists user.stripe_customer_id), create_checkout_session with mode=subscription,
  currency=cad, automatic_tax=true (TPS 5% + TVQ 9.975%), billing_address_collection,
  metadata on both Session and Subscription for the B-2.8 webhook.
- src/billing/routes.py — GET /checkout/<plan>?period=monthly|yearly returns 303
  redirect to Stripe-hosted Checkout. Friendly French flash + redirect to /tarifs
  on unknown plan, missing STRIPE_SECRET_KEY, missing Price IDs, or Stripe API error.
  GET /checkout/success and /checkout/cancel render brand-tokenized templates that
  extend marketing/base.html.
- templates/billing/{success,cancel}.html — explicit "activé sous quelques minutes"
  note (webhook is async), aucun montant prélevé reassurance on cancel.
- config/env.stripe.example — env vars + Stripe Dashboard setup checklist
  (CAD activation, Stripe Tax registrations, Apple/Google Pay enable, webhook).
- tests/test_stripe_checkout.py — 25 tests covering plans, stripe_client, routes,
  and the _PUBLIC_INDEXABLE_ENDPOINTS integration. Stripe SDK mocked via
  unittest.mock.patch (no network). Windows manual driver included.

Webhook (B-2.8) will be the source of truth for user.subscription_status.
This task only mutates user.stripe_customer_id (identity, not state).
Existing pricing CTAs in templates/marketing/_partials/_pricing_tiers.html
already link to /checkout/<slug> (verified) — no marketing template touched.

Tests: 25/25 new + 89/89 prior pass on Windows manual driver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:26:13 -04:00

69 lines
3.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
###############################################################################
# Stripe — Checkout + Subscriptions (B-2.7 / B-2.8)
###############################################################################
#
# Required for the /checkout/<plan> flow and the /webhooks/stripe receiver.
# The application will boot without these — billing routes will redirect to
# /tarifs with a "contact info@dictia.ca" message until the keys are set.
#
# Get these from https://dashboard.stripe.com (CAD account)
# - Use sk_test_/pk_test_/whsec_test_ keys against the Stripe test mode for
# pre-prod. Switch to live keys ONLY after end-to-end CAD/TVQ rehearsal.
# STRIPE_SECRET_KEY=sk_test_... # or sk_live_...
# STRIPE_PUBLISHABLE_KEY=pk_test_... # used client-side; not strictly needed for hosted Checkout
# STRIPE_WEBHOOK_SECRET=whsec_... # for B-2.8 webhook signature verification
###############################################################################
# Price IDs — one per plan, period, and (for hardware plans) setup fee.
###############################################################################
#
# Format: price_xxxxxxxxxxxxxxxxxxxxxxxxxx
# Naming convention in this codebase: STRIPE_<PLAN>_<TYPE>
# PLAN = DICTIA_8 | DICTIA_16 | DICTIA_CLOUD
# TYPE = SETUP (one-time, hardware only) | MONTHLY | YEARLY
#
# Yearly Price = Monthly Price × 12 × 0.85 (15 % discount). Configure both
# Prices in the Stripe Dashboard for each plan.
# DictIA 8 (8-channel hardware bundle): 3 450 $ setup + 173 $/mo
# STRIPE_DICTIA_8_SETUP=price_xxx
# STRIPE_DICTIA_8_MONTHLY=price_xxx
# STRIPE_DICTIA_8_YEARLY=price_xxx
# DictIA 16 (16-channel hardware bundle): 5 750 $ setup + 201 $/mo
# STRIPE_DICTIA_16_SETUP=price_xxx
# STRIPE_DICTIA_16_MONTHLY=price_xxx
# STRIPE_DICTIA_16_YEARLY=price_xxx
# DictIA Cloud (SaaS-only, no hardware): 369 $/mo
# STRIPE_DICTIA_CLOUD_MONTHLY=price_xxx
# STRIPE_DICTIA_CLOUD_YEARLY=price_xxx
###############################################################################
# Required Stripe Dashboard configuration
###############################################################################
#
# 1. Activate CAD currency on the account (Settings → Account → Currencies).
#
# 2. Enable Stripe Tax with TPS (5 %) and TVQ (9.975 %) for Quebec
# (Tax → Settings → Tax registrations → Canada → Quebec).
# All Checkout Sessions are created with `automatic_tax: { enabled: true }`
# and `billing_address_collection: required` so Stripe computes taxes.
#
# 3. Enable Apple Pay + Google Pay
# (Settings → Payment methods → Apple Pay, Google Pay).
# Apple Pay requires verifying the dictia.ca domain via the Stripe-hosted
# `.well-known/apple-developer-merchantid-domain-association` file.
#
# 4. For each plan, create:
# - One recurring monthly Price (CAD, billing_scheme=per_unit)
# - One recurring yearly Price (CAD, = monthly × 12 × 0.85)
# For DictIA 8 and DictIA 16, also create a one-time Price for the setup fee.
#
# 5. Create a webhook endpoint (B-2.8) pointing at https://dictia.ca/webhooks/stripe
# with at least the events: checkout.session.completed,
# customer.subscription.created, customer.subscription.updated,
# customer.subscription.deleted, invoice.payment_failed.
# Copy the signing secret into STRIPE_WEBHOOK_SECRET above.