DEV Community

Cover image for Using OpenAPI to Automate API Integration With Rapyd's Payment Gateway
mcduffin for Rapyd

Posted on • Originally published at community.rapyd.net

Using OpenAPI to Automate API Integration With Rapyd's Payment Gateway

By: Kevin Kimani

Payment gateways like Rapyd help businesses seamlessly handle transactions across different platforms and currencies.

Rapyd allows businesses to receive, manage, and disburse funds through a range of payment methods. Its flexible platform helps businesses simplify their financial setup and improve the customer experience. It's ideal for online marketplaces, e-commerce stores, online trading, subscription-based platforms, and cross-border transactions.

In this article, you'll learn how to use OpenAPI to generate a TypeScript client that integrates with the Rapyd API.

What Is the Rapyd API?

The Rapyd API simplifies payment integration by providing a unified platform for managing transactions. It provides endpoints for accepting payments, processing refunds, managing payouts, handling fraud prevention, and more.

A key feature of the Rapyd API is its ability to connect businesses to local and global payment networks, including card payments, bank transfers, e-wallets, and cash payments. It's also easy to use and integrate, thanks to its clear documentation and RESTful design.

How to Use OpenAPI to Automate API Integration with Rapyd's Payment Gateway

To follow along with this tutorial, you'll need the following:

  • Node.js installed on your local machine
  • A Rapyd account
  • The Git CLI installed on your local machine
  • ngrok installed on your local machine
  • A code editor and a web browser

Obtain Your Credentials from the Rapyd Client Portal

To interact with the Rapyd API, you need to provide a few headers in your requests, including a signature header. Some of the components required to calculate the signature are the access and secret keys, which you can obtain from the Rapyd Client Portal.

To obtain these credentials, in the Rapyd Client Portal , make sure you're in the Sandbox environment, then navigate to Developers > API access control and copy your access key and secret key:

Obtaining credentials from the Rapyd Client Portal

Set Up a Node.js Application

To keep this tutorial focused on generating a TypeScript client and integrating with the Rapyd API, we've prepared a starter template for the demo e-commerce application. Clone the template to your local machine by running the following command:

git clone --single-branch -b starter-template https://github.com/kimanikevin254/rapyd-openapi-ts-client.git
Enter fullscreen mode Exit fullscreen mode

This starter template includes:

  • Express to set up a web server
  • TypeORM as the ORM
  • A SQLite database
  • EJS for rendering templates
  • Passport for authentication (allows users to sign up and log in)

Next, cd into the project folder and rename .env.example to .env. Open the .env file and replace the placeholders for RAPYD_ACCESS_KEY and RAPYD_SECRET_KEY with the values you obtained from the Rapyd Client Portal.

You'll set the value for BASE_URI in the .env file later.

Install the dependencies and seed the database using the following commands:

npm install
npm run seed:products
Enter fullscreen mode Exit fullscreen mode

npm run seed:products executes the script in the src/database/seeds/products.seed.ts file that fetches ten products from the DummyJSON API and populates the database.

Next, run the server using the command npm run dev and navigate to http://localhost:3000/auth/signup. On this page, provide the required information and click the Sign Up button:

Sign Up page

After successfully signing up, you'll be redirected to the home page, where you can see a list of products:

List of products

Click the View Details button under any product to be redirected to its details page, where you can add the product to the cart by clicking Add to Cart:

Details page

Go ahead and add a product to your cart, then click Cart on the navigation bar. You'll see the item in your shopping cart, along with a checkout option. Don't click the Proceed to Checkout button yet, as the checkout functionality has not been set up. You'll set this up later.

Generate a TypeScript Client

Manually writing API clients can be time-consuming and result in errors. This is where the OpenAPI specification and tools like OpenAPI Generator are helpful.

OpenAPI provides a standardized way to describe APIs, allowing developers to automatically generate client SDKs in various programming languages, including TypeScript. With the OpenAPI Generator, you can quickly generate a fully typed TypeScript client for Rapyd's API, making integration easier, more efficient, and less error-prone. You can access Rapyd's OpenAPI specification on GitHub.

To generate a TypeScript client for the Rapyd API, execute the following command in the terminal (in the project root folder):

npx @openapitools/openapi-generator-cli generate -i https://raw.githubusercontent.com/Rapyd-Samples/RapydOpenAPI/master/rapyd-openapi.yaml -g typescript-axios -o ./rapyd-client
Enter fullscreen mode Exit fullscreen mode

This command uses the OpenAPI Generator CLI to generate a TypeScript client (with Axios) from Rapyd's OpenAPI specification and saves the generated code in the rapyd-client folder.

Next, you need to check if the generated client has any errors. A simple way to do this is to create a new file named test-client.ts in the project root folder and add the following code:

import { CheckoutPageApi } from "./rapyd-client";
console.log(new CheckoutPageApi());
Enter fullscreen mode Exit fullscreen mode

Execute the file using the command ts-node test-client.ts. You should see the following errors in the terminal:

TSError: ⨯ Unable to compile TypeScript:
rapyd-client/api.ts:3359:18 - error TS2304: Cannot find name 'CouponDurationEnum'.

3359     'duration'?: CouponDurationEnum;
 ~~~~~~~~~~~~~~~~~~
rapyd-client/api.ts:19833:32 - error TS2552: Cannot find name 'V1PayoutsBodyBeneficiaryEntityTypeEnum'. Did you mean 'PayoutRequestBeneficiaryEntityTypeEnum'?

19833     'beneficiary_entity_type': V1PayoutsBodyBeneficiaryEntityTypeEnum;
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  rapyd-client/api.ts:15418:14
    15418 export const PayoutRequestBeneficiaryEntityTypeEnum = {
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'PayoutRequestBeneficiaryEntityTypeEnum' is declared here.
# Truncated for brevity
Enter fullscreen mode Exit fullscreen mode

This error indicates that TypeScript cannot find certain type definitions (CouponDurationEnum and V1PayoutsBodyBeneficiaryEntityTypeEnum) in the generated client (rapyd-client/api.ts). This appears to be a potential bug in the OpenAPI generator where some enums are not generated correctly.

To work around this issue, you can write a script to automatically replace missing or incorrect enum references. Create a new file named fix-generated-client.ts in the project root folder and add the following code:

import fs from "fs";
import path from "path";

// Define the file path (adjust as needed)
const filePath = path.join(process.cwd(), "rapyd-client/api.ts");

try {
    // Read the file
    let fileContent = fs.readFileSync(filePath, "utf8");

    // Replace CouponDurationEnum with the correct type
    fileContent = fileContent.replace(
        /CouponDurationEnum/g,
        `"forever" | "repeating"`
    );

    // Fix incorrect enum references
    fileContent = fileContent.replace(
        /V1PayoutsBodyBeneficiaryEntityTypeEnum/g,
        "PayoutRequestBeneficiaryEntityTypeEnum"
    );

    // Write the corrected file back
    fs.writeFileSync(filePath, fileContent, "utf8");

    console.log("✅ Fixes applied successfully to rapyd-client/api.ts");
} catch (error) {
    console.error("❌ Error updating file:", error);
}
Enter fullscreen mode Exit fullscreen mode

This script reads the rapyd-client/api.ts file, fixes the incorrect enum names, and saves the updated content back to the file.

Run this script using the command ts-node fix-generated-client.ts, then run the test-client.ts file again to check if the errors have been fixed. Everything should be okay at this point.

Implement the Checkout Flow

To enable customers to pay for the items in the cart, you need to create a Rapyd checkout page. This page will collect customers' payment information and create a payment.

To create a checkout page, you need to make a call to the Rapyd API. As mentioned previously, each call needs to have a couple of headers that include a signature for your request.

To make it easy to generate the required headers for each API call, you'll create a utility file that generates all the required headers.

Create a new file named src/utils/signRapydRequest.ts and add the following code:

import crypto from "crypto";

const ACCESS_KEY = process.env.RAPYD_ACCESS_KEY;
const SECRET_KEY = process.env.RAPYD_SECRET_KEY;

export function signRequest(
    httpMethod: string,
    path: string,
    requestJson: string = ""
) {
    const salt = crypto.randomBytes(12).toString("base64");
    const timestamp = Math.floor(new Date().getTime() / 1000).toString();
    let body = requestJson;

    if (
        JSON.stringify(requestJson) !== "{}" &&
        requestJson !== "" &&
        typeof requestJson !== "object"
    ) {
        body = JSON.stringify(JSON.parse(requestJson));
    }

    // The signature string follows the order: method, path, salt, timestamp, access_key, secret_key, body
    const toSign = `${httpMethod.toLowerCase()}${path}${salt}${timestamp}${ACCESS_KEY}${SECRET_KEY}${body}`;

    // Generate HMAC signature with SHA-256 and encode in Base64 URL-safe format
    const hash = crypto.createHmac("sha256", SECRET_KEY);
    hash.update(toSign);
    const signature = Buffer.from(hash.digest("hex")).toString("base64");

    // Idempotency
    const idempotency = `${timestamp}${salt}`;

    return { salt, timestamp, signature, idempotency };
}
Enter fullscreen mode Exit fullscreen mode

This code generates a signature for an API request to the Rapyd API using HMAC with SHA-256. First, it creates a random salt and gets the current timestamp. Then, it prepares the data to sign, including the HTTP method, path, salt, timestamp, access key, secret key, and request body. It creates the signature by hashing this data with the secret key. Finally, it returns the salt, timestamp, signature, and an idempotency key for the request.

Now it's time to implement the checkout flow in the src/controller/checkout.controller.ts file. This file has a CheckoutController class with three methods:

  • index displays the checkout page
  • success displays the page where the user is redirected after a successful payment
  • createOrder is executed when the checkout page is loaded; it retrieves the cart ID from the request query params and uses it to retrieve a cart and create an order and a payment for the cart

The createOrder method is where you'll generate a Rapyd checkout page where the user will be redirected to make a payment.

To implement the checkout flow, add the following import statement to the src/controller/checkout.controller.ts file:

import { CheckoutPageApi, Configuration } from "../../rapyd-client";
import { signRequest } from "../utils/signRapydRequest";
Enter fullscreen mode Exit fullscreen mode

Next, create a configuration instance and pass it to the checkout page API instance by adding the following private properties to the class:

private config = new Configuration({});
private checkoutPageApi = new CheckoutPageApi(this.config);
Enter fullscreen mode Exit fullscreen mode

In the createOrder method, replace res.json({ success: true }) with the following:

// Prepare request body
const rapydRequestBody = {
    amount: order.totalAmount,
    country: "IT",
    currency: "USD",
    complete_checkout_url: `${process.env.BASE_URI}/checkout/success`,
    cancel_checkout_url: `${process.env.BASE_URI}/cart`,
    payment_method_type: "it_visa_card",
};

// Obtain the request headers
const { salt, timestamp, signature, idempotency } = signRequest(
    "POST",
    "/v1/checkout",
    JSON.stringify(rapydRequestBody)
);

// Create a checkout page
const { data } = await this.checkoutPageApi.generateHostedPagePayment(
    process.env.RAPYD_ACCESS_KEY,
    "application/json",
    salt,
    signature,
    timestamp,
    idempotency,
    rapydRequestBody
);

// Update payment with the Rapyd checkout ID
await this.paymentRepository.update(payment.id, {
    rapydCheckoutId: data.data.id,
});

// Redirect user to payment page
res.json({ success: true, paymentPageUrl: data.data.redirect_url });
Enter fullscreen mode Exit fullscreen mode

This code prepares a request body for creating a checkout page with Rapyd, including details like amount, country, and payment method. It generates the necessary request headers (salt, timestamp, signature, and idempotency) using the signRequest function. Then, it calls the Rapyd API to generate a hosted checkout page. Once the checkout page is created, it updates the payment record in the database with the Rapyd checkout ID. Finally, it returns a successful response with the URL to the payment page where the user should be redirected.

In this example, you hard-coded the country, currency, and payment method type. You can get these details by listing the payment methods by country.

Define Webhooks

Once the customer submits the payment information on the hosted checkout page and Rapyd processes the payment successfully, a payment succeeded webhook is sent to your application with all the payment-related information. You need to process this information and mark the order and the payment as completed in the database.

For the webhook to work, you need to use an HTTPS endpoint. To get this, run the following command in a separate terminal to enable ngrok to tunnel your localhost connection and give you an HTTPS endpoint:

ngrok http 3000
Enter fullscreen mode Exit fullscreen mode

You should get an output like the one below with a forwarding URL:

Session Status                online
Account                       email@domain.com (Plan: Free)
Update                        update available (version 3.19.1, Ctrl-U to update)
Version                       3.19.0
Region                        Europe (eu)
Latency                       215ms
Latency                       139ms
Web Interface                 http://127.0.0.1:4040
Forwarding <YOUR-FORWARDING-URL> -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              199     0       0.00    0.00    9.23    14.85
Enter fullscreen mode Exit fullscreen mode

Copy the value of the forwarding URL and assign it to the BASE_URI key in the .env file. Doing so provides this value to the parts of your code that need it, such as the complete checkout URL (where the user is taken after successful payment) that you configured in the previous section.

Next, open your Rapyd Client Portal and navigate to Developers > Webhooks > Management. In the callback URL field, provide the value of the ngrok forwarding URL and append /webhook/rapyd to it.

Under the "Collect" section, make sure the Payment Completed event is selected so that Rapyd can send you a webhook once this event is triggered. Lastly, make sure you apply the changes by clicking Save.

Providing callback URL

Process the "Payment Completed" Webhook Event

Now it's time to process the payment completed webhook event from Rapyd. In the starter template, the /webhook/rapyd route has already been defined in the src/router/index.ts and src/router/webhook.router.ts files and mapped to the rapyd method of the WebhookController class in the src/controller/webhook.controller.ts file.

Before you can process the event, you need a utility function that will help you verify the integrity of the webhook message. To do this, create a new file named validateRapydWebhook.ts in the src/utils folder and add the following code:

import { Request } from "express";
import crypto from "crypto";

const ACCESS_KEY = process.env.RAPYD_ACCESS_KEY;
const SECRET_KEY = process.env.RAPYD_SECRET_KEY;

export function validateRapydWebhook(req: Request) {
    const receivedSignature = req.headers.signature;

    const urlPath = `${process.env.BASE_URI}/webhook/rapyd`;
    const salt = req.headers.salt;
    const timestamp = req.headers.timestamp;
    const bodyString = JSON.stringify(req.body);

    const toSign = `${urlPath}${salt}${timestamp}${ACCESS_KEY}${SECRET_KEY}${bodyString}`;
    const hash = crypto.createHmac("sha256", SECRET_KEY);
    hash.update(toSign);
    const signature = Buffer.from(hash.digest("hex")).toString("base64");

    return receivedSignature === signature;
}
Enter fullscreen mode Exit fullscreen mode

This code defines a function that validates a Rapyd webhook by comparing the signature sent with the webhook request to a signature it generates using the request's data and a secret key. If the signatures match, the webhook is considered authentic.

Open the src/controller/webhook.controller.ts file and import the utility function you just created by adding the following import statement:

import { validateRapydWebhook } from "../utils/validateRapydWebhook";
Enter fullscreen mode Exit fullscreen mode

Next, replace the rapyd method with the following:

rapyd = async (req: Request, res: Response, next: NextFunction) => {
    try {
        if (!validateRapydWebhook(req)) {
            return;
        }

        if (req.body.type !== "PAYMENT_COMPLETED") {
            return;
        }

        console.log("Payment Completed event received...");

        const completePaymentUrl: string = req.body.data.complete_payment_url;
        const rapydCheckoutId: string =
            completePaymentUrl.split("/")[completePaymentUrl.split("/").length];
        const rapydPaymentId: string = req.body.data.id;

        // Retrieve payment
        const payment = await this.paymentRepository.findOne({
            where: { rapydCheckoutId, status: PaymentStatus.PENDING },
            relations: ["order"],
        });

        if (!payment) {
            return;
        }

        // Update payment with the Rapyd payment ID
        await this.paymentRepository.update(payment.id, {
            rapydPaymentId,
            status: PaymentStatus.COMPLETED,
        });

        console.log("Updated payment", payment.id);

        // Mark order as COMPLETED
        await this.orderRepository.update(payment.order.id, {
            status: OrderStatus.COMPLETED,
        });
        console.log("Updated order", payment.order.id);
    } catch (error) {
        console.log(error);
        next(error);
    }
};
Enter fullscreen mode Exit fullscreen mode

This code defines a function to handle the Rapyd webhook. It validates the webhook, checks if the webhook type is PAYMENT_COMPLETED, retrieves the payment using the Rapyd checkout ID, updates the payment status to COMPLETED, and marks the associated order as COMPLETED.

Test the Application

Now that everything is set up, you can go ahead and test the application.

Run the server using the command npm run dev and open the ngrok forwarding URL in your web browser. You'll be prompted to sign up or log in. After successful authentication, add a few products to the cart and navigate to the cart page:

Cart page

Click Proceed to Checkout to go to the checkout page, where you'll see a message confirming your order is being created:

Checkout page

After the order is created, you'll be redirected to the payment page, where you need to provide the payment details. Use the following details for testing:

  • Card number: 4242 4242 4242 4242
  • Expiry date: 12/34
  • CVV: 567
  • Name: John Doe

Payment page

Click Place Your Order, and you should receive a successful payment notification:

Payment successful

Click Finish to be redirected back to the application.

Success page

You can access the full code for this tutorial on GitHub.

Conclusion

Integrating with Rapyd's payment gateway using a TypeScript client makes handling payments in your application easy. By leveraging OpenAPI specifications, you can easily generate a TypeScript client that helps automate interactions with Rapyd's API.

In this tutorial, you learned how to define webhooks, handle responses securely, and update the status of orders and payments. With these steps, you can efficiently manage payments and ensure a smooth user experience for your customers.

What are you waiting for? Integrate with Rapyd's API today.

Top comments (0)