1 ---
2 name: stripe
3 description: Guidelines for using Stripe to integrate payments in mobile apps and any subsequent CRUD operations related to any Stripe entities
4 ---
5
6 ## Read the monetization skill first
7
8 Before using anything in this skill, read the `monetization` skill and follow its routing instructions. It is the single source of truth for choosing a payment provider on Replit. Only continue with this Stripe skill once the monetization skill has confirmed that Stripe is the right provider (either because the user explicitly named Stripe, the project already has Stripe wired up, or the routing question resolved to Stripe).
9
10
11 ## Introduction
12
13 Replit offers a native integration with Stripe that allows users to implement payments in their applications
14
15 ## Prerequisites
16
17 1. These packages must be installed in the workspace root package.json:
18 - `stripe` - Official Stripe SDK for API operations
19 - `stripe-replit-sync` - Handles webhook processing and database sync. Documentation: https://www.npmjs.com/package/stripe-replit-sync
20
21 If they are not installed, use npm to install them: `$ cd /home/runner/workspace && npm install <packages>`
22
23
24 2. The Stripe integration must be connected to the repl. You can do this by proposing the integration. Reference the `integrations` skill if necessary.
25 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()`.
26
27 Create it in the `server/` directory.
28
29
30 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.
31
32 You are required to ensure these requirements are met before setting up or using Stripe.
33
34 ## Project Structure
35
36 - **Scripts directory**: `scripts/` at the workspace root (e.g., `scripts/seed-products.ts`)
37 - **Run a script**: `npx tsx scripts/<script>.ts`
38 - **API server directory**: `server/`
39 - **Client app directory**: `client/`
40
41 Reference files use these terms. Map them to these concrete paths.
42
43 ## Initial Setup: Step-by-Step Implementation
44
45 Ensure prerequisites are met. Do not proceed until you have done so.
46
47 Follow these steps in order when implementing Stripe integration for the user. Create a task list to track implementation progress.
48
49 Read the code-templates reference file for full code file templates to use during these steps.
50
51 1. Set Up Database
52
53 Create the users table with Stripe ID references:
54
55 ```sql
56 CREATE TABLE users (
57 id TEXT PRIMARY KEY,
58 email TEXT,
59 stripe_customer_id TEXT,
60 stripe_subscription_id TEXT,
61 created_at TIMESTAMP DEFAULT NOW()
62 );
63 ```
64
65 **Important**: stripe-replit-sync creates the `stripe` schema automatically. Create application tables (users, orders, etc.) in the public schema, not in the stripe schema.
66
67 2. Create Webhook Handler
68
69 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.
70
71 3. Initialize Stripe on Startup
72
73 Add this initialization function to the API server's index.ts. **Important order:**
74
75 1. Run migrations to create schema
76 2. Get StripeSync instance
77 3. Set up managed webhook (uses StripeSync)
78 4. Sync existing data with syncBackfill()
79
80 ```typescript
81 import { runMigrations } from 'stripe-replit-sync';
82 import { getStripeSync } from './stripeClient';
83
84 async function initStripe() {
85 const databaseUrl = process.env.DATABASE_URL;
86 if (!databaseUrl) throw new Error('DATABASE_URL required');
87
88 // 1. Create stripe schema and tables
89 await runMigrations({ databaseUrl });
90
91 // 2. Get StripeSync instance (needed for webhook setup and backfill)
92 const stripeSync = await getStripeSync();
93
94 // 3. Set up managed webhook
95 const webhookBaseUrl = `https://${process.env.REPLIT_DOMAINS?.split(',')[0]}`;
96 const { webhook } = await stripeSync.findOrCreateManagedWebhook(
97 `${webhookBaseUrl}/api/stripe/webhook`
98 );
99
100 // 4. Sync all existing Stripe data
101 await stripeSync.syncBackfill();
102 }
103
104 await initStripe();
105 ```
106
107 4. Register Webhook Route in index.ts
108
109 **Critical:** Register the webhook route BEFORE `express.json()` middleware. See the indexTemplate for the correct pattern:
110
111 1. Route path is `/api/stripe/webhook`
112 2. Webhook route uses `express.raw()` to get Buffer in `req.body`
113 3. Webhook route is registered BEFORE `app.use(express.json())`
114 4. Other routes registered after get parsed JSON automatically
115
116 ```typescript
117 app.post(
118 '/api/stripe/webhook',
119 express.raw({ type: 'application/json' }),
120 async (req, res) => {
121 const signature = req.headers['stripe-signature'];
122 if (!signature) return res.status(400).json({ error: 'Missing signature' });
123
124 const sig = Array.isArray(signature) ? signature[0] : signature;
125
126 await WebhookHandlers.processWebhook(req.body as Buffer, sig);
127 res.status(200).json({ received: true });
128 }
129 );
130
131 app.use(express.json()); // Now apply to other routes
132 ```
133
134 5. Create Storage Layer
135
136 Create `storage.ts` - use the template provided in the code-templates reference. Query Stripe data from PostgreSQL `stripe` schema using standard SQL queries.
137
138 6. Set Up Other Routes
139
140 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.
141
142 7. Create Products Script (Recommended)
143
144 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.**
145
146 Use the seed-products template from the code-templates reference as a starting point:
147
148 - Modify the script to create the specific products needed
149 - Run it manually when adding products: `node seed-products.js`
150 - Webhooks automatically sync created products to the database
151 - Stripe handles test vs live mode based on API keys
152
153 ```typescript
154 import { getUncachableStripeClient } from './stripeClient';
155
156 async function createProducts() {
157 const stripe = await getUncachableStripeClient();
158
159 // Create product
160 const product = await stripe.products.create({
161 name: 'Pro Plan',
162 description: 'Professional subscription',
163 });
164
165 // Create prices for the product
166 const monthlyPrice = await stripe.prices.create({
167 product: product.id,
168 unit_amount: 2900, // $29.00
169 currency: 'usd',
170 recurring: { interval: 'month' },
171 });
172
173 console.log('Created:', product.id, monthlyPrice.id);
174 }
175
176 createProducts();
177 ```
178
179 **Usage:**
180 Run this script in development when you need to create or add products.
181
182 **Optional idempotency:**
183 To make the script safe to run multiple times, check if specific products exist first:
184
185 ```typescript
186 const products = await stripe.products.search({ query: "name:'Pro Plan'" });
187 if (products.data.length > 0) {
188 console.log('Pro Plan already exists');
189 return;
190 }
191 ```
192
193 ## Database Architecture
194
195 1. **stripe-replit-sync creates and manages the `stripe` schema automatically** - DO NOT create any tables in the stripe schema
196 2. The stripe schema contains tables for: products, prices, customers, subscriptions, payment_intents, etc.
197 3. **Application tables are created in the public schema (or other schemas)** - e.g., users, orders, etc.
198 4. Application tables store Stripe IDs as TEXT columns (e.g., `stripe_customer_id TEXT`, `stripe_subscription_id TEXT`)
199 5. Replit automatically handles deployment and Stripe data migration moving from sandbox to live.
200
201 ## Synchronization Flow
202
203 1. Stripe credentials are fetched from Replit connection API
204 2. **`runMigrations()`** creates the stripe schema and all Stripe tables (idempotent, safe to run on every startup)
205 3. **`syncBackfill()`** syncs ALL existing Stripe data from Stripe API to the local PostgreSQL database on startup
206 4. **Managed webhooks** are automatically configured by `stripe-replit-sync`
207 5. **Webhooks** keep data up-to-date when changes occur in Stripe after startup
208 6. Application queries Stripe data from PostgreSQL `stripe` schema (fast, no API calls needed)
209
210 ## Storing Data
211
212 Do NOT Create Product Tables or Maintain Any Other Source of Truth for Stripe Data
213
214 **Bad** -- creates duplicate product storage:
215
216 ```ts
217 export const products = pgTable("products", {
218 id: varchar("id").primaryKey(),
219 name: text("name"),
220 price: integer("price"),
221 // ... custom fields
222 });
223 ```
224
225 Correct Approach:
226
227 - NO product tables needed!
228 - Query directly from `stripe.products` and `stripe.prices`
229 - Use Stripe's metadata field for ALL custom attributes
230
231 ### Creating Products and Prices
232
233 **NEVER insert data directly into the stripe schema tables.** Instead:
234 - Create products via Stripe API (using scripts)
235 - Stripe webhooks managed by stripe-replit-sync automatically sync the data to the stripe schema in the local database
236 - Query the synced data from the `stripe` schema tables using standard SQL queries
237
238 Create products in Stripe with metadata (use API or script):
239
240 ```ts
241 await stripe.products.create({
242 name: "Product Name",
243 description: "...",
244 images: ["https://..."],
245 metadata: {
246 category: "electronics",
247 customField1: "value1",
248 featured: "true",
249 }
250 });
251 await stripe.prices.create({
252 product: product.id,
253 unit_amount: 9999,
254 currency: 'usd',
255 });
256 ```
257
258 Query from synced stripe tables:
259
260 ```ts
261 const result = await db.execute(sql`
262 SELECT
263 p.id,
264 p.name,
265 p.metadata,
266 pr.id as price_id,
267 pr.unit_amount
268 FROM stripe.products p
269 JOIN stripe.prices pr ON pr.product = p.id
270 WHERE p.active = true
271 `);
272 ```
273
274 Use real Stripe price IDs in checkout:
275
276 ```ts
277 // CORRECT
278 { price: "price_1ABC...", quantity: 1 }
279 // WRONG - Never use price_data
280 {
281 price_data: {
282 unit_amount: 9999,
283 currency: 'usd',
284 product_data: { name: "..." }
285 }
286 }
287 ```
288
289 ### Decision Tree: When to Create Tables
290
291 - Products/Prices? -- NO, use `stripe.products`/`stripe.prices`
292 - Customers? -- NO, use `stripe.customers`
293 - Subscriptions? -- NO, use `stripe.subscriptions`
294 - Orders/Cart/User preferences? -- YES, these aren't in Stripe
295 - Relationships to Stripe data? -- YES, store Stripe IDs as foreign keys
296
297 **Key Principle: "If it exists in Stripe, it belongs in Stripe"**
298
299 - Don't duplicate Stripe data in your own tables
300 - Don't create parallel storage systems
301 - Stripe + metadata = your complete product catalog
302 - Your tables should only store data that Stripe doesn't manage
303
304 **Creating products:**
305 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.
306
307 **Workflow:**
308
309 ```bash
310 # Development:
311 # - Run seed script to create products in Stripe (test mode)
312 # - syncBackfill() syncs them to local database
313
314 # Deployment (handled by Replit):
315 # - Replit copies products/prices from dev Stripe to prod Stripe
316 # - Your code runs unchanged, syncBackfill() syncs prod database
317
318 # Production:
319 # - Modify products via Stripe Dashboard (scripts can't run in deployments)
320 # - Webhooks automatically sync changes to database
321 ```
322
323 ## Publishing with Stripe
324
325 To publish the app with working Stripe payments:
326
327 1. Go to the Stripe Dashboard (https://dashboard.stripe.com/apikeys)
328 2. Get live API keys (starts with `pk_live_` and `sk_live_`)
329 3. Open the Publish pane in the Workspace
330 4. Enter the live Publishable Key and Secret Key
331 5. Publish the app
332
333 **Placeholder Keys (User-Requested Only):**
334
335 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.
336
337 If the user confirms they want to proceed without live keys, suggest the following values to them:
338 - **Publishable Key:** `pk_live_abcdef`
339 - **Secret Key:** `sk_live_abcdef`
340
341 **Consequences the user must understand:**
342 - Placeholder keys will NOT process real payments
343 - The product catalog and checkout will not function on the published URL
344 - Real Stripe keys must be added later for payments to work
345
346 ## Deleting Stripe Integration
347
348 To remove the Stripe integration from the project:
349
350 1. In the project, open the "Integrations" tab
351 2. Go to "Stripe" and click "Manage"
352 3. Select "Edit" then "Delete"
353
354 ## Secrets and Environment Variables
355
356 **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.
357
358 ## Key Rules
359
360 **DO:**
361 - Create products via Stripe API using scripts (never with SQL INSERT)
362 - Query Stripe data from PostgreSQL `stripe` schema tables (products, prices, customers, subscriptions, etc.)
363 - Store Stripe IDs in application tables as TEXT (e.g., `stripe_customer_id TEXT`)
364 - Keep webhook handler minimal (just call `processWebhook` with payload and signature)
365 - Let `syncBackfill()` sync existing Stripe data on startup
366 - Let managed webhooks handle webhook configuration automatically
367 - Register webhook route BEFORE `express.json()` middleware
368
369 **ABSOLUTELY DO NOT:**
370 - Create any tables in the `stripe` schema - stripe-replit-sync manages this automatically
371 - Insert, update, or delete data in `stripe` schema tables - only query from them
372 - Use SQL INSERT for products, prices, customers, subscriptions - use Stripe API instead
373 - Manually copy database data between environments - Replit handles Stripe product/price copying during deployment
374 - Add custom logic in webhook handler beyond calling `processWebhook`
375 - Skip running `runMigrations()` or `syncBackfill()`
376 - Create StripeSync instance before calling `runMigrations()`
377
378 ## Common Mistakes
379
380 **Database - DO NOT create Stripe tables:**
381
382 ```sql
383 -- WRONG - Creating Stripe tables manually
384 CREATE TABLE products (
385 id TEXT PRIMARY KEY,
386 name TEXT,
387 price INTEGER
388 );
389
390 -- CORRECT - Create application tables, store Stripe IDs
391 CREATE TABLE users (
392 id TEXT PRIMARY KEY,
393 stripe_customer_id TEXT
394 );
395 -- stripe-replit-sync creates stripe.products, stripe.prices, etc. automatically
396 ```
397
398 **Data Creation - Use Stripe API Scripts, NOT SQL:**
399
400 ```sql
401 -- WRONG - Inserting Stripe data with SQL
402 INSERT INTO stripe.products (id, name, description)
403 VALUES ('prod_123', 'Pro Plan', 'Professional subscription');
404 ```
405
406 ```typescript
407 // CORRECT - Use Stripe API in a script
408 const stripe = await getUncachableStripeClient();
409 const product = await stripe.products.create({
410 name: 'Pro Plan',
411 description: 'Professional subscription',
412 });
413 // Webhook syncs this to stripe.products automatically
414 ```
415
416 **Initialization Order:**
417
418 ```typescript
419 // WRONG - Creating StripeSync before migrations
420 const stripeSync = await getStripeSync();
421 await runMigrations({ databaseUrl });
422
423 // CORRECT - Migrations first, then StripeSync
424 await runMigrations({ databaseUrl });
425 const stripeSync = await getStripeSync();
426 await stripeSync.findOrCreateManagedWebhook(...);
427 await stripeSync.syncBackfill();
428 ```
429
430 **Frontend - Must parse JSON responses:**
431
432 ```typescript
433 // WRONG - returns Response object
434 const response = await apiRequest('POST', '/api/checkout', { priceId });
435 return response;
436
437 // CORRECT - parse to get data
438 const response = await apiRequest('POST', '/api/checkout', { priceId });
439 return await response.json();
440 ```
441
442 **Backend - Webhook route ordering:**
443
444 ```typescript
445 // WRONG - Webhook after express.json()
446 app.use(express.json());
447 app.post('/api/stripe/webhook', ...); // Too late! Body already parsed
448
449 // CORRECT - Webhook BEFORE express.json()
450 app.post('/api/stripe/webhook', express.raw({ type: 'application/json' }),
451 async (req, res) => {
452 await WebhookHandlers.processWebhook(req.body, sig);
453 }
454 );
455 app.use(express.json()); // Now apply to other routes
456 ```
457
458 ## References
459
460 - ./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.