mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-11 03:40:20 +00:00
Automatically use credit at checkout when available
This only cover the ideal scenario, error handling will be added later
This commit is contained in:
@@ -31,6 +31,11 @@ class CheckoutController < BaseController
|
||||
check_step
|
||||
end
|
||||
|
||||
if payment_step? || summary_step?
|
||||
credit_payment_method = @order.distributor.payment_methods.customer_credit
|
||||
@paid_with_credit = @order.payments.find_by(payment_method: credit_payment_method)&.amount
|
||||
end
|
||||
|
||||
return if available_shipping_methods.any?
|
||||
|
||||
flash[:error] = I18n.t('checkout.errors.no_shipping_methods_available')
|
||||
@@ -113,7 +118,7 @@ class CheckoutController < BaseController
|
||||
@selected_payment_method ||= Checkout::PaymentMethodFetcher.new(@order).call
|
||||
end
|
||||
|
||||
def update_order
|
||||
def update_order # rubocop:disable Metrics/CyclomaticComplexity
|
||||
return if params[:confirm_order] || @order.errors.any?
|
||||
|
||||
# Checking if shipping method updated before @order get updated. We can't use this guard
|
||||
@@ -121,6 +126,9 @@ class CheckoutController < BaseController
|
||||
shipping_method_updated = @order.shipping_method&.id != params[:shipping_method_id].to_i
|
||||
|
||||
@order.select_shipping_method(params[:shipping_method_id])
|
||||
|
||||
add_payment_with_credit if credit_available? && details_step?
|
||||
|
||||
@order.update(order_params)
|
||||
# We need to update voucher to take into account:
|
||||
# * when moving away from "details" step : potential change in shipping method fees
|
||||
@@ -139,6 +147,29 @@ class CheckoutController < BaseController
|
||||
VoucherAdjustmentsService.new(@order).update
|
||||
end
|
||||
|
||||
def credit_available?
|
||||
return false if @order.customer.nil?
|
||||
|
||||
available_credit > 0
|
||||
end
|
||||
|
||||
def add_payment_with_credit
|
||||
credit_payment_method = @order.distributor.payment_methods.customer_credit
|
||||
|
||||
return if @order.payments.where(payment_method: credit_payment_method).exists?
|
||||
|
||||
amount = [available_credit, @order.total].min
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
payment = @order.payments.create!(payment_method: credit_payment_method, amount:)
|
||||
payment.internal_purchase!
|
||||
end
|
||||
end
|
||||
|
||||
def available_credit
|
||||
@available_credit ||= @order.customer.customer_account_transactions.last&.balance || 0.00
|
||||
end
|
||||
|
||||
def validate_current_step
|
||||
Checkout::Validation.new(@order, params).call && @order.errors.empty?
|
||||
end
|
||||
|
||||
@@ -28,6 +28,8 @@ module Checkout
|
||||
def validate_payment
|
||||
return true if params.dig(:order, :payments_attributes, 0, :payment_method_id).present?
|
||||
return true if order.zero_priced_order?
|
||||
# No payment required, it's usually due to the order being paid by customer credit
|
||||
return true if order.outstanding_balance.zero?
|
||||
|
||||
order.errors.add :payment_method, I18n.t('checkout.errors.select_a_payment_method')
|
||||
end
|
||||
|
||||
@@ -8,10 +8,17 @@
|
||||
.checkout-title
|
||||
= t("checkout.step2.payment_method.title")
|
||||
|
||||
- if @order.zero_priced_order?
|
||||
- if @order.zero_priced_order? || @order.outstanding_balance.zero?
|
||||
%h3= t(:no_payment_required)
|
||||
= hidden_field_tag "order[payments_attributes][][amount]", 0
|
||||
- if @order.zero_priced_order?
|
||||
= hidden_field_tag "order[payments_attributes][][amount]", 0
|
||||
- if @paid_with_credit
|
||||
= t(:credit_used, amount: Spree::Money.new(@paid_with_credit))
|
||||
|
||||
- else
|
||||
- if @paid_with_credit
|
||||
= t(:credit_used, amount: Spree::Money.new(@paid_with_credit))
|
||||
|
||||
- selected_payment_method = @order.payments&.with_state(:checkout)&.first&.payment_method_id
|
||||
- selected_payment_method ||= available_payment_methods[0].id if available_payment_methods.length == 1
|
||||
- available_payment_methods.each do |payment_method|
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
.summary-right{ "data-controller": "sticky", "data-sticky-target": "container" }
|
||||
.summary-right-line.total
|
||||
.summary-right-line-label= t :order_total_price
|
||||
.summary-right-line-value#order_total= @order.display_total.to_html
|
||||
|
||||
.summary-right-line-value#order_total= @order.display_outstanding_balance.to_html
|
||||
|
||||
.summary-right-line
|
||||
.summary-right-line-label= t :order_produce
|
||||
@@ -78,6 +79,11 @@
|
||||
.summary-right-line-label= t :order_includes_tax
|
||||
.summary-right-line-value#tax-row= display_checkout_tax_total(@order)
|
||||
|
||||
- if @paid_with_credit.present?
|
||||
.summary-right-line
|
||||
.summary-right-line-label= t :customer_credit
|
||||
.summary-right-line-value#customer-credit= Spree::Money.new(-1 * @paid_with_credit).to_html
|
||||
|
||||
.checkout-submit
|
||||
- if any_terms_required?(@order.distributor)
|
||||
= render partial: "terms_and_conditions", locals: { f: f }
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
%h5.not-paid
|
||||
= t :order_balance_due
|
||||
%td.text-right.total.not-paid
|
||||
%h5.not-paid
|
||||
%h5.not-paid#balance-due
|
||||
= order.display_outstanding_balance.to_html
|
||||
- if order.outstanding_balance.negative?
|
||||
%tr.total
|
||||
|
||||
@@ -2568,6 +2568,8 @@ en:
|
||||
order_total: Total order
|
||||
order_payment: "Paying via:"
|
||||
no_payment_required: "No payment required"
|
||||
credit_used: "Credit used: %{amount}"
|
||||
customer_credit: Credit
|
||||
order_billing_address: Billing address
|
||||
order_delivery_on: Delivery on
|
||||
order_delivery_address: Delivery address
|
||||
|
||||
@@ -307,6 +307,91 @@ RSpec.describe CheckoutController do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with credit availablle" do
|
||||
let(:checkout_params) do
|
||||
{
|
||||
order: {
|
||||
email: user.email,
|
||||
bill_address_attributes: address.to_param,
|
||||
ship_address_attributes: address.to_param
|
||||
},
|
||||
shipping_method_id: order.shipment.shipping_method.id.to_s
|
||||
}
|
||||
end
|
||||
|
||||
let(:credit_payment_method) {
|
||||
order.distributor.payment_methods.customer_credit
|
||||
}
|
||||
|
||||
before do
|
||||
order.customer = create(:customer, enterprise: distributor)
|
||||
order.save!
|
||||
end
|
||||
|
||||
it "adds credit payment" do
|
||||
# Add credit
|
||||
create(
|
||||
:customer_account_transaction,
|
||||
amount: 100.00,
|
||||
customer: order.customer,
|
||||
payment_method: credit_payment_method
|
||||
)
|
||||
put(:update, params:)
|
||||
|
||||
credit_payment = order.payments.find_by(payment_method: credit_payment_method)
|
||||
expect(credit_payment).to be_present
|
||||
expect(credit_payment.amount).to eq(10.00) # order.total is 10.00
|
||||
end
|
||||
|
||||
context "when credit payment already added" do
|
||||
it "doesn't had more credit payment" do
|
||||
create(
|
||||
:customer_account_transaction,
|
||||
amount: 100.00,
|
||||
customer: order.customer,
|
||||
payment_method: credit_payment_method
|
||||
)
|
||||
put(:update, params:)
|
||||
|
||||
credit_payment = order.payments.find_by(payment_method: credit_payment_method)
|
||||
expect(credit_payment).to be_present
|
||||
|
||||
put(:update, params:)
|
||||
|
||||
credit_payments = order.payments.where(payment_method: credit_payment_method)
|
||||
p credit_payments
|
||||
expect(order.payments.where(payment_method: credit_payment_method).count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when no credit available" do
|
||||
it "doesn't add credit payment" do
|
||||
put(:update, params:)
|
||||
|
||||
credit_payment = order.payments.where(payment_method: credit_payment_method)
|
||||
expect(order.payments.where(payment_method: credit_payment_method)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "when no enough credit available" do
|
||||
it "adds credit payment using all credit" do
|
||||
# Add credit
|
||||
create(
|
||||
:customer_account_transaction,
|
||||
amount: 5.00,
|
||||
customer: order.customer,
|
||||
payment_method: credit_payment_method
|
||||
)
|
||||
put(:update, params:)
|
||||
|
||||
credit_payment = order.payments.find_by(payment_method: credit_payment_method)
|
||||
expect(credit_payment.amount).to eq(5.00)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO cover error scenarios here
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -526,6 +611,31 @@ RSpec.describe CheckoutController do
|
||||
end
|
||||
end
|
||||
|
||||
context "with an order paid with customer credit" do
|
||||
let(:params) do
|
||||
{ step: "payment" }
|
||||
end
|
||||
let(:credit_payment_method) {
|
||||
order.distributor.payment_methods.customer_credit
|
||||
}
|
||||
|
||||
before do
|
||||
# Add payment with credit
|
||||
payment = order.payments.create!(
|
||||
amount: order.total, payment_method: credit_payment_method
|
||||
)
|
||||
payment.complete!
|
||||
order.update_totals_and_states
|
||||
end
|
||||
|
||||
it "allows proceeding to confirmation" do
|
||||
put(:update, params:)
|
||||
|
||||
expect(response).to redirect_to checkout_step_path(:summary)
|
||||
expect(order.reload.state).to eq "confirmation"
|
||||
end
|
||||
end
|
||||
|
||||
context "with a saved credit card" do
|
||||
let!(:saved_card) { create(:stored_credit_card, user:) }
|
||||
let(:checkout_params) do
|
||||
|
||||
@@ -350,6 +350,45 @@ RSpec.describe "As a consumer, I want to checkout my order" do
|
||||
expect(page).to have_field "shipping_method_#{shipping_with_fee.id}", checked: false
|
||||
end
|
||||
end
|
||||
|
||||
context "wiht customer credit" do
|
||||
let(:credit_payment_method) { Spree::PaymentMethod.customer_credit }
|
||||
let(:credit_amount) { 100.00 }
|
||||
let(:customer) { create(:customer, user:, enterprise: distributor) }
|
||||
|
||||
before do
|
||||
order.update(customer_id: customer.id)
|
||||
order.update_totals_and_states
|
||||
|
||||
create(
|
||||
:customer_account_transaction,
|
||||
amount: credit_amount,
|
||||
customer: order.customer,
|
||||
payment_method: credit_payment_method
|
||||
)
|
||||
visit checkout_step_path(:details)
|
||||
fill_out_details
|
||||
fill_out_billing_address
|
||||
choose free_shipping.name
|
||||
proceed_to_payment
|
||||
end
|
||||
|
||||
it "adds a customer credit payment" do
|
||||
credit_payment = order.payments.find_by(payment_method: credit_payment_method)
|
||||
expect(credit_payment).not_to be_nil
|
||||
expect(credit_payment.amount).to eq(10.00) # order.total is 10.00
|
||||
end
|
||||
|
||||
context "when credit doesn't cover the whole order" do
|
||||
let(:credit_amount) { 5.00 }
|
||||
|
||||
it "adds a customer credit payment using the remaining credit" do
|
||||
credit_payment = order.payments.find_by(payment_method: credit_payment_method)
|
||||
expect(credit_payment).not_to be_nil
|
||||
expect(credit_payment.amount).to eq(5.00) # order.total is 10.00
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "not filling out delivery details" do
|
||||
|
||||
@@ -22,12 +22,6 @@ RSpec.describe "As a consumer, I want to checkout my order" do
|
||||
create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor],
|
||||
coordinator: create(:distributor_enterprise), variants: [variant])
|
||||
}
|
||||
let(:order) {
|
||||
create(:order, order_cycle:, distributor:, bill_address_id: nil,
|
||||
ship_address_id: nil, state: "cart",
|
||||
line_items: [create(:line_item, variant:)])
|
||||
}
|
||||
|
||||
let(:fee_tax_rate) { create(:tax_rate, amount: 0.10, zone:, included_in_price: true) }
|
||||
let(:fee_tax_category) { create(:tax_category, tax_rates: [fee_tax_rate]) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee, amount: 1.23, tax_category: fee_tax_category) }
|
||||
@@ -114,6 +108,40 @@ RSpec.describe "As a consumer, I want to checkout my order" do
|
||||
end
|
||||
end
|
||||
|
||||
context "with credit available" do
|
||||
let(:credit_payment_method) { Spree::PaymentMethod.customer_credit }
|
||||
let(:payment_amount) { 10.00 }
|
||||
|
||||
before do
|
||||
create(
|
||||
:customer_account_transaction,
|
||||
amount: 100, customer: order.customer,
|
||||
payment_method: credit_payment_method
|
||||
)
|
||||
# Add credit payment
|
||||
payment = order.payments.create!(payment_method: credit_payment_method,
|
||||
amount: payment_amount)
|
||||
payment.internal_purchase!
|
||||
|
||||
visit checkout_step_path(:payment)
|
||||
end
|
||||
|
||||
it "displays no payment required" do
|
||||
expect(page).to have_content "No payment required"
|
||||
expect(page).to have_content "Credit used: $10.00"
|
||||
end
|
||||
|
||||
context "when credit does not cover the whole order" do
|
||||
let(:credit_amount) { 5.00 }
|
||||
let(:payment_amount) { 5.00 }
|
||||
|
||||
it "shows credit used and available payment method" do
|
||||
expect(page).to have_content "Credit used: $5.00"
|
||||
expect(page).to have_content "Payment with Fee $1.23"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "vouchers" do
|
||||
context "with no voucher available" do
|
||||
before do
|
||||
|
||||
@@ -24,10 +24,9 @@ RSpec.describe "As a consumer, I want to checkout my order" do
|
||||
coordinator: create(:distributor_enterprise), variants: [variant])
|
||||
}
|
||||
let(:order) {
|
||||
create(:order, order_cycle:, distributor:, bill_address_id: nil,
|
||||
ship_address_id: nil, state: "cart",
|
||||
line_items: [create(:line_item, variant:)])
|
||||
create(:order_ready_for_confirmation, distributor:)
|
||||
}
|
||||
|
||||
let(:fee_tax_rate) { create(:tax_rate, amount: 0.10, zone:, included_in_price: true) }
|
||||
let(:fee_tax_category) { create(:tax_category, tax_rates: [fee_tax_rate]) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee, amount: 1.23, tax_category: fee_tax_category) }
|
||||
@@ -52,10 +51,6 @@ RSpec.describe "As a consumer, I want to checkout my order" do
|
||||
end
|
||||
|
||||
context "summary step" do
|
||||
let(:order) {
|
||||
create(:order_ready_for_confirmation, distributor:)
|
||||
}
|
||||
|
||||
describe "display the delivery address and not the ship address" do
|
||||
let(:ship_address) { create(:address, :randomized) }
|
||||
let(:bill_address) { create(:address, :randomized) }
|
||||
@@ -386,6 +381,100 @@ RSpec.describe "As a consumer, I want to checkout my order" do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with customer credit" do
|
||||
let(:credit_payment_method) { Spree::PaymentMethod.customer_credit }
|
||||
let(:order) { create(:order_ready_for_payment, distributor:) }
|
||||
let(:payment_amount) { 10.00 }
|
||||
|
||||
before do
|
||||
create(
|
||||
:customer_account_transaction,
|
||||
amount: 100,
|
||||
customer: order.customer,
|
||||
payment_method: credit_payment_method
|
||||
)
|
||||
# Add credit payment
|
||||
payment = order.payments.create!(payment_method: credit_payment_method,
|
||||
amount: payment_amount)
|
||||
payment.internal_purchase!
|
||||
end
|
||||
|
||||
it "displays the customer credit used" do
|
||||
# Move to ready for confirmation
|
||||
order.next!
|
||||
|
||||
visit checkout_step_path(:summary)
|
||||
|
||||
expect(page).to have_content "Customer credit"
|
||||
|
||||
within ".summary-right" do
|
||||
expect(page).to have_content "Credit"
|
||||
expect(page).to have_selector("#customer-credit", text: with_currency(-10.00))
|
||||
expect(page).to have_selector("#order_total", text: with_currency(0.00))
|
||||
end
|
||||
end
|
||||
|
||||
context "when completing order" do
|
||||
it "displays the order as paid" do
|
||||
# Move to ready for confirmation
|
||||
order.next!
|
||||
|
||||
visit checkout_step_path(:summary)
|
||||
place_order
|
||||
|
||||
# TODO it should be displaying some kind indication it was paid with credit
|
||||
expect(page).to have_content "PAID"
|
||||
expect(page).to have_content "Paying via: Customer credit"
|
||||
expect(page).to have_selector("#amount-paid", text: with_currency(10.00))
|
||||
end
|
||||
end
|
||||
|
||||
context "when credit doesn't cover the whole order" do
|
||||
let(:payment_amount) { 2.00 }
|
||||
let(:payment_method) { create(:payment_method, distributors: [distributor]) }
|
||||
|
||||
before do
|
||||
# Add another payment to cover the rest of the order
|
||||
order.payments.create!(payment_method:, amount: 8.00)
|
||||
end
|
||||
|
||||
it "displays the customer credit used" do
|
||||
# Move to ready for confirmation
|
||||
order.next!
|
||||
|
||||
visit checkout_step_path(:summary)
|
||||
|
||||
expect(page).to have_content payment_method.display_name
|
||||
|
||||
within ".summary-right" do
|
||||
expect(page).to have_content "Credit"
|
||||
expect(page).to have_selector("#customer-credit", text: with_currency(-2.00))
|
||||
# actual order total is 10.00
|
||||
expect(page).to have_selector("#order_total", text: with_currency(8.00))
|
||||
end
|
||||
end
|
||||
|
||||
context "when completing order" do
|
||||
it "displays part of the order whas paid with credit" do
|
||||
# Move to ready for confirmation
|
||||
order.next!
|
||||
|
||||
visit checkout_step_path(:summary)
|
||||
place_order
|
||||
|
||||
expect(page).to have_content "NOT PAID"
|
||||
expect(page).to have_content "Paying via: #{payment_method.display_name}"
|
||||
within "#line-items" do
|
||||
expect(page).to have_selector("#amount-paid", text: with_currency(2.00))
|
||||
# actual order total is 10.00
|
||||
expect(page).to have_content("Balance Due")
|
||||
expect(page).to have_selector("#balance-due", text: with_currency(8.00))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with previous open orders" do
|
||||
|
||||
Reference in New Issue
Block a user