diff --git a/app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb b/app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb index ea205fb2..1e01f49b 100644 --- a/app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb +++ b/app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb @@ -22,16 +22,25 @@ def process errors << { message: 'Order number param missing' } && (return self) unless order_number if (order = Spree::Order.find_by(number: order_number)) - upsert_order_captures(order, capture) - payments = order.flow_io_payments - map_payment_captures_to_spree(order, payments) if payments.present? - order + if order.payments.any? + store_payment_capture(order, capture) + else + FlowcommerceSpree::UpdatePaymentCaptureWorker.perform_in(1.minute, order.number, capture) + order + end else errors << { message: "Order #{order_number} not found" } self end end + def store_payment_capture(order, capture) + upsert_order_captures(order, capture) + payments = order.flow_io_payments + map_payment_captures_to_spree(order, payments) if payments.present? + order + end + private def upsert_order_captures(order, capture) diff --git a/app/workers/flowcommerce_spree/flow_io_worker.rb b/app/workers/flowcommerce_spree/flow_io_worker.rb new file mode 100644 index 00000000..2a60b789 --- /dev/null +++ b/app/workers/flowcommerce_spree/flow_io_worker.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module FlowcommerceSpree + class FlowIoWorker + include Sidekiq::Worker + + sidekiq_retries_exhausted do |message, exception| + Rails.logger.warn("[!] #{self.class} max attempts reached: #{message} - #{exception}") + notification_setting = FlowcommerceSpree::Config.notification_setting + return unless notification_setting[:slack].present? + + slack_message = "[#{Rails.env}] #{message}" + Slack_client.chat_postMessage(channel: notification_setting[:slack][:channel], text: slack_message) + end + end +end diff --git a/app/workers/flowcommerce_spree/import_item_worker.rb b/app/workers/flowcommerce_spree/import_item_worker.rb index 57001c1c..ecf2ccb4 100644 --- a/app/workers/flowcommerce_spree/import_item_worker.rb +++ b/app/workers/flowcommerce_spree/import_item_worker.rb @@ -1,19 +1,9 @@ # frozen_string_literal: true module FlowcommerceSpree - class ImportItemWorker - include Sidekiq::Worker + class ImportItemWorker < FlowIoWorker sidekiq_options retry: 3, queue: :flow_io - sidekiq_retries_exhausted do |message, exception| - Rails.logger.warn("[!] FlowcommerceSpree::ImportItemWorker max attempts reached: #{message} - #{exception}") - notification_setting = FlowcommerceSpree::Config.notification_setting - return unless notification_setting[:slack].present? - - slack_message = "[#{Rails.env}] #{message}" - Slack_client.chat_postMessage(channel: notification_setting[:slack][:channel], text: slack_message) - end - def perform(variant_sku) variant = Spree::Variant.find_by sku: variant_sku return unless variant diff --git a/app/workers/flowcommerce_spree/update_payment_capture_worker.rb b/app/workers/flowcommerce_spree/update_payment_capture_worker.rb new file mode 100644 index 00000000..f90ee392 --- /dev/null +++ b/app/workers/flowcommerce_spree/update_payment_capture_worker.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module FlowcommerceSpree + class UpdatePaymentCaptureWorker < FlowIoWorker + sidekiq_options retry: 3, queue: :flow_io + + def perform(order_number, capture = {}) + order = Spree::Order.find_by number: order_number + raise 'Order has no payments' if order.payments.empty? + + FlowcommerceSpree::Webhooks::CaptureUpsertedV2.new({ capture: capture }.as_json) + .store_payment_capture(order, capture) + end + end +end diff --git a/spec/services/flowcommerce_spree/webhooks/capture_upserted_v2_spec.rb b/spec/services/flowcommerce_spree/webhooks/capture_upserted_v2_spec.rb index b4836fff..18ac7ce5 100644 --- a/spec/services/flowcommerce_spree/webhooks/capture_upserted_v2_spec.rb +++ b/spec/services/flowcommerce_spree/webhooks/capture_upserted_v2_spec.rb @@ -121,93 +121,108 @@ expect(instance).to receive(:upsert_order_captures).with(order, data['capture']) end - context 'and the order contains no flow_io payments' do - it 'returns the Spree::Order with upserted captures, not mapping captures to Spree' do - expect(instance).not_to receive(:map_payment_captures_to_spree) + context 'when order has a payment assocaited' do + let!(:payment) { create(:payment, order: order, payment_method_id: gateway.id) } - result = instance.process + context 'and the order contains no flow_io payments' do + it 'returns the Spree::Order with upserted captures, not mapping captures to Spree' do + expect(instance).not_to receive(:map_payment_captures_to_spree) - expect_order_with_capture(result, 'succeeded') + result = instance.process + + expect_order_with_capture(result, 'succeeded') + end end - end - context 'and the order contains flow_io payments' do - let(:flow_payment) { build(:flow_order_payment, reference: order_auth.id) } - let(:zone) { create(:germany_zone, :with_flow_data) } + context 'and the order contains flow_io payments' do + let(:flow_payment) { build(:flow_order_payment, reference: order_auth.id) } + let(:zone) { create(:germany_zone, :with_flow_data) } + let(:payment_amount) { capture.amount } - [Time.now.utc, nil].each do |timestamp| - context "and the order is #{timestamp ? 'completed' : 'not_completed'}" do - let(:order) { create(:order, zone: zone, completed_at: timestamp) } - let(:finalize) { timestamp ? false : true } + [Time.now.utc, nil].each do |timestamp| + context "and the order is #{timestamp ? 'completed' : 'not_completed'}" do + let(:order) { create(:order, zone: zone, completed_at: timestamp) } + let(:finalize) { timestamp ? false : true } - before do - order.flow_data['order']['payments'] = [flow_payment] - order.update_column(:meta, order.meta.to_json) + before do + order.flow_data['order']['payments'] = [flow_payment] + order.update_column(:meta, order.meta.to_json) - expect(instance).to receive(:map_payment_captures_to_spree) - end + expect(instance).to receive(:map_payment_captures_to_spree) + end - context 'and received capture is successful' do - context 'and capture authorization matches flow_io payment authorization' do - context 'and a Spree::Payment with received capture authorization exists' do - let(:payment_amount) { capture.amount } - let!(:payment) do - create(:payment, - payment_method_id: gateway.id, amount: payment_amount, response_code: order_auth.id) - end + context 'and received capture is successful' do + context 'and capture authorization matches flow_io payment authorization' do + context 'and a Spree::Payment with received capture authorization exists' do + let(:payment_amount) { capture.amount } + let!(:payment) do + create(:payment, + order: order, + payment_method_id: gateway.id, + amount: payment_amount, + response_code: order_auth.id) + end - context 'and no Spree::PaymentCaptureEvent exists for this payment' do - before { Spree::PaymentCaptureEvent.destroy_all } + context 'and no Spree::PaymentCaptureEvent exists for this payment' do + before { Spree::PaymentCaptureEvent.destroy_all } - context 'and payment state is not complete' do - context 'and payment amount is not bigger than capture amount' do - it 'creates PaymentCaptureEvent, completes payment, returns order with upserted captures' do - expect(instance).to receive(:captured_payment) - expect_any_instance_of(Spree::Payment).to receive(:complete) - expect_order_finalize(order_finalize: finalize) + context 'and payment state is not complete' do + context 'and payment amount is not bigger than capture amount' do + it 'creates PaymentCaptureEvent, completes payment, returns order with upserted captures' do + expect(instance).to receive(:captured_payment) + expect_any_instance_of(Spree::Payment).to receive(:complete) + expect_order_finalize(order_finalize: finalize) - result = nil - expect { result = instance.process } - .to change { Spree::PaymentCaptureEvent.count }.from(0).to(1) + result = nil + expect { result = instance.process } + .to change { Spree::PaymentCaptureEvent.count }.from(0).to(1) - created_capture_event = Spree::PaymentCaptureEvent.first + created_capture_event = Spree::PaymentCaptureEvent.first - expect(created_capture_event.amount).to eql(capture.amount) - expect(created_capture_event.flow_data['id']).to eql(capture.id) - expect(payment.reload.state).to eql('completed') - expect_order_with_capture(result, 'succeeded') + expect(created_capture_event.amount).to eql(capture.amount) + expect(created_capture_event.flow_data['id']).to eql(capture.id) + expect(payment.reload.state).to eql('completed') + expect_order_with_capture(result, 'succeeded') + end end - end - context 'and payment amount is bigger than capture amount' do - let(:payment_amount) { capture.amount + 1 } + context 'and payment amount is bigger than capture amount' do + let(:payment_amount) { capture.amount + 1 } - it 'creates a PaymentCaptureEvent, and returns order with upserted captures' do - expect(instance).to receive(:captured_payment) - expect_any_instance_of(Spree::Payment).not_to receive(:complete) - expect(FlowcommerceSpree::OrderUpdater).not_to receive(:new) - expect_any_instance_of(FlowcommerceSpree::OrderUpdater).not_to receive(:finalize_order) + it 'creates a PaymentCaptureEvent, and returns order with upserted captures' do + expect(instance).to receive(:captured_payment) + expect_any_instance_of(Spree::Payment).not_to receive(:complete) + expect(FlowcommerceSpree::OrderUpdater).not_to receive(:new) + expect_any_instance_of(FlowcommerceSpree::OrderUpdater).not_to receive(:finalize_order) - result = nil - expect { result = instance.process } - .to change { Spree::PaymentCaptureEvent.count }.from(0).to(1) + result = nil + expect { result = instance.process } + .to change { Spree::PaymentCaptureEvent.count }.from(0).to(1) - created_capture_event = Spree::PaymentCaptureEvent.first + created_capture_event = Spree::PaymentCaptureEvent.first - expect(created_capture_event.amount).to eql(capture.amount) - expect(created_capture_event.flow_data['id']).to eql(capture.id) - expect(payment.reload.state).to eql('checkout') - expect_order_with_capture(result, 'succeeded') + expect(created_capture_event.amount).to eql(capture.amount) + expect(created_capture_event.flow_data['id']).to eql(capture.id) + expect(payment.reload.state).to eql('checkout') + expect_order_with_capture(result, 'succeeded') + end end end end - end - context 'and a Spree::PaymentCaptureEvent for this payment already exists' do - let!(:capture_event) do - create(:payment_capture_event, payment_id: payment.id, flow_data: { id: capture.id }) + context 'and a Spree::PaymentCaptureEvent for this payment already exists' do + let!(:capture_event) do + create(:payment_capture_event, payment_id: payment.id, flow_data: { id: capture.id }) + end + + it 'returns the Spree::Order with upserted captures, not creating a Spree::PaymentCaptureEvent' do + result = expect_no_new_capture_events(order_finalize: finalize) + expect_order_with_capture(result, 'succeeded') + end end + end + context 'and no Spree::Payment with received capture authorization exists' do it 'returns the Spree::Order with upserted captures, not creating a Spree::PaymentCaptureEvent' do result = expect_no_new_capture_events(order_finalize: finalize) expect_order_with_capture(result, 'succeeded') @@ -215,7 +230,9 @@ end end - context 'and no Spree::Payment with received capture authorization exists' do + context 'and capture authorization does not match flow_io payment authorization' do + let(:flow_payment) { build(:flow_order_payment, reference: 'wrong auth') } + it 'returns the Spree::Order with upserted captures, not creating a Spree::PaymentCaptureEvent' do result = expect_no_new_capture_events(order_finalize: finalize) expect_order_with_capture(result, 'succeeded') @@ -223,28 +240,26 @@ end end - context 'and capture authorization does not match flow_io payment authorization' do - let(:flow_payment) { build(:flow_order_payment, reference: 'wrong auth') } + context 'and received capture is not successful' do + let(:data) { { 'capture' => Oj.load(failed_capture.to_json) } } - it 'returns the Spree::Order with upserted captures, not creating a Spree::PaymentCaptureEvent' do + it 'returns the Spree::Order with upserted captures' do result = expect_no_new_capture_events(order_finalize: finalize) - expect_order_with_capture(result, 'succeeded') + expect_order_with_capture(result, 'failed') end end end - - context 'and received capture is not successful' do - let(:data) { { 'capture' => Oj.load(failed_capture.to_json) } } - - it 'returns the Spree::Order with upserted captures' do - result = expect_no_new_capture_events(order_finalize: finalize) - expect_order_with_capture(result, 'failed') - end - end end end end end + + context 'when order has no payments assocaited' do + it 'delays order capture' do + instance.process + expect(FlowcommerceSpree::UpdatePaymentCaptureWorker).to have_enqueued_sidekiq_job(order.number, anything) + end + end end end end diff --git a/spec/workers/update_payment_capture_worker_spec.rb b/spec/workers/update_payment_capture_worker_spec.rb new file mode 100644 index 00000000..7aab3c9b --- /dev/null +++ b/spec/workers/update_payment_capture_worker_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe FlowcommerceSpree::UpdatePaymentCaptureWorker, type: :worker do + let(:gateway) { create(:flow_io_gateway) } + let(:order) { create(:order) } + subject(:job) { described_class.new } + + describe '#perform' do + context 'when order has payments' do + let!(:payment) { create(:payment, order: order, payment_method_id: gateway.id) } + + it 'calls CaptureUpsertedV2#store_payment_capture method' do + expect_any_instance_of(FlowcommerceSpree::Webhooks::CaptureUpsertedV2).to(receive(:store_payment_capture)) + job.perform(order.number, anything) + end + end + + context 'when order has no payments' do + it 'does not call CaptureUpsertedV2#store_payment_capture method' do + expect_any_instance_of(FlowcommerceSpree::Webhooks::CaptureUpsertedV2).not_to(receive(:store_payment_capture)) + expect { job.perform(order.number, anything) }.to raise_error 'Order has no payments' + end + end + end +end