skills/payments-checkout/subscription-billing/SKILL.md
Sell recurring subscriptions with automated billing, dunning emails for failed payments, plan upgrade/downgrade prorations, and self-serve cancellation
npx skillsauth add finsilabs/awesome-ecommerce-skills subscription-billingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Subscription billing lets customers pay automatically on a recurring schedule — weekly, monthly, or annually. Shopify, WooCommerce, and BigCommerce each have dedicated subscription apps and plugins that handle the full lifecycle: initial checkout, recurring billing, failed payment recovery (dunning), plan changes, pausing, and cancellation. For headless storefronts, Stripe Subscriptions provides the same functionality via API with no need to build billing logic from scratch.
| Platform | Recommended Tool | Notes | |----------|-----------------|-------| | Shopify | Recharge or Seal Subscriptions or Bold Subscriptions (Shopify App Store) | Shopify has a native Subscriptions API; Recharge is the most widely used third-party app; Seal is a newer lower-cost option | | Shopify (simple) | Shopify Subscriptions (built-in, free) | For basic subscription needs — launched 2024; handles simple recurring orders | | WooCommerce | WooCommerce Subscriptions ($199/year, official WooCommerce extension) | The official and most complete WooCommerce subscription solution; handles all billing, dunning, and management | | BigCommerce | Recurly (via BigCommerce App Marketplace) or Rebilly | BigCommerce does not have native subscriptions; third-party apps handle it | | Custom / Headless | Stripe Subscriptions | Stripe Subscriptions handles the complete recurring billing lifecycle with webhooks |
For basic subscription products:
Shopify Subscriptions handles: recurring billing, payment failure emails, and a customer portal for managing subscriptions. For advanced dunning, analytics, and subscriber portals, use Recharge or Seal Subscriptions.
Recharge dunning defaults:
Customer portal for WooCommerce: Customers manage subscriptions from their My Account → Subscriptions page. WooCommerce Subscriptions provides pause, cancel, and payment method update functionality there by default.
Use Stripe Subscriptions for the full recurring billing lifecycle:
Create a subscription plan:
In the Stripe Dashboard → Products, create a product with a recurring price (e.g., "Monthly Plan — $29/month"). Copy the Price ID (starts with price_).
Subscribe a customer:
// Create or retrieve the Stripe customer
const customer = await stripe.customers.create({
email: customerEmail,
payment_method: paymentMethodId,
invoice_settings: { default_payment_method: paymentMethodId },
});
// Create the subscription
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: 'price_monthly_pro_29' }], // Price ID from Stripe Dashboard
payment_behavior: 'default_incomplete', // Create subscription, then confirm payment
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.payment_intent'],
trial_period_days: 14, // Optional trial
metadata: { customer_id: customerId },
});
// Return client_secret for 3DS confirmation if needed
const clientSecret = subscription.latest_invoice?.payment_intent?.client_secret;
res.json({ subscriptionId: subscription.id, clientSecret });
Sync subscription status via webhooks:
// Webhook handler for subscription events
async function handleSubscriptionWebhook(event) {
switch (event.type) {
case 'customer.subscription.updated':
await syncSubscriptionStatus(event.data.object);
break;
case 'customer.subscription.deleted':
// Subscription cancelled — revoke access
await db.subscriptions.update({
where: { stripeSubscriptionId: event.data.object.id },
data: { status: 'cancelled', cancelledAt: new Date() },
});
break;
case 'invoice.payment_succeeded':
await recordSuccessfulBillingCycle(event.data.object);
break;
case 'invoice.payment_failed':
// Send dunning email — Stripe retries automatically per your settings
await sendDunningEmail(event.data.object);
break;
case 'customer.subscription.trial_will_end':
// Send trial ending email 3 days before trial ends
await sendTrialEndingEmail(event.data.object);
break;
}
}
Configure dunning in Stripe Dashboard: Go to Stripe Dashboard → Billing → Settings → Smart Retries. Enable Smart Retries — Stripe's ML-based retry timing outperforms fixed schedules. Also configure automatic subscription cancellation after all retries fail under Stripe Dashboard → Billing → Settings → Automatic collection.
Plan changes with prorations:
// Upgrade or downgrade a subscription
const subscription = await stripe.subscriptions.retrieve(stripeSubscriptionId);
await stripe.subscriptions.update(stripeSubscriptionId, {
items: [{ id: subscription.items.data[0].id, price: newPriceId }],
proration_behavior: 'create_prorations', // Charge/credit the difference immediately
});
// Stripe creates a prorated invoice automatically
Cancellation with end-of-period access:
// Cancel at period end — customer retains access until the next billing date
await stripe.subscriptions.update(stripeSubscriptionId, {
cancel_at_period_end: true,
});
// Immediate cancellation
await stripe.subscriptions.cancel(stripeSubscriptionId);
| Problem | Solution |
|---------|----------|
| Subscription shows as active after cancellation | Sync status via webhooks — customer.subscription.deleted must trigger a database update; never poll Stripe for status on page load |
| WooCommerce Subscriptions not retrying failed payments | Verify the payment gateway supports tokenized recurring payments; Stripe via the WooCommerce Stripe plugin supports this; basic PayPal Standard does not |
| Trial converts to paid without customer knowing | Send the trial_will_end email 3 days before (Stripe fires the webhook automatically; Recharge and WooCommerce Subscriptions do this by default) |
| Duplicate dunning emails on retried webhooks | Check that the invoice attempt count matches the last dunning email you sent before sending another; use the invoice ID + attempt count as the idempotency key |
| Proration charges customer unexpectedly on upgrade | Show the upcoming invoice preview before applying plan changes (Stripe: use stripe.invoices.retrieveUpcoming()); show the proration amount to the customer first |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion