Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc(PaymentProviders): Refact webhooks folder structure #2829

Merged
merged 2 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
24 changes: 24 additions & 0 deletions app/services/payment_providers/adyen/webhooks/base_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module PaymentProviders
module Adyen
module Webhooks
class BaseService < BaseService
def initialize(organization_id:, event_json:)
@organization = Organization.find(organization_id)
@event_json = event_json

super
end

private

attr_reader :organization, :event_json

def event
@event ||= JSON.parse(event_json)['notificationItems'].first&.dig('NotificationRequestItem')
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

module PaymentProviders
module Webhooks
module Adyen
module Adyen
module Webhooks
class ChargebackService < BaseService
def call
status = event['additionalData']['disputeStatus']
Expand All @@ -21,10 +21,6 @@ def call

private

def event
@event ||= JSON.parse(event_json)['notificationItems'].first&.dig('NotificationRequestItem')
end

def payment_dispute_lost_at
Time.zone.parse(event['eventDate'])
end
Expand Down
2 changes: 1 addition & 1 deletion app/services/payment_providers/adyen_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def handle_event(organization:, event_json:)
result = service.update_status(provider_refund_id:, status:)
result.raise_if_error!
when 'CHARGEBACK'
PaymentProviders::Webhooks::Adyen::ChargebackService.call(
PaymentProviders::Adyen::Webhooks::ChargebackService.call(
organization_id: organization.id,
event_json:
)
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
36 changes: 36 additions & 0 deletions app/services/payment_providers/stripe/webhooks/base_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module PaymentProviders
module Stripe
module Webhooks
class BaseService < BaseService
def initialize(organization_id:, event:)
@organization = Organization.find(organization_id)
@event = event

super
end

private

attr_reader :organization, :event

def metadata
@metadata ||= event.data.object.metadata.to_h.symbolize_keys
end

def handle_missing_customer
# NOTE: Stripe customer was not created from lago
return result unless metadata&.key?(:lago_customer_id)

# NOTE: Customer does not belong to this lago instance or
# exists but does not belong to the organizations
# (Happens when the Stripe API key is shared between organizations)
return result if Customer.find_by(id: metadata[:lago_customer_id], organization_id: organization.id).nil?

result.not_found_failure!(resource: 'stripe_customer')
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

module PaymentProviders
module Webhooks
module Stripe
module Stripe
module Webhooks
class ChargeDisputeClosedService < BaseService
def call
status = event.data.object.status
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

module PaymentProviders
module Webhooks
module Stripe
module Stripe
module Webhooks
class CustomerUpdatedService < BaseService
def call
return handle_missing_customer unless stripe_customer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

module PaymentProviders
module Webhooks
module Stripe
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::Webhooks::Stripe::SetupIntentSucceededService.call(
organization_id: organization.id,
event_json:
).raise_if_error!
when 'customer.updated'
PaymentProviders::Webhooks::Stripe::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::Webhooks::Stripe::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
38 changes: 0 additions & 38 deletions app/services/payment_providers/webhooks/base_service.rb

This file was deleted.

Loading