From 865b8a5c69e71e707b6559ea21a37f3543ea7efe Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Fri, 15 May 2026 16:27:02 +0100 Subject: [PATCH] vault backup: 2026-05-15 16:27:02 --- raw/{ => _processed}/plugins__seo.md | 0 wiki/_master-index.md | 2 +- wiki/payloadcms/_index.md | 2 + wiki/payloadcms/plugin-stripe.md | 158 +++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 1 deletion(-) rename raw/{ => _processed}/plugins__seo.md (100%) create mode 100644 wiki/payloadcms/plugin-stripe.md diff --git a/raw/plugins__seo.md b/raw/_processed/plugins__seo.md similarity index 100% rename from raw/plugins__seo.md rename to raw/_processed/plugins__seo.md diff --git a/wiki/_master-index.md b/wiki/_master-index.md index 468bbf3..812e032 100644 --- a/wiki/_master-index.md +++ b/wiki/_master-index.md @@ -35,7 +35,7 @@ This 3-hop pattern works for hundreds of articles without vector search. | [[wiki/reports/_index\|reports/]] | Weekly and monthly summaries — generate: `uv run python scripts/report-generator.py --weekly` | 1 | | [[wiki/infrastructure/_index\|infrastructure/]] | Server inventory: all 10 SSH hosts — optical, optical-dev, optical-prod, baic, librechat, modocmms, box-cli, aimpress, pve | 12 | | [[wiki/testing/_index\|testing/]] | Web app testing: functional, performance, security, UI types; TDD/BDD/Agile methodologies; Selenium/Cypress/Playwright/JMeter/OWASP ZAP tools | 1 | -| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 120 | +| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 121 | | [[wiki/shared-patterns/_index\|shared-patterns/]] | Oliver Agency standard library patterns: httpx, structlog, pydantic-settings, alembic — reuse before writing from scratch | 4 | | [[wiki/mistakes/_index\|mistakes/]] | Anti-patterns extracted from sessions — per-stack running lists (fastapi, react, docker, postgres, general) — injected at session start | 5 | diff --git a/wiki/payloadcms/_index.md b/wiki/payloadcms/_index.md index d1e0280..657a5c6 100644 --- a/wiki/payloadcms/_index.md +++ b/wiki/payloadcms/_index.md @@ -122,3 +122,5 @@ | [[wiki/payloadcms/plugin-redirects\|Redirects Plugin]] | Managed `redirects` collection (from/to + doc reference), redirect types (301/302), overrides pattern, frontend integration (Next.js middleware/redirects()) | raw/plugins__redirects.md | 2026-05-15 | | [[wiki/payloadcms/plugin-search\|Search Plugin]] | First-party search index: static `search` collection, hook-free queries, priority sorting, `beforeSync`/`skipSync`, on-demand reindex | raw/plugins__search.md | 2026-05-15 | | [[wiki/payloadcms/plugin-sentry\|Sentry Plugin]] | Sentry error tracking + performance monitoring: Next.js setup prerequisite, Postgres pg driver injection for DB traces, captureErrors, context callback | raw/plugins__sentry.md | 2026-05-15 | +| [[wiki/payloadcms/plugin-seo\|SEO Plugin]] | `meta` field group (title/description/image) on collections/globals, auto-generate functions, real-time search preview, custom fields (og:title, json-ld), tabbedUI gotcha, direct field imports | raw/plugins__seo.md | 2026-05-15 | +| [[wiki/payloadcms/plugin-stripe\|Stripe Plugin]] | Two-way Stripe↔Payload sync, webhook handlers, REST proxy (dev-only), `sync` auto-hooks, serverless `waitUntil` fix, `skipSync` loop prevention | raw/plugins__stripe.md | 2026-05-15 | diff --git a/wiki/payloadcms/plugin-stripe.md b/wiki/payloadcms/plugin-stripe.md new file mode 100644 index 0000000..ab29f93 --- /dev/null +++ b/wiki/payloadcms/plugin-stripe.md @@ -0,0 +1,158 @@ +--- +title: "Stripe Plugin" +aliases: [payload-stripe, plugin-stripe, stripe-payments-payload] +tags: [payloadcms, stripe, payments, ecommerce, plugin, webhooks, sync] +sources: [raw/plugins__stripe.md] +created: 2026-05-15 +updated: 2026-05-15 +--- + +# Stripe Plugin + +Integrates [Stripe](https://stripe.com) billing into Payload CMS — two-way sync, webhook handling, and a proxied Stripe REST API behind Payload access control. + +## Installation + +```bash +pnpm add @payloadcms/plugin-stripe +``` + +## Basic Config + +```ts +import { stripePlugin } from '@payloadcms/plugin-stripe' + +buildConfig({ + plugins: [ + stripePlugin({ + stripeSecretKey: process.env.STRIPE_SECRET_KEY, + }), + ], +}) +``` + +## Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `stripeSecretKey` * | string | — | Stripe secret key | +| `stripeWebhooksEndpointSecret` | string | — | Webhook signing secret | +| `rest` | boolean | `false` | Open `/api/stripe/rest` proxy (dev only) | +| `webhooks` | object \| function | — | Webhook event handlers | +| `sync` | array | — | Auto-sync configs between collections and Stripe resources | +| `logs` | boolean | `false` | Log sync events to console | + +## Auto-Opened Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/stripe/rest` | POST | Proxied Stripe REST API (behind Payload access control) | +| `/api/stripe/webhooks` | POST | Stripe webhook receiver | + +## Webhooks Setup + +**Dev:** +```bash +stripe login +stripe listen --forward-to localhost:3000/api/stripe/webhooks +# paste the secret into .env as STRIPE_WEBHOOKS_ENDPOINT_SECRET +``` + +**Production:** Create webhook in Stripe dashboard → set URL to `YOUR_DOMAIN/api/stripe/webhooks` → paste secret into `.env`. + +**Handler config:** +```ts +stripePlugin({ + stripeSecretKey: process.env.STRIPE_SECRET_KEY, + stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET, + webhooks: { + 'customer.subscription.updated': ({ event, stripe, stripeConfig }) => { + // handle event + }, + }, +}) +``` + +### Serverless Warning + +Stripe expects a `2xx` response within 10–20 seconds. The plugin processes webhooks asynchronously — in serverless environments the function instance may close before processing completes, causing duplicate events. + +**Vercel fix:** install `@vercel/functions` — the plugin auto-detects it and wraps handlers in `waitUntil()`. + +## Sync — Two-Way Auto-Sync + +Maps Payload collection fields to Stripe resource properties. Hooks + webhook handlers are created automatically. + +```ts +stripePlugin({ + sync: [ + { + collection: 'customers', + stripeResourceType: 'customers', + stripeResourceTypeSingular: 'customer', + fields: [ + { fieldPath: 'name', stripeProperty: 'name' }, + ], + }, + ], +}) +``` + +**What `sync` does automatically:** +- Adds read-only `stripeID` field (Stripe-generated cross-reference) +- Adds direct link to resource on Stripe.com in admin +- Adds read-only `skipSync` flag to prevent infinite loops +- Attaches hooks: `beforeValidate: createNewInStripe`, `beforeChange: syncExistingWithStripe`, `afterDelete: deleteFromStripe` +- Handles webhooks: `STRIPE_TYPE.created/updated/deleted` + +> **Limitation:** Only top-level fields supported (Stripe API constraint). + +## Server-Side Usage + +Prefer direct Stripe SDK on server rather than the REST proxy: + +```ts +import Stripe from 'stripe' +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2022-08-01' }) +const customer = await stripe.customers.create({ email: data.email }) +``` + +Or use `stripeProxy` from the plugin: + +```ts +import { stripeProxy } from '@payloadcms/plugin-stripe' +const customer = await stripeProxy({ + stripeSecretKey: process.env.STRIPE_SECRET_KEY, + stripeMethod: 'customers.create', + stripeArgs: [{ email: data.email }], +}) +``` + +## TypeScript + +```ts +import { StripeConfig, StripeWebhookHandler, StripeProxy } from '@payloadcms/plugin-stripe/types' +``` + +## Key Takeaways + +- **Hides Stripe credentials** — client-side calls go through Payload's access control, never directly to Stripe +- **REST proxy (`rest: true`) is dev-only** — opening it in production gives authenticated users full Stripe API access +- **Two-way sync requires webhooks** — set `stripeWebhooksEndpointSecret` or changes from Stripe won't propagate to Payload +- **Serverless + webhooks = `@vercel/functions`** — without it, async webhook handlers may be killed before completing +- **`sync` only works on top-level Stripe fields** — nested fields require manual hooks +- **`skipSync` flag prevents infinite loops** — sync hooks set it before triggering webhooks, webhooks check it before re-syncing +- For e-commerce, pair with [[wiki/payloadcms/ecommerce|Ecommerce Plugin]] or [[wiki/payloadcms/ecommerce-payment-adapters|Payment Adapters]] + +## Related + +- [[wiki/payloadcms/plugins|Plugins Overview + Official]] +- [[wiki/payloadcms/ecommerce|Ecommerce Plugin]] +- [[wiki/payloadcms/ecommerce-payment-adapters|Ecommerce — Payment Adapters]] +- [[wiki/payloadcms/access-control|Access Control]] +- [[wiki/payloadcms/hooks-collections|Collection Hooks]] + +## Sources + +- `raw/plugins__stripe.md` +- https://payloadcms.com/docs/plugins/stripe