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.
- Sign Up for Stripe
- Set up you Elixir Phoenix App with Stripity Stripe
- Build Your Checkout
- Install Stripe CLI to Test Fulfilling Orders
- 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.
Click the Buy Now button to be redirected to the Stripe Checkout page to finalize payment.
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
)
- Enter
- 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.
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.
checkout.session.completed
event
Handle the 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
)
- Enter
- Click the Pay button
You should see:
- A
checkout.session.completed
in thestripe listen
terminal output - A print statement from your server’s event logs with the
checkout.session.completed
event similar to this:
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.