# Creating and Paying a Freight Invoice with the Rapyd API and FX

**URL:** https://community.rapyd.net/t/creating-and-paying-a-freight-invoice-with-the-rapyd-api-and-fx/59592
**Category:** 👓 Blog
**Tags:** collect-api, tech-blog
**Created:** [December 10, 2024, 6:24pm UTC](https://community.rapyd.net/t/creating-and-paying-a-freight-invoice-with-the-rapyd-api-and-fx/59592 "2024-12-10T18:24:16Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![Community\_Team](https://avatars.discourse-cdn.com/v4/letter/c/46a35a/32.png) [@Community\_Team](https://community.rapyd.net/u/Community_Team)
#### Post date: [December 10, 2024, 6:24pm UTC](https://community.rapyd.net/t/creating-and-paying-a-freight-invoice-with-the-rapyd-api-and-fx/59592/1 "2024-12-10T18:24:16Z")

</div>

![banner for creating and paying a freight invoice with Rapyd API and FX](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/f/c/fc09df4a2e8fe3f70516665a3fefbae2389ec4b2.jpeg)  
By: **Kevin Kimani**

A guaranteed refund system allows users to subscribe to a service with an assurance that they can cancel within a specified time frame if they’re dissatisfied and receive a full refund. This not only builds trust but also encourages potential subscribers to take the plunge, knowing they have an exit strategy if the service does not meet their expectations.

[Rapyd](https://www.rapyd.net/) is a leading global fintech platform, and its API capabilities extend to various financial operations, including subscriptions and refunds. With Rapyd’s [subscription](https://docs.rapyd.net/en/subscriptions.html) and [refund](https://docs.rapyd.net/en/refunds.html) features, you can create a powerful subscription-based service with a streamlined refund process, making it efficient and seamless for both the service provider and the subscriber.

In this tutorial, you’ll learn how to integrate Rapyd’s API to establish a guaranteed refund system for your subscription-based service using [Next.js](https://nextjs.org/). In this scenario, you’ll assume that you’re a service provider offering a subscription-based service with a weekly billing of $1.99. Your customer in the US can subscribe using the “us\_debit\_visa\_card” payment method, as per the available options [listed here](https://docs.rapyd.net/en/list-payment-methods-by-country.html). You’ll implement a function to allow subscribers to cancel within the first week for a full refund if they’re unsatisfied with the service.

## Prerequisites

To follow along, you’ll need the following:

- [Node.js](https://nodejs.org/en/download) and [git CLI](https://cli.github.com/), 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 [Postman web version](https://identity.getpostman.com/signup).
- Access to the [Rapyd Postman collection](https://bit.ly/3vxY1aG)
- [Learn how to make your request](https://docs.rapyd.net/en/make-your-first-api-call.html) to the Rapyd API.

### Cloning the Starter Template

To make it easy to follow along, you can clone and build on the premade starter template. To clone it to your local machine, execute the following:

```bash
git clone --single-branch -b starter-template https://github.com/kimanikevin254/rapyd-guaranteed-refund-system.git

```

This will create a folder named `rapyd-guaranteed-refund-system`. Execute the commands below to `cd` into the project folder, install the project dependencies, and run the Next.js application:

```bash
cd rapyd-guaranteed-refund-system && npm i && npm run dev

```

Once all the dependencies have been installed and the Next.js server is running, navigate to `http://localhost:3000/` on your browser, and you should see the login page.

 ![The login page](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/7/b/7bde1269fde4ff3b91e531837e3c31230e30d28d.png)

The starter template contains a basic authentication system that allows users to log in and sign up to access the subscription dashboard. It uses an SQLite database to store these credentials. The database has one user whose credentials are already prefilled in the login form. You can click the **Login** button to access the subscription dashboard.

 ![The subscription dashboard](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/9/e/9e6064637e46f55ec7bd0d32d05f5cd95c0a7471.png)

This dashboard contains hard-coded data that shows your current subscription. It also allows you to log out.

To build on this starter template and implement a guaranteed refund system with the Rapyd API, you’ll do the following:

1. Create a service and a plan.
2. Create a customer with a payment method and simulate 3DS authentication.
3. Create a subscription for a customer.
4. Enable the customer to cancel the subscription and request a refund.

### Creating a Service and a Plan

In Rapyd, a service is a type of [product](https://docs.rapyd.net/en/product.html) that is attached to a [plan](https://docs.rapyd.net/en/plan.html) that defines the pricing structure for the service. To create a service, you’ll use the Rapyd Postman collection. Once you import the collection into Postman, you should have something similar to this:

 ![Importing the Rapyd Postman collection](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/0/5/05c482414491ace56f5a9cdd5d359ace650f9cc2.png)

You now need to create an environment containing the Rapyd access and secret keys, as well as the base URI where you’ll send the API requests. Select **Environments** on the left sidebar and click **Create Environment** to create a new environment. Add the variables `rapyd_access_key`, `rapyd_secret_key`, and `base_uri`. For `base_uri`, provide the value `https://sandboxapi.rapyd.net/v1`. For `rapyd_access_key` and `rapyd_secret_key`, you can get their values by following the instructions provided in the [official documentation](https://docs.rapyd.net/en/make-your-first-api-call.html) under “Get Your API Keys”. Make sure you save the changes. After setting up your environment, you should end up with something like this:

 ![Rapyd refunds environment](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/9/6/9645c4b196e5b9ae65731aab8d594a2fca046608.png)

Select **Collections** from the left sidebar and load the newly created environment in the top-right corner. To create the service, search “create services” from the Rapyd API Postman collection. From the results, select “Create Services”. Replace the request body with the following:

```json
{
   "name": "CineView Unlimited",
   "type": "services",
   "active": true,
   "description": "Unlimited access to a vast library of movies"
}

```

The fields in the code above are as follows:

- `name` defines the name of the service that will be displayed to the customer.
- `type` indicates that this is a service.
- `active` indicates that this product is available for purchase.
- `description` provides details about the service.

Execute the POST request, and from the response you receive, take note of the product ID, which takes the form `product_ *********** `. You’ll use this to create a plan.

Next, from the Postman collection, search “create plan”, and from the results, select “Create Plan with product ID”. Replace the request body with the following:

```json
{
   "currency": "USD",
   "amount": "1.99",
   "interval": "week",
   "product": "product_ ***********",
   "billing_scheme": "per_unit",
   "nickname": "CineView Unlimited - Weekly Plan",
   "trial_period_days": 0,
   "metadata": {
   	"merchant_defined": true
   },
   "usage_type": "licensed"
}

```

In this JSON object, `interval`, `amount`, and `currency` indicate that the product defined by `product_id` is billed weekly at $1.99. `billing_scheme` has the value `per_unit`, which indicates that the specified amount is charged for each unit. `usage_type` is set to `licensed`, which means that the customer will be billed even when they have not used the service. `nickname` provides a full description of the plan.

Make sure you replace `product_ ***********` with the product ID you copied earlier. Execute the POST request, and from the response you receive, take note of the plan ID, which takes the form `plan_*********** `. You’ll use it to create a subscription for the customer.

### Creating a Customer and Simulating 3DS Authentication

You’ll now create the customer who will subscribe to your subscription-based service. To do this, you’ll modify the code in the starter template to incorporate the following logic:

1. When a user clicks the **Sign Up** button on your site, you will attempt to [create a customer on Rapyd](https://docs.rapyd.net/en/create-customer.html).
2. If the customer is successfully created on Rapyd, you will store their credentials in the SQLite database. This makes it possible to retrieve the Rapyd customer ID from the database when a user logs into your site.
3. After you store the details in the database, you will then [simulate 3DS authentication](https://docs.rapyd.net/en/simulating-3ds-authentication---api-method.html) to authenticate the user’s card.

#### Configuring the Starter Template

First, you need to configure a few things in the starter template so that your application can communicate with the Rapyd API. To do this, create the file **.env** in the project root folder and add the following:

```bash
RAPYD_ACCESS_KEY=<your-rapyd-access-key>
RAPYD_SECRET_KEY=<your-rapyd-secret-key>

```

Remember to replace the placeholder values with the corresponding values for the Rapyd secret and access keys that you obtained earlier.

Next, create a new directory named `utils` in the project root folder. In the newly created directory, create a **makeRequest.js** file and add the code below, which is provided by Rapyd to help in interacting with their APIs:

```js
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 {
   	let httpMethod = method;
   	let httpBaseURL = "sandboxapi.rapyd.net";
   	let httpURLPath = urlPath;
   	let salt = generateRandomString(8);
   	let idempotency = new Date().getTime().toString();
   	let timestamp = Math.round(new Date().getTime() / 1000);
   	let 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);
   	}
   });
}

export { makeRequest };

```

#### Creating a Customer on Rapyd

Next, you need to modify the code in the **app/api/auth/signup/route.js** file to add the functionality to create a customer on Rapyd.

Add the following import statement at the top of the file:

```js
import { makeRequest } from "@/utils/makeRequest";

```

Next, replace the code that is used to create the `users` table with the following:

```js
await db.run(
   `CREATE TABLE IF NOT EXISTS users (
   	id INTEGER PRIMARY KEY,
   	name TEXT,
   	email TEXT UNIQUE,
   	password TEXT,
   	rapyd_cust_id TEXT UNIQUE
   )`
);

```

The new code adds the `rapyd_cust_id` field, which stores the customer’s Rapyd ID.

Next, find the code below, which is contained in the same file:

```js
// Create the user
await db.run(`INSERT INTO users (name, email, password) VALUES (?, ?, ?)`, [
   data.fullname,
   data.email,
   await hashPassword(data.password), // You need to await the result of the asynchronous hashPassword function
]);

// Fetch the created user
const user = await db.get(`SELECT id, name, email FROM users WHERE email = ?`, [
   data.email,
]);

// Return the user data as a json response
return new Response(
   JSON.stringify({
   	user: {
       	id: user.id,
       	name: user.name,
       	email: user.email,
   	},
   }),
   {
   	headers: { "Content-Type": "application/json" },
   	status: 200,
   }
);

```

Replace this code with the following:

```js
// Create a customer in Rapyd
try {
   const body = {
   	name: data.fullname,
   	email: data.email,
   	payment_method: {
       	type: "us_debit_visa_card",
       	fields: {
           	number: data.card_number,
           	expiration_month: data.expiration_month,
           	expiration_year: data.expiration_year,
           	name: data.fullname,
           	cvv: data.cvv,
       	},
   	},
   	addresses: [
       	{
           	name: data.fullname,
           	line_1: data.address_line,
           	country: data.country,
           	zip: data.zip_code,
       	},
   	],
   };
   const result = await makeRequest("POST", "/v1/customers", body);

   // Create the user
   await db.run(
   	`INSERT INTO users (name, email, password, rapyd_cust_id) VALUES (?, ?, ?, ?)`,
   	[
       	data.fullname,
       	data.email,
       	await hashPassword(data.password), // Await the result of the asynchronous hashPassword function
       	result.body.data.id,
   	]
   );

   // Fetch the created user
   const user = await db.get(
   	`SELECT id, name, email, rapyd_cust_id FROM users WHERE email = ?`,
   	[data.email]
   );

   // Return the user data as a json response
   return new Response(
   	JSON.stringify({
       	user: {
           	id: user.id,
           	name: user.name,
           	email: user.email,
           	rapyd_cust_id: user.rapyd_cust_id,
           	card_auth_link:
               	result.body.data.payment_methods.data[0].redirect_url,
       	},
   	}),
   	{
       	headers: { "Content-Type": "application/json" },
       	status: 200,
   	}
   );
} catch (error) {
   console.error("Error completing request", error);

   // Return an error
   return new Response(
   	JSON.stringify({
       	error: "Unable to create the customer on Rapyd.",
   	}),
   	{
       	headers: { "Content-Type": "application/json" },
       	status: 400,
   	}
   );
}

```

The new code attempts to [create a customer with a payment method](https://docs.rapyd.net/en/create-customer.html) on Rapyd before storing the credentials in the SQLite database. Once a customer is successfully created on Rapyd, their credentials are stored in the database and the code returns a response with some data. In the returned data is `card_auth_link`, which contains a link used to simulate 3DS authentication. This link is extracted from the response received from the Rapyd API after creating the customer.

#### Passing the Necessary Props for 3DS Authentication

Now you need to update the **app/page.js** file to include a state that will store the 3DS authentication link returned from the API call and pass it down to the `Signup` component via the `FormSwitcher` component. The 3DS authentication link also needs to be passed down to the `Dashboard` component so that the 3DS authentication page can be displayed to users who have not authenticated their cards.

Add the following state to the **app/page.js** file:

```js
// Set the link to perform 3DS auth
const [cardAuthLink, setCardAuthLink] = useState(null)

```

You also need to modify the JSX as shown below to pass the state to the appropriate components:

```js
<div className='p-4 h-screen w-screen overflow-hidden'>
     {
       !user ?
       <FormSwitcher checkUserInfo={checkUserInfo} setCardAuthLink={setCardAuthLink} /> :
       <Dashboard checkUserInfo={checkUserInfo} cardAuthLink={cardAuthLink} setCardAuthLink={setCardAuthLink} />
     }
</div>

```

Open the **components/FormSwitcher.js** file and make sure you are destructuring the new props that you just passed down in the previous step:

```js
function FormSwitcher({ checkUserInfo, setCardAuthLink }) {
// Rest of the code

```

In the same file, make sure you are passing the `setCardAuthLink` function to the `Signup` component.

```js
<Signup checkUserInfo={checkUserInfo} setCardAuthLink={setCardAuthLink} />

```

Next, you need to modify the **components/Signup.js** file to use the `setCardAuthLink` function to update the `cardAuthLink` state with the 3DS authentication link received from the API call. In the **components/Signup.js** file, destructure all the props passed to this component as shown below:

```js
function Signup({ checkUserInfo, setCardAuthLink }) {

```

In the `createCustomer` function, add the following code above the `localStorage.setItem('user_info', JSON.stringify(data.user))` line of code:

```js
// cardAuthLink state update
if (data.user.card_auth_link) {
  setCardAuthLink(data.user.card_auth_link)
}

```

This code accesses the 3DS authentication link from the response and updates the `cardAuthLink` state.

### Checking If a Customer’s Card Is Authenticated

You need to implement an API endpoint that you will use to confirm if the customer’s card has been authenticated. Create a new file **retrievecustomer/route.js** in the `app/api/auth` folder and add the following code:

```js
import { makeRequest } from "@/utils/makeRequest";

export async function POST(req, res){
   const { customer_id } = await req.json()
  
   try {
       const result = await makeRequest('GET', `/v1/customers/${customer_id}`);
  
       const next_action = result.body.data.payment_methods.data[0].next_action

       return new Response(
           JSON.stringify({
               message: next_action,
           }),
           {
               headers: { "Content-Type": "application/json" },
               status: 200,
           }
       );
     } catch (error) {
       console.error("Error completing request", error);
       // Return an error
       return new Response(
           JSON.stringify({
               error: "Unable to retrieve the customer",
           }),
           {
               headers: { "Content-Type": "application/json" },
               status: 400,
           }
       );
     }
}

```

This code extracts a `customer_id` from the request body and makes an HTTP GET request to the Rapyd API to retrieve information related to the customer. From the response received, it extracts the `next_action` and then returns the appropriate response based on the success or failure of the request.

### Updating the Login Logic

You need to update the login logic so that a customer can log in to your application and retrieve the subscriptions. To do this, open the **app/api/auth/login/route.js** file and replace the locate the following code:

```js
return new Response(JSON.stringify({
       user: {
           id: existingUser.id,
           name: existingUser.name,
           email: existingUser.email,
       }
   }), {
       headers: { 'Content-Type': 'application/json' },
       status: 200
   })

```

Replace it with the following:

```js
return new Response(JSON.stringify({
       user: {
           id: existingUser.id,
           name: existingUser.name,
           email: existingUser.email,
           rapyd_cust_id: existingUser.rapyd_cust_id
       }
   }), {
       headers: { 'Content-Type': 'application/json' },
       status: 200
   })

```

The new code returns `rapyd_cust_id` as part of the response, which is saved to the browser’s local storage in the `Login` component. This makes it easy to retrieve the customer’s details from Rapyd when the customer logs in to your application.

### Creating a Subscription

You’ve now implemented the logic for creating a customer and simulating 3DS authentication, so the next step is to create a subscription for the customer.

To do this, create a new folder named `subscriptions` in the `app/api` folder. In the newly created folder, create the file **create/route.js** and add the code below:

```js
import { makeRequest } from "@/utils/makeRequest";

export async function POST(req, res) {
   const { customer_id } = await req.json();

   try {
   	const body = {
       	customer: customer_id,
       	billing: "pay_automatically",
       	cancel_at_period_end: true,
       	days_until_due: 0,
       	simultaneous_invoice: true,
       	subscription_items: [
           	{
               	plan: "<your-plan-id>",
               	quantity: 1,
           	},
       	],
   	};
   	const result = await makeRequest(
       	"POST",
       	"/v1/payments/subscriptions",
       	body
   	);

   	return new Response(
       	JSON.stringify({
           	message: "Subscription created successfully",
       	}),
       	{
           	headers: { "Content-Type": "application/json" },
           	status: 200,
       	}
   	);
   } catch (error) {
   	console.error("Error completing request", error);

   	return new Response(
       	JSON.stringify({
           	error: "Unable to create subscription.",
       	}),
       	{
           	headers: { "Content-Type": "application/json" },
           	status: 400,
       	}
   	);
   }
}

```

This code retrieves the customer’s ID from the request body and attempts to create a subscription for the customer. Remember to replace `<your-plan-id>` with the plan ID that you copied from Postman earlier.

In the JSON object that you pass to Rapyd, `customer` defines the ID of the customer you want to create a subscription for. `billing` determines the billing method at the end of the billing cycle. It is set to `pay_automatically`, which instructs Rapyd to create a payment object and attempt to make a payment using the designated payment method. `cancel_at_period_end` indicates that the subscription is canceled at the end of the current billing period. `subscription_items` defines the plans that the user is subscribing to and the quantity.

The code returns a response with the message “Subscription created successfully” if the request is successful. If an error is encountered, the code returns a response with the error message.

### Retrieving Subscriptions

To retrieve a customer’s subscriptions, create a new file **list/route.js** in the `app/api/subscriptions` folder and paste in the code below:

```js
import { makeRequest } from "@/utils/makeRequest";

export async function POST(req, res) {
   const { customer_id } = await req.json();

   try {
   	const result = await makeRequest(
       	"GET",
       	`/v1/payments/subscriptions?customer=${customer_id}`
   	);

   	return new Response(
       	JSON.stringify({
           	subscriptions: result.body.data,
       	}),
       	{
           	headers: { "Content-Type": "application/json" },
           	status: 200,
       	}
   	);
   } catch (error) {
   	console.error("Error completing request", error);

   	return new Response(
       	JSON.stringify({
           	error: "Unable to fetch the subscriptions data.",
       	}),
       	{
           	headers: { "Content-Type": "application/json" },
           	status: 400,
       	}
   	);
   }
}

```

This code retrieves the customer’s subscriptions using the provided customer ID and returns the result of this operation. In case an error is encountered, the code returns a response with the error message.

### Canceling a Subscription and Requesting a Full Refund

To allow users to cancel a subscription and request a full refund, create the new file **cancelandrefund/route.js** in the `app/api/subscriptions` folder and paste in the code below:

```js
import { makeRequest } from "@/utils/makeRequest";

export async function POST(req, res) {
   const { subscription_id } = await req.json();

   // Retrieve the subscription from Rapyd
   try {
   	const result = await makeRequest(
       	"GET",
       	`/v1/payments/subscriptions/${subscription_id}`
   	);

   	// Retrieve the subscription creation date
   	const subscription_creation_date = result.body.data.created_at;

   	// Check if the customer is eligible for a refund
   	let epoch_seconds = Math.floor(new Date().getTime() / 1000);
   	let days_since_creation =
       	(epoch_seconds - subscription_creation_date) / (24 * 60 * 60);

   	// For users who are not eligible for a refund,
   	// the subscription will be canceled at the end of the billing cycle
   	if (days_since_creation > 7) {
       	try {
           	const result = await makeRequest(
               	"DELETE",
               	`/v1/payments/subscriptions/${subscription_id}`,
               	{
                   	cancel_at_period_end: true,
               	}
           	);

           	return new Response(
               	JSON.stringify({
                   	message: "Subscription canceled successfully",
               	}),
               	{
                   	headers: { "Content-Type": "application/json" },
                   	status: 200,
               	}
           	);
       	} catch (error) {
           	console.error("Error completing request", error);

           	return new Response(
               	JSON.stringify({
                   	error: "Unable to cancel subscription",
               	}),
               	{
                   	headers: { "Content-Type": "application/json" },
                   	status: 400,
               	}
           	);
       	}
   	}

   	// For users who are eligible for a refund,
   	// the subscription will be canceled immediately
   	// and a refund will be issued.
   	else {
       	// Cancel subscription
       	try {
           	const result = await makeRequest(
               	"DELETE",
               	`/v1/payments/subscriptions/${subscription_id}`
           	);

           	// Retrieve the payment associated with the subscription
           	try {
               	const result = await makeRequest(
                   	"GET",
                   	`/v1/payments?subscription=${subscription_id}`
               	);

               	let subscription_payment_id = result.body.data[0].id;

               	// Create a refund
               	try {
                   	const body = {
                       	payment: subscription_payment_id,
                       	reason: "Subscription canceled within allowed timeframe.",
                   	};
                   	const result = await makeRequest(
                       	"POST",
                       	"/v1/refunds",
                       	body
                   	);

                   	return new Response(
                       	JSON.stringify({
                           	message:
                               	"Subscription canceled and refund issued successfully!",
                       	}),
                       	{
                           	headers: { "Content-Type": "application/json" },
                           	status: 200,
                       	}
                   	);
               	} catch (error) {
                   	console.error("Error completing request", error);
               	}
           	} catch (error) {
               	console.error("Error completing request", error);
           	}
       	} catch (error) {
           	console.error("Error completing request", error);
       	}
   	}
   } catch (error) {
   	console.error("Error completing request", error);

   	return new Response(
       	JSON.stringify({
           	error: "Unable to retrieve the subscription.",
       	}),
       	{
           	headers: { "Content-Type": "application/json" },
           	status: 400,
       	}
   	);
   }
}

```

The code extracts the `subscription_id` from the request body and uses it to fetch subscription details from the Rapyd API. It gets the creation date and stores it in the variable `subscription_creation_date`. The code then calculates the days since creation and checks eligibility for a refund. If more than seven days have passed, the user is not eligible for a refund and the subscription is set to cancel at the billing cycle’s end. For eligible customers, it immediately [cancels the subscription](https://docs.rapyd.net/en/cancel-subscription.html), [retrieves the payment associated with the subscription](https://docs.rapyd.net/en/retrieve-payment.html), extracts the payment ID associated with the subscription, and [initiates a refund](https://docs.rapyd.net/en/create-refund.html) using the payment ID. In this code snippet, the reason for canceling is hard-coded, but in a real-world application, providing a form that the users can fill out with their reason could help improve your service to address the customer’s concerns.

### Implementing the Dashboard Logic

You’ll now implement the UI to allow your customers to interact with this system.

To do this, you will update the code in the **components/Dashboard.jsx** file so that you can render the appropriate information. To render the 3DS authentication page, you will use an iframe to embed the page into our site. The user will only be able to access their subscriptions dashboard once they have authenticated their card. Once the user performs 3DS authentication, the page reloads. You will keep track of the page reload and use it to check whether the card has been authenticated.

Open the **components/Dashboard.jsx** file and replace the existing code with the following:

```js
"use client";

import React, { useEffect, useState } from "react";

function Dashboard({ checkUserInfo, cardAuthLink, setCardAuthLink }) {
  const [user, setUser] = useState(null);
  const [subscriptions, setSubscriptions] = useState(null);

  // Retrieve user details from local storage
  const retrieveUser = () => {
   const userInfo = JSON.parse(localStorage.getItem("user_info"));

   return setUser(userInfo);
  };

  // Set user on initial page load
  useEffect(() => {
   retrieveUser();
  }, []);

  // Log out the user
  const logout = () => {
   localStorage.removeItem("user_info");
   checkUserInfo();
  };

  // Retrieve subscriptions on page load
  const retrieveSubscriptions = async () => {
   try {
       const res = await fetch("/api/subscriptions/list", {
           method: "POST",
           headers: {
               "Content-Type": "application/json",
           },
           body: JSON.stringify({ customer_id: user.rapyd_cust_id }),
       });

       const data = await res.json();

       if (data.error) {
           alert(data.error);
       }

       if (data.subscriptions) {
           setSubscriptions(data.subscriptions);
       }
   } catch (error) {
       console.error(error);
   }
  };

   // Retrieve the user's subscriptions on page load 
  useEffect(() => {
   if (user) {
       retrieveSubscriptions();
   }
  }, [user]);

  // Create subscription
  const createSubscription = async () => {
   try {
       const res = await fetch("/api/subscriptions/create", {
           method: "POST",
           headers: {
               "Content-Type": "application/json",
           },
           body: JSON.stringify({ customer_id: user.rapyd_cust_id }),
       });

       const data = await res.json();

       if (data.error) {
           alert(data.error);
       }

       if (data.message) {
           // Set the subscriptions to null to trigger loading state
           setSubscriptions(null);

           // Retrieve the subscriptions
           retrieveSubscriptions();
       }
   } catch (error) {
       console.error(error);
   }
  };

  // Cancel subscription and request refund
  const cancelAndRefund = async () => {
   try {
       const res = await fetch("/api/subscriptions/cancelandrefund", {
           method: "POST",
           headers: {
               "Content-Type": "application/json",
           },
           body: JSON.stringify({
               subscription_id: subscriptions[0].id,
           }),
       });

       const data = await res.json();

       if (data.error) {
           alert(data.error);
       }

       if (data.message) {
           // Show the message
           alert(data.message);

           // Set the subscriptions to null to trigger loading state
           setSubscriptions(null);

           // Retrieve the subscriptions
           retrieveSubscriptions();
       }
   } catch (error) {
       console.error(error);
   }
  };

   // Track whether the iframe has reloaded  
   const [iframeReloaded, setIframeReloaded] = useState(false);
   // Track if you are checking the customer info   
   const [checkingCustomerInfo, setCheckingCustomerInfo] = useState(false)
   // Track if 3DS auth is required  
   const [nextAction, setNextAction] = useState('3d_verification')

   // Check customer info -> confirm if the card has been authenticated
   const checkCustomerInfo = async () => {
       try {
           // Trigger the "Verifying your payment information..." message
           setCheckingCustomerInfo(true)

           // Retrieve the customer info, specifically the payment method's next_action, using their Rapyd customer ID
           const res = await fetch("/api/auth/retrievecustomer", {
               method: "POST",
               headers: {
                   "Content-Type": "application/json",
               },
               body: JSON.stringify({
                   customer_id: user.rapyd_cust_id
               }),
           });

           const data = await res.json()

           // Hide the "Verifying your payment information..." message
           setCheckingCustomerInfo(false)

           // "not_applicable" means that the card has been authenticated successfully
           if(data.message === "not_applicable"){
               // Update the 3DS Auth link to null and next_action to not_applicable
               // This will display the dashboard
               setCardAuthLink(null)
               setNextAction(data.message)
           }

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

  // Handle iframe reload
  const handleIframeReload = async () => {
     if (iframeReloaded) {
      
       // Check if the card has been authenticated
       checkCustomerInfo()

     } else {
        // If this is the first load, set the flag for subsequent reload detections
        setIframeReloaded(true);
     }
  };

   // In Rapyd, when you create a customer and immediately try to retrieve their info,
   // the next_action changes to "not_applicable" even if 3DS auth hasn't been performed.
   // To avoid this, we will only check the customer info when the user logs in and avoid this when the user signs up
   // To know when a user signs in, the card auth link will be null
   // You can run the checkUserInfo function only when the cardAuthLink variable is null
  useEffect(() => {
   if(user && cardAuthLink === null){
       checkCustomerInfo()
   }
  }, [user, cardAuthLink])

  return (
   <div className="bg-white p-5 rounded shadow max-w-[600px] w-full mx-auto mt-8">
       {
           cardAuthLink || nextAction === '3d_verification' ?

           <div>

               <p className={checkingCustomerInfo ? "text-2xl font-bold" : "hidden"}>Verifying your payment information...</p>

               <iframe
                   src={cardAuthLink}
                   width="600"
                   height="400"
                   onLoad={handleIframeReload}
                   hidden={checkingCustomerInfo}
               ></iframe>

           </div> :

           <div>
               <div className="flex items-center justify-between">
                   <h2 className="text-lg font-bold">Subscription Dashboard</h2>
                   <button
                       onClick={() => logout()}
                       className="border px-4 py-1 cursor-pointer"
                   >
                       Log out
                   </button>
               </div>

               {subscriptions === null ? (
                   <p className="mb-5">Loading...</p>
               ) : (
                   <div className="mb-5">
                       {subscriptions.length === 0 ? (
                           <div>
                               <p>You do not have an active subscription.</p>

                               <div className="mt-4">
                                   <p>
                                       Subscribe to{" "}
                                       <strong>
                                           CineView Unlimited - Weekly Plan
                                       </strong>{" "}
                                       at $1.99
                                   </p>
                                   <button
                                       onClick={() => createSubscription()}
                                       className="mt-4 bg-green-600 text-white rounded cursor-pointer px-6 py-2"
                                   >
                                       Subscribe Now
                                   </button>
                               </div>
                           </div>
                       ) : (
                           <div>
                               <h3>Your Subscription</h3>
                               <p>
                                   <strong>Plan:</strong>{" "}
                                   {
                                       subscriptions[0].subscription_items.data[0]
                                           .plan.nickname
                                   }
                               </p>
                               <p>
                                   <strong>Status:</strong>{" "}
                                   {subscriptions[0].status
                                       .charAt(0)
                                       .toUpperCase() +
                                       subscriptions[0].status.slice(1)}
                               </p>
                               <p>
                                   <strong>Billed:</strong> $
                                   {
                                       subscriptions[0].subscription_items.data[0]
                                           .plan.amount
                                   }
                                   /{subscriptions[0].subscription_items.data[0].plan.interval}
                               </p>

                               {subscriptions[0].status === "active" && (
                                   <button
                                       onClick={() => cancelAndRefund()}
                                       className="mt-4 bg-red-600 text-white rounded cursor-pointer px-6 py-2"
                                   >
                                       Cancel Subscription
                                   </button>
                               )}
                           </div>
                       )}
                   </div>
               )}
           </div>
       }
   </div>
  );
}

export default Dashboard;

```

This component has several functions:

- `retrieveUser`: Retrieves user details from local storage and sets the `user` state
- `logout`: Clears user information from local storage and triggers a function (`checkUserInfo`) to handle user logout
- `retrieveSubscriptions`: Retrieves user subscriptions by making a POST request to `/api/subscriptions/list` and sets the `subscriptions` state
- `createSubscription`: Creates a new subscription by making a POST request to `/api/subscriptions/create` and triggers a reload to fetch the new subscriptions
- `cancelAndRefund`: Cancels a subscription and requests a refund by making a POST request to `/api/subscriptions/cancelandrefund` and triggers a reload of user subscriptions
- `checkCustomerInfo`: Checks customer information, specifically whether the card has been authenticated, by making a POST request to `/api/auth/retrievecustomer`; updates the state based on the card authentication status
- `handleIframeReload`: Handles iframe reload events and checks customer information

The code conditionally renders different content based on the state of the `cardAuthLink` or if the next action is `3d_verification`, which means that the card has not been authenticated. It displays a loading message or an iframe for card authentication, if applicable. If the card is authenticated, the code displays the subscription dashboard.

### Testing the Application

To test whether the application is working as expected, you need to delete the existing SQLite database as you have introduced new fields in the code. If your development server is already running, stop it using Ctrl+C or Cmd+C. Delete the **database.db** file in the `db` folder and run the development server once again using the command `npm run dev`.

To test the code, navigate to `http://localhost:3000/` on your browser. As you already logged in earlier, the dashboard page will be displayed. The starter template uses values stored in the browser’s local storage to check if a user is authenticated. Click the **Log out** button to clear the data in local storage, and you’ll be redirected to the login page.

Select **Sign Up** from the form switcher at the top of the page. The sign-up form has already been prefilled with some data. The password is `12345678`. Select the **Sign Up** button at the bottom of the page to create an account with the provided data.

After a short period of time, the **Rapyd 3DS Simulator** page will be displayed.

 ![Simulating 3DS authentication](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/1/a/1afd62bfd002b6b9e4ba3f39baa2ec7078f4209b.png)

Fill out `123456` in the input box and select **Continue** , and the page will display a message indicating that the details are being authenticated. In the background, the app is making a request to the Rapyd API to check if your card has been authenticated.

 ![Loading message](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/d/7/d7376b3bcd643dd554c74c4b862b7334249cd0a3.png)

After the card has been authenticated, you will see the subscription dashboard indicating that you do not have an active subscription.

 ![No active subscription](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/e/4/e4046fb13fbad5da0335ec46674eb142e8f2aed3.png)

Click the **Subscribe Now** button to subscribe to the “CineView Unlimited - Weekly Plan”. The page will show the loading message and then display your current subscription.

 ![Current subscription](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/d/a/da4924fa03a6171d0d5e7870e942a022bcff69a6.png)

To cancel the subscription and request a refund, click the **Cancel Subscription** button. The page will show an alert indicating that the subscription has been canceled and the refund issued successfully.

 ![Subscription canceled and refund issued](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/b/3/b3b7d259a69a45368db0b000305bce05c691ee9c.png)

Click the **OK** button on the alert, and the status of the subscription will be indicated as “Canceled”. This means that the subscription has been canceled by the customer but remains in the Rapyd database.

 ![Canceled subscription](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/9/9/99792d76842c3c72aef167b9f025cb382ad6e572.png)

You can head over to the [refunds page on the Rapyd Client dashboard](https://dashboard.rapyd.net/collect/refunds) to confirm that the refund was issued successfully.

 ![Rapyd client dashabord](https://us1.discourse-cdn.com/flex016/uploads/rapyd/original/3X/8/0/8074939fb2f6455a2beb1558c644bb7bf467087d.png)

This confirms that the application is working as expected.

## Get The Code

You can find the full code for this guide on [GitHub](https://github.com/kimanikevin254/rapyd-guaranteed-refund-system/tree/full-project-code).

---

<div class="post-metadata">

### Author: ![drew.harris](https://sea2.discourse-cdn.com/flex016/user_avatar/community.rapyd.net/drew.harris/32/1259_2.png) [@drew.harris](https://community.rapyd.net/u/drew.harris)
#### Post date: [January 21, 2025, 5:17pm UTC](https://community.rapyd.net/t/creating-and-paying-a-freight-invoice-with-the-rapyd-api-and-fx/59592/3 "2025-01-21T17:17:29Z")

</div>


