Basic implementation of transaction history

Dec 18, 2024

In this article, we will see how to implement a basic transaction history of a user using Prisma and PostgreSQL.

Transaction history

Setting up the project

To set up the project, you need to have Node.js and PostgreSQL installed on your machine. You can download and install them from the official websites. If you do not want to install PostgreSQL locally on your machine, you can use a cloud-based PostgreSQL service like neon.tech.

Creating the Prisma schema

schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model userbankdetails {
  id         Int       @default(autoincrement())
  username   String    @id @db.VarChar(50) // Primary key and foreign key to users.username
  email      String    @unique @db.VarChar(255)
  upiid      String    @unique @db.VarChar(255)
  balance    Decimal?  @default(1000.00) @db.Decimal(10, 2)
  created_at DateTime? @default(now()) @db.Timestamptz(6)

  user       users     @relation(fields: [username], references: [username]) // Foreign key relation
  transactionsFrom Transaction[] @relation("TransactionsFrom")  // Transactions initiated by the user
  transactionsTo   Transaction[] @relation("TransactionsTo")    // Transactions received by the user
}

model users {
  id         Int       @default(autoincrement())
  username   String    @id @db.VarChar(50)
  email      String    @unique @db.VarChar(255)
  password   String    @db.VarChar(255)
  created_at DateTime? @default(now()) @db.Timestamptz(6)

  bankdetails userbankdetails? // One-to-one relation
}

model Transaction {
  id          Int       @id @default(autoincrement())
  from_upi_id String    @db.VarChar(255)
  to_upi_id   String    @db.VarChar(255)
  amount      Decimal   @db.Decimal(10, 2)
  created_at  DateTime  @default(now()) @db.Timestamptz(6)

  sender      userbankdetails @relation("TransactionsFrom", fields: [from_upi_id], references: [upiid])
  receiver    userbankdetails @relation("TransactionsTo", fields: [to_upi_id], references: [upiid])
}

Migrating the database

To create the database tables based on the Prisma schema, you need to run the following commands:

npx prisma migrate dev --name init

This command will create the necessary tables in the database based on the Prisma schema.

Let us create couple of endpoints in the API

  • /create-user - This endpoint will create a new user in the database.
async function createUser(username: string, email: string, password: string) {
    const user = await prisma.users.create({
        data: {
            username,
            email,
            password,
        },
    });
    return user;
}

app.post("/create-user", async (req, res) => {
    const { username, email, password } = req.body;

    try {
        // Create user and wait for the operation to complete
        createUser(username, email, password);

        // Send a successful response once the user is created
        res.status(201).json({
            message: "User created successfully",
        });
    } catch (err) {
        if (err instanceof Error) {
            console.error("Error creating user:", err.message);
        } else {
            console.error("Error creating user:", err);
        }
        res.status(500).json({
            error: "An error occurred while creating the user.",
        });
    }
});
Create User
  • /create-bank-account - This endpoint will create a new bank account for a user in the database.
async function createUserBankDetails(
    username: string,
    email: string,
    upiid: string,
    balance: number,
) {
    const userBankDetails = await prisma.userbankdetails.create({
        data: {
            username,
            email,
            upiid,
            balance,
        },
    });
    return userBankDetails;
}

app.post("/create-bank-account", async (req, res) => {
    const { username, email, upiid, balance } = req.body;
    try {
        const userBankDetails = await createUserBankDetails(
            username,
            email,
            upiid,
            balance,
        );
        res.status(201).json(userBankDetails);
    } catch (err) {
        if (err instanceof Error) {
            console.error("Error creating user bank details:", err.message);
        } else {
            console.error("Error creating user bank details:", err);
        }
        res.status(500).json({
            error: "An error occurred while creating the user bank details.",
        });
    }
});
Create Bank Account
  • /send-money - This endpoint will send money from one user to another based on their upi_id.
async function sendMoney(from: string, to: string, amount: number) {
    const result = await prisma.$transaction(async (prisma) => {
        // Check sender's balance
        const sender = await prisma.userbankdetails.findUnique({
            where: { upiid: from },
        });

        if (!sender) {
            throw new Error("Sender account does not exist.");
        }

        const senderBalance = sender.balance ?? 0;

        if (Number(senderBalance) < amount) {
            throw new Error("Insufficient balance.");
        }

        // Check receiver's balance
        const receiver = await prisma.userbankdetails.findUnique({
            where: { upiid: to },
        });

        if (!receiver) {
            throw new Error("Receiver account does not exist.");
        }

        const receiverBalance = receiver.balance ?? 0;

        // Update sender's balance
        await prisma.userbankdetails.update({
            where: { upiid: from },
            data: {
                balance: new Decimal(senderBalance).minus(new Decimal(amount)),
            },
        });

        // Update receiver's balance
        await prisma.userbankdetails.update({
            where: { upiid: to },
            data: {
                balance: new Decimal(receiverBalance).plus(new Decimal(amount)),
            },
        });

        // Create the transaction history record
        await prisma.transaction.create({
            data: {
                from_upi_id: from,
                to_upi_id: to,
                amount: amount,
            },
        });
    });
}

app.post("/send-money", async (req, res) => {
    const { from, to, amount } = req.body;

    if (amount <= 0) {
        return res.status(400).json({ error: "Invalid transaction amount." });
    }

    try {
        await sendMoney(from, to, amount);
        res.status(200).json({ message: "Transaction successful." });
    } catch (err) {
        if (err instanceof Error) {
            console.error("Error sending money:", err.message);
        }
    }
});
Send Money

Implementing the transaction history

  • /get-transaction-history - This endpoint will return the transaction history of a user based on their upi_id.
async function getTransactionHistory(upiid: string) {
    const transactions = await prisma.transaction.findMany({
        where: {
            OR: [
                { from_upi_id: upiid },
                { to_upi_id: upiid },
            ],
        },
        orderBy: {
            created_at: 'desc',
        },
    });
    console.log("Transactions found:", transactions);
    return transactions;
}

app.get("/get-transaction-history", async (req, res) => {
    const { upiid } = req.query;

    if (typeof upiid !== 'string') {
        return res.status(400).json({ error: "Invalid UPI ID." });
    }

    try {
        const transactions = await getTransactionHistory(upiid);
        res.status(200).json(transactions);
    } catch (err) {
        if (err instanceof Error) {
            console.error("Error getting transaction history:", err.message);
        } else {
            console.error("Error getting transaction history:", err);
        }
        res.status(500).json({
            error: "An error occurred while getting the transaction history.",
        });
    }
});
Transaction History
Mounish Vatti