Basic Plaid Integration #1433

Merged
zachgoll merged 26 commits from zachgoll/plaid-integration into main 2024-11-16 02:49:37 +08:00
zachgoll commented 2024-11-07 23:18:05 +08:00 (Migrated from github.com)

This PR introduces a basic Plaid integration for our hosted app offering, which includes:

  • Bank linking
  • Automated transaction syncing
  • Automated investment trade syncing
  • Webhooks (with verification)

Below are some details of the changes and implementation.

Institution UI

Previously, we had an "Institutions" UI that allowed users to add accounts to "institutions" (which were really just "groups"). This feature was confusing (as reported by several users), and therefore, this PR removes it entirely. Now, users will see all manually tracked accounts in a single group and Plaid institutions in their own groups:

CleanShot 2024-11-15 at 09 06 09

Syncable Concern

I've reworked Syncable so that the Family, PlaidItem, and Account can all be "synced". Each "syncable" is expected to implement it's own implementation of sync_data:

# account.rb (example)
def sync_data(start_date: nil)
  update!(last_synced_at: Time.current)

  resolve_stale_issues
  Balance::Syncer.new(self, start_date: start_date).run
  Holding::Syncer.new(self, start_date: start_date).run
end

This method will be called by Sync, which is a generic model that performs a "sync" against a specific model and handles errors:

def perform
  start!

  syncable.sync_data(start_date: start_date)

  complete!
rescue StandardError => error
  fail! error
  raise error if Rails.env.development?
end

Initial Plaid Implementation

The Plaid domain consists of the following classes:

class PlaidItem < ApplicationRecord
  belongs_to :family
end

class PlaidAccount < ApplicationRecord
  belongs_to :plaid_item
  has_one :account, dependent: :destroy
end

# Existing model
class Account < Application 
  belongs_to :family 
  belongs_to :plaid_account, optional: true
end

A PlaidItem maps 1:1 with Plaid Items while a PlaidAccount maps 1:1 with Plaid Accounts. These classes use Provider::Plaid through the Plaidable concern in order to fetch Plaid data. These classes are then responsible for providing mappings and transformations required prior to loading data to the Account (Maybe domain).

An Account can optionally belong to a PlaidItem. In this case, it is a "linked account" and will be synced at both the PlaidItem level (fetching and storing Plaid data) and the Account level (syncing balances and holdings).

If an Account does not belong_to a PlaidItem, it is considered a "manually tracked account".

This PR introduces a basic Plaid integration for our hosted app offering, which includes: - Bank linking - Automated transaction syncing - Automated investment trade syncing - Webhooks (with verification) Below are some details of the changes and implementation. ## Institution UI Previously, we had an "Institutions" UI that allowed users to add accounts to "institutions" (which were really just "groups"). This feature was confusing (as reported by several users), and therefore, this PR removes it entirely. Now, users will see all manually tracked accounts in a single group and Plaid institutions in their own groups: ![CleanShot 2024-11-15 at 09 06 09](https://github.com/user-attachments/assets/f7da3c5f-f477-4b83-8283-6926427061ca) ## Syncable Concern I've reworked `Syncable` so that the `Family`, `PlaidItem`, and `Account` can all be "synced". Each "syncable" is expected to implement it's own implementation of `sync_data`: ```rb # account.rb (example) def sync_data(start_date: nil) update!(last_synced_at: Time.current) resolve_stale_issues Balance::Syncer.new(self, start_date: start_date).run Holding::Syncer.new(self, start_date: start_date).run end ``` This method will be called by `Sync`, which is a generic model that performs a "sync" against a specific model and handles errors: ```rb def perform start! syncable.sync_data(start_date: start_date) complete! rescue StandardError => error fail! error raise error if Rails.env.development? end ``` ## Initial Plaid Implementation The Plaid domain consists of the following classes: ```rb class PlaidItem < ApplicationRecord belongs_to :family end class PlaidAccount < ApplicationRecord belongs_to :plaid_item has_one :account, dependent: :destroy end # Existing model class Account < Application belongs_to :family belongs_to :plaid_account, optional: true end ``` A `PlaidItem` maps 1:1 with [Plaid Items](https://plaid.com/docs/api/items/) while a `PlaidAccount` maps 1:1 with [Plaid Accounts](https://plaid.com/docs/api/accounts/). These classes use `Provider::Plaid` through the `Plaidable` concern in order to fetch Plaid data. These classes are then responsible for providing mappings and transformations required prior to loading data to the `Account` (Maybe domain). An `Account` can optionally belong to a `PlaidItem`. In this case, it is a "linked account" and will be synced at both the `PlaidItem` level (fetching and storing Plaid data) and the `Account` level (syncing balances and holdings). If an `Account` does _not_ belong_to a `PlaidItem`, it is considered a "manually tracked account".
MzChynaBlackInc (Migrated from github.com) approved these changes 2024-11-15 11:34:03 +08:00
Sign in to join this conversation.