Skip to content

Commit

Permalink
misc(stripe): Refact webhook handling
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-pochet committed Nov 18, 2024
1 parent 3e0dd92 commit 6a7f7bf
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 388 deletions.
2 changes: 1 addition & 1 deletion app/jobs/payment_providers/stripe/handle_event_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class HandleEventJob < ApplicationJob
retry_on BaseService::NotFoundFailure

def perform(organization:, event:)
result = PaymentProviders::StripeService.new.handle_event(
result = PaymentProviders::Stripe::HandleEventService.call(
organization:,
event_json: event
)
Expand Down
99 changes: 99 additions & 0 deletions app/services/payment_providers/stripe/handle_event_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

module PaymentProviders
module Stripe
class HandleEventService < BaseService
EVENT_MAPPING = {
'setup_intent.succeeded' => PaymentProviders::Stripe::Webhooks::SetupIntentSucceededService,
'customer.updated' => PaymentProviders::Stripe::Webhooks::CustomerUpdatedService,
'charge.dispute.closed' => PaymentProviders::Stripe::Webhooks::ChargeDisputeClosedService
}.freeze

PAYMENT_SERVICE_CLASS_MAP = {
"Invoice" => Invoices::Payments::StripeService,
"PaymentRequest" => PaymentRequests::Payments::StripeService
}.freeze

def initialize(organization:, event_json:)
@organization = organization
@event_json = event_json

super
end

def call
unless PaymentProviders::StripeProvider::WEBHOOKS_EVENTS.include?(event.type)
Rails.logger.warn("Unexpected stripe event type: #{event.type}")
return result
end

if EVENT_MAPPING[event.type].present?
EVENT_MAPPING[event.type].call(
organization_id: organization.id,
event:
).raise_if_error!

return result
end

case event.type
when 'charge.succeeded'
payment_service_klass(event)
.new.update_payment_status(
organization_id: organization.id,
provider_payment_id: event.data.object.payment_intent,
status: 'succeeded',
metadata: event.data.object.metadata.to_h.symbolize_keys
).raise_if_error!
when 'payment_intent.payment_failed', 'payment_intent.succeeded'
status = (event.type == 'payment_intent.succeeded') ? 'succeeded' : 'failed'
payment_service_klass(event)
.new.update_payment_status(
organization_id: organization.id,
provider_payment_id: event.data.object.id,
status:,
metadata: event.data.object.metadata.to_h.symbolize_keys
).raise_if_error!
when 'payment_method.detached'
PaymentProviderCustomers::StripeService
.new
.delete_payment_method(
organization_id: organization.id,
stripe_customer_id: event.data.object.customer,
payment_method_id: event.data.object.id,
metadata: event.data.object.metadata.to_h.symbolize_keys
).raise_if_error!
when 'charge.refund.updated'
CreditNotes::Refunds::StripeService
.new.update_status(
provider_refund_id: event.data.object.id,
status: event.data.object.status,
metadata: event.data.object.metadata.to_h.symbolize_keys
)
end
rescue BaseService::NotFoundFailure => e
# NOTE: Error with stripe sandbox should be ignord
raise if event.livemode

Rails.logger.warn("Stripe resource not found: #{e.message}. JSON: #{event_json}")
BaseService::Result.new # NOTE: Prevents error from being re-raised
end

private

attr_reader :organization, :body, :event_json

def event
@event ||= ::Stripe::Event.construct_from(JSON.parse(event_json))
end

def payment_service_klass(event)
payable_type = event.data.object.metadata.to_h[:lago_payable_type] || "Invoice"

PAYMENT_SERVICE_CLASS_MAP.fetch(payable_type) do
raise NameError, "Invalid lago_payable_type: #{payable_type}"
end
end
end
end
end
10 changes: 3 additions & 7 deletions app/services/payment_providers/stripe/webhooks/base_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@ module PaymentProviders
module Stripe
module Webhooks
class BaseService < BaseService
def initialize(organization_id:, event_json:)
def initialize(organization_id:, event:)
@organization = Organization.find(organization_id)
@event_json = event_json
@event = event

super
end

private

attr_reader :organization, :event_json

def event
@event ||= ::Stripe::Event.construct_from(JSON.parse(event_json))
end
attr_reader :organization, :event

def metadata
@metadata ||= event.data.object.metadata.to_h.symbolize_keys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

module PaymentProviders
module Stripe
module Webhooks
module Stripe
module Webhooks
class SetupIntentSucceededService < BaseService
include Customers::PaymentProviderFinder

Expand Down
78 changes: 0 additions & 78 deletions app/services/payment_providers/stripe_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

module PaymentProviders
class StripeService < BaseService
PAYMENT_SERVICE_CLASS_MAP = {
"Invoice" => Invoices::Payments::StripeService,
"PaymentRequest" => PaymentRequests::Payments::StripeService
}.freeze

def create_or_update(**args)
payment_provider_result = PaymentProviders::FindService.call(
organization_id: args[:organization_id],
Expand Down Expand Up @@ -84,71 +79,6 @@ def handle_incoming_webhook(organization_id:, params:, signature:, code: nil)
result.service_failure!(code: 'webhook_error', message: 'Invalid signature')
end

def handle_event(organization:, event_json:)
event = ::Stripe::Event.construct_from(JSON.parse(event_json))
unless PaymentProviders::StripeProvider::WEBHOOKS_EVENTS.include?(event.type)
Rails.logger.warn("Unexpected stripe event type: #{event.type}")
return result
end

case event.type
when 'setup_intent.succeeded'
PaymentProviders::Stripe::Webhooks::SetupIntentSucceededService.call(
organization_id: organization.id,
event_json:
).raise_if_error!
when 'customer.updated'
PaymentProviders::Stripe::Webhooks::CustomerUpdatedService.call(
organization_id: organization.id,
event_json:
).raise_if_error!
when 'charge.succeeded'
payment_service_klass(event)
.new.update_payment_status(
organization_id: organization.id,
provider_payment_id: event.data.object.payment_intent,
status: 'succeeded',
metadata: event.data.object.metadata.to_h.symbolize_keys
).raise_if_error!
when 'charge.dispute.closed'
PaymentProviders::Stripe::Webhooks::ChargeDisputeClosedService.call(
organization_id: organization.id,
event_json:
).raise_if_error!
when 'payment_intent.payment_failed', 'payment_intent.succeeded'
status = (event.type == 'payment_intent.succeeded') ? 'succeeded' : 'failed'
payment_service_klass(event)
.new.update_payment_status(
organization_id: organization.id,
provider_payment_id: event.data.object.id,
status:,
metadata: event.data.object.metadata.to_h.symbolize_keys
).raise_if_error!
when 'payment_method.detached'
PaymentProviderCustomers::StripeService
.new
.delete_payment_method(
organization_id: organization.id,
stripe_customer_id: event.data.object.customer,
payment_method_id: event.data.object.id,
metadata: event.data.object.metadata.to_h.symbolize_keys
).raise_if_error!
when 'charge.refund.updated'
CreditNotes::Refunds::StripeService
.new.update_status(
provider_refund_id: event.data.object.id,
status: event.data.object.status,
metadata: event.data.object.metadata.to_h.symbolize_keys
)
end
rescue BaseService::NotFoundFailure => e
# NOTE: Error with stripe sandbox should be ignord
raise if event.livemode

Rails.logger.warn("Stripe resource not found: #{e.message}. JSON: #{event_json}")
BaseService::Result.new # NOTE: Prevents error from being re-raised
end

private

def unregister_webhook(stripe_provider, api_key)
Expand All @@ -166,13 +96,5 @@ def unregister_webhook(stripe_provider, api_key)

Sentry.capture_exception(e)
end

def payment_service_klass(event)
payable_type = event.data.object.metadata.to_h[:lago_payable_type] || "Invoice"

PAYMENT_SERVICE_CLASS_MAP.fetch(payable_type) do
raise NameError, "Invalid lago_payable_type: #{payable_type}"
end
end
end
end
9 changes: 3 additions & 6 deletions spec/jobs/payment_providers/stripe/handle_event_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
require 'rails_helper'

RSpec.describe PaymentProviders::Stripe::HandleEventJob, type: :job do
let(:stripe_service) { instance_double(PaymentProviders::StripeService) }
let(:result) { BaseService::Result.new }
let(:organization) { create(:organization) }

Expand All @@ -12,9 +11,8 @@
end

before do
allow(PaymentProviders::StripeService).to receive(:new)
.and_return(stripe_service)
allow(stripe_service).to receive(:handle_event)
allow(PaymentProviders::Stripe::HandleEventService)
.to receive(:call)
.and_return(result)
end

Expand All @@ -24,7 +22,6 @@
event: stripe_event
)

expect(PaymentProviders::StripeService).to have_received(:new)
expect(stripe_service).to have_received(:handle_event)
expect(PaymentProviders::Stripe::HandleEventService).to have_received(:call)
end
end
Loading

0 comments on commit 6a7f7bf

Please sign in to comment.