diff --git a/app/controllers/spree/api/v2/storefront/checkout_controller_decorator.rb b/app/controllers/spree/api/v2/storefront/checkout_controller_decorator.rb index 6f60d1a1..7683ecc5 100644 --- a/app/controllers/spree/api/v2/storefront/checkout_controller_decorator.rb +++ b/app/controllers/spree/api/v2/storefront/checkout_controller_decorator.rb @@ -17,6 +17,8 @@ def request_update_payment render_serialized_payload { serialize_resource(order) } else payment = find_payment(order, params[:payment_number]) + return render_error_payload('Payment is void (refunded or canceled)') if payment.void? + context = payment.request_update if context.success? && context.error_message.blank? diff --git a/app/models/spree/gateway/payway_v2.rb b/app/models/spree/gateway/payway_v2.rb index 434c487d..c55abf17 100644 --- a/app/models/spree/gateway/payway_v2.rb +++ b/app/models/spree/gateway/payway_v2.rb @@ -1,5 +1,5 @@ module Spree - class Gateway::PaywayV2 < PaymentMethod + class Gateway::PaywayV2 < ::Spree::PaymentMethod # preference :endpoint, :string # preference :return_url, :string # preference :continue_success_url, :string diff --git a/app/models/vpago/order_decorator.rb b/app/models/vpago/order_decorator.rb index 371d8f87..efb98128 100644 --- a/app/models/vpago/order_decorator.rb +++ b/app/models/vpago/order_decorator.rb @@ -10,6 +10,7 @@ def self.prepended(base) through: :line_items base.state_machine.before_transition from: :cart, do: :ensure_valid_vendor_payment_methods + base.state_machine.before_transition to: :complete, do: :generate_line_items_total_metadata end def ensure_valid_vendor_payment_methods @@ -25,30 +26,6 @@ def line_items_from_same_vendor? line_items.joins(:variant).pluck('spree_variants.vendor_id').uniq.size == 1 end - # Make sure the order confirmation is delivered when the order has been paid for. - def finalize! - # lock all adjustments (coupon promotions, etc.) - all_adjustments.each(&:close) - - # update payment and shipment(s) states, and save - updater.update_payment_state - - shipments.each do |shipment| - shipment.update!(self) - shipment.finalize! if paid? || authorized? - end - - updater.update_shipment_state - save! - updater.run_hooks - - touch :completed_at - - deliver_order_confirmation_email if !confirmation_delivered? && (paid? || authorized?) - - consider_risk - end - def required_payway_payout? line_items.any?(&:required_payway_payout?) || shipments.any?(&:required_payway_payout?) end @@ -90,29 +67,10 @@ def generate_line_items_total_metadata line_items.each(&:update_total_metadata) end - def send_confirmation_email! - return unless !confirmation_delivered? && (paid? || authorized?) - - deliver_order_confirmation_email - end - - def successful_payment - paid? || payments.any? { |p| p.after_pay_method? && p.authorized? } - end - - alias paid_or_authorized? successful_payment - - def authorized? - payments.last.authorized? - end - def order_adjustment_total adjustments.eligible.sum(:amount) end end end -if Spree::Order.included_modules.exclude?(Vpago::OrderDecorator) - Spree::Order.register_update_hook(:generate_line_items_total_metadata) - Spree::Order.prepend(Vpago::OrderDecorator) -end +Spree::Order.prepend(Vpago::OrderDecorator) if Spree::Order.included_modules.exclude?(Vpago::OrderDecorator) diff --git a/app/models/vpago/payment/vpago_payment_processing_decorator.rb b/app/models/vpago/payment/vpago_payment_processing_decorator.rb deleted file mode 100644 index 9f4f1415..00000000 --- a/app/models/vpago/payment/vpago_payment_processing_decorator.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Vpago - module Payment - module VpagoPaymentProcessingDecorator - def process! - if vpago_type? - process_with_vpago_gateway - else - super - end - end - - def cancel! - if vpago_type? - cancel_with_vpago_gateway - else - super - end - end - - # private - def vpago_type? - payment_method.is_a?(Spree::Gateway::Payway) || - payment_method.is_a?(Spree::Gateway::WingSdk) || - payment_method.is_a?(Spree::Gateway::Acleda) || - payment_method.is_a?(Spree::Gateway::PaywayV2) - end - - def cancel_with_vpago_gateway - response = payment_method.cancel(transaction_id) - handle_response(response, :void, :failure) - end - - def process_with_vpago_gateway - amount ||= money.money - started_processing! - - response = payment_method.process( - amount, - source, - gateway_options - ) - handle_response(response, :started_processing, :failure) - end - end - end -end - -Spree::Payment.include(Vpago::Payment::VpagoPaymentProcessingDecorator) diff --git a/app/models/vpago/payment_decorator.rb b/app/models/vpago/payment_decorator.rb index fbbce9dd..09f731f1 100644 --- a/app/models/vpago/payment_decorator.rb +++ b/app/models/vpago/payment_decorator.rb @@ -3,16 +3,6 @@ module PaymentDecorator def self.prepended(base) base.has_many :payouts, class_name: 'Spree::Payout', inverse_of: :payment base.after_create -> { Vpago::PayoutsGenerator.new(self).call }, if: :should_generate_payouts? - base.after_update :capture_pre_auth, if: :state_changed_to_complete? - base.after_update :cancel_pre_auth, if: :state_changed_to_failed? - end - - def state_changed_to_complete? - saved_change_to_state? && state == 'completed' - end - - def state_changed_to_failed? - saved_change_to_state? && state == 'failed' end def should_generate_payouts? @@ -23,41 +13,12 @@ def support_payout? payment_method.support_payout? end - # On the first call, everything works. The order is transitioned to complete and one Spree::Payment, - # which redirect the payment. But, after making the same call again, - # for instance because the payment wasn't completed or failed, - # another Spree::Payment is created but without a payment_url. So, if a consumer, - # for whatever reason, failed to complete the first payment, it would not be possible try again. - # This also meant that any consecutive Spree::Payment would not have a payment_url. The consumer is stuck - - def build_source - return unless new_record? - - return unless source_attributes.present? && source.blank? && payment_method.try(:payment_source_class) - - self.source = payment_method.payment_source_class.new(source_attributes) - source.payment_method_id = payment_method.id - source.user_id = order.user_id if order - - # Spree will not process payments if order is completed. - # We should call process! for completed orders to create a the gateway payment. - process! if order.completed? - end - def request_update updater = payment_method.payment_request_updater.new(self, { ignore_on_failed: true }) updater.call updater end - def authorized? - if source.is_a? Spree::VpagoPaymentSource - pending? - else - false - end - end - def payment_url return unless payment_method.type_payway_v2? diff --git a/lib/vpago/payment_status_marker.rb b/lib/vpago/payment_status_marker.rb index c31748e3..bdd905ab 100644 --- a/lib/vpago/payment_status_marker.rb +++ b/lib/vpago/payment_status_marker.rb @@ -47,47 +47,35 @@ def update_payment_and_order else transition_to_failed! end - - order_updater end def transition_to_paid! complete_payment! complete_order! - confirm_payouts! end - def transition_to_failed! - @payment.failure! unless @payment.failed? - @payment.order.update(state: 'payment') - - notify_failed_payment + def complete_payment! + @payment.complete! end - def order_updater - @payment.order.update_with_updater! - end + def complete_order! + context = Spree::Checkout::Complete.new.call(order: @payment.order) - def complete_payment! - return if @payment.completed? - - # not follow state machine rule when it manual - if @options[:updated_by_user_id].present? - ApplicationRecord.transaction do - @payment.state_changes.create!(previous_state: @payment.state, next_state: 'completed', name: 'payment') - @payment.update(state: 'completed') - end + if context.success? + @payment.capture_pre_auth + confirm_payouts! else - @payment.complete! + @error_message = context.error.value.full_messages.join(', ') + @payment.cancel_pre_auth + @payment.void! end end - def complete_order! - return if @payment.order.completed? + def transition_to_failed! + @payment.cancel_pre_auth + @payment.void! - order = @payment.order - order.finalize! - order.update(state: 'complete', completed_at: Time.zone.now) + notify_failed_payment end def payout_confirmed? diff --git a/lib/vpago/payway_v2/payment_request_updater.rb b/lib/vpago/payway_v2/payment_request_updater.rb index 59d5039e..f38b6d9f 100644 --- a/lib/vpago/payway_v2/payment_request_updater.rb +++ b/lib/vpago/payway_v2/payment_request_updater.rb @@ -4,22 +4,11 @@ class PaymentRequestUpdater < ::Vpago::PaymentRequestUpdater def call return if @payment.order.paid? - if items_eligible? - process_payment_status - else - mark_items_as_ineligible - end + process_payment_status end private - def mark_items_as_ineligible - @error_message = 'Items are not eligible due to insufficient stock' - marker_options = @options.merge(status: false, description: @error_message) - marker = ::Vpago::PaymentStatusMarker.new(@payment, marker_options) - marker.call - end - def process_payment_status checker = check_payway_status @@ -38,9 +27,14 @@ def mark_payment_as_success(checker) payway_v2_response: checker.json_response, payout_total: checker.payout_total } + marker_options = @options.merge(checker_result) marker = ::Vpago::PaymentStatusMarker.new(@payment, marker_options) marker.call + + return unless marker.error_message&.present? + + @error_message = marker.error_message end def mark_payment_as_failed(error_message) @@ -56,10 +50,6 @@ def check_payway_status trans_status.call trans_status end - - def items_eligible? - @payment&.order&.line_items&.all?(&:sufficient_stock?) == true - end end end end diff --git a/spec/lib/vpago/payment_status_marker_spec.rb b/spec/lib/vpago/payment_status_marker_spec.rb index 99fc5424..ef88b4de 100644 --- a/spec/lib/vpago/payment_status_marker_spec.rb +++ b/spec/lib/vpago/payment_status_marker_spec.rb @@ -11,6 +11,10 @@ before do allow(checker).to receive(:check_transaction_url).and_return('https://checkout-sandbox.payway.com.kh/api/payment-gateway/v1/payments/check-transaction') VCR.use_cassette('payway_v2_check_transaction_status_0') { checker.call } + + # skip validation + allow(payment.order).to receive(:ensure_line_items_present).and_return(true) + allow(payment.order).to receive(:delivery_required?).and_return(false) end describe '#call' do diff --git a/spec/lib/vpago/payway_v2/payment_request_updater_spec.rb b/spec/lib/vpago/payway_v2/payment_request_updater_spec.rb deleted file mode 100644 index d8a732c8..00000000 --- a/spec/lib/vpago/payway_v2/payment_request_updater_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'spec_helper' - -RSpec.describe Vpago::PaywayV2::PaymentRequestUpdater, type: :model do - let(:gateway) { create(:payway_v2_gateway, auto_capture: true) } - let(:payment_source) { create(:payway_payment_source, payment_method: gateway) } - let(:user) { create(:user) } - let(:order) { OrderWalkthrough.up_to(:payment) } - let(:payment) { create(:payway_payment, payment_method: gateway, source: payment_source, order: order , state: 'processing') } - - describe 'ineligible items' do - it 'marks the payment as failed due to insufficient stock' do - allow(order.line_items).to receive(:all?).and_return(false) - options = { updated_by_user_id: user.id } - updater = Vpago::PaywayV2::PaymentRequestUpdater.new(payment, options) - updater.call - - expect(payment.state).to eq('failed') - expect(payment.source.payment_description).to eq('Items are not eligible due to insufficient stock') - end - end -end