How to deposit money to your wallet and use the money to pay bills with flutterwave in a Node.js application
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
Connect with me on Twitter,
Subscribe to my newsletter
Read articles from kennedy Daniel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by