DEV Community

Cover image for Need Help Setting Up Flutterwave with TypeScript, Express.js, Sequelize, and PostgreSQL
Walex Eniola
Walex Eniola

Posted on

Need Help Setting Up Flutterwave with TypeScript, Express.js, Sequelize, and PostgreSQL

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:

  1. Installed the flutterwave-node-v3 package and configured it with my public and secret keys.
  2. Created TypeScript functions for payment initialization and verification.
  3. 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;
  }

Enter fullscreen mode Exit fullscreen mode
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.");
  }
};

Enter fullscreen mode Exit fullscreen mode

`
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,
});
Enter fullscreen mode Exit fullscreen mode

} 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");
}
Enter fullscreen mode Exit fullscreen mode

} 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:

  1. Has anyone successfully integrated Flutterwave into a TypeScript + Express.js project?
  2. Are there any community-supported type declarations or better approaches to use this package?
  3. 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! 🚀

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay