Skip to content

API Reference

chris edited this page Jun 4, 2024 · 11 revisions

The plugin library is divided into a web side and an api side. You'd import methods from each side depending on where you need them in your app (RedwoodJS documentation on "sides"):

// web-side
import { useStripeCheckout } from '@redwoodjs-stripe/web';

// api-side
import { createStripeCustomer } from '@redwoodjs-stripe/api';

Web-side API Reference

StripeProvider

Uses React context to manage the StripeCart, store customer data and contain the plugin logic on the web side. You can choose to not use the provider but then you would have to manually manage customers when checking out and use your own cart.

To use the provider wrap the store components or layout in the <StripeProvider/>. Put the Provider as high up in your "React DOM tree" as possible.

Example

import { StripeProvider } from '@redwoodjs-stripe/web';

// ...

<StripeProvider
  customer={{
    search: isAuthenticated ? `email: "${emailFromAuthUser}"` : '',
  }}
>
  <Storefront />
</StripeProvider>;

User Mapping

User mapping is managed via the customer object passed to the <StripeProvider/>.

customer has the following shape:

type Customer = {
  search: string | '';
  id: string | '';
};

search accepts a Search API query that is used to search for a Stripe Customer that matches the authenticated user's search string. In most cases your authentication provider will provide you with an email address that you will be able to use to build a search query string to map to a Stripe Customer. Note that a 'search' can only be used when a user is logged in. If nobody is logged in pass '' or do not pass a search parameter to the customer object.

In other cases you can pass get the Stripe Customer ID yourself and add that to the customer object. This is an example from the example-store-stripe repo. The Example Store uses DBAuth for authentication. The Stripe Customer Id is saved under stripeId in the User table. All the logic is on the backend for security. Some services are available to import, but more on that in the API Side Reference. This could look different depending on your authentication method.

Example

api/src/services/users/users.js

import {
  stripeCustomerSearch,
  createStripeCustomer,
} from '@redwoodjs-stripe/api'

// ...

export const addStripeId = async ({ id: userId }) => {
  let stripeId = ''
  const { email } = await getCustomerEmail({ id: userId })

  // get customerID from Stripe using email
  const customer = await stripeCustomerSearch({
    query: `email: \"${email}\"`,
  })

  if (customer == undefined) {
    const newCustomer = await createStripeCustomer({ data: { email: email } })
    stripeId = newCustomer.id
  } else {
    stripeId = customer.id
  }

  return await updateStripeId({
    id: userId,
    stripeId: stripeId,
  })
}

web/src/layouts/MainLayout/MainLayout.js

import { StripeProvider } from '@redwoodjs-stripe/web'

// ...
const { isAuthenticated } = useAuth()

const customerId = isAuthenticated ? getStripeID() : ''

// ...

return (
    <StripeProvider customer={{ id: customerId }}>
     // ...
    </StripeProvider>
 

Stripe Checkout hook

useStripeCheckout()

Hook to redirect to Stripe's hosted Checkout.

Returns an object containing a checkout function. See the comments in the example below for more information on its usage.

Example

import { useStripeCheckout } from '@redwoodjs-stripe/web';

// ...

const { checkout } = useStripeCheckout();

Checkout

checkout()

Creates a Stripe Checkout Session and redirects to the Stripe Checkout page.

await checkout({
  successUrl:
    'http://localhost:8910/stripe-demo?success=true&sessionId={CHECKOUT_SESSION_ID}',
  cancelUrl: 'http://localhost:8910/stripe-demo?success=false',
});

session has the following shape:

type Session = {
  mode?: string; // optional if cart items have 'type' as part their objects, else defaults to 'once-off'
  cart?: Product[]; // 'cart' is not required when using built-in cart
  customer: string; // optional when using user mapping via provider
  cancelUrl: string;
  successUrl: string;
};

Stripe Cart hook

useStripeCart()

Hook to manage the Stripe Cart. Returns an object with the cart api (described below).

Example

import { useStripeCart } from '@redwoodjs-stripe/web';
// ...
const { cart, clearCart, addToCart, removeFromCart, editCartItem } =
  useStripeCart();

Cart API

List cart items

cart

An array of StripeItem objects. Each item in the array has the following shape:

type StripeItem = {
  id: string;
  name?: string;
  images?: string[];
  description?: string;
  price?: number;
  type?: string;
};

Each object must contain an id. This id corresponds to the StripePrice's id when using the generated StripeProductsCell.

If you're checking out with both recurring and once-off items, type needs to be included in the object.

Remove cart items

clearCart()

Removes all items from cart.

  clearCart()
Add item to cart

addToCart(product)

Adds a Product to the cart.

addToCart({
  id: 'price_12345',
  // ...
});
Remove item from cart

removeFromCart(id)

Removes an item from the cart via the item's id.

removeFromCart('price_12345');
Edit cart item

editCartItem(id, details)

Edits the cart item. For example, to change the quantity:

editCartItem('price_12345', { quantity: 4 });

details has the following shape:

type Details = {
  quantity?: number;
};

Stripe Customer Portal hook

useStripeCustomerPortal()

Hook for Stripe's Customer Portal logic. Requires a customer search query to have been passed to the <StripeProvider/>. In other words, an authenticated user.

Returns an object containing a redirectToStripeCustomerPortal function.

import { useStripeCustomerPortal } from '@redwoodjs-stripe/web';
// ...
const { redirectToStripeCustomerPortal } = useStripeCustomerPortal();

Redirect to Stripe Customer Portal

redirectToStripeCustomerPortal(portalSession, skipAuth = false)

Creates a Stripe Customer Portal Session and redirects to Stripe's Customer Portal page.

await redirectToStripeCustomerPortal(
  {
    return_url: 'http://localhost:8910/stripe-demo',
  },
  true
);

portalSession has the following shape (Stripe Customer Portal API reference):

type PortalSession = {
  configuration?: config;
  locale?: string;
  on_behalf_of?: string;
  return_url: string;
};

Set the second parameter, skipAuth, to true when testing the Customer Portal redirect if you have not yet set up user authentication in your app. Once authentication is set up please remove the argument.

Create Stripe Portal Configuration

createStripeCustomerPortalConfig(portalConfig)

Creates a Customer Portal Configuration object.

```ts
type PortalConfig = {
  // TODO
}

StripeItemsCell

<StripeItemsCell params={priceParams, productParams}/>

As part of the setup process, a <StripeProductsCell/> gets generated inside of your app's components folder.

This Cell returns a list of StripeItems unique to this plugin. It uses a combination of data from Stripe Prices and Stripe Products. The data is combined as follows:

{
  id: stripePrice.id,
  name: stripeProduct.name,
  description: stripeProduct.description,
  images: stripeProduct.images,
  price: stripePrice.unit_amount,
  type: stripePrice.type,
}

The params passed to the Cell are the same parameters that would be passed to Stripe's Products and Prices list functions. The following example shows how to get a list of Products from active Stripe Products that have recurring Stripe Prices. In other words, this example shows a Cell that returns a list of active subscriptions.

import StripeItemsCell from 'src/components/StripeItemsCell/StripeItemsCell';

// ...

<StripeItemsCell
  params={{
    productParams: { active: true },
    priceParams: { type: 'recurring' },
  }}
/>;

priceParams has the following shape (for more information, look at the Stripe Price list API documentation)

type PriceParams = {
  active?: boolean;
  currency?: string;
  product?: string;
  created?: stripeCreated;
  ending_before?: string;
  limit?: number;
  lookup_keys?: string;
  recurring?: stripeRecurringPrice;
  starting_after?: string;
  type?: string;
};

productParams has the following shape (for more information, look at the Stripe Products list API documentation)

type ProductParams = {
  active?: boolean;
  created?: stripeCreated;
  ending_before?: string;
  ids?: string[];
  limit?: number;
  shippable?: boolean;
  starting_after?: string;
  url?: string;
};

The Cell has it's own UI for illustrative purpose. You should replace the UI with your own.

Api-side API Reference

Webhook handling

As part of the setup process, a /stripeWebhook function is generated. It uses the plugin handler function to verify and manage webhook events.

Stripe Webhook Handler

handleStripeWebhooks(event, context, webhookObject, verify = false)

This function verifies and manages Stripe webhook events.

import { handleStripeWebhooks } from '@redwoodjs-stripe';
// ...
const { results } = await handleStripeWebhooks(event, context, {
  'customer.updated': async (e) => {
    updateUser(e);
  },
  'payment_intent.succeeded': (e) => {
    sendCustomerThankYouEmail(e);
  },
});

webhookObject is an object mapping webhook event names to handler functions. Each handler function accepts the relevant event object as input.

webhook signature verification is enabled by default, and can be disabled by providing false for the verify parameter.

Installing Stripe CLI

To use webhooks and Stripe webhook events, we recommend installing the Stripe CLI.

After logging in you'll be given a webhook secret. Make sure it matches the STRIPE_WEBHOOK_SK key in your .env file

Testing Stripe webhooks locally

  1. Listen for webhook events locally

stripe listen --forward-to localhost:8911/stripeWebhook

If you'd prefer to use snakecase, then you can just change api/src/functions/stripeWebhook/stripeWebhook.js to api/src/functions/stripe_webhook/stripe_webhook.js and the other files in that folder

  1. Trigger webhook events locally via the CLI

stripe trigger customer.updated

Services

Services used in the plugin can also be imported into your project to build custom Services for more-complicated use cases.

Create Stripe Checkout Session

createStripeCheckoutSession(session)

Creates a Stripe Checkout Session and returns a Session object.

import { createStripeCheckoutSession } from '@redwoodjs-stripe/api';
// ...
const newSession = await createStripeCheckoutSession({
  mode: 'payment',
  cart: cartItems,
});

session has the following shape:

type Session = {
  customer?: Customer = {};
  mode: string;
  cart: CartItem[];
  successUrl: string = 'http://localhost:8910/stripe-demo?success=true&sessionId={CHECKOUT_SESSION_ID}';
  cancelUrl: string = 'http://localhost:8910/stripe-demo?success=false';
};

type Customer = {
  id: string;
  name?: string;
  email?: string;
};

type CartItem = {
  id: string;
  quantity: number;
};

Search for last added Stripe Customer

searchLatestStripeCustomer(string)

Accepts a Search API query and returns the Customer object of the last created Customer that matches the search query.

import { searchLatestStripeCustomer } from '@redwoodjs-stripe/api';
// ...
const customer = await searchLatestStripeCustomer('email: "user@test.com"');

Create new Stripe Customer

createStripeCustomer(customer)

Accepts an object with data to create a new Stripe customer and returns the newly created Stripe Customer object.

import { createStripeCustomer } from '@redwoodjs-stripe/api';
// ...
const customer = await createStripeCustomer({ email });

Schemas

Schemas are imported from the plugin. You can view the sdl files here. Although I tried to have the sdls match the Stripe objects, there may be times you need something more. For that reason my sdl files follow a naming convention so that you can add your own custom Stripe sdl files without there being a conflict.

The following Stripe API objects have sdl files:

CLI Reference

The plugin comes with a few commands that can be accessed via the @redwoodjs-stripe/cli package.

npx @redwoodjs-stripe/cli@latest <command>

Custom Stripe API Calls

If the plugin in does not have what you need, it does not take away your ability to create your own Stripe API calls. In fact it makes it a little bit easier. Theplugin sdl already has defined some of the Stripe objects. There's already some services that you can use. You can import the stripe from the plugin for the api side. It will already have your Stripe API keys saved do no need to reinvent the wheel.

Example if you needed to get a Stripe invoice. It is not currently supported in the plugin so you would need to create your own Stripe API call.

First set up the api side.

  1. Create custom service api/src/services/invoices.js
import { stripe } from '@redwoodjs-stripe/api'

export const invoice = async ({ id }) => {
  const res = await stripe.list.invoices.retrieve(id)
  return res
}
  1. Create custom sdl api/src/graphql/invoices.sdl.js
export const schema = `
  type invoice {
    id: ID!
    customer: String
    description: String
    payment_intent: String
  }
`
  1. Create custom query api/src/graphql/invoices.sdl.js
  //...

  type Query {
    invoice(id: ID!): invoice @requireAuth
  }

Note: query name matches service name

All done on the api side. Now to set up the web side

This example will use Redwoodjs' Cells to display the data. There are other ways to trigger api calls that are free to use. Cells are just a bit easier because they manage the view (UI) according to the state of your API call. If you used the plugin setup command then you will have an example of a Cell at web/src/components/StripeItemsCell/StripeItemsCell.js (you can also find it here)

  1. Generate a <Cell/>.
yarn rw g cell Invoice
  1. Import the <InvoiceCell/> wherever you need the UI to show. web/src/pages/HomePage/HomePage.js
import InvoiceCell from 'src/components/InvoiceCell'

const HomePage = () => {
  return (
    <div>
      <h1>Home</h1>
      <InvoiceCell id={'in_1NiCEu2eZvKYlo2CQgw5IdpD'} />
    </div>
  )
}

export default HomePage
  1. Define a query for fetching an invoice inside the Cell. web/src/components/InvoiceCell/InvoiceCell.js
export const QUERY = gql`
  query($id: ID!) {
    invoice(id: $id) {
      description
      customer
    }
  }
`
  1. Define UI based on status. web/src/components/InvoiceCell/InvoiceCell.js
  export const Loading = () => <div>Loading...</div>

export const Empty = () => <div>Could not find the invoice you are looking for</div>

export const Failure = ({ error }) => (
  <div>Error loading invoice: {error.message}</div>
)

export const Success = ({ invoice }) => {
  return(
    <article>
      <h3>Invoice for customer:{invoice.customer}
      <p>{invoice.description}</p>
    </article>
  )
}

And that's that

Setup

Sets up the plugin by copying boilerplate and creating a demo store.

npx @redwoodjs-stripe/cli@latest setup

Upgrade

Installs the latest version of the api and web sides of the plugin

npx @redwoodjs-stripe/cli@latest upgrade