Skip to main content Skills Marktplatz Entdecken und erkunden Sie KI-Skills, die von der Community erstellt wurden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Prompt kopierenPrompt-Details anzeigen Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
npx skills add https://github.com/thiagofernandes1987-create/APEX --skill stripe-integration-expertDer Befehl bleibt in einer Zeile. Scrollen Sie horizontal, um ihn vor dem Kopieren vollständig zu prüfen.
Sie bevorzugen eine lokale Kopie? Laden Sie die Dateien herunter, die SkillsMP derzeit vorliegen.
ZIP herunterladen Herunterladen... Mehr aus diesem Repository Token-aware reasoning workflow with real tools: picks an operating mode to control cost, runs a structured pipeline (decompose → validate → verify → snapshot), and gives Claude Program-of-Thought, RK4/Euler, a code gate, and a safe skill router. Use when: multi-step or high-stakes tasks, real math, precise computation, audits, or the user mentions APEX, PoT, pipeline, or scientific mode.
agent-framework-azure-ai-py **v00.33.0**: Ingested from antigravity-awesome-skills community repo
run multiple local CLI agents in parallel (separate tmux sessions)
Verwandte Berufe SOC
Basierend auf der SOC-Berufsklassifikation
name stripe-integration-expert description Adding subscription billing to any web app executor LLM_BEHAVIOR skill_id engineering.cs-engineering-team.stripe-integration-expert status ADOPTED security {"level":"standard","pii":false,"approval_required":false} anchors ["engineering"] tier 2 input_schema [{"name":"code_or_task","type":"string","description":"Code snippet, script, or task description to process","required":true}] output_schema [{"name":"result","type":"string","description":"Primary output from stripe integration expert"}]
Stripe Integration Expert
Tier: POWERFUL
Category: Engineering Team
Domain: Payments / Billing Infrastructure
Overview
Implement production-grade Stripe integrations: subscriptions with trials and proration, one-time payments, usage-based billing, checkout sessions, idempotent webhook handlers, customer portal, and invoicing. Covers Next.js, Express, and Django patterns.
Core Capabilities
Subscription lifecycle management (create, upgrade, downgrade, cancel, pause)
Trial handling and conversion tracking
Proration calculation and credit application
Usage-based billing with metered pricing
Idempotent webhook handlers with signature verification
Customer portal integration
Invoice generation and PDF access
Full Stripe CLI local testing setup
When to Use
Adding subscription billing to any web app
Implementing plan upgrades/downgrades with proration
Building usage-based or seat-based billing
Debugging webhook delivery failures
Migrating from one billing model to another
Subscription Lifecycle State Machine
FREE_TRIAL ──paid──► ACTIVE ──cancel──► CANCEL_PENDING ──period_end──► CANCELED
│ │ │
│ downgrade reactivate
│ ▼ │
│ DOWNGRADING ──period_end──► ACTIVE (lower plan) │
│ │
└──trial_end without payment──► PAST_DUE ──payment_failed 3x──► CANCELED
│
payment_success
│
▼
ACTIVE
DB subscription status values:
trialing | active | past_due | canceled | cancel_pending | paused | unpaid
Stripe Client Setup
import Stripe from "stripe"
export const stripe = new (process. . !, {
: ,
: ,
: {
: ,
: ,
},
})
= {
: {
: process. . !,
: process. . !,
: [ , ],
},
: {
: process. . !,
: process. . !,
: [ , ],
},
}
Stripe
env
STRIPE_SECRET_KEY
apiVersion
"2024-04-10"
typescript
true
appInfo
name
"myapp"
version
"1.0.0"
export
const
PLANS
starter
monthly
env
STRIPE_STARTER_MONTHLY_PRICE_ID
yearly
env
STRIPE_STARTER_YEARLY_PRICE_ID
features
"5 projects"
"10k events"
pro
monthly
env
STRIPE_PRO_MONTHLY_PRICE_ID
yearly
env
STRIPE_PRO_YEARLY_PRICE_ID
features
"Unlimited projects"
"1M events"
as
const
Checkout Session (Next.js App Router)
import { NextResponse } from "next/server"
import { stripe } from "@/lib/stripe"
import { getAuthUser } from "@/lib/auth"
import { db } from "@/lib/db"
export async function POST (req : Request ) {
const user = await getAuthUser ()
if (!user) return NextResponse .json ({ error : "Unauthorized" }, { status : 401 })
const { priceId, interval = "monthly" } = await req.json ()
let stripeCustomerId = user.stripeCustomerId
if (!stripeCustomerId) {
const customer = await stripe.customers .create ({
email : user.email ,
name : "username-undefined"
metadata : { userId : user.id },
})
stripeCustomerId = customer.id
await db.user .update ({ where : { id : user.id }, data : { stripeCustomerId } })
}
const session = await stripe.checkout .sessions .create ({
customer : stripeCustomerId,
mode : "subscription" ,
payment_method_types : ["card" ],
line_items : [{ price : priceId, quantity : 1 }],
allow_promotion_codes : true ,
subscription_data : {
trial_period_days : user.hasHadTrial ? undefined : 14 ,
metadata : { userId : user.id },
},
success_url : `${process.env.NEXT_PUBLIC_APP_URL} /dashboard?session_id={CHECKOUT_SESSION_ID}` ,
cancel_url : `${process.env.NEXT_PUBLIC_APP_URL} /pricing` ,
metadata : { userId : user.id },
})
return NextResponse .json ({ url : session.url })
}
Subscription Upgrade/Downgrade
export async function changeSubscriptionPlan (
subscriptionId : string ,
newPriceId : string ,
immediate = false
) {
const subscription = await stripe.subscriptions .retrieve (subscriptionId)
const currentItem = subscription.items .data [0 ]
if (immediate) {
return stripe.subscriptions .update (subscriptionId, {
items : [{ id : currentItem.id , price : newPriceId }],
proration_behavior : "always_invoice" ,
billing_cycle_anchor : "unchanged" ,
})
} else {
return stripe.subscriptions .update (subscriptionId, {
items : [{ id : currentItem.id , price : newPriceId }],
proration_behavior : "none" ,
billing_cycle_anchor : "unchanged" ,
})
}
}
export async function previewProration (subscriptionId : string , newPriceId : string ) {
const subscription = await stripe.subscriptions .retrieve (subscriptionId)
const prorationDate = Math .floor (Date .now () / 1000 )
const invoice = await stripe.invoices .retrieveUpcoming ({
customer : subscription.customer as string ,
subscription : subscriptionId,
subscription_items : [{ id : subscription.items .data [0 ].id , price : newPriceId }],
subscription_proration_date : prorationDate,
})
return {
amountDue : invoice.amount_due ,
prorationDate,
lineItems : invoice.lines .data ,
}
}
Complete Webhook Handler (Idempotent)
import { NextResponse } from "next/server"
import { headers } from "next/headers"
import { stripe } from "@/lib/stripe"
import { db } from "@/lib/db"
import Stripe from "stripe"
async function hasProcessedEvent (eventId : string ): Promise <boolean > {
const existing = await db.stripeEvent .findUnique ({ where : { id : eventId } })
return !!existing
}
async function markEventProcessed (eventId : string , type : string ) {
await db.stripeEvent .create ({ data : { id : eventId, type , processedAt : new Date () } })
}
export async function POST (req : Request ) {
const body = await req.text ()
const signature = headers ().get ("stripe-signature" )!
let event : Stripe .Event
try {
event = stripe.webhooks .constructEvent (body, signature, process.env .STRIPE_WEBHOOK_SECRET !)
} catch (err) {
console .error ("Webhook signature verification failed:" , err)
return NextResponse .json ({ error : "Invalid signature" }, { status : 400 })
}
if (await hasProcessedEvent (event.id )) {
return NextResponse .json ({ received : true , skipped : true })
}
try {
switch (event.type ) {
case "checkout.session.completed" :
await handleCheckoutCompleted (event.data .object as Stripe .Checkout .Session )
break
case "customer.subscription.created" :
case "customer.subscription.updated" :
await handleSubscriptionUpdated (event.data .object as Stripe .Subscription )
break
case "customer.subscription.deleted" :
await handleSubscriptionDeleted (event.data .object as Stripe .Subscription )
break
case "invoice.payment_succeeded" :
await handleInvoicePaymentSucceeded (event.data .object as Stripe .Invoice )
break
case "invoice.payment_failed" :
await handleInvoicePaymentFailed (event.data .object as Stripe .Invoice )
break
default :
console .log (`Unhandled event type: ${event.type } ` )
}
await markEventProcessed (event.id , event.type )
return NextResponse .json ({ received : true })
} catch (err) {
console .error (`Error processing webhook ${event.type } :` , err)
return NextResponse .json ({ error : "Processing failed" }, { status : 500 })
}
}
async function handleCheckoutCompleted (session : Stripe .Checkout .Session ) {
if (session.mode !== "subscription" ) return
const userId = session.metadata ?.userId
if (!userId) throw new Error ("No userId in checkout session metadata" )
const subscription = await stripe.subscriptions .retrieve (session.subscription as string )
await db.user .update ({
where : { id : userId },
data : {
stripeCustomerId : session.customer as string ,
stripeSubscriptionId : subscription.id ,
stripePriceId : subscription.items .data [0 ].price .id ,
stripeCurrentPeriodEnd : new Date (subscription.current_period_end * 1000 ),
subscriptionStatus : subscription.status ,
hasHadTrial : true ,
},
})
}
async function handleSubscriptionUpdated (subscription : Stripe .Subscription ) {
const user = await db.user .findUnique ({
where : { stripeSubscriptionId : subscription.id },
})
if (!user) {
const customer = await db.user .findUnique ({
where : { stripeCustomerId : subscription.customer as string },
})
if (!customer) throw new Error (`No user found for subscription ${subscription.id} ` )
}
await db.user .update ({
where : { stripeSubscriptionId : subscription.id },
data : {
stripePriceId : subscription.items .data [0 ].price .id ,
stripeCurrentPeriodEnd : new Date (subscription.current_period_end * 1000 ),
subscriptionStatus : subscription.status ,
cancelAtPeriodEnd : subscription.cancel_at_period_end ,
},
})
}
async function handleSubscriptionDeleted (subscription : Stripe .Subscription ) {
await db.user .update ({
where : { stripeSubscriptionId : subscription.id },
data : {
stripeSubscriptionId : null ,
stripePriceId : null ,
stripeCurrentPeriodEnd : null ,
subscriptionStatus : "canceled" ,
},
})
}
async function handleInvoicePaymentFailed (invoice : Stripe .Invoice ) {
if (!invoice.subscription ) return
const attemptCount = invoice.attempt_count
await db.user .update ({
where : { stripeSubscriptionId : invoice.subscription as string },
data : { subscriptionStatus : "past_due" },
})
if (attemptCount >= 3 ) {
await sendDunningEmail (invoice.customer_email !, "final" )
} else {
await sendDunningEmail (invoice.customer_email !, "retry" )
}
}
async function handleInvoicePaymentSucceeded (invoice : Stripe .Invoice ) {
if (!invoice.subscription ) return
await db.user .update ({
where : { stripeSubscriptionId : invoice.subscription as string },
data : {
subscriptionStatus : "active" ,
stripeCurrentPeriodEnd : new Date (invoice.period_end * 1000 ),
},
})
}
Usage-Based Billing
export async function reportUsage (subscriptionItemId : string , quantity : number ) {
await stripe.subscriptionItems .createUsageRecord (subscriptionItemId, {
quantity,
timestamp : Math .floor (Date .now () / 1000 ),
action : "increment" ,
})
}
export async function trackApiCall (userId : string ) {
const user = await db.user .findUnique ({ where : { id : userId } })
if (user?.stripeSubscriptionId ) {
const subscription = await stripe.subscriptions .retrieve (user.stripeSubscriptionId )
const meteredItem = subscription.items .data .find (
(item ) => item.price .recurring ?.usage_type === "metered"
)
if (meteredItem) {
await reportUsage (meteredItem.id , 1 )
}
}
}
Customer Portal
import { NextResponse } from "next/server"
import { stripe } from "@/lib/stripe"
import { getAuthUser } from "@/lib/auth"
export async function POST ( ) {
const user = await getAuthUser ()
if (!user?.stripeCustomerId ) {
return NextResponse .json ({ error : "No billing account" }, { status : 400 })
}
const portalSession = await stripe.billingPortal .sessions .create ({
customer : user.stripeCustomerId ,
return_url : `${process.env.NEXT_PUBLIC_APP_URL} /settings/billing` ,
})
return NextResponse .json ({ url : portalSession.url })
}
Testing with Stripe CLI
brew install stripe/stripe-cli/stripe
stripe login
stripe listen --forward-to localhost:3000/api/webhooks/stripe
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger invoice.payment_failed
stripe trigger customer.subscription.updated \
--override subscription:customer=cus_xxx
stripe events list --limit 10
Feature Gating Helper
export function isSubscriptionActive (user : { subscriptionStatus: string | null , stripeCurrentPeriodEnd: Date | null } ) {
if (!user.subscriptionStatus ) return false
if (user.subscriptionStatus === "active" || user.subscriptionStatus === "trialing" ) return true
if (user.subscriptionStatus === "past_due" && user.stripeCurrentPeriodEnd ) {
return user.stripeCurrentPeriodEnd > new Date ()
}
return false
}
export async function requireActiveSubscription ( ) {
const user = await getAuthUser ()
if (!isSubscriptionActive (user)) {
redirect ("/billing?reason=subscription_required" )
}
}
Common Pitfalls
Webhook delivery order not guaranteed — always re-fetch from Stripe API, never trust event data alone for DB updates
Double-processing webhooks — Stripe retries on 500; always use idempotency table
Trial conversion tracking — store hasHadTrial: true in DB to prevent trial abuse
Proration surprises — always preview proration before upgrade; show user the amount before confirming
Customer portal not configured — must enable features in Stripe dashboard under Billing → Customer portal settings
Missing metadata on checkout — always pass userId in metadata; can't link subscription to user without it
Why This Skill Exists Implement — Stripe Integration Expert
What If Fails If this skill fails to produce the expected output: (1) verify input completeness, (2) retry with more specific context, (3) fall back to the parent workflow without this skill.