Vict E-commerce: Open for business

Vict E-commerce: Open for business
Photo by Clay Banks / Unsplash

For the last blog Vict E-commerce: Adding Basic Security I implemented a change going from JWT to sessions while my overall understanding of security has improved slightly there is still room for improvement not only on my comprehension, but also in the code for this project. If you would like to read about the changes Jamil is doing check out his site: jaml.co

Over the previous two blogs we focused on making sure things were coming along to allow for merchants to have what they need to interact with for products & storefronts. Now that those pieces have been developed we want to shift focus to make progress on the customer portion of the e-commerce application.

First Time Customers

Similar to merchants we made a model for sequelize to use as a database table. We tracked the following things in a database:

  • First Name
  • Last Name
  • Email
  • Password
  • ID
  • Created_At
  • Updated_At

The model is put in the db directory in our src directory:

module.exports = (sequelize, DataTypes) => {
	const Customer = sequelize.define("customers", 
	{
		firstName: {
			type: DataTypes.STRING,
			allowNull: false,
		},
		lastName: {
			type: DataTypes.STRING,
			allowNull: false
		},
		email: {
			type: DataTypes.STRING,
			allowNull: false,
			unique: true,
			validate: {
				isEmail: {
					msg: "Must be a valid email address"
				}
			}
		},
		password: {
			type: DataTypes.STRING,
			allowNull: false,
		},
		id: {
			type: DataTypes.UUID,
			defaultValue: DataTypes.UUIDV4,
			primaryKey: true,
			allowNull: false
		}
	});

	return Customer
}

Customer Model

Sequelize generates for us the created_at & updated_at options as well as generating a uuid upon an entry into the database being added.

Customer Business Logic

Now that sequelize can use the customer model we want our server to perform three functions to register, login, & logout a customer. In the controller directory we want to make a customer controller.

For the register function we want it to do the following things:

  1. Collect the necessary data from the request body
  2. Define the number of salt rounds to use for bcrypt
  3. Encrypt the password that the customer provides
  4. Make a new customer entry in the database
  5. Make a new session for the recently registered customer
  6. Send a response that the entry was successful

The code should look like the following:

const registerCustomer = async (req, res) => {
	const { firstName, lastName, email, password } = req.body;
	const saltRounds = 10;

	try {
		const hashPassword = await bcrypt.hash(password, saltRounds);

		const customer = await Customer.create({
			firstName,
			lastName,
			email,
			password: hashPassword
		});

		req.session.userId = customer.id;
		return res.status(200).send({message: "Registration successful", customer});
	} catch (error) {
		console.error("Registration error:", error);
		return res.status(500).send(error.message);
	}
}

Register customer function

With a customer registered we want to allow them to login. The login function should do the following:

  1. Collect the email & password from the request body
  2. Check if the provided email is a customer in the customer database
  3. Compare the provided password to the one stored in the database using bcrpyt
  4. Make a new session for the customer
  5. Send a response that the customer successfully logged in

The login code should look like this:

const loginCustomer = async (req, res) => {
	const { email, password } = req.body;

	try {
		const customer = await Customer.findOne({ where: { email } });
		const passwordMatch = await bcrypt.compare(password, customer.password);

		if (customer == null || !passwordMatch) {
			return res.status(401).send("Incorrect Email or Password");
		}

		req.session.userId = customer.id;
		res.status(200).send(customer);
	} catch (error) {
		console.error("Login error:", error);
		return res.status(500).send(error.message);
	}
}

Login customer function

Lastly for the customer controller functions is the function to logout a customer. Its logic is much simpler than registering or logging in a customer. We simply need to know the session we want to remove from the database & delete that entry.

const logoutCustomer = async (req, res) => {
	const sId = req.session.id;

	req.session.destroy(sId, () => {
		res.send("Customer has been logged out");
	});
}

Logout customer function

With the model & controller made for the customer with need to make the routes necessary for the server to perform them upon request. Simply import the controller into the router file & make three post routes that each execute a function.

const express = require("express");
const router = express.Router();
const { registerCustomer, loginCustomer, logoutCustomer } = require("../controllers/customer.controller");

router.post("/register", registerCustomer);
router.post("/login", loginCustomer);
router.post("/logout", logoutCustomer);


module.exports = router;

Customer Router

Making a purchase

As with the rest of our code we want to make a model for sequelize to use to store transactions. The simplest transaction needs to track, the customer that wants to make a purchase, the product being purchased, the total of the transaction, and the id for the transaction.

module.exports = (sequelize, DataTypes) => {
	const Transaction = sequelize.define("transactions", 
	{
		id: {
			type: DataTypes.UUID,
			defaultValue: DataTypes.UUIDV4,
			primaryKey: true,
			allowNull: false
		},
		total: {
			type: DataTypes.DECIMAL(10, 2),
			allowNull: false
		},
		customer_id: {
			type: DataTypes.STRING,
			allowNull: false
		},
		product_id: {
			type: DataTypes.STRING,
			allowNull: false
		}
	});

	return Transaction
}

Transaction model

While it works as a transaction model I currently have defined it so it doesn't track multiple products. I will either have to change how a transaction tracks products or simply track that a transaction occurred and orders/purchases track products for a transaction.

Transaction Business Logic

The logic necessary for transaction at this time needs to perform the following:

  • Create a transaction upon purchase
  • Read All Transaction that a customer performed
  • Refunding a transaction

In order to create a transaction we first need to collect the transaction total, the product's id, & the customer's id from the request body. The next part is to create a transaction entry in the database. Finally sending a response if the transaction was completed successfully.

const createTransaction = async (req, res) => {
	const { total, product_id, customer_id } = req.body;

	try {
		const transaction = await Transaction.create({
			total,
			product_id,
			customer_id
		});

		return res.status(201).send("Transaction complete");
	} catch (error) {
		return res.send(error);
	}
};

Create Transaction Function

At the moment I only made it possible to collect all the transactions for a customer not for a storefront or even a single transaction. To view all the transactions for a customer simply collect the customer id from the request query.

const getAllCustomerTransactions = async (req, res) => {
	const customer_id = req.query.customer_id
	
	let { count, rows } = await Transaction.findAndCountAll({ where: { customer_id }});

	return res.send(rows);
};

Get All of a Customer's Transactions

Similar to the get all of a customer's transactions we only need to know the transaction's id and delete it from the database.

const refundTransaction = async (req, res) => {
	let id = req.query.transaction_id;

	try {
		await Transaction.destroy({
			where: {
				id,
			},
		});
		if (result[0] !== 0) {
			return res.send("Transaction refund successfully");
		}

		return res.status(404).send("Transaction not found.");	
	} catch (error) {
		return res.send(error);
	}
}

Delete Transaction

Future Plans

With the customer & transactions now added this means we have made a fairly simple store, that is unable to process payment to complete transactions, but who is keeping track of that. We want to spend our next update making improvement to the actual user experience of the store.

On the customer side of things we first want to implement a cart to track multiple items for a transaction. Modify transactions for multiple product purchases. View their order history. While on the merchant side we want to modify their orders to track and perform actions on them. For both of them we want to add roles to prevent one or the other from performing actions unless they are supposed to.

If you would like to check out the code or us it for yourself: Github Link