diff --git a/app/services/customers/create_service.rb b/app/services/customers/create_service.rb index e33620a7401..b2ed9f80bd5 100644 --- a/app/services/customers/create_service.rb +++ b/app/services/customers/create_service.rb @@ -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 @@ -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) diff --git a/app/services/customers/update_service.rb b/app/services/customers/update_service.rb index 127ae608217..89c4ef0e659 100644 --- a/app/services/customers/update_service.rb +++ b/app/services/customers/update_service.rb @@ -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 @@ -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) @@ -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 diff --git a/app/services/payment_provider_customers/create_service.rb b/app/services/payment_provider_customers/create_service.rb deleted file mode 100644 index 12ce2c71bb8..00000000000 --- a/app/services/payment_provider_customers/create_service.rb +++ /dev/null @@ -1,101 +0,0 @@ -# frozen_string_literal: true - -module PaymentProviderCustomers - class CreateService < BaseService - def initialize(customer) - @customer = customer - - super(nil) - end - - def create_or_update(customer_class:, payment_provider_id:, params:, async: true) - provider_customer = customer_class.find_by(customer_id: customer.id) - provider_customer ||= customer_class.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 = handle_provider_payment_methods(provider_customer:, params:) - 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 - - delegate :organization, to: :customer - - def handle_provider_payment_methods(provider_customer:, params:) - return provider_customer unless provider_customer.is_a?(PaymentProviderCustomers::StripeCustomer) - - provider_payment_methods = (params || {})[:provider_payment_methods] - - if provider_customer.persisted? - provider_customer.provider_payment_methods = provider_payment_methods if provider_payment_methods.present? - else - provider_customer.provider_payment_methods = provider_payment_methods.presence || %w[card] - end - - provider_customer - end - - def create_customer_on_provider_service(async) - if result.provider_customer.type == 'PaymentProviderCustomers::StripeCustomer' - return PaymentProviderCustomers::StripeCreateJob.perform_later(result.provider_customer) if async - - PaymentProviderCustomers::StripeCreateJob.perform_now(result.provider_customer) - elsif result.provider_customer.type == 'PaymentProviderCustomers::AdyenCustomer' - return PaymentProviderCustomers::AdyenCreateJob.perform_later(result.provider_customer) if async - - PaymentProviderCustomers::AdyenCreateJob.perform_now(result.provider_customer) - else - return PaymentProviderCustomers::GocardlessCreateJob.perform_later(result.provider_customer) if async - - PaymentProviderCustomers::GocardlessCreateJob.perform_now(result.provider_customer) - end - end - - def generate_checkout_url(async) - job_class = result.provider_customer.type.gsub(/Customer\z/, 'CheckoutUrlJob').constantize - - if async - job_class.perform_later(result.provider_customer) - else - job_class.new.perform(result.provider_customer) - end - 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 diff --git a/app/services/payment_providers/adyen/customers/create_service.rb b/app/services/payment_providers/adyen/customers/create_service.rb new file mode 100644 index 00000000000..e1ffdd1a29b --- /dev/null +++ b/app/services/payment_providers/adyen/customers/create_service.rb @@ -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 diff --git a/app/services/payment_providers/create_customer_factory.rb b/app/services/payment_providers/create_customer_factory.rb new file mode 100644 index 00000000000..e79c2a81c84 --- /dev/null +++ b/app/services/payment_providers/create_customer_factory.rb @@ -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 diff --git a/app/services/payment_providers/gocardless/customers/create_service.rb b/app/services/payment_providers/gocardless/customers/create_service.rb new file mode 100644 index 00000000000..0f55597a972 --- /dev/null +++ b/app/services/payment_providers/gocardless/customers/create_service.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module PaymentProviders + module Gocardless + 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::GocardlessCustomer.find_by(customer_id: customer.id) + provider_customer ||= PaymentProviderCustomers::GocardlessCustomer.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::GocardlessCreateJob.perform_later(result.provider_customer) if async + + PaymentProviderCustomers::GocardlessCreateJob.perform_now(result.provider_customer) + end + + def generate_checkout_url(async) + return PaymentProviderCustomers::GocardlessCheckoutUrlJob.perform_later(result.provider_customer) if async + + PaymentProviderCustomers::GocardlessCheckoutUrlJob.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 diff --git a/app/services/payment_providers/stripe/customers/create_service.rb b/app/services/payment_providers/stripe/customers/create_service.rb new file mode 100644 index 00000000000..145039b5595 --- /dev/null +++ b/app/services/payment_providers/stripe/customers/create_service.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +module PaymentProviders + module Stripe + 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::StripeCustomer.find_by(customer_id: customer.id) + provider_customer ||= PaymentProviderCustomers::StripeCustomer.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 = handle_provider_payment_methods(provider_customer:, params:) + 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 handle_provider_payment_methods(provider_customer:, params:) + provider_payment_methods = (params || {})[:provider_payment_methods] + + if provider_customer.persisted? + provider_customer.provider_payment_methods = provider_payment_methods if provider_payment_methods.present? + else + provider_customer.provider_payment_methods = provider_payment_methods.presence || %w[card] + end + + provider_customer + end + + def create_customer_on_provider_service(async) + return PaymentProviderCustomers::StripeCreateJob.perform_later(result.provider_customer) if async + + PaymentProviderCustomers::StripeCreateJob.perform_now(result.provider_customer) + end + + def generate_checkout_url(async) + return PaymentProviderCustomers::StripeCheckoutUrlJob.perform_later(result.provider_customer) if async + + PaymentProviderCustomers::StripeCheckoutUrlJob.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 diff --git a/app/services/payment_providers/stripe/webhooks/setup_intent_succeeded_service.rb b/app/services/payment_providers/stripe/webhooks/setup_intent_succeeded_service.rb index b1b03425eca..b49bab29772 100644 --- a/app/services/payment_providers/stripe/webhooks/setup_intent_succeeded_service.rb +++ b/app/services/payment_providers/stripe/webhooks/setup_intent_succeeded_service.rb @@ -4,7 +4,7 @@ module PaymentProviders module Stripe module Webhooks class SetupIntentSucceededService < BaseService - include Customers::PaymentProviderFinder + include ::Customers::PaymentProviderFinder def call return result if stripe_customer_id.nil? @@ -21,7 +21,7 @@ def call result.stripe_customer = stripe_customer result rescue ::Stripe::PermissionError => e - result.service_failure!(code: 'stripe_error', message: e.message) + result.service_failure!(code: "stripe_error", message: e.message) end private diff --git a/spec/services/payment_provider_customers/create_service_spec.rb b/spec/services/payment_provider_customers/create_service_spec.rb deleted file mode 100644 index a291f615e79..00000000000 --- a/spec/services/payment_provider_customers/create_service_spec.rb +++ /dev/null @@ -1,332 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe PaymentProviderCustomers::CreateService, type: :service do - let(:create_service) { described_class.new(customer) } - - let(:customer) { create(:customer) } - let(:stripe_provider) { create(:stripe_provider, organization: customer.organization) } - - let(:create_params) do - {provider_customer_id: 'id', sync_with_provider: true, provider_payment_methods:} - end - - let(:provider_payment_methods) { %w[card] } - - describe '.create_or_update' do - it 'creates a payment_provider_customer' do - result = create_service.create_or_update( - customer_class: PaymentProviderCustomers::StripeCustomer, - payment_provider_id: stripe_provider.id, - params: create_params - ) - - expect(result).to be_success - expect(result.provider_customer).to be_present - expect(result.provider_customer.provider_customer_id).to eq('id') - end - - context 'when payment provider is stripe' do - let(:service_call) do - create_service.create_or_update( - customer_class: PaymentProviderCustomers::StripeCustomer, - payment_provider_id: stripe_provider.id, - params: create_params - ) - end - - context 'when provider customer is persisted' do - before do - create( - :stripe_customer, - customer:, - payment_provider: stripe_provider, - provider_payment_methods: %w[sepa_debit] - ) - end - - context 'when provider payment methods are present' do - let(:provider_payment_methods) { %w[card sepa_debit] } - - it 'updates payment methods' do - result = service_call - - expect(result.provider_customer.provider_payment_methods).to eq(provider_payment_methods) - end - end - - context 'when provider payment methods are not present' do - let(:provider_payment_methods) { nil } - - it 'does not update payment methods' do - result = service_call - - expect(result.provider_customer.provider_payment_methods).to eq(%w[sepa_debit]) - end - end - end - - context 'when provider customer is not persisted' do - context 'when provider payment methods are present' do - let(:provider_payment_methods) { %w[card sepa_debit] } - - it 'saves payment methods' do - result = service_call - - expect(result.provider_customer.provider_payment_methods).to eq(provider_payment_methods) - end - end - - context 'when provider payment methods are not present' do - let(:provider_payment_methods) { nil } - - it 'saves default payment method' do - result = service_call - - expect(result.provider_customer.provider_payment_methods).to eq(%w[card]) - end - end - end - end - - context 'when no provider customer id and should create on service' do - let(:create_params) do - {provider_customer_id: nil, sync_with_provider: true, provider_payment_methods: %w[card]} - end - - it 'enqueues a job to create the customer on the provider' do - expect do - create_service.create_or_update( - customer_class: PaymentProviderCustomers::StripeCustomer, - payment_provider_id: stripe_provider.id, - params: create_params - ) - end.to have_enqueued_job(PaymentProviderCustomers::StripeCreateJob) - end - end - - context 'when no gocardless provider customer id and should create on service' do - let(:create_params) do - {provider_customer_id: nil, sync_with_provider: true} - end - - let(:gocardless_provider) do - create( - :gocardless_provider, - organization: customer.organization - ) - end - - it 'enqueues a job to create the customer on the provider' do - expect do - create_service.create_or_update( - customer_class: PaymentProviderCustomers::GocardlessCustomer, - payment_provider_id: gocardless_provider.id, - params: create_params - ) - end.to have_enqueued_job(PaymentProviderCustomers::GocardlessCreateJob) - end - end - - context 'when removing the provider customer id and should create on service' do - let(:create_params) do - {provider_customer_id: nil, sync_with_provider: true} - end - - let(:stripe_customer) do - create( - :stripe_customer, - customer:, - payment_provider: stripe_provider - ) - end - - before { stripe_customer } - - it 'updates the provider customer' do - expect do - result = create_service.create_or_update( - customer_class: PaymentProviderCustomers::StripeCustomer, - payment_provider_id: stripe_provider.id, - params: create_params - ) - - aggregate_failures do - expect(result).to be_success - - expect(result.provider_customer.provider_customer_id).to be_nil - end - end.not_to have_enqueued_job(PaymentProviderCustomers::StripeCreateJob) - end - end - - context 'when provider customer id is set' do - subject(:create_or_update) do - create_service.create_or_update( - customer_class:, - payment_provider_id: provider.id, - params: create_params - ) - end - - let(:create_params) do - {provider_customer_id: 'id', sync_with_provider:, provider_payment_methods: %w[card]} - end - - before do - allow(create_service).to receive(:generate_checkout_url).and_return(true) - allow(create_service).to receive(:create_customer_on_provider_service).and_return(true) - end - - context 'when sync with provider is blank' do - let(:sync_with_provider) { nil } - - context 'when customer type is adyen' do - let(:customer_class) { PaymentProviderCustomers::AdyenCustomer } - let(:provider) { create(:adyen_provider, organization: customer.organization) } - - context 'when provider customer exists' do - before do - create(:adyen_customer, customer:, payment_provider_id: provider.id) - end - - it 'generates checkout url' do - create_or_update - expect(create_service).to have_received(:generate_checkout_url) - end - - it 'does not create customer' do - create_or_update - expect(create_service).not_to have_received(:create_customer_on_provider_service) - end - end - - context 'when provider customer does not exist' do - it 'does not generate checkout url' do - create_or_update - expect(create_service).not_to have_received(:generate_checkout_url) - end - - it 'does not create customer' do - create_or_update - expect(create_service).not_to have_received(:create_customer_on_provider_service) - end - end - end - - context 'when customer type is gocardless' do - let(:customer_class) { PaymentProviderCustomers::GocardlessCustomer } - let(:provider) { create(:gocardless_provider, organization: customer.organization) } - - context 'when provider customer exists' do - before do - create(:gocardless_customer, customer:, payment_provider_id: provider.id) - end - - it 'generates checkout url' do - create_or_update - expect(create_service).to have_received(:generate_checkout_url) - end - - it 'does not create customer' do - create_or_update - expect(create_service).not_to have_received(:create_customer_on_provider_service) - end - end - - context 'when provider customer does not exist' do - it 'does not generate checkout url' do - create_or_update - expect(create_service).not_to have_received(:generate_checkout_url) - end - - it 'does not create customer' do - create_or_update - expect(create_service).not_to have_received(:create_customer_on_provider_service) - end - end - end - - context 'when customer type is stripe' do - let(:customer_class) { PaymentProviderCustomers::StripeCustomer } - let(:provider) { create(:stripe_provider, organization: customer.organization) } - - context 'when provider customer exists' do - before do - create(:stripe_customer, customer:, payment_provider_id: provider.id) - end - - it 'generates checkout url' do - create_or_update - expect(create_service).to have_received(:generate_checkout_url) - end - - it 'does not create customer' do - create_or_update - expect(create_service).not_to have_received(:create_customer_on_provider_service) - end - end - - context 'when provider customer does not exist' do - it 'does not generate checkout url' do - create_or_update - expect(create_service).not_to have_received(:generate_checkout_url) - end - - it 'does not create customer' do - create_or_update - expect(create_service).not_to have_received(:create_customer_on_provider_service) - end - end - end - end - - context 'when sync with provider is true' do - let(:sync_with_provider) { true } - - context 'when customer type is stripe' do - let(:customer_class) { PaymentProviderCustomers::StripeCustomer } - let(:provider) { create(:stripe_provider, organization: customer.organization) } - - it 'does not generate checkout url' do - create_or_update - expect(create_service).not_to have_received(:generate_checkout_url) - end - - it 'does not enqueue a job to create the customer on the provider' do - expect { create_or_update }.not_to enqueue_job(PaymentProviderCustomers::StripeCreateJob) - end - end - - context 'when customer type is adyen' do - let(:customer_class) { PaymentProviderCustomers::AdyenCustomer } - let(:provider) { create(:adyen_provider, organization: customer.organization) } - - it 'does not generate checkout url' do - create_or_update - expect(create_service).not_to have_received(:generate_checkout_url) - end - - it 'does not enqueue a job to create the customer on the provider' do - expect { create_or_update }.not_to enqueue_job(PaymentProviderCustomers::AdyenCreateJob) - end - end - - context 'when customer type is gocardless' do - let(:customer_class) { PaymentProviderCustomers::GocardlessCustomer } - let(:provider) { create(:gocardless_provider, organization: customer.organization) } - - it 'does not generate checkout url' do - create_or_update - expect(create_service).not_to have_received(:generate_checkout_url) - end - - it 'does not enqueue a job to create the customer on the provider' do - expect { create_or_update }.not_to enqueue_job(PaymentProviderCustomers::GocardlessCreateJob) - end - end - end - end - end -end diff --git a/spec/services/payment_providers/adyen/customers/create_service_spec.rb b/spec/services/payment_providers/adyen/customers/create_service_spec.rb new file mode 100644 index 00000000000..5f1763ec0b9 --- /dev/null +++ b/spec/services/payment_providers/adyen/customers/create_service_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe PaymentProviders::Adyen::Customers::CreateService, type: :service do + let(:create_service) { described_class.new(customer:, payment_provider_id:, params:, async:) } + + let(:customer) { create(:customer) } + let(:adyen_provider) { create(:adyen_provider, organization: customer.organization) } + let(:payment_provider_id) { adyen_provider.id } + let(:params) { {provider_customer_id: "id", sync_with_provider: true} } + let(:async) { true } + + describe ".call" do + it "creates a payment_provider_customer" do + result = create_service.call + + expect(result).to be_success + expect(result.provider_customer).to be_present + expect(result.provider_customer.provider_customer_id).to eq("id") + end + + context "when no provider customer id and should create on service" do + let(:params) do + {provider_customer_id: nil, sync_with_provider: true} + end + + it "enqueues a job to create the customer on the provider" do + expect { create_service.call }.to have_enqueued_job(PaymentProviderCustomers::AdyenCreateJob) + end + end + + context "when removing the provider customer id and should create on service" do + let(:params) do + {provider_customer_id: nil, sync_with_provider: true} + end + + let(:adyen_customer) do + create( + :adyen_customer, + customer:, + payment_provider: adyen_provider + ) + end + + before { adyen_customer } + + it "updates the provider customer" do + expect do + result = create_service.call + + aggregate_failures do + expect(result).to be_success + + expect(result.provider_customer.provider_customer_id).to be_nil + end + end.not_to have_enqueued_job(PaymentProviderCustomers::AdyenCreateJob) + end + end + + context "when provider customer id is set" do + let(:params) do + {provider_customer_id: "id", sync_with_provider:} + end + + before do + allow(create_service).to receive(:generate_checkout_url).and_return(true) + allow(create_service).to receive(:create_customer_on_provider_service).and_return(true) + end + + context "when sync with provider is blank" do + let(:sync_with_provider) { nil } + let(:provider) { create(:adyen_provider, organization: customer.organization) } + + context "when provider customer exists" do + before do + create(:adyen_customer, customer:, payment_provider_id: provider.id) + end + + it "generates checkout url" do + create_service.call + expect(create_service).to have_received(:generate_checkout_url) + end + + it "does not create customer" do + create_service.call + expect(create_service).not_to have_received(:create_customer_on_provider_service) + end + end + + context "when provider customer does not exist" do + it "does not generate checkout url" do + create_service.call + expect(create_service).not_to have_received(:generate_checkout_url) + end + + it "does not create customer" do + create_service.call + expect(create_service).not_to have_received(:create_customer_on_provider_service) + end + end + end + + context "when sync with provider is true" do + let(:sync_with_provider) { true } + let(:provider) { create(:adyen_provider, organization: customer.organization) } + + it "does not generate checkout url" do + create_service.call + expect(create_service).not_to have_received(:generate_checkout_url) + end + + it "does not enqueue a job to create the customer on the provider" do + expect { create_service.call }.not_to enqueue_job(PaymentProviderCustomers::AdyenCreateJob) + end + end + end + end +end diff --git a/spec/services/payment_providers/create_customer_factory_spec.rb b/spec/services/payment_providers/create_customer_factory_spec.rb new file mode 100644 index 00000000000..566a10832ed --- /dev/null +++ b/spec/services/payment_providers/create_customer_factory_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe PaymentProviders::CreateCustomerFactory, type: :service do + subject(:new_instance) { described_class.new_instance(provider:, customer:, payment_provider_id:, params:, async:) } + + let(:customer) { create(:customer) } + let(:payment_provider_id) { create(:stripe_provider, organization: customer.organization).id } + let(:params) { {provider_customer_id: "id", sync_with_provider: true} } + let(:async) { true } + + let(:provider) { "stripe" } + + describe ".new_instance" do + it "creates an instance of the stripe service" do + expect(new_instance).to be_instance_of(PaymentProviders::Stripe::Customers::CreateService) + end + + context "when provider is adyen" do + let(:provider) { "adyen" } + let(:payment_provider_id) { create(:adyen_provider, organization: customer.organization).id } + + it "creates an instance of the adyen service" do + expect(new_instance).to be_instance_of(PaymentProviders::Adyen::Customers::CreateService) + end + end + + context "when provider is gocardless" do + let(:provider) { "gocardless" } + let(:payment_provider_id) { create(:gocardless_provider, organization: customer.organization).id } + + it "creates an instance of the gocardless service" do + expect(new_instance).to be_instance_of(PaymentProviders::Gocardless::Customers::CreateService) + end + end + end +end diff --git a/spec/services/payment_providers/gocardless/customers/create_service_spec.rb b/spec/services/payment_providers/gocardless/customers/create_service_spec.rb new file mode 100644 index 00000000000..c8edd3f1e61 --- /dev/null +++ b/spec/services/payment_providers/gocardless/customers/create_service_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe PaymentProviders::Gocardless::Customers::CreateService, type: :service do + let(:create_service) { described_class.new(customer:, payment_provider_id:, params:, async:) } + + let(:customer) { create(:customer) } + let(:gocardless_provider) { create(:gocardless_provider, organization: customer.organization) } + let(:payment_provider_id) { gocardless_provider.id } + + let(:params) do + {provider_customer_id: "id", sync_with_provider: true} + end + + let(:async) { true } + + describe ".call" do + it "creates a payment_provider_customer" do + result = create_service.call + + expect(result).to be_success + expect(result.provider_customer).to be_present + expect(result.provider_customer.provider_customer_id).to eq("id") + end + + context "when no provider customer id and should create on service" do + let(:params) do + {provider_customer_id: nil, sync_with_provider: true} + end + + it "enqueues a job to create the customer on the provider" do + expect { create_service.call }.to have_enqueued_job(PaymentProviderCustomers::GocardlessCreateJob) + end + end + + context "when removing the provider customer id and should create on service" do + let(:params) do + {provider_customer_id: nil, sync_with_provider: true} + end + + let(:gocardless_customer) do + create( + :gocardless_customer, + customer:, + payment_provider: gocardless_provider + ) + end + + before { gocardless_customer } + + it "updates the provider customer" do + expect do + result = create_service.call + + aggregate_failures do + expect(result).to be_success + + expect(result.provider_customer.provider_customer_id).to be_nil + end + end.not_to have_enqueued_job(PaymentProviderCustomers::GocardlessCreateJob) + end + end + + context "when provider customer id is set" do + let(:params) do + {provider_customer_id: "id", sync_with_provider:, provider_payment_methods: %w[card]} + end + + before do + allow(create_service).to receive(:generate_checkout_url).and_return(true) + allow(create_service).to receive(:create_customer_on_provider_service).and_return(true) + end + + context "when sync with provider is blank" do + let(:sync_with_provider) { nil } + let(:provider) { create(:gocardless_provider, organization: customer.organization) } + + context "when provider customer exists" do + before do + create(:gocardless_customer, customer:, payment_provider_id: provider.id) + end + + it "generates checkout url" do + create_service.call + expect(create_service).to have_received(:generate_checkout_url) + end + + it "does not create customer" do + create_service.call + expect(create_service).not_to have_received(:create_customer_on_provider_service) + end + end + + context "when provider customer does not exist" do + it "does not generate checkout url" do + create_service.call + expect(create_service).not_to have_received(:generate_checkout_url) + end + + it "does not create customer" do + create_service.call + expect(create_service).not_to have_received(:create_customer_on_provider_service) + end + end + end + + context "when sync with provider is true" do + let(:sync_with_provider) { true } + + it "does not generate checkout url" do + create_service.call + expect(create_service).not_to have_received(:generate_checkout_url) + end + + it "does not enqueue a job to create the customer on the provider" do + expect { create_service.call }.not_to enqueue_job(PaymentProviderCustomers::GocardlessCreateJob) + end + end + end + end +end diff --git a/spec/services/payment_providers/stripe/customers/create_service_spec.rb b/spec/services/payment_providers/stripe/customers/create_service_spec.rb new file mode 100644 index 00000000000..67cfc2676ae --- /dev/null +++ b/spec/services/payment_providers/stripe/customers/create_service_spec.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe PaymentProviders::Stripe::Customers::CreateService, type: :service do + let(:create_service) { described_class.new(customer:, payment_provider_id:, params:, async:) } + + let(:customer) { create(:customer) } + let(:stripe_provider) { create(:stripe_provider, organization: customer.organization) } + let(:payment_provider_id) { stripe_provider.id } + let(:params) { {provider_customer_id: "id", sync_with_provider: true, provider_payment_methods:} } + let(:async) { true } + + let(:provider_payment_methods) { %w[card] } + + describe ".call" do + it "creates a payment_provider_customer" do + result = create_service.call + + expect(result).to be_success + expect(result.provider_customer).to be_present + expect(result.provider_customer.provider_customer_id).to eq("id") + end + + context "when provider customer is persisted" do + before do + create( + :stripe_customer, + customer:, + payment_provider: stripe_provider, + provider_payment_methods: %w[sepa_debit] + ) + end + + context "when provider payment methods are present" do + let(:provider_payment_methods) { %w[card sepa_debit] } + + it "updates payment methods" do + result = create_service.call + + expect(result.provider_customer.provider_payment_methods).to eq(provider_payment_methods) + end + end + + context "when provider payment methods are not present" do + let(:provider_payment_methods) { nil } + + it "does not update payment methods" do + result = create_service.call + + expect(result.provider_customer.provider_payment_methods).to eq(%w[sepa_debit]) + end + end + end + + context "when provider customer is not persisted" do + context "when provider payment methods are present" do + let(:provider_payment_methods) { %w[card sepa_debit] } + + it "saves payment methods" do + result = create_service.call + + expect(result.provider_customer.provider_payment_methods).to eq(provider_payment_methods) + end + end + + context "when provider payment methods are not present" do + let(:provider_payment_methods) { nil } + + it "saves default payment method" do + result = create_service.call + + expect(result.provider_customer.provider_payment_methods).to eq(%w[card]) + end + end + end + + context "when no provider customer id and should create on service" do + let(:params) do + {provider_customer_id: nil, sync_with_provider: true, provider_payment_methods: %w[card]} + end + + it "enqueues a job to create the customer on the provider" do + expect { create_service.call }.to have_enqueued_job(PaymentProviderCustomers::StripeCreateJob) + end + end + + context "when removing the provider customer id and should create on service" do + let(:params) do + {provider_customer_id: nil, sync_with_provider: true} + end + + let(:stripe_customer) do + create( + :stripe_customer, + customer:, + payment_provider: stripe_provider + ) + end + + before { stripe_customer } + + it "updates the provider customer" do + expect do + result = create_service.call + + aggregate_failures do + expect(result).to be_success + + expect(result.provider_customer.provider_customer_id).to be_nil + end + end.not_to have_enqueued_job(PaymentProviderCustomers::StripeCreateJob) + end + end + + context "when provider customer id is set" do + let(:params) do + {provider_customer_id: "id", sync_with_provider:, provider_payment_methods: %w[card]} + end + + before do + allow(create_service).to receive(:generate_checkout_url).and_return(true) + allow(create_service).to receive(:create_customer_on_provider_service).and_return(true) + end + + context "when sync with provider is blank" do + let(:sync_with_provider) { nil } + + let(:provider) { create(:stripe_provider, organization: customer.organization) } + + context "when provider customer exists" do + before do + create(:stripe_customer, customer:, payment_provider_id: provider.id) + end + + it "generates checkout url" do + create_service.call + expect(create_service).to have_received(:generate_checkout_url) + end + + it "does not create customer" do + create_service.call + expect(create_service).not_to have_received(:create_customer_on_provider_service) + end + end + + context "when provider customer does not exist" do + it "does not generate checkout url" do + create_service.call + expect(create_service).not_to have_received(:generate_checkout_url) + end + + it "does not create customer" do + create_service.call + expect(create_service).not_to have_received(:create_customer_on_provider_service) + end + end + end + + context "when sync with provider is true" do + let(:sync_with_provider) { true } + let(:provider) { create(:stripe_provider, organization: customer.organization) } + + it "does not generate checkout url" do + create_service.call + expect(create_service).not_to have_received(:generate_checkout_url) + end + + it "does not enqueue a job to create the customer on the provider" do + expect { create_service.call }.not_to enqueue_job(PaymentProviderCustomers::StripeCreateJob) + end + end + end + end +end