[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M719R68GG4F21PHJTY8WM2S5":3,"skill-same:01M719R68GG4F21PHJTY8WM2S5":85},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":30,"publishedAt":51,"linkOnly":52,"markdownContent":53,"markdownBody":54,"frontMatter":55,"refName":63,"commitSha":64,"skillDirectory":65,"forksCount":66,"repositoryHtmlUrl":67,"repositoryHomepageUrl":68,"ownerHtmlUrl":69,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":70,"install":71,"resources":84},"01M719R68GG4F21PHJTY8WM2S5","stripe-payments",null,"Add Stripe payments to a web app — Checkout Sessions, Payment Intents, subscriptions, webhooks, customer portal, and pricing pages. Covers the decision of which Stripe API to use, produces working integration code, and handles webhook verification. No MCP server needed — uses Stripe npm package directly. Triggers: 'add payments', 'stripe', 'checkout', 'subscription', 'payment form', 'pricing page', 'billing', 'accept payments', 'stripe webhook', 'customer portal'.","dahaliztupe-sketch\u002FKalmeron-Two","dahaliztupe-sketch","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F271085317?v=4","01M719765Q83P08JCQ1SWNQR0N","01M719766HQBGF44W90MPJKTD2",1,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fstripe-payments\u002FSKILL.md",[17,22,25],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":26,"categoryName":27,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],"stripe payments","stripe","payments","read","customer","verification","server","api","code","mcp server","Bash","MCP","only","npm package","decision","checkout","webhook","billing","which","app","2026-11-10 10:12:30",false,"---\nname: stripe-payments\ndescription: \"Add Stripe payments to a web app — Checkout Sessions, Payment Intents, subscriptions, webhooks, customer portal, and pricing pages. Covers the decision of which Stripe API to use, produces working integration code, and handles webhook verification. No MCP server needed — uses Stripe npm package directly. Triggers: 'add payments', 'stripe', 'checkout', 'subscription', 'payment form', 'pricing page', 'billing', 'accept payments', 'stripe webhook', 'customer portal'.\"\ncompatibility: claude-code-only\nallowed-tools:\n  - Read\n  - Write\n  - Edit\n  - Bash\n  - Glob\n  - Grep\n---\n\n# Stripe Payments\n\nAdd Stripe payments to a web app. Covers the common patterns — one-time payments, subscriptions, webhooks, customer portal — with working code. No MCP server needed.\n\n## Which Stripe API Do I Need?\n\n| You want to... | Use | Complexity |\n|----------------|-----|-----------|\n| Accept a one-time payment | Checkout Sessions | Low — Stripe hosts the payment page |\n| Embed a payment form in your UI | Payment Element + Payment Intents | Medium — you build the form, Stripe handles the card |\n| Recurring billing \u002F subscriptions | Checkout Sessions (subscription mode) | Low-Medium |\n| Save a card for later | Setup Intents | Low |\n| Marketplace \u002F platform payments | Stripe Connect | High |\n| Let customers manage billing | Customer Portal | Low — Stripe hosts it |\n\n**Default recommendation**: Start with Checkout Sessions. It's the fastest path to accepting money. You can always add embedded forms later.\n\n## Setup\n\n### Install\n\n```bash\nnpm install stripe @stripe\u002Fstripe-js\n```\n\n### API Keys\n\n```bash\n# Get keys from: https:\u002F\u002Fdashboard.stripe.com\u002Fapikeys\n# Test keys start with sk_test_ and pk_test_\n# Live keys start with sk_live_ and pk_live_\n\n# For Cloudflare Workers — store as secrets:\nnpx wrangler secret put STRIPE_SECRET_KEY\nnpx wrangler secret put STRIPE_WEBHOOK_SECRET\n\n# For local dev — .dev.vars:\nSTRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\nSTRIPE_PUBLISHABLE_KEY=pk_test_...\n```\n\n### Server-Side Client\n\n```typescript\nimport Stripe from 'stripe';\n\n\u002F\u002F Cloudflare Workers\nconst stripe = new Stripe(c.env.STRIPE_SECRET_KEY);\n\n\u002F\u002F Node.js\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n```\n\n## One-Time Payment (Checkout Sessions)\n\nThe fastest way to accept payment. Stripe hosts the entire checkout page.\n\n### Create a Checkout Session (Server)\n\n```typescript\napp.post('\u002Fapi\u002Fcheckout', async (c) => {\n  const { priceId, successUrl, cancelUrl } = await c.req.json();\n\n  const session = await stripe.checkout.sessions.create({\n    mode: 'payment',\n    line_items: [{ price: priceId, quantity: 1 }],\n    success_url: successUrl || `${new URL(c.req.url).origin}\u002Fsuccess?session_id={CHECKOUT_SESSION_ID}`,\n    cancel_url: cancelUrl || `${new URL(c.req.url).origin}\u002Fpricing`,\n  });\n\n  return c.json({ url: session.url });\n});\n```\n\n### Redirect to Checkout (Client)\n\n```typescript\nasync function handleCheckout(priceId: string) {\n  const res = await fetch('\u002Fapi\u002Fcheckout', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ priceId }),\n  });\n  const { url } = await res.json();\n  window.location.href = url;\n}\n```\n\n### Create Products and Prices\n\n```bash\n# Via Stripe CLI (recommended for setup)\nstripe products create --name=\"Pro Plan\" --description=\"Full access\"\nstripe prices create --product=prod_XXX --unit-amount=2900 --currency=aud --recurring[interval]=month\n\n# Or via Dashboard: https:\u002F\u002Fdashboard.stripe.com\u002Fproducts\n```\n\n**Hardcode price IDs** in your code (they don't change):\n```typescript\nconst PRICES = {\n  pro_monthly: 'price_1234567890',\n  pro_yearly: 'price_0987654321',\n} as const;\n```\n\n## Subscriptions\n\nSame as one-time but with `mode: 'subscription'`:\n\n```typescript\nconst session = await stripe.checkout.sessions.create({\n  mode: 'subscription',\n  line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],\n  success_url: `${origin}\u002Fdashboard?session_id={CHECKOUT_SESSION_ID}`,\n  cancel_url: `${origin}\u002Fpricing`,\n  \u002F\u002F Link to existing customer if known:\n  customer: customerId, \u002F\u002F or customer_email: 'user@example.com'\n});\n```\n\n### Check Subscription Status\n\n```typescript\nasync function hasActiveSubscription(customerId: string): Promise\u003Cboolean> {\n  const subs = await stripe.subscriptions.list({\n    customer: customerId,\n    status: 'active',\n    limit: 1,\n  });\n  return subs.data.length > 0;\n}\n```\n\n## Webhooks\n\nStripe sends events to your server when things happen (payment succeeded, subscription cancelled, etc.). **You must verify the webhook signature.**\n\n### Webhook Handler (Cloudflare Workers \u002F Hono)\n\n```typescript\napp.post('\u002Fapi\u002Fwebhooks\u002Fstripe', async (c) => {\n  const body = await c.req.text();\n  const sig = c.req.header('stripe-signature')!;\n\n  let event: Stripe.Event;\n  try {\n    \u002F\u002F Use constructEventAsync for Workers (no Node crypto)\n    event = await stripe.webhooks.constructEventAsync(\n      body,\n      sig,\n      c.env.STRIPE_WEBHOOK_SECRET\n    );\n  } catch (err) {\n    console.error('Webhook signature verification failed:', err);\n    return c.json({ error: 'Invalid signature' }, 400);\n  }\n\n  switch (event.type) {\n    case 'checkout.session.completed': {\n      const session = event.data.object as Stripe.Checkout.Session;\n      \u002F\u002F Fulfill the order — update database, send email, grant access\n      await handleCheckoutComplete(session);\n      break;\n    }\n    case 'customer.subscription.updated': {\n      const sub = event.data.object as Stripe.Subscription;\n      await handleSubscriptionChange(sub);\n      break;\n    }\n    case 'customer.subscription.deleted': {\n      const sub = event.data.object as Stripe.Subscription;\n      await handleSubscriptionCancelled(sub);\n      break;\n    }\n    case 'invoice.payment_failed': {\n      const invoice = event.data.object as Stripe.Invoice;\n      await handlePaymentFailed(invoice);\n      break;\n    }\n  }\n\n  return c.json({ received: true });\n});\n```\n\n### Register Webhook\n\n```bash\n# Local testing with Stripe CLI:\nstripe listen --forward-to http:\u002F\u002Flocalhost:8787\u002Fapi\u002Fwebhooks\u002Fstripe\n\n# Production — register via Dashboard:\n# https:\u002F\u002Fdashboard.stripe.com\u002Fwebhooks\n# URL: https:\u002F\u002Fyourapp.com\u002Fapi\u002Fwebhooks\u002Fstripe\n# Events: checkout.session.completed, customer.subscription.updated,\n#          customer.subscription.deleted, invoice.payment_failed\n```\n\n### Cloudflare Workers Gotcha\n\n`constructEvent` (synchronous) uses Node.js `crypto` which doesn't exist in Workers. Use `constructEventAsync` instead — it uses the Web Crypto API.\n\n## Customer Portal\n\nLet customers manage their own subscriptions (upgrade, downgrade, cancel, update payment method):\n\n```typescript\napp.post('\u002Fapi\u002Fbilling\u002Fportal', async (c) => {\n  const { customerId } = await c.req.json();\n\n  const session = await stripe.billingPortal.sessions.create({\n    customer: customerId,\n    return_url: `${new URL(c.req.url).origin}\u002Fdashboard`,\n  });\n\n  return c.json({ url: session.url });\n});\n```\n\nConfigure the portal in Dashboard: https:\u002F\u002Fdashboard.stripe.com\u002Fsettings\u002Fbilling\u002Fportal\n\n## Pricing Page Pattern\n\nGenerate a pricing page that reads from Stripe products:\n\n```typescript\n\u002F\u002F Server: fetch products and prices\napp.get('\u002Fapi\u002Fpricing', async (c) => {\n  const prices = await stripe.prices.list({\n    active: true,\n    expand: ['data.product'],\n    type: 'recurring',\n  });\n\n  return c.json(prices.data.map(price => ({\n    id: price.id,\n    name: (price.product as Stripe.Product).name,\n    description: (price.product as Stripe.Product).description,\n    amount: price.unit_amount,\n    currency: price.currency,\n    interval: price.recurring?.interval,\n  })));\n});\n```\n\nOr hardcode if you only have 2-3 plans — simpler and no API call on every page load.\n\n## Stripe CLI (Local Development)\n\n```bash\n# Install\nbrew install stripe\u002Fstripe-cli\u002Fstripe\n\n# Login\nstripe login\n\n# Listen for webhooks locally\nstripe listen --forward-to http:\u002F\u002Flocalhost:8787\u002Fapi\u002Fwebhooks\u002Fstripe\n\n# Trigger test events\nstripe trigger checkout.session.completed\nstripe trigger customer.subscription.created\nstripe trigger invoice.payment_failed\n```\n\n## Common Patterns\n\n### Link Stripe Customer to Your User\n\n```typescript\n\u002F\u002F On first checkout, create or find customer:\nconst session = await stripe.checkout.sessions.create({\n  customer_email: user.email,  \u002F\u002F Creates new customer if none exists\n  \u002F\u002F OR\n  customer: user.stripeCustomerId,  \u002F\u002F Use existing\n  metadata: { userId: user.id },  \u002F\u002F Link back to your user\n  \u002F\u002F ...\n});\n\n\u002F\u002F In webhook, save the customer ID:\ncase 'checkout.session.completed': {\n  const session = event.data.object;\n  await db.update(users)\n    .set({ stripeCustomerId: session.customer as string })\n    .where(eq(users.id, session.metadata.userId));\n}\n```\n\n### Free Trial\n\n```typescript\nconst session = await stripe.checkout.sessions.create({\n  mode: 'subscription',\n  line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],\n  subscription_data: {\n    trial_period_days: 14,\n  },\n  \u002F\u002F ...\n});\n```\n\n### Australian Dollars\n\n```typescript\n\u002F\u002F Set currency when creating prices\nconst price = await stripe.prices.create({\n  product: 'prod_XXX',\n  unit_amount: 2900,  \u002F\u002F $29.00 in cents\n  currency: 'aud',\n  recurring: { interval: 'month' },\n});\n```\n\n## Gotchas\n\n| Gotcha | Fix |\n|--------|-----|\n| `constructEvent` fails on Workers | Use `constructEventAsync` (Web Crypto API) |\n| Webhook fires but handler not called | Check the endpoint URL matches exactly (trailing slash matters) |\n| Test mode payments not appearing | Make sure you're using `sk_test_` key, not `sk_live_` |\n| Price amounts are in cents | `2900` = $29.00. Always divide by 100 for display |\n| Customer email doesn't match user | Use `customer` (existing ID) not `customer_email` for returning users |\n| Subscription status stale | Don't cache — check via API or trust webhook events |\n| Webhook retries | Stripe retries failed webhooks for up to 3 days. Return 200 quickly. |\n| CORS on checkout redirect | Checkout URL is on stripe.com — use `window.location.href`, not fetch |\n","# Stripe Payments\n\nAdd Stripe payments to a web app. Covers the common patterns — one-time payments, subscriptions, webhooks, customer portal — with working code. No MCP server needed.\n\n## Which Stripe API Do I Need?\n\n| You want to... | Use | Complexity |\n|----------------|-----|-----------|\n| Accept a one-time payment | Checkout Sessions | Low — Stripe hosts the payment page |\n| Embed a payment form in your UI | Payment Element + Payment Intents | Medium — you build the form, Stripe handles the card |\n| Recurring billing \u002F subscriptions | Checkout Sessions (subscription mode) | Low-Medium |\n| Save a card for later | Setup Intents | Low |\n| Marketplace \u002F platform payments | Stripe Connect | High |\n| Let customers manage billing | Customer Portal | Low — Stripe hosts it |\n\n**Default recommendation**: Start with Checkout Sessions. It's the fastest path to accepting money. You can always add embedded forms later.\n\n## Setup\n\n### Install\n\n```bash\nnpm install stripe @stripe\u002Fstripe-js\n```\n\n### API Keys\n\n```bash\n# Get keys from: https:\u002F\u002Fdashboard.stripe.com\u002Fapikeys\n# Test keys start with sk_test_ and pk_test_\n# Live keys start with sk_live_ and pk_live_\n\n# For Cloudflare Workers — store as secrets:\nnpx wrangler secret put STRIPE_SECRET_KEY\nnpx wrangler secret put STRIPE_WEBHOOK_SECRET\n\n# For local dev — .dev.vars:\nSTRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\nSTRIPE_PUBLISHABLE_KEY=pk_test_...\n```\n\n### Server-Side Client\n\n```typescript\nimport Stripe from 'stripe';\n\n\u002F\u002F Cloudflare Workers\nconst stripe = new Stripe(c.env.STRIPE_SECRET_KEY);\n\n\u002F\u002F Node.js\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n```\n\n## One-Time Payment (Checkout Sessions)\n\nThe fastest way to accept payment. Stripe hosts the entire checkout page.\n\n### Create a Checkout Session (Server)\n\n```typescript\napp.post('\u002Fapi\u002Fcheckout', async (c) => {\n  const { priceId, successUrl, cancelUrl } = await c.req.json();\n\n  const session = await stripe.checkout.sessions.create({\n    mode: 'payment',\n    line_items: [{ price: priceId, quantity: 1 }],\n    success_url: successUrl || `${new URL(c.req.url).origin}\u002Fsuccess?session_id={CHECKOUT_SESSION_ID}`,\n    cancel_url: cancelUrl || `${new URL(c.req.url).origin}\u002Fpricing`,\n  });\n\n  return c.json({ url: session.url });\n});\n```\n\n### Redirect to Checkout (Client)\n\n```typescript\nasync function handleCheckout(priceId: string) {\n  const res = await fetch('\u002Fapi\u002Fcheckout', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ priceId }),\n  });\n  const { url } = await res.json();\n  window.location.href = url;\n}\n```\n\n### Create Products and Prices\n\n```bash\n# Via Stripe CLI (recommended for setup)\nstripe products create --name=\"Pro Plan\" --description=\"Full access\"\nstripe prices create --product=prod_XXX --unit-amount=2900 --currency=aud --recurring[interval]=month\n\n# Or via Dashboard: https:\u002F\u002Fdashboard.stripe.com\u002Fproducts\n```\n\n**Hardcode price IDs** in your code (they don't change):\n```typescript\nconst PRICES = {\n  pro_monthly: 'price_1234567890',\n  pro_yearly: 'price_0987654321',\n} as const;\n```\n\n## Subscriptions\n\nSame as one-time but with `mode: 'subscription'`:\n\n```typescript\nconst session = await stripe.checkout.sessions.create({\n  mode: 'subscription',\n  line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],\n  success_url: `${origin}\u002Fdashboard?session_id={CHECKOUT_SESSION_ID}`,\n  cancel_url: `${origin}\u002Fpricing`,\n  \u002F\u002F Link to existing customer if known:\n  customer: customerId, \u002F\u002F or customer_email: 'user@example.com'\n});\n```\n\n### Check Subscription Status\n\n```typescript\nasync function hasActiveSubscription(customerId: string): Promise\u003Cboolean> {\n  const subs = await stripe.subscriptions.list({\n    customer: customerId,\n    status: 'active',\n    limit: 1,\n  });\n  return subs.data.length > 0;\n}\n```\n\n## Webhooks\n\nStripe sends events to your server when things happen (payment succeeded, subscription cancelled, etc.). **You must verify the webhook signature.**\n\n### Webhook Handler (Cloudflare Workers \u002F Hono)\n\n```typescript\napp.post('\u002Fapi\u002Fwebhooks\u002Fstripe', async (c) => {\n  const body = await c.req.text();\n  const sig = c.req.header('stripe-signature')!;\n\n  let event: Stripe.Event;\n  try {\n    \u002F\u002F Use constructEventAsync for Workers (no Node crypto)\n    event = await stripe.webhooks.constructEventAsync(\n      body,\n      sig,\n      c.env.STRIPE_WEBHOOK_SECRET\n    );\n  } catch (err) {\n    console.error('Webhook signature verification failed:', err);\n    return c.json({ error: 'Invalid signature' }, 400);\n  }\n\n  switch (event.type) {\n    case 'checkout.session.completed': {\n      const session = event.data.object as Stripe.Checkout.Session;\n      \u002F\u002F Fulfill the order — update database, send email, grant access\n      await handleCheckoutComplete(session);\n      break;\n    }\n    case 'customer.subscription.updated': {\n      const sub = event.data.object as Stripe.Subscription;\n      await handleSubscriptionChange(sub);\n      break;\n    }\n    case 'customer.subscription.deleted': {\n      const sub = event.data.object as Stripe.Subscription;\n      await handleSubscriptionCancelled(sub);\n      break;\n    }\n    case 'invoice.payment_failed': {\n      const invoice = event.data.object as Stripe.Invoice;\n      await handlePaymentFailed(invoice);\n      break;\n    }\n  }\n\n  return c.json({ received: true });\n});\n```\n\n### Register Webhook\n\n```bash\n# Local testing with Stripe CLI:\nstripe listen --forward-to http:\u002F\u002Flocalhost:8787\u002Fapi\u002Fwebhooks\u002Fstripe\n\n# Production — register via Dashboard:\n# https:\u002F\u002Fdashboard.stripe.com\u002Fwebhooks\n# URL: https:\u002F\u002Fyourapp.com\u002Fapi\u002Fwebhooks\u002Fstripe\n# Events: checkout.session.completed, customer.subscription.updated,\n#          customer.subscription.deleted, invoice.payment_failed\n```\n\n### Cloudflare Workers Gotcha\n\n`constructEvent` (synchronous) uses Node.js `crypto` which doesn't exist in Workers. Use `constructEventAsync` instead — it uses the Web Crypto API.\n\n## Customer Portal\n\nLet customers manage their own subscriptions (upgrade, downgrade, cancel, update payment method):\n\n```typescript\napp.post('\u002Fapi\u002Fbilling\u002Fportal', async (c) => {\n  const { customerId } = await c.req.json();\n\n  const session = await stripe.billingPortal.sessions.create({\n    customer: customerId,\n    return_url: `${new URL(c.req.url).origin}\u002Fdashboard`,\n  });\n\n  return c.json({ url: session.url });\n});\n```\n\nConfigure the portal in Dashboard: https:\u002F\u002Fdashboard.stripe.com\u002Fsettings\u002Fbilling\u002Fportal\n\n## Pricing Page Pattern\n\nGenerate a pricing page that reads from Stripe products:\n\n```typescript\n\u002F\u002F Server: fetch products and prices\napp.get('\u002Fapi\u002Fpricing', async (c) => {\n  const prices = await stripe.prices.list({\n    active: true,\n    expand: ['data.product'],\n    type: 'recurring',\n  });\n\n  return c.json(prices.data.map(price => ({\n    id: price.id,\n    name: (price.product as Stripe.Product).name,\n    description: (price.product as Stripe.Product).description,\n    amount: price.unit_amount,\n    currency: price.currency,\n    interval: price.recurring?.interval,\n  })));\n});\n```\n\nOr hardcode if you only have 2-3 plans — simpler and no API call on every page load.\n\n## Stripe CLI (Local Development)\n\n```bash\n# Install\nbrew install stripe\u002Fstripe-cli\u002Fstripe\n\n# Login\nstripe login\n\n# Listen for webhooks locally\nstripe listen --forward-to http:\u002F\u002Flocalhost:8787\u002Fapi\u002Fwebhooks\u002Fstripe\n\n# Trigger test events\nstripe trigger checkout.session.completed\nstripe trigger customer.subscription.created\nstripe trigger invoice.payment_failed\n```\n\n## Common Patterns\n\n### Link Stripe Customer to Your User\n\n```typescript\n\u002F\u002F On first checkout, create or find customer:\nconst session = await stripe.checkout.sessions.create({\n  customer_email: user.email,  \u002F\u002F Creates new customer if none exists\n  \u002F\u002F OR\n  customer: user.stripeCustomerId,  \u002F\u002F Use existing\n  metadata: { userId: user.id },  \u002F\u002F Link back to your user\n  \u002F\u002F ...\n});\n\n\u002F\u002F In webhook, save the customer ID:\ncase 'checkout.session.completed': {\n  const session = event.data.object;\n  await db.update(users)\n    .set({ stripeCustomerId: session.customer as string })\n    .where(eq(users.id, session.metadata.userId));\n}\n```\n\n### Free Trial\n\n```typescript\nconst session = await stripe.checkout.sessions.create({\n  mode: 'subscription',\n  line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],\n  subscription_data: {\n    trial_period_days: 14,\n  },\n  \u002F\u002F ...\n});\n```\n\n### Australian Dollars\n\n```typescript\n\u002F\u002F Set currency when creating prices\nconst price = await stripe.prices.create({\n  product: 'prod_XXX',\n  unit_amount: 2900,  \u002F\u002F $29.00 in cents\n  currency: 'aud',\n  recurring: { interval: 'month' },\n});\n```\n\n## Gotchas\n\n| Gotcha | Fix |\n|--------|-----|\n| `constructEvent` fails on Workers | Use `constructEventAsync` (Web Crypto API) |\n| Webhook fires but handler not called | Check the endpoint URL matches exactly (trailing slash matters) |\n| Test mode payments not appearing | Make sure you're using `sk_test_` key, not `sk_live_` |\n| Price amounts are in cents | `2900` = $29.00. Always divide by 100 for display |\n| Customer email doesn't match user | Use `customer` (existing ID) not `customer_email` for returning users |\n| Subscription status stale | Don't cache — check via API or trust webhook events |\n| Webhook retries | Stripe retries failed webhooks for up to 3 days. Return 200 quickly. |\n| CORS on checkout redirect | Checkout URL is on stripe.com — use `window.location.href`, not fetch |\n",{"name":5,"description":7,"allowed-tools":56,"compatibility":62},[57,58,59,41,60,61],"Read","Write","Edit","Glob","Grep","claude-code-only","main","13d9afadbb6a87d6209fe4bd6ca2bbe4553119ac",".agents\u002Fskills\u002Fstripe-payments",0,"https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two","https:\u002F\u002Fkalmeron-two.vercel.app","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch","2026-10-05 12:24:53",{"source":8,"skillName":5,"commands":72,"prompt":82,"zipAvailable":83},[73,76,79],{"tool":74,"command":75},"npx","npx skills add dahaliztupe-sketch\u002FKalmeron-Two --skill stripe-payments",{"tool":77,"command":78},"bunx","bunx skills add dahaliztupe-sketch\u002FKalmeron-Two --skill stripe-payments",{"tool":80,"command":81},"pnpm","pnpm dlx skills add dahaliztupe-sketch\u002FKalmeron-Two --skill stripe-payments","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fstripe-payments\u002FSKILL.md",true,[],[86,99,123,141,170,201],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":90,"sourceHtmlUrl":91,"categories":92,"keywords":94,"publishedAt":51},"01M719SWYD9RGS97HJ69PXWY62","add-new-agent","خطوات تفصيليّة لإضافة وكيل ذكاء اصطناعي جديد إلى منصّة Kalmeron من الفكرة إلى الإنتاج","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002FSKILL.md",[93],{"categoryNo":23,"categoryName":24,"taxonomyType":20,"taxonomyTypeDesc":21},[95,96,97,98],"add","add new agent","new","new agent",{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":114,"publishedAt":51},"01M719SSKNBA7T5NS7V6DP0PX4","writing-skills","Use when creating new skills, editing existing skills, or verifying skills work before deployment","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fwriting-skills\u002FSKILL.md",[105,108,111],{"categoryNo":106,"categoryName":107,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":109,"categoryName":110,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":112,"categoryName":113,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000036","内容创作者",[115,116,97,117,118,119,120,121,122],"writing skills","writing","when","work","editing","deployment","use","Existing",{"skillNo":124,"name":125,"nameZh":6,"description":126,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":127,"categories":128,"keywords":131,"publishedAt":51},"01M719SRFDNKA3044QD3FYZ05R","writing-plans","Use when you have a spec or requirements for a multi-step task, before touching code","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fwriting-plans\u002FSKILL.md",[129,130],{"categoryNo":109,"categoryName":110,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":112,"categoryName":113,"taxonomyType":28,"taxonomyTypeDesc":29},[132,116,133,39,134,135,117,121,136,137,138,139,140],"writing plans","plans","requirements","you","for","task","multi","spec","multi-step task",{"skillNo":142,"name":143,"nameZh":6,"description":144,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":145,"categories":146,"keywords":153,"publishedAt":51},"01M719SPN5FM1JYJSR9DGJQPZ0","web-design-guidelines","Review UI code for Web Interface Guidelines compliance. Use when asked to \"review my UI\", \"check accessibility\", \"audit design\", \"review UX\", or \"check my site against best practices\".","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fweb-design-guidelines\u002FSKILL.md",[147,150],{"categoryNo":148,"categoryName":149,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":151,"categoryName":152,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000032","设计师",[154,155,156,157,158,159,160,161,162,163,164,39,136,165,166,167,121,117,168,169],"web design","web design guidelines","web","design","guidelines","review","accessibility","ux","audit","compliance","check","vercel","interface","web interface guidelines","best practices","file",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":174,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":181,"publishedAt":51},"01M719SK11T05QYVPTWWYMYY5B","vitest","Set up Vitest testing in any project — detects project type (Cloudflare Workers, React, Node), generates vitest.config.ts, test setup, utilities, and sample tests. Also covers mocking patterns, coverage config, workspace setup, and Jest migration. Trigger with 'set up vitest', 'add tests', 'configure vitest', 'migrate from jest', 'vitest config', 'add unit tests', or 'testing setup'.","MIT","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvitest\u002FSKILL.md",[177,178],{"categoryNo":106,"categoryName":107,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":179,"categoryName":180,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000009","前端开发",[172,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200],"cloudflare","cloudflare workers","config","node","sample","react","jest","migrate","configure","Utilities","up","testing","workspace setup","project","unit","SET","coverage","test setup","patterns",{"skillNo":202,"name":203,"nameZh":6,"description":204,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":205,"categories":206,"keywords":207,"publishedAt":51},"01M719SGV9R1N88QWAB7Y925HD","verification-loops","Use when about to claim work is complete, fixed, or passing, before committing or creating PRs. Requires running verification commands and confirming output before making any success claims. Evidence before assertions always.","https:\u002F\u002Fgithub.com\u002Fdahaliztupe-sketch\u002FKalmeron-Two\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverification-loops\u002FSKILL.md",[],[208,36,209,210,121,211,212,213,214,215,216,217,118,218,219,117],"loops","verification loops","claim","assertions","prs","complete","evidence","running","commands","about","claims","output"]