mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-13 04:00:21 +00:00
Creating a new payment will try to complete the order, so we want to redeem any VINE voucher associated with the order first
207 lines
7.2 KiB
Ruby
207 lines
7.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Spree
|
|
module Admin
|
|
class PaymentsController < Spree::Admin::BaseController
|
|
before_action :load_order, except: [:show]
|
|
before_action :load_payment, only: [:fire, :show]
|
|
before_action :load_data
|
|
before_action :can_transition_to_payment
|
|
# We ensure that items are in stock before all screens if the order is in the Payment state.
|
|
# This way, we don't allow someone to enter credit card details for an order only to be told
|
|
# that it can't be processed.
|
|
before_action :ensure_sufficient_stock_lines
|
|
|
|
respond_to :html
|
|
|
|
def index
|
|
@payments = @order.payments
|
|
redirect_to spree.new_admin_order_payment_url(@order) if @payments.empty?
|
|
end
|
|
|
|
def new
|
|
@payment = @order.payments.build
|
|
end
|
|
|
|
def create
|
|
# Try to redeem VINE voucher first as we don't want to create a payment and complete
|
|
# the order if it fails
|
|
return redirect_to spree.admin_order_payments_path(@order) unless redeem_vine_voucher
|
|
|
|
@payment = @order.payments.build(object_params)
|
|
load_payment_source
|
|
begin
|
|
unless @payment.save
|
|
redirect_to spree.admin_order_payments_path(@order)
|
|
return
|
|
end
|
|
|
|
::Orders::WorkflowService.new(@order).complete! unless @order.completed?
|
|
|
|
authorize_stripe_sca_payment
|
|
@payment.process_offline!
|
|
flash[:success] = flash_message_for(@payment, :successfully_created)
|
|
redirect_to spree.admin_order_payments_path(@order)
|
|
rescue Spree::Core::GatewayError => e
|
|
flash[:error] = e.message.to_s
|
|
redirect_to spree.admin_order_payments_path(@order)
|
|
end
|
|
end
|
|
|
|
# When a user fires an event, take them back to where they came from
|
|
# (we can't use respond_override because Spree no longer uses respond_with)
|
|
def fire
|
|
event = params[:e]
|
|
return unless event && @payment.payment_source
|
|
|
|
# Because we have a transition method also called void, we do this to avoid conflicts.
|
|
event = "void_transaction" if event == "void"
|
|
if allowed_events.include?(event) && @payment.public_send("#{event}!")
|
|
flash[:success] = t(:payment_updated)
|
|
else
|
|
flash[:error] = t(:cannot_perform_operation)
|
|
end
|
|
rescue StandardError => e
|
|
logger.error e.message
|
|
Bugsnag.notify(e)
|
|
flash[:error] = e.message
|
|
ensure
|
|
redirect_to request.referer
|
|
end
|
|
|
|
def paypal_refund
|
|
if request.get?
|
|
if @payment.source.state == 'refunded'
|
|
flash[:error] = Spree.t(:already_refunded, scope: 'paypal')
|
|
redirect_to admin_order_payment_path(@order, @payment)
|
|
end
|
|
elsif request.post?
|
|
response = @payment.payment_method.refund(@payment, params[:refund_amount])
|
|
if response.success?
|
|
flash[:success] = Spree.t(:refund_successful, scope: 'paypal')
|
|
redirect_to admin_order_payments_path(@order)
|
|
else
|
|
flash.now[:error] = Spree.t(:refund_unsuccessful, scope: 'paypal') +
|
|
" (#{response.errors.first.long_message})"
|
|
render
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def load_payment_source
|
|
if @payment.payment_method.is_a?(Spree::Gateway) &&
|
|
@payment.payment_method.payment_profiles_supported? &&
|
|
params[:card].present? &&
|
|
(params[:card] != 'new')
|
|
@payment.source = CreditCard.find_by(id: params[:card])
|
|
end
|
|
end
|
|
|
|
def object_params
|
|
if params[:payment] &&
|
|
params[:payment_source] &&
|
|
source_params = params.delete(:payment_source)[params[:payment][:payment_method_id]]
|
|
params[:payment][:source_attributes] = source_params
|
|
end
|
|
|
|
params.require(:payment).permit(
|
|
:amount, :payment_method_id,
|
|
source_attributes: ::PermittedAttributes::PaymentSource.attributes
|
|
)
|
|
end
|
|
|
|
def load_data
|
|
@amount = params[:amount] || load_order.total
|
|
|
|
# Only show payments for the order's distributor
|
|
@payment_methods = PaymentMethod.
|
|
available(:back_end).
|
|
for_distributor(@order.distributor)
|
|
|
|
@payment_method = if @payment&.payment_method
|
|
@payment.payment_method
|
|
else
|
|
@payment_methods.first
|
|
end
|
|
|
|
credit_card_ids = @order.payments.from_credit_card.pluck(:source_id).uniq
|
|
@previous_cards = CreditCard.where(id: credit_card_ids).with_payment_profile
|
|
end
|
|
|
|
# At this point admin should have passed through Customer Details step
|
|
# where order.next is called which leaves the order in payment step
|
|
#
|
|
# Orders in complete or canceled step also allows to access this controller
|
|
#
|
|
# Otherwise redirect user to that step
|
|
def can_transition_to_payment
|
|
return if @order.confirmation? || @order.payment? ||
|
|
@order.complete? || @order.canceled? || @order.resumed?
|
|
|
|
flash[:notice] = Spree.t(:fill_in_customer_info)
|
|
redirect_to spree.edit_admin_order_customer_url(@order)
|
|
end
|
|
|
|
def ensure_sufficient_stock_lines
|
|
return if !@order.payment? || @order.insufficient_stock_lines.blank?
|
|
|
|
flash[:error] = I18n.t("spree.orders.line_item.insufficient_stock",
|
|
on_hand: "0 #{out_of_stock_item_names}")
|
|
redirect_to spree.edit_admin_order_url(@order)
|
|
end
|
|
|
|
def out_of_stock_item_names
|
|
@order.insufficient_stock_lines.map do |line_item|
|
|
line_item.variant.name
|
|
end.join(", ")
|
|
end
|
|
|
|
def load_order
|
|
@order = Order.find_by!(number: params[:order_id])
|
|
authorize! action, @order
|
|
@order
|
|
end
|
|
|
|
def load_payment
|
|
@payment = Payment.find(params[:id])
|
|
end
|
|
|
|
def authorize_stripe_sca_payment
|
|
return unless @payment.payment_method.instance_of?(Spree::Gateway::StripeSCA)
|
|
|
|
OrderManagement::Order::StripeScaPaymentAuthorize.
|
|
new(@order, payment: @payment, off_session: true).call!
|
|
|
|
raise Spree::Core::GatewayError, I18n.t('authorization_failure') if @order.errors.any?
|
|
|
|
return unless @payment.requires_authorization?
|
|
|
|
raise Spree::Core::GatewayError, I18n.t('action_required')
|
|
end
|
|
|
|
def allowed_events
|
|
%w{capture void_transaction credit refund resend_authorization_email
|
|
capture_and_complete_order}
|
|
end
|
|
|
|
def redeem_vine_voucher
|
|
vine_voucher_redeemer = VineVoucherRedeemerService.new(order: @order)
|
|
if vine_voucher_redeemer.call == false
|
|
# rubocop:disable Rails/DeprecatedActiveModelErrorsMethods
|
|
flash[:error] = if vine_voucher_redeemer.errors.keys.include?(:redeeming_failed)
|
|
vine_voucher_redeemer.errors[:redeeming_failed]
|
|
else
|
|
I18n.t('checkout.errors.voucher_redeeming_error')
|
|
end
|
|
# rubocop:enable Rails/DeprecatedActiveModelErrorsMethods
|
|
return false
|
|
end
|
|
|
|
true
|
|
end
|
|
end
|
|
end
|
|
end
|