DEV Community

Joshua Evuetapha
Joshua Evuetapha

Posted on

How to Accept Crypto Payments in a Next.js Application using Coinbase Commerce

The adaptation of cryptocurrencies is increasing fast these digital assets can be used to settle debts, and several businesses now accept cryptocurrencies as a means of payment. The most significant advantage of cryptocurrencies is that they are decentralized, making payments easy as people can instantly transfer digital assets anywhere in the world without restrictions.

Given that cryptocurrencies are gaining popularity, you may want to accept cryptocurrencies in your web application, one way to do that is by using coinbase commerce. Coinbase commerce is a platform that allows merchants to accept digital payments through cryptocurrencies, and they support many cryptocurrencies, you can look at their list of supported cryptocurrencies here.

Next.js is a popular Frontend framework built on React.js, it supports serverless functions this will be very vital in this article, these serverless functions allow us to write backend codes which essentially eliminate the need to have two separate codebases for the frontend and backend.

Prerequisite

You need the following to successfully follow this article.

Project Set up

First, let’s create a new Next.js application by running the command below on the terminal, Follow the instructions.

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

Next, navigate to the project directory and install the following dependencies by running the command below.

npm install coinbase-commerce-node axios
Enter fullscreen mode Exit fullscreen mode

axios is an HTTP client and coinbase-commerce-node is the coin base SDK for Nodejs.

Once these packages have been installed, open the project directory with your text editor, and run the following command in your terminal to start the app in development mode.

npm run dev
Enter fullscreen mode Exit fullscreen mode

The application will be served at [localhost:3000](http://localhost:3000) if port 3000 is free, else another port will be chosen.

Building the Application

In this article, we will be building a simple web-based e-commerce shop that accepts cryptocurrencies using Next.js, we will use coinbase commerce to create payment links, and we will create a webhook URL to verify transactions, and test this webhook locally using a tool called Ngrok, the data source for the application will be a hard-coded array of shop items, now open the project directory in your text editor, and let’s write some codes.

You can get the source code of this project here, feel free to clone the repo.

The data source

In a real-world application, the data would likely come from a database or a server, but in this article, it will be hard-coded. At the root directory of your project create a file name data.js this is where all the application data will come from, copy and paste the code snippet below in that file.

export const products = [
    {
        id: 1,
        name: "Small Black Chair",
        price: 10,
        currency: "USD",
        description: "Soft Chair, Best value for Money"
    },
    {
        id: 2,
        name: "Spoon",
        price: 2,
        currency: "USDT",
        description: "Spoon gets the best value for Money"
    },
    {
        id: 3,
        name: "Small Table",
        price: 10,
        currency: "USDC",
        description: "Soft Table, Best value for Money"
    },
    {
        id: 4,
        name: "Silver",
        price: 0.2,
        currency: "ETH",
        description: "Silver spoon set"
    },
    {
        id: 5,
        name: "Silver Cup",
        price: 0.03,
        currency: "BTC",
        description: "Cup for the big men"
    },
    {
        id: 6,
        name: "Basic Cup",
        price: 2000,
        currency: "DOGE",
        description: "Best value for Money"
    }
]
Enter fullscreen mode Exit fullscreen mode

Building the Home Page

Next.js uses file-based routing so any react component exported as default in the pages directory will be rendered as the page when that route is visited, you can learn more about it here.

Navigate to pages/index.js delete the boilerplate code and input the following code snippets, we will discourse the code next.

import { useState } from 'react';
import axios from 'axios';
import { products } from '../data';

export default function Home() {
  return (
    <div className={"container"}>
      {
        products.map( (product, index) => {
          return (<Products key={index} product={product} />)
        })
      }
    </div>
  )
}

const Products = ({product}) => {
  const [loading, setLoading] =  useState(false);

  const coinbase = async () => {
    setLoading(true)
    try {
      const data = await axios.post('/api/init', { id: product.id })
      setLoading(false)
      window.open(data.data.hosted_url, '_blank');
    } catch (e) {
      console.error(e)
      setLoading(false)
    }
  }

  return (
    <div>
      <h4>{product.name}</h4>
      <p>{product.description}</p>
      <p>Price: {product.price} {product.currency}</p>
      <button onClick={coinbase} disabled={loading}> Pay With Crtpto </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above contains two components, Home and Products, the Home component is exported as the default component, while the Product component is not exported this is because we are only using it in this file to display each product.

The code snippet below in the Home component maps the products array to the Product component.

products.map( (product, index) => {
  return (<Products key={index} product={product} />)
})      
Enter fullscreen mode Exit fullscreen mode

Let’s go into the Product component, this component displays a particular product, and it contains a very important function, the coinbase function, this function sends a post request to this route /api/init, we will set up this route later if this request is successful this route will create a payment charge using the product id and returns the charge details, then the code will open a new tab on the browser using the payment link returned from coinbase commerce.

Let’s take a look at the coinbase function below;

  const coinbase = async () => {
    setLoading(true)
    try {
      const data = await axios.post('/api/init', { id: product.id })
      setLoading(false)
      window.open(data.data.hosted_url, '_blank');
    } catch (e) {
      console.error(e)
      setLoading(false)
    }
  }
Enter fullscreen mode Exit fullscreen mode

This piece of code const data = await axios.post('/api/init', { id: product.id }) send a post request to our api routes passing the product ID as the body of the request, and this line of code window.open(data.data.hosted_url, '_blank'); opens another tab in the browser, using the link returned from our post request, next we are going to set up the init route.

Styling the Application

Now let’s add some styling to give the application a better look, we are going to be using global CSS for this one, global CSS now open the styles/global.css file, delete the boilerplate CSS and paste the CSS code snippet below, you can learn more about global CSS in Next.js from here.

body {
  background-color: whitesmoke;
}

.container {
  padding: 0 2rem;
  display: flex;
  justify-content: space-evenly;
  height: 100vh;
  flex-wrap: wrap;
}

.container > div {
  display: block;
  margin: 0.8em;
  padding: 4em;
  border: 2px solid black;
  flex-grow: 1;
}
Enter fullscreen mode Exit fullscreen mode

Building the payment route handler

Next.js allows us to create serverless functions so that we can create backend API route handlers. You can learn more about them here. Next, create a file pages/api/init.js and paste the following code snippets. we will discourse the code next.

import { Client, resources } from 'coinbase-commerce-node';
import { products } from '../../data';

Client.init(String(process.env.COINBASE_API));
const { Charge } = resources;

const coinInitRoute = async(req, res) => {

  const { id } = req.body

  const product = products.find(product => product.id === id)

  try {

    const chargeData = {
      name: product.name,
      description: product.description,
      pricing_type: "fixed_price",
      local_price: {
        amount: product.price,
        currency: product.currency,
      },
      metadata: {
        id: product.id,
        userID: 1
      },
    };

    const charge = await Charge.create(chargeData);

    res.send(charge);

  } catch (e) {
    res.status(500).send({ error:e });
  }

}

export default coinInitRoute
Enter fullscreen mode Exit fullscreen mode

We start by importing the modules needed to create a charge, we will be using Client and resources from the coinbase-commerce-node package, next we import our product data,

this line of code Client.init(process.env.COINBASE_API) initiates the coinbase commerce client using our coinbase commerce API key we will talk about how to get this later.

import { Client, resources } from 'coinbase-commerce-node';
import { products } from '../../data';

Client.init(process.env.COINBASE_API);
const { Charge } = resources;
Enter fullscreen mode Exit fullscreen mode

The flow of this route handler is simple, we retrieve the product id from the req.body and use it to search for the correct product.

  const product = products.find(product => product.id === id)
Enter fullscreen mode Exit fullscreen mode

Next, we create a charge using the product data, we set the pricing_type to fixed_price so the user can’t pay any amount, the pricing type can be set to no_price this will allow the user to pay any amount, metadata field is used to send additional data to coinbase commerce for identifying the user, this metadata is useful in identifying the user and the product they paid for, in our use case we set the userID to 1.

const chargeData = {
      name: product.name,
      description: product.description,
      pricing_type: "fixed_price",
      local_price: {
        amount: product.price,
        currency: product.currency,
      },
      metadata: {
        id: product.id,
        userID: 1
      },
    };
Enter fullscreen mode Exit fullscreen mode

The next two lines of codes create a charge by sending a request to coinbase commerce once this request is successful it sends the charged object to the client

const charge = await Charge.create(chargeData);
res.send(charge);
Enter fullscreen mode Exit fullscreen mode

You can check the coinbase commerce documentation here.

Creating Coinbase API key

We have created our application, but we can’t create charges just yet because we need an API KEY from coinbase commerce to get started, first create a .env file at the root of the project, and add COINBASE_API and COINBASE_SECRET to the .env file, the file should look like the one below.

COINBASE_API = 
COINBASE_SECRET =
Enter fullscreen mode Exit fullscreen mode

Currently, coinbase commerce doesn’t have a sandbox or a testing environment, so we will have to allow our testing with live API keys and real cryptocurrencies. So log in to your coinbase commerce account, click on the profile icon on the top right corner of the page, then click on settings.

Create API Key

Click on the Security tab and click on the New API key button to create an API key, then copy the API key, open the .env file and paste the keys to the COINBASE_API key.

Coinbase Api

Test Running the Application

Run the application with this command npm run dev on the terminal then view the app on your browser and click on Pay With Crypto button on any of the items, this will open a new tab to make payments.

Homepage

You will have just one hour to make a payment, by choosing any of the methods below, you can choose to pay with coinbase or any of the cryptocurrencies listed below.

Coinbase Pay

Verifying Transactions

Now users can buy items from your application using cryptocurrencies but can’t verify who made the transaction or what they bought. To verify transactions you need to set up a webhook URL for coinbase commerce to send an HTTP post request to that route when a user makes a payment.

Setting up the webhook route

Next, create a file pages/api/verify.js and paste the following code snippets, we will discourse the code next.

import { Client, Webhook } from 'coinbase-commerce-node';

Client.init(process.env.COINBASE_API);

export default async function coinVerifyRoute(req, res) {

    try {

        const rawBody = JSON.stringify(req.body)
        const signature = String(req.headers['x-cc-webhook-signature']);
        const webhookSecret = String(process.env.COINBASE_SECRET);
        const event = Webhook.verifyEventBody(rawBody, signature, webhookSecret);

        console.log(event)

        if (event.type === 'charge:pending') {
            // TODO
            // user paid, but transaction not confirm on blockchain 
            console.log("pending")
        }

        if (event.type === 'charge:confirmed') {
            // TODO
            // all good, charge confirmed
            console.log("confirmed")
        }

        if (event.type === 'charge:failed') {
            // TODO
            // charge failed or expired
            console.log("failed")
        }

    } catch (e) {
        res.status(500).send("error"); 
    }

    res.send(`success`); 

};
Enter fullscreen mode Exit fullscreen mode

We start by importing the modules needed to make our webhook work, we will be using Client and Webhook from the coinbase-commerce-node package.

import { Client, Webhook } from 'coinbase-commerce-node';
Enter fullscreen mode Exit fullscreen mode

Next, we verify that the post request is coming from coinbase commerce and not a malicious person, you can notice that we are using an environment variable COINBASE_SECRET this variable will be gotten later from coinbase commerce when we try to test our webhook. This piece of code Webhook.verifyEventBody(rawBody, signature, webhookSecret); verifies the message sender, if the message is not coming from coinbase commerce it throws an error.

const rawBody = JSON.stringify(req.body)
const signature = String(req.headers['x-cc-webhook-signature']);
const webhookSecret = String(process.env.COINBASE_SECRET);
const event = Webhook.verifyEventBody(rawBody, signature, webhookSecret);
Enter fullscreen mode Exit fullscreen mode

Next let’s talk about events that can be sent by coinbase commerce, in our code we had just three events, which are charge:pending, 'charge:confirmed, and 'charge:failed, the charge:pending event is fired when the user have made payment but the transaction has not been confirmed on the blockchain, the 'charge:confirmed as the name implies is sent when the transaction has been confirmed on the blockchain, you should confirm the price before you give value and 'charge:failed' when the transaction failed.

        if (event.type === 'charge:pending') {
            // TODO
            // user paid, but transaction not confirm on blockchain 
            console.log("pending")
        }

        if (event.type === 'charge:confirmed') {
            // TODO
            // all good, charge confirmed
            console.log("confirmed")
        }

        if (event.type === 'charge:failed') {
            // TODO
            // charge failed or expired
            console.log("failed")
        }
Enter fullscreen mode Exit fullscreen mode

Before you give value to your customer, first confirm the amount paid and the currency, and use the information on the metadata field, to identify customer and products, you can learn more about the coinbase commerce webhooks here.

Testing the webhook Url

To test our webhook URL we will make use of a tool called Ngrok, if you don’t have it you can install it here, once installed run the following commands

npm run dev
Enter fullscreen mode Exit fullscreen mode

The above command will run our app on port 3000, next run the command below to connect your application to the internet using ngrok, make sure your application port is the same as the one on the command.

ngrok HTTP 3000
Enter fullscreen mode Exit fullscreen mode

Ngrok

Copy the second Forwarding URL we are going to use this as our domain for the coinbase commerce URL

Setting the webhook Url

Login to your coinbase commerce account, navigate settings, and click on the notification tab, then click on the Add Endpoint past the Forwarding URL gotten from ngrok, and add the /api/verify route as shown in the image below.

Note on a live server this webhook URL should be https://your-domain/api/verify

Webhook Url

Next get your shared secret by clicking the Show shared secret button on the bottom right corner, copy the shared secret, open the .env file and assign it to the COINBASE_SECRET .

Shared Secret

Now let’s manually test our webhook by sending it fake requests, First, click on the edit button on the right of the webhook endpoint.

Click Edit

Click on the Send Test button and choose any event of you choice, let’s start we confirm as shown in the photo below.

Send Test

You should get a response like this

Test Response

Look at your terminal, to see the dummy data sent by coinbase commerce, feel free to test as many events as you want.

Webhook Response

Conclusion

In this article, we built a simple web-based ecommerce shop that accepts cryptocurrencies using Next.js, we talked about coinbase-commerce-node, which is the official nodejs SDK for coinbase commerce, we created API keys in coinbase commerce, and retrieved our coinbase commerce “shared secret”, we created a payment link with coinbase-commerce using this SDK, we created a webhook URL to verify transactions, and we manually tested our webhook endpoint locally from our coinbase commerce account, using a tool named ngrok, you can get the source code of this project here, feel free to clone the repo.

Top comments (1)

Collapse
 
fillis34 profile image
Fillis34

Thanks, that was very informative. Modern crypto payment technologies are beginning to be actively used where natural financial instruments are limited. I read that Russia plans to introduce such a payment system to circumvent sanctions. It is very interesting that crypto technologies do not have an official legislative base there. In general, you can read about the participation in the project of such platforms as Bitcoin Apex. I'm very interested in how it will eventually be implemented. It is also necessary to take into account the fact that there are countries where crypto falls under direct state control (for example, the USA)