Permalink
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...
1 parent 0b0a258 commit d777c031a1df9ffd526620d4a30ff54ccc1a1295 @paulcc paulcc committed with schof May 30, 2009
View
2 app/controllers/admin/orders_controller.rb
@@ -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
View
10 app/controllers/orders_controller.rb
@@ -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
View
2 app/models/creditcard.rb
@@ -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
View
13 app/models/order.rb
@@ -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
View
4 app/views/admin/orders/index.html.erb
@@ -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 %>
View
10 app/views/orders/_payment.html.erb
@@ -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>
View
53 lib/spree/checkout.rb
@@ -8,44 +8,49 @@ 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
if @order.valid?
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
View
6 public/javascripts/checkout.js
@@ -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();
View
20 spec/models/shipping_method_spec.rb
@@ -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
View
2 test/functional/orders_controller_test.rb
@@ -23,4 +23,4 @@ class OrdersControllerTest < ActionController::TestCase
should_change "Address.count", :by => 2
end
end
-end
+end

0 comments on commit d777c03

Please sign in to comment.