@xenterprises/fastify-xstripe
v1.2.1
Published
Fastify plugin for Stripe webhooks with simplified, testable handlers for subscription events.
Downloads
42
Readme
@xenterprises/fastify-xstripe
Fastify v5 plugin for Stripe webhook handling with built-in signature verification, 23 default event handlers, and the Stripe client decorated on the Fastify instance.
Install
npm install @xenterprises/fastify-xstripe stripeQuick Start
import Fastify from 'fastify';
import xStripe from '@xenterprises/fastify-xstripe';
const fastify = Fastify({ logger: true });
await fastify.register(xStripe, {
apiKey: process.env.STRIPE_API_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
});
// Use the Stripe client directly
const customer = await fastify.stripe.customers.create({ email: '[email protected]' });
await fastify.listen({ port: 3000 });Options
| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| apiKey | string | — | Yes | Stripe secret API key (sk_test_... or sk_live_...) |
| webhookSecret | string | — | Yes | Stripe webhook signing secret (whsec_...) |
| webhookPath | string | "/stripe/webhook" | No | Path where the webhook POST route is registered |
| handlers | object | {} | No | Custom event handlers that override the defaults |
| apiVersion | string | "2024-11-20.acacia" | No | Stripe API version |
All options are validated at startup. Invalid or missing required options throw with an [xStripe] prefix.
Decorated Properties
| Property | Type | Description |
|----------|------|-------------|
| fastify.stripe | Stripe | The initialized Stripe SDK client — use it to call any Stripe API |
Custom Handlers
Override any default handler with your business logic. Handlers receive (event, fastify, stripe):
await fastify.register(xStripe, {
apiKey: process.env.STRIPE_API_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
handlers: {
'customer.subscription.created': async (event, fastify, stripe) => {
const subscription = event.data.object;
await db.users.update({
where: { stripeCustomerId: subscription.customer },
data: { subscriptionId: subscription.id, status: subscription.status },
});
},
'invoice.payment_failed': async (event, fastify, stripe) => {
const invoice = event.data.object;
const customer = await stripe.customers.retrieve(invoice.customer);
await sendEmail(customer.email, 'Payment Failed', 'Please update your card.');
},
},
});Default Event Handlers
All 23 built-in handlers log structured data via fastify.log. Override any of them via the handlers option.
Subscription Events
customer.subscription.created— logs subscriptionId, customerId, status, planIdcustomer.subscription.updated— logs subscriptionId, customerId, status, previous changescustomer.subscription.deleted— logs subscriptionId, customerId, canceledAtcustomer.subscription.trial_will_end— logs subscriptionId, customerId, trialEndcustomer.subscription.paused— logs subscriptionId, customerIdcustomer.subscription.resumed— logs subscriptionId, customerId
Invoice Events
invoice.created— logs invoiceId, customerId, amount, statusinvoice.finalized— logs invoiceId, customerId, amountinvoice.paid— logs invoiceId, customerId, subscriptionId, amountinvoice.payment_failed— logs (warn) invoiceId, customerId, amount, attemptCountinvoice.upcoming— logs customerId, subscriptionId, amount, periodEnd
Payment Events
payment_intent.succeeded— logs paymentIntentId, customerId, amount, currencypayment_intent.payment_failed— logs (warn) paymentIntentId, customerId, amount, lastPaymentError
Customer Events
customer.created— logs customerId, emailcustomer.updated— logs customerId, previous changescustomer.deleted— logs customerId
Payment Method Events
payment_method.attached— logs paymentMethodId, customerId, typepayment_method.detached— logs paymentMethodId, type
Checkout Events
checkout.session.completed— logs sessionId, customerId, subscriptionId, mode, paymentStatuscheckout.session.expired— logs sessionId
Charge Events
charge.succeeded— logs chargeId, customerId, amount, currency, paymentMethodcharge.failed— logs (error) chargeId, customerId, amount, failureCode, failureMessagecharge.refunded— logs chargeId, customerId, amountRefunded, refundCount
Helper Utilities
Import from @xenterprises/fastify-xstripe/helpers:
import { helpers } from '@xenterprises/fastify-xstripe';
helpers.formatAmount(2000, 'USD'); // "$20.00"
helpers.getPlanName(subscription); // "Pro Plan"
helpers.isActiveSubscription(subscription); // true
helpers.isInTrial(subscription); // true/false
helpers.getDaysUntilTrialEnd(subscription); // 3
helpers.isRenewal(event); // true/false
helpers.calculateMRR(subscription); // 2000 (cents)
helpers.getSubscriptionStatusText('active'); // "Active"
helpers.getEventDescription(event); // "Payment received"
helpers.getCustomerEmail(event, stripe); // "[email protected]"
helpers.isTestEvent(event); // true/false
helpers.getMetadata(event); // { key: "value" }
helpers.getPaymentMethodType(pm); // "Card"
helpers.getInvoiceLineItems(invoice); // [{ description, amount, ... }]
helpers.isSubscriptionInvoice(invoice); // true/false
helpers.getNextBillingDate(subscription); // Date
helpers.formatDate(1700000000); // "November 14, 2023"Environment Variables
| Name | Required | Description |
|------|----------|-------------|
| STRIPE_API_KEY | Yes | Stripe secret key (sk_test_... or sk_live_...) |
| STRIPE_WEBHOOK_SECRET | Yes | Webhook signing secret from Stripe Dashboard or CLI (whsec_...) |
Error Reference
All errors use the [xStripe] prefix for easy identification in logs.
| Error | When |
|-------|------|
| [xStripe] apiKey is required and must be a string | Missing or non-string apiKey option |
| [xStripe] webhookSecret is required and must be a string | Missing or non-string webhookSecret option |
| [xStripe] webhookPath must be a string | Non-string webhookPath option |
| [xStripe] handlers must be a plain object | handlers is not an object or is an array |
| [xStripe] apiVersion must be a string | Non-string apiVersion option |
| [xStripe] Missing stripe-signature header | Webhook request without signature header (HTTP 400) |
| [xStripe] Webhook signature verification failed: ... | Invalid webhook signature (HTTP 400) |
How It Works
- Registration — Validates all options, initializes the Stripe SDK client, and decorates it as
fastify.stripe. - Webhook Route — Registers a POST route at
webhookPaththat reads the raw body, verifies the Stripe signature usingstripe.webhooks.constructEvent(), and dispatches to the matching handler. - Handler Dispatch — User-provided handlers override defaults via object spread (
{ ...defaultHandlers, ...userHandlers }). If a handler throws, the error is logged but the webhook still returns HTTP 200 to prevent Stripe retries. - Stripe Client — The
fastify.stripedecorator gives full access to the Stripe SDK for any API call (customers, subscriptions, invoices, etc.).
Testing Webhooks Locally
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login and forward webhooks
stripe login
stripe listen --forward-to localhost:3000/stripe/webhook
# Trigger test events
stripe trigger customer.subscription.created
stripe trigger invoice.payment_failed
stripe trigger checkout.session.completedLicense
UNLICENSED
