Complete Stripe Payment Integration Guide: from basic checkout to subscription management
Stripe is one of the most popular online payment processing platforms, serving millions of businesses worldwide. Known for clean APIs, comprehensive documentation, and global payment capabilities, it is a go-to choice for both independent developers and large enterprises. This guide walks through complete Stripe integration from scratch.
A complete integration really comes down to four things: creating an account and fetching keys, collecting payments with Checkout or Payment Elements, configuring pricing plans for subscription products, and using webhooks to sync payment results back into your own system. Many teams finish the first three steps and go live, only to find their orders and books no longer match — the problem is almost always a missing webhook or reconciliation step. The sections below walk through each of these in order.
1. Account Setup
Create Account
Visit Stripe website to register. Stripe supports merchants in most countries and regions worldwide, including Hong Kong and Singapore. You'll need:
- Basic personal or business information
- Bank account details for settlement
- Identity verification documents
Get API Keys
In Stripe Dashboard → Developers → API Keys, you can get two sets of keys:
# Test mode keys (development)
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxx
# Live mode keys (production)
STRIPE_PUBLISHABLE_KEY=pk_live_xxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxx
Important: Never expose Secret Key in client-side code or public repositories.
2. Basic Payment Flow
Frontend (Checkout Mode)
Stripe Checkout is the simplest integration — Stripe hosts the payment page:
<script src="https://js.stripe.com/v3/"></script>
<button id="checkout-button">Pay $20.00</button>
<script>
const stripe = Stripe('pk_test_xxxxxxxxxxxx');
document.getElementById('checkout-button').addEventListener('click', async () => {
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ product: 'premium-plan', quantity: 1 })
});
const session = await response.json();
const result = await stripe.redirectToCheckout({ sessionId: session.id });
});
</script>
Backend (Node.js)
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/api/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
line_items: [{
price_data: {
currency: 'usd',
product_data: { name: 'Premium Plan' },
unit_amount: 2000,
},
quantity: 1,
}],
mode: 'payment',
success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cancel',
});
res.json({ id: session.id });
});
Test Cards and Local Development
Stripe provides a set of fixed test card numbers in test mode so you can validate the entire flow without real charges:
| Test Card | Scenario |
|---|---|
4242 4242 4242 4242 |
Payment succeeds |
4000 0000 0000 0002 |
Insufficient funds, declined |
4000 0000 0000 9995 |
Triggers 3DS verification |
4000 0000 0000 3220 |
Chargeback (dispute) after success |
For local development, install the Stripe CLI to forward remote events to your dev machine:
stripe listen --forward-to localhost:3000/webhook
The CLI prints a webhook signing secret starting with whsec_; put it in the STRIPE_WEBHOOK_SECRET environment variable and your local server will receive real-format events like checkout.session.completed, giving you end-to-end integration testing.
Supported Payment Methods
| Method | Region | Use Case |
|---|---|---|
| Credit/Debit Cards | Global | Universal online payments |
| Apple Pay | Global | Mobile & Safari |
| Google Pay | Global | Android & Chrome |
| Alipay | China | Chinese consumers |
| WeChat Pay | China | Chinese consumers |
| IDEAL | Netherlands | Local Dutch payments |
| SEPA Direct Debit | EU | European bank transfers |
| BACS Direct Debit | UK | UK bank transfers |
3. Subscription Management
// Create product
const product = await stripe.products.create({
name: 'Monthly Pro',
description: 'All premium features'
});
// Create pricing ($29.99/month)
const price = await stripe.prices.create({
product: product.id,
unit_amount: 2999,
currency: 'usd',
recurring: { interval: 'month' }
});
// Create subscription
app.post('/api/create-subscription', async (req, res) => {
const { customerId, priceId } = req.body;
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
payment_behavior: 'default_incomplete',
expand: ['latest_invoice.payment_intent'],
});
res.json({ subscriptionId: subscription.id });
});
Subscription products usually also need trial periods and price changes. Offering a 14-day trial to new users is the most common approach, done by passing trial_period_days when creating the subscription:
// Create a subscription with a 14-day trial
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
trial_period_days: 14,
payment_settings: { payment_method_types: ['card'] },
});
When the trial ends, Stripe generates the first invoice and charges the customer; if the charge fails the subscription moves to incomplete and Stripe retries according to your policy (for example, 3 attempts over 4 days). Changing the price does not require rebuilding the subscription — call stripe.subscriptions.update and modify the price in items, and Stripe automatically prorates the difference for the remaining period. To stop a subscription, pass cancel_at_period_end: true so it expires naturally at the end of the current billing cycle instead of being cut off immediately.
4. Webhook Handling
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'checkout.session.completed':
// Handle successful payment
break;
case 'invoice.payment_succeeded':
// Subscription renewal
break;
case 'customer.subscription.deleted':
// Subscription cancelled
break;
}
res.json({received: true});
});
5. Cross-border Considerations
- Currency conversion: 135+ currencies supported
- Settlement: Standard 7-day cycle (2-day available for fee)
- Fees: 2.9% + $0.30 (domestic), +1.5% (international cards)
- Refunds: Fee non-refundable, refund itself is free
- Fraud prevention: Stripe Radar ML-powered detection
6. Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Payment declined | Card info error/insufficient funds | Use test mode to debug |
| Webhook verification fails | Secret mismatch | Verify Signing Secret |
| Checkout won't load | Wrong API keys | Check key mode (test/live) |
| Cross-border fails | Card doesn't support intl | Ask user to contact bank |
7. A Complete Order-to-Renewal Scenario
Take a typical SaaS membership site to see the whole chain: a user picks "Pro, $29.99/month" on the checkout page and clicks pay.
- The frontend calls
/api/create-checkout-session; the backend creates the session withmode: 'subscription'andcustomer_creation: 'always', so Stripe also creates a customer profile. - The user is redirected to Stripe's hosted Checkout page and pays; Stripe notifies your server with the
checkout.session.completedevent. - Your server updates the local order status to "paid" on receipt of the event and records the subscription relationship via
customer.subscription.created, storing thesubscriptionIdin the database. - On each monthly billing date, Stripe charges automatically and pushes
invoice.paid; if the charge fails it pushesinvoice.payment_failed, and you can send the user a reminder email so the service is not interrupted by a missed payment. - When the user cancels in their dashboard, you receive
customer.subscription.deletedand use it to revoke premium features.
In this chain, the local database only displays subscription state — the source of truth is Stripe's events. Even if an event is missed, you can reconcile the next day with stripe.subscriptions.retrieve.
Reference: Stripe Checkout docs https://docs.stripe.com/payments/checkout; Subscriptions & billing https://docs.stripe.com/billing; Webhook signatures https://docs.stripe.com/webhooks/signatures
16IDC Takeaway
Stripe is the top choice for cross-border websites, especially those serving US/European users. For China-focused sites, integrate Alipay and WeChat Pay as well. Stripe's pay-as-you-go model (no monthly fee) is ideal for startups.