Vexor

Refunds

Refunds allow you to return the funds to the user in different scenarios. This guide will walk you through creating a basic refund system using Vexor.

Setup

If you've come this far, you already have a good foundation! This guide assumes you have:

  • A working Vexor project
  • Experience with either one-time payments or subscriptions
  • Completed the checkouts guide or subscriptions guide
  • Set up webhooks following our webhooks guide
    (Required for tracking the identifier which we are going to use to process the refund ⚠️)

If you're new to Vexor, we recommend completing the previously mentioned guides first. They provide step-by-step instructions for:

  • Creating your Vexor dashboard project
  • Setting up your API Keys
  • Initializing your project with the Vexor SDK
  • Configuring access tokens
  • Configuring and managing Secret Keys, Access Tokens, Client IDs, Webhook Secrets, and more for various providers
  • Setting up your environment variables

Each guide includes helpful video tutorials to walk you through the process.

Let's code 🚀

Create the app

In this example we will implement Vexor refunds in one of the previous examples. We are going to clone the app we created in the Checkouts Guide but the same logic can be applied to any other application.

  • Clone the webhooks branch of the checkout repo:
git clone -b webhooks https://github.com/Vectrals-Software/vexor-checkout-nextjs.git

This branch includes the webhooks logic already implemented, we are going to use it to track the identifier.

  • Once you have cloned the repo, open it on your code editor and install the dependencies:
npm install

Environment variables

Add the necessary environment variables to your project:

NEXT_PUBLIC_VEXOR_PROJECT="YOUR_PROJECT_ID"
NEXT_PUBLIC_VEXOR_PUBLISHABLE_KEY="YOUR_PUBLIC_KEY"
VEXOR_SECRET_KEY="YOUR_SECRET_KEY" 

Create a server action to handle refunds

Refunds are supposed to be handled in the server side. Why? Because refunds are sensitive financial operations that need Vexor's secret key. You don't want to expose your secret key in the client side. In fact, as we are exporting vexor from @/lib/vexor file, we can use it in the server side without any problem. If you try to use it in the client side, you will get an error because the secret key environment variable VEXOR_SECRET_KEY is not available in the client side.

We are going to create a server action in the Next.js app. If you are using another framework, you can use the appropriate folder structure. Even in Next.js you can also use api routes to handle refunds if you prefer. In this guide, we are going to use server actions for simplicity.

  1. Create a folder named /actions inside the /src folder. Remember that we are following the Next.js convention for the folder structure, if you are using another framework, you can use the appropriate folder structure.

  2. Inside the /actions folder, create a file named refund.ts.

  3. Let's complete the refund.ts file with the following code:

'use server'
 
import { vexor } from "@/lib/vexor";
import { VexorRefundResponse } from "vexor";
 
export const handleRefund = async (identifier: string) => {
 
    try {
        const response : VexorRefundResponse = await vexor.refund({
            platform: 'stripe',
            identifier
        });
 
        if (response.error) {
            throw new Error(response.error);
        }
 
        return { success: true, response };
    } catch (error) {
        console.error(error);
        return { error: true, response: error };
    }
}

Let's build a simple component for the refund

Create a file named refund-card.tsx inside a src/components folder and add the following code:

// src/components/refund-card.tsx
 
"use client"; // Mark it as a client component
 
import { handleRefund } from "@/actions/refund";
import { useTransition } from "react";
import { useState } from "react";
 
const RefundCard: React.FC = () => {
    const [isPending, startTransition] = useTransition();
    const [identifier, setIdentifier] = useState("");
 
    const onSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        startTransition(async () => {
            const result = await handleRefund(identifier);
            console.log(result);
        });
    };
 
    return (
        <div className="border rounded-lg p-4 shadow-sm w-full">
            <h2 className="text-xl font-bold mb-4">Refund Payment</h2>
            <form onSubmit={onSubmit}>
                <div className="mb-4">
                    <label htmlFor="identifier" className="block mb-2">
                        Payment Identifier
                    </label>
                    <input
                        id="identifier"
                        type="text"
                        value={identifier}
                        onChange={(e) => setIdentifier(e.target.value)}
                        className="w-full p-2 border rounded"
                        placeholder="Enter payment identifier"
                        required
                    />
                </div>
                <button
                    type="submit"
                    className="bg-foreground text-background px-4 py-2 rounded w-full"
                    disabled={isPending}
                >
                    {isPending ? "Processing..." : "Refund Payment"}
                </button>
            </form>
        </div>
    );
};
 
export default RefundCard;
 

We have used a form to submit the identifier, we are doing it for simplicity and demonstration purposes. In production you should create a more robust logic to handle the refund.

Import the component in the page

Go to the main page.tsx file and import the PortalCard component:

// src/app/page.tsx
 
import RefundCard from "@/components/refund-card";

Then add the RefundCard component to the page, you can do it anywhere in the page. In this example we will add it below the ProductCard component.

Your page.tsx file should look like this:

// src/app/page.tsx
 
import ProductCard, { Product } from "@/components/product-card";
import RefundCard from "@/components/refund-card";
const products: Product[] = [
  {
      id: "item-1",
      title: "Premium Widget",
      description: "High-quality widget with advanced features",
      quantity: 2,
      unit_price: 2
  },
  {
      id: "item-2",
      title: "Standard Gadget",
      description: "Reliable gadget for everyday use",
      quantity: 1,
      unit_price: 10
  }
];
 
export default function Home() {
  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start w-full max-w-4xl">
        <h1 className="text-3xl font-bold mb-8">Our Products</h1>
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-8 w-full">
          {products.map((product) => (
            <ProductCard key={product.id} product={product} />
          ))}
        </div>
        <RefundCard />
      </main>
    </div>
  );
}

If you run the project now with npm run dev, you will see the RefundCard component with a button to process the refund. You should see something like this:

refund card ui

Test the subscription

  1. Run your app locally and test a purchase by clicking the "Buy" button. You should be redirected to the payment page for the purchase.
  2. Complete the payment and you should see the webhook response in the console where you ran your app.

We assume you have completed the webhooks guide and you have the webhook logic already implemented and working for the purchase.

  1. Now you can test the refund by entering the identifier in the refund card and clicking the "Refund Payment" button. You should see the refund response in the console.

Congratulations! 🎉

You have implemented a basic refund system. You can extend this logic to more complex scenarios. The good thing about this is that you don't need to worry about the platform specific parameters, we take care of that. You just need to provide the identifier and we will handle the rest. Easy, right? 🤓

On this page