stripe-integration
<!-- Loaded by nextjs-ecommerce-engineer when task involves Stripe, payment processing, Payment Intents, webhooks, or checkout -->
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
<!-- Loaded by nextjs-ecommerce-engineer when task involves Stripe, payment processing, Payment Intents, webhooks, or checkout -->
Agent definition
stripe-integration.mdStripe Integration Reference
<!-- Loaded by nextjs-ecommerce-engineer when task involves Stripe, payment processing, Payment Intents, webhooks, or checkout -->
Stripe's cardinal rule: the client never sees raw card data. The browser collects card details directly into Stripe's systems via Elements; your server only ever handles payment tokens and intents.
Stripe Elements Setup
**When to use:** Building a custom checkout form embedded in your UI (vs. redirecting to Stripe-hosted Checkout).
// app/checkout/page.tsx — server component wraps client checkout
import { stripe } from '@/lib/stripe'
import { CheckoutForm } from './CheckoutForm'
import { getCart } from '@/lib/cart'
export default async function CheckoutPage() {
const cart = await getCart()
if (!cart || cart.items.length === 0) redirect('/cart')
// Create Payment Intent on the server before rendering
const total = cart.items.reduce(
(sum, item) => sum + item.product.price * item.quantity, 0
)
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(total * 100), // Stripe uses cents
currency: 'usd',
metadata: { cartId: cart.id },
automatic_payment_methods: { enabled: true },
})
return (
<CheckoutForm
clientSecret={paymentIntent.client_secret!}
total={total}
/>
)
}// app/checkout/CheckoutForm.tsx
'use client'
import { useState } from 'react'
import { loadStripe } from '@stripe/stripe-js'
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js'
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!)
interface CheckoutFormProps {
clientSecret: string
total: number
}
export function CheckoutForm({ clientSecret, total }: CheckoutFormProps) {
return (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutFormInner total={total} />
</Elements>
)
}
function CheckoutFormInner({ total }: { total: number }) {
const stripe = useStripe()
const elements = useElements()
const [processing, setProcessing] = useState(false)
const [error, setError] = useState<string | null>(null)
async function handleSubmit(e: React.FormEvent): Promise<void> {
e.preventDefault()
if (!stripe || !elements) return
setProcessing(true)
setError(null)
const { error: stripeError } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/checkout/success`,
},
})
if (stripeError) {
setError(stripeError.message ?? 'Payment failed')
setProcessing(false)
}
// On success, Stripe redirects to return_url
}
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
{error && <p role="alert" className="text-red-700 text-sm mt-2">{error}</p>}
<button type="submit" disabled={processing || !stripe}>
{processing ? 'Processing...' : `Pay ${new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(total)}`}
</button>
</form>
)
}---
Payment Intents
**When to use:** One-time payments. Create the intent server-side, confirm client-side via Elements.
// lib/stripe.ts — Stripe singleton
import Stripe from 'stripe'
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-06-20',
typescript: true,
})// app/api/payment-intent/route.ts — alternative: create via API route
import { NextResponse } from 'next/server'
import { stripe } from '@/lib/stripe'
import { z } from 'zod'
const CreateIntentSchema = z.object({
cartId: z.string().cuid(),
})
export async function POST(req: Request) {
const body = await req.json()
const parsed = CreateIntentSchema.safeParse(body)
if (!parsed.success) {
return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
}
// Fetch cart and compute total server-side — never trust client-provided amounts
const cart = await db.cart.findUniqueOrThrow({
where: { id: parsed.data.cartId },
include: { items: { include: { product: true } } },
})
const amount = cart.items.reduce(
(sum, item) => sum + item.product.price * item.quantity, 0
)
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount * 100),
currency: 'usd',
metadata: { cartId: cart.id },
automatic_payment_methods: { enabled: true },
})
return NextResponse.json({ clientSecret: paymentIntent.client_secret })
}---
Checkout Sessions (Stripe-Hosted)
**When to use:** When you want Stripe to handle the entire checkout UI — faster to implement, less control.
// app/api/checkout-session/route.ts
import { stripe } from '@/lib/stripe'
import { getCart } from '@/lib/cart'
export async function POST() {
const cart = await getCart()
if (!cart) return new Response('No cart', { status: 400 })
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: cart.items.map(item => ({
price_data: {
currency: 'usd',
product_data: {
name: item.product.name,
images: [item.product.imageUrl],
},
unit_amount: Math.round(item.product.price * 100),
},
quantity: item.quantity,
})),
success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/cart`,
metadata: { cartId: cart.id },
})
return Response.json({ url: session.url })
}---
Webhook Signature Verification
**When to use:** Every Stripe webhook handler, without exception. Unverified webhooks can be spoofed.
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers'
import { stripe } from '@/lib/stripe'
// CRITICAL: disable Next.js body parsing — Stripe needs the raw body string
export const config = { api: { bodyParser: falseRead more
Stripe Integration Reference
<!-- Loaded by nextjs-ecommerce-engineer when task involves Stripe, payment processing, Payment Intents, webhooks, or checkout -->
Stripe's cardinal rule: the client never sees raw card data. The browser collects card details directly into Stripe's systems via Elements; your server only ever handles payment tokens and intents.
Stripe Elements Setup
**When to use:** Building a custom checkout form embedded in your UI (vs. redirecting to Stripe-hosted Checkout).
// app/checkout/page.tsx — server component wraps client checkout
import { stripe } from '@/lib/stripe'
import { CheckoutForm } from './CheckoutForm'
import { getCart } from '@/lib/cart'
export default async function CheckoutPage() {
const cart = await getCart()
if (!cart || cart.items.length === 0) redirect('/cart')
// Create Payment Intent on the server before rendering
const total = cart.items.reduce(
(sum, item) => sum + item.product.price * item.quantity, 0
)
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(total * 100), // Stripe uses cents
currency: 'usd',
metadata: { cartId: cart.id },
automatic_payment_methods: { enabled: true },
})
return (
<CheckoutForm
clientSecret={paymentIntent.client_secret!}
total={total}
/>
)
}// app/checkout/CheckoutForm.tsx
'use client'
import { useState } from 'react'
import { loadStripe } from '@stripe/stripe-js'
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js'
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!)
interface CheckoutFormProps {
clientSecret: string
total: number
}
export function CheckoutForm({ clientSecret, total }: CheckoutFormProps) {
return (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutFormInner total={total} />
</Elements>
)
}
function CheckoutFormInner({ total }: { total: number }) {
const stripe = useStripe()
const elements = useElements()
const [processing, setProcessing] = useState(false)
const [error, setError] = useState<string | null>(null)
async function handleSubmit(e: React.FormEvent): Promise<void> {
e.preventDefault()
if (!stripe || !elements) return
setProcessing(true)
setError(null)
const { error: stripeError } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/checkout/success`,
},
})
if (stripeError) {
setError(stripeError.message ?? 'Payment failed')
setProcessing(false)
}
// On success, Stripe redirects to return_url
}
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
{error && <p role="alert" className="text-red-700 text-sm mt-2">{error}</p>}
<button type="submit" disabled={processing || !stripe}>
{processing ? 'Processing...' : `Pay ${new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(total)}`}
</button>
</form>
)
}---
Payment Intents
**When to use:** One-time payments. Create the intent server-side, confirm client-side via Elements.
// lib/stripe.ts — Stripe singleton
import Stripe from 'stripe'
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-06-20',
typescript: true,
})// app/api/payment-intent/route.ts — alternative: create via API route
import { NextResponse } from 'next/server'
import { stripe } from '@/lib/stripe'
import { z } from 'zod'
const CreateIntentSchema = z.object({
cartId: z.string().cuid(),
})
export async function POST(req: Request) {
const body = await req.json()
const parsed = CreateIntentSchema.safeParse(body)
if (!parsed.success) {
return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
}
// Fetch cart and compute total server-side — never trust client-provided amounts
const cart = await db.cart.findUniqueOrThrow({
where: { id: parsed.data.cartId },
include: { items: { include: { product: true } } },
})
const amount = cart.items.reduce(
(sum, item) => sum + item.product.price * item.quantity, 0
)
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount * 100),
currency: 'usd',
metadata: { cartId: cart.id },
automatic_payment_methods: { enabled: true },
})
return NextResponse.json({ clientSecret: paymentIntent.client_secret })
}---
Checkout Sessions (Stripe-Hosted)
**When to use:** When you want Stripe to handle the entire checkout UI — faster to implement, less control.
// app/api/checkout-session/route.ts
import { stripe } from '@/lib/stripe'
import { getCart } from '@/lib/cart'
export async function POST() {
const cart = await getCart()
if (!cart) return new Response('No cart', { status: 400 })
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: cart.items.map(item => ({
price_data: {
currency: 'usd',
product_data: {
name: item.product.name,
images: [item.product.imageUrl],
},
unit_amount: Math.round(item.product.price * 100),
},
quantity: item.quantity,
})),
success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/cart`,
metadata: { cartId: cart.id },
})
return Response.json({ url: session.url })
}---
Webhook Signature Verification
**When to use:** Every Stripe webhook handler, without exception. Unverified webhooks can be spoofed.
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers'
import { stripe } from '@/lib/stripe'
// CRITICAL: disable Next.js body parsing — Stripe needs the raw body string
export const config = { api: { bodyParser: falseEssays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

