소스 정보
- 저장소
- artofrawr/claude-control
- 최근 소스 활동
- 2026년 4월 10일 00:31
- 감지된 SKILL.md 언어
- 영어
- 스타
- 1
- 포크
- 0
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/artofrawr/claude-control --skill stripe명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | stripe |
| description | Stripe Checkout, subscriptions, webhooks, customer portal |
| disable-model-invocation | false |
Load with: base.md + [framework].md
For integrating Stripe payments into web applications - one-time payments, subscriptions, and checkout flows.
Sources: Stripe Checkout | Payment Element Best Practices | Building Solid Stripe Integrations | Subscriptions
# .env
STRIPE_SECRET_KEY=sk_test_xxx # Server-side only
STRIPE_PUBLISHABLE_KEY=pk_test_xxx # Client-side safe
STRIPE_WEBHOOK_SECRET=whsec_xxx # For webhook verification
# Production
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_PUBLISHABLE_KEY=pk_live_xxx
# Node.js
npm install stripe @stripe/stripe-js
# Python
pip install stripe
| Method | Best For | Complexity |
|---|---|---|
| Checkout (Hosted) | Quick setup, Stripe-hosted page | Low |
| Checkout (Embedded) | Custom site, embedded form | Low |
| Payment Element | Full customization, complex flows | Medium |
| Custom Form | Complete control (rare) | High |
Recommendation: Start with Checkout, migrate to Payment Element if needed.
// app/api/checkout/route.ts (Next.js App Router)
import Stripe from "stripe";
import { NextResponse } from "next/server";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: Request) {
const { priceId, mode = "payment" } = await request.json();
try {
const session = await stripe.checkout.sessions.create({
mode: mode as "payment" | "subscription",
payment_method_types: ["card"],
line_items: [
{
price: priceId,
quantity: 1,
},
],
success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/canceled`,
// Optional: Link to existing customer
// customer: customerId,
// Optional: Collect shipping
: {
: ,
: ,
},
});
.({ : session., : session. });
} (error) {
.(, error);
.({ : }, { : });
}
}
# app/api/checkout.py
import stripe
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
import os
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
router = APIRouter()
class CheckoutRequest(BaseModel):
price_id: str
mode: str = "payment" # or "subscription"
@router.post("/api/checkout")
async def create_checkout_session(request: CheckoutRequest):
try:
session = stripe.checkout.Session.create(
mode=request.mode,
payment_method_types=["card"],
line_items=[{
"price": request.price_id,
"quantity": 1,
}],
success_url=f"{os.environ['APP_URL']}/success?session_id={{CHECKOUT_SESSION_ID}}",
cancel_url=f"{os.environ['APP_URL']}/canceled",
metadata={
"user_id": "user_123",
},
)
return {"session_id": session.id, "url": session.url}
except stripe.error.StripeError as e:
raise HTTPException(status_code=400, detail=str(e))
// components/CheckoutButton.tsx
"use client";
import { loadStripe } from "@stripe/stripe-js";
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
export function CheckoutButton({ priceId }: { priceId: string }) {
const handleCheckout = async () => {
const response = await fetch("/api/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ priceId }),
});
const { url } = await response.json();
// Redirect to Stripe Checkout
window.location.href = url;
};
return (
<button onClick={handleCheckout}>
Subscribe Now
</button>
);
}
For keeping users on your site:
// components/EmbeddedCheckout.tsx
"use client";
import { useEffect, useState } from "react";
import { loadStripe } from "@stripe/stripe-js";
import {
EmbeddedCheckoutProvider,
EmbeddedCheckout,
} from "@stripe/react-stripe-js";
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
export function EmbeddedCheckoutForm({ priceId }: { priceId: string }) {
const [clientSecret, setClientSecret] = useState("");
useEffect(() => {
fetch("/api/checkout/embedded", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ priceId }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, [priceId]);
if (!clientSecret) return ;
(
);
}
Server endpoint for embedded:
// app/api/checkout/embedded/route.ts
export async function POST(request: Request) {
const { priceId } = await request.json();
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: priceId, quantity: 1 }],
ui_mode: "embedded",
return_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
});
return NextResponse.json({ clientSecret: session.client_secret });
}
Never trust client-side data. Always verify payments via webhooks.
// app/api/webhooks/stripe/route.ts
import Stripe from "stripe";
import { headers } from "next/headers";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(request: Request) {
const body = await request.text();
const signature = headers().get("stripe-signature")!;
let event: Stripe.Event;
// Verify webhook signature
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (err) {
console.error("Webhook signature verification failed");
return new Response("Invalid signature", { status: 400 });
}
// Handle events
switch (event.) {
: {
session = event.. ..;
(session);
;
}
:
: {
subscription = event.. .;
(subscription);
;
}
: {
subscription = event.. .;
(subscription);
;
}
: {
invoice = event.. .;
(invoice);
;
}
:
.();
}
(, { : });
}
() {
userId = session.?.;
customerId = session. ;
subscriptionId = session. ;
db..({
: { : userId },
: {
: customerId,
: subscriptionId,
: ,
},
});
}
# app/api/webhooks.py
import stripe
from fastapi import APIRouter, Request, HTTPException
router = APIRouter()
@router.post("/api/webhooks/stripe")
async def stripe_webhook(request: Request):
payload = await request.body()
sig_header = request.headers.get("stripe-signature")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, os.environ["STRIPE_WEBHOOK_SECRET"]
)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid payload")
except stripe.error.SignatureVerificationError:
raise HTTPException(status_code=400, detail="Invalid signature")
# Handle events
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
await handle_checkout_complete(session)
elif event["type"] == "customer.subscription.deleted":
subscription = event["data"]["object"]
await handle_subscription_canceled(subscription)
return {"status": "success"}
| Event | When | Action |
|---|---|---|
checkout.session.completed | Payment successful | Provision access |
customer.subscription.created | New subscription | Store subscription ID |
customer.subscription.updated | Plan change | Update plan in DB |
customer.subscription.deleted | Canceled | Revoke access |
invoice.payment_failed | Payment failed | Notify user, retry |
invoice.paid | Renewal successful | Extend access |
price_xxx)// One-time product
const product = await stripe.products.create({
name: "Pro Plan",
description: "Full access to all features",
});
const price = await stripe.prices.create({
product: product.id,
unit_amount: 2999, // $29.99 in cents
currency: "usd",
});
// Subscription product
const subscriptionPrice = await stripe.prices.create({
product: product.id,
unit_amount: 999, // $9.99/month
currency: "usd",
recurring: {
interval: "month",
},
});
Let users manage their subscriptions:
// app/api/portal/route.ts
export async function POST(request: Request) {
const { customerId } = await request.json();
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.NEXT_PUBLIC_URL}/settings`,
});
return NextResponse.json({ url: session.url });
}
Configure portal at: https://dashboard.stripe.com/settings/billing/portal
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: priceId, quantity: 1 }],
subscription_data: {
trial_period_days: 14,
// Cancel if no payment method after trial
trial_settings: {
end_behavior: { missing_payment_method: "cancel" },
},
},
success_url: successUrl,
cancel_url: cancelUrl,
});
// lib/subscription.ts
export async function getSubscriptionStatus(customerId: string) {
const subscriptions = await stripe.subscriptions.list({
customer: customerId,
status: "all",
limit: 1,
});
if (subscriptions.data.length === 0) {
return { status: "none", plan: null };
}
const subscription = subscriptions.data[0];
return {
status: subscription.status,
plan: subscription.items.data[0].price.id,
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
cancelAtPeriodEnd: subscription.cancel_at_period_end,
};
}
| Card Number | Scenario |
|---|---|
4242424242424242 | Success |
4000000000000002 | Declined |
4000002500003155 | Requires 3D Secure |
4000000000009995 | Insufficient funds |
# Install CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to local server
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Trigger test events
stripe trigger checkout.session.completed
stripe trigger customer.subscription.deleted
project/
├── app/
│ ├── api/
│ │ ├── checkout/
│ │ │ └── route.ts # Create checkout session
│ │ ├── portal/
│ │ │ └── route.ts # Customer portal
│ │ └── webhooks/
│ │ └── stripe/
│ │ └── route.ts # Webhook handler
│ ├── pricing/
│ │ └── page.tsx # Pricing page
│ ├── success/
│ │ └── page.tsx # Post-checkout success
│ └── settings/
│ └── page.tsx # Manage subscription
├── lib/
│ ├── stripe.ts # Stripe client
│ └── subscription.ts # Subscription helpers
└── .env.local
STRIPE_SECRET_KEYconst processedEvents = new Set<string>(); // Use Redis in production
export async function POST(request: Request) {
// ... verify signature ...
// Skip duplicate events
if (processedEvents.has(event.id)) {
return new Response("Already processed", { status: 200 });
}
processedEvents.add(event.id);
// Process event...
}
// Always use cents (smallest currency unit)
const priceInCents = 2999; // $29.99
// Helper functions
const toCents = (dollars: number) => Math.round(dollars * 100);
const toDollars = (cents: number) => cents / 100;
// Display
const displayPrice = (cents: number) =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(toDollars(cents));
// app/pricing/page.tsx
const plans = [
{
name: "Starter",
price: "$9/mo",
priceId: "price_starter_monthly",
features: ["Feature 1", "Feature 2"],
},
{
name: "Pro",
price: "$29/mo",
priceId: "price_pro_monthly",
features: ["Everything in Starter", "Feature 3", "Feature 4"],
popular: true,
},
];
export default function PricingPage() {
return (
<div className="grid md:grid-cols-2 gap-8">
{plans.map((plan) => (
<div key={plan.name} className={plan.popular ? "border-blue-500" : ""}>
<h3>{plan.name}</h3>
<p>{plan.price}</p>
<ul>
{plan.features.map((f) => <li key={f}>{f})}
))}
);
}
// middleware.ts
import { getSubscriptionStatus } from "@/lib/subscription";
export async function middleware(request: NextRequest) {
const session = await getSession();
if (request.nextUrl.pathname.startsWith("/pro")) {
const { status } = await getSubscriptionStatus(session.stripeCustomerId);
if (status !== "active" && status !== "trialing") {
return NextResponse.redirect(new URL("/pricing", request.url));
}
}
}
invoice.payment_failed# Install
npm install stripe @stripe/stripe-js @stripe/react-stripe-js
# Stripe CLI
stripe login
stripe listen --forward-to localhost:3000/api/webhooks/stripe
stripe trigger checkout.session.completed
# Test mode prefix
sk_test_xxx # Secret key
pk_test_xxx # Publishable key
# Live mode prefix
sk_live_xxx
pk_live_xxx
| Endpoint | Purpose |
|---|---|
POST /api/checkout | Create checkout session |
POST /api/portal | Customer billing portal |
POST /api/webhooks/stripe | Handle Stripe events |
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx