mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { PlaidItem } from '../models/PlaidItem';
|
||||
import { PlaidApi } from 'plaid';
|
||||
import { PLAID_CLIENT } from '../../Plaid/Plaid.module';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { SystemPlaidItem } from '../models/SystemPlaidItem';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import {
|
||||
IPlaidItemCreatedEventPayload,
|
||||
PlaidItemDTO,
|
||||
} from '../types/BankingPlaid.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class PlaidItemService {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
|
||||
@Inject(SystemPlaidItem.name)
|
||||
private readonly systemPlaidItemModel: TenantModelProxy<
|
||||
typeof SystemPlaidItem
|
||||
>,
|
||||
|
||||
@Inject(PlaidItem.name)
|
||||
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||
|
||||
@Inject(PLAID_CLIENT)
|
||||
private readonly plaidClient: PlaidApi,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Exchanges the public token to get access token and item id and then creates
|
||||
* a new Plaid item.
|
||||
* @param {PlaidItemDTO} itemDTO - Plaid item data transfer object.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async item(itemDTO: PlaidItemDTO): Promise<void> {
|
||||
const { publicToken, institutionId } = itemDTO;
|
||||
|
||||
const tenant = await this.tenancyContext.getTenant();
|
||||
const tenantId = tenant.id;
|
||||
|
||||
// Exchange the public token for a private access token and store with the item.
|
||||
const response = await this.plaidClient.itemPublicTokenExchange({
|
||||
public_token: publicToken,
|
||||
});
|
||||
const plaidAccessToken = response.data.access_token;
|
||||
const plaidItemId = response.data.item_id;
|
||||
|
||||
// Store the Plaid item metadata on tenant scope.
|
||||
const plaidItem = await this.plaidItemModel().query().insertAndFetch({
|
||||
tenantId,
|
||||
plaidAccessToken,
|
||||
plaidItemId,
|
||||
plaidInstitutionId: institutionId,
|
||||
});
|
||||
// Stores the Plaid item id on system scope.
|
||||
await this.systemPlaidItemModel().query().insert({ tenantId, plaidItemId });
|
||||
|
||||
// Triggers `onPlaidItemCreated` event.
|
||||
await this.eventEmitter.emitAsync(events.plaid.onItemCreated, {
|
||||
plaidAccessToken,
|
||||
plaidItemId,
|
||||
plaidInstitutionId: institutionId,
|
||||
} as IPlaidItemCreatedEventPayload);
|
||||
}
|
||||
}
|
||||
240
packages/server/src/modules/BankingPlaid/command/PlaidSyncDB.ts
Normal file
240
packages/server/src/modules/BankingPlaid/command/PlaidSyncDB.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import * as R from 'ramda';
|
||||
import bluebird from 'bluebird';
|
||||
import { entries, groupBy } from 'lodash';
|
||||
import {
|
||||
AccountBase as PlaidAccountBase,
|
||||
Item as PlaidItem,
|
||||
Institution as PlaidInstitution,
|
||||
Transaction as PlaidTransaction,
|
||||
} from 'plaid';
|
||||
import {
|
||||
transformPlaidAccountToCreateAccount,
|
||||
transformPlaidTrxsToCashflowCreate,
|
||||
} from '../utils';
|
||||
import { Knex } from 'knex';
|
||||
import uniqid from 'uniqid';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { RemovePendingUncategorizedTransaction } from '../../BankingTransactions/commands/RemovePendingUncategorizedTransaction.service';
|
||||
import { CreateAccountService } from '../../Accounts/CreateAccount.service';
|
||||
import { Account } from '../../Accounts/models/Account.model';
|
||||
import { events } from '@/common/events/events';
|
||||
import { PlaidItem as PlaidItemModel } from '../models/PlaidItem';
|
||||
import { IAccountCreateDTO } from '@/interfaces/Account';
|
||||
import { IPlaidTransactionsSyncedEventPayload } from '../types/BankingPlaid.types';
|
||||
import { UncategorizedBankTransaction } from '../../BankingTransactions/models/UncategorizedBankTransaction';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { CreateUncategorizedTransactionService } from '@/modules/BankingCategorize/commands/CreateUncategorizedTransaction.service';
|
||||
import { TenantModelProxy } from '../../System/models/TenantBaseModel';
|
||||
|
||||
const CONCURRENCY_ASYNC = 10;
|
||||
|
||||
@Injectable()
|
||||
export class PlaidSyncDb {
|
||||
constructor(
|
||||
private readonly createAccountService: CreateAccountService,
|
||||
private readonly createUncategorizedTransaction: CreateUncategorizedTransactionService,
|
||||
private readonly removePendingTransaction: RemovePendingUncategorizedTransaction,
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||
|
||||
@Inject(PlaidItemModel.name)
|
||||
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItemModel>,
|
||||
|
||||
@Inject(UncategorizedBankTransaction.name)
|
||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||
typeof UncategorizedBankTransaction
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Syncs the Plaid bank account.
|
||||
* @param {IAccountCreateDTO} createBankAccountDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async syncBankAccount(
|
||||
createBankAccountDTO: IAccountCreateDTO,
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const plaidAccount = await this.accountModel()
|
||||
.query(trx)
|
||||
.findOne('plaidAccountId', createBankAccountDTO.plaidAccountId);
|
||||
// Can't continue if the Plaid account is already created.
|
||||
if (plaidAccount) {
|
||||
return;
|
||||
}
|
||||
await this.createAccountService.createAccount(createBankAccountDTO, trx, {
|
||||
ignoreUniqueName: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the plaid accounts to the system accounts.
|
||||
* @param {PlaidAccount[]} plaidAccounts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async syncBankAccounts(
|
||||
plaidAccounts: PlaidAccountBase[],
|
||||
institution: PlaidInstitution,
|
||||
item: PlaidItem,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
const transformToPlaidAccounts = R.curry(
|
||||
transformPlaidAccountToCreateAccount,
|
||||
)(item, institution);
|
||||
const accountCreateDTOs = R.map(transformToPlaidAccounts)(plaidAccounts);
|
||||
|
||||
await bluebird.map(
|
||||
accountCreateDTOs,
|
||||
(createAccountDTO: any) => this.syncBankAccount(createAccountDTO, trx),
|
||||
{ concurrency: CONCURRENCY_ASYNC },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synsc the Plaid transactions to the system GL entries.
|
||||
* @param {number} plaidAccountId - Plaid account ID.
|
||||
* @param {PlaidTransaction[]} plaidTranasctions - Plaid transactions
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async syncAccountTranactions(
|
||||
plaidAccountId: number,
|
||||
plaidTranasctions: PlaidTransaction[],
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
const batch = uniqid();
|
||||
const cashflowAccount = await this.accountModel()
|
||||
.query(trx)
|
||||
.findOne({ plaidAccountId })
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transformes the Plaid transactions to cashflow create DTOs.
|
||||
const transformTransaction = R.curry(transformPlaidTrxsToCashflowCreate)(
|
||||
cashflowAccount.id,
|
||||
);
|
||||
const uncategorizedTransDTOs =
|
||||
R.map(transformTransaction)(plaidTranasctions);
|
||||
|
||||
// Creating account transaction queue.
|
||||
await bluebird.map(
|
||||
uncategorizedTransDTOs,
|
||||
(uncategoriedDTO) =>
|
||||
this.createUncategorizedTransaction.create(
|
||||
{ ...uncategoriedDTO, batch },
|
||||
trx,
|
||||
),
|
||||
{ concurrency: 1 },
|
||||
);
|
||||
// Triggers `onPlaidTransactionsSynced` event.
|
||||
await this.eventPublisher.emitAsync(events.plaid.onTransactionsSynced, {
|
||||
plaidAccountId,
|
||||
batch,
|
||||
} as IPlaidTransactionsSyncedEventPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the accounts transactions in paraller under controlled concurrency.
|
||||
* @param {PlaidTransaction[]} plaidTransactions
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async syncAccountsTransactions(
|
||||
plaidAccountsTransactions: PlaidTransaction[],
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
const groupedTrnsxByAccountId = entries(
|
||||
groupBy(plaidAccountsTransactions, 'account_id'),
|
||||
);
|
||||
await bluebird.map(
|
||||
groupedTrnsxByAccountId,
|
||||
([plaidAccountId, plaidTransactions]: [number, PlaidTransaction[]]) => {
|
||||
return this.syncAccountTranactions(
|
||||
plaidAccountId,
|
||||
plaidTransactions,
|
||||
trx,
|
||||
);
|
||||
},
|
||||
{ concurrency: CONCURRENCY_ASYNC },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the removed Plaid transactions ids from the cashflow system transactions.
|
||||
* @param {string[]} plaidTransactionsIds - Plaid Transactions IDs.
|
||||
*/
|
||||
public async syncRemoveTransactions(
|
||||
plaidTransactionsIds: string[],
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const uncategorizedTransactions =
|
||||
await this.uncategorizedBankTransactionModel()
|
||||
.query(trx)
|
||||
.whereIn('plaidTransactionId', plaidTransactionsIds);
|
||||
const uncategorizedTransactionsIds = uncategorizedTransactions.map(
|
||||
(trans) => trans.id,
|
||||
);
|
||||
await bluebird.map(
|
||||
uncategorizedTransactionsIds,
|
||||
(uncategorizedTransactionId: number) =>
|
||||
this.removePendingTransaction.removePendingTransaction(
|
||||
uncategorizedTransactionId,
|
||||
trx,
|
||||
),
|
||||
{ concurrency: CONCURRENCY_ASYNC },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the Plaid item last transaction cursor.
|
||||
* @param {string} itemId - Plaid item ID.
|
||||
* @param {string} lastCursor - Last transaction cursor.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async syncTransactionsCursor(
|
||||
plaidItemId: string,
|
||||
lastCursor: string,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
await this.plaidItemModel()
|
||||
.query(trx)
|
||||
.findOne({ plaidItemId })
|
||||
.patch({ lastCursor });
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last feeds updated at of the given Plaid accounts ids.
|
||||
* @param {string[]} plaidAccountIds - Plaid accounts ids.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async updateLastFeedsUpdatedAt(
|
||||
plaidAccountIds: string[],
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
await this.accountModel()
|
||||
.query(trx)
|
||||
.whereIn('plaid_account_id', plaidAccountIds)
|
||||
.patch({
|
||||
lastFeedsUpdatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the accounts feed active status of the given Plaid accounts ids.
|
||||
* @param {number[]} plaidAccountIds - Plaid accounts ids.
|
||||
* @param {boolean} isFeedsActive - Feeds active status.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async updateAccountsFeedsActive(
|
||||
plaidAccountIds: string[],
|
||||
isFeedsActive: boolean = true,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
await this.accountModel()
|
||||
.query(trx)
|
||||
.whereIn('plaid_account_id', plaidAccountIds)
|
||||
.patch({
|
||||
isFeedsActive,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import { Knex } from 'knex';
|
||||
import { PlaidSyncDb } from './PlaidSyncDB';
|
||||
import { PlaidFetchedTransactionsUpdates } from '../types/BankingPlaid.types';
|
||||
import { PlaidItem } from '../models/PlaidItem';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import {
|
||||
CountryCode,
|
||||
PlaidApi,
|
||||
Transaction as PlaidTransaction,
|
||||
RemovedTransaction,
|
||||
} from 'plaid';
|
||||
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class PlaidUpdateTransactions {
|
||||
constructor(
|
||||
private readonly plaidSync: PlaidSyncDb,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(PlaidItem.name)
|
||||
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||
|
||||
@Inject(PLAID_CLIENT)
|
||||
private readonly plaidClient: PlaidApi,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handles sync the Plaid item to Bigcaptial under UOW.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} plaidItemId - Plaid item id.
|
||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||
*/
|
||||
public async updateTransactions(plaidItemId: string) {
|
||||
return this.uow.withTransaction((trx: Knex.Transaction) => {
|
||||
return this.updateTransactionsWork(plaidItemId, trx);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the fetching and storing the following:
|
||||
* - New, modified, or removed transactions.
|
||||
* - New bank accounts.
|
||||
* - Last accounts feeds updated at.
|
||||
* - Turn on the accounts feed flag.
|
||||
* @param {number} tenantId - Tenant ID.
|
||||
* @param {string} plaidItemId - The Plaid ID for the item.
|
||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||
*/
|
||||
public async updateTransactionsWork(
|
||||
plaidItemId: string,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<{
|
||||
addedCount: number;
|
||||
modifiedCount: number;
|
||||
removedCount: number;
|
||||
}> {
|
||||
// Fetch new transactions from plaid api.
|
||||
const { added, modified, removed, cursor, accessToken } =
|
||||
await this.fetchTransactionUpdates(plaidItemId);
|
||||
|
||||
const request = { access_token: accessToken };
|
||||
const {
|
||||
data: { accounts, item },
|
||||
} = await this.plaidClient.accountsGet(request);
|
||||
|
||||
const plaidAccountsIds = accounts.map((a) => a.account_id);
|
||||
const {
|
||||
data: { institution },
|
||||
} = await this.plaidClient.institutionsGetById({
|
||||
institution_id: item.institution_id,
|
||||
country_codes: [CountryCode.Us, CountryCode.Gb],
|
||||
});
|
||||
// Sync bank accounts.
|
||||
await this.plaidSync.syncBankAccounts(accounts, institution, item, trx);
|
||||
// Sync removed transactions.
|
||||
await this.plaidSync.syncRemoveTransactions(
|
||||
removed?.map((r) => r.transaction_id),
|
||||
trx,
|
||||
);
|
||||
// Sync bank account transactions.
|
||||
await this.plaidSync.syncAccountsTransactions(added.concat(modified), trx);
|
||||
// Sync transactions cursor.
|
||||
await this.plaidSync.syncTransactionsCursor(plaidItemId, cursor, trx);
|
||||
// Update the last feeds updated at of the updated accounts.
|
||||
await this.plaidSync.updateLastFeedsUpdatedAt(plaidAccountsIds, trx);
|
||||
// Turn on the accounts feeds flag.
|
||||
await this.plaidSync.updateAccountsFeedsActive(plaidAccountsIds, true, trx);
|
||||
|
||||
return {
|
||||
addedCount: added.length,
|
||||
modifiedCount: modified.length,
|
||||
removedCount: removed.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches transactions from the `Plaid API` for a given item.
|
||||
* @param {number} tenantId - Tenant ID.
|
||||
* @param {string} plaidItemId - The Plaid ID for the item.
|
||||
* @returns {Promise<PlaidFetchedTransactionsUpdates>}
|
||||
*/
|
||||
private async fetchTransactionUpdates(
|
||||
plaidItemId: string,
|
||||
): Promise<PlaidFetchedTransactionsUpdates> {
|
||||
// the transactions endpoint is paginated, so we may need to hit it multiple times to
|
||||
// retrieve all available transactions.
|
||||
const plaidItem = await this.plaidItemModel()
|
||||
.query()
|
||||
.findOne('plaidItemId', plaidItemId);
|
||||
|
||||
if (!plaidItem) {
|
||||
throw new Error('The given Plaid item id is not found.');
|
||||
}
|
||||
const { plaidAccessToken, lastCursor } = plaidItem;
|
||||
let cursor = lastCursor;
|
||||
|
||||
// New transaction updates since "cursor"
|
||||
let added: PlaidTransaction[] = [];
|
||||
let modified: PlaidTransaction[] = [];
|
||||
// Removed transaction ids
|
||||
let removed: RemovedTransaction[] = [];
|
||||
let hasMore = true;
|
||||
|
||||
const batchSize = 100;
|
||||
try {
|
||||
// Iterate through each page of new transaction updates for item
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (hasMore) {
|
||||
const request = {
|
||||
access_token: plaidAccessToken,
|
||||
cursor: cursor,
|
||||
count: batchSize,
|
||||
};
|
||||
const response = await this.plaidClient.transactionsSync(request);
|
||||
const data = response.data;
|
||||
// Add this page of results
|
||||
added = added.concat(data.added);
|
||||
modified = modified.concat(data.modified);
|
||||
removed = removed.concat(data.removed);
|
||||
hasMore = data.has_more;
|
||||
// Update cursor to the next cursor
|
||||
cursor = data.next_cursor;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error fetching transactions: ${err.message}`);
|
||||
cursor = lastCursor;
|
||||
}
|
||||
return { added, modified, removed, cursor, accessToken: plaidAccessToken };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import { PlaidItem } from '../models/PlaidItem';
|
||||
import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class PlaidWebooks {
|
||||
constructor(
|
||||
private readonly updateTransactionsService: PlaidUpdateTransactions,
|
||||
|
||||
@Inject(PlaidItem.name)
|
||||
private readonly plaidItemModel: typeof PlaidItem,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Listens to Plaid webhooks
|
||||
* @param {string} webhookType - Webhook type.
|
||||
* @param {string} plaidItemId - Plaid item Id.
|
||||
* @param {string} webhookCode - webhook code.
|
||||
*/
|
||||
public async webhooks(
|
||||
plaidItemId: string,
|
||||
webhookType: string,
|
||||
webhookCode: string,
|
||||
): Promise<void> {
|
||||
const _webhookType = webhookType.toLowerCase();
|
||||
|
||||
// There are five types of webhooks: AUTH, TRANSACTIONS, ITEM, INCOME, and ASSETS.
|
||||
// @TODO implement handling for remaining webhook types.
|
||||
const webhookHandlerMap = {
|
||||
transactions: this.handleTransactionsWebooks.bind(this),
|
||||
item: this.itemsHandler.bind(this),
|
||||
};
|
||||
const webhookHandler =
|
||||
webhookHandlerMap[_webhookType] || this.unhandledWebhook;
|
||||
|
||||
await webhookHandler(plaidItemId, webhookCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all unhandled/not yet implemented webhook events.
|
||||
* @param {string} webhookType - Webhook type.
|
||||
* @param {string} webhookCode - Webhook code.
|
||||
* @param {string} plaidItemId - Plaid item id.
|
||||
*/
|
||||
private async unhandledWebhook(
|
||||
webhookType: string,
|
||||
webhookCode: string,
|
||||
plaidItemId: string,
|
||||
): Promise<void> {
|
||||
console.log(
|
||||
`UNHANDLED ${webhookType} WEBHOOK: ${webhookCode}: Plaid item id ${plaidItemId}: unhandled webhook type received.`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs to console and emits to socket
|
||||
* @param {string} additionalInfo - Additional info.
|
||||
* @param {string} webhookCode - Webhook code.
|
||||
* @param {string} plaidItemId - Plaid item id.
|
||||
*/
|
||||
private serverLogAndEmitSocket(
|
||||
additionalInfo: string,
|
||||
webhookCode: string,
|
||||
plaidItemId: string,
|
||||
): void {
|
||||
console.log(
|
||||
`PLAID WEBHOOK: TRANSACTIONS: ${webhookCode}: Plaid_item_id ${plaidItemId}: ${additionalInfo}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all transaction webhook events. The transaction webhook notifies
|
||||
* you that a single item has new transactions available.
|
||||
* @param {string} plaidItemId - Plaid item id.
|
||||
* @param {string} webhookCode - Webhook code.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async handleTransactionsWebooks(
|
||||
tenantId: number,
|
||||
plaidItemId: string,
|
||||
webhookCode: string,
|
||||
): Promise<void> {
|
||||
const plaidItem = await this.plaidItemModel
|
||||
.query()
|
||||
.findOne({ plaidItemId })
|
||||
.throwIfNotFound();
|
||||
|
||||
switch (webhookCode) {
|
||||
case 'SYNC_UPDATES_AVAILABLE': {
|
||||
if (plaidItem.isPaused) {
|
||||
this.serverLogAndEmitSocket(
|
||||
'Plaid item syncing is paused.',
|
||||
webhookCode,
|
||||
plaidItemId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Fired when new transactions data becomes available.
|
||||
const { addedCount, modifiedCount, removedCount } =
|
||||
await this.updateTransactionsService.updateTransactions(plaidItemId);
|
||||
|
||||
this.serverLogAndEmitSocket(
|
||||
`Transactions: ${addedCount} added, ${modifiedCount} modified, ${removedCount} removed`,
|
||||
webhookCode,
|
||||
plaidItemId,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'DEFAULT_UPDATE':
|
||||
case 'INITIAL_UPDATE':
|
||||
case 'HISTORICAL_UPDATE':
|
||||
/* ignore - not needed if using sync endpoint + webhook */
|
||||
break;
|
||||
default:
|
||||
this.serverLogAndEmitSocket(
|
||||
`unhandled webhook type received.`,
|
||||
webhookCode,
|
||||
plaidItemId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all Item webhook events.
|
||||
* @param {number} tenantId - Tenant ID
|
||||
* @param {string} webhookCode - The webhook code
|
||||
* @param {string} plaidItemId - The Plaid ID for the item
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async itemsHandler(
|
||||
plaidItemId: string,
|
||||
webhookCode: string,
|
||||
): Promise<void> {
|
||||
switch (webhookCode) {
|
||||
case 'WEBHOOK_UPDATE_ACKNOWLEDGED':
|
||||
this.serverLogAndEmitSocket('is updated', webhookCode, plaidItemId);
|
||||
break;
|
||||
case 'ERROR': {
|
||||
break;
|
||||
}
|
||||
case 'PENDING_EXPIRATION': {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this.serverLogAndEmitSocket(
|
||||
'unhandled webhook type received.',
|
||||
webhookCode,
|
||||
plaidItemId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user