Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 12, 2022 08:50 pm GMT

How to accept payments in a Remix application with Stripe

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 youre able to let Stripe do the heavy lifting.

NB: No matter which option you chose, youll want a webhook handler to automate fulfillment. Heres code for a Remix action to setup a webhook handler for Stripe webhooks.

No-code

Stripe 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 dont 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 dont need to keep track of or customize each payment flow per-customer.

Low-code

Stripe 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 dont need full control over the payment form inputs.

The most code

Stripe Elements is the option well discuss today. Elements are pre-built form components that you can embed directly into your web application (theres mobile versions too!). Theres an official 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 isnt clear, take a look at Charles Making sense of Stripe Checkout, Payment Links, and the Payment Element. My recommendation is to start with PaymentLinks, if thats not feature rich enough graduate to Checkout, if thats not enough control, step up into PaymentElement and read on!

Pop open that terminal and lets 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:

STRIPE_SECRET_KEY=sk_test...

Lets get into the code!

Well 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 lets 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 were using the PaymentElement, we also need to pass an options prop.

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

Okay, but youll notice that code uses two things we dont 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.

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 or The PaymentIntents Lifecycle by Paul.

To keep the code nice and organized in Remix, lets 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.

Lets 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 have an example showing how to pass environment variables to the client. Incidentally, the example is for Stripe publishable (public) keys!

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

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

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 Stripes /v1/payment_intents endpoint with some args. Lets talk about each of those:

  • amount this is the amount in the smallest currency denomination. In this case cents, so were 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. 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.

Lets head back to our Payment form and use the resulting PaymentIntents client_secret in our options.

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

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:

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! Lets talk about the children that go inside of Elements, now.

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

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. Lets 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. Well pull that in and add a little button:

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!

import {PaymentElement} from '@stripe/react-stripe-js'//... <Form>  <PaymentElement />  <button>Pay</button></Form>

PaymentElement rendered on a page

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, were tokenizing (fancy way of saying were 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 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:

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 youve 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, heres the code:

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!

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 thats 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? Thats the view that will be rendered when customers are redirected and where we can show the order confirmation or success page.

Well add a new helper to ~/payments.ts for fetching the PaymentIntent server side:

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

Then use that to build our Success page:

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


Original Link: https://dev.to/stripe/how-to-accept-payments-in-a-remix-application-with-stripe-20k0

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To