mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-25 08:09:38 +02:00
Enhance Plaid connection management with re-authentication and error handling (#1854)
* Enhance Plaid connection management with re-authentication and error handling - Add support for Plaid item status tracking (good/requires_update) - Implement re-authentication flow for Plaid connections - Handle connection errors and provide user-friendly reconnection options - Update Plaid link token generation to support item updates - Add localization for new connection management states * Remove redundant 'reconnect' localization for Plaid items
This commit is contained in:
parent
08a2d35308
commit
f1f2e103ce
11 changed files with 156 additions and 19 deletions
|
@ -62,7 +62,7 @@ class Family < ApplicationRecord
|
|||
country != "US" && country != "CA"
|
||||
end
|
||||
|
||||
def get_link_token(webhooks_url:, redirect_url:, accountable_type: nil, region: :us)
|
||||
def get_link_token(webhooks_url:, redirect_url:, accountable_type: nil, region: :us, access_token: nil)
|
||||
provider = if region.to_sym == :eu
|
||||
self.class.plaid_eu_provider
|
||||
else
|
||||
|
@ -77,6 +77,7 @@ class Family < ApplicationRecord
|
|||
webhooks_url: webhooks_url,
|
||||
redirect_url: redirect_url,
|
||||
accountable_type: accountable_type,
|
||||
access_token: access_token
|
||||
).link_token
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ class PlaidItem < ApplicationRecord
|
|||
include Plaidable, Syncable
|
||||
|
||||
enum :plaid_region, { us: "us", eu: "eu" }
|
||||
enum :status, { good: "good", requires_update: "requires_update" }, default: :good
|
||||
|
||||
if Rails.application.credentials.active_record_encryption.present?
|
||||
encrypts :access_token, deterministic: true
|
||||
|
@ -19,6 +20,7 @@ class PlaidItem < ApplicationRecord
|
|||
|
||||
scope :active, -> { where(scheduled_for_deletion: false) }
|
||||
scope :ordered, -> { order(created_at: :desc) }
|
||||
scope :needs_update, -> { where(status: :requires_update) }
|
||||
|
||||
class << self
|
||||
def create_from_public_token(token, item_name:, region:)
|
||||
|
@ -38,13 +40,35 @@ class PlaidItem < ApplicationRecord
|
|||
def sync_data(start_date: nil)
|
||||
update!(last_synced_at: Time.current)
|
||||
|
||||
plaid_data = fetch_and_load_plaid_data
|
||||
|
||||
accounts.each do |account|
|
||||
account.sync_later(start_date: start_date)
|
||||
begin
|
||||
plaid_data = fetch_and_load_plaid_data
|
||||
update!(status: :good) if requires_update?
|
||||
plaid_data
|
||||
rescue Plaid::ApiError => e
|
||||
handle_plaid_error(e)
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
plaid_data
|
||||
def get_update_link_token(webhooks_url:, redirect_url:)
|
||||
begin
|
||||
family.get_link_token(
|
||||
webhooks_url: webhooks_url,
|
||||
redirect_url: redirect_url,
|
||||
region: plaid_region,
|
||||
access_token: access_token
|
||||
)
|
||||
rescue Plaid::ApiError => e
|
||||
error_body = JSON.parse(e.response_body)
|
||||
|
||||
if error_body["error_code"] == "ITEM_NOT_FOUND"
|
||||
# Mark the connection as invalid but don't auto-delete
|
||||
update!(status: :requires_update)
|
||||
raise PlaidConnectionLostError
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def post_sync
|
||||
|
@ -151,4 +175,14 @@ class PlaidItem < ApplicationRecord
|
|||
rescue StandardError => e
|
||||
Rails.logger.warn("Failed to remove Plaid item #{id}: #{e.message}")
|
||||
end
|
||||
|
||||
def handle_plaid_error(error)
|
||||
error_body = JSON.parse(error.response_body)
|
||||
|
||||
if error_body["error_code"] == "ITEM_LOGIN_REQUIRED"
|
||||
update!(status: :requires_update)
|
||||
end
|
||||
end
|
||||
|
||||
class PlaidConnectionLostError < StandardError; end
|
||||
end
|
||||
|
|
|
@ -65,18 +65,25 @@ class Provider::Plaid
|
|||
raise JWT::VerificationError, "Invalid webhook body hash" unless ActiveSupport::SecurityUtils.secure_compare(expected_hash, actual_hash)
|
||||
end
|
||||
|
||||
def get_link_token(user_id:, webhooks_url:, redirect_url:, accountable_type: nil)
|
||||
request = Plaid::LinkTokenCreateRequest.new({
|
||||
def get_link_token(user_id:, webhooks_url:, redirect_url:, accountable_type: nil, access_token: nil)
|
||||
request_params = {
|
||||
user: { client_user_id: user_id },
|
||||
client_name: "Maybe Finance",
|
||||
products: [ get_primary_product(accountable_type) ],
|
||||
additional_consented_products: get_additional_consented_products(accountable_type),
|
||||
country_codes: country_codes,
|
||||
language: "en",
|
||||
webhook: webhooks_url,
|
||||
redirect_uri: redirect_url,
|
||||
transactions: { days_requested: MAX_HISTORY_DAYS }
|
||||
})
|
||||
}
|
||||
|
||||
if access_token.present?
|
||||
request_params[:access_token] = access_token
|
||||
else
|
||||
request_params[:products] = [ get_primary_product(accountable_type) ]
|
||||
request_params[:additional_consented_products] = get_additional_consented_products(accountable_type)
|
||||
end
|
||||
|
||||
request = Plaid::LinkTokenCreateRequest.new(request_params)
|
||||
|
||||
client.link_token_create(request)
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue