Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.aspfox.com/llms.txt

Use this file to discover all available pages before exploring further.

Changing plan prices

No code changes required. Update prices entirely in Stripe:
  1. Stripe Dashboard → Products → click on Pro
  2. Click Edit on the pricing section
  3. Stripe does not allow editing existing price amounts. Create a new price instead:
    • Click Add another price
    • Set the new amount and billing interval
    • Archive the old price
  4. Copy the new Price ID → update STRIPE_PRO_PRICE_ID in your environment variables
  5. Restart the API (or redeploy)
Existing subscribers stay on their current price. New subscribers use the new price.

Adding a fourth plan

Step 1: Create the plan in Stripe Dashboard and copy the Price ID. Step 2: Add a new environment variable: STRIPE_ENTERPRISE_PRICE_ID=price_… Step 3: Add the plan to the Plan enum in Domain:
// src/Acme.Domain/Enums/Plan.cs
public enum Plan { Free, Pro, Business, Enterprise }
Step 4: Update StripeService.GetPriceIdForPlan():
Plan.Enterprise => _config.EnterprisePriceId
    ?? throw new InvalidOperationException("Enterprise Price ID not configured"),
Step 5: Update the frontend PLANS constant:
// frontend/src/lib/constants.ts
export const PLANS = [
  { name: 'Free', price: 0, features: ['…'] },
  { name: 'Pro', price: 29, features: ['…'] },
  { name: 'Business', price: 79, features: ['…'] },
  { name: 'Enterprise', price: 199, features: ['…'] },
] as const;

Testing the full billing flow locally

Prerequisites: Stripe CLI installed, stripe listen running, API started. Complete a test checkout:
  1. Log in to the frontend at http://localhost:5173
  2. Navigate to Billing → click Upgrade to Pro
  3. You are redirected to Stripe’s test checkout
  4. Use test card: 4242 4242 4242 4242, any future expiry, any CVC
  5. Click Subscribe
  6. You are redirected back to /billing?success=true
  7. The Billing page now shows the Pro plan
Watch webhook events in the terminal:
--> checkout.session.completed [evt_…]
<-- 200 POST http://localhost:5000/api/v1/webhooks/stripe [200ms]
Verify the database:
make shell-db
SELECT plan, status, trial_ends_at FROM subscriptions WHERE tenant_id = '…';
\q

Stripe test card numbers

ScenarioCard number
Successful payment4242 4242 4242 4242
Card declined4000 0000 0000 0002
Requires 3DS authentication4000 0027 6000 3184
Insufficient funds4000 0000 0000 9995
Expired card4000 0000 0000 0069
Use any future expiry date and any 3-digit CVC for all test cards.

Triggering specific webhook events manually

# Test subscription deletion
stripe trigger customer.subscription.deleted

# Test payment failure
stripe trigger invoice.payment_failed

# Test payment success
stripe trigger invoice.payment_succeeded
stripe trigger uses Stripe’s test fixture data. The tenantId in the metadata may not match a real tenant in your local database. Webhook handlers that look up a tenant by the metadata tenantId will log a warning and return 200 without updating local state. This is expected behavior — the trigger is useful for testing the webhook parsing and signature verification, not the full business logic.

Recovering from a missed webhook

The SubscriptionSyncJob runs every 2 hours and reconciles all tenant subscriptions with Stripe. If a webhook was missed, the sync job corrects local state within 2 hours automatically. To correct immediately:
  1. Go to the Hangfire dashboard: http://localhost:5000/hangfire
  2. Click Recurring Jobs
  3. Find subscription-sync
  4. Click Trigger now
Or trigger it directly from a shell (requires admin access):
# This endpoint triggers the sync job and returns immediately.
# The job runs asynchronously.
curl -X POST http://localhost:5000/api/v1/admin/jobs/subscription-sync \
  -H "Authorization: Bearer <admin-token>"