npm.io
0.1.9 • Published 6d ago

@liveedevteam/stripe

Licence
MIT
Version
0.1.9
Deps
0
Size
89 kB
Vulns
0
Weekly
1.2K

@liveedevteam/stripe

Stripe integration module for Next.js App Router + Supabase — one-time payments, subscriptions, webhooks, and server actions.

Claude Code setup skill

This package ships a Claude Code skill that automates the full integration — migration, webhook route, env vars, and more. No copy-pasting required.

How to use it

After installing the package, open Claude Code in your project and type:

set up stripe

Claude detects what's already in place and walks through every step automatically.

What the skill does:

  1. Checks for missing dependencies and installs them
  2. Creates the Supabase migration (stripe_customers, subscriptions, orders, products, prices, webhook_events) and applies it
  3. Creates app/api/webhooks/stripe/route.ts with signature verification and idempotency
  4. Scaffolds a CheckoutButton component (and billing portal button if subscriptions are enabled)
  5. Adds all required env vars to .env.local (with placeholder values to fill in)
  6. Optionally configures Slack failure notifications
  7. Warns you to run the backfill script if existing users are present

The skill file (SKILL.md) ships inside the package — Claude Code reads it automatically from node_modules/@liveedevteam/stripe/SKILL.md. You get updated instructions with every version you install.

Example session
You: set up stripe

Claude: Running preflight checks...
  ✓ @liveedevteam/stripe installed
  ✓ stripe installed
  ✓ @supabase/ssr installed
  ✗ Supabase migration not found

  Creating migration...
  Writing supabase/migrations/20260101000000_create_stripe_tables.sql
  Running: supabase db push

  Creating webhook route...
  Writing app/api/webhooks/stripe/route.ts

  Adding env vars to .env.local...
  STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY

  Done. Next steps:
  1. Fill in the placeholder values in .env.local
  2. Run: stripe listen --forward-to localhost:3000/api/webhooks/stripe
  3. Test: stripe trigger checkout.session.completed

Requirements

  • Next.js 14+
  • Supabase (Auth + Postgres)
  • Node.js 18+

Installation

pnpm add @liveedevteam/stripe stripe @supabase/ssr

Environment variables

STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx
SUPABASE_SERVICE_ROLE_KEY=xxx

Database migration

supabase migration new create_stripe_tables

Paste into the generated file:

create table stripe_customers (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) not null,
  stripe_customer_id text unique not null,
  created_at timestamptz default now()
);

create table subscriptions (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) not null,
  stripe_subscription_id text unique not null,
  stripe_price_id text not null,
  status text not null,
  current_period_start timestamptz,
  current_period_end timestamptz,
  cancel_at_period_end boolean default false,
  cancel_at timestamptz,
  created_at timestamptz default now()
);

create table orders (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id), -- nullable for anonymous payments
  stripe_session_id text unique not null,
  amount integer not null,
  currency text not null,
  status text not null,
  created_at timestamptz default now()
);

create table webhook_events (
  id text primary key,
  type text not null,
  processed_at timestamptz default now()
);

-- Row Level Security
alter table stripe_customers enable row level security;
alter table subscriptions enable row level security;
alter table orders enable row level security;
alter table webhook_events enable row level security;

create policy "users_read_own_stripe_customer" on stripe_customers
  for select to authenticated using (auth.uid() = user_id);

create policy "users_read_own_subscriptions" on subscriptions
  for select to authenticated using (auth.uid() = user_id);

create policy "users_read_own_orders" on orders
  for select to authenticated using (auth.uid() = user_id);
supabase db push

Webhook route

// app/api/webhooks/stripe/route.ts
import { createWebhookHandler } from '@liveedevteam/stripe/webhooks'

export const POST = createWebhookHandler()

With optional Slack notifications on failure:

export const POST = createWebhookHandler({
  slack: {
    webhookUrl: process.env.SLACK_WEBHOOK_URL!,
    channel: '#payments-alerts',
  },
})

Server actions

Checkout button
'use client'
import { createCheckout } from '@liveedevteam/stripe/actions'

export const CheckoutButton = ({ priceId, mode }: {
  priceId: string
  mode: 'payment' | 'subscription'
}) => (
  <form action={() => createCheckout(priceId, mode)}>
    <button type="submit">Checkout</button>
  </form>
)
Billing portal
'use client'
import { getBillingPortal } from '@liveedevteam/stripe/actions'

export const BillingPortalButton = () => (
  <form action={getBillingPortal}>
    <button type="submit">Manage Subscription</button>
  </form>
)
Guard a page
import { requireActiveSubscription } from '@liveedevteam/stripe/actions'

export default async function DashboardPage() {
  await requireActiveSubscription() // redirects to /pricing if not active
  return <div>...</div>
}
Check subscription status
import { getSubscription } from '@liveedevteam/stripe/actions'

const subscription = await getSubscription() // null for anonymous or no subscription
if (subscription?.status === 'active' || subscription?.status === 'trialing') {
  // has access
}
Cancel a subscription
import { cancelSubscription } from '@liveedevteam/stripe/actions'

// Cancel at period end (default) — user keeps access until billing period ends
await cancelSubscription()

// Cancel immediately
await cancelSubscription(true)

The DB is updated automatically via customer.subscription.updated / customer.subscription.deleted webhooks.

Upgrade or downgrade
import { changeSubscription } from '@liveedevteam/stripe/actions'

await changeSubscription('price_new_plan_id')

// Control proration
await changeSubscription('price_new_plan_id', 'none')           // no proration
await changeSubscription('price_new_plan_id', 'always_invoice') // invoice immediately

The DB is updated automatically via customer.subscription.updated webhook.

TypeScript types

import type { Subscription, Database } from '@liveedevteam/stripe/types'

Subscription is derived directly from the Database schema so it stays in sync with your table.

Anonymous user support

Action Anonymous
createCheckout('payment') Allowed — order recorded with user_id = null
createCheckout('subscription') Throws Unauthorized
getBillingPortal() Throws Unauthorized
getSubscription() Returns null
requireActiveSubscription() Redirects to /pricing
cancelSubscription() Throws Unauthorized
changeSubscription(priceId) Throws Unauthorized

Local testing

stripe listen --forward-to localhost:3000/api/webhooks/stripe
stripe trigger checkout.session.completed

Testing

import { buildWebhookRequest, stripeFixtures } from '@liveedevteam/stripe/testing'

Build a signed webhook request to pass directly to your route handler in tests:

const req = buildWebhookRequest(
  'checkout.session.completed',
  stripeFixtures.checkoutSessionCompleted({ mode: 'subscription', userId: 'user-1' }),
  { secret: process.env.STRIPE_WEBHOOK_SECRET! }
)
const res = await POST(req)
expect(res.status).toBe(200)

Available fixtures: checkoutSessionCompleted, subscription, invoice. All are shaped for Stripe API version 2026-05-27.dahlia.

Existing users backfill

If users already had Stripe subscriptions before you installed this package, sync them into the stripe_customers table:

node node_modules/@liveedevteam/stripe/dist/scripts/backfill.js

The script looks up each user by email in Stripe and records the match — it does not create new Stripe customers. Always run against staging first.

License

MIT

Keywords