Skip to content

Instantly share code, notes, and snippets.

@vladimir-e
Created August 12, 2013 15:09
Show Gist options
  • Save vladimir-e/6211657 to your computer and use it in GitHub Desktop.
Save vladimir-e/6211657 to your computer and use it in GitHub Desktop.
Shopping cart
class Cart
include ActiveAttr::Model
attribute :items # has_many
attribute :qty, :type => Integer
attribute :subtotal_chf, :type => Integer
attribute :subtotal_eur, :type => Integer
attribute :order_id, :type => Integer
def initialize(storage)
@storage = storage
load
self
end
class << self
# ugly shortcut
def items_count(storage)
( storage[:cart][:items].try(:count) if storage[:cart] ).to_i
end
end
def << (item)
if item.valid?
item.cart = self
existing_item = find(item.id)
if existing_item
existing_item.qty += item.qty
else
self.items << item
end
# don't move it to else condition
item.after_add
self.qty += item.qty
self.subtotal_chf += item.price_chf.cents * item.qty
self.subtotal_eur += item.price_eur.cents * item.qty
save
self
end
end
def find(item_id)
items.select{ |ci| ci.id == item_id }.first
end
def inc(item_id, new_qty)
item = find item_id
if item
item.qty += new_qty.to_i
self.qty += new_qty.to_i
self.subtotal_chf += item.price_chf.cents * new_qty.to_i
self.subtotal_eur += item.price_eur.cents * new_qty.to_i
save
item
end
end
def dec(item_id, new_qty)
item = find item_id
if item
if (item.qty - new_qty.to_i) < 1
remove item_id
else
item.qty -= new_qty.to_i
self.qty -= new_qty.to_i
self.subtotal_chf -= item.price_chf.cents * new_qty.to_i
self.subtotal_eur -= item.price_eur.cents * new_qty.to_i
save
item
end
end
end
def remove(item_id)
item = find item_id
return false if !item
items.reject!{ |ci| ci.id == item_id }
self.qty -= item.qty
self.subtotal_chf -= item.price_chf.cents * item.qty
self.subtotal_eur -= item.price_eur.cents * item.qty
save
item
end
def currency
Shop.current.currency
end
def shipping_chf
0
end
def shipping_eur
0
end
def mwst_chf
Shop.mwst(subtotal_chf, shipping_chf).round
end
def mwst_eur
Shop.mwst(subtotal_eur, shipping_eur).round
end
def total_chf
subtotal_chf + mwst_chf
end
def total_eur
subtotal_eur + mwst_eur
end
[:subtotal, :shipping, :mwst, :total].each do |name|
define_method(name) do
cents = send("#{name}_#{currency}")
Money.new(cents || 0, currency)
end
end
def clear!
erase_storage!
load
end
def empty?
items.count == 0
end
def as_json
{
qty: I18n.t('cart.item', count: items.count),
subtotal: subtotal.format,
}
end
def to_order
{
:subtotal_chf_cents => subtotal_chf,
:subtotal_eur_cents => subtotal_eur,
:items => items.map(&:to_order)
}
end
def update_attributes(attribs)
attribs.each do |key, val|
storage[key.to_sym] = val
self.send("#{key.to_sym}=", val)
end
end
private
def load
items_data = storage[:items] || []
self.items = items_data.map do |i|
ci = CartItem.new(i)
ci.cart = self
ci
end
self.qty = storage[:qty] || 0
self.subtotal_chf = storage[:subtotal_chf] || 0
self.subtotal_eur = storage[:subtotal_eur] || 0
self.order_id = storage[:order_id]
end
def save
storage[:items] = self.items.map(&:serialize)
storage[:qty] = self.qty
storage[:subtotal_chf] = self.subtotal_chf
storage[:subtotal_eur] = self.subtotal_eur
storage[:order_id] = self.order_id
end
def storage
@storage[:cart] ||= Hash.new
end
def erase_storage!
@storage[:cart] = nil
end
end
class CartItem
include ActiveAttr::Model
attribute :qty, :type => Integer, :default => 1
attribute :product_id, :type => Integer
attribute :subproduct_id, :type => Integer
attribute :size_id, :type => Integer
attribute :colour_id, :type => Integer
attr_accessor :cart
validates_presence_of :product_id
validate :product_exist
def id
@id ||= "#{product_id}#{colour_id.to_i}#{size_id.to_i}".to_i
end
# triggers when valid item added to cart
def after_add
if subproduct?
sp = Product.find(product_id)
.subproducts.where(colour_id: colour_id, size_id: size_id)
self.subproduct_id = sp.first.id if sp.any?
end
end
def product
@product ||= ProductPresenter.new(find_product_record)
end
def colour
if colour_id?
@colour ||= Colour.find(colour_id)
end
end
def size
if size_id?
@size ||= Size.find(size_id)
end
end
def serialize
attribs = self.attributes
attribs.delete("cart")
attribs
end
def price
product.send("price_#{cart.currency}")
end
def total
price * qty
end
def title
@title = product.title
if colour_id?
@title += " (#{colour.name})"
end
if size_id?
@title += " #{size.name}"
end
@title
end
def image
@image ||= if colour_id?
product.colours_images[colour_id]
else
product.image
end
end
def subproduct?
colour_id? || size_id?
end
def to_param
id
end
def to_s
self.title
end
def as_json
{
id: id,
title: title,
qty: qty,
formatted_price: formatted_price,
total: total.format,
product_path: Rails.application.routes.url_helpers.product_path(product_id),
cart_item_path: Rails.application.routes.url_helpers.cart_item_path(self)
}
end
def to_order
{
:qty => qty,
:price_chf_cents => price_chf_cents,
:price_eur_cents => price_eur_cents,
:product_id => product_id,
:subproduct_id => subproduct_id,
}
end
def method_missing(name, *args)
product.send(name, *args)
end
private
def product_exist
errors.add(:product_id, "Product should exist") unless Product.where(:id => self.product_id).any?
end
def find_product_record
if subproduct? && subproduct_id?
Subproduct.find subproduct_id
else
Product.find product_id
end
end
end
require 'spec_helper'
require "active_attr/rspec"
describe Cart do
let(:storage) { {:session_id => "e0d8ca9c9341ffad30fa03163b4414a1"} }
let(:product) { FactoryGirl.create(:product, :price_eur => 200, :price_chf => 250) }
def cart_item(attributes = {})
attributes = {
:product_id => product.id,
}.merge(attributes)
CartItem.new(attributes)
end
def cart_item_unique(attributes = {})
attributes = {
:product_id => FactoryGirl.create(:product,
:price_eur_cents => Random.rand(300..2000),
:price_chf_cents => Random.rand(450..3000)
).id
}.merge(attributes)
cart_item(attributes)
end
subject { Cart.new storage }
its(:items) { should eql([]) }
its(:qty) { should eql(0) }
its(:subtotal_chf) { should eql(0) }
its(:subtotal_eur) { should eql(0) }
its(:total_chf) { should eql(0) }
its(:total_eur) { should eql(0) }
its(:shipping_chf) { should eql(0) }
its(:shipping_eur) { should eql(0) }
its(:mwst_chf) { should eql(0) }
its(:mwst_eur) { should eql(0) }
it { should respond_to(:subtotal) }
it { should respond_to(:shipping) }
it { should respond_to(:mwst) }
it { should respond_to(:total) }
it { should respond_to(:<<) }
it { should respond_to(:currency) }
describe "#<<" do
it "doesn't add invalid item" do
(subject << cart_item(product_id: 999)).should be_nil
subject.items.length.should eql(0)
end
it "adds cart_item to items" do
subject << cart_item_unique
subject << cart_item_unique
subject << cart_item_unique
subject.items.length.should eql(3)
end
it "only increases qty if same item added" do
subject << cart_item(:qty => 1)
subject << cart_item(:qty => 3)
subject << cart_item(:qty => 5)
subject.items.length.should eql(1)
subject.qty.should eql(9)
subject.items.first.qty.should eql(9)
end
it "accumulates items quantity in #qty" do
subject << cart_item(:qty => 3)
subject << cart_item(:qty => 5)
subject.qty.should eql(8)
end
it "accumulates items price in #subtotal in cents" do
ci1 = cart_item(:qty => 2)
ci1.price_chf = 50
ci1.price_eur = 20
subject << ci1
ci2 = cart_item(:qty => 1)
ci2.price_chf = 200
ci2.price_eur = 130
subject << ci2
subject.subtotal_chf.should eql((50*2+200)*100)
subject.subtotal_eur.should eql((20*2+130)*100)
end
it "triggers CartItem#after_add callback" do
ci = cart_item
ci.should_receive(:after_add)
subject << ci
end
end
describe "#find" do
it "returns item by item_id" do
ci1 = cart_item_unique
ci2 = cart_item_unique
subject << ci1
subject << ci2
subject << cart_item_unique
subject.find(ci2.id).should eql(ci2)
subject.find(ci1.id).should eql(ci1)
end
end
describe "#inc" do
it "increases item's quantity and other stuff happens automatically" do
ci = cart_item_unique(:qty => 3)
subject << ci
subject.inc(ci.id, 2)
subject.qty.should eql(5)
subject.subtotal_chf.should eql(ci.price_chf_cents*5)
subject.subtotal_eur.should eql(ci.price_eur_cents*5)
subject.items.first.qty.should eql(5)
end
end
describe "#dec" do
it "descreases and stuff" do
ci = cart_item_unique(:qty => 3)
subject << ci
subject.dec(ci.id, 2)
subject.qty.should eql(1)
subject.subtotal_chf.should eql(ci.price_chf_cents*1)
subject.subtotal_eur.should eql(ci.price_eur_cents*1)
subject.items.first.qty.should eql(1)
end
it "removes item if new qty less than one" do
ci = cart_item_unique(:qty => 2)
subject << ci
subject.dec(ci.id, 3)
subject.items.count.should eql(0)
subject.subtotal_chf.should eql(0)
subject.subtotal_eur.should eql(0)
subject.qty.should eql(0)
end
end
describe "#remove" do
let (:ci1) { cart_item_unique(:qty => 3) }
let (:ci2) { cart_item_unique(:qty => 1) }
before(:each) do
subject << ci1
subject << ci2
end
it "returns false if item not found" do
subject.remove(999).should be_false
end
it "returns removed item" do
subject.remove(ci1.id).should eql(ci1)
end
it "removes item by item_id" do
subject.items.count.should eql(2)
subject.remove ci1.id
subject.items.count.should eql(1)
subject.remove ci2.id
subject.items.should be_empty
subject.send(:storage)[:items].should be_empty
end
it "changes qty and subtotal" do
subject.qty.should eql(4)
subject.remove ci1.id
subject.qty.should eql(1)
subject.remove ci2.id
subject.qty.should eql(0)
subject.subtotal_chf.should eql(0)
subject.subtotal_eur.should eql(0)
end
end
describe "#subtotal" do
its(:subtotal) { should be_instance_of(Money) }
it "proxy to #subtotal_{current_currency} attribute" do
subject.subtotal_chf = 555
subject.subtotal_eur = 777
subject.stub(:currency).and_return(:chf)
subject.subtotal.should eql(Money.new(555, :chf))
subject.stub(:currency).and_return(:eur)
subject.subtotal.should eql(Money.new(777, :eur))
end
end
describe "#clear!" do
it "resets storage" do
subject << cart_item(:qty => 3)
subject << cart_item(:qty => 1)
subject.clear!
subject.send(:storage)[:items].should be_nil
subject.send(:storage)[:qty].should be_nil
subject.send(:storage)[:total_eur].should be_nil
subject.send(:storage)[:total_chf].should be_nil
subject.items.should be_empty
subject.qty.should eql(0)
subject.subtotal_eur.should eql(0)
subject.subtotal_chf.should eql(0)
end
end
describe "#load" do
let(:storage_mock) {
p1 = FactoryGirl.create(:product)
p2 = FactoryGirl.create(:product)
{
:items => [
{product_id: p1.id, qty: 4},
{product_id: p2.id, qty: 2}
],
:qty => 6,
:subtotal_eur => 600,
:subtotal_chf => 800
}
}
before(:each) do
subject.stub(:storage).and_return(storage_mock)
subject.send("load")
end
it "gets values from storage" do
subject.qty.should eql(6)
subject.subtotal_chf.should eql(800)
subject.subtotal_eur.should eql(600)
subject.items.count.should eql(2)
end
it "unserializes items" do
ci1 = subject.items.first
ci2 = subject.items.last
ci1.should be_instance_of(CartItem)
ci1.cart.should be_eql(subject)
ci1.price_chf.should be_a_kind_of(Money)
ci1.price_eur.should be_a_kind_of(Money)
end
end
describe "#to_order" do
it "returns Hash with totals and items array" do
ci1 = cart_item_unique(:qty => 2)
ci2 = cart_item_unique(:qty => 3)
total_chf = (ci1.price_chf*ci1.qty)+(ci2.price_chf*ci2.qty)
total_eur = (ci1.price_eur*ci1.qty)+(ci2.price_eur*ci2.qty)
subject << ci1
subject << ci2
result = subject.to_order
result[:subtotal_chf_cents].should eql(total_chf.cents)
result[:subtotal_eur_cents].should eql(total_eur.cents)
end
end
describe "#update_attributes" do
it "updates value in this instance and in storage" do
subject.update_attributes(order_id: 123)
subject.order_id.should eql(123)
subject.send(:storage)[:order_id].should eql(123)
end
end
it "stores serialized items in storage" do
subject << cart_item
subject.send(:storage)[:items].first.should be_instance_of(Hash)
end
end
require 'spec_helper'
require "active_attr/rspec"
describe CartItem do
let(:product) do
FactoryGirl.create(:product, :price_chf_cents => 5000,
:price_eur_cents => 3000)
end
def valid_item(attribs = {})
CartItem.any_instance.stub(:cart).and_return(Cart.new({:session => :qwerty}))
CartItem.new({"product_id" => product.id}.merge(attribs))
end
def cart_item_with_subproduct(attribs)
product
sp = FactoryGirl.build(:subproduct_orphan, {id: 99}.merge(attribs))
product.subproducts << sp
ci = valid_item( {:product_id => product.id}.merge(attribs) )
ci.after_add
ci
end
subject { valid_item }
it { should have_attribute(:product_id) }
it { should have_attribute(:subproduct_id) }
it { should have_attribute(:qty).of_type(Integer).with_default_value_of(1) }
it { should respond_to(:cart) }
its(:price) { should be_a_kind_of(Money) }
its(:price) { should eql(product.price) }
describe "validates" do
it { subject.should be_valid }
it "poduct presence" do
p = CartItem.new(:product_id => nil)
p.should have_errors_on(:product_id)
end
it "product existence" do
p = CartItem.new(:product_id => 999)
p.should have_errors_on(:product_id)
end
end
describe "associations" do
describe "#product" do
it {should respond_to(:product)}
its(:product) { should be_instance_of(ProductPresenter) }
context "references" do
specify "to Product" do
ci = valid_item(:product_id => product.id)
ci.product.id.should eql(product.id)
end
specify "to Subproduct if colour and/or size attributes set" do
ci = cart_item_with_subproduct colour_id: 333, size_id:5, price_eur_cents: 3990, price_chf_cents: 6990
ci.product.id.should eql(99)
ci.product.price_chf.to_s.should eql("69.90")
ci.product.price_eur.to_s.should eql("39.90")
end
end
end
describe "#colour" do
its(:colour) { should be_nil }
it "associates with colour if colour_id specified" do
colour = FactoryGirl.create(:colour)
subject.colour_id = colour.id
subject.colour.should eql(colour)
end
end
describe "#size" do
its(:size) { should be_nil }
it "associates with size if colour_id specified" do
size = FactoryGirl.create(:size_with_type)
subject.size_id = size.id
subject.size.should eql(size)
end
end
end
describe "when references to subproduct" do
it "should return its attributes" do
product.price_chf_cents = 1200
product.price_eur_cents = 900
product.save!
ci1 = cart_item_with_subproduct colour_id: 3, size_id: 5, price_chf_cents: 9996, price_eur_cents: 3999
# just to make sure that there is a reference to product
ci1.product.product.price_chf.to_s.should eql("12.00")
# now actual check
ci1.price_chf.to_s.should eql("99.96")
ci1.price_eur.to_s.should eql("39.99")
end
end
describe "callbacks" do
let(:cart) { Cart.new({:session_id => "e0d8ca9c9341ffad30fa03163b4414a1"}) }
describe "#after_add" do
context "set subproduct_id when" do
specify "colour and size provided" do
ci = cart_item_with_subproduct colour_id: 3, size_id: 5
ci.subproduct_id.should eql(99)
end
specify "only colour provided" do
ci = cart_item_with_subproduct colour_id: 3, size_id: nil
ci.subproduct_id.should eql(99)
end
specify "only size provided" do
ci = cart_item_with_subproduct colour_id: nil, size_id: 5
ci.subproduct_id.should eql(99)
end
end
it "leaves subproduct_id empty if no colour/size specified" do
ci = valid_item(colour_id: nil, size_id: nil)
ci.after_add
ci.subproduct_id.should be_nil
end
end
end
describe "#id" do
before(:each) do
CartItem.any_instance.stub(:product_id).and_return(999)
end
it "is a combination of product_id, colour_id and size_id" do
item = valid_item(colour_id: 123, size_id: 555)
item.id.should be_instance_of(Fixnum)
item.id.should eql("999123555".to_i)
end
it "replaces nil with 0" do
item = valid_item(colour_id: 123, size_id: nil)
item.id.should eql("9991230".to_i)
end
it "replaces nil with 0" do
item = valid_item(colour_id: nil, size_id: 555)
item.id.should eql("9990555".to_i)
end
end
describe "#serialize_item" do
it "converts to hash of simple data structures and removes #cart reference" do
serialized = subject.serialize
serialized.should be_instance_of(Hash)
serialized["product_id"].should eql(product.id)
serialized["qty"].should eql(1)
serialized["cart"].should be_nil
# make sure there is only strings, integers and nils left
serialized.each do |k,v|
[String, Fixnum, NilClass].should include(v.class), "#{k} isn't simple data struct"
end
end
it "holds subproduct_id too" do
ci = cart_item_with_subproduct colour_id: 333, size_id:5
serialized = ci.serialize
serialized["subproduct_id"].should eql(99)
end
end
describe "#price" do
it "uses cart currency" do
subject.cart.stub(:currency).and_return(:chf)
subject.price.currency.id.should eql(:chf)
subject.cart.stub(:currency).and_return(:eur)
subject.price.currency.id.should eql(:eur)
end
end
describe "#total" do
it "multiplie price by qty" do
subject.stub(:price).and_return(300)
subject.stub(:qty).and_return(3)
subject.total.should eql(900)
end
end
describe "#subproduct?" do
it "detects if current item is subproduct based on colour_id or size_id presense" do
valid_item(:colour_id => nil, :size_id => nil).subproduct?.should be_false
valid_item(:colour_id => 555, :size_id => nil).subproduct?.should be_true
valid_item(:colour_id => nil, :size_id => 333).subproduct?.should be_true
end
end
describe "#method_missing" do
it "proxy calls to product presenter " do
ProductPresenter.any_instance.stub(:formatted_price).and_return("CHF 55.99")
subject.formatted_price.should eql("CHF 55.99")
end
end
end
class CartItemsController < ApplicationController
def index
@cart = Cart.new(session)
respond_to do |format|
format.html
format.json { render cart.to_json }
end
end
def create
@item = CartItem.new(params[:cart_item])
cart = Cart.new session
respond_to do |format|
if cart << @item
# in case it was existing item get it again
# there may be different qty
@item = cart.find(@item.id)
format.html { redirect_to product_url(@item.product), notice: I18n.translate("cart.added", :product => @item.title) }
format.json { render json: {item: @item.as_json, cart: cart.as_json}, status: :created, location: product_url(@item.product_id) }
else
format.html { redirect_to :back, alert: I18n.translate("cart.error") }
format.json { render json: @item.errors, status: :unprocessable_entity }
end
end
end
def update
cart = Cart.new(session)
id = params[:id].to_i
qty = params[:op].to_i
if qty > 0 then cart.inc(id, qty) end
if qty < 0 then cart.dec(id, qty.abs) end
respond_to do |format|
format.html { redirect_to :back, notice: I18n.translate("cart.updated") }
format.json { render :json => {item: cart.find(id).as_json, cart: cart.as_json} }
end
end
def destroy
cart = Cart.new(session)
@item = cart.remove(params[:id].to_i)
respond_to do |format|
format.html { redirect_to :back, notice: I18n.translate("cart.removed", :product => @item) }
format.json { render :json => cart.as_json }
end
end
def clear
cart = Cart.new session
if cart.order_id
current_order = current_user.account.orders.where(id: cart.order_id).first
if current_order
current_order.destroy
end
end
cart.clear!
respond_to do |format|
format.html { redirect_to :back, notice: I18n.translate("cart.cleared") }
format.json { render :json => cart.as_json }
end
end
end
class OrdersController < ApplicationController
before_filter :initialize_cart
before_filter :redirect_empty_cart, :only => [:new, :create]
before_filter :redirect_invalid_order_id, :only => [:payment, :pay]
def new
if signed_in?
@user = current_user
else
@user = User.new
@user.build_account
session[:continue_order_after_sign_in] = 1
end
@order = @user.account.orders.build
@order.populate_from(@user.account)
end
def create
if signed_in?
@user = current_user
else
@user = User.new
@user.build_account
end
@order = @user.account.orders.new(permitted_params.checkout)
@order.setup @cart.to_order
@order.ip = request.remote_ip
@order.useragent = request.env['HTTP_USER_AGENT']
# if editing order
if signed_in? && @cart.order_id
# find recently created order
current_order = current_user.account.orders.where(id: @cart.order_id).first
if current_order
# destroy it and create new one with the same id
# to avoid messing with updated items or attributes
@order.id = current_order.id
current_order.destroy
end
end
respond_to do |format|
if @order.valid?
if @user.new_record?
@user.account.populate_from(@order)
@user.setup!(email: @order.email)
@user.save
sign_in(:user, @user)
AccountMailer.welcome(@user.account, @user.password).deliver
end
@order.email = nil
@order.save
# set current order in case user want to get back and edit items or info
@cart.update_attributes(order_id: @order.id)
session.delete :continue_order_after_sign_in
format.html { redirect_to payment_order_path(@order) }
format.json { render json: @order, status: :created, location: payment_order_path }
else
format.html do
flash.now[:alert] = I18n.t('checkout.form_error')
render action: "new"
end
format.json { render json: @order.errors, status: :unprocessable_entity }
end
end
end
def payment
@order = current_order
end
def pay
@order = current_order
@order.payment_method = params[:order][:payment_method]
case @order.payment_method
when 'prepay'
@order.status = :new
if @order.save
order_success
else
order_failure
end
when 'postpay'
@order.status = :pending
pp = Postpay.new
redirect_url = pp.submit_cart_request(@order)
if redirect_url
@order.save
redirect_to redirect_url
elsif pp.already_sent?
redirect_url = pp.continue_order(@order)
redirect_to redirect_url
else
order_failure(pp.error)
end
else
order_failure
end
end
def order_success
@cart.clear!
OrderMailer.receipt(@order).deliver
OrderMailer.admin_notice(@order).deliver
redirect_to confirmation_order_path(@order), notice: I18n.t('checkout.order_success')
end
private :order_success
def order_failure(error_message = I18n.t('checkout.error'))
redirect_to payment_order_path(params[:id].to_i), alert: error_message
end
private :order_failure
def postpay_success
# check order status to make sure it has been paid
# then redirect or display error
@order = current_order
@order.status = :new
@order.save
order_success
end
def postpay_error
order_failure
end
def postpay_back
redirect_to payment_order_path(params[:id].to_i)
end
def confirmation
@order = current_order
end
private
def initialize_cart
@cart = Cart.new session
end
def current_order
current_user.account.orders.find(params[:id].to_i)
end
def redirect_empty_cart
if @cart.items.count == 0
redirect_to cart_items_path, alert: I18n.t('cart.empty') unless Rails.env.test?
end
end
def redirect_invalid_order_id
if @cart.order_id != params[:id].to_i
redirect_to new_order_path, alert: I18n.t('checkout.error') unless Rails.env.test?
end
end
end
require 'spec_helper'
describe OrdersController do
describe "GET 'new'" do
it "returns http success" do
get :new
response.should be_success
response.should render_template(:new)
end
end
describe "POST 'create'" do
let (:valid_params) do
{
title: "mr",
lastname: "Super",
firstname: "Hero",
email: "[email protected]",
phone: "+7-SUPER-HERO",
mobile: "",
company: "Avengers",
street: "Power st 777 apt. 4",
city: "Unrealville",
region: "",
postcode: "99999",
country: "Switzerland",
ship_to_billing: "0",
shipping_title: "mrs",
shipping_lastname: "Blade",
shipping_firstname: "Sonya",
shipping_street: "Magic st 211",
shipping_city: "",
shipping_region: "",
shipping_postcode: "",
shipping_country: "",
comment: "Thanks!",
terms: "1"
}
end
describe "with valid params" do
describe "new customer" do
it "creates new user and customer account and order" do
expect {
post :create, :order => valid_params
}.to change(Order, :count).by(1)
order = Order.last
order.firstname.should eql("Hero")
order.account.firstname.should eql("Hero")
order.account.lastname.should eql("Super")
order.account.email.should eql("[email protected]")
response.should redirect_to(payment_order_path(order))
end
end
describe "existing customer" do
before(:each) do
@user = FactoryGirl.create(:user, email: "[email protected]")
@account = @user.account
sign_in @user
end
it "creates new order, but not account or user records" do
expect {
post :create, :order => valid_params
}.to change(Order, :count).by(1)
Account.count.should be(1)
User.count.should be(1)
Order.last.account_id.should eql(@account.id)
end
end
end
describe "with invalid attributes" do
let (:invalid_params) do
{
company: "Test inc.",
account_attributes: {
user_attributes: { email: "[email protected]" }
}
}
end
it "renders :new and assign unsaved order" do
post :create, order: invalid_params
assigns(:order).new_record?.should be_true
response.should render_template(:new)
end
it "should not create any db records yet" do
post :create, order: invalid_params
User.count.should be(0)
Account.count.should be(0)
Order.count.should be(0)
OrderItem.count.should be(0)
end
end
end
let(:account) do
user = FactoryGirl.create(:user)
user.account
end
let(:order) { FactoryGirl.create(:order, account_id: account.id) }
let(:product) { FactoryGirl.create(:product) }
describe "order processing" do
before(:each) do
sign_in account.user
end
describe "GET 'payment'" do
it "returns http success" do
get :payment, id: order.id
response.should be_success
response.should render_template(:payment)
assigns(:order).should eql(order)
end
end
describe "POST 'pay'" do
describe "payment method" do
it "unknown should fail order" do
get :pay, id: order.id, order: {payment_method: "unknown"}
response.should redirect_to(payment_order_path(order.id))
end
describe "prepay" do
it "should place order" do
get :pay, id: order.id, order: {payment_method: "prepay"}
assigns(:order).status.should eql(:new)
mails_count = ActionMailer::Base.deliveries.count
ActionMailer::Base.deliveries[mails_count - 2].to.should eql([account.email])
ActionMailer::Base.deliveries.last.to.should eql([Shop.current.email])
response.should redirect_to(confirmation_order_path(order))
end
end
describe "postpay" do
it "redirects to postpay gate" do
postpay_url = "https://pp-api.mepa-home.de/frontend/checkout/overview.html"
Postpay.any_instance.stub(:submit_cart_request).and_return(postpay_url)
get :pay, id: order.id, order: {payment_method: "postpay"}
assigns(:order).status.should eql(:pending)
response.should redirect_to(postpay_url)
end
it "redirects to gate to continue order" do
continue_url = "https://pp-api.mepa-home.de/frontend/checkout/overview.html"
Postpay.any_instance.stub(:submit_cart_request).and_return(nil)
Postpay.any_instance.stub(:already_sent?).and_return(true)
Postpay.any_instance.stub(:continue_order).and_return(continue_url)
get :pay, id: order.id, order: {payment_method: "postpay"}
response.should redirect_to(continue_url)
end
it "redirects back if failed" do
continue_url = "https://pp-api.mepa-home.de/frontend/checkout/overview.html"
Postpay.any_instance.stub(:submit_cart_request).and_return(nil)
Postpay.any_instance.stub(:already_sent?).and_return(false)
get :pay, id: order.id, order: {payment_method: "postpay"}
response.should redirect_to(payment_order_path(order.id))
end
end
end
end
describe "returning from postpay" do
it "success" do
get :postpay_success, id: order.id
assigns(:order).status.should eql(:new)
mails_count = ActionMailer::Base.deliveries.count
ActionMailer::Base.deliveries[mails_count - 2].to.should eql([account.email])
ActionMailer::Base.deliveries.last.to.should eql([Shop.current.email])
response.should redirect_to(confirmation_order_path(order))
end
it "back" do
get :postpay_back, id: order.id
response.should redirect_to(payment_order_path(order.id))
end
it "error" do
get :postpay_error, id: order.id
response.should redirect_to(payment_order_path(order.id))
end
end
describe "GET 'confirmation'" do
it "returns http success" do
get :confirmation, id: order.id
response.should be_success
response.should render_template(:confirmation)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment