require 'authorize_net' module Caboose class CheckoutController < Caboose::ApplicationController before_filter :ensure_line_items, :only => [:step_one, :step_two] protect_from_forgery def ensure_line_items redirect_to '/checkout/empty' if @invoice.line_items.empty? end # @route GET /checkout/json def invoice_json render :json => @invoice.as_json( :include => [ :customer, :shipping_address, :billing_address, :invoice_transactions, { :line_items => { :include => { :variant => { :include => [ { :product_images => { :methods => :urls }}, { :product => { :include => { :product_images => { :methods => :urls }}}} ], :methods => :title } } } }, { :invoice_packages => { :include => [:shipping_package, :shipping_method] }}, { :discounts => { :include => :gift_card }} ] ) end # @route GET /checkout/stripe/json def stripe_json sc = @site.store_config u = logged_in_user render :json => { :stripe_key => sc.stripe_publishable_key.strip, :customer_id => u.stripe_customer_id, :card_last4 => u.card_last4, :card_brand => u.card_brand, :card_exp_month => u.card_exp_month, :card_exp_year => u.card_exp_year } end #=========================================================================== # Step 1 - Login or register # @route GET /checkout def index if logged_in? if @invoice.customer_id.nil? @invoice.customer_id = logged_in_user.id @invoice.save end #redirect_to '/checkout/addresses' #return @invoice.verify_invoice_packages # See if any there are any empty invoice packages #@invoice.invoice_packages.each do |op| # count = 0 # @invoice.line_items.each do |li| # count = count + 1 if li.invoice_package_id == op.id # end # op.destroy if count == 0 #end # ## See if any line items aren't associated with an invoice package #line_items_attached = true #@invoice.line_items.each do |li| # line_items_attached = false if li.invoice_package_id.nil? #end # #ops = @invoice.invoice_packages #if ops.count == 0 || !line_items_attached # @invoice.calculate # LineItem.where(:invoice_id => @invoice.id).update_all(:invoice_package_id => nil) # InvoicePackage.where(:invoice_id => @invoice.id).destroy_all # InvoicePackage.create_for_invoice(@invoice) #end #render :file => "caboose/checkout/checkout_#{@site.store_config.pp_name}" render :file => "caboose/checkout/checkout" end end # Step 3 - Shipping method # @route GET /checkout/shipping/json def shipping_json render :json => { :error => 'Not logged in.' } and return if !logged_in? render :json => { :error => 'No shippable items.' } and return if !@invoice.has_shippable_items? render :json => { :error => 'Empty shipping address.' } and return if @invoice.shipping_address.nil? @invoice.calculate #ops = @invoice.invoice_packages #if params[:recalculate_invoice_packages] || ops.count == 0 # # Remove any invoice packages # LineItem.where(:invoice_id => @invoice.id).update_all(:invoice_package_id => nil) # InvoicePackage.where(:invoice_id => @invoice.id).destroy_all # # # Calculate what shipping packages we'll need # InvoicePackage.create_for_invoice(@invoice) #end # Now get the rates for those packages rates = ShippingCalculator.rates(@invoice) render :json => rates end # Step 5 - Update Stripe Details # @route PUT /checkout/stripe-details def update_stripe_details render :json => false and return if !logged_in? sc = @site.store_config Stripe.api_key = sc.stripe_secret_key.strip u = logged_in_user c = nil if u.stripe_customer_id c = Stripe::Customer.retrieve(u.stripe_customer_id) begin c.source = params[:token] c.save rescue c = nil end end if c.nil? c = Stripe::Customer.create( :source => params[:token], :email => u.email, :metadata => { :user_id => u.id } ) end u.stripe_customer_id = c.id u.card_last4 = params[:card][:last4] u.card_brand = params[:card][:brand] u.card_exp_month = params[:card][:exp_month] u.card_exp_year = params[:card][:exp_year] u.save render :json => { :success => true, :customer_id => u.stripe_customer_id, :card_last4 => u.card_last4, :card_brand => u.card_brand, :card_exp_month => u.card_exp_month, :card_exp_year => u.card_exp_year } end # @route DELETE /checkout/payment-method def remove_payment_method render :json => false and return if !logged_in? resp = Caboose::StdClass.new sc = @site.store_config if sc.pp_name == 'stripe' Stripe.api_key = sc.stripe_secret_key.strip u = logged_in_user if u.stripe_customer_id begin c = Stripe::Customer.retrieve(u.stripe_customer_id) c.delete rescue Exception => ex Caboose.log(ex) resp.error = ex.message if !ex.message.starts_with?('No such customer') end if resp.error.nil? u.stripe_customer_id = nil u.card_last4 = nil u.card_brand = nil u.card_exp_month = nil u.card_exp_year = nil u.save else resp.success = true end end end render :json => resp end # @route POST /checkout/confirm def confirm render :json => { :error => 'Not logged in.' } and return if !logged_in? #render :json => { :error => 'Invalid billing address.' } and return if @invoice.billing_address.nil? render :json => { :error => 'Cart is empty.' } and return if @invoice.line_items.nil? or @invoice.line_items.count == 0 if !@invoice.instore_pickup && @invoice.has_shippable_items? render :json => { :error => 'Invalid shipping address.' } and return if @invoice.shipping_address.nil? render :json => { :error => 'Invalid shipping methods.' } and return if @invoice.has_empty_shipping_methods? end resp = Caboose::StdClass.new sc = @site.store_config @invoice.customer.create_variant_limits if @invoice.customer # Make sure all the variants still exist @invoice.line_items.each do |li| v = Variant.where(:id => li.variant_id).first vl = VariantLimit.where(:variant_id => v.id, :user_id => @invoice.customer_id).first if v.nil? || v.status == 'Deleted' render :json => { :error => 'One or more of the products you are purchasing are no longer available.' } return elsif v.quantity_in_stock < li.quantity render :json => { :error => "There are only #{v.quantity_in_stock} of #{v.full_title} available. Please update your quantity." } return elsif vl && !vl.qty_within_range(vl.current_value + li.quantity, @invoice) render :json => { :error => 'You have exceeded the limit you are allowed to purchase.' } return end end error = false requires_payment = @invoice.line_items.count > 0 && @invoice.total > 0 && @invoice.payment_terms == Invoice::PAYMENT_TERMS_PIA if requires_payment ot = nil case sc.pp_name when StoreConfig::PAYMENT_PROCESSOR_AUTHNET when StoreConfig::PAYMENT_PROCESSOR_STRIPE Stripe.api_key = sc.stripe_secret_key.strip begin c = Stripe::Charge.create( :amount => (@invoice.total * 100).to_i, :currency => 'usd', :customer => logged_in_user.stripe_customer_id, :capture => false, :metadata => { :invoice_id => @invoice.id }, :statement_descriptor => "Invoice ##{@invoice.id}" ) rescue Exception => ex render :json => { :error => ex.message } return end ot = Caboose::InvoiceTransaction.create( :invoice_id => @invoice.id, :transaction_id => c.id, :transaction_type => c.captured ? Caboose::InvoiceTransaction::TYPE_AUTHCAP : Caboose::InvoiceTransaction::TYPE_AUTHORIZE, :payment_processor => sc.pp_name, :amount => c.amount/100.0, :date_processed => DateTime.now.utc, :success => c.status == 'succeeded' ) end if !ot.success render :json => { :error => error } return else @invoice.financial_status = Invoice::FINANCIAL_STATUS_AUTHORIZED @invoice.take_gift_card_funds end end @invoice.status = Invoice::STATUS_PENDING @invoice.invoice_number = @site.store_config.next_invoice_number # Update variant limits to reflect this purchase @invoice.line_items.each do |li| vl = VariantLimit.where(:user_id => @invoice.customer_id, :variant_id => li.variant_id).first if vl vl.current_value += li.quantity vl.save end end # Send out emails begin InvoicesMailer.configure_for_site(@site.id).customer_new_invoice(@invoice).deliver InvoicesMailer.configure_for_site(@site.id).fulfillment_new_invoice(@invoice).deliver rescue puts "==================================================================" puts "Error sending out invoice confirmation emails for invoice ID #{@invoice.id}" puts "==================================================================" end # Emit invoice event Caboose.plugin_hook('invoice_authorized', @invoice) if @invoice.total > 0 # Save the invoice @invoice.save # Decrement quantities of variants @invoice.decrement_quantities # Clear the cart and re-initialize session[:cart_id] = nil init_cart resp.success = true resp.redirect = '/checkout/thanks' render :json => resp end # @route GET /checkout/thanks def thanks @logged_in_user = logged_in_user # Find the last invoice for the user @last_invoice = Invoice.where(:customer_id => @logged_in_user.id).reorder("id desc").limit(1).first add_ga_event('Ecommerce', 'Checkout', 'Payment', (@last_invoice.total*100).to_i) end #=========================================================================== # @route GET /checkout/state-options def state_options options = Caboose::States.all.collect { |abbr, state| { 'value' => abbr, 'text' => abbr }} render :json => options end # @route GET /checkout/total def verify_total total = 0.00 if logged_in? @invoice.calculate total = @invoice.total end render :json => total.to_f.round(2) end # @route GET /checkout/address def address render :json => { :shipping_address => @invoice.shipping_address, :billing_address => @invoice.billing_address } end # @route PUT /checkout/addresses def update_addresses # Grab or create addresses shipping_address = if @invoice.shipping_address then @invoice.shipping_address else Address.new end billing_address = if @invoice.billing_address then @invoice.billing_address else Address.new end has_shippable_items = @invoice.has_shippable_items? # Shipping address if has_shippable_items shipping_address.first_name = params[:shipping][:first_name] shipping_address.last_name = params[:shipping][:last_name] shipping_address.company = params[:shipping][:company] shipping_address.address1 = params[:shipping][:address1] shipping_address.address2 = params[:shipping][:address2] shipping_address.city = params[:shipping][:city] shipping_address.state = params[:shipping][:state] shipping_address.zip = params[:shipping][:zip] end # Billing address if has_shippable_items && params[:use_as_billing] billing_address.update_attributes(shipping_address.attributes) else billing_address.first_name = params[:billing][:first_name] billing_address.last_name = params[:billing][:last_name] billing_address.company = params[:billing][:company] billing_address.address1 = params[:billing][:address1] billing_address.address2 = params[:billing][:address2] billing_address.city = params[:billing][:city] billing_address.state = params[:billing][:state] billing_address.zip = params[:billing][:zip] end # Save address info; generate ids render :json => { :success => false, :errors => shipping_address.errors.full_messages, :address => 'shipping' } and return if has_shippable_items && !shipping_address.save render :json => { :success => false, :errors => billing_address.errors.full_messages, :address => 'billing' } and return if !billing_address.save # Associate address info with invoice @invoice.shipping_address_id = shipping_address.id @invoice.billing_address_id = billing_address.id #render :json => { :redirect => 'checkout/shipping' } render :json => { :success => @invoice.save, :errors => @invoice.errors.full_messages } end # @route PUT /checkout/shipping-address def update_shipping_address resp = Caboose::StdClass.new # Grab or create addresses sa = @invoice.shipping_address if sa.nil? sa = Address.create @invoice.shipping_address_id = sa.id @invoice.save end save = true recalc_shipping = false params.each do |name, value| case name when 'address1' then recalc_shipping = true if sa.address1 != value when 'address2' then recalc_shipping = true if sa.address2 != value when 'city' then recalc_shipping = true if sa.city != value when 'state' then recalc_shipping = true if sa.state != value when 'zip' then recalc_shipping = true if sa.zip != value end case name when 'name' then sa.name = value when 'first_name' then sa.first_name = value when 'last_name' then sa.last_name = value when 'street' then sa.street = value when 'address1' then sa.address1 = value when 'address2' then sa.address2 = value when 'company' then sa.company = value when 'city' then sa.city = value when 'state' then sa.state = value when 'province' then sa.province = value when 'province_code' then sa.province_code = value when 'zip' then sa.zip = value when 'country' then sa.country = value when 'country_code' then sa.country_code = value when 'phone' then sa.phone = value end end if recalc_shipping @invoice.invoice_packages.each do |op| op.shipping_method_id = nil op.total = nil op.save end end resp.success = save && sa.save render :json => resp end # @route PUT /checkout/billing-address def update_billing_address # Grab or create addresses ba = @invoice.billing_address if ba.nil? ba = Address.create @invoice.billing_address_id = ba.id @invoice.save end ba.first_name = params[:first_name] ba.last_name = params[:last_name] ba.company = params[:company] ba.address1 = params[:address1] ba.address2 = params[:address2] ba.city = params[:city] ba.state = params[:state] ba.zip = params[:zip] ba.save render :json => { :success => true } end # @route PUT /checkout/invoice def update_invoice render :json => false and return if !logged_in? resp = Caboose::StdClass.new params.each do |k,v| case k when 'instore_pickup' @invoice.instore_pickup = v @invoice.save @invoice.invoice_packages.each do |ip| ip.instore_pickup = v ip.save end @invoice.calculate end end resp.success = true render :json => resp end # @route POST /checkout/attach-user def attach_user render :json => { :success => false, :errors => ['User is not logged in'] } and return if !logged_in? @invoice.customer_id = logged_in_user.id #Caboose.log("Attaching user to invoice: customer_id = #{@invoice.customer_id}") render :json => { :success => @invoice.save, :errors => @invoice.errors.full_messages, :logged_in => logged_in? } end # @route POST /checkout/guest def attach_guest resp = Caboose::StdClass.new email = params[:email] if email != params[:confirm_email] resp.error = "Emails do not match." elsif Caboose::User.where(:email => email, :is_guest => false).exists? resp.error = "A user with that email address already exists." else user = Caboose::User.where(:email => email, :is_guest => true).first if user.nil? user = Caboose::User.create(:email => email) user.is_guest = true user.save user = Caboose::User.where(:email => email).first end @invoice.customer_id = user.id login_user(user) if !@invoice.valid? resp.errors = @invoice.errors.full_messages else @invoice.save resp.redirect = '/checkout/addresses' end end render :json => resp end # @route PUT /checkout/shipping def update_shipping op = InvoicePackage.find(params[:invoice_package_id]) op.shipping_method_id = params[:shipping_method_id] op.total = params[:total] op.save op.invoice.calculate render :json => { :success => true } end end end