You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

472 lines
18 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
10 years ago
11 years ago
10 years ago
10 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. module Spree
  2. CheckoutController.class_eval do
  3. before_filter :redirect_to_paypal_express_form_if_needed, :only => [:update]
  4. def paypal_checkout
  5. load_order
  6. opts = all_opts(@order, params[:payment_method_id], 'checkout')
  7. opts.merge!(address_options(@order))
  8. @gateway = paypal_gateway
  9. if Spree::Config[:auto_capture]
  10. @ppx_response = @gateway.setup_purchase(opts[:money], opts)
  11. else
  12. @ppx_response = @gateway.setup_authorization(opts[:money], opts)
  13. end
  14. unless @ppx_response.success?
  15. gateway_error(@ppx_response)
  16. redirect_to edit_order_url(@order)
  17. return
  18. end
  19. redirect_to(@gateway.redirect_url_for(response.token, :review => payment_method.preferred_review))
  20. rescue ActiveMerchant::ConnectionError => e
  21. gateway_error I18n.t(:unable_to_connect_to_gateway)
  22. redirect_to :back
  23. end
  24. def paypal_payment
  25. load_order
  26. opts = all_opts(@order,params[:payment_method_id], 'payment')
  27. unless payment_method.preferred_cart_checkout
  28. opts.merge!(address_options(@order))
  29. else
  30. opts.merge!(shipping_options)
  31. end
  32. @gateway = paypal_gateway
  33. if Spree::Config[:auto_capture]
  34. @ppx_response = @gateway.setup_purchase(opts[:money], opts)
  35. else
  36. @ppx_response = @gateway.setup_authorization(opts[:money], opts)
  37. end
  38. unless @ppx_response.success?
  39. gateway_error(@ppx_response)
  40. redirect_to edit_order_checkout_url(@order, :state => "payment")
  41. return
  42. end
  43. redirect_to(@gateway.redirect_url_for(@ppx_response.token, :review => payment_method.preferred_review))
  44. rescue ActiveMerchant::ConnectionError => e
  45. gateway_error I18n.t(:unable_to_connect_to_gateway)
  46. redirect_to :back
  47. end
  48. def paypal_confirm
  49. load_order
  50. opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment')
  51. gateway = paypal_gateway
  52. @ppx_details = gateway.details_for params[:token]
  53. if @ppx_details.success?
  54. # now save the updated order info
  55. #TODO Search for existing records
  56. Spree::PaypalAccount.create(:email => @ppx_details.params["payer"],
  57. :payer_id => @ppx_details.params["payer_id"],
  58. :payer_country => @ppx_details.params["payer_country"],
  59. :payer_status => @ppx_details.params["payer_status"])
  60. @order.special_instructions = @ppx_details.params["note"]
  61. unless payment_method.preferred_no_shipping
  62. ship_address = @ppx_details.address
  63. order_ship_address = Spree::Address.new :firstname => @ppx_details.params["first_name"],
  64. :lastname => @ppx_details.params["last_name"],
  65. :address1 => ship_address["address1"],
  66. :address2 => ship_address["address2"],
  67. :city => ship_address["city"],
  68. :country => Spree::Country.find_by_iso(ship_address["country"]),
  69. :zipcode => ship_address["zip"],
  70. # phone is currently blanked in AM's PPX response lib
  71. :phone => @ppx_details.params["phone"] || "(not given)"
  72. state = Spree::State.find_by_abbr(ship_address["state"].upcase) if ship_address["state"].present?
  73. if state
  74. order_ship_address.state = state
  75. else
  76. order_ship_address.state_name = ship_address["state"]
  77. end
  78. order_ship_address.save!
  79. @order.ship_address = order_ship_address
  80. @order.bill_address ||= order_ship_address
  81. #Add Instant Update Shipping
  82. if payment_method.preferred_cart_checkout
  83. add_shipping_charge
  84. end
  85. end
  86. @order.state = "payment"
  87. @order.save
  88. if payment_method.preferred_review
  89. @order.next
  90. render 'spree/shared/paypal_express_confirm'
  91. else
  92. paypal_finish
  93. end
  94. else
  95. gateway_error(@ppx_details)
  96. #Failed trying to get payment details from PPX
  97. redirect_to edit_order_checkout_url(@order, :state => "payment")
  98. end
  99. rescue ActiveMerchant::ConnectionError => e
  100. gateway_error I18n.t(:unable_to_connect_to_gateway)
  101. redirect_to edit_order_url(@order)
  102. end
  103. def paypal_finish
  104. load_order
  105. opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment' )
  106. gateway = paypal_gateway
  107. method = Spree::Config[:auto_capture] ? :purchase : :authorize
  108. ppx_auth_response = gateway.send(method, (@order.total*100).to_i, opts)
  109. paypal_account = Spree::PaypalAccount.find_by_payer_id(params[:PayerID])
  110. payment = @order.payments.create(
  111. :amount => ppx_auth_response.params["gross_amount"].to_f,
  112. :source => paypal_account,
  113. :source_type => 'Spree::PaypalAccount',
  114. :payment_method_id => params[:payment_method_id],
  115. :response_code => ppx_auth_response.authorization,
  116. :avs_response => ppx_auth_response.avs_result["code"])
  117. payment.started_processing!
  118. record_log payment, ppx_auth_response
  119. if ppx_auth_response.success?
  120. #confirm status
  121. case ppx_auth_response.params["payment_status"]
  122. when "Completed"
  123. payment.complete!
  124. when "Pending"
  125. payment.pend!
  126. else
  127. payment.pend!
  128. Rails.logger.error "Unexpected response from PayPal Express"
  129. Rails.logger.error ppx_auth_response.to_yaml
  130. end
  131. @order.update_attributes({:state => "complete", :completed_at => Time.now}, :without_protection => true)
  132. state_callback(:after) # So that after_complete is called, setting session[:order_id] to nil
  133. # Since we dont rely on state machine callback, we just explicitly call this method for spree_store_credits
  134. if @order.respond_to?(:consume_users_credit, true)
  135. @order.send(:consume_users_credit)
  136. end
  137. @order.finalize!
  138. flash[:notice] = I18n.t(:order_processed_successfully)
  139. redirect_to completion_route
  140. else
  141. payment.failure!
  142. order_params = {}
  143. gateway_error(ppx_auth_response)
  144. #Failed trying to complete pending payment!
  145. redirect_to edit_order_checkout_url(@order, :state => "payment")
  146. end
  147. rescue ActiveMerchant::ConnectionError => e
  148. gateway_error I18n.t(:unable_to_connect_to_gateway)
  149. redirect_to edit_order_url(@order)
  150. end
  151. private
  152. def asset_url(_path)
  153. URI::HTTP.build(:path => ActionController::Base.helpers.asset_path(_path), :host => Spree::Config[:site_url].strip).to_s
  154. end
  155. def record_log(payment, response)
  156. payment.log_entries.create(:details => response.to_yaml)
  157. end
  158. def redirect_to_paypal_express_form_if_needed
  159. return unless (params[:state] == "payment")
  160. return unless params[:order][:payments_attributes]
  161. if @order.update_attributes(object_params)
  162. if params[:order][:coupon_code] and !params[:order][:coupon_code].blank? and @order.coupon_code.present?
  163. event_name = "spree.checkout.coupon_code_added"
  164. if promo = Spree::Promotion.with_coupon_code(@order.coupon_code).where(:event_name => event_name).first
  165. fire_event(event_name, :coupon_code => @order.coupon_code)
  166. else
  167. flash[:error] = t(:promotion_not_found)
  168. render :edit and return
  169. end
  170. end
  171. end
  172. load_order
  173. payment_method = Spree::PaymentMethod.find(params[:order][:payments_attributes].first[:payment_method_id])
  174. if payment_method.kind_of?(Spree::BillingIntegration::PaypalExpress) || payment_method.kind_of?(Spree::BillingIntegration::PaypalExpressUk)
  175. redirect_to(paypal_payment_order_checkout_url(@order, :payment_method_id => payment_method.id)) and return
  176. end
  177. end
  178. def fixed_opts
  179. if Spree::PaypalExpress::Config[:paypal_express_local_confirm].nil?
  180. user_action = "continue"
  181. else
  182. user_action = Spree::PaypalExpress::Config[:paypal_express_local_confirm] == "t" ? "continue" : "commit"
  183. end
  184. #asset_url doesn't like Spree::Config[:logo] being an absolute url
  185. #if statement didn't work within hash
  186. if URI.parse(Spree::Config[:logo]).absolute?
  187. chosen_image = Spree::Config[:logo]
  188. else
  189. chosen_image = asset_url(Spree::Config[:logo])
  190. end
  191. { :description => "Goods from #{Spree::Config[:site_name]}", # site details...
  192. #:page_style => "foobar", # merchant account can set named config
  193. :background_color => "ffffff", # must be hex only, six chars
  194. :header_background_color => "ffffff",
  195. :header_border_color => "ffffff",
  196. :header_image => chosen_image,
  197. :allow_note => true,
  198. :locale => user_locale,
  199. :req_confirm_shipping => false, # for security, might make an option later
  200. :user_action => user_action
  201. # WARNING -- don't use :ship_discount, :insurance_offered, :insurance since
  202. # they've not been tested and may trigger some paypal bugs, eg not showing order
  203. # see http://www.pdncommunity.com/t5/PayPal-Developer-Blog/Displaying-Order-Details-in-Express-Checkout/bc-p/92902#C851
  204. }
  205. end
  206. def user_locale
  207. I18n.locale.to_s
  208. end
  209. # hook to override paypal site options
  210. def paypal_site_opts
  211. {:currency => payment_method.preferred_currency, :allow_guest_checkout => payment_method.preferred_allow_guest_checkout }
  212. end
  213. def order_opts(order, payment_method, stage)
  214. items = order.line_items.map do |item|
  215. price = (item.price * 100).to_i # convert for gateway
  216. { :name => item.variant.product.name,
  217. :description => (item.variant.product.description[0..120] if item.variant.product.description),
  218. :number => item.variant.sku,
  219. :quantity => item.quantity,
  220. :amount => price,
  221. :weight => item.variant.weight,
  222. :height => item.variant.height,
  223. :width => item.variant.width,
  224. :depth => item.variant.weight }
  225. end
  226. credits = order.adjustments.eligible.map do |credit|
  227. if credit.amount < 0.00
  228. { :name => credit.label,
  229. :description => credit.label,
  230. :sku => credit.id,
  231. :quantity => 1,
  232. :amount => (credit.amount*100).to_i }
  233. end
  234. end
  235. credits_total = 0
  236. credits.compact!
  237. if credits.present?
  238. items.concat credits
  239. credits_total = credits.map {|i| i[:amount] * i[:quantity] }.sum
  240. end
  241. unless @order.payment_method.preferred_cart_checkout
  242. order_total = (order.total * 100).to_i
  243. shipping_total = (order.ship_total*100).to_i
  244. else
  245. shipping_cost = shipping_options[:shipping_options].first[:amount]
  246. order_total = (order.total * 100 + (shipping_cost)).to_i
  247. shipping_total = (shipping_cost).to_i
  248. end
  249. opts = { :return_url => paypal_confirm_order_checkout_url(order, :payment_method_id => payment_method),
  250. :cancel_return_url => edit_order_checkout_url(order, :state => :payment),
  251. :order_id => order.number,
  252. :custom => order.number,
  253. :items => items,
  254. :subtotal => ((order.item_total * 100) + credits_total).to_i,
  255. :tax => (order.tax_total*100).to_i,
  256. :shipping => shipping_total,
  257. :money => order_total,
  258. :max_amount => (order.total * 300).to_i}
  259. if stage == "checkout"
  260. opts[:handling] = 0
  261. opts[:callback_url] = spree_root_url + "paypal_express_callbacks/#{order.number}"
  262. opts[:callback_timeout] = 3
  263. elsif stage == "payment"
  264. #hack to add float rounding difference in as handling fee - prevents PayPal from rejecting orders
  265. #because the integer totals are different from the float based total. This is temporary and will be
  266. #removed once Spree's currency values are persisted as integers (normally only 1c)
  267. if @order.payment_method.preferred_cart_checkout
  268. opts[:handling] = 0
  269. else
  270. opts[:handling] = (order.total*100).to_i - opts.slice(:subtotal, :tax, :shipping).values.sum
  271. end
  272. end
  273. opts
  274. end
  275. def shipping_options
  276. #Uses users address if exists, if not uses first shipping method
  277. if (current_user.present? && current_user.addresses.present?)
  278. estimate_shipping_for_user
  279. shipping_default = @rate_hash_user.map.with_index do |shipping_method, idx|
  280. { :default => (idx == 0 ? true : false),
  281. :name => shipping_method.name,
  282. :amount => (shipping_method.cost*100).to_i }
  283. end
  284. else
  285. shipping_method = ShippingMethod.all.first
  286. shipping_default = [{ :default => true,
  287. :name => shipping_method.name,
  288. :amount => ((shipping_method.calculator.compute(self).to_f) * 100).to_i }]
  289. end
  290. {
  291. :callback_url => spree_root_url + "paypal_shipping_update",
  292. :callback_timeout => 6,
  293. :callback_version => '61.0',
  294. :shipping_options => shipping_default
  295. }
  296. end
  297. def address_options(order)
  298. if payment_method.preferred_no_shipping
  299. { :no_shipping => true }
  300. else
  301. {
  302. :no_shipping => false,
  303. :address_override => true,
  304. :address => {
  305. :name => "#{order.ship_address.firstname} #{order.ship_address.lastname}",
  306. :address1 => order.ship_address.address1,
  307. :address2 => order.ship_address.address2,
  308. :city => order.ship_address.city,
  309. :state => order.ship_address.state.nil? ? order.ship_address.state_name.to_s : order.ship_address.state.abbr,
  310. :country => order.ship_address.country.iso,
  311. :zip => order.ship_address.zipcode,
  312. :phone => order.ship_address.phone
  313. }
  314. }
  315. end
  316. end
  317. def all_opts(order, payment_method, stage=nil)
  318. opts = fixed_opts.merge(order_opts(order, payment_method, stage)).merge(paypal_site_opts)
  319. if stage == "payment"
  320. opts.merge! flat_rate_shipping_and_handling_options(order, stage)
  321. end
  322. # suggest current user's email or any email stored in the order
  323. opts[:email] = spree_current_user ? spree_current_user.email : order.email
  324. opts
  325. end
  326. # hook to allow applications to load in their own shipping and handling costs
  327. def flat_rate_shipping_and_handling_options(order, stage)
  328. # max_fallback = 0.0
  329. # shipping_options = ShippingMethod.all.map do |shipping_method|
  330. # { :name => "#{shipping_method.name}",
  331. # :amount => (shipping_method.rate),
  332. # :default => shipping_method.is_default }
  333. # end
  334. # default_shipping_method = ShippingMethod.find(:first, :conditions => {:is_default => true})
  335. # opts = { :shipping_options => shipping_options,
  336. # }
  337. # #opts[:shipping] = (default_shipping_method.nil? ? 0 : default_shipping_method.fallback_amount) if stage == "checkout"
  338. # opts
  339. {}
  340. end
  341. def gateway_error(response)
  342. if response.is_a? ActiveMerchant::Billing::Response
  343. text = response.params['message'] ||
  344. response.params['response_reason_text'] ||
  345. response.message
  346. else
  347. text = response.to_s
  348. end
  349. msg = "#{I18n.t('gateway_error')}: #{text}"
  350. logger.error(msg)
  351. flash[:error] = msg
  352. end
  353. # create the gateway from the supplied options
  354. def payment_method
  355. @payment_method ||= Spree::PaymentMethod.find(params[:payment_method_id])
  356. end
  357. def paypal_gateway
  358. payment_method.provider
  359. end
  360. def add_shipping_charge
  361. # Replace with these changes once Active_Merchant pushes pending pull request
  362. # shipment_name = @ppx_details.shipping['amount'].chomp(" Shipping")
  363. # shipment_cost = @ppx_details.shipping['name'].to_f
  364. shipment_name = @ppx_details.params['UserSelectedOptions']['ShippingOptionName'].chomp(" Shipping")
  365. shipment_cost = @ppx_details.params['UserSelectedOptions']['ShippingOptionAmount'].to_f
  366. if @order.shipping_method_id.blank? && @order.rate_hash.present?
  367. selected_shipping = @order.rate_hash.detect { |v| v['name'] == shipment_name && v['cost'] == shipment_cost }
  368. @order.shipping_method_id = selected_shipping.id
  369. end
  370. @order.shipments.each { |s| s.destroy unless s.shipping_method.available_to_order?(@order) }
  371. @order.create_shipment!
  372. @order.update!
  373. end
  374. def estimate_shipping_for_user
  375. zipcode = current_user.addresses.first.zipcode
  376. country = current_user.addresses.first.country.iso
  377. shipping_methods = Spree::ShippingMethod.all
  378. #TODO remove hard coded shipping
  379. #Make a deep copy of the order object then stub out the parts required to get a shipping quote
  380. @shipping_order = Marshal::load(Marshal.dump(@order)) #Make a deep copy of the order object
  381. @shipping_order.ship_address = Spree::Address.new(:country => Spree::Country.find_by_iso(country), :zipcode => zipcode)
  382. shipment = Spree::Shipment.new(:address => @shipping_order.ship_address)
  383. @shipping_order.ship_address.shipments<<shipment
  384. @shipping_order.shipments<<shipment
  385. @rate_hash_user = @shipping_order.rate_hash
  386. #TODO
  387. end
  388. end
  389. end