agentsclimarketplace

Stripe payments

Skill Ampli-Group/agentic-mobile-blueprint/.agents/skills/stripe-payments

The production-ready foundation for shipping mobile and web apps. Auth, deployment, monitoring, and CI/CD — already wired together.

Install
npx -y skills add Ampli-Group/agentic-mobile-blueprint --skill stripe-payments

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Integrate Stripe payments into the app — products, prices, checkout sessions, webhooks, customer portal, and subscription management. Use when setting up payments from scratch, adding a new product/plan, configuring webhooks, or debugging payment flows.

SKILL.md

8.7 KB, as published. Nobody here has run it

Stripe Payments

Stack

  • Stripe SDK (server-side): Supabase Edge Functions handle all Stripe API calls
  • Stripe.js (client-side): Frontend and mobile use Stripe's hosted checkout or @stripe/stripe-react-native
  • Webhooks: Stripe → Edge Function → Supabase DB (source of truth for subscription state)

Never put the Stripe secret key in frontend or mobile code. Only the publishable key goes client-side.


Setup

Install dependencies

# Frontend
cd frontend && npm install @stripe/stripe-js @stripe/react-stripe-js

# Mobile
cd mobile && npx expo install @stripe/stripe-react-native

Environment variables

# supabase/functions/.env.local
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# frontend/.env.local
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

# mobile/.env.local
EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

Get keys from dashboard.stripe.com/test/apikeys.


Products and Prices

Create products in the Stripe dashboard or via CLI. Products represent what you sell; prices represent how much and how often.

Stripe Dashboard (manual, recommended for first setup)

  1. dashboard.stripe.com/productsAdd product
  2. Fill in name, description, image
  3. Add pricing:
    • One-time: flat amount
    • Recurring: monthly/annual, amount
  4. Copy the Price ID (price_...) — you'll reference this in code

Via Stripe CLI (scriptable)

stripe products create --name="Pro Plan" --description="Full access"
stripe prices create \
  --product=prod_xxx \
  --unit-amount=999 \
  --currency=usd \
  --recurring[interval]=month

Edge Function: Create Checkout Session

// supabase/functions/create-checkout/index.ts
import Stripe from "npm:stripe@14";
import { createClient } from "npm:@supabase/supabase-js@2";

const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!);

Deno.serve(async (req) => {
  const { priceId, userId, returnUrl } = await req.json();

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",           // or "payment" for one-time
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${returnUrl}?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: returnUrl,
    metadata: { userId },           // passed back in webhook
    allow_promotion_codes: true,
    billing_address_collection: "auto",
  });

  return new Response(JSON.stringify({ url: session.url }));
});

Redirect the user to session.url — Stripe handles the entire payment flow.


Edge Function: Webhooks

Webhooks are how Stripe tells your app about payment events. This is the source of truth — never trust client-side "payment successful" redirects.

// supabase/functions/stripe-webhook/index.ts
import Stripe from "npm:stripe@14";
import { createClient } from "npm:@supabase/supabase-js@2";

const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!);
const webhookSecret = Deno.env.get("STRIPE_WEBHOOK_SECRET")!;

Deno.serve(async (req) => {
  const signature = req.headers.get("stripe-signature")!;
  const body = await req.text();

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
  } catch {
    return new Response("Invalid signature", { status: 400 });
  }

  const supabase = createClient(
    Deno.env.get("PUBLIC_SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  switch (event.type) {
    case "checkout.session.completed": {
      const session = event.data.object as Stripe.CheckoutSession;
      const userId = session.metadata?.userId;
      await supabase.from("subscriptions").upsert({
        user_id: userId,
        stripe_customer_id: session.customer as string,
        stripe_subscription_id: session.subscription as string,
        status: "active",
      });
      break;
    }
    case "customer.subscription.updated":
    case "customer.subscription.deleted": {
      const sub = event.data.object as Stripe.Subscription;
      await supabase.from("subscriptions")
        .update({ status: sub.status })
        .eq("stripe_subscription_id", sub.id);
      break;
    }
  }

  return new Response(JSON.stringify({ received: true }));
});

Key events to handle:

  • checkout.session.completed — user paid, activate subscription
  • customer.subscription.updated — plan changed, update status
  • customer.subscription.deleted — cancelled, downgrade access
  • invoice.payment_failed — payment failed, notify user

Webhook Setup

Local development

# Install Stripe CLI
brew install stripe/stripe-cli/stripe

# Login
stripe login

# Forward webhooks to local edge function
stripe listen --forward-to http://localhost:54321/functions/v1/stripe-webhook

# Copy the webhook signing secret printed by the CLI → STRIPE_WEBHOOK_SECRET in .env.local

Production

  1. dashboard.stripe.com/webhooksAdd endpoint
  2. Endpoint URL: https://YOUR_PROJECT.supabase.co/functions/v1/stripe-webhook
  3. Events to send: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.payment_failed
  4. Copy Signing secret (whsec_...) → add to Supabase secrets:
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...

Customer Portal

Let users manage their subscription (cancel, update payment method, change plan):

// supabase/functions/create-portal-session/index.ts
const session = await stripe.billingPortal.sessions.create({
  customer: stripeCustomerId,   // from your subscriptions table
  return_url: returnUrl,
});
return new Response(JSON.stringify({ url: session.url }));

Enable and configure the portal: dashboard.stripe.com/settings/billing/portal

  • Set which plans users can switch to
  • Enable/disable cancellation
  • Configure cancellation flow (survey, retention offers)

Database Schema

create table subscriptions (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users not null,
  stripe_customer_id text unique,
  stripe_subscription_id text unique,
  stripe_price_id text,
  status text not null default 'inactive',  -- active | past_due | canceled | trialing
  current_period_end timestamptz,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- RLS: users can read their own subscription
alter table subscriptions enable row level security;
create policy "users read own" on subscriptions for select using (auth.uid() = user_id);

Test Cards

CardScenario
4242 4242 4242 4242Successful payment
4000 0025 0000 3155Requires 3D Secure
4000 0000 0000 9995Declined
4000 0000 0000 0341Attaches successfully but payment always fails

Any future expiry, any CVC, any postal code.


Go Live Checklist

  • Switch from sk_test_ to sk_live_ keys in Supabase secrets
  • Switch publishable key to pk_live_ in frontend/mobile env
  • Create new webhook endpoint pointing to production (test and live webhooks are separate)
  • Verify products/prices exist in live mode (test mode data doesn't carry over)
  • Test a real payment with a real card
  • Enable Radar fraud rules: dashboard.stripe.com/radar/rules

Gotchas

Webhook signature verification fails — The webhook secret for local CLI (whsec_... from stripe listen) is different from the production dashboard secret. Use the right one per environment.

Missing SUPABASE_SERVICE_ROLE_KEY — Webhook handler must use the service role key to bypass RLS when writing subscription data. The publishable key won't work.

Duplicate webhook events — Stripe can deliver the same event more than once. Use upsert instead of insert, or check for existing records before writing.

Subscription not activating — Webhook is not reaching the function. Check Stripe dashboard → Webhooks → Recent deliveries for errors.

Apple/Google take 30% cut on mobile — If your app sells digital goods or subscriptions through the mobile app, Apple and Google require you to use their in-app purchase system (not Stripe). Stripe is fine for web checkout. This is the "App Store tax" — plan around it.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.