Configuration
The Stripe integration requires three environment variables on the backend:| Variable | Description |
|---|---|
STRIPE_SECRET_KEY | Your Stripe secret key (starts with sk_) |
STRIPE_PRO_PRICE_ID | The Stripe Price ID for the Pro plan (starts with price_) |
STRIPE_WEBHOOK_SECRET | The webhook signing secret from your Stripe dashboard (starts with whsec_) |
API endpoints
All payment endpoints are mounted at/api/payments.
| Method | Path | Auth required | Description |
|---|---|---|---|
POST | /api/payments/create-checkout | Yes | Start a Stripe Checkout session |
POST | /api/payments/billing-portal | Yes | Open the Stripe Billing Portal |
POST | /api/payments/cancel | Yes | Schedule subscription cancellation |
GET | /api/payments/status | Yes | Get current subscription status |
POST | /api/payments/webhook | No (raw body) | Receive Stripe webhook events |
Creating a checkout session
To start the subscription checkout flow, send aPOST request to /api/payments/create-checkout. No request body is required — the user’s ID and email are read from the JWT token.
url. Stripe handles everything from there. On success, Stripe redirects to {FRONTEND_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}. On cancellation, the user lands on {FRONTEND_URL}/payment/cancel.
The checkout session is created with
allow_promotion_codes: true, so users can enter valid Stripe promotion codes at checkout.Managing subscription via billing portal
The Stripe Billing Portal lets Pro users update their payment method, download invoices, and manage their subscription directly through a Stripe-hosted interface.url. After they finish, Stripe returns them to {FRONTEND_URL}/settings.
Canceling a subscription
Cancellation in Hayon is always deferred to the end of the current billing period. The user retains Pro access until then.cancel_at_period_end = true in Stripe and syncs the cancelAtPeriodEnd flag to the user’s record in MongoDB. When the period ends, Stripe fires customer.subscription.deleted and Hayon automatically downgrades the user to the Free plan.
Error cases:
| Condition | Status | Message |
|---|---|---|
| User is on the Free plan | 400 | No active Pro subscription to cancel |
| No Stripe subscription ID on record | 400 | No Stripe subscription ID found |
| Already scheduled for cancellation | 400 | Subscription is already scheduled for cancellation |
Checking subscription status
subscription.status: active, cancelled, pastDue.
Webhook handling
Stripe sends lifecycle events toPOST /api/payments/webhook. This endpoint is unauthenticated and does not go through the global express.json() middleware.
Raw body requirement
This is configured inpayment.routes.ts:
stripe-signature header and verifies the event against your STRIPE_WEBHOOK_SECRET before processing.
Handled webhook events
checkout.session.completed
checkout.session.completed
Fired when the user completes a checkout session and payment is confirmed. Hayon:
- Reads the
userIdfrom the session’smetadatafield. - Retrieves the full subscription object from Stripe to get billing period dates.
- Upgrades the user to the Pro plan in MongoDB, storing
stripeCustomerId,stripeSubscriptionId,currentPeriodStart, andcurrentPeriodEnd.
invoice.payment_succeeded
invoice.payment_succeeded
Fired on every successful invoice payment. The first invoice (reason:
subscription_create) is skipped because it is already handled by checkout.session.completed. For subsequent renewals, Hayon:- Looks up the user by
stripeCustomerId. - Fetches updated billing period dates from Stripe.
- Resets usage counters and updates
currentPeriodStart/currentPeriodEndin MongoDB.
invoice.payment_failed
invoice.payment_failed
Fired when a renewal invoice fails (e.g., expired or declined card). Hayon marks the user’s subscription
status as pastDue. The user’s account stays on Pro but they will be prompted to update their payment method via the billing portal.customer.subscription.deleted
customer.subscription.deleted
Fired after a subscription fully expires — typically after a
cancel_at_period_end period ends or Stripe terminates a past-due subscription. Hayon downgrades the user to the Free plan and clears the Stripe subscription reference.customer.subscription.updated
customer.subscription.updated
Fired whenever a subscription is updated — most commonly when
cancel_at_period_end is toggled (e.g., via the billing portal). Hayon syncs the cancelAtPeriodEnd flag in MongoDB to match Stripe’s record.Testing webhooks locally
Use the Stripe CLI to forward events to your local server:whsec_...). Set this as STRIPE_WEBHOOK_SECRET in your .env for local development.
