spree_paypal_express 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +14 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +23 -0
  5. data/README.markdown +146 -0
  6. data/Rakefile +13 -0
  7. data/Versionfile +5 -0
  8. data/app/assets/images/paypal.png +0 -0
  9. data/app/assets/javascripts/admin/spree_paypal_express.js +0 -0
  10. data/app/assets/javascripts/store/spree_paypal_express.js +0 -0
  11. data/app/assets/stylesheets/admin/spree_paypal_express.css +0 -0
  12. data/app/assets/stylesheets/store/spree_paypal_express.css +0 -0
  13. data/app/controllers/spree/checkout_controller_decorator.rb +384 -0
  14. data/app/controllers/spree/paypal_express_callbacks_controller.rb +44 -0
  15. data/app/helpers/spree/checkout_helper_decorator.rb +7 -0
  16. data/app/models/spree/billing_integration/paypal_express.rb +3 -0
  17. data/app/models/spree/billing_integration/paypal_express_base.rb +62 -0
  18. data/app/models/spree/billing_integration/paypal_express_uk.rb +3 -0
  19. data/app/models/spree/log_entry_decorator.rb +3 -0
  20. data/app/models/spree/payment_decorator.rb +3 -0
  21. data/app/models/spree/paypal_account.rb +36 -0
  22. data/app/overrides/spree/shared/_order_details/add_paypal_details.html.erb.deface +16 -0
  23. data/app/views/spree/admin/payments/source_forms/_paypalexpress.html.erb +9 -0
  24. data/app/views/spree/admin/payments/source_forms/_paypalexpressuk.html.erb +9 -0
  25. data/app/views/spree/admin/payments/source_views/_paypalexpress.html.erb +110 -0
  26. data/app/views/spree/admin/payments/source_views/_paypalexpressuk.html.erb +110 -0
  27. data/app/views/spree/admin/paypal_payments/refund.html.erb +15 -0
  28. data/app/views/spree/checkout/payment/_paypalexpress.html.erb +3 -0
  29. data/app/views/spree/checkout/payment/_paypalexpressuk.html.erb +3 -0
  30. data/app/views/spree/shared/_paypal_express_checkout.html.erb +3 -0
  31. data/app/views/spree/shared/paypal_express_confirm.html.erb +23 -0
  32. data/capture-notes +28 -0
  33. data/config/initializers/paypal_express.rb +1 -0
  34. data/config/locales/en-GB.yml +30 -0
  35. data/config/locales/en.yml +32 -0
  36. data/config/routes.rb +25 -0
  37. data/db/migrate/20100224133156_create_paypal_accounts.rb +14 -0
  38. data/db/migrate/20120117182027_namespace_paypal_accounts.rb +5 -0
  39. data/lib/generators/spree_paypal_express/install/install_generator.rb +18 -0
  40. data/lib/spree/paypal_express_configuration.rb +5 -0
  41. data/lib/spree_paypal_express.rb +3 -0
  42. data/lib/spree_paypal_express/engine.rb +38 -0
  43. data/response-example-one +55 -0
  44. data/response-xml-one +137 -0
  45. data/spec/controllers/checkout_controller_spec.rb +323 -0
  46. data/spec/factories/address_factory.rb +13 -0
  47. data/spec/factories/order_factory.rb +11 -0
  48. data/spec/factories/ppx_factory.rb +5 -0
  49. data/spec/factories/state_factory.rb +5 -0
  50. data/spec/models/billing_integration/paypal_express_base_spec.rb +135 -0
  51. data/spec/requests/paypal_express_spec.rb +40 -0
  52. data/spec/spec_helper.rb +37 -0
  53. data/spec/support/authentication_helpers.rb +13 -0
  54. data/spec/support/controller_hacks.rb +33 -0
  55. data/spec/support/shared_connection.rb +12 -0
  56. data/spec/support/url_helpers.rb +7 -0
  57. data/spree_paypal_express.gemspec +24 -0
  58. metadata +224 -0
@@ -0,0 +1,7 @@
1
+ *.swp
2
+ .DS_store
3
+ spec/test_app
4
+ spec/dummy
5
+ .rvmrc
6
+ .bundle
7
+ Gemfile.lock
@@ -0,0 +1,14 @@
1
+ before_script:
2
+ - "export DISPLAY=:99.0"
3
+ - "sh -e /etc/init.d/xvfb start"
4
+ - "DISPLAY=:99.0 bundle exec rake test_app"
5
+ script: "DISPLAY=:99.0 bundle exec rspec spec"
6
+ notifications:
7
+ email:
8
+ - briandquinn@gmail.com
9
+ branches:
10
+ only:
11
+ - master
12
+ - 1-1-stable
13
+ rvm:
14
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Redistribution and use in source and binary forms, with or without modification,
2
+ are permitted provided that the following conditions are met:
3
+
4
+ * Redistributions of source code must retain the above copyright notice,
5
+ this list of conditions and the following disclaimer.
6
+ * Redistributions in binary form must reproduce the above copyright notice,
7
+ this list of conditions and the following disclaimer in the documentation
8
+ and/or other materials provided with the distribution.
9
+ * Neither the name of the Rails Dog LLC nor the names of its
10
+ contributors may be used to endorse or promote products derived from this
11
+ software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,146 @@
1
+ # Official PayPal Express for Spree
2
+
3
+ [![Build Status](https://secure.travis-ci.org/spree/spree_paypal_express.png?branch=master)](http://travis-ci.org/spree/spree_paypal_express)
4
+
5
+ This is the official PayPal Express extension for Spree, based on the extension by PaulCC it has been extended to support Spree's
6
+ Billing Integrations which allows users to configure the PayPal Express gateway including API login / password and signatures fields
7
+ via the Admin UI.
8
+
9
+ This extension allows the store to use PayPal Express from two locations:
10
+
11
+ 1. Checkout Payment - When configured the PayPal Express checkout button will appear alongside the standard credit card payment
12
+ options on the payment stage of the standard checkout. The selected shipping address and shipping method / costs are automatically
13
+ sent to the PayPal review page (along with detailed order information).
14
+
15
+
16
+ 2. Cart Checkout (THIS FEATURE IS NOT YET COMPLETE) - Presents the PayPal checkout button on the users Cart page and redirects the user to complete
17
+ all shipping / addressing information on PaypPal's site. This also supports PayPal's Instant Update feature to retrieve shipping options live from
18
+ Spree when the user selects / changes their shipping address on PayPal's site.
19
+
20
+ This extension follows the documented flow for a PayPal Express Checkout, where a user is forwarded to PayPal to allow them to login and review
21
+ the order (possibly select / change shipping address and method), then the user is redirected back to Spree to confirm the order. The user
22
+ MUST confirm the order on the Spree site before the payment is authorized / captured from PayPal (and the order is transitioned to the New state).
23
+
24
+
25
+ Installation
26
+ ============
27
+
28
+ 1. Add the following line to your application's Gemfile
29
+
30
+ gem "spree_paypal_express", :git => "git://github.com/spree/spree_paypal_express.git"
31
+
32
+ **Note:** The :git option is only required for the edge version, and can be removed to used the released gem.
33
+
34
+ 2. Install the gem using Bundler:
35
+
36
+ bundle install
37
+
38
+ 3. Copy & run migrations
39
+
40
+ bundle exec rails g spree_paypal_express:install
41
+
42
+ Versions
43
+ ========
44
+
45
+ To determine the correct version of this extension, please refer to the Versionfile.
46
+
47
+ IPN & eCheck Support
48
+ ===================
49
+ eCheck payments are now fully supported and PayPal's Instant Payment Notification service is also supported for receiving updates relating to eCheck payments only. To configure eCheck payments you'll need to:
50
+
51
+ 1. Configure your PayPal account to accept eCheck payments (under Profile on PayPal's website).
52
+
53
+ 2. Set the IPN URL on your PayPal account (under Profile on PayPal's website) to:
54
+
55
+ https://www.yourstore.com/paypal_notify
56
+
57
+ 3. Enable auto_capture within Spree (as eCheck payments are only supported for purchase and not authorize requests).
58
+
59
+ Spree::Config.set(:auto_capture => true)
60
+
61
+
62
+
63
+ Configuration
64
+ =============
65
+
66
+ 1. Before you begin
67
+
68
+ You'll need to have a Paypal developer account (developer.paypal.com) and both buyer and seller test accounts.
69
+
70
+ **Tip:** these are sandbox only, so use email addresses and passwords that are easy to remember, e.g. buyer@example.com and seller@example.com.
71
+
72
+ Your sandbox credentials are available from the API Credentials link.
73
+
74
+ 2. Setup the Payment Method
75
+
76
+ Log in as an admin and add a new **Payment Method** (under Configuration), using following details:
77
+
78
+ **Name:** Paypal Express
79
+
80
+ **Environment:** Development (or what ever environment you prefer)
81
+
82
+ **Active:** Yes
83
+
84
+ **Provider:** Spree::BillingIntegration::PaypalExpress
85
+
86
+ Click **Create* , and now add your credentials in the screen that follows:
87
+
88
+ **Review:** unchecked [1]
89
+
90
+ **Signature:** API signature from your paypal seller test account
91
+
92
+ **Server:** test (for Development or live for Production)
93
+
94
+ **Test Mode:** checked (or unchecked for Production)
95
+
96
+ **Password:** API Password from your paypal seller test account
97
+
98
+ **Login:** API Username from your paypal seller test account (care to use the API Username and not the Test Account address)
99
+
100
+ Click **Update**
101
+
102
+ Test Drive
103
+ ==========
104
+
105
+ While testing PayPal Express checkout locally make sure you're logged into your PayPal **developer** account in another browser window before attempting a PayPal payment, as you'll be redirected and forced to sign in to your developer account.
106
+
107
+ 1. Add an item to cart
108
+
109
+ 2. Check out
110
+
111
+ 3. Address step: complete it using a valid US address.
112
+
113
+ 4. Delivery step: pick anything
114
+
115
+ 5. On the Payment Step, you should see a PayPal button. You can select it directly or just click "Continue"
116
+
117
+ 6. You will get redirected to PayPals sandbox site, be sure to log in as a **Buyer** / **Personal** test account and not the account you use to configure the Payment Method with.
118
+
119
+ 7. You should now see the paypal order details screen with a Pay Now button.
120
+
121
+ 8. Click Pay Now, and you should now be redirected back to Spree's order thank you page.
122
+
123
+ 9. Log into the Admin UI and review the Order and Payment details to confirm the successful checkout.
124
+
125
+
126
+ Running Specs
127
+ =============
128
+
129
+ 1. Create Test App
130
+
131
+ rake test_app
132
+
133
+ 2. Run Specs
134
+
135
+ rake spec
136
+
137
+ NOTES
138
+ =====
139
+
140
+ To automatically capture funds or enable accepting eCheck payments, add this to you site extension's activate method:
141
+
142
+ if Spree::Config.instance
143
+ Spree::Config[:auto_capture] = true
144
+ end
145
+
146
+ [1] If you check the review checkbox in the admin section for Payment Methods/Paypal Express, the flow is slightly different. Instead of Pay Now on Paypal's order details page, it now says Continue. And the user is directed back to the spree app's Confirmation page showing a place order button. Use whichever suits your needs best. Personally, I leave review unchecked to cut down on the steps in the checkout flow.
@@ -0,0 +1,13 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+ require 'spree/core/testing_support/common_rake'
4
+
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => [:spec ]
8
+
9
+ desc "Generates a dummy app for testing"
10
+ task :test_app do
11
+ ENV['LIB_NAME'] = 'spree_paypal_express'
12
+ Rake::Task['common:test_app'].invoke
13
+ end
@@ -0,0 +1,5 @@
1
+ "1.1.0" => { :branch => "1-1-stable" }
2
+ "1.0.0" => { :branch => "1-0-stable" }
3
+ "0.70.x" => { :ref => "bea1aa48e0089083546bec4b19565a40e9a50a20" }
4
+ "0.60.x" => { :ref => "073f2f814dd8f3ad2e66ddde2c7079d8c76e4d27" }
5
+ "0.50.x" => { :ref => "39a3b00602d591e5c27bf13941aa5c13e4b95579" }
@@ -0,0 +1,384 @@
1
+ module Spree
2
+ CheckoutController.class_eval do
3
+ before_filter :redirect_to_paypal_express_form_if_needed, :only => [:update]
4
+
5
+ def paypal_checkout
6
+ load_order
7
+ opts = all_opts(@order, params[:payment_method_id], 'checkout')
8
+ opts.merge!(address_options(@order))
9
+ @gateway = paypal_gateway
10
+
11
+ if Spree::Config[:auto_capture]
12
+ @ppx_response = @gateway.setup_purchase(opts[:money], opts)
13
+ else
14
+ @ppx_response = @gateway.setup_authorization(opts[:money], opts)
15
+ end
16
+
17
+ unless @ppx_response.success?
18
+ gateway_error(@ppx_response)
19
+ redirect_to edit_order_url(@order)
20
+ return
21
+ end
22
+
23
+ redirect_to(@gateway.redirect_url_for(response.token, :review => payment_method.preferred_review))
24
+ rescue ActiveMerchant::ConnectionError => e
25
+ gateway_error I18n.t(:unable_to_connect_to_gateway)
26
+ redirect_to :back
27
+ end
28
+
29
+ def paypal_payment
30
+ load_order
31
+ opts = all_opts(@order,params[:payment_method_id], 'payment')
32
+ opts.merge!(address_options(@order))
33
+ @gateway = paypal_gateway
34
+
35
+ if Spree::Config[:auto_capture]
36
+ @ppx_response = @gateway.setup_purchase(opts[:money], opts)
37
+ else
38
+ @ppx_response = @gateway.setup_authorization(opts[:money], opts)
39
+ end
40
+
41
+ unless @ppx_response.success?
42
+ gateway_error(@ppx_response)
43
+ redirect_to edit_order_checkout_url(@order, :state => "payment")
44
+ return
45
+ end
46
+
47
+ redirect_to(@gateway.redirect_url_for(@ppx_response.token, :review => payment_method.preferred_review))
48
+ rescue ActiveMerchant::ConnectionError => e
49
+ gateway_error I18n.t(:unable_to_connect_to_gateway)
50
+ redirect_to :back
51
+ end
52
+
53
+ def paypal_confirm
54
+ load_order
55
+
56
+ opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment')
57
+ gateway = paypal_gateway
58
+
59
+ @ppx_details = gateway.details_for params[:token]
60
+
61
+ if @ppx_details.success?
62
+ # now save the updated order info
63
+
64
+ Spree::PaypalAccount.create(:email => @ppx_details.params["payer"],
65
+ :payer_id => @ppx_details.params["payer_id"],
66
+ :payer_country => @ppx_details.params["payer_country"],
67
+ :payer_status => @ppx_details.params["payer_status"])
68
+
69
+ @order.special_instructions = @ppx_details.params["note"]
70
+
71
+ unless payment_method.preferred_no_shipping
72
+ ship_address = @ppx_details.address
73
+ order_ship_address = Spree::Address.new :firstname => @ppx_details.params["first_name"],
74
+ :lastname => @ppx_details.params["last_name"],
75
+ :address1 => ship_address["address1"],
76
+ :address2 => ship_address["address2"],
77
+ :city => ship_address["city"],
78
+ :country => Spree::Country.find_by_iso(ship_address["country"]),
79
+ :zipcode => ship_address["zip"],
80
+ # phone is currently blanked in AM's PPX response lib
81
+ :phone => @ppx_details.params["phone"] || "(not given)"
82
+
83
+ if (state = Spree::State.find_by_abbr(ship_address["state"].upcase))
84
+ order_ship_address.state = state
85
+ else
86
+ order_ship_address.state_name = ship_address["state"]
87
+ end
88
+ order_ship_address.save!
89
+
90
+ @order.ship_address = order_ship_address
91
+ @order.bill_address ||= order_ship_address
92
+ end
93
+ @order.state = "payment"
94
+ @order.save
95
+
96
+ if payment_method.preferred_review
97
+ @order.next
98
+ render 'spree/shared/paypal_express_confirm'
99
+ else
100
+ paypal_finish
101
+ end
102
+
103
+ else
104
+ gateway_error(@ppx_details)
105
+
106
+ #Failed trying to get payment details from PPX
107
+ redirect_to edit_order_checkout_url(@order, :state => "payment")
108
+ end
109
+ rescue ActiveMerchant::ConnectionError => e
110
+ gateway_error I18n.t(:unable_to_connect_to_gateway)
111
+ redirect_to edit_order_url(@order)
112
+ end
113
+
114
+ def paypal_finish
115
+ load_order
116
+
117
+ opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment' )
118
+ gateway = paypal_gateway
119
+
120
+ method = Spree::Config[:auto_capture] ? :purchase : :authorize
121
+ ppx_auth_response = gateway.send(method, (@order.total*100).to_i, opts)
122
+
123
+ paypal_account = Spree::PaypalAccount.find_by_payer_id(params[:PayerID])
124
+
125
+ payment = @order.payments.create(
126
+ :amount => ppx_auth_response.params["gross_amount"].to_f,
127
+ :source => paypal_account,
128
+ :source_type => 'Spree::PaypalAccount',
129
+ :payment_method_id => params[:payment_method_id],
130
+ :response_code => ppx_auth_response.authorization,
131
+ :avs_response => ppx_auth_response.avs_result["code"])
132
+
133
+ payment.started_processing!
134
+
135
+ record_log payment, ppx_auth_response
136
+
137
+ if ppx_auth_response.success?
138
+ #confirm status
139
+ case ppx_auth_response.params["payment_status"]
140
+ when "Completed"
141
+ payment.complete!
142
+ when "Pending"
143
+ payment.pend!
144
+ else
145
+ payment.pend!
146
+ Rails.logger.error "Unexpected response from PayPal Express"
147
+ Rails.logger.error ppx_auth_response.to_yaml
148
+ end
149
+
150
+ @order.update_attributes({:state => "complete", :completed_at => Time.now}, :without_protection => true)
151
+
152
+ state_callback(:after) # So that after_complete is called, setting session[:order_id] to nil
153
+
154
+ # Since we dont rely on state machine callback, we just explicitly call this method for spree_store_credits
155
+ if @order.respond_to?(:consume_users_credit, true)
156
+ @order.send(:consume_users_credit)
157
+ end
158
+
159
+ @order.finalize!
160
+ flash[:notice] = I18n.t(:order_processed_successfully)
161
+ redirect_to completion_route
162
+
163
+ else
164
+ payment.failure!
165
+ order_params = {}
166
+ gateway_error(ppx_auth_response)
167
+
168
+ #Failed trying to complete pending payment!
169
+ redirect_to edit_order_checkout_url(@order, :state => "payment")
170
+ end
171
+ rescue ActiveMerchant::ConnectionError => e
172
+ gateway_error I18n.t(:unable_to_connect_to_gateway)
173
+ redirect_to edit_order_url(@order)
174
+ end
175
+
176
+ private
177
+
178
+ def asset_url(_path)
179
+ URI::HTTP.build(:path => ActionController::Base.helpers.asset_path(_path), :host => Spree::Config[:site_url]).to_s
180
+ end
181
+
182
+ def record_log(payment, response)
183
+ payment.log_entries.create(:details => response.to_yaml)
184
+ end
185
+
186
+ def redirect_to_paypal_express_form_if_needed
187
+ return unless (params[:state] == "payment")
188
+ return unless params[:order][:payments_attributes]
189
+
190
+ if @order.update_attributes(object_params)
191
+ if params[:order][:coupon_code] and !params[:order][:coupon_code].blank? and @order.coupon_code.present?
192
+ fire_event('spree.checkout.coupon_code_added', :coupon_code => @order.coupon_code)
193
+ end
194
+ end
195
+
196
+ load_order
197
+ payment_method = Spree::PaymentMethod.find(params[:order][:payments_attributes].first[:payment_method_id])
198
+
199
+ if payment_method.kind_of?(Spree::BillingIntegration::PaypalExpress) || payment_method.kind_of?(Spree::BillingIntegration::PaypalExpressUk)
200
+ redirect_to(paypal_payment_order_checkout_url(@order, :payment_method_id => payment_method.id)) and return
201
+ end
202
+ end
203
+
204
+ def fixed_opts
205
+ if Spree::PaypalExpress::Config[:paypal_express_local_confirm].nil?
206
+ user_action = "continue"
207
+ else
208
+ user_action = Spree::PaypalExpress::Config[:paypal_express_local_confirm] == "t" ? "continue" : "commit"
209
+ end
210
+
211
+ #asset_url doesn't like Spree::Config[:logo] being an absolute url
212
+ #if statement didn't work within hash
213
+ if URI.parse(Spree::Config[:logo]).absolute?
214
+ chosen_image = Spree::Config[:logo]
215
+ else
216
+ chosen_image = asset_url(Spree::Config[:logo])
217
+ end
218
+
219
+
220
+ { :description => "Goods from #{Spree::Config[:site_name]}", # site details...
221
+ #:page_style => "foobar", # merchant account can set named config
222
+ :background_color => "ffffff", # must be hex only, six chars
223
+ :header_background_color => "ffffff",
224
+ :header_border_color => "ffffff",
225
+ :header_image => chosen_image,
226
+ :allow_note => true,
227
+ :locale => user_locale,
228
+ :req_confirm_shipping => false, # for security, might make an option later
229
+ :user_action => user_action
230
+
231
+ # WARNING -- don't use :ship_discount, :insurance_offered, :insurance since
232
+ # they've not been tested and may trigger some paypal bugs, eg not showing order
233
+ # see http://www.pdncommunity.com/t5/PayPal-Developer-Blog/Displaying-Order-Details-in-Express-Checkout/bc-p/92902#C851
234
+ }
235
+ end
236
+
237
+ def user_locale
238
+ I18n.locale.to_s
239
+ end
240
+
241
+ # hook to override paypal site options
242
+ def paypal_site_opts
243
+ {:currency => payment_method.preferred_currency, :allow_guest_checkout => payment_method.preferred_allow_guest_checkout }
244
+ end
245
+
246
+ def order_opts(order, payment_method, stage)
247
+ items = order.line_items.map do |item|
248
+ price = (item.price * 100).to_i # convert for gateway
249
+ { :name => item.variant.product.name,
250
+ :description => (item.variant.product.description[0..120] if item.variant.product.description),
251
+ :number => item.variant.sku,
252
+ :quantity => item.quantity,
253
+ :amount => price,
254
+ :weight => item.variant.weight,
255
+ :height => item.variant.height,
256
+ :width => item.variant.width,
257
+ :depth => item.variant.weight }
258
+ end
259
+
260
+ credits = order.adjustments.eligible.map do |credit|
261
+ if credit.amount < 0.00
262
+ { :name => credit.label,
263
+ :description => credit.label,
264
+ :sku => credit.id,
265
+ :quantity => 1,
266
+ :amount => (credit.amount*100).to_i }
267
+ end
268
+ end
269
+
270
+ credits_total = 0
271
+ credits.compact!
272
+ if credits.present?
273
+ items.concat credits
274
+ credits_total = credits.map {|i| i[:amount] * i[:quantity] }.sum
275
+ end
276
+
277
+ opts = { :return_url => paypal_confirm_order_checkout_url(order, :payment_method_id => payment_method),
278
+ :cancel_return_url => edit_order_checkout_url(order, :state => :payment),
279
+ :order_id => order.number,
280
+ :custom => order.number,
281
+ :items => items,
282
+ :subtotal => ((order.item_total * 100) + credits_total).to_i,
283
+ :tax => (order.tax_total*100).to_i,
284
+ :shipping => (order.ship_total*100).to_i,
285
+ :money => (order.total * 100 ).to_i }
286
+
287
+ if stage == "checkout"
288
+ opts[:handling] = 0
289
+
290
+ opts[:callback_url] = spree_root_url + "paypal_express_callbacks/#{order.number}"
291
+ opts[:callback_timeout] = 3
292
+ elsif stage == "payment"
293
+ #hack to add float rounding difference in as handling fee - prevents PayPal from rejecting orders
294
+ #because the integer totals are different from the float based total. This is temporary and will be
295
+ #removed once Spree's currency values are persisted as integers (normally only 1c)
296
+ opts[:handling] = (order.total*100).to_i - opts.slice(:subtotal, :tax, :shipping).values.sum
297
+ end
298
+
299
+ opts
300
+ end
301
+
302
+ def address_options(order)
303
+ if payment_method.preferred_no_shipping
304
+ { :no_shipping => true }
305
+ else
306
+ {
307
+ :no_shipping => false,
308
+ :address_override => true,
309
+ :address => {
310
+ :name => "#{order.ship_address.firstname} #{order.ship_address.lastname}",
311
+ :address1 => order.ship_address.address1,
312
+ :address2 => order.ship_address.address2,
313
+ :city => order.ship_address.city,
314
+ :state => order.ship_address.state.nil? ? order.ship_address.state_name.to_s : order.ship_address.state.abbr,
315
+ :country => order.ship_address.country.iso,
316
+ :zip => order.ship_address.zipcode,
317
+ :phone => order.ship_address.phone
318
+ }
319
+ }
320
+ end
321
+ end
322
+
323
+ def all_opts(order, payment_method, stage=nil)
324
+ opts = fixed_opts.merge(order_opts(order, payment_method, stage)).merge(paypal_site_opts)
325
+
326
+ if stage == "payment"
327
+ opts.merge! flat_rate_shipping_and_handling_options(order, stage)
328
+ end
329
+
330
+ # suggest current user's email or any email stored in the order
331
+ opts[:email] = current_user ? current_user.email : order.email
332
+
333
+ opts
334
+ end
335
+
336
+ # hook to allow applications to load in their own shipping and handling costs
337
+ def flat_rate_shipping_and_handling_options(order, stage)
338
+ # max_fallback = 0.0
339
+ # shipping_options = ShippingMethod.all.map do |shipping_method|
340
+ # max_fallback = shipping_method.fallback_amount if shipping_method.fallback_amount > max_fallback
341
+ # { :name => "#{shipping_method.id}",
342
+ # :label => "#{shipping_method.name} - #{shipping_method.zone.name}",
343
+ # :amount => (shipping_method.fallback_amount*100) + 1,
344
+ # :default => shipping_method.is_default }
345
+ # end
346
+ #
347
+ #
348
+ # default_shipping_method = ShippingMethod.find(:first, :conditions => {:is_default => true})
349
+ #
350
+ # opts = { :shipping_options => shipping_options,
351
+ # :max_amount => (order.total + max_fallback)*100
352
+ # }
353
+ #
354
+ # opts[:shipping] = (default_shipping_method.nil? ? 0 : default_shipping_method.fallback_amount) if stage == "checkout"
355
+ #
356
+ # opts
357
+ {}
358
+ end
359
+
360
+ def gateway_error(response)
361
+ if response.is_a? ActiveMerchant::Billing::Response
362
+ text = response.params['message'] ||
363
+ response.params['response_reason_text'] ||
364
+ response.message
365
+ else
366
+ text = response.to_s
367
+ end
368
+
369
+ msg = "#{I18n.t('gateway_error')}: #{text}"
370
+ logger.error(msg)
371
+ flash[:error] = msg
372
+ end
373
+
374
+ # create the gateway from the supplied options
375
+ def payment_method
376
+ @payment_method ||= Spree::PaymentMethod.find(params[:payment_method_id])
377
+ end
378
+
379
+ def paypal_gateway
380
+ payment_method.provider
381
+ end
382
+
383
+ end
384
+ end