Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2696,2788 [Spree Upgrade] Fix use of shipping method ID for subscriptions #3560

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
Spree::Admin::Orders::CustomerDetailsController.class_eval do
before_filter :set_guest_checkout_status, only: :update

def update
if @order.update_attributes(params[:order])
if params[:guest_checkout] == "false"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised to compare a boolean value to a string. Rails does parse JSON to Ruby primitives. How is it possible that we end up with a String and not with a FalseClass instance 🤔 ? I would have expected to read this as if !params[:guest_checkout]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sauloperez This isn't the case when the string is sent to the server as HTTP form data. However, this string would still be converted to a boolean as we expect when assigned to an ActiveRecord boolean attribute. 🙂

@order.associate_user!(Spree.user_class.find_by_email(@order.email))
end

AdvanceOrderService.new(@order).call

@order.shipments.map &:refresh_rates
flash[:success] = Spree.t('customer_details_updated')
redirect_to admin_order_customer_path(@order)
else
render :action => :edit
end
end

# Inherit CanCan permissions for the current order
def model_class
load_order unless @order
Expand Down
11 changes: 11 additions & 0 deletions app/controllers/spree/admin/orders_controller_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ def index
# within the page then fetches the data it needs from Api::OrdersController
end

def edit
@order.shipments.map &:refresh_rates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are two points about this method:

these are both out of scope of this PR.


AdvanceOrderService.new(@order).call

# The payment step shows an error of 'No pending payments'
# Clearing the errors from the order object will stop this error
# appearing on the edit page where we don't want it to.
@order.errors.clear
end

# Re-implement spree method so that it redirects to edit instead of rendering edit
# This allows page reloads while adding variants to the order (/edit), without being redirected to customer details page (/update)
def update
Expand Down
29 changes: 29 additions & 0 deletions app/controllers/spree/admin/payments_controller_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
Spree::Admin::PaymentsController.class_eval do
append_before_filter :filter_payment_methods

def create
@payment = @order.payments.build(object_params)
if @payment.payment_method.is_a?(Spree::Gateway) && @payment.payment_method.payment_profiles_supported? && params[:card].present? and params[:card] != 'new'
@payment.source = CreditCard.find_by_id(params[:card])
end

begin
unless @payment.save
redirect_to admin_order_payments_path(@order)
return
end

if @order.completed?
@payment.process!
flash[:success] = flash_message_for(@payment, :successfully_created)

redirect_to admin_order_payments_path(@order)
else
AdvanceOrderService.new(@order).call!

flash[:success] = Spree.t(:new_order_completed)
redirect_to edit_admin_order_url(@order)
end

rescue Spree::Core::GatewayError => e
flash[:error] = "#{e.message}"
redirect_to new_admin_order_payment_path(@order)
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not good that we need to copy spree code to our code base but I am actually very happy that we are reclaiming this payments code to OFN, because the spree code is not good in v2 and it's a big mess in v3.7 here.

this kind of logic must go to a service, it's not the responsibility of the controller.
So I see this as the first step to improve it on our side.

It's out of scope for this PR.


# 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)
Expand Down
42 changes: 42 additions & 0 deletions app/services/advance_order_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class AdvanceOrderService
Copy link
Contributor

@luisramos0 luisramos0 Mar 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attr_reader :order

def initialize(order)
@order = order
end

def call
advance_order(advance_order_options)
end

def call!
advance_order!(advance_order_options)
end

private

def advance_order_options
shipping_method_id = order.shipping_method.id if order.shipping_method.present?
{ shipping_method_id: shipping_method_id }
end

def advance_order(options)
until order.state == "complete"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be great to stick to the approach in line 24 for consistency IMO

Copy link
Member Author

@kristinalim kristinalim Mar 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave this a try just now, @sauloperez, and confirmed that a completed but later cancelled order would attach an error here (ActiveRecord::Base#errors) whereas using order.completed? would not. Spree::Order#completed? looks at completed_at, not the order state.

We can actually remove advance_order! in Spree 2.2. (Discussion here: heere) Are you okay with waiting until then so we avoid doing a more comprehensive check of different scenarios?

break unless order.next
after_transition_hook(options)
end
end

def advance_order!(options)
until order.completed?
order.next!
after_transition_hook(options)
end
end

def after_transition_hook(options)
if order.state == "delivery"
order.select_shipping_method(options[:shipping_method_id]) if options[:shipping_method_id]
end
end
end
11 changes: 11 additions & 0 deletions app/services/order_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ def create
set_user
build_line_items
set_addresses
create_shipment
set_shipping_method
create_payment

@order
end

Expand Down Expand Up @@ -65,6 +68,14 @@ def set_addresses
@order.update_attributes(attrs.slice(:bill_address_attributes, :ship_address_attributes))
end

def create_shipment
@order.create_proposed_shipments
end

def set_shipping_method
@order.select_shipping_method(attrs[:shipping_method_id])
end

def create_payment
@order.update_distribution_charge!
@order.payments.create(payment_method_id: attrs[:payment_method_id], amount: @order.reload.total)
Expand Down
17 changes: 10 additions & 7 deletions app/services/order_syncer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,12 @@ def update_payment_for(order)
end

def update_shipment_for(order)
shipment = order.shipments.with_state('pending').where(shipping_method_id: shipping_method_id_was).last
if shipment
shipment.update_attributes(shipping_method_id: shipping_method_id)
order.update_attribute(:shipping_method_id, shipping_method_id)
return if pending_shipment_with?(order, shipping_method_id) # No need to do anything.

if pending_shipment_with?(order, shipping_method_id_was)
order.select_shipping_method(shipping_method_id)
else
unless order.shipments.with_state('pending').where(shipping_method_id: shipping_method_id).any?
order_update_issues.add(order, I18n.t('admin.shipping_method'))
end
order_update_issues.add(order, I18n.t('admin.shipping_method'))
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see order_shipment.rb, in ofn v2 we have one very useful rule (it makes everything much easier): one order can only have one shipment, there will never be 2 shipments in an order. So you can use order.shipment.

also, spree::shipment.selected_shipping_rate is kept to single selection in spree::shipment.selected_shipping_rate_id=
so you can always use order.shipment.selected_shipping_rate

also, you can simply use order_shipment.shipping_method that redirects to spree::shipment.shipping_method that redirects to spree::shipment.selected_shipping_rate :-)

I think these facts will help simplify this code, right?

end

Expand Down Expand Up @@ -106,4 +104,9 @@ def force_ship_address_required?(order)
order.ship_address[attr] == distributor_address[attr]
end
end

def pending_shipment_with?(order, shipping_method_id)
return false unless order.shipment.present? && order.shipment.state == "pending"
order.shipping_method.id == shipping_method_id
end
end
4 changes: 2 additions & 2 deletions spec/controllers/admin/proxy_orders_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'spec_helper'

xdescribe Admin::ProxyOrdersController, type: :controller do
describe Admin::ProxyOrdersController, type: :controller do
include AuthenticationWorkflow

describe 'cancel' do
Expand Down Expand Up @@ -107,7 +107,7 @@
before { shop.update_attributes(owner: user) }

context "when resuming succeeds" do
it 'renders the resumed proxy_order as json' do
xit 'renders the resumed proxy_order as json' do
spree_get :resume, params
json_response = JSON.parse(response.body)
expect(json_response['state']).to eq "resumed"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
login_as_enterprise_user [order.distributor]
end

it "advances the order state" do
expect {
spree_post :update, order: { email: user.email, bill_address_attributes: address_params,
ship_address_attributes: address_params },
order_id: order.number
}.to change { order.reload.state }.from("cart").to("complete")
end

context "when adding details of a registered user" do
it "redirects to shipments on success" do
spree_post :update, order: { email: user.email, bill_address_attributes: address_params, ship_address_attributes: address_params }, order_id: order.number
Expand Down
12 changes: 12 additions & 0 deletions spec/controllers/spree/admin/orders_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
include AuthenticationWorkflow
include OpenFoodNetwork::EmailHelper

describe "#edit" do
let!(:order) { create(:order_with_totals_and_distribution, ship_address: create(:address)) }

before { login_as_admin }

it "advances the order state" do
expect {
spree_get :edit, id: order
}.to change { order.reload.state }.from("cart").to("complete")
end
end

context "#update" do
let(:params) do
{ id: order,
Expand Down
20 changes: 19 additions & 1 deletion spec/controllers/spree/admin/payments_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,27 @@
let!(:order) { create(:order, distributor: shop, state: 'complete') }
let!(:line_item) { create(:line_item, order: order, price: 5.0) }

before do
allow(controller).to receive(:spree_current_user) { user }
end

context "#create" do
let!(:payment_method) { create(:payment_method, distributors: [ shop ]) }
let!(:order) do
create(:order_with_totals_and_distribution, distributor: shop, state: "payment")
end

let(:params) { { amount: order.total, payment_method_id: payment_method.id } }

it "advances the order state" do
expect {
spree_post :create, payment: params, order_id: order.number
}.to change { order.reload.state }.from("payment").to("complete")
end
end

context "as an enterprise user" do
before do
allow(controller).to receive(:spree_current_user) { user }
order.reload.update_totals
end

Expand Down
2 changes: 1 addition & 1 deletion spec/features/admin/subscriptions_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'spec_helper'

xfeature 'Subscriptions' do
feature 'Subscriptions' do
include AdminHelper
include AuthenticationWorkflow
include WebHelper
Expand Down
3 changes: 2 additions & 1 deletion spec/jobs/subscription_placement_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@

describe "performing the job" do
context "when unplaced proxy_orders exist" do
let!(:proxy_order) { create(:proxy_order) }
let!(:subscription) { create(:subscription, with_items: true) }
let!(:proxy_order) { create(:proxy_order, subscription: subscription) }

before do
allow(job).to receive(:proxy_orders) { ProxyOrder.where(id: proxy_order.id) }
Expand Down
4 changes: 2 additions & 2 deletions spec/models/proxy_order_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'spec_helper'

xdescribe ProxyOrder, type: :model do
describe ProxyOrder, type: :model do
describe "cancel" do
let(:order_cycle) { create(:simple_order_cycle) }
let(:subscription) { create(:subscription) }
Expand Down Expand Up @@ -78,7 +78,7 @@
describe "resume" do
let!(:payment_method) { create(:payment_method) }
let!(:shipment) { create(:shipment) }
let(:order) { create(:order_with_totals, shipments: [shipment]) }
let(:order) { create(:order_with_totals, ship_address: create(:address), shipments: [shipment]) }
let(:proxy_order) { create(:proxy_order, order: order, canceled_at: Time.zone.now) }
let(:order_cycle) { proxy_order.order_cycle }

Expand Down
55 changes: 55 additions & 0 deletions spec/services/advance_order_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require "spec_helper"

describe AdvanceOrderService do
let!(:distributor) { create(:distributor_enterprise) }
let!(:order) do
create(:order_with_totals_and_distribution, distributor: distributor,
bill_address: create(:address),
ship_address: create(:address))
end

let(:service) { described_class.new(order) }

it "transitions the order multiple steps" do
expect(order.state).to eq("cart")
service.call
order.reload
expect(order.state).to eq("complete")
end

describe "transition from delivery" do
let!(:shipping_method_a) { create(:shipping_method, distributors: [ distributor ]) }
let!(:shipping_method_b) { create(:shipping_method, distributors: [ distributor ]) }
let!(:shipping_method_c) { create(:shipping_method, distributors: [ distributor ]) }

before do
# Create shipping rates for available shipping methods.
order.shipments.each(&:refresh_rates)
end

it "retains delivery method of the order" do
order.select_shipping_method(shipping_method_b.id)
service.call
order.reload
expect(order.shipping_method).to eq(shipping_method_b)
end
end

context "when raising on error" do
it "transitions the order multiple steps" do
service.call!
order.reload
expect(order.state).to eq("complete")
end

context "when order cannot advance to the next state" do
let!(:order) do
create(:order, distributor: distributor)
end

it "raises error" do
expect { service.call! }.to raise_error(StateMachine::InvalidTransition)
end
end
end
end
18 changes: 18 additions & 0 deletions spec/services/order_factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
let(:customer) { create(:customer, user: user) }
let(:shop) { create(:distributor_enterprise) }
let(:order_cycle) { create(:simple_order_cycle) }
let!(:other_shipping_method_a) { create(:shipping_method) }
let!(:shipping_method) { create(:shipping_method) }
let!(:other_shipping_method_b) { create(:shipping_method) }
let(:payment_method) { create(:payment_method) }
let(:ship_address) { create(:address) }
let(:bill_address) { create(:address) }
Expand All @@ -21,6 +24,7 @@
attrs[:customer_id] = customer.id
attrs[:distributor_id] = shop.id
attrs[:order_cycle_id] = order_cycle.id
attrs[:shipping_method_id] = shipping_method.id
attrs[:payment_method_id] = payment_method.id
attrs[:bill_address_attributes] = bill_address.attributes.except("id")
attrs[:ship_address_attributes] = ship_address.attributes.except("id")
Expand All @@ -35,13 +39,27 @@
expect(order.user).to eq user
expect(order.distributor).to eq shop
expect(order.order_cycle).to eq order_cycle
expect(order.shipments.first.shipping_method).to eq shipping_method
expect(order.payments.first.payment_method).to eq payment_method
expect(order.bill_address).to eq bill_address
expect(order.ship_address).to eq ship_address
expect(order.total).to eq 38.0
expect(order.complete?).to be false
end

it "retains address, delivery, and payment attributes until completion of the order" do
AdvanceOrderService.new(order).call

order.reload

expect(order.customer).to eq customer
expect(order.shipping_method).to eq shipping_method
expect(order.payments.first.payment_method).to eq payment_method
expect(order.bill_address).to eq bill_address
expect(order.ship_address).to eq ship_address
expect(order.total).to eq 38.0
end

context "when the customer does not have a user associated with it" do
before { customer.update_attribute(:user_id, nil) }

Expand Down
Loading