How to Email an Invoice After Payments Are Complete
Tags: payments, api, invoice, node
Published: 2023-08-15 20:06:33

*By Kevin Kimani*

Invoice management is a process that involves generating, sending, tracking, and reconciling invoices for goods or services that a customer has purchased. Invoice management is significant for e-commerce customers as it allows them to track their financial transactions and ensure that they were billed correctly. It also helps the customers to stay organized and be able to manage their finances more effectively.

Rapyd Collect is an API provided by [Rapyd](https://www.rapyd.net/), a fintech company that offers a global payment processing platform. The Rapyd Collect API allows businesses from around the world to easily collect payments from their clients via several payment methods, including bank transfers, bank redirect, card, cash, and local eWallet. 

With the Rapyd Collect API, you can build an e-commerce platform that allows you to create a product on Rapyd, collect payments via a Rapyd checkout page, create an invoice for the product purchased, and integrate a mailer service to automatically send the invoice to the customer once the payment is complete.

In this tutorial, you‘ll learn to create and send custom invoices using the Rapyd Collect API. To do so, you will:

- Create a Node.js web app that contains a product page
- Integrate the Rapyd checkout flow
- Use the Rapyd Collect API to create a product (service) in Rapyd, set up a plan and subscription, and create an invoice containing the items that the client subscribes to
- Integrate a mailer service (Mailgun) to automatically send the invoice to the client once the payments are complete

You can find the code for this tutorial in [this GitHub repo]( https://github.com/Rapyd-Samples/rapyd-checkout-invoice).

## Implementing Automated Invoice Emails After Payment

To follow along in implementing the system described above, you’ll need:

- Node.js, npm, a code editor, and a browser installed on your local machine.
- A [Rapyd](https://dashboard.rapyd.net/sign-up) account.
- [Postman](https://www.postman.com/downloads/) set up on your local machine. You can also use the [web version](https://identity.getpostman.com/signup).
- A [Mailgun](https://www.mailgun.com/) account. [Sign up](https://signup.mailgun.com/new/signup) for a free trial and be sure to verify your email. 

You also need to head over to the [Rapyd Docs](https://docs.rapyd.net/en/make-your-first-api-call.html) and learn how to make your first API call.

### Setting Up the Development Environment

To get started, create a folder called `rapyd-invoice`. Open it in your code editor and run the following command in the terminal:

```bash
npm init -y
```

This command will initialize a Node.js project in the `rapyd-invoice` directory and create a `package.json` file. Next, run the command below to install the dependencies that will be used in this project:

```bash
npm i express dotenv ejs node-persist nodemon body-parser mailgun-js
```

These dependencies include:

- `express` - to set up a Node.js server.
- `dotenv` - to load environment variables.
- `ejs`- to serve as the templating engine.
- `node-persist` - to create local storage on the server.
- `nodemon` - to monitor changes in the server code and restart it automatically.
- `body-parser` - to parse incoming request bodies.
- `mailgun-js` - to mail the invoice to the customer.

Next, modify the `package.json` `scripts` key-value pair with the following to include a command to start the server:

```js
  "scripts": {
    "start": "nodemon index.js"
  },
```

Then create a `utils.js` file in the project root folder and add the code below, which is provided by Rapyd to help in interacting with their APIs:

```js
require('dotenv').config({path: './.env'});

const https = require('https');
const crypto = require('crypto');
const accessKey = process.env.RAPYD_ACCESS_KEY
const secretKey = process.env.RAPYD_SECRET_KEY
const log = false;

async function makeRequest(method, urlPath, body = null) {

    try {
   	 httpMethod = method;
   	 httpBaseURL = "sandboxapi.rapyd.net";
   	 httpURLPath = urlPath;
   	 salt = generateRandomString(8);
   	 idempotency = new Date().getTime().toString();
   	 timestamp = Math.round(new Date().getTime() / 1000);
   	 signature = sign(httpMethod, httpURLPath, salt, timestamp, body)

   	 const options = {
   		 hostname: httpBaseURL,
   		 port: 443,
   		 path: httpURLPath,
   		 method: httpMethod,
   		 headers: {
       		 'Content-Type': 'application/json',
       		 salt: salt,
       		 timestamp: timestamp,
       		 signature: signature,
       		 access_key: accessKey,
       		 idempotency: idempotency
   		 }
   	 }

   	 return await httpRequest(options, body, log);
    }
    catch (error) {
   	 console.error("Error generating request options");
   	 throw error;
    }
}

function sign(method, urlPath, salt, timestamp, body) {

    try {
   	 let bodyString = "";
   	 if (body) {
   		 bodyString = JSON.stringify(body);
   		 bodyString = bodyString == "{}" ? "" : bodyString;
   	 }

   	 let toSign = method.toLowerCase() + urlPath + salt + timestamp + accessKey + secretKey + bodyString;
   	 log && console.log(`toSign: ${toSign}`);

   	 let hash = crypto.createHmac('sha256', secretKey);
   	 hash.update(toSign);
   	 const signature = Buffer.from(hash.digest("hex")).toString("base64")
   	 log && console.log(`signature: ${signature}`);

   	 return signature;
    }
    catch (error) {
   	 console.error("Error generating signature");
   	 throw error;
    }
}

function generateRandomString(size) {
    try {
   	 return crypto.randomBytes(size).toString('hex');
    }
    catch (error) {
   	 console.error("Error generating salt");
   	 throw error;
    }
}

async function httpRequest(options, body) {

    return new Promise((resolve, reject) => {

   	 try {
  		 
   		 let bodyString = "";
   		 if (body) {
       		 bodyString = JSON.stringify(body);
       		 bodyString = bodyString == "{}" ? "" : bodyString;
   		 }

   		 log && console.log(`httpRequest options: ${JSON.stringify(options)}`);
   		 const req = https.request(options, (res) => {
       		 let response = {
           		 statusCode: res.statusCode,
           		 headers: res.headers,
           		 body: ''
       		 };

       		 res.on('data', (data) => {
           		 response.body += data;
       		 });

       		 res.on('end', () => {

           		 response.body = response.body ? JSON.parse(response.body) : {}
           		 log && console.log(`httpRequest response: ${JSON.stringify(response)}`);

           		 if (response.statusCode !== 200) {
               		 return reject(response);
           		 }

           		 return resolve(response);
       		 });
   		 })
  		 
   		 req.on('error', (error) => {
       		 return reject(error);
   		 })
  		 
   		 req.write(bodyString)
   		 req.end();
   	 }
   	 catch(err) {
   		 return reject(err);
   	 }
    })

}

exports.makeRequest = makeRequest;
```

Next, create an `.env` file in the project root folder and paste the following content:

```bash
RAPYD_ACCESS_KEY=
RAPYD_SECRET_KEY=
MAILGUN_API_KEY=
MAILGUN_DOMAIN=
MAILGUN_SENDER_EMAIL=
```

To get the values for the Rapyd access and secret keys, follow the instructions provided [here](https://docs.rapyd.net/en/make-your-first-api-call.html) under Get Your API Keys.

To get the values for the Mailgun fields, log into your Mailgun account and click on **Sending** on the sidebar. Select **Domains**, copy the domain provided, and assign it to `MAILGUN_DOMAIN`.

![An image showing Mailgun domains](https://i.imgur.com/m9PVBqY.png)

Next, click on your account name in the top right corner. In the dropdown that appears, select **API keys**. Copy the value of **Private API key** and assign it to `MAILGUN_API_KEY`.

![An image showing Mailgun API keys](https://i.imgur.com/rBqJPSS.png)

For the `MAILGUN_SENDER_EMAIL`, use the email you used to create your Mailgun account.

Finally, create an `index.js` file in the project root folder and add the code below to create a basic Express server:

```js
// import the required dependencies
require('dotenv').config({path: './.env'});

const express = require('express')
var bodyParser = require('body-parser')
const storage = require('node-persist');

var mailgun = require('mailgun-js')({apiKey: process.env.MAILGUN_API_KEY, domain: process.env.MAILGUN_DOMAIN});

storage.init()

const app = express()

// set the port
const port = 3000

// import the utility file for making requests to the Rapyd API
const makeRequest = require('./utils').makeRequest;

// variables
const planId = "<your-plan-id>"

//set the render engine
app.set("view engine", "ejs")

// parse application/json
app.use(bodyParser.json())

//Add routes here

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
  })
```

You can confirm that the server is running successfully by running the following command in the terminal:

```bash
npm start
```

### Creating the Product Page

The product page will show the details of the service, as well as a form where the customer will input their details. Before implementing this page, you’ll need to create a [product](https://docs.rapyd.net/en/product.html) and a [plan](https://docs.rapyd.net/en/plan-364800.html) in Rapyd.

With Rapyd, a product can be of type `goods` or `service`. For this tutorial, you’ll be creating a `service` product. To learn more about `goods`, you can check out the [official documentation](https://docs.rapyd.net/en/product.html). A `service` product needs to be attached to a plan that describes the pricing structure of the service. The plan is then attached to a [subscription](https://docs.rapyd.net/en/subscription-364807.html).

To [create a product](https://docs.rapyd.net/build-with-rapyd/reference/product#create-product) of type `service`, you will use the [Rapyd Postman collection](https://bit.ly/3vxY1aG). Open the collection in either Postman for Web or the Postman app installed on your local machine.

Once you load the collection, you should have something similar to this:

![An image showing the loaded postman collection](https://i.imgur.com/Age0L3o.png)

Next, you need to create a Rapyd environment containing the Rapyd access and secret keys, as well as the `base uri` where you will be sending the API requests. Click on **Environments** in the left sidebar and create a new environment containing the required fields. You should end up with something like this:

![An image showing the created Rapyd environment](https://i.imgur.com/ypuUcGx.png)

Then head back to the **Collections** tab and load the newly created environment in the top right corner:

![An image showing the Postman collection with the loaded environment](https://i.imgur.com/M0V0j3L.png)

Next, from the Rapyd API Collection, search for "create services". From the results, click on **Create Services**, and in the tab that opens, send a POST request with the following request body:

```json
{
    "id": "",
    "name": "Monthly Parking Service Booking",
    "type": "services",
    "active": true,
    "description": "Sign up for our monthly parking service and enjoy the convenience of having a guaranteed spot every day",
    "images": [
   	 "https://images.unsplash.com/photo-1506521781263-d8422e82f27a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
    ],
    "shippable": false,
    "statement_descriptor": "",
    "unit_label": ""
}
```

More information about each of these fields is discussed in the [official documentation](https://docs.rapyd.net/en/create-product.html).

From the response received, copy `data.id`, which represents the `product_id`, as it will be used to create a plan.

![An image showing how to create a service](https://i.imgur.com/0OF2715.png)

To create a plan, you’ll use Postman. From the Rapyd API collection, search for "create plan". From the search results, select the **Create plan with product ID** option. From the tab that opens, send a POST request with the following request body:

> Remember to replace `your-product-id` with the corresponding product ID that you received from the previous step.

```json
{
	"currency": "USD",
	"interval": "month",
            “amount”: 1150,
	"product": "your-product-id",
	"aggregate_usage": "",
	"billing_scheme": "per_unit",
	"nickname": "basic",
	"transform_usage": {},
	"trial_period_days": 0,
	"usage_type": "licensed"
}
```

More information about each of these fields is discussed in the [official documentation](https://docs.rapyd.net/en/create-plan.html).

![An image showing how to create a plan](https://i.imgur.com/VfMc1lB.png)

Copy the plan ID from the response, as you will use it to query the plan and the product and display the data on a web page. Open the `index.js` file and assign your plan ID to the const `planId` that has been defined already.

Now you can create a route to query for the product and plan information and return it to the frontend. Add the following code to the `index.js` file:

> Make sure you add the code before the `app.listen()` function.

```js
// Route to query the plan and return plan+product details
app.get('/', async (req, res) => {
    try {
   	 const { body: { data } } = await makeRequest('GET', `/v1/plans/${planId}`);

   	 await storage.setItem('productPrice', data.amount)

   	 res.render('product', {
   		 title: data.product.name,
   		 price: data.amount,
   		 description: data.product.description,
   		 image: data.product.images[0]
   	 })
    } catch (error) {
   	 console.log(error)
    }
})
```

In the code above, you:

- Make a GET request to the Rapyd Collect API to retrieve the plan information.
- Save the product price to the local storage.
- Extract the required data and return it to the client along with the template that will be rendered when a request is made to this route.

To create the `product` template, create a folder called `views` in the project root folder. Inside it, create a `product.ejs` file and add the following code:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Product Page</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
    <style>
   	 .hidden{
   		 display: none;
   	 }
    </style>
</head>
  <body class="container-sm text-center">
    <!-- Display product -->
    <div class="mt-3 d-flex flex-row">
   	 <img src=<%=image%> alt="Product image" style="max-width: 450px;">

   	 <div class="px-5 py-3 text-start d-flex flex-column justify-content-between align-items-start">
   		 <h2 class="title"><%=title%></h2>
   		 <p class="description"><%=description%></p>
   		 <p class="price"><strong>Price:</strong> $<%=price%> (Billed monthly)</p>
   		 <button class="btn btn-primary" id="purchaseBtn">Book Now</button>
   	 </div>
    </div>

    <!-- Customer details form -->
    <div class="mt-5 hidden" id="custContainer">
   	 <hr>
   	 <h3>Customer Details</h3>
   	 <form class="text-start row g-3">
   		 <h5>Personal Details</h5>
   		 <div class="col-md-4">
     		 <label for="custName" class="form-label">Name</label>
     		 <input type="text" class="form-control" id="custName" value="Jane Doe">
   		 </div>
   		 <div class="col-md-4">
     		 <label for="custEmail" class="form-label">Email</label>
     		 <input type="text" class="form-control" id="custEmail">
   		 </div>
   		 <div class="col-md-4">
       		 <label for="custPhone" class="form-label">Phone</label>
       		 <input type="text" class="form-control" id="custPhone" value="+14155559993">
   		 </div>

   		 <h5>Payment Details</h5>
   		 <div class="col-3">
     		 <label for="custCardNo" class="form-label">Card Number</label>
     		 <input type="text" class="form-control" id="custCardNo" placeholder="4111 1111 1111 1111" value="4111111111111111">
   		 </div>
   		 <div class="col-3">
       		 <label for="custCardExpMonth" class="form-label">Expiry Month</label>
       		 <input type="number" class="form-control" id="custCardExpMonth" placeholder="12" value="12">
   		 </div>
   		 <div class="col-3">
       		 <label for="custCardExpYear" class="form-label">Expiry Year</label>
       		 <input type="number" class="form-control" id="custCardExpYear" placeholder="34" value="34">
   		 </div>
   		 <div class="col-3">
       		 <label for="custCardCVV" class="form-label">CVV</label>
       		 <input type="number" class="form-control" id="custCardCVV" placeholder="567" value="567">
   		 </div>

   		 <div class="col-12">
     		 <button type="submit" id="checkoutBtn" class="btn btn-primary">Checkout</button>
   		 </div>
 		 </form>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
    <script>
   	 const purchaseBtn = document.getElementById('purchaseBtn')
   	 const custContainer = document.getElementById('custContainer')
   	 const checkoutBtn = document.getElementById("checkoutBtn")

   	 purchaseBtn.addEventListener('click', () => {
   		 custContainer.classList.remove('hidden')
   		 purchaseBtn.setAttribute('disabled', 'true')
   		 purchaseBtn.innerText = 'Provide your details below'
   	 })

   	 custContainer.addEventListener('submit', (e) => {
   		 e.preventDefault()

   		 const customerDetails = {
   		 name: document.getElementById('custName').value,
   		 email: document.getElementById('custEmail').value,
   		 phone: document.getElementById('custPhone').value,
   		 cardNo: document.getElementById('custCardNo').value,
   		 cardExpMonth: document.getElementById('custCardExpMonth').value,
   		 cardExpYear: document.getElementById('custCardExpYear').value,
   		 cardCVV: document.getElementById('custCardCVV').value
   	 }

   		 console.log(customerDetails)

   		 fetch('http://localhost:3000/', {
       		 method: 'POST',
       		 headers: {
           		 'Content-Type': 'application/json'
       		 },
       		 body: JSON.stringify({
           		 customerDetails: customerDetails
       		 })
   		 }).then(res => {
           console.log(res)
           if(res.status === 200){
               window.location.assign('http://localhost:3000/checkout')
           }
     })

   	 })
    </script>
  </body>
</html>
```

The code above renders a template that displays the product information. It also has a button called **Book Now**, which, when clicked, displays a form for the customer to fill in their details. The form has a **Checkout** button, which sends the form data to the server and redirects the customer to the `/checkout` route when clicked.

Next, you need to create a route that will receive these customer details and use them to [create a customer](https://docs.rapyd.net/build-with-rapyd/reference/customer#create-customer) on Rapyd. Open `index.js` and add the following route:

```js
// Route to receive client details and create a customer on Rapyd
app.post('/', async (req, res) => {
    //create a customer using the payment details from the request body
    try {
    const body = {
      name: req.body.customerDetails.name,
      email: req.body.customerDetails.email,
      phone_number: req.body.customerDetails.phone,
      payment_method: {
   	 type: 'us_debit_visa_card',
   	 fields: {
 		 number: req.body.customerDetails.cardNo,
 		 expiration_month: req.body.customerDetails.cardExpMonth,
 		 expiration_year: req.body.customerDetails.cardExpYear,
 		 cvv: req.body.customerDetails.cardCVV
   	 }
      }
    }
    const { body: { data } }  = await makeRequest('POST', '/v1/customers', body)

    await storage.setItem('customerId', data.id)
    await storage.setItem('defaultPaymentMethod', data.default_payment_method)
    await storage.setItem('customerEmail', data.email)

    res.status(200).json({ success: true })

    } catch (error) {
      console.log(error)
    }
})
```

The code above creates a customer with a default payment method. From the API response, it extracts the customer ID, default payment method, and customer email and saves the details to local storage.

### Creating the Checkout Page

Rapyd Checkout is available as a [hosted page]https://docs.rapyd.net/en/hosted-checkout-page-integration.html) and as a [toolkit](https://docs.rapyd.net/en/checkout-toolkit-integration.html). This tutorial implements the checkout toolkit integration.

To create a checkout page, you first have to make a POST request to the Rapyd Collect API with the required fields. From the API response, you need to extract the checkout ID and send it to the frontend. For the `expiration` date, make sure you add a future date (epoch timestamp) that is not more than one year from today’s date.  To do so, add the following code to your `index.js` file:

```js
// Route to create a checkout page and return the checkout ID to the client
app.get('/checkout', async (req, res) => {
    let customerId = await storage.getItem('customerId')
    let productPrice = await storage.getItem('productPrice')
    
    try {
   	 const body = {
 		 amount: productPrice,
 		 country: 'US',
 		 currency: 'USD',
 		 customer: customerId,
 		 language: 'en',
 		 expiration: 1704019927,
   	 };

   	 const { body: { data }} = await makeRequest('POST', '/v1/checkout', body);
 
   	 res.render('checkout', {
 		 checkoutId: data.id
      })
    
      } catch (error) {
   	 console.error('Error completing request', error);
      }
})
```

Next, create a file called `checkout.ejs` in the `views` folder and paste in the code below:

```js
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Checkout Page</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
  <body class="container-sm text-center">
    <div class="cont">
   	 <h1>Checkout page</h1>

   	 <!-- iframe -->
   	 <div id="rapyd-checkout"></div>
    </div>

    <!-- code to display the iframe -->
    <script src="https://sandboxcheckouttoolkit.rapyd.net"></script>
    <script>
   	 window.onload = function () {
   		 let checkout = new RapydCheckoutToolkit({
       		 pay_button_text: "Click to pay",
       		 pay_button_color: "blue",
       		 id: "<%=checkoutId%>"
   		 });
   		 checkout.displayCheckout();
   	 }
   	 window.addEventListener('onCheckoutPaymentSuccess', function (event) {
   		 console.log(event.detail)
   		 console.log(typeof(event.detail.status))
   		 alert('payment successful')
  		 
   		 window.location.assign('http://localhost:3000/verification')
  		 
   	 });
   	 window.addEventListener('onCheckoutPaymentFailure', function (event) {
   		 console.log(event.detail.error)
   		 alert('error while processing your payment')
   	 });
    </script>
  </body>
</html>
```

The code above uses the `checkoutId` from the server to render a checkout page containing the details of the customer and the amount to be paid.

On successful payment, the page displays an alert message: "payment successful". When the customer clicks on the **OK** button, they are redirected to the verification page.

### Creating the Verification Page

On this page, the customer needs to perform 3DS authentication. Since this guide covers working in the sandbox, 3DS authentication will only be simulated. 3DS authentication is only relevant where the payment method is `card`. The Rapyd 3DS simulator helps to authenticate the card so that the payment can be completed.

To render a verification page when the customer is navigated there, add the following route to the `index.js` file:

```js
app.get('/verification', async (req, res) => {
    let customerId = await storage.getItem('customerId')
    let defaultPaymentMethod = await storage.getItem('defaultPaymentMethod')

    // Create subscription
    try {
   	 const subscriptionBody = {
 		 customer: customerId,
 		 billing: 'pay_automatically',
 		 billing_cycle_anchor: '',
 		 cancel_at_period_end: true,
 		 coupon: '',
 		 days_until_due: null,
 		 payment_method: defaultPaymentMethod,
 		 subscription_items: [
   		 {
     		 plan: planId,
     		 quantity: 1
   		 }
 		 ],
 		 tax_percent: 10.5,
 		 plan_token: ''
   	 };

   	 const { body: { data }} = await makeRequest('POST', '/v1/payments/subscriptions', subscriptionBody);

   	 await storage.setItem('subscriptionId', data.id)
  	 
   	 // create invoice
   	 try {
   		 let subscriptionId = await storage.getItem('subscriptionId')
   		 const invoiceBody = {
     		 customer: customerId,
     		 billing: 'pay_automatically',
     		 days_until_due: null,
     		 description: '',
     		 due_date: 0,
     		 metadata: {
       		 merchant_defined: true
     		 },
     		 statement_descriptor: '',
     		 subscription: subscriptionId,
     		 tax_percent: '',
     		 currency: 'USD'
   		 };
   		 const { body: { data }} = await makeRequest('POST', '/v1/invoices', invoiceBody);

   		 await storage.setItem('invoiceId', data.id)
  	 
   		 console.log(data);

   		 // create invoice items
   		 try {
       		 let customerId = await storage.getItem('customerId')
       		 let invoiceId = await storage.getItem('invoiceId')
       		 let subscriptionId = await storage.getItem('subscriptionId')
		let productPrice = await storage.getItem('productPrice')

       		 const invoiceItemBody = {
         		 currency: 'USD',
         		 customer: customerId,
         		 invoice: invoiceId,
         		 plan: planId,
         		 metadata: {
           		 merchant_defined: true
         		 },
         		 amount: productPrice
       		 };
       		 const { body: { data }} = await makeRequest('POST', '/v1/invoice_items', invoiceItemBody);
  		 
       		 console.log(data);
     		 } catch (error) {
       		 console.error('Error completing request', error);
     		 }
 		 } catch (error) {
   		 console.error('Error completing request', error);
 		 }
 		 } catch (error) {
   	 console.error('Error completing request', error);
      }

    // finalize invoice
    let invoiceId = await storage.getItem('invoiceId')
    try {
   	 const { body: { data }} = await makeRequest(
 		 'POST',
 		 `/v1/invoices/${invoiceId}/finalize`
   	 );
    
   	 console.log(data);

   	 res.render('verification', {
   		 authLink: data.payment.redirect_url
   	 })

      } catch (error) {
   	 console.error('Error completing request', error);
      }
})
```

In the code above, you:

- [Create a subscription](https://docs.rapyd.net/en/create-subscription.html) and save the `subscriptionId` from the API response in local storage. 
- [Create an invoice](https://docs.rapyd.net/en/create-invoice.html) using the `subscriptionId` and save the `invoiceId` from the API response in local storage. 
     - Note: An invoice has a few statuses, as discussed [here](https://docs.rapyd.net/en/create-invoice.html) under the Response Parameters. The invoice you just created is in a draft state. In this state, you edit the invoice and add invoice items. You will later finalize the invoice to mark it as "paid" and complete the payment associated with it.
- [Create invoice items](https://docs.rapyd.net/en/create-invoice-item.html) using the `invoiceID` and add them to the invoice.
- [Finalize the invoice](https://docs.rapyd.net/en/finalize-invoice.html) by making a POST request with the `invoiceId`. From the API response received, you extract the `redirect_url` to simulate 3DS authentication and send it to the front end.

Next, you need to create the `verification` page that the customer can use to simulate 3DS authentication. Create a `verification.ejs` file in the `views` folder and add the code below:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Verification Page</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
  <body class="container-sm text-center">
    <div class="cont">
   	 <h1>3DS Verification</h1>
   	 <!-- <p>Open the link below in a new page to simulate 3DS authentication</p> -->
   	 <p>
 		 <a href="<%=authLink%>" target="_blank" id="auth">Simulate 3DS Authentication</a>
   	 </p>
   	 <p>Once you're done, come back to this page and select "Generate Invoice".</p>
   	 <button class="btn btn-primary" id="generateBtn" disabled>Generate Invoice</button>
    </div>

    <script>
   	 const generateBtn = document.getElementById('generateBtn')

   	 document.getElementById('auth').addEventListener('click', () => {
 		 generateBtn.removeAttribute('disabled')
   	 })

   	 generateBtn.addEventListener('click', () => {
   		 window.location.assign('http://localhost:3000/invoice')
   	 })
    </script>
  </body>
</html>

```

The code above renders an HTML file that instructs the customer to click the link displayed, perform 3DS simulation, and then return to the page and click the **Generate Invoice** button. This button redirects the customer to the invoice page.

### Creating the Invoice Page

This page will display the invoice data to the customer. To create it, you first need the server code to retrieve the invoice and the invoice items. Open `index.js` and add the following route:

```js
// Retrieve invoice, line items => return line items to client
app.get('/invoice', async (req, res) => {
    let custEmail = await storage.getItem('customerEmail')

    let item
    let quantity
    let subTotal
    let tax
    let total
    let status

    // retrieve invoice
    try {
   	 let invoiceId = await storage.getItem('invoiceId')

   	 console.log(invoiceId)

   	 const { body: { data }} = await makeRequest(
 		 'GET',
 		 `/v1/invoices/${invoiceId}`
   	 );

   	 console.log(data)

   	 quantity = data.lines.length
   	 subTotal = data.subtotal
   	 tax = data.tax
   	 total = data.total
   	 status = data.status

   	 // retrieve subscription
   	 try {
   		 const subscriptionId = await storage.getItem('subscriptionId')
   		 const { body: { data }} = await makeRequest(
     		 'GET',
     		 `/v1/payments/subscriptions/${subscriptionId}`
   		 );
  	 
   		 item = data.subscription_items.data[0].plan.product.name;

   		 // console.log(data.subscription_items.data[0].plan.product)

 		 } catch (error) {
   		 console.error('Error completing request', error);
 		 }
      } catch (error) {
   	 console.error('Error completing request', error);
      }

      // send email
      let invoiceId = await storage.getItem('invoiceId')
      var data = {
      from: `Monthly Booking Services <${process.env.MAILGUN_SENDER_EMAIL}>`,
      to: custEmail,
      subject: 'Monthly Booking Services Invoice',
      html: `<h1>Monthly Booking Services</h1><h3>Your Invoice ID: ${invoiceId}</h3><p>Item: ${item}</p><p>Quantity: ${quantity}</p><p>Tax: ${tax}</p><p>Total: ${total}</p><p>Invoice status: ${status}</p>`
      };
	 
      mailgun.messages().send(data, function (error, body) {
   	 if(error){
 		 console.log(error)
   	 }
      console.log(body);
      });

      res.render('invoice', {
   	 item,
   	 quantity,
   	 subTotal,
   	 tax,
   	 total,
   	 status,
   	 custEmail
      })
})
```

The code above retrieves the invoice and the subscription associated with the invoice to extract the name of the product the customer has subscribed to. All this information is then returned to the front end. The code also includes the logic to send the invoice details to the customer’s email via Mailgun.

Create a file called `invoice.ejs` inside the `views` folder and add the following code to render a page that displays the invoice data to the customer:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Invoice Page</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
  <body class="container-sm text-center">
    <div class="cont">
   	 <h2>Your Invoice</h2>
   	 <table class="table">
   		 <thead>
     		 <tr>
       		 <th scope="col">#</th>
       		 <th scope="col">Item</th>
       		 <th scope="col">Quantity</th>
       		 <th scope="col">Rate</th>
     		 </tr>
   		 </thead>
   		 <tbody>
     		 <tr>
       		 <th scope="row">1</th>
       		 <td><%=item%></td>
       		 <td><%=quantity%></td>
       		 <td>$<%=subTotal%></td>
     		 </tr>

     		 <tr>
       		 <td colspan="3" class="text-end">Sub Total</td>
       		 <td>$<%=subTotal%></td>
     		 </tr>
     		 <tr>
       		 <td colspan="3" class="text-end">Tax</td>
       		 <td>$<%=tax%></td>
     		 </tr>
     		 <tr>
       		 <td colspan="3" class="text-end">Total</td>
       		 <td>$<%=total%></td>
     		 </tr>
     		 <tr>
       		 <td colspan="3" class="text-end">Invoice Status</td>
       		 <td class="bg-success text-white"><%=status%></td>
     		 </tr>
   		 </tbody>
 		 </table>

 		 <p class="bg-primary text-white py-4 px-2">Invoice successfully sent to: <%=custEmail%></p>
    </div>
  </body>
</html>
```

### Testing the Application

To confirm that the application is working as expected, run `npm start` in the terminal and open `http://localhost:3000` on your browser. You should see the product page:

![Product page](https://i.imgur.com/vQnZNYJ.png)

When you click the **Book Now** button, the customer details form, which contains some placeholder values for testing purposes, is displayed:

![Full product page](https://i.imgur.com/D8S7HB1.png)

Enter the customer email (make sure to use your own personal email that you used to register your Mailgun account) and click on the **Checkout** button. You will be redirected to the Checkout page. Once you’re there, select the **Click to pay** button and click **OK** on the alert upon successful payment. 

![Checkout page](https://i.imgur.com/284NOJ0.png)

This will redirect you to the verification page. Select **Simulate 3DS Authentication**.

![Verification page](https://i.imgur.com/VCJ7u0i.png)

This opens a new tab. Enter the code provided and click the **Continue** button.

![3DS simulator page](https://i.imgur.com/nalA1SF.png)

Navigate back to the verification page and select **Generate Invoice**. This will redirect you to the invoice page and inform you that an email has been sent to the customer's email.

![Invoice page](https://i.imgur.com/ASuYVWh.png)

Open your inbox and you should see the received email.

![An image showing the received email](https://i.imgur.com/97qfOk7.png)

## Conclusion

In this article, you learned about Rapyd and the Rapyd Collect API. You also learned about various Rapyd Collect concepts, such as products, plans, subscriptions, invoices, and invoice items. Lastly, you learned how to combine all these concepts to create a web app that automatically sends an invoice to the customer once payments are complete via a mailer service.

[Rapyd](https://www.rapyd.net/) is a payment processing solution that helps you liberate global commerce with all the tools you need for payments, payouts, and business everywhere. It aims to make it easier for businesses to expand globally by simplifying the process of accepting and making payments in different countries and currencies. Rapyd’s developer-friendly API allows businesses to easily integrate their payment systems with the Rapyd network.