Backend
Stripe Integration
Payment processing with Stripe
Stripe Integration
TurboKit includes Stripe integration for payment processing, subscriptions, and customer portal.
Setup
Environment Variables
STRIPE_SECRET_KEY=sk_test_your_secret_key
STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secretWebhook Setup
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login to Stripe
stripe login
# Forward webhooks to your local server
stripe listen --forward-to localhost:4101/webhooks/stripeCopy the webhook secret (starts with whsec_) and add it to your .env file.
API Endpoints
Products
| Endpoint | Method | Description |
|---|---|---|
/stripe/products | GET | List all products |
/stripe/products/:id | GET | Get single product |
/stripe/products | POST | Create new product |
/stripe/products/:id | PUT | Update product |
/stripe/products/:id | DELETE | Delete product |
Checkout
| Endpoint | Method | Description |
|---|---|---|
/stripe/checkout-session | POST | Create checkout session |
/stripe/customer-portal | POST | Create customer portal |
Webhooks
| Endpoint | Method | Description |
|---|---|---|
/webhooks/stripe | POST | Handle Stripe webhook |
Checkout Flow
1. Create Checkout Session
// Frontend
const createCheckout = async (priceId: string) => {
const response = await fetch("/stripe/checkout-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
priceId,
successUrl: `${window.location.origin}/success`,
cancelUrl: `${window.location.origin}/pricing`,
}),
});
const data = await response.json();
if (data.success && data.data.url) {
window.location.href = data.data.url;
}
};2. Handle Webhook Events
export async function handleStripeWebhook(request: Request) {
const signature = request.headers.get("stripe-signature");
const event = stripe.webhooks.constructEvent(
await request.text(),
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
switch (event.type) {
case "checkout.session.completed":
await handleCheckoutComplete(event.data.object);
break;
case "customer.subscription.updated":
await handleSubscriptionUpdate(event.data.object);
break;
case "customer.subscription.deleted":
await handleSubscriptionCancel(event.data.object);
break;
}
}Customer Portal
Allow customers to manage their subscriptions:
const openCustomerPortal = async () => {
const response = await fetch("/stripe/customer-portal", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
returnUrl: window.location.href,
}),
});
const data = await response.json();
if (data.success && data.data.url) {
window.location.href = data.data.url;
}
};Test Cards
Use these test cards in development:
| Card Number | Description |
|---|---|
| 4242 4242 4242 4242 | Successful payment |
| 4000 0000 0000 9995 | Declined payment |
| 4000 0025 0000 3155 | 3D Secure required |
Use any future expiry date and any 3-digit CVC.
Best Practices
- Verify Webhooks: Always verify webhook signatures
- Idempotency: Handle duplicate webhook events gracefully
- Error Handling: Log all Stripe API errors
- Metadata: Use metadata to link Stripe objects to your database
- Customer Portal: Let users manage their own subscriptions
- Test Thoroughly: Test all payment flows in test mode first
Warning: Never expose your secret key (
sk_test_orsk_live_) in client-side code.