Skip to content

Commit

Permalink
misc(payment_providers): Split customer creation by providers (#2896)
Browse files Browse the repository at this point in the history
## Context

The `PaymentProviderCustomers::CreateService` is now too complex and the
logic to handle specific logic for the payment providers is splitted
between this service and the `Customers::CreateService` and
`Customers::UpdateService` making it hard to understand, maintain and
extand (see the Cashfree provider PR as an example)

## Description

This PR propose a new approach to handle provider related logic, that
will be extended in the future to all interactions with the providers:
- Move all the logic into
`/app/services/payment_providers/_PROVIDER_NAME_/_LAGO_RESOURCE_/__ACTION_service.rb`
(example:
`/app/services/payment_providers/stripe/customers/create_service.rb`)
- Add a dedicated factory responsible for initializing the instance of
the right payment provider service
  • Loading branch information
vincent-pochet authored Dec 6, 2024
1 parent 41cdc73 commit 35d4b3c
Show file tree
Hide file tree
Showing 13 changed files with 741 additions and 505 deletions.
20 changes: 5 additions & 15 deletions app/services/customers/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def handle_api_billing_configuration(customer, params, new_customer)

if billing.key?(:payment_provider)
customer.payment_provider = nil
if %w[stripe gocardless adyen].include?(billing[:payment_provider])
if Customer::PAYMENT_PROVIDERS.include?(billing[:payment_provider])
customer.payment_provider = billing[:payment_provider]
customer.payment_provider_code = billing[:payment_provider_code] if billing.key?(:payment_provider_code)
end
Expand All @@ -312,23 +312,13 @@ def handle_api_billing_configuration(customer, params, new_customer)
end

def create_or_update_provider_customer(customer, billing_configuration = {})
provider_class = case billing_configuration[:payment_provider] || customer.payment_provider
when "stripe"
PaymentProviderCustomers::StripeCustomer
when "gocardless"
PaymentProviderCustomers::GocardlessCustomer
when "adyen"
PaymentProviderCustomers::AdyenCustomer
end

create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: provider_class,
PaymentProviders::CreateCustomerFactory.new_instance(
provider: billing_configuration[:payment_provider] || customer.payment_provider,
customer:,
payment_provider_id: payment_provider(customer)&.id,
params: billing_configuration,
async: !(billing_configuration || {})[:sync]
)

create_result.raise_if_error!
).call.raise_if_error!
end

def track_customer_created(customer)
Expand Down
65 changes: 10 additions & 55 deletions app/services/customers/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ def initialize(customer:, args:)
end

def call
return result.not_found_failure!(resource: 'customer') unless customer
return result.not_found_failure!(resource: "customer") unless customer

unless valid_metadata_count?(metadata: args[:metadata])
return result.single_validation_failure!(
field: :metadata,
error_code: 'invalid_count'
error_code: "invalid_count"
)
end

Expand Down Expand Up @@ -152,7 +152,7 @@ def call
customer: result.customer,
new_customer: false
)
SendWebhookJob.perform_later('customer.updated', customer)
SendWebhookJob.perform_later("customer.updated", customer)
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
Expand Down Expand Up @@ -182,63 +182,18 @@ def assign_premium_attributes(customer, args)
def create_or_update_provider_customer(customer, payment_provider, billing_configuration = {})
handle_provider_customer = customer.payment_provider.present?
handle_provider_customer ||= (billing_configuration || {})[:provider_customer_id].present?
handle_provider_customer ||= customer.send(:"#{payment_provider}_customer")&.provider_customer_id.present?
return unless handle_provider_customer

case payment_provider
when 'stripe'
handle_provider_customer ||= customer.stripe_customer&.provider_customer_id.present?

return unless handle_provider_customer

update_stripe_customer(customer, billing_configuration)
when 'gocardless'
handle_provider_customer ||= customer.gocardless_customer&.provider_customer_id.present?

return unless handle_provider_customer

update_gocardless_customer(customer, billing_configuration)
when 'adyen'
handle_provider_customer ||= customer.adyen_customer&.provider_customer_id.present?

return unless handle_provider_customer

update_adyen_customer(customer, billing_configuration)
end
end

def update_stripe_customer(customer, billing_configuration)
create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: PaymentProviderCustomers::StripeCustomer,
payment_provider_id: payment_provider(customer)&.id,
params: billing_configuration
)
create_result.raise_if_error!

# NOTE: Create service is modifying an other instance of the provider customer
customer.stripe_customer&.reload
end

def update_gocardless_customer(customer, billing_configuration)
create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: PaymentProviderCustomers::GocardlessCustomer,
payment_provider_id: payment_provider(customer)&.id,
params: billing_configuration
)
create_result.raise_if_error!

# NOTE: Create service is modifying an other instance of the provider customer
customer.gocardless_customer&.reload
end

def update_adyen_customer(customer, billing_configuration)
create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: PaymentProviderCustomers::AdyenCustomer,
PaymentProviders::CreateCustomerFactory.new_instance(
provider: payment_provider,
customer:,
payment_provider_id: payment_provider(customer)&.id,
params: billing_configuration
)
create_result.raise_if_error!
).call.raise_if_error!

# NOTE: Create service is modifying an other instance of the provider customer
customer.adyen_customer&.reload
customer.reload
end

def applied_dunning_campaign
Expand Down
101 changes: 0 additions & 101 deletions app/services/payment_provider_customers/create_service.rb

This file was deleted.

79 changes: 79 additions & 0 deletions app/services/payment_providers/adyen/customers/create_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

module PaymentProviders
module Adyen
module Customers
class CreateService < BaseService
def initialize(customer:, payment_provider_id:, params:, async: true)
@customer = customer
@payment_provider_id = payment_provider_id
@params = params || {}
@async = async

super
end

def call
provider_customer = PaymentProviderCustomers::AdyenCustomer.find_by(customer_id: customer.id)
provider_customer ||= PaymentProviderCustomers::AdyenCustomer.new(customer_id: customer.id, payment_provider_id:)

if params.key?(:provider_customer_id)
provider_customer.provider_customer_id = params[:provider_customer_id].presence
end

if params.key?(:sync_with_provider)
provider_customer.sync_with_provider = params[:sync_with_provider].presence
end

provider_customer.save!

result.provider_customer = provider_customer

if should_create_provider_customer?
create_customer_on_provider_service(async)
elsif should_generate_checkout_url?
generate_checkout_url(async)
end

result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
end

private

attr_accessor :customer, :payment_provider_id, :params, :async

delegate :organization, to: :customer

def create_customer_on_provider_service(async)
return PaymentProviderCustomers::AdyenCreateJob.perform_later(result.provider_customer) if async

PaymentProviderCustomers::AdyenCreateJob.perform_now(result.provider_customer)
end

def generate_checkout_url(async)
return PaymentProviderCustomers::AdyenCheckoutUrlJob.perform_later(result.provider_customer) if async

PaymentProviderCustomers::AdyenCheckoutUrlJob.perform_now(result.provider_customer)
end

def should_create_provider_customer?
# NOTE: the customer does not exists on the service provider
# and the customer id was not removed from the customer
# customer sync with provider setting is set to true
!result.provider_customer.provider_customer_id? &&
!result.provider_customer.provider_customer_id_previously_changed? &&
result.provider_customer.sync_with_provider.present?
end

def should_generate_checkout_url?
!result.provider_customer.id_previously_changed?(from: nil) && # it was not created but updated
result.provider_customer.provider_customer_id_previously_changed? &&
result.provider_customer.provider_customer_id? &&
result.provider_customer.sync_with_provider.blank?
end
end
end
end
end
20 changes: 20 additions & 0 deletions app/services/payment_providers/create_customer_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module PaymentProviders
class CreateCustomerFactory
def self.new_instance(provider:, customer:, payment_provider_id:, params:, async: true)
service_class(provider:).new(customer:, payment_provider_id:, params:, async:)
end

def self.service_class(provider:)
case provider
when "adyen"
PaymentProviders::Adyen::Customers::CreateService
when "gocardless"
PaymentProviders::Gocardless::Customers::CreateService
when "stripe"
PaymentProviders::Stripe::Customers::CreateService
end
end
end
end
Loading

0 comments on commit 35d4b3c

Please sign in to comment.