E-commerce payments using Firebase, NodeJS, and Square API
Tags: firebase, node, googlecloud
Published: 2021-02-12 15:42:45

While building my client's art portfolio project, I was tasked with processing payments using Firebase. Processing payments is not generally considered a front-end process unless you might be using Paypal. In my case, I was using [Square API](https://developer.squareup.com/us/en). Square depends on a backend to process payments. In my case, I concluded that I need to create a Firebase Cloud Function.
 
The process happens like this: the front end passes the data to the cloud function which acts like a backend. After the backend receives the data, the Square SDK to processes the payment and the order data is stored into my database after the payment is complete. To understand this whole process about creating payments, see the [Square API payments docs](https://github.com/square/square-nodejs-sdk/blob/master/doc/api/payments.md). 
In this post, I will go over how to use the Google Firebase Cloud Functions written in NodeJS to process a payment.
The first step is to setup Firebase Functions. Here is a quick setup video:

{% youtube DYfP-UIKxH0 %}

In my example, I will be using Typescript as they recommended in the video. Typescript in many ways is like Javascript; however, I noticed it can be very picky about how to properly code, so I would For beginners (like me), I had to learn a little bit about how Typescript uses tslint to ensure correct usage. Here is a video walkthrough for Typescript:

{% youtube BwuLxPH8IDs %}

After setting up the Firebase functions, it's time to get into how to setup NodeJS. Start by importing and initializing firebase:

```javascript
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin'
admin.initializeApp()

```
This will do the import for the functions that will later be used. When creating Firebase Cloud Functions using NodeJS, it is important to understand the slight differences in just using a NodeJS app. When creating the function, that function can be directly called or called via HTTP requests. The Firebase docs have several examples below.

[HTTP requests via Firebase](https://firebase.google.com/docs/functions/http-events)

Instead of creating a listen to a port and starting nodemon, you have to export the function. Also, you can perform an emulate and inspect your function to perform continuous development updates as I discussed in a previous post [here](https://dev.to/ting682/using-firebase-cloud-functions-in-nodejs-with-react-15b5)

So in this post, we are going to create an express app that becomes an exported cloud function. More on that in a little while. 

```javascript
const cors = require('cors')
const express = require('express');
const bodyParser = require('body-parser');
const { Client, Environment, ApiError } = require('square');
import { v4 as uuidv4 } from 'uuid'
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
```
I have installed cors to ensure that cross-origin is enabled. I create the express app, so all of this should be familiar with regard to creating an express application. I also initialize the square SDK and use the Client, Environment, and ApiError for processing errors. I also install body-parser to process HTTP POST requests. More info about body parser can be found [here](https://www.geeksforgeeks.org/body-parser-middleware-in-node-js/). On a side note, I also create an instance of uuidv4 to be used later to create an order ID.
 
Also, if you don't already have a square account, you should create one here: [Square developer](https://developer.squareup.com/us/en)

This will give you a sandbox square API access token to use in your application.

In this post, most of the NodeJS code used is from the payment form walkthrough below.

[Square payment walkthrough](https://developer.squareup.com/docs/payment-form/payment-form-walkthrough)

I will not be going through the front-end portion of this tutorial. I will post a front end tutorial in a future post.

So the next step is to create the square SDK client as shown below.

```javascript
const client = new Client({
  environment: Environment.Sandbox,
  accessToken: 'your-square-access-token',
});
```

So before getting into the details of how to process the POST payment request in NodeJS, let's delve in a little bit about the contents of the POST request that is sent to the backend. I used a React front end to create a POST fetch to the back end. Here is the code for the POST fetch:

```javascript
fetch('https://project.cloudfunctions.net/payments/', {
    
    method: 'POST',
    headers: {
        
        'Square-Version': "2020-12-16",
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + process.env.REACT_APP_SQUAREPAY_SANDBOX_ACCESS_TOKEN,
        
    },
    body: JSON.stringify({
        
        idempotency_key: idempotency_key,
        location_id: LOCATION_ID,
        nonce: nonce,
        amount: cart.reduce((acc, item) => {
          return acc + parseInt(item.price)
      }, 0) * 100,
        uid: localStorage.getItem('JWT'), //uid of the cart
        emailAddress: emailRef.current.value,
        orderLocaleDate: (new Date()).toLocaleDateString('en', { year: 'numeric', month: 'long', day: 'numeric' }) + " " + (new Date()).toLocaleTimeString('en-US') + " " + (new Date()).toString().match(/([A-Z]+[\+-][0-9]+.*)/)[1],
        billing: {
          firstName: billingFirstNameRef.current.value,
          lastName: billingLastNameRef.current.value,
          address1: billingAddress1Ref.current.value,
          address2: billingAddress2Ref.current.value,
          city: billingCityRef.current.value,
          state: billingStateRef.current.value,
          zip: billingZipRef.current.value,
          phone: billingPhoneRef.current.value
        },
        shipping: {
          firstName: shippingFirstNameRef.current.value,
          lastName: shippingLastNameRef.current.value,
          address1: shippingAddress1Ref.current.value,
          address2: shippingAddress2Ref.current.value,
          city: shippingCityRef.current.value,
          state: shippingStateRef.current.value,
          zip: shippingZipRef.current.value,
          phone: shippingPhoneRef.current.value
        },
        buyerVerificationToken: buyerVerificationToken
    })   
    })
    .catch(err => {
    alert('Network error: ' + err);
    })
    .then(response => {
        console.log(response)
        
    if (!response.ok) {
        return response.json().then(
        errorInfo => Promise.reject(errorInfo));
    }
    return response.json();
    })
    .then(data => {
    console.log(data);
    alert('Payment complete successfully!');
    })
    .catch(err => {
    console.log(err);
    alert('Payment failed to complete!');
    });
``` 
So the most important points of this code to keep in mind are to obtain the 'nonce' or the single access token that is generated from the Square Form. This needs to be passed into the body. Also, the idempotency key to required to ensure that no duplicate charges are made. Here is Square's explanation [idempotency](https://developer.squareup.com/docs/working-with-apis/idempotency). 

Also required fields are the location ID that is created with the square developer sandbox account. And the last required field is the amount to be charged. In my application, I ensured that the billing and shipping information is also passed in to create the order details. Another field that might be required is the buyer verification token. Here is an explanation about the workings of that: [buyer verification token](https://developer.squareup.com/docs/payment-form/cookbook/verify-buyer-on-card-charge)

After the fetch is sent, now we want to discuss how to set up the post payment in NodeJS. We will walk through this step by step.

```javascript
app.post('/', async (req: any, res: any) => {
  const requestParams = req.body;
  const orderId = uuidv4()
  let lineItems :any = [];
  
  const paymentsApi = client.paymentsApi;
  const requestBody = {
    sourceId: requestParams.nonce,
    amountMoney: {
      amount: requestParams.amount,
      currency: 'USD',
    },
    order_id: orderId,
    locationId: requestParams.location_id,
    idempotencyKey: requestParams.idempotency_key,
    buyer_email_address: requestParams.emailAddress,
    billing_address: {
      first_name: requestParams.billing.firstName,
      last_name: requestParams.billing.lastName,
      address_1: requestParams.billing.address1,
      address_2: requestParams.billing.address2,
      locality: requestParams.billing.city,
      postal_code: requestParams.billing.zip,
    },
    shipping_address: {
      first_name: requestParams.shipping.firstName,
      last_name: requestParams.shipping.lastName,
      address_1: requestParams.shipping.address1,
      address_2: requestParams.shipping.address2,
      locality: requestParams.shipping.city,
      postal_code: requestParams.shipping.zip,
    },
    statement_description_identifier: orderId,
    verification_token: requestParams.buyerVerificationToken,
  };

  try {
    const response = await paymentsApi.createPayment(requestBody);
    res.status(200).json({
      'title': 'Payment Successful',
      'result': response.result,
    });

    jwt.verify(requestParams.uid, functions.config().jwt.secret, async (err :any , data :any) => {
      if(err){
        res.sendStatus(403)
      } 
      else if(data.uid){
        req.uid = data.uid
  
        
  
        const cartsRef = admin.database().ref('carts/' + data.uid)
        cartsRef.once('value').then(async snap => {
          const cartData = snap.val()
          
          let updatedAt;
          for (const [key, item] of Object.entries(cartData)) {
            
            const itemValue:any = item
            
            
  
            if (key === 'updatedAt') {
              updatedAt = itemValue
            } else {
              
              lineItems.push({
                quantity: "1", 
                name: itemValue.item.title,
                image: itemValue.item.imageUrl,
                description: itemValue.item.description,
                price: itemValue.item.price,
                basePriceMoney: {
                  amount: itemValue.item.price,
                  currency: 'USD',
                },
              })
            }
            
          }

          client.ordersApi.createOrder({
            order: {
              locationId: requestParams.location_id,
              referenceId: response.result.payment.orderId,
              lineItems: lineItems,
              idempotencyKey: requestParams.idempotency_key,
            },
          })
          
          const orderRef = admin.database().ref('orders/' + orderId)
        
          await orderRef.set({
            squareOrderId: response.result.payment.orderId,
            orderId: orderId,
            lineItems: lineItems,
            squareUpdatedAt: response.result.payment.updatedAt,
            updatedAt: updatedAt,
            billing: requestParams.billing,
            orderLocaleDate: requestParams.orderLocaleDate,
            totalPrice: requestParams.amount,
            shipping: requestParams.shipping,
            emailAddress: requestParams.emailAddress,
            squarePaymentId: response.result.payment.id,
            receiptNumber: response.result.payment.receiptNumber,
            receiptUrl: response.result.payment.receiptUrl,
          })

        }).catch(errorData => {
          res.json({error: errorData})
        })
     }
    })

    
  } catch(error) {
    let errorResult = null;
    if (error instanceof ApiError) {
      errorResult = error.errors;
    } else {
      errorResult = error;
    }
    res.status(500).json({
      'title': 'Payment Failure',
      'result': errorResult,
    });
  }
});
```

Let's go through a few lines to setup. We want to store the request body in a variable. We also want to create a unique order number. Also, for our order, we need to retrieve the line items from the cart and process them. And lastly, we want to create an instance of the square payments API using the Square SDK.

```javascript
  const requestParams = req.body;
  const orderId = uuidv4()
  let lineItems :any = [];
  
  const paymentsApi = client.paymentsApi;
```

After we have all of this initial code, we want to create the body for the payment because we are using the cloud function to create another post to Square:

```javascript
  const requestBody = {
    sourceId: requestParams.nonce,
    amountMoney: {
      amount: requestParams.amount,
      currency: 'USD',
    },
    order_id: orderId,
    locationId: requestParams.location_id,
    idempotencyKey: requestParams.idempotency_key,
    buyer_email_address: requestParams.emailAddress,
    billing_address: {
      first_name: requestParams.billing.firstName,
      last_name: requestParams.billing.lastName,
      address_1: requestParams.billing.address1,
      address_2: requestParams.billing.address2,
      locality: requestParams.billing.city,
      postal_code: requestParams.billing.zip,
    },
    shipping_address: {
      first_name: requestParams.shipping.firstName,
      last_name: requestParams.shipping.lastName,
      address_1: requestParams.shipping.address1,
      address_2: requestParams.shipping.address2,
      locality: requestParams.shipping.city,
      postal_code: requestParams.shipping.zip,
    },
    statement_description_identifier: orderId,
    verification_token: requestParams.buyerVerificationToken,
  };
```

All of these key value pairs are sent to the payments API. When Square processes the payment, it will keep hold of this as part of the record. In my case, I want to also send information to my database to keep record of the order that was processed. We will cover that later. So now I create a try block to process the payment. 

```javascript
  try {
    const response = await paymentsApi.createPayment(requestBody);
    res.status(200).json({
      'title': 'Payment Successful',
      'result': response.result,
    });
```

And for the last step, I retrieve the contents of the shopping cart to create an order in my database:

```javascript
    jwt.verify(requestParams.uid, 'jwt_secret', async (err :any , data :any) => {
      if(err){
        res.sendStatus(403)
      } 
      else if(data.uid){
        req.uid = data.uid

        const cartsRef = admin.database().ref('carts/' + data.uid)
        cartsRef.once('value').then(async snap => {
          const cartData = snap.val()
          
          let updatedAt;
          for (const [key, item] of Object.entries(cartData)) {
            
            const itemValue:any = item

            if (key === 'updatedAt') {
              updatedAt = itemValue
            } else {
              
              lineItems.push({
                quantity: "1", 
                name: itemValue.item.title,
                image: itemValue.item.imageUrl,
                description: itemValue.item.description,
                price: itemValue.item.price,
                basePriceMoney: {
                  amount: itemValue.item.price,
                  currency: 'USD',
                },
              })
            }
            
          }

          const orderRef = admin.database().ref('orders/' + orderId)
        
          await orderRef.set({
            squareOrderId: response.result.payment.orderId,
            orderId: orderId,
            lineItems: lineItems,
            squareUpdatedAt: response.result.payment.updatedAt,
            updatedAt: updatedAt,
            billing: requestParams.billing,
            orderLocaleDate: requestParams.orderLocaleDate,
            totalPrice: requestParams.amount,
            shipping: requestParams.shipping,
            emailAddress: requestParams.emailAddress,
            squarePaymentId: response.result.payment.id,
            receiptNumber: response.result.payment.receiptNumber,
            receiptUrl: response.result.payment.receiptUrl,
          })

        }).catch(errorData => {
          res.json({error: errorData})
        })
     }
    })

    
  }
```

After this, I do the catch block if the order was not successful:

```javascript
catch(error) {
    let errorResult = null;
    if (error instanceof ApiError) {
      errorResult = error.errors;
    } else {
      errorResult = error;
    }
    res.status(500).json({
      'title': 'Payment Failure',
      'result': errorResult,
    });
  }
```

In a future post, I will try to tackle how to send the order confirmation. I hope this was informative. Happy coding!