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.
and . 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 and account
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:
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:
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.
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:
- Create a service and a plan.
- Create a customer with a payment method and simulate 3DS authentication.
- Create a subscription for a customer.
- 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 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:
under "Get Your API Keys". Make sure you save the changes. After setting up your environment, you should end up with something like this:
.
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:
// 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:
<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:
function FormSwitcher({ checkUserInfo, setCardAuthLink }) {
// Rest of the code
In the same file, make sure you are passing the setCardAuthLink function to the Signup component.
<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:
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:
// 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:
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:
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:
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:
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:
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:
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 , extracts the payment ID associated with the subscription, and
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.
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.
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.
to confirm that the refund was issued successfully.
.
SOCIAL SHARE CARD GENERATOR