# /specweave-payments:stripe-setup Complete Stripe integration setup guide with production-ready code templates, security best practices, and testing workflows. You are a payment integration expert who implements secure, PCI-compliant Stripe payment systems. ## Your Task Set up complete Stripe payment integration with checkout flows, webhook handling, subscription billing, and customer management. ### 1. Environment Setup **Install Dependencies**: ```bash # Node.js npm install stripe @stripe/stripe-js dotenv # Python pip install stripe python-dotenv # Ruby gem install stripe dotenv # PHP composer require stripe/stripe-php vlucas/phpdotenv ``` **Environment Variables**: ```bash # .env (NEVER commit this file!) # Get keys from https://dashboard.stripe.com/apikeys # Test mode (development) STRIPE_PUBLISHABLE_KEY=pk_test_51... STRIPE_SECRET_KEY=sk_test_51... STRIPE_WEBHOOK_SECRET=whsec_... # Live mode (production) # STRIPE_PUBLISHABLE_KEY=pk_live_51... # STRIPE_SECRET_KEY=sk_live_51... # STRIPE_WEBHOOK_SECRET=whsec_... # App configuration STRIPE_SUCCESS_URL=https://yourdomain.com/success STRIPE_CANCEL_URL=https://yourdomain.com/cancel STRIPE_CURRENCY=usd ``` ### 2. Backend Setup (Node.js/Express) **Stripe Client Initialization**: ```typescript // src/config/stripe.ts import Stripe from 'stripe'; import dotenv from 'dotenv'; dotenv.config(); if (!process.env.STRIPE_SECRET_KEY) { throw new Error('STRIPE_SECRET_KEY is not set in environment variables'); } export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2023-10-16', typescript: true, maxNetworkRetries: 2, timeout: 10000, // 10 seconds }); export const STRIPE_CONFIG = { publishableKey: process.env.STRIPE_PUBLISHABLE_KEY!, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!, successUrl: process.env.STRIPE_SUCCESS_URL || 'http://localhost:3000/success', cancelUrl: process.env.STRIPE_CANCEL_URL || 'http://localhost:3000/cancel', currency: process.env.STRIPE_CURRENCY || 'usd', }; ``` **Payment Service**: ```typescript // src/services/payment.service.ts import { stripe } from '../config/stripe'; import type Stripe from 'stripe'; export class PaymentService { /** * Create a one-time payment checkout session */ async createCheckoutSession(params: { amount: number; currency?: string; customerId?: string; metadata?: Record; }): Promise { try { const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price_data: { currency: params.currency || 'usd', product_data: { name: 'Payment', description: 'One-time payment', }, unit_amount: params.amount, // Amount in cents }, quantity: 1, }, ], mode: 'payment', success_url: `${process.env.STRIPE_SUCCESS_URL}?session_id={CHECKOUT_SESSION_ID}`, cancel_url: process.env.STRIPE_CANCEL_URL, customer: params.customerId, metadata: params.metadata, // Enable automatic tax calculation (optional) automatic_tax: { enabled: false }, // Customer email collection customer_email: params.customerId ? undefined : '', }); return session; } catch (error) { console.error('Failed to create checkout session:', error); throw new Error('Payment session creation failed'); } } /** * Create a payment intent for custom checkout UI */ async createPaymentIntent(params: { amount: number; currency?: string; customerId?: string; paymentMethodTypes?: string[]; metadata?: Record; }): Promise { try { const paymentIntent = await stripe.paymentIntents.create({ amount: params.amount, currency: params.currency || 'usd', customer: params.customerId, payment_method_types: params.paymentMethodTypes || ['card'], metadata: params.metadata, // Automatic payment methods (enables more payment methods) automatic_payment_methods: { enabled: true, allow_redirects: 'never', // or 'always' for redirect-based methods }, }); return paymentIntent; } catch (error) { console.error('Failed to create payment intent:', error); throw new Error('Payment intent creation failed'); } } /** * Retrieve a payment intent */ async getPaymentIntent(paymentIntentId: string): Promise { try { return await stripe.paymentIntents.retrieve(paymentIntentId); } catch (error) { console.error('Failed to retrieve payment intent:', error); throw new Error('Payment intent retrieval failed'); } } /** * Confirm a payment intent (server-side confirmation) */ async confirmPaymentIntent( paymentIntentId: string, paymentMethodId: string ): Promise { try { return await stripe.paymentIntents.confirm(paymentIntentId, { payment_method: paymentMethodId, }); } catch (error) { console.error('Failed to confirm payment intent:', error); throw new Error('Payment confirmation failed'); } } /** * Create or update a customer */ async createCustomer(params: { email: string; name?: string; phone?: string; metadata?: Record; paymentMethodId?: string; }): Promise { try { const customer = await stripe.customers.create({ email: params.email, name: params.name, phone: params.phone, metadata: params.metadata, payment_method: params.paymentMethodId, invoice_settings: params.paymentMethodId ? { default_payment_method: params.paymentMethodId, } : undefined, }); return customer; } catch (error) { console.error('Failed to create customer:', error); throw new Error('Customer creation failed'); } } /** * Attach a payment method to a customer */ async attachPaymentMethod( paymentMethodId: string, customerId: string, setAsDefault = true ): Promise { try { // Attach payment method const paymentMethod = await stripe.paymentMethods.attach(paymentMethodId, { customer: customerId, }); // Set as default if requested if (setAsDefault) { await stripe.customers.update(customerId, { invoice_settings: { default_payment_method: paymentMethodId, }, }); } return paymentMethod; } catch (error) { console.error('Failed to attach payment method:', error); throw new Error('Payment method attachment failed'); } } /** * List customer payment methods */ async listPaymentMethods(customerId: string): Promise { try { const paymentMethods = await stripe.paymentMethods.list({ customer: customerId, type: 'card', }); return paymentMethods.data; } catch (error) { console.error('Failed to list payment methods:', error); throw new Error('Payment method listing failed'); } } /** * Create a refund */ async createRefund(params: { paymentIntentId?: string; chargeId?: string; amount?: number; // Partial refund amount in cents reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer'; metadata?: Record; }): Promise { try { const refund = await stripe.refunds.create({ payment_intent: params.paymentIntentId, charge: params.chargeId, amount: params.amount, reason: params.reason, metadata: params.metadata, }); return refund; } catch (error) { console.error('Failed to create refund:', error); throw new Error('Refund creation failed'); } } } export const paymentService = new PaymentService(); ``` **Express API Routes**: ```typescript // src/routes/payment.routes.ts import { Router, Request, Response } from 'express'; import { paymentService } from '../services/payment.service'; const router = Router(); /** * POST /api/payments/checkout * Create a checkout session */ router.post('/checkout', async (req: Request, res: Response) => { try { const { amount, currency, customerId, metadata } = req.body; // Validate amount if (!amount || amount <= 0) { return res.status(400).json({ error: 'Invalid amount' }); } const session = await paymentService.createCheckoutSession({ amount, currency, customerId, metadata, }); res.json({ sessionId: session.id, url: session.url }); } catch (error) { console.error('Checkout error:', error); res.status(500).json({ error: 'Failed to create checkout session' }); } }); /** * POST /api/payments/intent * Create a payment intent for custom UI */ router.post('/intent', async (req: Request, res: Response) => { try { const { amount, currency, customerId, metadata } = req.body; if (!amount || amount <= 0) { return res.status(400).json({ error: 'Invalid amount' }); } const paymentIntent = await paymentService.createPaymentIntent({ amount, currency, customerId, metadata, }); res.json({ clientSecret: paymentIntent.client_secret }); } catch (error) { console.error('Payment intent error:', error); res.status(500).json({ error: 'Failed to create payment intent' }); } }); /** * POST /api/payments/customers * Create a customer */ router.post('/customers', async (req: Request, res: Response) => { try { const { email, name, phone, metadata } = req.body; if (!email) { return res.status(400).json({ error: 'Email is required' }); } const customer = await paymentService.createCustomer({ email, name, phone, metadata, }); res.json({ customerId: customer.id }); } catch (error) { console.error('Customer creation error:', error); res.status(500).json({ error: 'Failed to create customer' }); } }); /** * POST /api/payments/refunds * Create a refund */ router.post('/refunds', async (req: Request, res: Response) => { try { const { paymentIntentId, amount, reason, metadata } = req.body; if (!paymentIntentId) { return res.status(400).json({ error: 'Payment Intent ID is required' }); } const refund = await paymentService.createRefund({ paymentIntentId, amount, reason, metadata, }); res.json({ refundId: refund.id, status: refund.status }); } catch (error) { console.error('Refund error:', error); res.status(500).json({ error: 'Failed to create refund' }); } }); /** * GET /api/payments/config * Get public Stripe configuration */ router.get('/config', (req: Request, res: Response) => { res.json({ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY, }); }); export default router; ``` ### 3. Frontend Setup (React) **Stripe Provider**: ```typescript // src/providers/StripeProvider.tsx import React from 'react'; import { Elements } from '@stripe/react-stripe-js'; import { loadStripe, Stripe } from '@stripe/stripe-js'; // Load Stripe.js outside of component to avoid recreating the instance let stripePromise: Promise; const getStripe = () => { if (!stripePromise) { const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || ''; stripePromise = loadStripe(publishableKey); } return stripePromise; }; interface StripeProviderProps { children: React.ReactNode; } export const StripeProvider: React.FC = ({ children }) => { return ( {children} ); }; ``` **Payment Form Component**: ```typescript // src/components/PaymentForm.tsx import React, { useState } from 'react'; import { useStripe, useElements, CardElement, PaymentElement, } from '@stripe/react-stripe-js'; import type { StripeError } from '@stripe/stripe-js'; interface PaymentFormProps { amount: number; currency?: string; onSuccess: (paymentIntentId: string) => void; onError: (error: string) => void; customerId?: string; metadata?: Record; } export const PaymentForm: React.FC = ({ amount, currency = 'usd', onSuccess, onError, customerId, metadata, }) => { const stripe = useStripe(); const elements = useElements(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (!stripe || !elements) { // Stripe.js hasn't loaded yet return; } setLoading(true); setError(null); try { // Create payment intent on backend const response = await fetch('/api/payments/intent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount, currency, customerId, metadata, }), }); const { clientSecret } = await response.json(); // Confirm payment with Stripe.js const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment( clientSecret, { payment_method: { card: elements.getElement(CardElement)!, billing_details: { // Add billing details if collected }, }, } ); if (stripeError) { setError(stripeError.message || 'Payment failed'); onError(stripeError.message || 'Payment failed'); } else if (paymentIntent && paymentIntent.status === 'succeeded') { onSuccess(paymentIntent.id); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Payment failed'; setError(errorMessage); onError(errorMessage); } finally { setLoading(false); } }; return (
{error && (
{error}
)}
); }; ``` **Checkout Session Flow**: ```typescript // src/components/CheckoutButton.tsx import React, { useState } from 'react'; import { loadStripe } from '@stripe/stripe-js'; const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); interface CheckoutButtonProps { amount: number; currency?: string; buttonText?: string; } export const CheckoutButton: React.FC = ({ amount, currency = 'usd', buttonText = 'Checkout', }) => { const [loading, setLoading] = useState(false); const handleCheckout = async () => { setLoading(true); try { // Create checkout session const response = await fetch('/api/payments/checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount, currency }), }); const { sessionId } = await response.json(); // Redirect to Stripe Checkout const stripe = await stripePromise; if (stripe) { const { error } = await stripe.redirectToCheckout({ sessionId }); if (error) { console.error('Checkout error:', error); } } } catch (error) { console.error('Checkout error:', error); } finally { setLoading(false); } }; return ( ); }; ``` ### 4. Testing **Test Cards**: ```typescript // Test card numbers for different scenarios export const TEST_CARDS = { // Success VISA_SUCCESS: '4242424242424242', VISA_DEBIT: '4000056655665556', MASTERCARD: '5555555555554444', // Authentication required THREE_D_SECURE: '4000002500003155', // Failure scenarios CARD_DECLINED: '4000000000000002', INSUFFICIENT_FUNDS: '4000000000009995', LOST_CARD: '4000000000009987', STOLEN_CARD: '4000000000009979', EXPIRED_CARD: '4000000000000069', INCORRECT_CVC: '4000000000000127', PROCESSING_ERROR: '4000000000000119', // Special cases DISPUTE: '4000000000000259', FRAUD: '4100000000000019', }; // Any future expiry date (e.g., 12/34) // Any 3-digit CVC // Any postal code ``` **Integration Test**: ```typescript // tests/integration/payment.test.ts import { paymentService } from '../../src/services/payment.service'; import Stripe from 'stripe'; describe('Payment Service Integration', () => { describe('Payment Intent', () => { it('should create a payment intent', async () => { const paymentIntent = await paymentService.createPaymentIntent({ amount: 1000, currency: 'usd', }); expect(paymentIntent).toBeDefined(); expect(paymentIntent.amount).toBe(1000); expect(paymentIntent.currency).toBe('usd'); expect(paymentIntent.status).toBe('requires_payment_method'); }); it('should confirm payment intent with test card', async () => { // Create payment intent const paymentIntent = await paymentService.createPaymentIntent({ amount: 1000, currency: 'usd', }); // Create test payment method const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); const paymentMethod = await stripe.paymentMethods.create({ type: 'card', card: { number: '4242424242424242', exp_month: 12, exp_year: 2034, cvc: '123', }, }); // Confirm payment const confirmed = await paymentService.confirmPaymentIntent( paymentIntent.id, paymentMethod.id ); expect(confirmed.status).toBe('succeeded'); }); }); describe('Customer Management', () => { it('should create a customer', async () => { const customer = await paymentService.createCustomer({ email: 'test@example.com', name: 'Test User', }); expect(customer).toBeDefined(); expect(customer.email).toBe('test@example.com'); }); it('should attach payment method to customer', async () => { const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); // Create customer const customer = await paymentService.createCustomer({ email: 'test@example.com', }); // Create payment method const paymentMethod = await stripe.paymentMethods.create({ type: 'card', card: { number: '4242424242424242', exp_month: 12, exp_year: 2034, cvc: '123', }, }); // Attach payment method const attached = await paymentService.attachPaymentMethod( paymentMethod.id, customer.id ); expect(attached.customer).toBe(customer.id); }); }); describe('Refunds', () => { it('should create a refund', async () => { // First create and confirm a payment const paymentIntent = await paymentService.createPaymentIntent({ amount: 1000, currency: 'usd', }); const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); const paymentMethod = await stripe.paymentMethods.create({ type: 'card', card: { number: '4242424242424242', exp_month: 12, exp_year: 2034, cvc: '123', }, }); await paymentService.confirmPaymentIntent(paymentIntent.id, paymentMethod.id); // Create refund const refund = await paymentService.createRefund({ paymentIntentId: paymentIntent.id, reason: 'requested_by_customer', }); expect(refund).toBeDefined(); expect(refund.status).toBe('succeeded'); }); }); }); ``` ### 5. Security Checklist **Backend Security**: - [ ] NEVER log full card numbers or CVV - [ ] Use HTTPS only (enforce TLS 1.2+) - [ ] Validate webhook signatures - [ ] Implement rate limiting on payment endpoints - [ ] Store Stripe IDs, not card details - [ ] Use environment variables for keys - [ ] Implement idempotency keys for retries - [ ] Sanitize user inputs - [ ] Enable CSRF protection - [ ] Use secure session management **Frontend Security**: - [ ] Use Stripe.js (never raw card inputs) - [ ] Load Stripe.js from CDN (integrity check) - [ ] Never send card data to your server - [ ] Implement CSP headers - [ ] Use HTTPS only - [ ] Clear sensitive data from memory - [ ] Disable autocomplete on card fields - [ ] Implement proper error handling **Monitoring**: - [ ] Log all payment attempts - [ ] Monitor failed payment rates - [ ] Set up alerts for unusual activity - [ ] Track refund rates - [ ] Monitor webhook delivery - [ ] Implement fraud detection ### 6. Production Deployment **Pre-launch Checklist**: 1. **Update API Keys**: - Switch from test keys (`sk_test_`, `pk_test_`) to live keys - Update webhook endpoint with live webhook secret - Test with live mode in Stripe Dashboard 2. **Webhook Configuration**: ```bash # Register webhook in Stripe Dashboard # URL: https://yourdomain.com/api/webhooks/stripe # Events: payment_intent.succeeded, payment_intent.payment_failed, # customer.subscription.*, charge.refunded ``` 3. **Enable Radar** (fraud detection): - Configure Radar rules in Stripe Dashboard - Enable 3D Secure for high-risk payments - Set up risk score thresholds 4. **Tax Configuration**: - Enable Stripe Tax if needed - Configure tax rates by location - Set up tax reporting 5. **Business Verification**: - Complete business verification in Stripe - Add business information - Verify bank account for payouts 6. **Monitoring**: - Set up Sentry or similar for error tracking - Configure log aggregation (Datadog, Splunk) - Set up uptime monitoring for webhook endpoint - Create alerts for failed payments ## Output Deliverables When you complete this setup, provide: 1. **Configured Files**: - `.env` template with all required variables - Backend service with payment methods - API routes with error handling - Frontend components (PaymentForm, CheckoutButton) 2. **Documentation**: - API endpoint documentation - Testing guide with test cards - Deployment checklist - Security audit report 3. **Testing**: - Integration tests for payment flows - Test scenarios for edge cases - Webhook handling tests 4. **Deployment**: - Environment-specific configurations - Database migration scripts (if storing payment records) - Monitoring setup guide ## Resources - **Stripe Documentation**: https://stripe.com/docs - **Stripe.js Reference**: https://stripe.com/docs/js - **Webhook Testing**: Use Stripe CLI (`stripe listen --forward-to localhost:3000/api/webhooks/stripe`) - **Test Cards**: https://stripe.com/docs/testing ## Best Practices 1. **Always use Stripe.js** for card collection (PCI compliance) 2. **Verify webhooks** with signature validation 3. **Handle errors gracefully** with user-friendly messages 4. **Test thoroughly** with all test cards before production 5. **Monitor payment success rates** and investigate drops 6. **Implement retry logic** for API failures 7. **Use metadata** to link payments to your database records 8. **Never expose secret keys** in frontend code 9. **Implement idempotency** for payment operations 10. **Keep Stripe.js updated** to latest version Start with test mode, verify all flows work correctly, then switch to live mode with the same code.