obsidian/wiki/payloadcms/plugin-stripe.md
2026-05-15 16:27:02 +01:00

158 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 1020 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