Stripe
Stripe is the leading payment platform for online businesses, offering complete payment infrastructure with support for cards, wallets, bank transfers, and 40+ payment methods worldwide. Learn how to integrate Stripe into your Astro and React projects to accept payments, manage subscriptions, and handle transactions securely with PCI compliance built-in.
Last updated: 2026-03-29
Setting Up Stripe
Create a Stripe account to get your API keys. Stripe provides test mode for development and live mode for production. Install client and server packages. The client handles payment UI, the server processes transactions.
// Install Stripe packages
npm install @stripe/stripe-js @stripe/react-stripe-js stripe
// Create .env file for API keys
PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_YOUR_KEY
STRIPE_SECRET_KEY=sk_test_YOUR_KEY
// Initialize Stripe on the client
// src/lib/stripe-client.js
import { loadStripe } from '@stripe/stripe-js';
export const stripePromise = loadStripe(
import.meta.env.PUBLIC_STRIPE_PUBLISHABLE_KEY
);
// Initialize Stripe on the server
// src/lib/stripe-server.js
import Stripe from 'stripe';
export const stripe = new Stripe(import.meta.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-11-20'
});Creating a Checkout Form
Stripe Elements provides pre-built, PCI-compliant UI components for collecting payment information. The CardElement component handles card input with built-in validation, formatting, and security. Wrap your checkout form with the Elements provider.
// Payment form component
// src/components/CheckoutForm.jsx
import { useState } from 'react';
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
export function CheckoutForm({ amount }) {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState(null);
const [processing, setProcessing] = useState(false);
const [succeeded, setSucceeded] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
setProcessing(true);
// Create payment intent on server
const response = await fetch('/api/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount })
});
const { clientSecret } = await response.json();
// Confirm payment with Stripe
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: 'Customer Name',
email: 'customer@example.com'
}
}
});
if (error) {
setError(error.message);
setProcessing(false);
} else if (paymentIntent.status === 'succeeded') {
setSucceeded(true);
setProcessing(false);
}
};
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 bg-white rounded-lg shadow">
<div className="mb-4">
<label className="block text-sm font-medium mb-2 text-gray-700">
Card Details
</label>
<CardElement
className="p-3 border border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': { color: '#aab7c4' }
}
}
}}
/>
</div>
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded">
{error}
</div>
)}
{succeeded && (
<div className="mb-4 p-3 bg-green-50 border border-green-200 text-green-700 rounded">
Payment successful!
</div>
)}
<button
type="submit"
disabled={!stripe || processing || succeeded}
className="w-full px-4 py-3 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition"
>
{processing ? 'Processing...' : `Pay $${(amount / 100).toFixed(2)}`}
</button>
</form>
);
}
// Wrap with Elements provider in your page
// src/pages/checkout.astro
---
import { stripePromise } from '../lib/stripe-client';
---
<script>
import { Elements } from '@stripe/react-stripe-js';
import { CheckoutForm } from '../components/CheckoutForm';
const App = () => (
<Elements stripe={stripePromise}>
<CheckoutForm amount={2000} />
</Elements>
);
</script>Server-Side Payment Processing
Payment processing must happen server-side for security. Create API endpoints to generate payment intents, which represent a payment's lifecycle from creation to completion. The client secret from the payment intent is passed to the client to confirm payment securely.
// Create payment intent endpoint
// src/pages/api/create-payment-intent.ts
import type { APIRoute } from 'astro';
import { stripe } from '../../lib/stripe-server';
export const POST: APIRoute = async ({ request }) => {
try {
const { amount } = await request.json();
// Validate amount
if (!amount || amount < 50) {
return new Response(
JSON.stringify({ error: 'Invalid amount' }),
{ status: 400 }
);
}
// Create payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
automatic_payment_methods: {
enabled: true,
},
metadata: {
order_id: 'ORDER_123',
customer_email: 'customer@example.com'
}
});
return new Response(
JSON.stringify({ clientSecret: paymentIntent.client_secret }),
{ status: 200 }
);
} catch (error) {
console.error('Payment intent error:', error);
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500 }
);
}
};
// Webhook handler for payment events
// src/pages/api/webhook.ts
import type { APIRoute } from 'astro';
import { stripe } from '../../lib/stripe-server';
const endpointSecret = import.meta.env.STRIPE_WEBHOOK_SECRET;
export const POST: APIRoute = async ({ request }) => {
const body = await request.text();
const sig = request.headers.get('stripe-signature');
let event;
try {
event = stripe.webhooks.constructEvent(body, sig, endpointSecret);
} catch (err) {
return new Response(`Webhook Error: ${err.message}`, { status: 400 });
}
// Handle different event types
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('Payment succeeded:', paymentIntent.id);
// Update order status, send confirmation email, etc.
break;
case 'payment_intent.payment_failed':
const failedPayment = event.data.object;
console.log('Payment failed:', failedPayment.id);
// Handle failed payment
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
return new Response(JSON.stringify({ received: true }), { status: 200 });
};Handling Subscriptions
Stripe makes recurring billing simple with subscription management. Create subscription products in your Stripe Dashboard, then use the API to create checkout sessions that handle recurring payments automatically. Stripe manages billing cycles, proration, and failed payment retries.
// Create subscription checkout session
// src/pages/api/create-subscription.ts
import type { APIRoute } from 'astro';
import { stripe } from '../../lib/stripe-server';
export const POST: APIRoute = async ({ request }) => {
try {
const { priceId, customerEmail } = await request.json();
// Create checkout session for subscription
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [
{
price: priceId,
quantity: 1,
},
],
customer_email: customerEmail,
success_url: `${request.url}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${request.url}/cancel`,
subscription_data: {
trial_period_days: 14, // Optional: free trial
metadata: {
user_id: '123'
}
}
});
return new Response(
JSON.stringify({ url: session.url }),
{ status: 200 }
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500 }
);
}
};
// Manage subscription status with webhooks
// Handle events: customer.subscription.created, updated, deleted