MCPcopy
hub / github.com/adrianhajdin/banking

github.com/adrianhajdin/banking @main sqlite

repository ↗ · DeepWiki ↗
128 symbols 352 edges 57 files 0 documented · 0%
README
<a href="https://youtu.be/PuOVqP_cjkE?feature=shared" target="_blank">
  <img src="https://github.com/adrianhajdin/banking/assets/151519281/3c03519c-7ebd-4539-b598-49e63d1770b4" alt="Project Banner">
</a>







<img src="https://img.shields.io/badge/-Next_JS-black?style=for-the-badge&logoColor=white&logo=nextdotjs&color=000000" alt="nextdotjs" />
<img src="https://img.shields.io/badge/-TypeScript-black?style=for-the-badge&logoColor=white&logo=typescript&color=3178C6" alt="typescript" />
<img src="https://img.shields.io/badge/-Tailwind_CSS-black?style=for-the-badge&logoColor=white&logo=tailwindcss&color=06B6D4" alt="tailwindcss" />
<img src="https://img.shields.io/badge/-Appwrite-black?style=for-the-badge&logoColor=white&logo=appwrite&color=FD366E" alt="appwrite" />

A Fintech Bank Application

 Build this project step by step with our detailed tutorial on <a href="https://www.youtube.com/@javascriptmastery/videos" target="_blank"><b>JavaScript Mastery</b></a> YouTube. Join the JSM family!

📋 Table of Contents

  1. 🤖 Introduction
  2. ⚙️ Tech Stack
  3. 🔋 Features
  4. 🤸 Quick Start
  5. 🕸️ Code Snippets to Copy
  6. 🔗 Assets
  7. 🚀 More

🚨 Tutorial

This repository contains the code corresponding to an in-depth tutorial available on our YouTube channel, JavaScript Mastery.

If you prefer visual learning, this is the perfect resource for you. Follow our tutorial to learn how to build projects like these step-by-step in a beginner-friendly manner!

🤖 Introduction

Built with Next.js, Horizon is a financial SaaS platform that connects to multiple bank accounts, displays transactions in real-time, allows users to transfer money to other platform users, and manages their finances altogether.

If you're getting started and need assistance or face any bugs, join our active Discord community with over 34k+ members. It's a place where people help each other out.

⚙️ Tech Stack

  • Next.js
  • TypeScript
  • Appwrite
  • Plaid
  • Dwolla
  • React Hook Form
  • Zod
  • TailwindCSS
  • Chart.js
  • ShadCN

🔋 Features

👉 Authentication: An ultra-secure SSR authentication with proper validations and authorization

👉 Connect Banks: Integrates with Plaid for multiple bank account linking

👉 Home Page: Shows general overview of user account with total balance from all connected banks, recent transactions, money spent on different categories, etc

👉 My Banks: Check the complete list of all connected banks with respective balances, account details

👉 Transaction History: Includes pagination and filtering options for viewing transaction history of different banks

👉 Real-time Updates: Reflects changes across all relevant pages upon connecting new bank accounts.

👉 Funds Transfer: Allows users to transfer funds using Dwolla to other accounts with required fields and recipient bank ID.

👉 Responsiveness: Ensures the application adapts seamlessly to various screen sizes and devices, providing a consistent user experience across desktop, tablet, and mobile platforms.

and many more, including code architecture and reusability.

🤸 Quick Start

Follow these steps to set up the project locally on your machine.

Prerequisites

Make sure you have the following installed on your machine:

Cloning the Repository

git clone https://github.com/adrianhajdin/banking.git
cd banking

Installation

Install the project dependencies using npm:

npm install

Set Up Environment Variables

Create a new file named .env in the root of your project and add the following content:

#NEXT
NEXT_PUBLIC_SITE_URL=

#APPWRITE
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
NEXT_PUBLIC_APPWRITE_PROJECT=
APPWRITE_DATABASE_ID=
APPWRITE_USER_COLLECTION_ID=
APPWRITE_BANK_COLLECTION_ID=
APPWRITE_TRANSACTION_COLLECTION_ID=
APPWRITE_SECRET=

#PLAID
PLAID_CLIENT_ID=
PLAID_SECRET=
PLAID_ENV=
PLAID_PRODUCTS=
PLAID_COUNTRY_CODES=

#DWOLLA
DWOLLA_KEY=
DWOLLA_SECRET=
DWOLLA_BASE_URL=https://api-sandbox.dwolla.com
DWOLLA_ENV=sandbox

Replace the placeholder values with your actual respective account credentials. You can obtain these credentials by signing up on the Appwrite, Plaid and Dwolla

Running the Project

npm run dev

Open http://localhost:3000 in your browser to view the project.

🕸️ Snippets

.env.example

#NEXT
NEXT_PUBLIC_SITE_URL=

#APPWRITE
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
NEXT_PUBLIC_APPWRITE_PROJECT=
APPWRITE_DATABASE_ID=
APPWRITE_USER_COLLECTION_ID=
APPWRITE_BANK_COLLECTION_ID=
APPWRITE_TRANSACTION_COLLECTION_ID=
APPWRITE_SECRET=

#PLAID
PLAID_CLIENT_ID=
PLAID_SECRET=
PLAID_ENV=sandbox
PLAID_PRODUCTS=auth,transactions,identity
PLAID_COUNTRY_CODES=US,CA

#DWOLLA
DWOLLA_KEY=
DWOLLA_SECRET=
DWOLLA_BASE_URL=https://api-sandbox.dwolla.com
DWOLLA_ENV=sandbox

exchangePublicToken

// This function exchanges a public token for an access token and item ID
export const exchangePublicToken = async ({
  publicToken,
  user,
}: exchangePublicTokenProps) => {
  try {
    // Exchange public token for access token and item ID
    const response = await plaidClient.itemPublicTokenExchange({
      public_token: publicToken,
    });

    const accessToken = response.data.access_token;
    const itemId = response.data.item_id;

    // Get account information from Plaid using the access token
    const accountsResponse = await plaidClient.accountsGet({
      access_token: accessToken,
    });

    const accountData = accountsResponse.data.accounts[0];

    // Create a processor token for Dwolla using the access token and account ID
    const request: ProcessorTokenCreateRequest = {
      access_token: accessToken,
      account_id: accountData.account_id,
      processor: "dwolla" as ProcessorTokenCreateRequestProcessorEnum,
    };

    const processorTokenResponse =
      await plaidClient.processorTokenCreate(request);
    const processorToken = processorTokenResponse.data.processor_token;

    // Create a funding source URL for the account using the Dwolla customer ID, processor token, and bank name
    const fundingSourceUrl = await addFundingSource({
      dwollaCustomerId: user.dwollaCustomerId,
      processorToken,
      bankName: accountData.name,
    });

    // If the funding source URL is not created, throw an error
    if (!fundingSourceUrl) throw Error;

    // Create a bank account using the user ID, item ID, account ID, access token, funding source URL, and sharable ID
    await createBankAccount({
      userId: user.$id,
      bankId: itemId,
      accountId: accountData.account_id,
      accessToken,
      fundingSourceUrl,
      sharableId: encryptId(accountData.account_id),
    });

    // Revalidate the path to reflect the changes
    revalidatePath("/");

    // Return a success message
    return parseStringify({
      publicTokenExchange: "complete",
    });
  } catch (error) {
    // Log any errors that occur during the process
    console.error("An error occurred while creating exchanging token:", error);
  }
};

user.actions.ts

```typescript "use server";

import { revalidatePath } from "next/cache"; import { cookies } from "next/headers"; import { ID, Query } from "node-appwrite"; import { CountryCode, ProcessorTokenCreateRequest, ProcessorTokenCreateRequestProcessorEnum, Products, } from "plaid";

import { plaidClient } from "@/lib/plaid.config"; import { parseStringify, extractCustomerIdFromUrl, encryptId, } from "@/lib/utils";

import { createAdminClient, createSessionClient } from "../appwrite.config";

import { addFundingSource, createDwollaCustomer } from "./dwolla.actions";

const { APPWRITE_DATABASE_ID: DATABASE_ID, APPWRITE_USER_COLLECTION_ID: USER_COLLECTION_ID, APPWRITE_BANK_COLLECTION_ID: BANK_COLLECTION_ID, } = process.env;

export const signUp = async ({ password, ...userData }: SignUpParams) => { let newUserAccount;

try { // create appwrite user const { database, account } = await createAdminClient(); newUserAccount = await account.create( ID.unique(), userData.email, password, ${userData.firstName} ${userData.lastName} );

if (!newUserAccount) throw new Error("Error creating user");

// create dwolla customer
const dwollaCustomerUrl = await createDwollaCustomer({
  ...userData,
  type: "personal",
});

if (!dwollaCustomerUrl) throw new Error("Error creating dwolla customer");
const dwollaCustomerId = extractCustomerIdFromUrl(dwollaCustomerUrl);

const newUser = await database.createDocument(
  DATABASE_ID!,
  USER_COLLECTION_ID!,
  ID.unique(),
  {
    ...userData,
    userId: newUserAccount.$id,
    dwollaCustomerUrl,
    dwollaCustomerId,
  }
);

const session = await account.createEmailPasswordSession(
  userData.email,
  password
);

cookies().set("appwrite-session", session.secret, {
  path: "/",
  httpOnly: true,
  sameSite: "strict",
  secure: true,
});

return parseStringify(newUser);

} catch (error) { console.error("Error", error);

// check if account has been created, if so, delete it
if (newUserAccount?.$id) {
  const { user } = await createAdminClient();
  await user.delete(newUserAccount?.$id);
}

return null;

} };

export const signIn = async ({ email, password }: signInProps) => { try { const { account } = await createAdminClient(); const session = await account.createEmailPasswordSession(email, password);

cookies().set("appwrite-session", session.secret, {
  path: "/",
  httpOnly: true,
  sameSite: "strict",
  secure: true,
});

const user = await getUserInfo({ userId: session.userId });

return parseStringify(user);

} catch (error) { console.error("Error", error); return null; } };

export const getLoggedInUser = async () => { try { const { account } = await createSessionClient(); const result = await account.get();

const user = await getUserInfo({ userId: result.$id });

return parseStringify(user);

} catch (error) { console.error("Error", error); return null; } };

// CREATE PLAID LINK TOKEN export const createLinkToken = async (user: User) => { try { const tokeParams = { user: { client_user_id: user.$id, }, client_name: user.firstName + user.lastName, products: ["auth"] as Products[], language: "en", country_codes: ["US"] as CountryCode[], };

const response = await plaidClient.linkTokenCreate(tokeParams);

return parseStringify({ linkToken: response.data.link_token });

} catch (error) { console.error( "An error occurred while creating a new Horizon user:", error ); } };

// EXCHANGE PLAID PUBLIC TOKEN // This function exchanges a public token for an access token and item ID export const exchangePublicToken = async ({ publicToken, user, }: exchangePublicTokenProps) => { try { // Exchange public token for access token and item ID const response = await plaidClient.itemPublicTokenExchange({ public_token: publicToken, });

const accessToken = response.data.access_token;
const itemId = response.data.item_id;

// Get account information from Plaid using the access token
const accountsResponse = await plaidClient.accountsGet({
  access_token: accessToken,
});

const accountData = accountsResponse.data.accounts[0];

// Create a processor token for Dwolla using the access token and account ID
const request: ProcessorTokenCreateRequest = {
  access_token: accessToken,
  account_id: accountData.account_id,
  processor: "dwolla" as ProcessorTokenCreateRequestProcessorEnum,
};

const processorTokenResponse =
  await plaidClient.processorTokenCreate(request);
const processorToken = processorTokenResponse.data.processor_token;

// Create a funding source URL for the account using the Dwolla customer ID, processor token, and bank name
const fundingSourceUrl = await addFundingSource({
  dwollaCustomerId: user.dwollaCustomerId,
  processorToken,
  bankName: accountData.name,
});

// If the funding source URL is not created, throw an error
if (!fundingSourceUrl) throw Error;

// Create a bank account using the user ID, item ID, account ID, access token, funding source URL, and sharable ID
await createBankAccount({
  userId: user.$id,
  bankId: itemId,
  accountId: accountData.account_id,
  accessToken,
  fundingSourceUrl,
  sharableId: encryptId(accountData.account_id),
});

// Revalidate the path to reflect the cha

Extension points exported contracts — how you extend this code

CreditCardProps (Interface)
(no doc)
types/index.d.ts
CustomInput (Interface)
(no doc)
components/CustomInput.tsx
TextareaProps (Interface)
(no doc)
components/ui/textarea.tsx
UrlQueryParams (Interface)
(no doc)
lib/utils.ts
BankInfoProps (Interface)
(no doc)
types/index.d.ts
InputProps (Interface)
(no doc)
components/ui/input.tsx
HeaderBoxProps (Interface)
(no doc)
types/index.d.ts
SheetContentProps (Interface)
(no doc)
components/ui/sheet.tsx

Core symbols most depended-on inside this repo

cn
called by 53
lib/utils.ts
parseStringify
called by 16
lib/utils.ts
createAdminClient
called by 9
lib/appwrite.ts
formatAmount
called by 5
lib/utils.ts
getLoggedInUser
called by 5
lib/actions/user.actions.ts
useFormField
called by 4
components/ui/form.tsx
formUrlQuery
called by 4
lib/utils.ts
getAccounts
called by 4
lib/actions/bank.actions.ts

Shape

Function 87
Interface 41

Languages

TypeScript100%

Modules by API surface

types/index.d.ts35 symbols
lib/utils.ts14 symbols
lib/actions/user.actions.ts11 symbols
lib/actions/dwolla.actions.ts6 symbols
lib/appwrite.ts5 symbols
lib/actions/bank.actions.ts4 symbols
components/ui/sheet.tsx3 symbols
lib/actions/transaction.actions.ts2 symbols
components/ui/form.tsx2 symbols
components/TransactionsTable.tsx2 symbols
components/PlaidLink.tsx2 symbols
components/PaymentTransferForm.tsx2 symbols

Dependencies from manifests, versioned

@hookform/resolvers3.3.4 · 1×
@radix-ui/react-dialog1.0.5 · 1×
@radix-ui/react-label2.0.2 · 1×
@radix-ui/react-progress1.0.3 · 1×
@radix-ui/react-slot1.0.2 · 1×
@sentry/nextjs7.112.2 · 1×
@types/node20 · 1×
@types/react18 · 1×
@types/react-dom18 · 1×
chart.js4.4.2 · 1×

For agents

$ claude mcp add banking \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact