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

  1. Currency conversion: 135+ currencies supported
  2. Settlement: Standard 7-day cycle (2-day available for fee)
  3. Fees: 2.9% + $0.30 (domestic), +1.5% (international cards)
  4. Refunds: Fee non-refundable, refund itself is free
  5. 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.

  1. The frontend calls /api/create-checkout-session; the backend creates the session with mode: 'subscription' and customer_creation: 'always', so Stripe also creates a customer profile.
  2. The user is redirected to Stripe's hosted Checkout page and pays; Stripe notifies your server with the checkout.session.completed event.
  3. Your server updates the local order status to "paid" on receipt of the event and records the subscription relationship via customer.subscription.created, storing the subscriptionId in the database.
  4. On each monthly billing date, Stripe charges automatically and pushes invoice.paid; if the charge fails it pushes invoice.payment_failed, and you can send the user a reminder email so the service is not interrupted by a missed payment.
  5. When the user cancels in their dashboard, you receive customer.subscription.deleted and 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.