mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-21 14:19:39 +02:00
Plaid webhook processor
This commit is contained in:
parent
5125411822
commit
ffc5f844b2
3 changed files with 55 additions and 25 deletions
|
@ -9,7 +9,8 @@ class WebhooksController < ApplicationController
|
||||||
client = Provider::Registry.plaid_provider_for_region(:us)
|
client = Provider::Registry.plaid_provider_for_region(:us)
|
||||||
|
|
||||||
client.validate_webhook!(plaid_verification_header, webhook_body)
|
client.validate_webhook!(plaid_verification_header, webhook_body)
|
||||||
client.process_webhook(webhook_body)
|
|
||||||
|
PlaidItem::WebhookProcessor.new(webhook_body).process
|
||||||
|
|
||||||
render json: { received: true }, status: :ok
|
render json: { received: true }, status: :ok
|
||||||
rescue => error
|
rescue => error
|
||||||
|
@ -24,7 +25,8 @@ class WebhooksController < ApplicationController
|
||||||
client = Provider::Registry.plaid_provider_for_region(:eu)
|
client = Provider::Registry.plaid_provider_for_region(:eu)
|
||||||
|
|
||||||
client.validate_webhook!(plaid_verification_header, webhook_body)
|
client.validate_webhook!(plaid_verification_header, webhook_body)
|
||||||
client.process_webhook(webhook_body)
|
|
||||||
|
PlaidItem::WebhookProcessor.new(webhook_body).process
|
||||||
|
|
||||||
render json: { received: true }, status: :ok
|
render json: { received: true }, status: :ok
|
||||||
rescue => error
|
rescue => error
|
||||||
|
|
51
app/models/plaid_item/webhook_processor.rb
Normal file
51
app/models/plaid_item/webhook_processor.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
class PlaidItem::WebhookProcessor
|
||||||
|
MissingItemError = Class.new(StandardError)
|
||||||
|
|
||||||
|
def initialize(webhook_body)
|
||||||
|
parsed = JSON.parse(webhook_body)
|
||||||
|
@webhook_type = parsed["webhook_type"]
|
||||||
|
@webhook_code = parsed["webhook_code"]
|
||||||
|
@item_id = parsed["item_id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def process
|
||||||
|
unless plaid_item
|
||||||
|
handle_missing_item
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
case [ webhook_type, webhook_code ]
|
||||||
|
when [ "TRANSACTIONS", "SYNC_UPDATES_AVAILABLE" ]
|
||||||
|
plaid_item.sync_later
|
||||||
|
when [ "INVESTMENTS_TRANSACTIONS", "DEFAULT_UPDATE" ]
|
||||||
|
plaid_item.sync_later
|
||||||
|
when [ "HOLDINGS", "DEFAULT_UPDATE" ]
|
||||||
|
plaid_item.sync_later
|
||||||
|
else
|
||||||
|
Rails.logger.warn("Unhandled Plaid webhook type: #{webhook_type}:#{webhook_code}")
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
# To always ensure we return a 200 to Plaid (to keep endpoint healthy), silently capture and report all errors
|
||||||
|
Sentry.capture_exception(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
attr_reader :webhook_type, :webhook_code, :item_id
|
||||||
|
|
||||||
|
def plaid_item
|
||||||
|
@plaid_item ||= PlaidItem.find_by(plaid_id: item_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_missing_item
|
||||||
|
return if plaid_item.present?
|
||||||
|
|
||||||
|
# If we cannot find an item in our DB, that means we've reached an invalid data state where
|
||||||
|
# the Plaid Item (upstream) still exists (and is being billed), but doesn't exist internally.
|
||||||
|
#
|
||||||
|
# Since we don't have the item which has the access token, there is nothing we can do programmatically
|
||||||
|
# here, so we just need to report it to Sentry and manually handle it.
|
||||||
|
Sentry.capture_exception(MissingItemError.new("Received Plaid webhook for item no longer in our DB. Manual action required to resolve.")) do |scope|
|
||||||
|
scope.set_tags(plaid_item_id: item_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,29 +11,6 @@ class Provider::Plaid
|
||||||
@region = region
|
@region = region
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_webhook(webhook_body)
|
|
||||||
parsed = JSON.parse(webhook_body)
|
|
||||||
|
|
||||||
type = parsed["webhook_type"]
|
|
||||||
code = parsed["webhook_code"]
|
|
||||||
|
|
||||||
item = PlaidItem.find_by(plaid_id: parsed["item_id"])
|
|
||||||
|
|
||||||
case [ type, code ]
|
|
||||||
when [ "TRANSACTIONS", "SYNC_UPDATES_AVAILABLE" ]
|
|
||||||
item.sync_later
|
|
||||||
when [ "INVESTMENTS_TRANSACTIONS", "DEFAULT_UPDATE" ]
|
|
||||||
item.sync_later
|
|
||||||
when [ "HOLDINGS", "DEFAULT_UPDATE" ]
|
|
||||||
item.sync_later
|
|
||||||
else
|
|
||||||
Rails.logger.warn("Unhandled Plaid webhook type: #{type}:#{code}")
|
|
||||||
end
|
|
||||||
rescue => error
|
|
||||||
# Processing errors shouldn't return a 400 to Plaid since they are internal, so capture silently
|
|
||||||
Sentry.capture_exception(error)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_webhook!(verification_header, raw_body)
|
def validate_webhook!(verification_header, raw_body)
|
||||||
jwks_loader = ->(options) do
|
jwks_loader = ->(options) do
|
||||||
key_id = options[:kid]
|
key_id = options[:kid]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue