Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Revision of 4c4daa1a744f8a1bcf841d4b3c7ff9b925369740, to remove the c…

…hange to the shipping

calculator interface (instead, creates a dummy shipment just before doing the calcs)

Fix for 463. Revised the checkout process so that creditcard info is held entirely in memory and only persisted (with number/cvv blanked) when the order succeeds; also clarified the get/post behaviour

Highlights:

* logic for GET/POST/PUT cleaned up, drawing on Ethan Rowe's useful comments in
  http://groups.google.com/group/spree-user/browse_thread/thread/1500ac8d7d7b2ec3

* creditcard info is stored entirely as an object attribute and not persisted. It is
  only saved by after being connected to a payment - which is done by authorize()
  when the request succeeds.

* creditcards association removed from order side - need to go through payments now
  (this is also in preparation for generalising payments to non-cc forms)

* the (single) shipment is only created when the order is completed
  (there's no good reason to have it before, and it did complicate things)

* interface to shipping calculator IS UNCHANGED

* order.save! now used - silent failure not really an option

* relevant admin code, checkout views, and javascript code updated correspondingly
  • Loading branch information...
commit d777c031a1df9ffd526620d4a30ff54ccc1a1295 1 parent 0b0a258
Paul Callaghan paulcc authored schof committed
2  app/controllers/admin/orders_controller.rb
View
@@ -43,7 +43,7 @@ def collection
#set results per page to default or form result
@search.per_page = Spree::Config[:orders_per_page]
- @collection = @search.find(:all, :include => [:user, :shipments, {:creditcards => :address}] )
+ @collection = @search.find(:all, :include => [:user, :shipments, {:creditcard_payments => {:creditcard => :address}}] )
end
# Allows extensions to add new forms of payment to provide their own display of transactions
10 app/controllers/orders_controller.rb
View
@@ -91,9 +91,11 @@ def load_data
end
def rate_hash
- shipment = @order.shipments.last
- @order.shipping_methods.collect { |ship_method| {:id => ship_method.id,
- :name => ship_method.name,
- :rate => number_to_currency(ship_method.calculate_shipping(shipment)) } }
+ fake_shipment = Shipment.new :order => @order, :address => @order.ship_address
+ @order.shipping_methods.collect do |ship_method|
+ { :id => ship_method.id,
+ :name => ship_method.name,
+ :rate => number_to_currency(ship_method.calculate_shipping(fake_shipment)) }
+ end
end
end
2  app/models/creditcard.rb
View
@@ -1,5 +1,5 @@
class Creditcard < ActiveRecord::Base
- before_create :filter_sensitive
+ before_save :filter_sensitive
belongs_to :order
belongs_to :address
has_many :creditcard_payments
13 app/models/order.rb
View
@@ -8,12 +8,10 @@ class Order < ActiveRecord::Base
has_many :state_events
has_many :payments
has_many :creditcard_payments
- has_many :creditcards
belongs_to :user
has_many :shipments, :dependent => :destroy
belongs_to :bill_address, :foreign_key => "bill_address_id", :class_name => "Address"
belongs_to :ship_address, :foreign_key => "ship_address_id", :class_name => "Address"
- accepts_nested_attributes_for :creditcards, :reject_if => proc { |attributes| attributes['number'].blank? }
accepts_nested_attributes_for :ship_address, :bill_address
validates_associated :line_items, :message => "are not valid"
@@ -31,7 +29,13 @@ class Order < ActiveRecord::Base
# attr_accessible is a nightmare with attachment_fu, so use attr_protected instead.
attr_protected :ship_amount, :tax_amount, :item_total, :total, :user, :number, :ip_address, :checkout_complete, :state, :token
-
+
+ # for memory-only storage of creditcard details
+ attr_accessor :creditcard
+
+ # for storage of shipping method before it is saved in a shipment
+ attr_accessor :initial_shipping_method
+
def to_param
self.number if self.number
generate_order_number unless self.number
@@ -181,10 +185,11 @@ def update_totals
private
def complete_order
+ shipments.build(:address => ship_address, :shipping_method => initial_shipping_method)
self.update_attribute(:checkout_complete, true)
InventoryUnit.sell_units(self)
update_totals
- save_result = save
+ save_result = save!
if user && user.email
OrderMailer.deliver_confirm(self)
end
4 app/views/admin/orders/index.html.erb
View
@@ -63,8 +63,8 @@
<%= user.text_field :email, :size=>25 %>
</p>
<% end %>
- <% orders.fields_for orders.object.creditcards do |cc| %>
- <% cc.fields_for cc.object.address do |address| %>
+ <% orders.fields_for orders.object.creditcard_payments do |cc| %>
+ <% cc.fields_for cc.object.creditcard.address do |address| %>
<p>
<label><%= t("first_name") %></label><br />
<%= address.text_field :lower_of_firstname_contains, :size=>25 %>
10 app/views/orders/_payment.html.erb
View
@@ -1,4 +1,4 @@
-<% order_form.fields_for :creditcards do |creditcard_form| %>
+<% order_form.fields_for :creditcard do |creditcard_form| %>
<h2><%= t("payment_information") %></h2>
<div class="inner">
<p>
@@ -20,13 +20,13 @@
<%= creditcard_form.text_field :issue_number, {:style => 'width:40px', :title => t('enter_exactly_as_shown_on_card') + ', e.g. 2, 01.' } -%>
&nbsp;<strong>OR</strong>&nbsp;
<%= t('start_date') %>
- <%= select_month(@date, :prefix => 'order[creditcards_attributes][0]', :field_name => 'start_month', :use_month_numbers => true, :include_blank => true) -%>
- <%= select_year(@date, :prefix => 'order[creditcards_attributes][0]', :field_name => 'start_year', :start_year => Date.today.year - 15, :end_year => Date.today.year, :include_blank => true) -%>
+ <%= select_month(@date, :prefix => 'order[creditcard]', :field_name => 'start_month', :use_month_numbers => true, :include_blank => true) -%>
+ <%= select_year(@date, :prefix => 'order[creditcard]', :field_name => 'start_year', :start_year => Date.today.year - 15, :end_year => Date.today.year, :include_blank => true) -%>
</p>
<p>
<label for=""><%= t("expiration") %></label>
- <%= select_month(Date.today, :prefix => 'order[creditcards_attributes][0]', :field_name => 'month', :use_month_numbers => true, :class => 'required') -%>
- <%= select_year(Date.today, :prefix => 'order[creditcards_attributes][0]', :field_name => 'year', :start_year => Date.today.year, :end_year => Date.today.year + 15, :class => 'required') -%>
+ <%= select_month(Date.today, :prefix => 'order[creditcard]', :field_name => 'month', :use_month_numbers => true, :class => 'required') -%>
+ <%= select_year(Date.today, :prefix => 'order[creditcard]', :field_name => 'year', :start_year => Date.today.year, :end_year => Date.today.year + 15, :class => 'required') -%>
<span class="req">*</span>
</p>
<p>
53 lib/spree/checkout.rb
View
@@ -8,34 +8,35 @@ def checkout
load_data
load_checkout_steps
- # push the current record ids into the incoming params to allow nested_attribs to do update-in-place
- # assumes that if a record is set, it was set from the nested hash and so update will work
- if params[:order]
- params[:order][:bill_address_attributes][:id] = @order.bill_address.id if @order.bill_address
- params[:order][:ship_address_attributes][:id] = @order.ship_address.id if @order.ship_address
+ if request.get? # default values needed for GET / first pass
+ @order.bill_address ||= Address.new(:country => @default_country)
+ @order.ship_address ||= Address.new(:country => @default_country)
+
+ if @order.creditcard.nil?
+ @order.creditcard = Creditcard.new(:month => Date.today.month, :year => Date.today.year)
+ end
end
- # and now do the over-write, saving any new changes as we go
- @order.update_attributes(params[:order])
+ unless request.get? # the proper processing
+ @order.initial_shipping_method = ShippingMethod.find_by_id(params[:method_id]) if params[:method_id]
+ @method_id = params[:method_id]
- # default values needed for GET / first pass
- @order.bill_address ||= Address.new(:country => @default_country)
- @order.ship_address ||= Address.new(:country => @default_country)
- if @order.creditcards.empty?
- @order.creditcards.build(:month => Date.today.month, :year => Date.today.year)
- end
- @shipping_method = ShippingMethod.find_by_id(params[:method_id]) if params[:method_id]
- @shipping_method ||= @order.shipping_methods.first
- @order.shipments.build(:address => @order.ship_address, :shipping_method => @shipping_method) if @order.shipments.empty?
+ # push the current record ids into the incoming params to allow nested_attribs to do update-in-place
+ if @order.bill_address && params[:order][:bill_address_attributes]
+ params[:order][:bill_address_attributes][:id] = @order.bill_address.id
+ end
+ if @order.ship_address && params[:order][:ship_address_attributes]
+ params[:order][:ship_address_attributes][:id] = @order.ship_address.id
+ end
- if request.post?
- # @order.creditcards.clear
- # @order.attributes = params[:order]
- @order.creditcards[0].address = @order.bill_address if @order.creditcards.present?
+ # and now do the over-write, saving any new changes as we go
+ @order.update_attributes(params[:order])
+
+ # set some derived information
@order.user = current_user
- @order.ip_address = request.env['REMOTE_ADDR']
- @order.update_totals
@order.email = current_user.email if @order.email.blank? && current_user
+ @order.ip_address = request.env['REMOTE_ADDR']
+ @order.update_totals
begin
# need to check valid b/c we dump the creditcard info while saving
@@ -43,9 +44,13 @@ def checkout
if params[:final_answer].blank?
@order.save
else
- @order.creditcards[0].authorize(@order.total)
+ # now fetch the CC info and do the authorization
+ @order.creditcard = Creditcard.new params[:order][:creditcard]
+ @order.creditcard.address = @order.bill_address
+ @order.creditcard.order = @order;
+ @order.creditcard.authorize(@order.total)
+
@order.complete
- # remove the order from the session
session[:order_id] = nil
end
else
6 public/javascripts/checkout.js
View
@@ -4,7 +4,7 @@ $(function() {
$('span#bcountry select').change(function() { update_state('b'); });
$('span#scountry select').change(function() { update_state('s'); });
get_states();
- $('input#order_creditcards_attributes_0_number').blur(set_card_validation);
+ $('input#order_creditcard_number').blur(set_card_validation);
// hook up the continue buttons for each section
for(var i=0; i < regions.length; i++) {
@@ -451,11 +451,11 @@ var card_type = function(number) {
};
var set_card_validation = function () {
- if ($("#order_creditcards_attributes_0_number").val().match(/^\s*$/)) {
+ if ($("#order_creditcard_number").val().match(/^\s*$/)) {
$('#card_type').hide();
return;
}
- current_card_type = card_type($("#order_creditcards_attributes_0_number").val());
+ current_card_type = card_type($("#order_creditcard_number").val());
$('#card_type').show();
$('#card_type #looks_like').hide();
$('#card_type #unrecognized').hide();
20 spec/models/shipping_method_spec.rb
View
@@ -1,16 +1,16 @@
require File.dirname(__FILE__) + '/../spec_helper'
class MockCalculator
- def available?(order)
+ def available?(shipment)
true
end
- def calculate_shipping(order)
+ def calculate_shipping(shipment)
2.5
end
end
class MockUnavailableCalculator
- def available?(order)
+ def available?(shipment)
false
end
end
@@ -22,23 +22,23 @@ def available?(order)
@zone = mock_model(Zone)
@address = mock_model(Address)
@shipping_method = ShippingMethod.new(:zone => @zone, :shipping_calculator => "MockCalculator")
- @order = mock_model(Order, :address => @address)
+ @shipment = mock_model(Shipment, :address => @address)
end
describe "available?" do
it "should return true if the calculator indicates method is supported" do
- @shipping_method.available?(@order).should be_true
+ @shipping_method.available?(@shipment).should be_true
end
it "should return false if the calculator indicates method is not supported" do
@shipping_method = ShippingMethod.new(:zone => @zone, :shipping_calculator => "MockUnavailableCalculator")
- @shipping_method.available?(@order).should be_false
+ @shipping_method.available?(@shipment).should be_false
end
end
describe "calculate_shipping" do
it "should be 0 if the shipping address does not fall within the method's zone" do
@zone.stub!(:include?).with(@address).and_return(false)
- @shipping_method.calculate_shipping(@order).should == 0
+ @shipping_method.calculate_shipping(@shipment).should == 0
end
describe "when the shipping address is included within the method's zone" do
before :each do
@@ -48,11 +48,11 @@ def available?(order)
it "should use the calculate_shipping method of the specified calculator" do
@calculator = MockCalculator.new
MockCalculator.stub!(:new, :return => @calculator)
- @calculator.should_receive(:calculate_shipping).with(@order)
- @shipping_method.calculate_shipping(@order)
+ @calculator.should_receive(:calculate_shipping).with(@shipment)
+ @shipping_method.calculate_shipping(@shipment)
end
it "should return the correct amount" do
- @shipping_method.calculate_shipping(@order).should == 2.5
+ @shipping_method.calculate_shipping(@shipment).should == 2.5
end
end
end
2  test/functional/orders_controller_test.rb
View
@@ -23,4 +23,4 @@ class OrdersControllerTest < ActionController::TestCase
should_change "Address.count", :by => 2
end
end
-end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.