Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/bank-webhook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"@repo/db": "*",
"@types/express": "^4.17.21",
"@types/ws": "^8.18.1",
"axios": "^1.13.2",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"esbuild": "^0.25.10",
Expand Down
222 changes: 76 additions & 146 deletions apps/bank-webhook/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,147 +3,85 @@ import cors from "cors";
import crypto from "crypto";
import db from "@repo/db/client";
import prisma from "@repo/db/client";
import axios from "axios";

const app = express();
app.use(cors({ origin: ["http://localhost:3001"] }));
app.use(express.json());

app.post("/hdfcWebhook", async (req, res) => {
const { token, amount, status, type } = req.body;
const { token, amount, status, type } = req.body;

try {
if (type === "ONRAMP") {
const transaction = await db.onRampTransaction.findUnique({
where: { token },
});

if (!transaction) {
return res.status(404).json({ message: "Transaction not found" });
}

const paymentInformation = {
token,
userId: transaction.userId,
amount: Number(amount),
};

const [balanceResult, transactionResult] = await db.$transaction([
db.balance.upsert({
where: { userId: paymentInformation.userId },
update: { amount: { increment: paymentInformation.amount } },
create: {
userId: paymentInformation.userId,
amount: paymentInformation.amount,
locked: 0,
},
}),
db.onRampTransaction.update({
where: { token: paymentInformation.token },
data: { status: status === "SUCCESS" ? "Success" : "Failure" },
}),
]);

console.log("✅ Balance:", balanceResult.amount);
console.log("✅ Transaction:", transactionResult.status);
} else if (type === "BILL") {
const bill = await db.billSchedule.findUnique({
where: { token },
});

if (!bill) {
return res.status(404).json({ message: "Bill not found" });
}

const billStatus = status === "SUCCESS" ? "PAID" : "OVERDUE";
await db.billSchedule.update({
where: { token },
data: { status: billStatus },
});

console.log("✅ Bill:", bill.id, billStatus);

const notifyEndpoints = ["http://localhost:3001/api/bills/notify"];

for (const endpoint of notifyEndpoints) {
await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ billId: bill.id, status: billStatus, token }),
});
}
} else if (type === "P2P") {
const transaction = await db.p2pTransfer.findUnique({
where: { id: token },
});

if (!transaction)
return res.status(404).json({ message: "P2P not found" });

if (status === "SUCCESS") {
await db.$transaction([
db.balance.upsert({
where: { userId: transaction.toUserId! },
update: { amount: { increment: transaction.amount } },
create: {
userId: transaction.toUserId!,
amount: transaction.amount,
locked: 0,
},
}),
db.p2pTransfer.update({
where: { id: transaction.id },
data: { status: "SUCCESS" },
}),
]);
}
} else if (type === "FREEZE") {
// Find the wrong-send request by the bank token stored on the wrongSendRequest record.
// (Querying transaction.bankToken failed because p2pTransfer doesn't have that field.)
const request = await db.wrongSendRequest.findFirst({
where: { txnId : Number(token) },
include: {
sender: { select: { id: true, name: true, number: true } },
transaction: true,
},
});

if (!request) return res.status(404).json({ message: "Request not found" });

// Safe access (use optional chaining). convert paise -> rupees for message.
const amountRupees = Number(request.amount) / 100;
const sender = (request as any).sender;
const senderName = sender?.name ?? "Someone";
const senderNumber = sender?.number;
const receiverNumber = request.receiverNumber;

// Notify receiver via SMS
if (receiverNumber) {
const msg = `${senderName} sent ₹${amountRupees} by mistake. Return in 24 hrs or ₹50 fee: yourapp.com/return/${request.id}`;
await axios.get(
`https://www.fast2sms.com/dev/bulkV2?authorization=${process.env.FAST2SMS}&message=${encodeURIComponent(
msg
)}&numbers=${receiverNumber}`
);
}

// Notify sender
if (senderNumber) {
await axios.get(
`https://www.fast2sms.com/dev/bulkV2?authorization=${process.env.FAST2SMS}&message=${encodeURIComponent(
`Bank frozen ₹${amountRupees}. Receiver notified.`
)}&numbers=${senderNumber}`
);
}
} else {
return res.status(400).json({ message: "Invalid type" });
try {
if (type === "ONRAMP") {
const transaction = await db.onRampTransaction.findUnique({
where: { token }
});

if (!transaction) {
return res.status(404).json({ message: "Transaction not found" });
}

const paymentInformation = {
token,
userId: transaction.userId,
amount: Number(amount)
};

const [balanceResult, transactionResult] = await db.$transaction([
db.balance.upsert({
where: { userId: paymentInformation.userId },
update: { amount: { increment: paymentInformation.amount } },
create: {
userId: paymentInformation.userId,
amount: paymentInformation.amount,
locked: 0
}
}),
db.onRampTransaction.update({
where: { token: paymentInformation.token },
data: { status: status === "SUCCESS" ? "Success" : "Failure" }
})
]);

console.log("✅ Balance:", balanceResult.amount);
console.log("✅ Transaction:", transactionResult.status);
} else if (type === "BILL") {
const bill = await db.billSchedule.findUnique({
where: { token }
});

if (!bill) {
return res.status(404).json({ message: "Bill not found" });
}

const billStatus = status === "SUCCESS" ? "PAID" : "OVERDUE";
await db.billSchedule.update({
where: { token },
data: { status: billStatus }
});

console.log("✅ Bill:", bill.id, billStatus);

const notifyEndpoints = [
"http://localhost:3001/api/bills/notify"
];

for (const endpoint of notifyEndpoints) {
await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ billId: bill.id, status: billStatus, token })
});
}
} else {
return res.status(400).json({ message: "Invalid type" });
}

res.json({ message: "Captured" });
} catch (e) {
console.error("❌ Webhook error:", e);
res.status(500).json({ message: "Processing failed" });
}

res.json({ message: "Captured" });
} catch (e) {
console.error("❌ Webhook error:", e);
res.status(500).json({ message: "Processing failed" });
}
});

app.post("/webhook/upi-payment", async (req, res) => {
Expand All @@ -153,9 +91,7 @@ app.post("/webhook/upi-payment", async (req, res) => {
return res.status(400).json({ error: "Missing qrId or transactionId" });
}

const payment = await prisma.merchantPayment.findUnique({
where: { qrId },
});
const payment = await prisma.merchantPayment.findUnique({ where: { qrId } });
if (!payment) {
return res.status(404).json({ error: "Payment not found" });
}
Expand Down Expand Up @@ -192,9 +128,7 @@ app.post("/webhook/qr-payment", async (req, res) => {
if (event === "qr_code.paid") {
const { qr_id, amount, payment_id } = req.body.payload.qr_code.entity;
try {
const payment = await prisma.merchantPayment.findUnique({
where: { qrId: qr_id },
});
const payment = await prisma.merchantPayment.findUnique({ where: { qrId: qr_id } });
if (!payment) {
return res.status(404).json({ error: "Payment not found" });
}
Expand All @@ -206,15 +140,11 @@ app.post("/webhook/qr-payment", async (req, res) => {
});

// Transfer amount to merchant (simplified, update merchant balance or trigger transfer)
const merchant = await prisma.merchant.findUnique({
where: { id: payment.merchantId },
});
const merchant = await prisma.merchant.findUnique({ where: { id: payment.merchantId } });
// Add logic to update merchant balance or initiate payout if needed

// Trigger notification (e.g., via email or push notification)
console.log(
`Payment of ₹${amount / 100} successful for merchant ${merchant?.name}`
);
console.log(`Payment of ₹${amount / 100} successful for merchant ${merchant?.name}`);

res.status(200).json({ status: "success" });
} catch (error) {
Expand All @@ -231,4 +161,4 @@ app.get("/health", (req, res) => {
});

const PORT = process.env.PORT || 3002;
app.listen(PORT, () => console.log(`Webhook backend running on port ${PORT}`));
app.listen(PORT, () => console.log(`Webhook backend running on port ${PORT}`));
Loading