Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Mar 2, 2024
1 parent f3b043a commit d236647
Show file tree
Hide file tree
Showing 12 changed files with 522 additions and 8 deletions.
5 changes: 3 additions & 2 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@vercel/edge-config": "^1.1.0",
"@vercel/speed-insights": "^1.0.10",
"@zip.js/zip.js": "2.7.36",
"ai": "^3.0.2",
"base64-arraybuffer": "^1.0.2",
"change-case": "^5.4.3",
"dub": "^0.8.0",
Expand All @@ -53,7 +54,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.50.1",
"react-hook-form": "^7.51.0",
"react-hotkeys-hook": "^4.5.0",
"react-intersection-observer": "^9.8.1",
"react-pdf": "^7.7.1",
Expand All @@ -65,7 +66,7 @@
"use-long-press": "^3.2.0",
"usehooks-ts": "2.15.1",
"zod": "^3.22.4",
"zustand": "^4.5.1"
"zustand": "^4.5.2"
},
"devDependencies": {
"@midday/tsconfig": "workspace:*",
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 0 additions & 1 deletion packages/email/emails/inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
Body,
Container,
Font,
Head,
Heading,
Html,
Img,
Expand Down
5 changes: 0 additions & 5 deletions packages/gocardless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ const ONE_HOUR = 3600;
const ACCESS_VALID_FOR_DAYS = 180;
const MAX_HISTORICAL_DAYS = 730;

enum balanceType {
interimBooked = "interimBooked",
interimAvailable = "interimAvailable",
}

const keys = {
accessToken: "go_cardless_access_token_v2",
refreshToken: "go_cardless_refresh_token_v2",
Expand Down
286 changes: 286 additions & 0 deletions packages/providers/src/gocardless/gocardless-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
import { client } from "@midday/kv";
import {
GoCardLessBank,
GoCardLessBuildLinkOptions,
GoCardLessGetAccountsOptions,
GoCardLessGetTransactionsParams,
GoCardLessTransaction,
} from "./types";

export class GoCardLessApi {
#baseUrl = "https://bankaccountdata.gocardless.com";

#accessValidForDays = 180;
#maxHistoricalDays = 730;

// Cache keys
#accessTokenCacheKey = "gocardless_access_token";
#refreshTokenCacheKey = "gocardless_refresh_token";
#banksCacheKey = "gocardless_banks";

#oneHour = 3600;

async #getRefreshToken(refresh: string) {
const res = await fetch(`${this.#baseUrl}/api/v2/token/refresh/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
refresh,
}),
});

const result = await res.json();

await client.set(this.#accessTokenCacheKey, result.access, {
ex: result.access_expires - this.#oneHour,
nx: true,
});

return result.access;
}

async #getAccessToken() {
const [accessToken, refreshToken] = await Promise.all([
client.get(this.#accessTokenCacheKey),
client.get(this.#refreshTokenCacheKey),
]);

if (accessToken) {
return accessToken;
}

if (refreshToken) {
return this.#getRefreshToken(refreshToken);
}

const res = await fetch(`${this.#baseUrl}/api/v2/token/new/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
secret_id: process.env.GOCARDLESS_SECRET_ID,
secret_key: process.env.GOCARDLESS_SECRET_KEY,
}),
});

const result = await res.json();

await Promise.all([
client.set(this.#accessTokenCacheKey, result.access, {
ex: result.access_expires - this.#oneHour,
nx: true,
}),
client.set(this.#refreshTokenCacheKey, result.refresh, {
ex: result.refresh_expires - this.#oneHour,
nx: true,
}),
]);

return result.access;
}

public async getBanks(countryCode: string): Promise<GoCardLessBank[]> {
try {
const banks = await client.get(this.#banksCacheKey);

if (banks) {
return banks;
}

const token = await this.#getAccessToken();

const res = await fetch(
`${
this.#baseUrl
}/api/v2/institutions/?country=${countryCode.toLowerCase()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);

const result: GoCardLessBank[] = await res.json();

client.set(this.#banksCacheKey, result, {
ex: this.#oneHour,
nx: true,
});

return result;
} catch (error) {
console.log("getBanks", error);
}
}

public async buildLink({
institutionId,
agreement,
redirect,
}: GoCardLessBuildLinkOptions) {
try {
const token = await this.#getAccessToken();

const res = await fetch(`${this.#baseUrl}/api/v2/requisitions/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
redirect,
institution_id: institutionId,
agreement,
}),
});

return res.json();
} catch (error) {
console.log("buildLink", error);
}
}

public async createEndUserAgreement(institutionId: string) {
const token = await this.#getAccessToken();

const res = await fetch(`${this.#baseUrl}/api/v2/agreements/enduser/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
institution_id: institutionId,
access_scope: ["balances", "details", "transactions"],
access_valid_for_days: this.#accessValidForDays,
max_historical_days: this.#maxHistoricalDays,
}),
});

return res.json();
}

public async getAccountDetails(id: string) {
const token = await this.#getAccessToken();

const [account, details] = await Promise.all([
fetch(`${this.#baseUrl}/api/v2/accounts/${id}/`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}),
fetch(`${this.#baseUrl}/api/v2/accounts/${id}/details/`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}),
]);

const accountData = await account.json();
const detailsData = await details.json();

return {
...accountData,
...detailsData?.account,
};
}

public async getAccounts({
accountId,
countryCode,
}: GoCardLessGetAccountsOptions) {
const [token, banks] = await Promise.all([
this.#getAccessToken,
this.getBanks(countryCode),
]);

const result = await fetch(
`${this.#baseUrl}/api/v2/requisitions/${accountId}/`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);

const data = await result.json();

return Promise.all(
data.accounts?.map(async (id: string) => {
const accountData = await this.getAccountDetails(id);

return {
...accountData,
bank: banks.find((bank) => bank.id === accountData.institution_id),
};
})
);
}

public async getTransactions(
params: GoCardLessGetTransactionsParams
): Promise<GoCardLessTransaction[]> {
const token = await this.#getAccessToken();

const url = new URL(
`${this.#baseUrl}/api/v2/accounts/${params.accountId}/transactions/`
);

if (params.date_from) {
url.searchParams.append("date_from", params.date_from);
}

if (params.date_to) {
url.searchParams.append("date_from", params.date_to);
}

const result = await fetch(url.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});

return result.json();
}

public async getRequisitions() {
const token = await this.#getAccessToken();

const result = await fetch(`${this.#baseUrl}/api/v2/requisitions/`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});

return result.json();
}

public async deleteRequisition(id: string) {
const token = await this.#getAccessToken();

const result = await fetch(`${this.#baseUrl}/api/v2/requisitions/${id}/`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});

return result.json();
}
}
22 changes: 22 additions & 0 deletions packages/providers/src/gocardless/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { GoCardLessApi } from "./gocardless-api";

export class GoCardLessProvider {
#api: GoCardLessApi;

constructor() {
this.#api = new GoCardLessApi();
}

#transformTransactions(transactions) {}

#transformAccounts() {}

public async getTransactions(params) {
try {
const response = await this.#api.getTransactions(params);
return this.#transformTransactions(response);
} catch (error) {
console.log("getTransactions", error);
}
}
}
Loading

0 comments on commit d236647

Please sign in to comment.