Skip to content

Commit

Permalink
Add: mock hdfc added, successful transaction possible using hdfc
Browse files Browse the repository at this point in the history
  • Loading branch information
sudhanshugautam2911 committed Nov 7, 2024
1 parent 77a453c commit bd1e8e6
Show file tree
Hide file tree
Showing 31 changed files with 1,409 additions and 111 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
6 changes: 3 additions & 3 deletions apps/bank-webhook/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ app.post("/hdfcWebhook", async (req, res) => {
token: string;
userId: string;
amount: string;
status: string;
paymentStatus: string;
} = {
token: req.body.token,
userId: req.body.user_identifier,
amount: req.body.amount,
status: req.body.status
paymentStatus: req.body.paymentStatus
};

try {
if (status == 'success') {
if (paymentInformation.paymentStatus == 'success') {
await db.$transaction([
db.balance.updateMany({
where: {
Expand Down
4 changes: 2 additions & 2 deletions apps/merchant-app/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "web",
"name": "merchant-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev --port 3000",
"build": "next build",
"start": "next start",
"lint": "eslint . --max-warnings 0"
Expand Down
40 changes: 40 additions & 0 deletions apps/mock-hdfc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# env files (can opt-in for commiting if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions apps/mock-hdfc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Binary file added apps/mock-hdfc/app/favicon.ico
Binary file not shown.
Binary file added apps/mock-hdfc/app/fonts/GeistMonoVF.woff
Binary file not shown.
Binary file added apps/mock-hdfc/app/fonts/GeistVF.woff
Binary file not shown.
3 changes: 3 additions & 0 deletions apps/mock-hdfc/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
35 changes: 35 additions & 0 deletions apps/mock-hdfc/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});

export const metadata: Metadata = {
title: "Netbaking Portal - dummy bank",
description: "This is hdfc netbanking dummy portal to make payments.",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
40 changes: 40 additions & 0 deletions apps/mock-hdfc/app/lib/actions/cancelPayment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use server";
import axios from 'axios';
import jwt from 'jsonwebtoken';


export async function cancelPayment(token: any) {
// Imagine Logic here: Reduce users balance from hdfc bank and tell to update balance on payment-pe website

if (!token) {
return {
status: 400,
message: 'Token is required',
};
}

try {
const decodedData = jwt.verify(token, process.env.JWT_SECRET || '') as { userId: string; amount: number };
const payload = {
token: token,
user_identifier: decodedData.userId,
amount: decodedData.amount,
status: 'failure'
}
// console.log(payload);

await axios.post(process.env.BANK_WEBHOOK || '', payload)

return {
status: 200,
message: 'Success',
};
} catch (error) {
console.log("Token verification error:", error);

return {
status: 401,
message: "Invalid or expired token",
};
}
}
39 changes: 39 additions & 0 deletions apps/mock-hdfc/app/lib/actions/validatePayment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use server";
import axios from 'axios';
import jwt from 'jsonwebtoken';


export async function validatePayment(token: any) {
// Imagine Logic here: Reduce users balance from hdfc bank and tell to update balance on payment-pe website

if (!token) {
return {
status: 400,
message: 'Token is required',
};
}

try {
const decodedData = jwt.verify(token, process.env.JWT_SECRET || '') as { userId: string; amount: number };
const payload = {
token: token,
user_identifier: decodedData.userId,
amount: decodedData.amount*100,
paymentStatus: 'success'
}

await axios.post(process.env.BANK_WEBHOOK || '', payload)

return {
status: 200,
message: 'Success',
};
} catch (error) {
console.log("Error:", error);

return {
status: 401,
message: "Unable to proceed!",
};
}
}
29 changes: 29 additions & 0 deletions apps/mock-hdfc/app/lib/actions/verifyToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use server";
import jwt from 'jsonwebtoken';


export async function verifyToken(token: any) {

if (!token) {
return {
status: 400,
message: 'Token is required',
};
}

try {
const decodedData = jwt.verify(token, process.env.JWT_SECRET || '') as { userId: string; amount: number };

return {
status: 200,
data: decodedData,
};
} catch (error) {
console.log("Token verification error:", error);

return {
status: 401,
message: "Invalid or expired token",
};
}
}
8 changes: 8 additions & 0 deletions apps/mock-hdfc/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { redirect } from 'next/navigation'


export default function Home() {
return (
redirect('/pay-money')
);
}
122 changes: 122 additions & 0 deletions apps/mock-hdfc/app/pay-money/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use client'

import { Card } from '@repo/ui/card'
import { TextInput } from '@repo/ui/textinput'
import { useSearchParams } from 'next/navigation'
import { useEffect, useState, Suspense } from 'react'
import { verifyToken } from '../lib/actions/verifyToken'
import { Button } from '@repo/ui/button'
import { validatePayment } from '../lib/actions/validatePayment'
import { cancelPayment } from '../lib/actions/cancelPayment'

const PayMoneyContent = () => {
const [accountNumber, setAccountNumber] = useState(123412341234)
const [password, setPassword] = useState("testing-password")
const [userId, setUserId] = useState("")
const [amount, setAmount] = useState(0)
const [loading, setLoading] = useState(false)
const [error, setError] = useState("")

const searchParams = useSearchParams()
const token = searchParams.get('token')

useEffect(() => {
async function fetchDecodedCode() {
if (!token) {
setError("Token is required")
return
}

setLoading(true)
try {
const res = await verifyToken(token)
if (res.status === 200) {
const decoded = res.data
setUserId(decoded?.userId ?? "")
setAmount(decoded?.amount ?? 0)
} else {
throw new Error("Invalid Token")
}
} catch (err) {
setError("Invalid token")
console.log("Error verifying token:", err)
} finally {
setLoading(false)
}
}
fetchDecodedCode()
}, [token])

const handleCancel = async () => {
setLoading(true)
try {
await cancelPayment(token)
window.location.href = process.env.NEXT_PUBLIC_REDIRECT_URL || ''
} catch (err) {
setError("Error during cancellation")
console.error("Cancel payment failed:", err)
} finally {
setLoading(false)
}
}

const handlePayment = async () => {
setLoading(true)
try {
await validatePayment(token)
window.location.href = process.env.NEXT_PUBLIC_REDIRECT_URL || ''
} catch (err) {
setError("Error during payment validation")
console.error("Payment validation failed:", err)
} finally {
setLoading(false)
}
}

return (
<div className='h-screen flex flex-col items-center justify-center'>
<div className='pb-10'>
<h1 className='text-xl font-mono underline'>HDFC Netbanking</h1>
</div>

<div className="w-full grid grid-cols-4">
<div className='col-start-2'>
<h2 className='font-mono'>Hey!</h2>
<h4 className='text-lg font-mono text-gray-700'>You are about to pay: <span className=''>{amount}</span></h4>
<p className='text-sm text-gray-400 font-mono'>to Payment-Pe</p>
</div>
<Card title={"Payment Portal"}>
<TextInput label={"Account number"} placeholder={"Account number"} value={accountNumber} onChange={(val) => {
setAccountNumber(Number(val))
}} />
<TextInput label={"Netbanking Password"} placeholder={"Password"} value={password} onChange={(val) => {
setPassword(val)
}} />
<div className='mt-5'>
{loading ? (
<p>Loading...</p> // Show loading text or spinner
) : (
<>
<Button onClick={handleCancel} disabled={loading}>Cancel</Button>
<Button onClick={handlePayment} disabled={loading}>Pay Now</Button>
</>
)}
{error && <p className="text-red-500">{error}</p>} {/* Error message */}
</div>
</Card>
</div>
<div className='w-full text-center mt-12'>
<p className='text-xs text-gray-500'>*This is not real hdfc netbanking portal</p>
<p className='text-xs text-gray-500'>*For testing you can use pre-filled values and just click on pay now button</p>
</div>
</div>
)
}

export default function PayMoney() {
return (
<Suspense fallback={<div>Loading...</div>}>
<PayMoneyContent />
</Suspense>
)
}
Loading

0 comments on commit bd1e8e6

Please sign in to comment.