UNPKG

skills-ws

Version:

87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.

114 lines (83 loc) • 3.26 kB
## Contents - Common Mistakes - 1. Parsing JSON Before Webhooks - 2. Provisioning on Success URL Instead of Webhooks - 3. Not Handling Idempotency - 4. Storing API Keys in Plaintext - 5. Not Pinning Stripe API Version - 6. Ignoring pastdue Status - 7. Not Expanding Objects in Webhook Handlers - 8. Hardcoding Price IDs - 9. Not Handling Trial Expiration - 10. Race Conditions Between Webhooks ## Common Mistakes ### 1. Parsing JSON Before Webhooks **Wrong:** ```js app.use(express.json()); // This parses ALL requests including webhooks app.post('/webhooks/stripe', handleWebhook); // Signature verification WILL FAIL ``` **Right:** ```js app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), handleWebhook); app.use(express.json()); // After webhook route ``` ### 2. Provisioning on Success URL Instead of Webhooks **Wrong:** ```js app.get('/success', async (req, res) => { await activateSubscription(req.query.session_id); // User closes tab = no provisioning }); ``` **Right:** Provision in `checkout.session.completed` webhook. Success URL is just a thank-you page. ### 3. Not Handling Idempotency **Wrong:** ```js case 'checkout.session.completed': await createAccount(data); // Duplicate event = duplicate account! ``` **Right:** Check `processed_events` table before acting. Use `INSERT ... ON CONFLICT DO NOTHING` or similar. ### 4. Storing API Keys in Plaintext **Wrong:** ```sql INSERT INTO api_keys (key) VALUES ('sk_live_actual_key_here'); ``` **Right:** Store SHA-256 hash. Show the key once at creation. User must regenerate if lost. ### 5. Not Pinning Stripe API Version **Wrong:** ```js const stripe = require('stripe')(key); // Uses latest version — may break unexpectedly ``` **Right:** ```js const stripe = require('stripe')(key, { apiVersion: '2026-06-24.dahlia' }); ``` ### 6. Ignoring `past_due` Status If a payment fails, the subscription goes `past_due`. Many apps only check for `active` and immediately cut off access. This frustrates customers who just have an expired card. **Right:** Implement grace periods. Send dunning emails. Give them time to update payment info. ### 7. Not Expanding Objects in Webhook Handlers ```js // The webhook event only contains IDs, not full objects // If you need product metadata, retrieve with expand: const subscription = await stripe.subscriptions.retrieve(data.id, { expand: ['items.data.price.product'], }); ``` ### 8. Hardcoding Price IDs **Wrong:** ```js const PRICE_ID = 'price_1234567890'; // Breaks between test/live, fragile ``` **Right:** Use environment variables, lookup keys, or metadata: ```js const prices = await stripe.prices.list({ lookup_keys: ['pro_monthly'], limit: 1, }); const priceId = prices.data[0].id; ``` ### 9. Not Handling Trial Expiration Trials end and `customer.subscription.updated` fires with `status: 'active'` (if payment succeeds) or `status: 'past_due'` (if it fails). Many devs forget to handle the failure case, leaving trialing users with indefinite free access. ### 10. Race Conditions Between Webhooks Stripe doesn't guarantee event ordering. You might receive `customer.subscription.updated` before `checkout.session.completed`. Design handlers to be independent and idempotent. ---