mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-11 03:40:20 +00:00
Add Orders::CustomerCreditService.refund
It will be used to credit the customer any fund from an order in credit_owed state
This commit is contained in:
@@ -10,15 +10,47 @@ module Orders
|
||||
add_payment_with_credit if credit_available?
|
||||
end
|
||||
|
||||
def refund # rubocop:disable Metrics/AbcSize
|
||||
if order.payment_state != "credit_owed"
|
||||
return Response.new(
|
||||
success: false, message: I18n.t(:no_credit_owed, scope: translation_scope)
|
||||
)
|
||||
end
|
||||
|
||||
if credit_payment_method.nil?
|
||||
error_message = I18n.t(:credit_payment_method_missing, scope: translation_scope)
|
||||
log_error(error_message)
|
||||
return Response.new(success: false, message: error_message)
|
||||
end
|
||||
|
||||
amount = order.new_outstanding_balance
|
||||
order.customer.with_lock do
|
||||
payment = order.payments.create!( payment_method: credit_payment_method, amount: amount,
|
||||
state: "completed", skip_source_validation: true)
|
||||
|
||||
options = { customer_id: order.customer_id, payment_id: payment.id,
|
||||
order_number: order.number }
|
||||
response = credit_payment_method.void((-1 * amount * 100).round, nil, options)
|
||||
|
||||
raise response.message if response.failure?
|
||||
|
||||
Response.new(success: true, message: I18n.t(:refund_sucessful, scope: translation_scope))
|
||||
end
|
||||
rescue StandardError => e
|
||||
# Even though the transaction rolled back, the order still have a payment in memory,
|
||||
# so we reload the payments so the payment doesn't get saved later on
|
||||
order.payments.reload
|
||||
log_error(e)
|
||||
Response.new(success: false, message: e.to_s)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :order
|
||||
|
||||
def add_payment_with_credit
|
||||
credit_payment_method = order.distributor.payment_methods.customer_credit
|
||||
|
||||
if credit_payment_method.nil?
|
||||
error_message = "Customer credit payment method is missing, please check configuration"
|
||||
error_message = I18n.t(:credit_payment_method_missing, scope: translation_scope)
|
||||
log_error(error_message)
|
||||
return
|
||||
end
|
||||
@@ -50,9 +82,34 @@ module Orders
|
||||
@available_credit ||= order.customer.customer_account_transactions.last&.balance || 0.00
|
||||
end
|
||||
|
||||
def credit_payment_method
|
||||
order.distributor.payment_methods.customer_credit
|
||||
end
|
||||
|
||||
def log_error(error)
|
||||
Rails.logger.error("Orders::CustomerCreditService: #{error}")
|
||||
Alert.raise(error)
|
||||
end
|
||||
|
||||
def translation_scope
|
||||
"orders.customer_credit_service"
|
||||
end
|
||||
|
||||
class Response
|
||||
attr_reader :message
|
||||
|
||||
def initialize(success:, message:)
|
||||
@success = success
|
||||
@message = message
|
||||
end
|
||||
|
||||
def success?
|
||||
@success
|
||||
end
|
||||
|
||||
def failure?
|
||||
!success?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5209,3 +5209,8 @@ en:
|
||||
not_enough_credit_available: Not enough credit available
|
||||
customer_account_transaction:
|
||||
account_creation: Account creation
|
||||
orders:
|
||||
customer_credit_service:
|
||||
no_credit_owed: No credit owed
|
||||
credit_payment_method_missing: Customer credit payment method is missing, please check configuration
|
||||
refund_sucessful: Refund successful!
|
||||
|
||||
@@ -5,15 +5,15 @@ require 'spec_helper'
|
||||
RSpec.describe Orders::CustomerCreditService do
|
||||
subject { described_class.new(order) }
|
||||
|
||||
let(:order) {
|
||||
create(:order_with_line_items, line_items_count: 1, distributor:, order_cycle:,
|
||||
customer: create(:customer, enterprise: distributor))
|
||||
}
|
||||
let(:distributor) { create(:distributor_enterprise) }
|
||||
let(:order_cycle) { create(:order_cycle, distributors: [distributor]) }
|
||||
let(:credit_payment_method) { order.distributor.payment_methods.customer_credit }
|
||||
|
||||
describe "#apply" do
|
||||
let(:order) {
|
||||
create(:order_with_line_items, line_items_count: 1, distributor:, order_cycle:,
|
||||
customer: create(:customer, enterprise: distributor))
|
||||
}
|
||||
it "adds a customer credit payment to the order" do
|
||||
# Add credit
|
||||
create(
|
||||
@@ -126,4 +126,110 @@ RSpec.describe Orders::CustomerCreditService do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#refund" do
|
||||
let(:order) { create(:completed_order_with_fees) }
|
||||
|
||||
before do
|
||||
# Overpay to put the order payment state in "credit_owed"
|
||||
payment = order.payments.first
|
||||
payment.complete!
|
||||
payment.update(amount: 48.00)
|
||||
order.update_order!
|
||||
end
|
||||
|
||||
it "adds a customer credit payment to the order" do
|
||||
expect { subject.refund }.to change { order.payments.count }.by(1)
|
||||
|
||||
last_payment = order.payments.reload.order(:id).last
|
||||
expect(last_payment.payment_method).to eq(credit_payment_method)
|
||||
expect(last_payment.amount).to eq(-12.00)
|
||||
expect(last_payment.state).to eq("completed")
|
||||
|
||||
expect(order.payment_state).to eq("paid")
|
||||
end
|
||||
|
||||
it "adds an entry in customer account transaction" do
|
||||
subject.refund
|
||||
|
||||
last_transaction = order.customer.customer_account_transactions.last
|
||||
expect(last_transaction.payment_method).to eq(credit_payment_method)
|
||||
expect(last_transaction.amount).to eq(12.00)
|
||||
end
|
||||
|
||||
it "returns sucessful reponse" do
|
||||
response = subject.refund
|
||||
|
||||
expect(response.success?).to eq(true)
|
||||
expect(response.message).to eq("Refund successful!")
|
||||
end
|
||||
|
||||
context "when order payment state is not 'credit_owed'" do
|
||||
before do
|
||||
order.update(payment_state: "paid")
|
||||
end
|
||||
|
||||
it "does nothing" do
|
||||
expect { subject.refund }.not_to change { order.payments.count }
|
||||
end
|
||||
|
||||
it "returns a failed respond" do
|
||||
response = subject.refund
|
||||
|
||||
expect(response.failure?).to eq(true)
|
||||
expect(response.message).to eq("No credit owed")
|
||||
end
|
||||
end
|
||||
|
||||
context "when credit payment method is missing" do
|
||||
before do
|
||||
credit_payment_method.destroy!
|
||||
end
|
||||
|
||||
it "logs the error" do
|
||||
expect(Alert).to receive(:raise).with(
|
||||
"Customer credit payment method is missing, please check configuration"
|
||||
)
|
||||
subject.refund
|
||||
end
|
||||
|
||||
it "doesn't create a credit payment" do
|
||||
expect { subject.refund }.not_to change { order.payments.count }
|
||||
end
|
||||
|
||||
it "returns a failed response" do
|
||||
response = subject.refund
|
||||
|
||||
expect(response.failure?).to be(true)
|
||||
expect(response.message).to eq(
|
||||
"Customer credit payment method is missing, please check configuration"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when payment creation fails" do
|
||||
before do
|
||||
failed_response = ActiveMerchant::Billing::Response.new(false, "Void error")
|
||||
allow_any_instance_of(Spree::PaymentMethod::CustomerCredit).to receive(:void)
|
||||
.and_return(failed_response)
|
||||
end
|
||||
|
||||
it "logs the error" do
|
||||
expect(Alert).to receive(:raise).with(RuntimeError)
|
||||
subject.refund
|
||||
end
|
||||
|
||||
it "doesn't create a credit payment" do
|
||||
# We use `length` to check the payments in memory
|
||||
expect { subject.refund }.not_to change { order.payments.length }
|
||||
end
|
||||
|
||||
it "returns a failed response" do
|
||||
response = subject.refund
|
||||
|
||||
expect(response.failure?).to eq(true)
|
||||
expect(response.message).to eq(RuntimeError.new("Void error").to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user