Skip to content

Commit

Permalink
misc(payment_providers): Split customer creation in
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-pochet committed Dec 2, 2024
1 parent f575884 commit 0e3071f
Show file tree
Hide file tree
Showing 14 changed files with 824 additions and 430 deletions.
36 changes: 12 additions & 24 deletions app/services/customers/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ def create_from_api(organization:, params:)
unless valid_metadata_count?(metadata: params[:metadata])
return result.single_validation_failure!(
field: :metadata,
error_code: 'invalid_count'
error_code: "invalid_count"
)
end

unless valid_finalize_zero_amount_invoice?(params[:finalize_zero_amount_invoice])
return result.single_validation_failure!(
field: :finalize_zero_amount_invoice,
error_code: 'invalid_value'
error_code: "invalid_value"
)
end

unless valid_integration_customers_count?(integration_customers: params[:integration_customers])
return result.single_validation_failure!(
field: :integration_customers,
error_code: 'invalid_count_per_integration_type'
error_code: "invalid_count_per_integration_type"
)
end

Expand All @@ -52,7 +52,7 @@ def create_from_api(organization:, params:)
customer.legal_number = params[:legal_number] if params.key?(:legal_number)
customer.net_payment_term = params[:net_payment_term] if params.key?(:net_payment_term)
customer.external_salesforce_id = params[:external_salesforce_id] if params.key?(:external_salesforce_id)
customer.finalize_zero_amount_invoice = params[:finalize_zero_amount_invoice] || 'inherit' if params.key?(:finalize_zero_amount_invoice)
customer.finalize_zero_amount_invoice = params[:finalize_zero_amount_invoice] || "inherit" if params.key?(:finalize_zero_amount_invoice)
customer.firstname = params[:firstname] if params.key?(:firstname)
customer.lastname = params[:lastname] if params.key?(:lastname)
customer.customer_type = params[:customer_type] if params.key?(:customer_type)
Expand Down Expand Up @@ -117,7 +117,7 @@ def create(**args)
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 @@ -280,7 +280,7 @@ def handle_api_billing_configuration(customer, params, new_customer)

if billing.key?(:payment_provider)
customer.payment_provider = nil
if %w[stripe gocardless cashfree 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 @@ -303,39 +303,27 @@ 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 'cashfree'
PaymentProviderCustomers::CashfreeCustomer
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)
SegmentTrackJob.perform_later(
membership_id: CurrentContext.membership,
event: 'customer_created',
event: "customer_created",
properties: {
customer_id: customer.id,
created_at: customer.created_at,
payment_provider: customer.payment_provider,
organization_id: customer.organization_id
}
)
SendWebhookJob.perform_later('customer.created', customer)
SendWebhookJob.perform_later("customer.created", customer)
end

def should_create_billing_configuration?(billing, customer)
Expand Down
81 changes: 9 additions & 72 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,83 +182,20 @@ 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 'cashfree'
handle_provider_customer ||= customer.cashfree_customer&.provider_customer_id.present?

return unless handle_provider_customer

update_cashfree_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,
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.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_cashfree_customer(customer, billing_configuration)
create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: PaymentProviderCustomers::CashfreeCustomer,
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.cashfree_customer&.reload
end

def update_adyen_customer(customer, billing_configuration)
create_result = PaymentProviderCustomers::CreateService.new(customer).create_or_update(
customer_class: PaymentProviderCustomers::AdyenCustomer,
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.adyen_customer&.reload
end

def applied_dunning_campaign
return customer.applied_dunning_campaign unless args.key?(:applied_dunning_campaign_id)
return unless args[:applied_dunning_campaign_id]
Expand Down
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module PaymentProviders
module Cashfree
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::CashfreeCustomer.find_by(customer_id: customer.id)
provider_customer ||= PaymentProviderCustomers::CashfreeCustomer.new(customer_id: customer.id, payment_provider_id:)

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

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
end
end
end
end
22 changes: 22 additions & 0 deletions app/services/payment_providers/create_customer_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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 "cashfree"
PaymentProviders::Cashfree::Customers::CreateService
when "gocardless"
PaymentProviders::Gocardless::Customers::CreateService
when "stripe"
PaymentProviders::Stripe::Customers::CreateService
end
end
end
end
Loading

0 comments on commit 0e3071f

Please sign in to comment.