Set Up Stripe Checkout for Elixir Phoenix

Stripe Checkout is the simplest way to integrate Stripe payments into your Elixir Phoenix application. We’re going to use the Stripity Stripe library to create a simple checkout flow using Stripe Checkout.

The flow will begin with a customer clicking a Buy Now button to initiate an order. After they click the button, our app will get a new session from Stripe and redirect the customer to Stripe to complete checkout. Once the customer successfully checks out, Stripe will redirect the customer back to a "success" page in our app.

Our app will also handle a webhook event from Stripe informing the app of the successful checkout. Once we receive this event, we can execute code to fulfill the order.

Overview

We’ll cover the basics of using Stripe Checkout to accept payments in your Phoenix application.

  1. Sign Up for Stripe
  2. Set up you Elixir Phoenix App with Stripity Stripe
  3. Build Your Checkout
  4. Install Stripe CLI to Test Fulfilling Orders
  5. Handle Events to Fulfill the Order

Prerequisites

To follow this guide, make sure you have at least the following applications installed on your machine. In the brackets are the versions with which this guide was written. This guide takes advantage of the latest features in these versions.

  • Elixir (1.13.1-otp-24)
  • Erlang (OTP 24.2)
  • Phoenix (1.6.8)

If you run into problems, the finished project is available on GitHub as StakNine HelloStripe.

Sign Up for Stripe

First Sign up for a Stripe account. Then visit your Stripe dashboard and verify you are in test mode.

To get an idea about what we are building, explore a full, working code sample of an integration with Stripe Checkout. The client- and server-side code redirects to a prebuilt payment page hosted on Stripe.

This guide closely mirrors the Prebuild Checkout page quickstart guide for other languages Stripe officially supports.

Set up you Elixir Phoenix App with Stripity Stripe

We are going to create a simple Elixir Phoenix LiveView project called HelloStripe.

Use the command line to update your Phoenix version (you need at least v1.6.8) and create a new Phoenix project.

$ mix archive.install hex phx_new
$ mix phx.new hello_stripe

Change into the project directory, create your database and start your server.

$ cd hello_stripe
$ mix ecto.create
$ mix phx.server

Now visit http://localhost:4000 and you should see the Welcome to Phoenix! page.

Install the package

Next install the Stripity Stripe Elixir library and we’ll set up configuration.

Add the package to mix.exs:

{:stripity_stripe, "~> 2.13.0"}

Run mix deps.get.

Stripe requires you to configure your secret key to make Stripe API calls.

Update your config file at config/config.exs:

config :stripity_stripe, api_key: System.get_env("STRIPE_API_KEY")

Add a .env file with your API key as an environment variable:

# THESE ARE USED FOR STRIPE INTEGRATION AND CAN BE FOUND IN STRIPE DASHBOARD
export STRIPE_API_KEY=sk_test_secretkey
export STRIPE_PUBLIC=pk_test_secretkey

Add .env to .gitignore so you don’t check your secret key into git.

Run source .env so you environment has your API keys.

Note: Stripity Stripe relies on an older version of Stripe’s API dated 2019-12-03. Using it, you may experience failed charges in regions with very recent regulatory changes, such as India. Read about setting your API version here and here.

Add controller action to use Stripe Checkout

We will add a controller action that creates a Checkout Session using Stripe’s API.

Create a Checkout Session

A Checkout Session controls what your Stripe customer sees in the Stripe-hosted payment page such as line items, the order amount and currency, and acceptable payment methods. Card payments are enabled by default but you can enable and disable other payment methods in the Stripe Dashboard.

First add the path in the router.ex:

  scope "/", HelloStripeWeb do
    pipe_through :browser

    # Add the following route
    resources "/checkout-session", CheckoutSessionController, only: [:create]
    get "/", PageController, :index
  end

Then add the controller at lib/hello_stripe/controllers/checkout_session_controller.ex:

defmodule HelloStripeWeb.CheckoutSessionController do
  use HelloStripeWeb, :controller

  def create(conn, _params) do
    url = HelloStripeWeb.Endpoint.url()

    params = %{
      line_items: [
        %{
          # Provide the exact Price ID (e.g. pr_1234) of the product you want to sell
          price: "price_1L2LhZHk1jpJeQdKmTHZ6aUr",
          quantity: 1
        }
      ],
      mode: "payment",
      success_url: url <> "/success",
      cancel_url: url <> "/cancel",
      automatic_tax: %{enabled: true}
    }

    {:ok, session} = Stripe.Session.create(params)

    conn
    |> put_status(303)
    |> redirect(external: session.url)
  end
end

Let’s review the params we send with our request.

Define a product to sell

Always keep sensitive information about your product inventory, such as price and availability, on your server to prevent customer manipulation from the client. Define product information when you create the Checkout Session using predefined price IDs or on the fly with price_data.

Create a new test product here with standard pricing and a one-time price. Then update the Price ID:

          # Provide the exact Price ID (e.g. pr_1234) of the product you want to sell
          price: "price_1L2LhZHk1jpJeQdKmTHZ6aUr",

Choose the mode

Checkout has three modes: payment, subscription, or setup. Use payment mode for one-time purchases. Learn more about subscription and setup modes in the docs.

      mode: "payment",

Supply success and cancel URLs

Specify URLs for success and cancel pages—make sure they’re publicly accessible so Stripe can redirect customers to them. You can also handle both the success and canceled states with the same URL. We’ll create these pages a little later.

      success_url: url <> "/success",
      cancel_url: url <> "/cancel",

Redirect to Checkout

Send a request to the Stripe API to create a session then redirect your customer to the URL for the Checkout page returned in the Stripe API response.

    {:ok, session} = Stripe.Session.create(params)

    conn
    |> put_status(303)
    |> redirect(external: session.url)

Build Your Checkout

Now we’ll build the static pages for your site’s checkout flow.

Add success and canceled pages

Create a success page for the URL you provided as the Checkout Session success_url to display order confirmation messaging or order details to your customer. Stripe redirects to this page after the customer successfully completes the checkout.

Add another page for cancel_url. Stripe redirects to this page when the customer clicks the back button in Checkout.

Update router.ex:

    resources "/checkout-session", CheckoutSessionController, only: [:create]
    ## Add the two following routes
    get "/success", PageController, :success
    get "/cancel", PageController, :cancel
    get "/", PageController, :index

Update your page_controller.ex:

  def success(conn, _params) do
    render(conn, "success.html")
  end

  def cancel(conn, _params) do
    render(conn, "cancel.html")
  end

Create a new file lib/hello_stripe_web/templates/page/success.html.heex.

<section>
  <p>
    We appreciate your business! If you have any questions, please email
    <a href="mailto:[email protected]">[email protected]</a>.
  </p>
</section>

Create a new file lib/hello_stripe_web/templates/page/cancel.html.heex.

<section>
  <p>Forgot to add something to your cart? Shop around then come back to pay!</p>
</section>

Add an order preview page with a checkout button

Finally, we’ll update the home page to show a preview of the customer’s order. Your order preview page should allow them to review or modify their order—as soon as they’re sent to the Checkout page, the order is final and they can’t modify it without creating a new Checkout Session.

In our example, there is nothing to modify since we are creating a Buy Now button for a single product.

We’ll add a button to the order preview page. When your customer clicks this button, they’re redirected to the Stripe-hosted payment page using the controller action we created earlier.

Open lib/hello_stripe_web/templates/page/index.html.heex and delete the generated code.

Replace it with the following:

<section>
  <div class="product">
    <img src="https://i.imgur.com/EHyR2nP.png" alt="The cover of Stubborn Attachments" />
    <div class="description">
      <h3>Stubborn Attachments</h3>
      <h5>$20.00</h5>
    </div>
  </div>
  <%= button("Buy Now", to: "/checkout-session", method: :post) %>
</section>

With the order page complete, we’re ready to try out our checkout flow.

Run the application

Start your server with mix phx.start and navigate to http://localhost:4000.

Try it out

You should see your homepage has been replaced with your order preview and checkout button.

Stripe Order Page

Click the Buy Now button to be redirected to the Stripe Checkout page to finalize payment.

Stripe Checkout

Now complete the payment using test data for the credit card.

  • Enter an email address and name
  • Fill out your payment form with test data
    • Enter 4242 4242 4242 4242 as the card number
    • Enter any future date for card expiry
    • Enter any 3-digit number for CVV
    • Enter any address & billing postal code (90210)
  • Click the Pay button

Use any of these test cards to simulate a payment.

  • Payment succeeds: 4242 4242 4242 4242
  • Payment requires authentication: 4000 0025 0000 3155
  • Payment is declined: 4000 0000 0000 9995

With a successful payment, you should be redirected to your "success" page.

Stripe success page

If you cancel the checkout session, you should go to the "cancelled" page.

We’ve made great progress, but our customer is at the "success" page and we haven’t done anything to fulfill the order!

Install Stripe CLI to Test Fulfilling Orders

Stripe webhooks are an automated message from Stripe to your app with information about your customers’ actions on Stripe. For example, when a customer successfully checks out, Stripe will send your app a webhook so you can fulfill the order.

The quickest way to develop and test webhooks locally is with the Stripe CLI.

As a first step, follow the install guide for Stripe CLI.

After you finish the install guide, run the following command to test that your Stripe CLI installation works.

stripe status

✔ All services are online.

Set Up Stripe CLI

Next, set up Stripe CLI to forward events to your local server, so you can test your event handler locally:

stripe listen --forward-to localhost:4000/webhook/stripe

Ready! Your webhook signing secret is 'whsec_<REDACTED>' (^C to quit)

Verify events came from Stripe

Anyone can POST data to your event handler. Before processing an event, always verify that it came from the Stripe API before trusting it. The Stripity Stripe library has built-in support for verifying webhook events.

config :stripity_stripe,
  api_key: System.get_env("STRIPE_API_KEY"),
  stripe_webhook_secret: System.get_env("STRIPE_WEBHOOK_SECRET")

Then you need to add the webhook signing secret from the command line to you .env file.

# THESE ARE USED FOR STRIPE INTEGRATION AND CAN BE FOUND IN STRIPE DASHBOARD
export STRIPE_API_KEY=sk_test_secretkey
export STRIPE_PUBLIC=pk_test_secretkey
## Add the following
export STRIPE_WEBHOOK_SECRET=whsec_secret

Run source .env so you environment has your API keys.

Create Event Handler

In this section, you’ll create a small Plug Stripe event handler so Stripe can send you a checkout.session.completed event when a customer completes checkout.

First, add Stripe.WebhookPlug to your endpoint.ex file.

  # Before Plug.Parsers
  plug Stripe.WebhookPlug,
    at: "/webhook/stripe",
    handler: HelloStripeWeb.StripeHandler,
    secret: {Application, :get_env, [:stripity_stripe, :stripe_webhook_secret]}

Next we’ll create the HelloStripWeb.StripeHandler module to handle events we receive.

Handle Events to Fulfill the Order

To fulfill the order, you’ll need to handle the checkout.session.completed event. Depending on which payment methods you accept (for example, cards, mobile wallets), you’ll also optionally handle a few extra events after this basic step.

Handle the checkout.session.completed event

Now that you have the basic structure and security in place to make sure any event you process came from Stripe, you can handle the checkout.session.completed event. This event includes the Checkout Session object, which contains details about your customer and their payment.

When handling this event, you might also consider:

  • Saving a copy of the order in your own database.
  • Sending the customer a receipt email.
  • Reconciling the line items and quantity purchased by the customer if using line_item.adjustable_quantity.

Add a file at lib/hello_stripe_web/stripe_handler.ex to create your event handler to fulfill the order:

defmodule HelloStripeWeb.StripeHandler do
  @behaviour Stripe.WebhookHandler

  @impl true
  def handle_event(%Stripe.Event{type: "checkout.session.completed"} = event) do
    # TODO: handle the charge.succeeded event
    IO.inspect(event, label: "checkout session completed")
    {:ok, event}
  end

  @impl true
  def handle_event(event) do
    IO.inspect(event)
    :ok
  end
end

With your event handler in place, we’re ready to test it out.

Testing

Next, go through Checkout as a customer:

  • Click your checkout button
  • Fill out your payment form with test data
    • Enter 4242 4242 4242 4242 as the card number
    • Enter any future date for card expiry
    • Enter any 3-digit number for CVV
    • Enter any billing address & postal code (90210)
  • Click the Pay button

You should see:

  • A checkout.session.completed in the stripe listen terminal output
  • A print statement from your server’s event logs with the checkout.session.completed event similar to this:
Stripe Event data

With the data you receive from Stripe webhooks, you can fulfill the order.

Conclusion

Congratulations! You have a basic Checkout integration working to start accepting payments. You added a Buy Now button to your webpage. When a customer clicks the button, they are redirected to a Stripe Checkout session to enter their payment information and finalize the order. Once they finalize their order they are redirected to a success page in your app.

You also set up an event handler for Stripe webhooks so you can fulfill orders after successful checkout.

With the basics complete, you might want to enable more payment methods in the Stripe Dashboard. Card payments, Apple Pay, and Google Pay are enabled by default.

While this covered a simple use case, you should understand the fundamentals of how to set up Stripe Checkout in your Elixir Phoenix application. If you want to learn about setting up more tools for your Phoenix application, check out my book the Phoenix Deployment Handbook.

Want To Know How To Deploy Phoenix Apps Using A single Command?

This brand-new FREE training reveals the most powerful new way to reduce your deployment time and skyrocket your productivity… and truly see your programming career explode off the charts!