Add paid-account subscription checkout and confirmation flow #359

Open
opened 2026-06-03 23:38:52 -05:00 by Codex · 1 comment
Member

The Stripe/Cashier foundation from issue #1 is in place, but MyVideoGameList still needs the actual paid-account subscription flow built on top of it.

Issue #1's follow-up notes called out that the donation/payment foundation should be easy to integrate with paid accounts, likely with multiple buttons for differing amounts. The older subscription-confirmation note also included a useful receipt-style template: welcome message, transaction/subscription details, renewal or expiration date, cancellation guidance, FAQ/support links, and a reminder to keep the message for records. This issue tracks turning that foundation into the first user-facing paid-account subscription experience.

Scope

  • Paid-account subscription entry point for authenticated users.
  • Production/staging env-var setup for Stripe keys, webhook secret, Cashier currency, paid-account Price IDs, and subscription type names.
  • One or more configured Stripe Price choices rendered as clear paid-account buttons/cards.
  • Server-side Stripe Checkout Session creation in subscription mode using configured Price IDs.
  • Authenticated Stripe customer association through Cashier.
  • Subscription success, cancellation, and failure feedback.
  • Account billing management through the existing Customer Portal route.
  • Receipt/confirmation messaging for successful subscriptions using the issue #1 template as inspiration.
  • Safe handling of incomplete, cancelled, expired, and past-due subscription states.
  • Tests for pricing config, checkout handoff, portal access, webhooks, status display, and secret safety.

Helpful Stripe Documentation

Acceptance Criteria

  • Required env vars are identified and configured for each deployed environment: STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET, CASHIER_CURRENCY, CASHIER_CURRENCY_LOCALE, paid-account Stripe Price IDs, and subscription type names.
  • Paid-account prices are read from config/env and no Stripe Price IDs or secrets are hard-coded in Blade or controllers.
  • Missing or incomplete paid-account env/config fails safely with a user-friendly message and without creating a partial subscription attempt.
  • Authenticated users can choose from the configured paid-account options and start Stripe Checkout in subscription mode.
  • Guests cannot start paid-account subscription checkout and are prompted to log in.
  • Checkout uses Stripe Billing, Prices, Checkout Sessions, and Cashier rather than manual renewal logic.
  • Successful subscription checkout returns show a clear paid-account confirmation state.
  • Confirmation messaging includes useful receipt-style details when available: transaction/subscription identifier, subscription name/amount, renewal or expiration date, cancellation guidance, and support/FAQ links.
  • Cancelled or failed subscription attempts show safe feedback without exposing provider details.
  • Users with a Stripe customer can access Customer Portal billing management from an appropriate account/settings surface.
  • Stripe webhooks keep local subscription state current for created, updated, deleted, incomplete, past-due, and cancelled subscriptions.
  • User-facing UI supports light/dark mode and follows existing Laravel/Tailwind site layout patterns.
  • The final implementation and environment setup are checked against the linked Stripe documentation before closeout.

Test Coverage Required

  • Feature tests for paid-account option rendering and configured Price IDs not being leaked as secrets.
  • Feature/unit tests for required paid-account env/config presence and missing-config failure behavior.
  • Feature tests confirming guests cannot start subscription checkout.
  • Feature tests confirming authenticated users can start subscription checkout through a fake billing gateway.
  • Tests confirming checkout uses configured server-side Price IDs rather than client-submitted price data.
  • Feature tests for success, cancel, and failure return states.
  • Tests for Customer Portal access and safe failure behavior when a user does not yet have a Stripe customer.
  • Webhook tests for subscription created, updated, deleted/cancelled, incomplete, and past-due states where handled by the app.
  • Regression tests confirming Stripe secrets and webhook secrets are never rendered in HTML.
  • Run the focused affected tests, then run vendor/bin/pint --dirty before closing the issue.

Progress Checklist

  • Stripe/Cashier package is installed
  • User is billable and retains legacy stripe_id
  • Cashier-compatible subscription tables exist
  • Paid-account Price ID config placeholder exists
  • Fakeable billing gateway exists
  • Customer Portal route exists
  • Review linked Stripe docs before implementation and again before closeout
  • Decide paid-account tiers/prices and env key names for each option
  • Configure required Stripe/Cashier env vars in staging and production
  • Confirm Stripe Dashboard Price IDs and Customer Portal settings match the env values
  • Add paid-account subscription routes/controllers/actions
  • Add paid-account option UI for authenticated users
  • Add subscription Checkout Session handoff using configured Stripe Prices
  • Add subscription success, cancellation, and failure feedback views
  • Add receipt/confirmation messaging based on the issue #1 template note
  • Add billing/customer-portal entry point to account/settings UI
  • Confirm webhook-driven subscription state updates meet paid-account access needs
  • Add tests for env/config, rendering, checkout, returns, portal access, webhooks, state handling, and secret safety
  • Confirm paid-account subscription flow is ready for production configuration
The Stripe/Cashier foundation from issue #1 is in place, but MyVideoGameList still needs the actual paid-account subscription flow built on top of it. Issue #1's follow-up notes called out that the donation/payment foundation should be easy to integrate with paid accounts, likely with multiple buttons for differing amounts. The older subscription-confirmation note also included a useful receipt-style template: welcome message, transaction/subscription details, renewal or expiration date, cancellation guidance, FAQ/support links, and a reminder to keep the message for records. This issue tracks turning that foundation into the first user-facing paid-account subscription experience. ## Scope - Paid-account subscription entry point for authenticated users. - Production/staging env-var setup for Stripe keys, webhook secret, Cashier currency, paid-account Price IDs, and subscription type names. - One or more configured Stripe Price choices rendered as clear paid-account buttons/cards. - Server-side Stripe Checkout Session creation in subscription mode using configured Price IDs. - Authenticated Stripe customer association through Cashier. - Subscription success, cancellation, and failure feedback. - Account billing management through the existing Customer Portal route. - Receipt/confirmation messaging for successful subscriptions using the issue #1 template as inspiration. - Safe handling of incomplete, cancelled, expired, and past-due subscription states. - Tests for pricing config, checkout handoff, portal access, webhooks, status display, and secret safety. ## Helpful Stripe Documentation - [Design a subscriptions integration](https://docs.stripe.com/billing/subscriptions/designing-integration) - use this to confirm the subscription lifecycle, pricing model, and state handling. - [Create subscriptions with Checkout](https://docs.stripe.com/payments/subscriptions) - use this for `mode=subscription`, passing configured Price IDs, and subscription Checkout behavior. - [Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions) - use this when implementing and testing the server-side Checkout Session handoff. - [Products and Prices](https://docs.stripe.com/products-prices/how-products-and-prices-work) - use this when deciding paid-account products, recurring prices, and Price ID env values. - [Customer Portal integration](https://docs.stripe.com/customer-management/integrate-customer-portal) and [Customer Portal configuration](https://docs.stripe.com/customer-management/configure-portal) - use these to verify billing-management links and Dashboard portal settings. - [Webhook signature verification](https://docs.stripe.com/webhooks/signatures) and [subscription webhook events](https://docs.stripe.com/billing/subscriptions/webhooks) - use these to verify webhook setup, signing secrets, and subscription status updates. - [Go-live checklist](https://docs.stripe.com/get-started/checklist/go-live) - use this before marking the paid-account flow production-ready. ## Acceptance Criteria - Required env vars are identified and configured for each deployed environment: `STRIPE_KEY`, `STRIPE_SECRET`, `STRIPE_WEBHOOK_SECRET`, `CASHIER_CURRENCY`, `CASHIER_CURRENCY_LOCALE`, paid-account Stripe Price IDs, and subscription type names. - Paid-account prices are read from config/env and no Stripe Price IDs or secrets are hard-coded in Blade or controllers. - Missing or incomplete paid-account env/config fails safely with a user-friendly message and without creating a partial subscription attempt. - Authenticated users can choose from the configured paid-account options and start Stripe Checkout in subscription mode. - Guests cannot start paid-account subscription checkout and are prompted to log in. - Checkout uses Stripe Billing, Prices, Checkout Sessions, and Cashier rather than manual renewal logic. - Successful subscription checkout returns show a clear paid-account confirmation state. - Confirmation messaging includes useful receipt-style details when available: transaction/subscription identifier, subscription name/amount, renewal or expiration date, cancellation guidance, and support/FAQ links. - Cancelled or failed subscription attempts show safe feedback without exposing provider details. - Users with a Stripe customer can access Customer Portal billing management from an appropriate account/settings surface. - Stripe webhooks keep local subscription state current for created, updated, deleted, incomplete, past-due, and cancelled subscriptions. - User-facing UI supports light/dark mode and follows existing Laravel/Tailwind site layout patterns. - The final implementation and environment setup are checked against the linked Stripe documentation before closeout. ## Test Coverage Required - Feature tests for paid-account option rendering and configured Price IDs not being leaked as secrets. - Feature/unit tests for required paid-account env/config presence and missing-config failure behavior. - Feature tests confirming guests cannot start subscription checkout. - Feature tests confirming authenticated users can start subscription checkout through a fake billing gateway. - Tests confirming checkout uses configured server-side Price IDs rather than client-submitted price data. - Feature tests for success, cancel, and failure return states. - Tests for Customer Portal access and safe failure behavior when a user does not yet have a Stripe customer. - Webhook tests for subscription created, updated, deleted/cancelled, incomplete, and past-due states where handled by the app. - Regression tests confirming Stripe secrets and webhook secrets are never rendered in HTML. - Run the focused affected tests, then run `vendor/bin/pint --dirty` before closing the issue. ## Progress Checklist - [x] Stripe/Cashier package is installed - [x] `User` is billable and retains legacy `stripe_id` - [x] Cashier-compatible subscription tables exist - [x] Paid-account Price ID config placeholder exists - [x] Fakeable billing gateway exists - [x] Customer Portal route exists - [ ] Review linked Stripe docs before implementation and again before closeout - [ ] Decide paid-account tiers/prices and env key names for each option - [ ] Configure required Stripe/Cashier env vars in staging and production - [ ] Confirm Stripe Dashboard Price IDs and Customer Portal settings match the env values - [ ] Add paid-account subscription routes/controllers/actions - [ ] Add paid-account option UI for authenticated users - [ ] Add subscription Checkout Session handoff using configured Stripe Prices - [ ] Add subscription success, cancellation, and failure feedback views - [ ] Add receipt/confirmation messaging based on the issue #1 template note - [ ] Add billing/customer-portal entry point to account/settings UI - [ ] Confirm webhook-driven subscription state updates meet paid-account access needs - [ ] Add tests for env/config, rendering, checkout, returns, portal access, webhooks, state handling, and secret safety - [ ] Confirm paid-account subscription flow is ready for production configuration
jimmyb self-assigned this 2026-06-05 09:18:24 -05:00
Author
Member

Implemented and committed the current issue #359 slice in commit 53fe918 (Add donation billing and gated account subscriptions).

What changed

  • Updated the reusable Help Support MyVideoGameList donation card:
    • Uniform $10, $25, $50 preset buttons.
    • Tailwind-style custom amount input group with inline Donate button.
    • $5.00 minimum donation default and client-side below-minimum feedback.
    • Donation UI/result text sizing and button polish.
  • Kept donations live and added billing history support:
    • Donation history table on the Settings Billing tab.
    • Local receipt redirect route: /billing/donations/{donation}/receipt.
    • Stripe receipt lookup is resolved on click rather than during settings render.
  • Added account subscription foundation:
    • /account-subscriptions routes/controller/actions.
    • Config/env-driven account subscription options and Stripe Price IDs.
    • Player, Adventurer, Champion recurring tiers plus Legendary Supporter one-time option.
    • Stripe Checkout handoff through Cashier using server-side configured Prices.
    • Success/cancel/failure feedback pages with receipt-style subscription details where available.
    • Customer Portal link support.
    • Webhook syncing for subscription created/updated/deleted states, including local period and latest invoice fields.
    • Subscription invoice redirect route: /billing/subscriptions/{subscription}/invoice.
  • Added account subscription page assets under public/images/.
  • Added laravel/pennant and gated user-facing account subscription interfaces behind App\Features\AccountSubscriptions.
    • Default is hidden via ACCOUNT_SUBSCRIPTIONS_ENABLED=false.
    • Donations remain visible/usable.
    • The account subscription routes still exist but return 404 while the feature is disabled.
    • Footer Upgrade and Settings subscription UI are hidden while disabled.

Verification run

  • php artisan test --compact tests/Feature/AccountSubscriptionTest.php - passed (15 passed)
  • php artisan test --compact tests/Feature/Settings/ProfileUpdateTest.php --filter="profile billing tab" - passed (5 passed)
  • php artisan test --compact tests/Feature/DonationTest.php - passed (16 passed)
  • php artisan test --compact tests/Feature/AccountSubscriptionTest.php tests/Feature/BillingFoundationTest.php tests/Feature/DonationTest.php - passed earlier during closeout (41 passed)
  • vendor/bin/pint --dirty --format agent - passed
  • npm run build - passed

Important notes / remaining work

  • Account subscriptions are intentionally hidden for now. This lets the code ship safely without exposing the new subscription purchase UI until we are ready.
  • Staging Stripe/Cashier env vars and Price IDs have been configured by Jimmy. Production env vars should wait until the new site is deployed live.
  • The issue text still uses "paid-account" language in places, while the implementation now uses "account subscriptions" for user-facing naming and code naming.
  • The broader subscription entitlement/perk enforcement is not implemented here. This work builds checkout, billing history, webhook sync, portal/invoice plumbing, and the gated UI foundation.
  • If we want to manage the feature through Pennant storage rather than the env-backed feature class later, we can update App\Features\AccountSubscriptions to use stored Pennant values/admin tooling.

Live enablement checklist

Before turning this on in production:

  1. Deploy the code and run migrations, including:
    • 2026_06_05_120000_add_local_billing_history_fields_to_subscriptions_table.php
    • 2026_06_06_013630_create_features_table.php
  2. Configure production Stripe/Cashier env vars:
    • STRIPE_KEY
    • STRIPE_SECRET
    • STRIPE_WEBHOOK_SECRET
    • CASHIER_CURRENCY
    • CASHIER_CURRENCY_LOCALE
    • all STRIPE_ACCOUNT_SUBSCRIPTION_*_PRICE_ID values
    • STRIPE_LEGENDARY_SUPPORTER_PRICE_ID
  3. Confirm Stripe Dashboard live-mode Products/Prices match the env values.
  4. Confirm Stripe Customer Portal live-mode settings:
    • Return URL points back to MyVideoGameList.
    • Invoice history and payment method updates are enabled.
    • Cancellation/update behavior matches what we want to allow.
    • Only recurring subscription products are exposed for subscription changes.
  5. Confirm the production webhook endpoint is live and listening for the events this app handles:
    • checkout.session.completed
    • checkout.session.expired
    • payment_intent.payment_failed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
  6. Set ACCOUNT_SUBSCRIPTIONS_ENABLED=true.
  7. Clear cached config/routes/views if needed (php artisan optimize:clear, then rebuild cache as appropriate for deploy).
  8. Smoke test with a real live-mode flow:
    • Authenticated subscription checkout.
    • Success/cancel/failure returns.
    • Webhook-created subscription state.
    • Settings Billing tab.
    • Customer Portal and invoice links.

Once the production/dashboard pieces are confirmed and the feature flag is enabled, the remaining issue checkboxes related to external config and production readiness can be marked complete.

Implemented and committed the current issue #359 slice in commit `53fe918` (`Add donation billing and gated account subscriptions`). ## What changed - Updated the reusable `Help Support MyVideoGameList` donation card: - Uniform `$10`, `$25`, `$50` preset buttons. - Tailwind-style custom amount input group with inline Donate button. - `$5.00` minimum donation default and client-side below-minimum feedback. - Donation UI/result text sizing and button polish. - Kept donations live and added billing history support: - Donation history table on the Settings Billing tab. - Local receipt redirect route: `/billing/donations/{donation}/receipt`. - Stripe receipt lookup is resolved on click rather than during settings render. - Added account subscription foundation: - `/account-subscriptions` routes/controller/actions. - Config/env-driven account subscription options and Stripe Price IDs. - Player, Adventurer, Champion recurring tiers plus Legendary Supporter one-time option. - Stripe Checkout handoff through Cashier using server-side configured Prices. - Success/cancel/failure feedback pages with receipt-style subscription details where available. - Customer Portal link support. - Webhook syncing for subscription created/updated/deleted states, including local period and latest invoice fields. - Subscription invoice redirect route: `/billing/subscriptions/{subscription}/invoice`. - Added account subscription page assets under `public/images/`. - Added `laravel/pennant` and gated user-facing account subscription interfaces behind `App\Features\AccountSubscriptions`. - Default is hidden via `ACCOUNT_SUBSCRIPTIONS_ENABLED=false`. - Donations remain visible/usable. - The account subscription routes still exist but return 404 while the feature is disabled. - Footer `Upgrade` and Settings subscription UI are hidden while disabled. ## Verification run - `php artisan test --compact tests/Feature/AccountSubscriptionTest.php` - passed (`15 passed`) - `php artisan test --compact tests/Feature/Settings/ProfileUpdateTest.php --filter="profile billing tab"` - passed (`5 passed`) - `php artisan test --compact tests/Feature/DonationTest.php` - passed (`16 passed`) - `php artisan test --compact tests/Feature/AccountSubscriptionTest.php tests/Feature/BillingFoundationTest.php tests/Feature/DonationTest.php` - passed earlier during closeout (`41 passed`) - `vendor/bin/pint --dirty --format agent` - passed - `npm run build` - passed ## Important notes / remaining work - Account subscriptions are intentionally hidden for now. This lets the code ship safely without exposing the new subscription purchase UI until we are ready. - Staging Stripe/Cashier env vars and Price IDs have been configured by Jimmy. Production env vars should wait until the new site is deployed live. - The issue text still uses "paid-account" language in places, while the implementation now uses "account subscriptions" for user-facing naming and code naming. - The broader subscription entitlement/perk enforcement is not implemented here. This work builds checkout, billing history, webhook sync, portal/invoice plumbing, and the gated UI foundation. - If we want to manage the feature through Pennant storage rather than the env-backed feature class later, we can update `App\Features\AccountSubscriptions` to use stored Pennant values/admin tooling. ## Live enablement checklist Before turning this on in production: 1. Deploy the code and run migrations, including: - `2026_06_05_120000_add_local_billing_history_fields_to_subscriptions_table.php` - `2026_06_06_013630_create_features_table.php` 2. Configure production Stripe/Cashier env vars: - `STRIPE_KEY` - `STRIPE_SECRET` - `STRIPE_WEBHOOK_SECRET` - `CASHIER_CURRENCY` - `CASHIER_CURRENCY_LOCALE` - all `STRIPE_ACCOUNT_SUBSCRIPTION_*_PRICE_ID` values - `STRIPE_LEGENDARY_SUPPORTER_PRICE_ID` 3. Confirm Stripe Dashboard live-mode Products/Prices match the env values. 4. Confirm Stripe Customer Portal live-mode settings: - Return URL points back to MyVideoGameList. - Invoice history and payment method updates are enabled. - Cancellation/update behavior matches what we want to allow. - Only recurring subscription products are exposed for subscription changes. 5. Confirm the production webhook endpoint is live and listening for the events this app handles: - `checkout.session.completed` - `checkout.session.expired` - `payment_intent.payment_failed` - `customer.subscription.created` - `customer.subscription.updated` - `customer.subscription.deleted` 6. Set `ACCOUNT_SUBSCRIPTIONS_ENABLED=true`. 7. Clear cached config/routes/views if needed (`php artisan optimize:clear`, then rebuild cache as appropriate for deploy). 8. Smoke test with a real live-mode flow: - Authenticated subscription checkout. - Success/cancel/failure returns. - Webhook-created subscription state. - Settings Billing tab. - Customer Portal and invoice links. Once the production/dashboard pieces are confirmed and the feature flag is enabled, the remaining issue checkboxes related to external config and production readiness can be marked complete.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
MyVideoGameList/myvideogamelist.com#359
No description provided.