Skip to content

docs: stripe payment integration research findings #1104

@lancy

Description

@lancy

Overview

This issue documents comprehensive research on Stripe payment integration, covering SDK usage, API integration patterns, and best practices.

Current State

The vm0-2 project currently does not have Stripe integrated. No Stripe-related dependencies exist in the codebase.


Key Findings

Two Integration Approaches

Approach Description Complexity
Stripe Checkout Pre-built, hosted payment page. Stripe handles the entire UI. Simple (2/5)
Payment Elements Customizable, embeddable React components. More control over UI/UX. Moderate (3/5)

Both approaches are PCI compliant.


Core APIs

1. Checkout Sessions API

Creates redirect-based payment flows:

const session = await stripe.checkout.sessions.create({
  mode: 'payment', // or 'subscription', 'setup'
  line_items: [{
    price: 'price_xxx',
    quantity: 1
  }],
  success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://example.com/cancel',
  metadata: { order_id: '12345' }
});

2. Payment Intents API

Tracks payments through their lifecycle:

const paymentIntent = await stripe.paymentIntents.create({
  amount: 1099,  // Amount in cents
  currency: 'usd',
  metadata: { order_id: '6735' },
  setup_future_usage: 'off_session',
});

Lifecycle States: requires_payment_methodrequires_confirmationrequires_actionprocessingsucceeded

3. Subscriptions API

For recurring payments:

// Create Product & Price
const product = await stripe.products.create({ name: 'Premium Plan' });
const price = await stripe.prices.create({
  product: product.id,
  unit_amount: 1500,
  currency: 'usd',
  recurring: { interval: 'month' },
});

// Create Subscription Checkout
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: price.id, quantity: 1 }],
  success_url: 'https://example.com/success',
});

React/TypeScript Integration

Installation

npm install stripe @stripe/react-stripe-js @stripe/stripe-js

Frontend Setup

import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';

const stripePromise = loadStripe('pk_test_...');

export default function App() {
  return (
    <Elements stripe={stripePromise} options={{ clientSecret: '...' }}>
      <CheckoutForm />
    </Elements>
  );
}

PaymentElement Component

import { useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js';

function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: { return_url: window.location.origin + '/success' },
    });

    if (error) console.log(error.message);
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button disabled={!stripe}>Pay</button>
    </form>
  );
}

Webhook Setup

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'] as string;
  const event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);

  switch (event.type) {
    case 'payment_intent.succeeded':
      // Handle successful payment
      break;
    case 'invoice.paid':
      // Subscription payment succeeded
      break;
    case 'invoice.payment_failed':
      // Handle failed payment
      break;
  }

  res.json({ received: true });
});

Key Events:

  • payment_intent.succeeded / payment_intent.payment_failed
  • checkout.session.completed / checkout.session.expired
  • invoice.paid / invoice.payment_failed
  • customer.subscription.created / deleted

Error Handling

Error Type Description
StripeCardError Payment declined or card issue
StripeInvalidRequestError Invalid API parameters
StripeAPIConnectionError Network connectivity issue
StripeAuthenticationError Invalid API key
StripeRateLimitError Too many requests

Common Decline Codes: insufficient_funds, card_declined, expired_card, incorrect_cvc, generic_decline


Testing

Test Cards

Card Number Behavior
4242 4242 4242 4242 Successful payment
4000 0000 0000 9995 Decline (insufficient funds)
4000 0025 0000 3155 Requires 3D Secure

CLI Testing

stripe listen --forward-to localhost:3000/webhook
stripe trigger payment_intent.succeeded

Security & Compliance

  • Use Stripe.js or Elements (handles card data in iframes)
  • Never handle raw card numbers on your server
  • Always load Stripe.js from js.stripe.com
  • Store API keys in environment variables
  • Stripe handles 3D Secure (SCA) automatically for EU payments

Pricing

Feature Cost
Standard payments 2.9% + $0.30 per transaction (US)
Recurring billing +0.5% per recurring transaction
International cards +1%

Key Resources


🤖 Generated with Claude Code from conversation research

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions