---
title: Stripe Plugin
label: Stripe
order: 40
desc: Easily accept payments with Stripe
keywords: plugins, stripe, payments, ecommerce
---

[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-stripe)](https://www.npmjs.com/package/@payloadcms/plugin-stripe)

With this plugin you can easily integrate [Stripe](https://stripe.com) into Payload. Simply provide your Stripe credentials and this plugin will open up a two-way communication channel between the two platforms. This enables you to easily sync data back and forth, as well as proxy the Stripe REST API through Payload's [Access Control](../access-control). Use this plugin to completely offload billing to Stripe and retain full control over your application's data.

For example, you might be building an e-commerce or SaaS application, where you have a `products` or a `plans` collection that requires either a one-time payment or a subscription. You can to tie each of these products to Stripe, then easily subscribe to billing-related events to perform your application's business logic, such as active purchases or subscription cancellations.

To build a checkout flow on your front-end you can either use [Stripe Checkout](https://stripe.com/payments/checkout), or you can also build a completely custom checkout experience from scratch using [Stripe Web Elements](https://stripe.com/docs/payments/elements). Then to build fully custom, secure customer dashboards, you can leverage Payload's Access Control to restrict access to your Stripe resources so your users never have to leave your site to manage their accounts.

The beauty of this plugin is the entirety of your application's content and business logic can be handled in Payload while Stripe handles solely the billing and payment processing. You can build a completely proprietary application that is endlessly customizable and extendable, on APIs and databases that you own. Hosted services like Shopify or BigCommerce might fracture your application's content then charge you for access.

<Banner type="info">
  This plugin is completely open-source and the [source code can be found
  here](https://github.com/payloadcms/payload/tree/main/packages/plugin-stripe). If you need help,
  check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a
  bug, please [open a new
  issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20stripe&template=bug_report.md&title=plugin-stripe%3A)
  with as much detail as possible.
</Banner>

## Core features

- Hides your Stripe credentials when shipping SaaS applications
- Allows restricted keys through [Payload access control](https://payloadcms.com/docs/access-control/overview)
- Enables a two-way communication channel between Stripe and Payload
- Proxies the [Stripe REST API](https://stripe.com/docs/api)
- Proxies [Stripe webhooks](https://stripe.com/docs/webhooks)
- Automatically syncs data between the two platforms

## Installation

Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):

```bash
  pnpm add @payloadcms/plugin-stripe
```

## Basic Usage

In the `plugins` array of your [Payload Config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):

```ts
import { buildConfig } from 'payload'
import { stripePlugin } from '@payloadcms/plugin-stripe'

const config = buildConfig({
  plugins: [
    stripePlugin({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
    }),
  ],
})

export default config
```

### Options

| Option                         | Type               | Default     | Description                                                                                                              |
| ------------------------------ | ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
| `stripeSecretKey` \*           | string             | `undefined` | Your Stripe secret key                                                                                                   |
| `stripeWebhooksEndpointSecret` | string             | `undefined` | Your Stripe webhook endpoint secret                                                                                      |
| `rest`                         | boolean            | `false`     | When `true`, opens the `/api/stripe/rest` endpoint                                                                       |
| `webhooks`                     | object \| function | `undefined` | Either a function to handle all webhooks events, or an object of Stripe webhook handlers, keyed to the name of the event |
| `sync`                         | array              | `undefined` | An array of sync configs                                                                                                 |
| `logs`                         | boolean            | `false`     | When `true`, logs sync events to the console as they happen                                                              |

_\* An asterisk denotes that a property is required._

## Endpoints

The following custom endpoints are automatically opened for you:

| Endpoint               | Method | Description                                                                                                                                                                                                                                |
| ---------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/api/stripe/rest`     | `POST` | Proxies the [Stripe REST API](https://stripe.com/docs/api) behind [Payload access control](https://payloadcms.com/docs/access-control/overview) and returns the result. See the [REST Proxy](#stripe-rest-proxy) section for more details. |
| `/api/stripe/webhooks` | `POST` | Handles all Stripe webhook events                                                                                                                                                                                                          |

##### Stripe REST Proxy

If `rest` is true, proxies the [Stripe REST API](https://stripe.com/docs/api) behind [Payload access control](https://payloadcms.com/docs/access-control/overview) and returns the result. This flag should only be used for local development, see the security note below for more information.

```ts
const res = await fetch(`/api/stripe/rest`, {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    // Authorization: `JWT ${token}` // NOTE: do this if not in a browser (i.e. curl or Postman)
  },
  body: JSON.stringify({
    stripeMethod: 'stripe.subscriptions.list',
    stripeArgs: [
      {
        customer: 'abc',
      },
    ],
  }),
})
```

If you need to proxy the API server-side, use the [stripeProxy](#node) function.

<Banner type="info">
  <strong>Note:</strong>
  <br />
  The `/api` part of these routes may be different based on the settings defined in your Payload
  config.
</Banner>

<Banner type="warning">
  <strong>Warning:</strong>
  <br />
  Opening the REST proxy endpoint in production is a potential security risk. Authenticated users will have open access to the Stripe REST API. In production, open your own endpoint and use the [stripeProxy](#node) function to proxy the Stripe API server-side.
</Banner>

## Webhooks

[Stripe webhooks](https://stripe.com/docs/webhooks) are used to sync from Stripe to Payload. Webhooks listen for events on your Stripe account so you can trigger reactions to them. Follow the steps below to enable webhooks.

Development:

1. Login using Stripe cli `stripe login`
1. Forward events to localhost `stripe listen --forward-to localhost:3000/api/stripe/webhooks`
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`

Production:

1. Login and [create a new webhook](https://dashboard.stripe.com/test/webhooks/create) from the Stripe dashboard
1. Paste `YOUR_DOMAIN_NAME/api/stripe/webhooks` as the "Webhook Endpoint URL"
1. Select which events to broadcast
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`
1. Then, handle these events using the `webhooks` portion of this plugin's config:

```ts
import { buildConfig } from 'payload'
import stripePlugin from '@payloadcms/plugin-stripe'

const config = buildConfig({
  plugins: [
    stripePlugin({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
      stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
      webhooks: {
        'customer.subscription.updated': ({ event, stripe, stripeConfig }) => {
          // do something...
        },
      },
      // NOTE: you can also catch all Stripe webhook events and handle the event types yourself
      // webhooks: (event, stripe, stripeConfig) => {
      //   switch (event.type): {
      //     case 'customer.subscription.updated': {
      //       // do something...
      //       break;
      //     }
      //     default: {
      //       break;
      //     }
      //   }
      // }
    }),
  ],
})

export default config
```

For a full list of available webhooks, see [here](https://stripe.com/docs/cli/trigger#trigger-event).

## Node

On the server you should interface with Stripe directly using the [stripe](https://www.npmjs.com/package/stripe) npm module. That might look something like this:

```ts
import Stripe from 'stripe'

const stripeSecretKey = process.env.STRIPE_SECRET_KEY
const stripe = new Stripe(stripeSecretKey, { apiVersion: '2022-08-01' })

export const MyFunction = async () => {
  try {
    const customer = await stripe.customers.create({
      email: data.email,
    })

    // do something...
  } catch (error) {
    console.error(error.message)
  }
}
```

Alternatively, you can interface with the Stripe using the `stripeProxy`, which is exactly what the `/api/stripe/rest` endpoint does behind-the-scenes. Here's the same example as above, but piped through the proxy:

```ts
import { stripeProxy } from '@payloadcms/plugin-stripe'

export const MyFunction = async () => {
  try {
    const customer = await stripeProxy({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
      stripeMethod: 'customers.create',
      stripeArgs: [
        {
          email: data.email,
        },
      ],
    })

    if (customer.status === 200) {
      // do something...
    }

    if (customer.status >= 400) {
      throw new Error(customer.message)
    }
  } catch (error) {
    console.error(error.message)
  }
}
```

## Sync

This option will setup a basic sync between Payload collections and Stripe resources for you automatically. It will create all the necessary hooks and webhooks handlers, so the only thing you have to do is map your Payload fields to their corresponding Stripe properties. As documents are created, updated, and deleted from either Stripe or Payload, the changes are reflected on either side.

<Banner type="info">
  <strong>Note:</strong>
  <br />
  If you wish to enable a _two-way_ sync, be sure to setup [`webhooks`](#webhooks) and pass the
  `stripeWebhooksEndpointSecret` through your config.
</Banner>

```ts
import { buildConfig } from 'payload'
import stripePlugin from '@payloadcms/plugin-stripe'

const config = buildConfig({
  plugins: [
    stripePlugin({
      stripeSecretKey: process.env.STRIPE_SECRET_KEY,
      stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
      sync: [
        {
          collection: 'customers',
          stripeResourceType: 'customers',
          stripeResourceTypeSingular: 'customer',
          fields: [
            {
              fieldPath: 'name', // this is a field on your own Payload Config
              stripeProperty: 'name', // use dot notation, if applicable
            },
          ],
        },
      ],
    }),
  ],
})

export default config
```

<Banner type="warning">
  <strong>Note:</strong>
  <br />
  Due to limitations in the Stripe API, this currently only works with top-level fields. This is
  because every Stripe object is a separate entity, making it difficult to abstract into a simple
  reusable library. In the future, we may find a pattern around this. But for now, cases like that
  will need to be hard-coded.
</Banner>

Using `sync` will do the following:

- Adds and maintains a `stripeID` read-only field on each collection, this is a field generated _by Stripe_ and used as a cross-reference
- Adds a direct link to the resource on Stripe.com
- Adds and maintains an `skipSync` read-only flag on each collection to prevent infinite syncs when hooks trigger webhooks
- Adds the following hooks to each collection:
  - `beforeValidate`: `createNewInStripe`
  - `beforeChange`: `syncExistingWithStripe`
  - `afterDelete`: `deleteFromStripe`
- Handles the following Stripe webhooks
  - `STRIPE_TYPE.created`: `handleCreatedOrUpdated`
  - `STRIPE_TYPE.updated`: `handleCreatedOrUpdated`
  - `STRIPE_TYPE.deleted`: `handleDeleted`

## TypeScript

All types can be directly imported:

```ts
import {
  StripeConfig,
  StripeWebhookHandler,
  StripeProxy,
  ...
} from '@payloadcms/plugin-stripe/types';
```

## Examples

The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) contains an official [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. You can also check out [How to Build An E-Commerce Site With Next.js](https://payloadcms.com/blog/how-to-build-an-e-commerce-site-with-nextjs) post for a bit more context around this template.
