1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-24 15:49:39 +02:00

Basic Plaid Integration (#1433)
Some checks are pending
Publish Docker image / ci (push) Waiting to run
Publish Docker image / Build docker image (push) Blocked by required conditions

* Basic plaid data model and linking

* Remove institutions, add plaid items

* Improve schema and Plaid provider

* Add webhook verification sketch

* Webhook verification

* Item accounts and balances sync setup

* Provide test encryption keys

* Fix test

* Only provide encryption keys in prod

* Try defining keys in test env

* Consolidate account sync logic

* Add back plaid account initialization

* Plaid transaction sync

* Sync UI overhaul for Plaid

* Add liability and investment syncing

* Handle investment webhooks and process current day holdings

* Remove logs

* Remove "all" period select for performance

* fix amount calc

* Remove todo comment

* Coming soon for investment historical data

* Document Plaid configuration

* Listen for holding updates
This commit is contained in:
Zach Gollwitzer 2024-11-15 13:49:37 -05:00 committed by GitHub
parent 3bc9da4105
commit cbba2ba675
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
127 changed files with 1537 additions and 841 deletions

View file

@ -4,8 +4,8 @@ class AccountsController < ApplicationController
before_action :set_account, only: %i[sync]
def index
@institutions = Current.family.institutions
@accounts = Current.family.accounts.ungrouped.alphabetically
@manual_accounts = Current.family.accounts.manual.active.alphabetically
@plaid_items = Current.family.plaid_items.active.ordered
end
def summary
@ -27,11 +27,16 @@ class AccountsController < ApplicationController
unless @account.syncing?
@account.sync_later
end
redirect_to account_path(@account)
end
def sync_all
Current.family.accounts.active.sync
redirect_back_or_to accounts_path, notice: t(".success")
unless Current.family.syncing?
Current.family.sync_later
end
redirect_to accounts_path
end
private

View file

@ -4,6 +4,7 @@ module AccountableResource
included do
layout :with_sidebar
before_action :set_account, only: [ :show, :edit, :update, :destroy ]
before_action :set_link_token, only: :new
end
class_methods do
@ -16,8 +17,7 @@ module AccountableResource
def new
@account = Current.family.accounts.build(
currency: Current.family.currency,
accountable: accountable_type.new,
institution_id: params[:institution_id]
accountable: accountable_type.new
)
end
@ -29,20 +29,35 @@ module AccountableResource
def create
@account = Current.family.accounts.create_and_sync(account_params.except(:return_to))
redirect_to account_params[:return_to].presence || @account, notice: t(".success")
redirect_to account_params[:return_to].presence || @account, notice: t("accounts.create.success", type: accountable_type.name.underscore.humanize)
end
def update
@account.update_with_sync!(account_params.except(:return_to))
redirect_back_or_to @account, notice: t(".success")
redirect_back_or_to @account, notice: t("accounts.update.success", type: accountable_type.name.underscore.humanize)
end
def destroy
@account.destroy!
redirect_to accounts_path, notice: t(".success")
@account.destroy_later
redirect_to accounts_path, notice: t("accounts.destroy.success", type: accountable_type.name.underscore.humanize)
end
private
def set_link_token
@link_token = Current.family.get_link_token(
webhooks_url: webhooks_url,
redirect_url: accounts_url,
accountable_type: accountable_type.name
)
end
def webhooks_url
return webhooks_plaid_url if Rails.env.production?
base_url = ENV.fetch("DEV_WEBHOOKS_URL", root_url.chomp("/"))
base_url + "/webhooks/plaid"
end
def accountable_type
controller_name.classify.constantize
end
@ -53,7 +68,7 @@ module AccountableResource
def account_params
params.require(:account).permit(
:name, :is_active, :balance, :subtype, :currency, :institution_id, :accountable_type, :return_to,
:name, :is_active, :balance, :subtype, :currency, :accountable_type, :return_to,
accountable_attributes: self.class.permitted_accountable_attributes
)
end

View file

@ -2,12 +2,20 @@ module AutoSync
extend ActiveSupport::Concern
included do
before_action :sync_family, if: -> { Current.family.present? && Current.family.needs_sync? }
before_action :sync_family, if: :family_needs_auto_sync?
end
private
def sync_family
Current.family.sync
Current.family.update!(last_synced_at: Time.current)
Current.family.sync_later
end
def family_needs_auto_sync?
return false unless Current.family.present?
return false unless Current.family.accounts.any?
Current.family.last_synced_at.blank? ||
Current.family.last_synced_at.to_date < Date.current
end
end

View file

@ -1,40 +0,0 @@
class InstitutionsController < ApplicationController
before_action :set_institution, except: %i[new create]
def new
@institution = Institution.new
end
def create
Current.family.institutions.create!(institution_params)
redirect_to accounts_path, notice: t(".success")
end
def edit
end
def update
@institution.update!(institution_params)
redirect_to accounts_path, notice: t(".success")
end
def destroy
@institution.destroy!
redirect_to accounts_path, notice: t(".success")
end
def sync
@institution.sync
redirect_back_or_to accounts_path, notice: t(".success")
end
private
def institution_params
params.require(:institution).permit(:name, :logo)
end
def set_institution
@institution = Current.family.institutions.find(params[:id])
end
end

View file

@ -0,0 +1,38 @@
class PlaidItemsController < ApplicationController
before_action :set_plaid_item, only: %i[destroy sync]
def create
Current.family.plaid_items.create_from_public_token(
plaid_item_params[:public_token],
item_name: item_name,
)
redirect_to accounts_path, notice: t(".success")
end
def destroy
@plaid_item.destroy_later
redirect_to accounts_path, notice: t(".success")
end
def sync
unless @plaid_item.syncing?
@plaid_item.sync_later
end
redirect_to accounts_path
end
private
def set_plaid_item
@plaid_item = Current.family.plaid_items.find(params[:id])
end
def plaid_item_params
params.require(:plaid_item).permit(:public_token, metadata: {})
end
def item_name
plaid_item_params.dig(:metadata, :institution, :name)
end
end

View file

@ -11,8 +11,7 @@ class PropertiesController < ApplicationController
currency: Current.family.currency,
accountable: Property.new(
address: Address.new
),
institution_id: params[:institution_id]
)
)
end

View file

@ -1,7 +1,19 @@
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token, only: [ :stripe ]
skip_before_action :verify_authenticity_token
skip_authentication
def plaid
webhook_body = request.body.read
plaid_verification_header = request.headers["Plaid-Verification"]
Provider::Plaid.validate_webhook!(plaid_verification_header, webhook_body)
Provider::Plaid.process_webhook(webhook_body)
render json: { received: true }, status: :ok
rescue => error
render json: { error: "Invalid webhook: #{error.message}" }, status: :bad_request
end
def stripe
webhook_body = request.body.read
sig_header = request.env["HTTP_STRIPE_SIGNATURE"]