Hi DEV Community,
I'm currently working on integrating Flutterwave into my application built with TypeScript, Express.js, Sequelize, and PostgreSQL. However, I’ve been facing a few challenges and would appreciate any guidance or resources you can share.
Here’s a summary of what I’ve done so far:
- Installed the flutterwave-node-v3 package and configured it with my public and secret keys.
- Created TypeScript functions for payment initialization and verification.
- Used Sequelize to model my database tables (e.g., Tickets and Events).
Issues Encountered:
a. When calling flw.initializePayment, I get the error:
TypeError: initializePayment is not a function.
b. Adding TypeScript support is proving tricky since there are no official TypeScript types for flutterwave-node-v3, and writing custom type declarations hasn’t resolved the issue.
c. Need help understanding the best practices for handling asynchronous callbacks and updating event and ticket statuses in the database.
What I’ve Tried:
// src/types/flutterwave-node-v3.d.ts
declare module 'flutterwave-node-v3' {
// TypeScript interfaces for the Flutterwave response and request types
export interface PaymentInitiateResponse {
status: string;
message: string;
data: {
link: string;
};
}
export interface TransactionVerifyResponse {
status: string;
message: string;
data: {
tx_ref: string;
flw_ref: string;
currency: string;
status: string;
};
}
export interface Flutterwave {
initializePayment(
payload: {
tx_ref: string;
amount: number;
currency: string;
redirect_url: string;
customer: {
email: string;
};
}
): Promise<PaymentInitiateResponse>;
TransactionVerify(
payload: { id: string }
): Promise<TransactionVerifyResponse>;
}
const Flutterwave: new (publicKey: string, secretKey: string) => Flutterwave;
export = Flutterwave;
}
import Flutterwave from "flutterwave-node-v3";
import { FLWPUBK, FLWSECK } from "../config";
const flw = new Flutterwave(FLWPUBK, FLWSECK);
export const initiatePayment = async (payload: {
tx_ref: string;
amount: number;
currency: string;
email: string;
redirect_url: string;
}) => {
try {
const response = await flw.Charge.card({
tx_ref: payload.tx_ref,
amount: payload.amount,
currency: payload.currency,
redirect_url: payload.redirect_url,
customer: {
email: payload.email,
},
payment_options: "card", // Optional: specify payment options
});
if (response.status === "success") {
return response.meta.authorization.redirect; // Payment link
} else {
throw new Error(response.message || "Failed to initiate payment.");
}
} catch (error) {
console.error("Payment initiation error:", error);
throw new Error("Failed to initiate payment.");
}
};
export const verifyPayment = async (transactionId: string) => {
try {
const response = await flw.Transaction.verify({ id: transactionId });
if (response.status === "success") {
return response.data;
} else {
throw new Error("Payment verification failed.");
}
} catch (error) {
console.error("Payment verification error:", error);
throw new Error("Failed to verify payment.");
}
};
`
import { Request, Response } from "express";
import { EventAttribute, EventInstance } from "../models/eventModel";
import { TicketAttribute, TicketInstance } from "../models/ticketModel";
import { v4 as uuidv4 } from "uuid";
import { JwtPayload } from "jsonwebtoken";
import QRCode from "qrcode";
import { NotificationInstance } from "../models/notificationModel";
import { initiatePayment, verifyPayment } from "../interface/payment.dto";
import { BASE_URL, FRONTEND_URL } from "../config";
import { UserAttribute, UserInstance } from "../models/userModel";
export const purchaseTicket = async (
req: JwtPayload,
res: Response
): Promise => {
const userId = req.user;
const { eventId } = req.params;
const { ticketType,currency } = req.body;
try {
const user = (await UserInstance.findOne({
where: { id: userId },
})) as unknown as UserAttribute;
if (!user) {
return res.status(404).json({ error: "USer not found" });
}
const event = (await EventInstance.findOne({
where: { id: eventId },
})) as unknown as EventAttribute;
if (!event) {
return res.status(404).json({ error: "Event not found" });
}
if (new Date() > new Date(event.date)) {
return res
.status(400)
.json({ error: "Cannot purchase tickets for expired events" });
}
const ticketPrice = event.ticketType[ticketType];
if (!ticketPrice) {
return res.status(400).json({ error: "Invalid ticket type" });
}
const ticketId = uuidv4();
const qrCodeData = {
ticketId,
userId,
eventId: event.id,
ticketType,
price: ticketPrice,
purchaseDate: new Date(),
};
const qrCode = await QRCode.toDataURL(JSON.stringify(qrCodeData));
const newTicket = await TicketInstance.create({
id: ticketId,
eventId: event.id,
userId,
ticketType,
price: ticketPrice,
purchaseDate: new Date(),
qrCode,
paid: false,
currency,
validationStatus: "Invalid",
}) as unknown as TicketAttribute;
const notification = await NotificationInstance.create({
id: uuidv4(),
title: "Ticket Purchase Successful",
message: `You have successfully purchased a ${ticketType} ticket for the event ${event.title}.`,
userId,
isRead: false,
});
const paymentLink = await initiatePayment({
tx_ref: newTicket.id,
amount: newTicket.price,
currency: newTicket.currency,
email: user.email,
redirect_url: `${BASE_URL}/tickets/callback`,
});
return res.status(201).json({
message: "Ticket created successfully",
paymentLink,
ticket: newTicket,
});
} catch (error: any) {
return res
.status(500)
.json({ error: "Failed to purchase ticket", details: error.message });
}
};
export const paymentVerification = async (
req: JwtPayload,
res: Response
): Promise => {
const { transaction_id, tx_ref } = req.query;
try {
// Verify payment
const paymentData = await verifyPayment(transaction_id as string);
if (paymentData.tx_ref === tx_ref) {
await TicketInstance.update(
{
paid: true,
validationStatus: "Valid",
flwRef: paymentData.flw_ref,
currency: paymentData.currency,
},
{
where: { id: tx_ref },
}
);
const ticket = (await TicketInstance.findOne({
where: { id: tx_ref },
})) as unknown as TicketAttribute;
const event = (await EventInstance.findOne({
where: { id: ticket?.eventId },
})) as unknown as EventAttribute;
if (event) {
if (event.quantity <= 0) {
throw new Error("Event is sold out");
}
await EventInstance.update(
{
quantity: event.quantity - 1,
sold: event.sold + 1,
},
{ where: { id: ticket?.eventId } }
);
}
res.redirect(`${FRONTEND_URL}/payment-success`);
} else {
throw new Error("Transaction reference mismatch");
}
} catch (error) {
console.error(error);
res.redirect(${FRONTEND_URL}/payment-failed
);
}
};`
Tech Stack Details:
- Backend Framework: Express.js
- ORM: Sequelize
- Database: PostgreSQL
- Language: TypeScript
Questions:
- Has anyone successfully integrated Flutterwave into a TypeScript + Express.js project?
- Are there any community-supported type declarations or better approaches to use this package?
- What’s the best way to handle the transaction_id callback for verifying payments and updating database records?
If you’ve encountered similar issues or have suggestions (articles, YouTube tutorials, or GitHub repositories), I’d greatly appreciate your help.
Thanks in advance! 🚀
Top comments (0)