DevHive-Cli stripe

Guidelines for using Stripe to integrate payments in mobile apps and any subsequent CRUD operations related to any Stripe entities

install
source · Clone the upstream repo
git clone https://github.com/El3tar-cmd/DevHive-Cli
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/El3tar-cmd/DevHive-Cli "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/stripe" ~/.claude/skills/el3tar-cmd-devhive-cli-stripe && rm -rf "$T"
manifest: skills/stripe/SKILL.md
source content

Introduction

Replit offers a native integration with Stripe that allows users to implement payments in their applications

Prerequisites

  1. These packages must be installed in the workspace root package.json:

    Do not install them in any workspace package. If they are not installed, use pnpm to install them via the following command:

    $ cd /home/runner/workspace && pnpm add -w <packages>

  2. The Stripe integration must be connected to the repl. You can do this by proposing the integration. Reference the

    integrations
    skill if necessary. Once connected, create
    stripeClient.ts
    using the template from the code-templates reference -- this file fetches Stripe credentials from the Replit connection API and provides the authenticated client via
    getUncachableStripeClient()
    .

    Create it in both the API server directory and the scripts directory.

  3. Ensure a PostgreSQL database exists. If you don't have one yet, use the tool to create a PostgreSQL database. Never use memory database for storing Stripe data.

You are required to ensure these requirements are met before setting up or using Stripe.

Project Structure

  • Scripts directory: The
    @workspace/scripts
    monorepo package (e.g.,
    src/seed-products.ts
    )
  • Run a script:
    pnpm --filter @workspace/scripts exec tsx src/<script>.ts
  • API server directory: Monorepo package defining Express API server code
  • Client app directory: The frontend application

Reference files use these terms. Map them to these concrete paths.

Initial Setup: Step-by-Step Implementation

Ensure prerequisites are met. Do not proceed until you have done so.

Follow these steps in order when implementing Stripe integration for the user. Create a task list to track implementation progress.

Read the code-templates reference file for full code file templates to use during these steps.

  1. Set Up Database

    Create the users table with Stripe ID references:

    CREATE TABLE users (
      id TEXT PRIMARY KEY,
      email TEXT,
      stripe_customer_id TEXT,
      stripe_subscription_id TEXT,
      created_at TIMESTAMP DEFAULT NOW()
    );
    

    Important: stripe-replit-sync creates the

    stripe
    schema automatically. Create application tables (users, orders, etc.) in the public schema, not in the stripe schema.

  2. Create Webhook Handler

    In the API server create

    webhookHandlers.ts
    - use the template provided in the code-templates reference. It handles webhook processing by calling
    getStripeSync()
    from stripeClient.

  3. Initialize Stripe on Startup

    Add this initialization function to the API server's index.ts. Important order:

    1. Run migrations to create schema
    2. Get StripeSync instance
    3. Set up managed webhook (uses StripeSync)
    4. Sync existing data with syncBackfill()
    import { runMigrations } from 'stripe-replit-sync';
    import { getStripeSync } from './stripeClient';
    
    async function initStripe() {
      const databaseUrl = process.env.DATABASE_URL;
      if (!databaseUrl) throw new Error('DATABASE_URL required');
    
      // 1. Create stripe schema and tables
      await runMigrations({ databaseUrl });
    
      // 2. Get StripeSync instance (needed for webhook setup and backfill)
      const stripeSync = await getStripeSync();
    
      // 3. Set up managed webhook
      const webhookBaseUrl = `https://${process.env.REPLIT_DOMAINS?.split(',')[0]}`;
      const { webhook } = await stripeSync.findOrCreateManagedWebhook(
        `${webhookBaseUrl}/api/stripe/webhook`
      );
    
      // 4. Sync all existing Stripe data
      await stripeSync.syncBackfill();
    }
    
    await initStripe();
    
  4. Register Webhook Route in index.ts

    Critical: Register the webhook route BEFORE

    express.json()
    middleware. See the indexTemplate for the correct pattern:

    1. Route path is
      /api/stripe/webhook
    2. Webhook route uses
      express.raw()
      to get Buffer in
      req.body
    3. Webhook route is registered BEFORE
      app.use(express.json())
    4. Other routes registered after get parsed JSON automatically
    app.post(
      '/api/stripe/webhook',
      express.raw({ type: 'application/json' }),
      async (req, res) => {
        const signature = req.headers['stripe-signature'];
        if (!signature) return res.status(400).json({ error: 'Missing signature' });
    
        const sig = Array.isArray(signature) ? signature[0] : signature;
    
        await WebhookHandlers.processWebhook(req.body as Buffer, sig);
        res.status(200).json({ received: true });
      }
    );
    
    app.use(express.json()); // Now apply to other routes
    
  5. Create Storage Layer

    Create

    storage.ts
    - use the template provided in the code-templates reference. Query Stripe data from PostgreSQL
    stripe
    schema using standard SQL queries.

  6. Set Up Other Routes

    Create

    routes.ts
    - use the template provided in the code-templates reference for products, prices, checkout, etc. These routes are registered AFTER
    express.json()
    so they get parsed JSON.

  7. Create Products Script (Recommended)

    Create

    seed-products.ts
    - a script to add products and prices to Stripe via the API. This is the recommended way to create products. Run it manually when creating products.

    Use the seed-products template from the code-templates reference as a starting point:

    • Modify the script to create the specific products needed
    • Run it manually when adding products:
      node seed-products.js
    • Webhooks automatically sync created products to the database
    • Stripe handles test vs live mode based on API keys
    import { getUncachableStripeClient } from './stripeClient';
    
    async function createProducts() {
      const stripe = await getUncachableStripeClient();
    
      // Create product
      const product = await stripe.products.create({
        name: 'Pro Plan',
        description: 'Professional subscription',
      });
    
      // Create prices for the product
      const monthlyPrice = await stripe.prices.create({
        product: product.id,
        unit_amount: 2900, // $29.00
        currency: 'usd',
        recurring: { interval: 'month' },
      });
    
      console.log('Created:', product.id, monthlyPrice.id);
    }
    
    createProducts();
    

    Usage: Run this script in development when you need to create or add products.

    Optional idempotency: To make the script safe to run multiple times, check if specific products exist first:

    const products = await stripe.products.search({ query: "name:'Pro Plan'" });
    if (products.data.length > 0) {
      console.log('Pro Plan already exists');
      return;
    }
    

Database Architecture

  1. stripe-replit-sync creates and manages the
    stripe
    schema automatically
    - DO NOT create any tables in the stripe schema
  2. The stripe schema contains tables for: products, prices, customers, subscriptions, payment_intents, etc.
  3. Application tables are created in the public schema (or other schemas) - e.g., users, orders, etc.
  4. Application tables store Stripe IDs as TEXT columns (e.g.,
    stripe_customer_id TEXT
    ,
    stripe_subscription_id TEXT
    )
  5. Replit automatically handles deployment and Stripe data migration moving from sandbox to live.

Synchronization Flow

  1. Stripe credentials are fetched from Replit connection API
  2. runMigrations()
    creates the stripe schema and all Stripe tables (idempotent, safe to run on every startup)
  3. syncBackfill()
    syncs ALL existing Stripe data from Stripe API to the local PostgreSQL database on startup
  4. Managed webhooks are automatically configured by
    stripe-replit-sync
  5. Webhooks keep data up-to-date when changes occur in Stripe after startup
  6. Application queries Stripe data from PostgreSQL
    stripe
    schema (fast, no API calls needed)

Storing Data

Do NOT Create Product Tables or Maintain Any Other Source of Truth for Stripe Data

Bad -- creates duplicate product storage:

export const products = pgTable("products", {
  id: varchar("id").primaryKey(),
  name: text("name"),
  price: integer("price"),
  // ... custom fields
});

Correct Approach:

  • NO product tables needed!
  • Query directly from
    stripe.products
    and
    stripe.prices
  • Use Stripe's metadata field for ALL custom attributes

Creating Products and Prices

NEVER insert data directly into the stripe schema tables. Instead:

  • Create products via Stripe API (using scripts)
  • Stripe webhooks managed by stripe-replit-sync automatically sync the data to the stripe schema in the local database
  • Query the synced data from the
    stripe
    schema tables using standard SQL queries

Create products in Stripe with metadata (use API or script):

await stripe.products.create({
  name: "Product Name",
  description: "...",
  images: ["https://..."],
  metadata: {
    category: "electronics",
    customField1: "value1",
    featured: "true",
  }
});
await stripe.prices.create({
  product: product.id,
  unit_amount: 9999,
  currency: 'usd',
});

Query from synced stripe tables:

const result = await db.execute(sql`
  SELECT
    p.id,
    p.name,
    p.metadata,
    pr.id as price_id,
    pr.unit_amount
  FROM stripe.products p
  JOIN stripe.prices pr ON pr.product = p.id
  WHERE p.active = true
`);

Use real Stripe price IDs in checkout:

// CORRECT
{ price: "price_1ABC...", quantity: 1 }
// WRONG - Never use price_data
{
  price_data: {
    unit_amount: 9999,
    currency: 'usd',
    product_data: { name: "..." }
  }
}

Decision Tree: When to Create Tables

  • Products/Prices? -- NO, use
    stripe.products
    /
    stripe.prices
  • Customers? -- NO, use
    stripe.customers
  • Subscriptions? -- NO, use
    stripe.subscriptions
  • Orders/Cart/User preferences? -- YES, these aren't in Stripe
  • Relationships to Stripe data? -- YES, store Stripe IDs as foreign keys

Key Principle: "If it exists in Stripe, it belongs in Stripe"

  • Don't duplicate Stripe data in your own tables
  • Don't create parallel storage systems
  • Stripe + metadata = your complete product catalog
  • Your tables should only store data that Stripe doesn't manage

Creating products: Write a seed script that calls Stripe API (

stripe.products.create()
,
stripe.prices.create()
). Run this script in development to create your products. Webhooks automatically sync them to the database. Replit handles deployment automatically.

Workflow:

# Development:
# - Run seed script to create products in Stripe (test mode)
# - syncBackfill() syncs them to local database

# Deployment (handled by Replit):
# - Replit copies products/prices from dev Stripe to prod Stripe
# - Your code runs unchanged, syncBackfill() syncs prod database

# Production:
# - Modify products via Stripe Dashboard (scripts can't run in deployments)
# - Webhooks automatically sync changes to database

Publishing with Stripe

To publish the app with working Stripe payments:

  1. Go to the Stripe Dashboard (https://dashboard.stripe.com/apikeys)
  2. Get live API keys (starts with
    pk_live_
    and
    sk_live_
    )
  3. Open the Publish pane in the Workspace
  4. Enter the live Publishable Key and Secret Key
  5. Publish the app

Placeholder Keys (User-Requested Only):

Only offer placeholder keys if the user explicitly asks to publish without live Stripe keys and understands the consequences. Do NOT use these for any other purpose.

If the user confirms they want to proceed without live keys, suggest the following values to them:

  • Publishable Key:
    pk_live_abcdef
  • Secret Key:
    sk_live_abcdef

Consequences the user must understand:

  • Placeholder keys will NOT process real payments
  • The product catalog and checkout will not function on the published URL
  • Real Stripe keys must be added later for payments to work

Deleting Stripe Integration

To remove the Stripe integration from the project:

  1. In the project, open the "Integrations" tab
  2. Go to "Stripe" and click "Manage"
  3. Select "Edit" then "Delete"

Secrets and Environment Variables

NEVER write secrets or environment variables directly to the

.replit
file. This includes Stripe API keys, webhook signing secrets, database URLs, and any other sensitive configuration. Instead, reference the
environment-secrets
skill for the correct way to manage secrets and environment variables.

Key Rules

DO:

  • Create products via Stripe API using scripts (never with SQL INSERT)
  • Query Stripe data from PostgreSQL
    stripe
    schema tables (products, prices, customers, subscriptions, etc.)
  • Store Stripe IDs in application tables as TEXT (e.g.,
    stripe_customer_id TEXT
    )
  • Keep webhook handler minimal (just call
    processWebhook
    with payload and signature)
  • Let
    syncBackfill()
    sync existing Stripe data on startup
  • Let managed webhooks handle webhook configuration automatically
  • Register webhook route BEFORE
    express.json()
    middleware

ABSOLUTELY DO NOT:

  • Create any tables in the
    stripe
    schema - stripe-replit-sync manages this automatically
  • Insert, update, or delete data in
    stripe
    schema tables - only query from them
  • Use SQL INSERT for products, prices, customers, subscriptions - use Stripe API instead
  • Manually copy database data between environments - Replit handles Stripe product/price copying during deployment
  • Add custom logic in webhook handler beyond calling
    processWebhook
  • Skip running
    runMigrations()
    or
    syncBackfill()
  • Create StripeSync instance before calling
    runMigrations()

Common Mistakes

Database - DO NOT create Stripe tables:

-- WRONG - Creating Stripe tables manually
CREATE TABLE products (
  id TEXT PRIMARY KEY,
  name TEXT,
  price INTEGER
);

-- CORRECT - Create application tables, store Stripe IDs
CREATE TABLE users (
  id TEXT PRIMARY KEY,
  stripe_customer_id TEXT
);
-- stripe-replit-sync creates stripe.products, stripe.prices, etc. automatically

Data Creation - Use Stripe API Scripts, NOT SQL:

-- WRONG - Inserting Stripe data with SQL
INSERT INTO stripe.products (id, name, description)
VALUES ('prod_123', 'Pro Plan', 'Professional subscription');
// CORRECT - Use Stripe API in a script
const stripe = await getUncachableStripeClient();
const product = await stripe.products.create({
  name: 'Pro Plan',
  description: 'Professional subscription',
});
// Webhook syncs this to stripe.products automatically

Initialization Order:

// WRONG - Creating StripeSync before migrations
const stripeSync = await getStripeSync();
await runMigrations({ databaseUrl });

// CORRECT - Migrations first, then StripeSync
await runMigrations({ databaseUrl });
const stripeSync = await getStripeSync();
await stripeSync.findOrCreateManagedWebhook(...);
await stripeSync.syncBackfill();

Frontend - Must parse JSON responses:

// WRONG - returns Response object
const response = await apiRequest('POST', '/api/checkout', { priceId });
return response;

// CORRECT - parse to get data
const response = await apiRequest('POST', '/api/checkout', { priceId });
return await response.json();

Backend - Webhook route ordering:

// WRONG - Webhook after express.json()
app.use(express.json());
app.post('/api/stripe/webhook', ...);  // Too late! Body already parsed

// CORRECT - Webhook BEFORE express.json()
app.post('/api/stripe/webhook', express.raw({ type: 'application/json' }),
  async (req, res) => {
    await WebhookHandlers.processWebhook(req.body, sig);
  }
);
app.use(express.json());  // Now apply to other routes

References

  • ./references/code-templates.md -- Code file templates for initial Stripe setup. ONLY read this reference when performing initial setup of Stripe in a project that does not already have it configured. Do not read for subsequent modifications, queries, or other Stripe operations.