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:
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
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
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:
After successfully signing up, you'll be redirected to the home page, where you can see a 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:
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
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());
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
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);
}
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 };
}
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";
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);
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 });
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
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
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.
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;
}
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";
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);
}
};
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:
Click Proceed to Checkout to go to the checkout page, where you'll see a message confirming your order is being created:
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
Click Place Your Order, and you should receive a successful payment notification:
Click Finish to be redirected back to the application.
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)