Tutorial

How to add a Coinbase crypto checkout to your Phoenix site

payments crypto webhook

In a previous tutorial, I showed how to add Stripe checkout in a Phoenix application. If you however want to accept Bitcoin, Etherium or any other crypto currencies, you can implement a Coinbase hosted checkout. So, in this tutorial, I want to show you how you can get started to receive Bitcoin or Etherium on your site.

On my site I have already implemented Stripe checkout. But even though Stripe is great, it might not work equally good in all countries and for all potential customers. So I wanted to see if I could take payments with Bitcoin, Etherium or any other crypto currency.

As it turns out, Coinbase have a hosted checkout solution as well. I am already signed up as a private coinbase customer.

However for this to work, I also need to sign up as a Coinbase commerce customer, which is free but they take a transaction fee 1% which is lower than the Stripe fee.

## Signup to Coinbase to receive payements

To get started with this tutorial, you can create an account on the Coinbase commerse site. https://commerce.coinbase.com. The signup is pretty straight forward and you create a wallet for the purpose of receiving payments.

After Signup for Coinbase

After going through the signup process, I can create my first product. In Coinbase, its called a checkout. It is possible to sell one item at a fixed price or accept any amount of cryptocurrency with a checkout. Each charge can be generated automatically on a per-customer basis. Checkouts can contain multiple charges. You can embed payment buttons to integrate checkouts into your website. Every checkout has a public, accessible page that can easily be shared with others.

In the first step, I will pick the option "Sell a Product"

When I have gone trough the process of naming the product, uploading the image and setting the price, I get a unique URL to the checkout page or code to embed.

The code for implementing the checkout button on the website looks like this:

<a class="w-full btn btn-primary"
   href="https://commerce.coinbase.com/checkout/cbd2db5b-2105-4324-bfc9-6714831c3840">
  <i class="bi bi-currency-bitcoin"></i>
  <span class="ml-1">Crypto Checkout</span>
</a>
<script src="https://commerce.coinbase.com/v1/checkout.js?version=201807"></script>

Note that I also add the JS to Conbase checkout. And the result on the webpage looks like this:

Crypto checkout link

Crypto checkout link

If a customer clicks the Crypto heckout button, they will be redirected to the Coinbase hosted checkout page. And there, they can choose from a number of different crypto currencies.

Coinbase hosted checkout

Coinbase hosted checkout

Receiving the Webhooks from Coinbase

After Coinbase has received the payment, It can send out a webhook to a specified URL. Again, this is very similar to have Stripe does it. To receive the webhook, and save the order and payment in the database, I need to:

  1. Add Coinbase secret to the config file
  2. I need to create a route and controller for Coinbase to post to.
  3. I need to make a custom plug that parses the raw post body
  4. Validate the Coinbase signing secret in the controller
  5. Parse what type of webhook it is and eventually mark the order as paid

Add Coinbase secret to the config file

The Coibase secret is needed for receiving webhooks. The secret is received from the Coinbase interface where you setup the custom webhook endpoint.

config :fullstack_phoenix,
  stripe_service: Stripe,
  coinbase_secret: System.get_env("COINBASE_SECRET")

Create a controller and route and

The first thing I need to do is to create a basic controller and a route. Since Coinbase is posting the webhooks to the specified URL, I need to setup so I have a create-action that listens to posts.

defmodule FullstackPhoenixWeb.HandleCoinbaseController do
  use FullstackPhoenixWeb, :controller  def create(conn, payload) do
    conn
    |> Plug.Conn.send_resp(201, "{}")
  end
end

At this point, I only want to setup a bare bone controller. In the routes file, I put in a basic route for posts and point it to the HandleCoinbaseController. The specific route is

scope "/api", FullstackPhoenixWeb do
  pipe_through :api  post "/webhooks/coinbase", HandleCoinbaseController, :create
end

Note I dont cover it in this tutorial, but to test this locally, you can register for and use ngrok service so you can send webhooks to your local environment. If you do that, remember to use the URL that ngrok provides in the Coinbase settings.

Custom plug for parsing the raw body

Next step is to create a custom plug that reads the raw body that Coinbase posts. I need to do this becase Phoenix parses the body and makes minor changes to it and since I verify the payload with a hashing algoritm, I need to use the body before Phoenix changes it.

Basically, I just need to grap the raw body and the webhook signature and assign it in plug assigns. I have the plug looking like this:

defmodule FullstackPhoenixWeb.Plugs.ParseRawBody do
  import Plug.Conn, only: [get_req_header: 2, assign: 3, read_body: 1]  def init(options), do: options  def call(conn, _opts) do
    case get_signature(conn) do
      [] ->
        conn
      ["" <> signature] ->
        {:ok, raw<i>body, </i>conn} = read_body(conn)        conn
        |> assign(:raw<i>body, raw</i>body)
        |> assign(:signature, signature)
    end
  end  defp get_signature(conn) do
    get_req_header(conn, "x-cc-webhook-signature")
  end
end

And in the endpoint.ex, make sure to add the plug before Plug.Parsers

FullstackPhoenixWeb.Endpoint
  # BEFORE plug Plug.Parsers
  plug FullstackPhoenixWeb.Plugs.ParseRawBody
  
  plug Plug.Parsers
end
This now mean, that all incoming webhooks have there signature and body assigned to conn assigns.

Validate the Coinbase signing secret in the controller

In the controller, I need to compare the signature sent in the header with a calculated signature to assert that the post is actually from Coinbase and can be trusted. Every Coinbase Commerce webhook request includes an X-CC-Webhook-Signature header. This header contains the SHA256 HMAC signature of the raw request payload, computed using your webhook shared secret as the key.

The controller code for this logic looks like this:

defmodule FullstackPhoenixWeb.HandleCoinbaseController do
  use FullstackPhoenixWeb, :controller  require Logger  plug :verify_header  def create(conn, payload) do    conn
    |> Plug.Conn.send_resp(201, "{}")
  end  defp verify<i>header(conn, </i>) do
    with "" <> signature <- conn.assigns[:signature],
         "" <> raw<i>body <- conn.assigns[:raw</i>body],
         true <- compare<i>signature(signature, raw</i>body) do
      conn
    else
      _ ->
        conn
        |> send_resp(401, "unauthorized")
        |> halt()
    end
  end  defp compare<i>signature(signature, raw</i>body) do
    secret = Application.get<i>env(:fullstack</i>phoenix, :coinbase_secret) || ""    signature == :crypto.mac(:hmac, :sha256, secret, raw_body) |> Base.encode16(case: :lower)
  end
end

Related Tutorials

Published 23 Sep - 2020

Setup Stripe with Phoenix LiveView

In this tutorial, I will go through how I setup Stripe payments with Phoenix and LiveView to make your app prepared for accepting payments. The tuto..