How to accept payments in a Remix application with Stripe
Tags: remix, stripe, payments, tutorial
Published: 2022-04-12 20:50:00

> Remix is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience. - Remix.run. 

We can quickly add Stripe into a Remix application to start accepting one time or recurring payments.

There are several options for accepting payments with Stripe that range in implementation complexity. This article covers the most involved integration path with Stripe Elements, but I wanted to share a few shorter-paths which you should choose if you’re able to let Stripe do the heavy lifting. 

**NB**: No matter which option you chose, you’ll want a webhook handler to automate fulfillment. Here’s [code for a Remix action to setup a webhook handler](https://gist.github.com/cjavilla-stripe/241682c549292bc21165744401d38793) for Stripe webhooks.

## No-code

[Stripe Payment Links](https://stripe.com/payments/payment-links) is a no-code option where you create a link directly from the Stripe Dashboard that you can share with customers via email or on social. Payment Links are great if you don’t need to customize the payment flow per-customer and they can be used to accept both one-time and recurring payments. **Pick Payment Links if you don’t need to keep track of or customize each payment flow per-customer.**


## Low-code

[Stripe Checkout](https://stripe.com/payments/checkout) is an option where you use one API call server-side to customize the payment flow per customer and redirect to Stripe. You can also customize the look and feel to pretty closely match your colors, fonts, and things like border-radius. **Pick Checkout if you don’t need full control over the payment form inputs.** 


## The most code

[Stripe Elements](https://stripe.com/payments/elements) is the option we’ll discuss today. Elements are pre-built form components that you can embed directly into your web application (there’s [mobile](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=payment-sheet) versions too!). There’s an official [react-stripe-js](https://github.com/stripe/react-stripe-js) library that exposes the new PaymentElement. The PaymentElement supports several payment methods and is the best default. You should only implement individual elements (like one for Card and one for SEPA) if you need some very specific functionality, otherwise, consider individual elements *legacy*. **Pick PaymentElement if you need to full control over the payment form.**

**Summary**:  If the choice still isn’t clear, take a look at Charles’ “[Making sense of Stripe Checkout, Payment Links, and the Payment Element.](https://dev.to/stripe/making-sense-of-stripe-checkout-payment-links-and-the-payment-element-23o5)” My recommendation is to start with PaymentLinks, if that’s not feature rich enough graduate to Checkout, if that’s not enough control, step up into PaymentElement and read on!

----------

Pop open that terminal and let’s install dependencies. 


- `stripe` is stripe-node for server side API calls to Stripe. 
- `@stripe/stripe-js` helps load Stripe.js, a client side library for working with payment details. 
- `@stripe/react-stripe-js` contains all the React-specific components and hooks that help with integrating Stripe. This is also a client-side library and depends on `@stripe/stripe-js`.


npm add stripe @stripe/stripe-js @stripe/react-stripe-js

Set your secret API key as an environment variable in `.env` in the root of your project. You can find your API keys in the [Stripe dashboard](https://dashboard.stripe.com/test/apikeys):

```
STRIPE_SECRET_KEY=sk_test...
```


Let’s get into the code!

We’ll create a new route called `/pay` with two nested UI components: one for the payment form and one for the order confirmation page where we show a success message after payment.

Start by adding these files and directories to routes:

```
  app/
    - routes/
      - pay/
          - index.tsx
          - success.tsx
      pay.tsx
```

Then let’s tackle the outer structure of the payment flow with `pay.tsx`. 

In order to render any Stripe Elements in our React application, we need to use the Elements provider. The Elements provider wraps any subcomponents that render Stripe Elements. It expects a `stripe` prop and since we’re using the PaymentElement, we also need to pass an `options` prop.

```jsx
<Elements stripe={stripePromise} options={{clientSecret: paymentIntent.client_secret}}>
  Some subcomponents that will render Stripe Elements... more on this later.
</Elements>
```

Okay, but you’ll notice that code uses two things we don’t know about yet: `stripePromise` and `paymentIntent`. Read on, friend!

The `stripe` prop wants either reference to a fully initialized instance of the Stripe.js client, or a promise that will eventually resolve to one. Luckily, `@stripe/stripe-js` has a helper for this called `loadStripe` where we pass in our publishable API key and get back a promise.

```js
const stripePromise = loadStripe('pk_test...')
```

One down, one to go, but first, some context.


In order to render a `PaymentElement` with all the bells and whistles like multiple payment methods, localization, and the correct currency support, it needs a `PaymentIntent` or a `SetupIntent`. For now, we only need to know that we need one of those, but if you want to read more, take a look at "[Level up your integration with SetupIntents](https://dev.to/stripe/level-up-your-integration-with-setupintents-2fl4)”  or “[The PaymentIntents Lifecycle](https://dev.to/stripe/the-paymentintents-lifecycle-4f5o)” by [Paul](https://twitter.com/paul_asjes).

To keep the code nice and organized in Remix, let’s create a new file in the `app/` directory called `payments.ts` that will serve as our container for all API interactions to Stripe.

```
    app/
      - routes/
          - pay/
              - index.tsx
              - success.tsx
          pay.tsx
      - payments.ts
```


We need to make an API call to Stripe to create a PaymentIntent so that we can use the resulting PaymentIntent to initialize our Elements provider and ultimately render the PaymentElement.

Let’s get stripe-node initialized and ready to make calls with our secret key from the .env. Remix differs a bit from Next.js in how they handle environment variables that are used on the client side. [The Remix docs](https://remix.run/docs/en/v1/guides/envvars#browser-environment-variables) have an example showing how to pass environment variables to the client. Incidentally, the example is for Stripe publishable (public) keys!

```js
import Stripe from 'stripe'
    
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
```

Next, we’ll export a function that we can use from loaders in our views.


```js
export async function createPaymentIntent() {
  return await stripe.paymentIntents.create({
    amount: 2000,
    currency: 'usd',
    automatic_payment_methods: {
      enabled: true
    }
  })
}
```


`stripe.paymentIntents.create` makes an API call to Stripe’s `/v1/payment_intents` endpoint with some args. Let’s talk about each of those:

- `amount` this is the amount in the smallest currency denomination. In this case cents, so we’re hoping to eventually charge $20.
- `currency` the currency the customer will pay with.
- `automatic_payment_methods` Stripe supports more than just credit card payments. You can accept cards, but also bank accounts, buy-now-pay-later, and vouchers too! This argument will enable the PaymentIntent to derive the list of supported payment method types from the [settings in your Stripe dashboard](https://dashboard.stripe.com/settings/payment_methods). This means you can enable and disable different types of payment directly in the dashboard instead of needing to change any code. This is 100% a best practice! Note: passing `payment_method_types` is legacy, prefer `automatic_payment_methods`. 

Let’s head back to our Payment form and use the resulting PaymentIntent’s client_secret in our options.

By exporting a loader, we can create a payment intent that’s available when we render our component later:

```js
export const loader = async () => {
  return await createPaymentIntent()
}
```

Then we can pull in the `useLoaderData` hook from Remix and use that to access the PaymentIntent in our component. Now our incomplete Pay component looks like this:

```js
import {useLoaderData} from 'remix'
import {Elements} from '@stripe/react-stripe-js'
import {loadStripe} from '@stripe/stripe-js'

const stripePromise = loadStripe('pk_test...');

export default function Pay() {
  const paymentIntent = useLoaderData();

  return (
    <Elements
      stripe={stripePromise}
      options={{clientSecret: paymentIntent.client_secret}}>
      Seriously, when are we going to talk about what goes here??
    </Elements>
  )
}
```

It works! Let’s talk about the children that go inside of Elements, now. 

Remix is built on React router, so gives us the `Outlet` component. We’ll use that to render our nested UI components, including the payment form:

```js
import {Outlet} from 'remix'

<Elements
  stripe={stripePromise}
  options={{clientSecret: paymentIntent.client_secret}}>
  <Outlet />
</Elements>
```

Outlet will be replaced by the nested UI components inside of the `/pay` directory. Let’s go add our payment form to `/app/routes/pay/index.tsx` 

Remix provides a Form component that works very similar to the HTML `form` element. We’ll pull that in and add a little button:

```js
import {Form} from 'remix'

export default function Index() {
  return (
    <Form>
      <button>Pay</button>
    </Form>
  )
}
```

Now we can drop in our PaymentElement component and you should see something fancy render on your screen when browsing to http://localhost:3000/pay!

```jsx
import {PaymentElement} from '@stripe/react-stripe-js'

//... 

<Form>
  <PaymentElement />
  <button>Pay</button>
</Form>
```

![PaymentElement rendered on a page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1kxyx7qkdrt2rbybaej.png)


We still need to wire up the form so that it passes those payment details to Stripe and confirms the payment. One of the nice things about Remix is that you can usually have forms submit directly to the server, then handle the form data in an action. In this case, we’re tokenizing (fancy way of saying we’re sending the raw card numbers directly to Stripe and getting back a token), the payment details. That tokenization step happens *client* side so that the raw card numbers never hit your server and help you avoid some extra PCI compliance burden.

Technically, you may still want to submit the form data to your server after confirming payment. Remix offers a [useSubmit](https://remix.run/docs/en/v1/api/remix#usesubmit) hook for that. 

To confirm client side, we use a Stripe.js helper called `confirmPayment` in a submit handler for the form, the same way we would in any other React application. 

The submit handler might look something like this:

```js
const handleSubmit = async (e) => {
  e.preventDefault();

  await stripe.confirmPayment({
    elements,
    confirmParams: {
      return_url: 'http://localhost:3000/pay/success'
    }
  })
}
```

Hold on a min, again new unknown variables! Where does `elements` come from? Is that the same `stripe` object we created on the server?

Sheesh, impatient developers are hard to please! 😄 

Remember that `stripe` prop we passed into the Elements provider earlier? Well, `react-stripe-js` has a handy hook for getting access to that in a child component like our payment form:  `useStripe`. If you’ve used Stripe.js before *without* React, you may be familiar with the `elements` object that is used for creating the older Card element. The `Elements` provider also creates an `elements` object for us that we can get through `useElements`. Two nice hooks are the answer to our mystery, here’s the code:

```js
import {useStripe, useElements} from '@stripe/react-stripe-js'

const stripe = useStripe();
const elements = useElements();

const handleSubmit = async (e) => {
  e.preventDefault();

  await stripe.confirmPayment({
    elements,
    confirmParams: {
      return_url: 'http://localhost:3000/pay/success'
    }
  })
}
```

🎶 all together now!

```jsx
import {Form} from 'remix'
import {PaymentElement, useElements, useStripe} from '@stripe/react-stripe-js'

export default function Index() {
  const elements = useElements();
  const stripe = useStripe();
  const submit = useSubmit();

  const handleSubmit = async (e) => {
    e.preventDefault();

    await stripe.confirmPayment({
      elements,
      confirmParams: {
        return_url: 'http://localhost:3000/pay/success'
      }
    })
  }

  return (
      <Form onSubmit={handleSubmit}>
        <PaymentElement />

        <button>Pay</button>
      </Form>
  )
}
```

Notice the `return_url` on the `confirmParams` we passed into `confirmPayment` that’s the URL to which the customer will be redirected after paying. The URL will include query string params for the ID and client secret of the PaymentIntent.

Recall that nested UI component we added at the beginning, `/app/routes/pay/success.tsx`? That’s the view that will be rendered when customers are redirected and where we can show the order confirmation or success page.

We’ll add a new helper to `~/payments.ts` for fetching the PaymentIntent server side:

```js
export async function retrievePaymentIntent(id) {
  return await stripe.paymentIntents.retrieve(id)
}
```

Then use that to build our Success page:

```jsx
import {useLoaderData} from 'remix'
import {retrievePaymentIntent} from '~/payments'

export const loader = async ({request}) => {
  const url = new URL(request.url);
  const id = url.searchParams.get("payment_intent")
  return await retrievePaymentIntent(id)
}

export default function Success() {
  const paymentIntent = useLoaderData();
  return (
    <>
      <h3>Thank you for your payment!</h3>
      <pre>
        {JSON.stringify(paymentIntent, null, 2)}
      </pre>
    </>
  )
}
```

If you must use a custom form, then use the guide here to implement the PaymentElement with automatic_payment_methods enabled.

Please let us know if you have any feedback by tweeting @StripeDev. Join us on Discord if you have any questions about how to get up and running: discord.gg/stripe. 

Find the full project here for reference: https://github.com/cjavilla-stripe/remix-stripe-sample