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.

175 lines
7.7 KiB

13 years ago
  1. # WARNING: the details of UK tax and my site's shipping are a bit hard-coded here for now
  2. # aim to unpick this later
  3. module Spree::PaypalExpress
  4. include ERB::Util
  5. include Spree::PaymentGateway
  6. include Spree::PaypalExpress::Gateway
  7. def fixed_opts
  8. { :description => "Goods from a Spree-based site", # site details...
  9. #:page_style => "foobar", # merchant account can set named config
  10. :header_image => "https://" + Spree::Config[:site_url] + "/images/logo.png",
  11. :background_color => "e1e1e1", # must be hex only, six chars
  12. :header_background_color => "ffffff",
  13. :header_border_color => "00735a",
  14. :allow_note => true,
  15. :locale => Spree::Config[:default_locale],
  16. :notify_url => 'to be done', # this is a callback
  17. :req_confirm_shipping => false, # for security, might make an option later
  18. }
  19. end
  20. # TODO: generalise the tax and shipping calcs
  21. # might be able to get paypal to do some of the shipping choice and costing
  22. def order_opts(order)
  23. items = order.line_items.map do |item|
  24. { :name => item.variant.product.name,
  25. :description => item.variant.product.description[0..120],
  26. :sku => item.variant.sku,
  27. :qty => item.quantity,
  28. :amount => item.price - 0.15 * item.price, # avoid some rounding err, more needed
  29. :tax => 0.15 * item.price,
  30. :weight => item.variant.weight,
  31. :height => item.variant.height,
  32. :width => item.variant.width,
  33. :depth => item.variant.weight }
  34. end
  35. opts = { :return_url => request.protocol + request.host_with_port + "/orders/#{order.number}/paypal_finish",
  36. :cancel_return_url => "http://" + request.host_with_port + "/orders/#{order.number}/edit",
  37. :order_id => order.number,
  38. :custom => order.number,
  39. # :no_shipping => false,
  40. # :address_override => false,
  41. :items => items,
  42. :subtotal => items.map {|i| i[:amount] * i[:qty] }.sum,
  43. :handling => 0,
  44. :tax => items.map {|i| i[:tax] * i[:qty]}.sum
  45. # WARNING -- don't use :ship_discount, => :insurance_offered, :insurance since
  46. # they've not been tested and may trigger some paypal bugs, eg not showing order
  47. # see http://www.pdncommunity.com/t5/PayPal-Developer-Blog/Displaying-Order-Details-in-Express-Checkout/bc-p/92902#C851
  48. }
  49. opts[:email] = current_user.email if current_user
  50. opts
  51. end
  52. def all_opts(order)
  53. shipping_cost = NetstoresShipping::Calculator.calculate_order_shipping(order)
  54. opts = fixed_opts.merge(:shipping => shipping_cost).merge(order_opts order)
  55. # WARNING: paypal expects this sum to work (TODO: shift to AM code? and throw wobbly?)
  56. # however: might be rounding issues when it comes to tax, though you can capture slightly extra
  57. opts[:money] = opts.slice(:subtotal, :shipping, :handling, :tax).values.sum
  58. if opts[:money] != order.total
  59. raise "Ouch - precision problems: #{opts[:money]} vs #{order.total}"
  60. end
  61. [:money, :subtotal, :shipping, :handling, :tax].each {|amt| opts[amt] *= 100}
  62. opts[:items].each {|item| [:amount,:tax].each {|amt| item[amt] *= 100} }
  63. opts
  64. end
  65. def paypal_checkout
  66. # need build etc? at least to finalise the total?
  67. gateway = paypal_gateway
  68. opts = all_opts(@order)
  69. response = gateway.setup_authorization(opts[:money], opts)
  70. gateway_error(response) unless response.success?
  71. redirect_to (gateway.redirect_url_for response.token)
  72. end
  73. def paypal_finish
  74. gateway = paypal_gateway
  75. opts = { :token => params[:token],
  76. :payer_id => params[:PayerID] }.merge all_opts(@order)
  77. info = gateway.details_for params[:token]
  78. response = gateway.authorize(opts[:money], opts)
  79. gateway_error(response) unless response.success?
  80. # now save info
  81. order = Order.find_by_number(params[:id])
  82. order.checkout.email = info.email
  83. order.checkout.special_instructions = info.params["note"]
  84. ship_address = info.address
  85. order_ship_address = Address.create :firstname => info.params["first_name"],
  86. :lastname => info.params["last_name"],
  87. :address1 => ship_address["address1"],
  88. :address2 => ship_address["address2"],
  89. :city => ship_address["city"],
  90. :state => State.find_by_name(ship_address["state"]),
  91. :country => Country.find_by_iso(ship_address["country"]),
  92. :zipcode => ship_address["zip"],
  93. :phone => ship_address["phone"] || "(not given)"
  94. order.checkout.update_attributes :ship_address => order_ship_address,
  95. :shipping_method => ShippingMethod.first # TODO: refine/choose
  96. fake_card = Creditcard.new :checkout => order.checkout,
  97. :cc_type => "visa", # hands are tied
  98. :month => Time.now.month,
  99. :year => Time.now.year,
  100. :first_name => info.params["first_name"],
  101. :last_name => info.params["last_name"],
  102. :display_number => "paypal:" + info.payer_id
  103. payment = order.paypal_payments.create(:amount => response.params["gross_amount"].to_i || 999,
  104. :creditcard => fake_card)
  105. # query - need 0 in amount for an auth? see main code
  106. transaction = CreditcardTxn.new( :amount => response.params["gross_amount"].to_i || 999,
  107. :response_code => response.authorization,
  108. :txn_type => CreditcardTxn::TxnType::AUTHORIZE)
  109. payment.creditcard_txns << transaction
  110. order.save!
  111. order.complete # get return of status? throw of problems??? else weak go-ahead
  112. session[:order_id] = nil if order.checkout_complete
  113. redirect_to order_url(order, :checkout_complete => true, :order_token => session[:order_token])
  114. end
  115. def do_capture(authorization)
  116. response = paypal_gateway.capture((100 * authorization.amount).to_i, authorization.response_code)
  117. gateway_error(response) unless response.success?
  118. # TODO needs to be cleaned up or recast...
  119. payment = PaypalPayment.find(authorization.creditcard_payment_id)
  120. # create a transaction to reflect the capture
  121. payment.txns << CreditcardTxn.new( :amount => authorization.amount,
  122. :response_code => response.authorization,
  123. :txn_type => CreditcardTxn::TxnType::CAPTURE )
  124. end
  125. private
  126. # copied from main spree code, and slightly tweaked
  127. def paypal_gateway
  128. #? return Spree::BogusGateway.new if ENV['RAILS_ENV'] == "development" and Spree::Gateway::Config[:use_bogus]
  129. paypal_gw = ::Gateway.find_by_name("Paypal Express UK")
  130. gateway_config = GatewayConfiguration.find_by_gateway_id(paypal_gw.id)
  131. config_options = {}
  132. gateway_config.gateway_option_values.each do |option_value|
  133. key = option_value.gateway_option.name.to_sym
  134. config_options[key] = option_value.value
  135. end
  136. gateway = gateway_config.gateway.clazz.constantize.new(config_options)
  137. end
  138. end