How to deposit money to your wallet and use the money to pay bills with flutterwave in a Node.js application

kennedy Danielkennedy Daniel
11 min read

Prerequisites

  • Good understanding of JavaScript

  • MongoDB

  • Node.js

  • How to use Postman

  • Flutterwave account

  • Basic react.js

Flutterwave:

Flutterwave is a financial technology company. It provides a platform that enables businesses and individuals to make and accept payments. Flutterwave offers a wide range of payment solutions, including online card payments, mobile money, bank transfers, and more.

We are going to use Flutterwave APIs to build a bill-paying application.

Getting Started

Open your terminal or command prompt and navigate to the directory where you want to create your project. Then, run the following command to create a new directory for your project:

mkdir wallet-demo-with-flutterwave-backend
cd wallet-demo-with-flutterwave-backend

Inside your project directory, you need to initialize a Node.js project using the following command:

npm init

This command will guide you through setting up your project by asking a series of questions. You can press Enter to accept the default values or provide your own.

Installing some packages

Make sure you are in the wallet-demo-with-flutterwave-backend directory. To install some packages write the following command:

npm i axios bcryptjs dotenv express jsonwebtoken cors mongoose nodemon

nodemon is a utility that monitors changes in your Node.js application's source files and automatically restarts the application when changes are detected.

Create a folder in the root directory called src, in the src folder, create 5 folders named config, controllers, models, routes, and middleware. Also, create the index.js and .env files in the root directory.

Connecting Database and Creating Node.js Server

In the .env file

PORT = 3000
MONGO = // Mongodb uri
const env = require("dotenv");

require("./src/config/database").connect();
const express = require("express");
const app = express()

app.use(express.json());

env.config()


const { PORT } = process.env;
const port = process.env.PORT || PORT;

// server listening
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

in the database file:

const mongoose = require("mongoose");

const { MONGO } = process.env;

exports.connect = () => {
  //connecting database
  mongoose
    .connect(MONGO, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    })
    .then(() => {
      console.log("connected to database");
    })
    .catch((error) => {
      console.log("connection failed");
      console.error(error);
    });
};

Let's start our server by editing the script on the package.json.

  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

In the terminal type npm run dev then our server starts running and shows connected to database.

Creating the User Database

We will create a user database that stores user information like email and hash_password, first_name and last_name

const mongoose = require("mongoose");


const userSchema = new mongoose.Schema(
  {
    first_name: { type: String, default: null },
    last_name: { type: String, default: null },
    email: { type: String, unique: true },
    hash_password: { type: String },
  },
  {
    timestamps: true,
  }
);

userSchema.methods = {
//create a function that will take password as a parameter
  authenticate: function (password) {
//compare the password with the hash_password
    return bcrypt.compare(password, this.hash_password);
  },
};


module.exports = mongoose.model("User", userSchema);

Let's create a file in the controller folder called user, to handle the logging-in and authentication.

const User = require("../model/user");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");


exports.signup = (req, res) => {
  // check if user already exists
  User.findOne({ email: req.body.email }).exec(async (err, user) => {
    if (user) return res.status(400).json({ msg: "user already exist" });
    console.log(req)
    //get the user data from req.body
    const { first_name, last_name, email, password } = req.body;
    //hash the password
    const hash_password = await bcrypt.hash(password, 10);
    // create new user
    const _user = new User({
      first_name,
      last_name,
      email,
      hash_password,
    });

    _user.save((err, data) => {
      if (err) return res.status(400).json({ err });
      if (data)
        return res.status(201).json({ user: "user created successsfully" });
    });
  });
};

Code to login in user:

exports.signin = (req, res) => {
//check if the email gotten from the user is the same with the eamil in the database
  User.findOne({ email: req.body.email }).exec(async (err, user) => {
    if (err) return res.status(400).json({ err });
    if (user) {
      console.log(user)
//pass the password to the authentication function that we created in the model page
      const promise = await user.authenticate(req.body.password);

      if (promise) {
        const token = jwt.sign(
          { _id: user._id },
          process.env.TOKEN,
          {}
        );
        const { _id, first_name, last_name, email, } = user;
        res.status(200).json({
          token,
          user: { _id, first_name, last_name, email, },
        });
      } else {
        return res.status(400).json({ msg: "invalid password" });
      }
    } else {
      return res.status(400).json({ msg: "something went wrong" });
    }
  });
};

Add the TOKEN to your env file

Go to the route folder and create a file user.js , paste the code below in file,

const express = require("express");
const { signup, signin  } = require("../controllers/user");
const router = express.Router();


router.post("/register" ,signup) ;
router.post("/signin", signin);

module.exports = router;

Go to your index.js file and import the user.js route

const userRoute = require('./src/routes/user')

app.use('/api', userRoute)

Go to Postman and try the sign in and register

Middleware for Protected Routes

After sign in a token is always generated... if a user is not signed in we need middleware to protect the route.

const jwt = require("jsonwebtoken");
const path = require('path')

exports.requireSignin = (req, res, next) => {
  if (req.headers.authorization) {
    const token = req.headers.authorization.split(" ")[1];
    const user = jwt.verify(token, process.env.TOKEN_KEY);
    req.user = user;
  } else {
    res.status(400).json({ msg: "not authorized" });
  }

  next();
};

in the route folder create a file for transactions and wallet. this 2 route are going to be protected.

const express = require("express");
const {
  transactionResponce,
  getTranSAction,
} = require("../controllers/transactions");
const { requireSignin } = require("../middleware");
const router = express.Router();

router.get("/response", transactionResponce);
router.get("/gettransaction", requireSignin, getTranSAction);

module.exports = router;

getTransaction is a protected endpoint because only the user should be able to see his/her transactions. same with getWallet endpoint.

const express = require("express");
const { getWallet, updateWallet  } = require("../controllers/wallet");
const router = express.Router();
const { requireSignin } = require("../middleware/index");


router.get("/wallet/balance" ,requireSignin, getWallet) ;



module.exports = router;

Deposit money to your Flutterwave wallet.

Go to Flutterwave documentation to get your API keys

We are going to use react.js as the frontend js framework for this project. After installing react and setting it up, install Flutterwave in your react app npm i flutterwave-react-v3, then create a component.

create a transaction.js file in the model folder.

model/transaction.js

const { Schema, model } = require("mongoose");

const transactionSchema = Schema(
  {
    userId: {
      type: Schema.Types.ObjectId,
      ref: "user",
    },
    transactionId: {
      type: Number,
      trim: true,
    },
    email: {
      type: String,
      required: [true, "email is required"],
      trim: true,
    },
    phone: {
      type: String,
    },
    amount: {
      type: Number,
      required: [true, "amount is required"],
    },
    currency: {
      type: String,
      required: [true, "currency is required"],
      enum: ["NGN"],
    },
    paymentStatus: {
      type: String,
      enum: ["successful", "pending", "failed"],
      default: "pending",
    },
    paymentGateway: {
      type: String,
      required: [true, "payment gateway is required"],
      enum: ["flutterwave"], 
    },
  },
  {
    timestamps: true,
  }
);

const Transaction = model("Transaction", transactionSchema);
module.exports = Transaction;

controllers/transaction.js

const User = require("../model/user");
const WalletTransaction = require("../model/wallet_transaction");
const Wallet = require("../model/wallet");
const axios = require("axios");
const path = require("path");
const Transaction = require("../model/transaction");


exports.transactionResponce = async (req, res) => {
  const { tx_ref } = req.query;
  console.log(tx_ref);

  // URL with transaction ID of which will be used to confirm transaction status
  const url = `https://api.flutterwave.com/v3/transactions/verify_by_reference?tx_ref=${tx_ref}`;

  // Network call to confirm transaction status
  const response = await axios({
    url,
    method: "get",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY_TWO}`,
    },
  });

  const { status, currency, id, amount, customer } = response.data.data;
  // console.log(response.data.data);

  // check if transaction id already exist

  const transactionExist = await Transaction.findOne({ transactionId: id });

  if (transactionExist) {
    return res.status(409).send("Transaction Already Exist");
  }
  // check if customer exist in our database
  const user = await User.findOne({ email: customer.email });
  console.log(amount); 
  console.log(customer.email);
};

In the above code snippet, we want to get the tx_ref from our query after a successful transaction and pass it to the flutterwave URL to get the status, currency, id, amount and customer. If the transaction exists then the user will be notified.

We check if the customer exists in our database via the customer's email.

Check if the user has a Wallet

Next, we check if the user has a wallet if the user has we create a transaction and save it to the database, if the user does not have a wallet we create a wallet.

const User = require("../model/user");
const WalletTransaction = require("../model/wallet_transaction");
const Wallet = require("../model/wallet");
const axios = require("axios");
const path = require("path");
const Transaction = require("../model/transaction");

const validateUserWallet = async (userId) => {
  try {
    // check if user have a wallet, else create wallet
    const userWallet = await Wallet.findOne({ userId });

    if (!userWallet) {
      // create wallet
      const wallet = await Wallet.create({
        userId,
      });
      return wallet;
    }
    return userWallet;
  } catch (error) {
    console.log(error);
  }
};

const createWalletTransaction = async (userId, status, currency, amount) => {
  try {
    // create wallet transaction
    const walletTransaction = await WalletTransaction.create({
      amount,
      userId,
      isInflow: true,
      currency,
      status,
    });
    return walletTransaction;
  } catch (error) {
    console.log(error);
  }
};

const createTransaction = async (
  userId,
  id,
  status,
  currency,
  amount,
  customer
) => {
  try {
    // create transaction
    const transaction = await Transaction.create({
      userId,
      transactionId: id,
      name: customer.name,
      email: customer.email,
      phone: customer.phone_number,
      amount,
      currency,
      paymentStatus: status,
      paymentGateway: "flutterwave",
    });
    return transaction;
  } catch (error) {
    // console.log(error);
  }
};

const updateWallet = async (userId, amount) => {
  try {
    // update wallet
    const wallet = await Wallet.findOneAndUpdate(
      { userId },
      { $inc: { balance: amount } },
      { new: true }
    );
    return wallet;
  } catch (error) {
    // console.log(error);
  }
};

exports.transactionResponce = async (req, res) => {
  const { transaction_id, tx_ref } = req.query;
  console.log(tx_ref);

  // URL with transaction ID of which will be used to confirm transaction status
  const url = `https://api.flutterwave.com/v3/transactions/verify_by_reference?tx_ref=${tx_ref}`;

  // Network call to confirm transaction status
  const response = await axios({
    url,
    method: "get",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY_TWO}`,
    },
  });

  const { status, currency, id, amount, customer } = response.data.data;

  // check if transaction id already exist
  const transactionExist = await Transaction.findOne({ transactionId: id });

  if (transactionExist) {
    return res.status(409).send("Transaction Already Exist");
  }
  // check if customer exist in our database
  const user = await User.findOne({ email: customer.email });
  console.log(amount); 
  console.log(customer.email);

  // check if user have a wallet, else create wallet
  const wallet = await validateUserWallet(user._id);

  // create wallet transaction
  await createWalletTransaction(user._id, status, currency, amount);

  // create transaction
  await createTransaction(user._id, id, status, currency, amount, customer);

  await updateWallet(user._id, amount);
  if (wallet) {
    res.sendFile(path.join(__dirname + "/index.html"));
  }



};

  exports.getTranSAction = async (req,res) => {
    const bills = await Transaction.find({userId:req.user._id});

    res.status(200).json({
      success: true,
      bills,
    });
  }

In the snippet above we also upadated the wallet, to add to add to the amount in the wallet whenever there is a deposit

then we get transactions of a particular user from the database.

Paying Bills From The wallet

get categories from flutterwave bill paying api, For more information visit the documentation https://developer.flutterwave.com/docs/making-payments/bill-payments/

// Get categories for different bills 

exports.getDataCats = async (req, res) => {
  var config = {
    method: "GET",
    url: `https://api.flutterwave.com/v3/bill-categories?data_bundle=1`,
    headers: {
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
  };

  axios(config).then(function (response) {

    const getdataSuccess = {
      type: response.data,
    };
     res.status(200) .json({ message: "success", getdataSuccess });
  });
};

exports.getAirtimeCats = async (req, res) => {
  var config = {
    method: "GET",
    url: `https://api.flutterwave.com/v3/bill-categories?airtime=1`,
    headers: {
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
  };

  axios(config).then(function (response) {

    const getdataSuccess = {
      type: response.data,
    };
     res.status(200) .json({ message: "success, getdataSuccess });
  });
};

exports.getCableCats = async (req, res) => {
  var config = {
    method: "GET",
    url: `https://api.flutterwave.com/v3/bill-categories?cable=1`,
    headers: {
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
  };
  axios(config).then(function (response) {

    const getdataSuccess = {
      type: response.data,
    };
     res.status(200) .json({ message: "success", getdataSuccess });

  });

};

exports.getPowerCats = async (req, res) => {
  var config = {
    method: "GET",
    url: `https://api.flutterwave.com/v3/bill-categories?power=1`,
    headers: {
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
  };
  axios(config).then(function (response) {

    const getdataSuccess = {
      type: response.data,
    };
     res.status(200) .json({ message: "success", getdataSuccess });
  });

};

To pay the bills and save it in the database

exports.createBill = async (req, res) => {
  var config = {
    method: "POST",
    url: `https://api.flutterwave.com/v3/bills`,
    headers: {
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    data: {
      country: req.body.country,
      customer: req.body.customer,
      amount: req.body.amount,
      type: req.body.type,
      reference: Math.random() * 1000000000,
    },

  };
  console.log(req.user)

  axios(config)
    .then(function (response) {
      console.log(JSON.stringify(response.data));

      const billsData = {
        user: req.user._id,
        network: response.data.data.network,
        phone: response.data.data.phone_number,
        amount: response.data.data.amount,
        transactionId: response.data.data.flw_ref
      };
      const billsPayed = new PayBills(billsData);
      billsPayed.save((err, billsPayed) => {
        if (err) res.status(400).json({ err });
        if (billsPayed)
          res
            .status(200)
            .json({ message: "success", billsPayed });
      });
    })

    .catch(function (error) {
      console.log(error);
    });
};

Let's test the APIs with Postman, we register and then put the token on the bearer token tab under the authorization tab.

We have a successful payment.

Update wallet amount after paying a bill.

After paying a bill we update the amount in the user's wallet.

exports.updateWallet = async (req, res) => {
    //get the params using req.params and pass it it the flutterwave url 
  const url = `https://api.flutterwave.com/v3/bills/${req.params.transId}`;
  const response = await axios({
    url,
    method: "get",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Authorization: `${process.env.FLUTTERWAVE_V3_SECRET_KEY}`,
    },
  });

  // get the amount spent in paying a bill by the user from the response
  const {  amount } = response.data.data;
  try {
    // get the email of the user from params and find the user in the database
    const account = await User.findOne({ email: req.params.user });
    const user = account._id
    console.log(user)
    // find the a wallet and update it if the user is in the database
      const wallet = await Wallet.findOneAndUpdate(
        { userId: user },
        //subtract the amount payed from the amount in the users wallet
        { $inc: { balance: -amount } },
        { new: true }
      );
      res.status(200).json(wallet);
    // }


  } catch (error) {
    console.log(error);
  }
};

Lets get the current amount in the database

Let's check the current amount after paying a bill

Conclusion

We learned how to authenticate, protect routes, deposit to our mobile wallet and pay bills from our mobile wallet.

Reference

Flutterwave documentation

Olanetsoft's Blog

Connect with me on Twitter,

0
Subscribe to my newsletter

Read articles from kennedy Daniel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

kennedy Daniel
kennedy Daniel