spree_paypal_express_mutalis 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +16 -0
  5. data/CONTRIBUTING.md +81 -0
  6. data/Gemfile +8 -0
  7. data/LICENSE.md +26 -0
  8. data/README.md +86 -0
  9. data/Rakefile +23 -0
  10. data/app/assets/javascripts/admin/spree_paypal_express.js +25 -0
  11. data/app/assets/javascripts/store/spree_paypal_express.js +19 -0
  12. data/app/assets/stylesheets/admin/spree_paypal_express.css +3 -0
  13. data/app/assets/stylesheets/store/spree_paypal_express.css +3 -0
  14. data/app/controllers/spree/admin/payments_controller_decorator.rb +19 -0
  15. data/app/controllers/spree/admin/paypal_payments_controller.rb +15 -0
  16. data/app/controllers/spree/paypal_controller.rb +144 -0
  17. data/app/models/spree/gateway/pay_pal_express.rb +100 -0
  18. data/app/models/spree/paypal_express_checkout.rb +4 -0
  19. data/app/views/spree/admin/payments/_paypal_complete.html.erb +20 -0
  20. data/app/views/spree/admin/payments/paypal_refund.html.erb +31 -0
  21. data/app/views/spree/admin/payments/source_forms/_paypal.html.erb +6 -0
  22. data/app/views/spree/admin/payments/source_views/_paypal.html.erb +35 -0
  23. data/app/views/spree/checkout/payment/_paypal.html.erb +6 -0
  24. data/config/locales/en.yml +16 -0
  25. data/config/routes.rb +18 -0
  26. data/db/migrate/20130723042610_create_spree_paypal_express_checkouts.rb +8 -0
  27. data/db/migrate/20130808030836_add_transaction_id_to_spree_paypal_express_checkouts.rb +6 -0
  28. data/db/migrate/20130809013846_add_state_to_spree_paypal_express_checkouts.rb +5 -0
  29. data/db/migrate/20130809014319_add_refunded_fields_to_spree_paypal_express_checkouts.rb +8 -0
  30. data/lib/generators/spree_paypal_express/install/install_generator.rb +31 -0
  31. data/lib/spree_paypal_express.rb +3 -0
  32. data/lib/spree_paypal_express/engine.rb +26 -0
  33. data/lib/spree_paypal_express/factories.rb +6 -0
  34. data/lib/spree_paypal_express/version.rb +3 -0
  35. data/script/rails +7 -0
  36. data/spec/features/paypal_spec.rb +244 -0
  37. data/spec/models/pay_pal_express_spec.rb +50 -0
  38. data/spec/spec_helper.rb +45 -0
  39. data/spree_paypal_express.gemspec +39 -0
  40. metadata +253 -0
@@ -0,0 +1,100 @@
1
+ require 'paypal-sdk-merchant'
2
+ module Spree
3
+ class Gateway::PayPalExpress < Gateway
4
+ preference :login, :string
5
+ preference :password, :string
6
+ preference :signature, :string
7
+ preference :server, :string, default: 'sandbox'
8
+
9
+ def supports?(source)
10
+ true
11
+ end
12
+
13
+ def provider_class
14
+ ::PayPal::SDK::Merchant::API
15
+ end
16
+
17
+ def provider
18
+ ::PayPal::SDK.configure(
19
+ :mode => preferred_server.present? ? preferred_server : "sandbox",
20
+ :username => preferred_login,
21
+ :password => preferred_password,
22
+ :signature => preferred_signature)
23
+ provider_class.new
24
+ end
25
+
26
+ def auto_capture?
27
+ true
28
+ end
29
+
30
+ def method_type
31
+ 'paypal'
32
+ end
33
+
34
+ def purchase(amount, express_checkout, gateway_options={})
35
+ pp_request = provider.build_do_express_checkout_payment({
36
+ :DoExpressCheckoutPaymentRequestDetails => {
37
+ :PaymentAction => "Sale",
38
+ :Token => express_checkout.token,
39
+ :PayerID => express_checkout.payer_id,
40
+ :PaymentDetails => [{
41
+ :OrderTotal => {
42
+ :currencyID => Spree::Config[:currency],
43
+ # gsub is here because PayPal fails to acknowledge
44
+ # that some people like their currencies written as:
45
+ # 21,99
46
+ # As opposed to:
47
+ # 21.99
48
+ # The international payments company fails to handle
49
+ # international payment amounts. SMH.
50
+ :value => ::Money.new(amount, Spree::Config[:currency]).to_s.gsub(',', '.') }
51
+ }]
52
+ }
53
+ })
54
+
55
+ pp_response = provider.do_express_checkout_payment(pp_request)
56
+ if pp_response.success?
57
+ # We need to store the transaction id for the future.
58
+ # This is mainly so we can use it later on to refund the payment if the user wishes.
59
+ transaction_id = pp_response.do_express_checkout_payment_response_details.payment_info.first.transaction_id
60
+ express_checkout.update_column(:transaction_id, transaction_id)
61
+ # This is rather hackish, required for payment/processing handle_response code.
62
+ Class.new do
63
+ def success?; true; end
64
+ def authorization; nil; end
65
+ end.new
66
+ else
67
+ class << pp_response
68
+ def to_s
69
+ errors.map(&:long_message).join(" ")
70
+ end
71
+ end
72
+ pp_response
73
+ end
74
+ end
75
+
76
+ def refund(payment, amount)
77
+ refund_type = payment.amount == amount.to_f ? "Full" : "Partial"
78
+ refund_transaction = provider.build_refund_transaction({
79
+ :TransactionID => payment.source.transaction_id,
80
+ :RefundType => refund_type,
81
+ :Amount => {
82
+ :currencyID => payment.currency,
83
+ :value => amount },
84
+ :RefundSource => "any" })
85
+ refund_transaction_response = provider.refund_transaction(refund_transaction)
86
+ if refund_transaction_response.success?
87
+ payment.source.update_attributes({
88
+ :refunded_at => Time.now,
89
+ :refund_transaction_id => refund_transaction_response.RefundTransactionID,
90
+ :state => "refunded",
91
+ :refund_type => refund_type
92
+ } )
93
+ end
94
+ refund_transaction_response
95
+ end
96
+ end
97
+ end
98
+
99
+ # payment.state = 'completed'
100
+ # current_order.state = 'complete'
@@ -0,0 +1,4 @@
1
+ module Spree
2
+ class PaypalExpressCheckout < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,20 @@
1
+ <%= form_tag paypal_refund_admin_order_payment_path(@order, @payment) do %>
2
+ <div class="label-block left five columns alpha">
3
+ <div>
4
+ <fieldset data-hook="admin_variant_new_form">
5
+ <legend><%= Spree.t('refund', :scope => :paypal) %></legend>
6
+ <div class='field'>
7
+ <%= label_tag 'refund_amount', Spree.t(:refund_amount, :scope => 'paypal') %>
8
+ <small><em><%= Spree.t(:original_amount, :scope => 'paypal', :amount => @payment.display_amount) %></em></small><br>
9
+ <% symbol = ::Money.new(1, Spree::Config[:currency]).symbol %>
10
+ <% if Spree::Config[:currency_symbol_position] == "before" %>
11
+ <%= symbol %><%= text_field_tag 'refund_amount', @payment.amount %>
12
+ <% else %>
13
+ <%= text_field_tag 'refund_amount', @payment.amount %><%= symbol %>
14
+ <% end %>
15
+ </div>
16
+ <%= button Spree.t(:refund, :scope => 'paypal'), 'icon-dollar' %>
17
+ </fieldset>
18
+ </div>
19
+ </div>
20
+ <% end %>
@@ -0,0 +1,31 @@
1
+ <%= render :partial => 'spree/admin/shared/order_tabs', :locals => { :current => 'Payments' } %>
2
+
3
+ <% content_for :page_title do %>
4
+ <i class="icon-arrow-right"></i>
5
+ <%= link_to Spree.t(:payments), admin_order_payments_path(@order) %>
6
+ <i class="icon-arrow-right"></i>
7
+ <%= payment_method_name(@payment) %>
8
+ <i class="icon-arrow-right"></i>
9
+ <%= Spree.t('refund', :scope => :paypal) %>
10
+ <% end %>
11
+
12
+ <%= form_tag paypal_refund_admin_order_payment_path(@order, @payment) do %>
13
+ <div class="label-block left five columns alpha">
14
+ <div>
15
+ <fieldset data-hook="admin_variant_new_form">
16
+ <legend><%= Spree.t('refund', :scope => :paypal) %></legend>
17
+ <div class='field'>
18
+ <%= label_tag 'refund_amount', Spree.t(:refund_amount, :scope => 'paypal') %>
19
+ <small><em><%= Spree.t(:original_amount, :scope => 'paypal', :amount => @payment.display_amount) %></em></small><br>
20
+ <% symbol = ::Money.new(1, Spree::Config[:currency]).symbol %>
21
+ <% if Spree::Config[:currency_symbol_position] == "before" %>
22
+ <%= symbol %><%= text_field_tag 'refund_amount', @payment.amount %>
23
+ <% else %>
24
+ <%= text_field_tag 'refund_amount', @payment.amount %><%= symbol %>
25
+ <% end %>
26
+ </div>
27
+ <%= button Spree.t(:refund, :scope => 'paypal'), 'icon-dollar' %>
28
+ </fieldset>
29
+ </div>
30
+ </div>
31
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <div id='paypal-warning' style="display:none">
2
+ <strong>You cannot charge PayPal accounts through the admin backend at this time.</strong>
3
+ </div>
4
+ <script>
5
+ SpreePaypalExpress.paymentMethodID = "<%= payment_method.id %>"
6
+ </script>
@@ -0,0 +1,35 @@
1
+ <fieldset data-hook="paypal">
2
+ <legend align="center"><%= Spree.t(:transaction, :scope => :paypal) %></legend>
3
+
4
+ <div class="row">
5
+ <div class="alpha six columns">
6
+ <dl>
7
+ <dt><%= Spree.t(:payer_id, :scope => :paypal) %>:</dt>
8
+ <dd><%= payment.source.payer_id %></dd>
9
+
10
+ <dt><%= Spree.t(:token, :scope => :paypal) %>:</dt>
11
+ <dd><%= payment.source.token %></dd>
12
+
13
+ <dt><%= Spree.t(:transaction_id) %>:</dt>
14
+ <dd><%= payment.source.transaction_id %></dd>
15
+ </dl>
16
+ </div>
17
+
18
+ <% if payment.source.state != 'refunded' %>
19
+ <%= button_link_to Spree.t('actions.refund', :scope => :paypal), spree.paypal_refund_admin_order_payment_path(@order, payment), :icon => 'icon-dollar' %>
20
+ <% else %>
21
+ <div class="alpha six columns">
22
+ <dl>
23
+ <dt><%= Spree.t(:state, :scope => :paypal) %>:</dt>
24
+ <dd><%= payment.source.state.titleize %></dd>
25
+
26
+ <dt><%= Spree.t(:refunded_at, :scope => :paypal) %>:</dt>
27
+ <dd><%= pretty_time(payment.source.refunded_at) %></dd>
28
+
29
+ <dt><%= Spree.t(:refund_transaction_id, :scope => :paypal) %>:</dt>
30
+ <dd><%= payment.source.refund_transaction_id %></dd>
31
+ </dl>
32
+ </div>
33
+ <% end %>
34
+ </div>
35
+ </fieldset>
@@ -0,0 +1,6 @@
1
+
2
+ <%= link_to(image_tag("https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif"), paypal_express_url(:payment_method_id => payment_method.id), :method => :post, :id => "paypal_button") %>
3
+
4
+ <script>
5
+ SpreePaypalExpress.paymentMethodID = "<%= payment_method.id %>"
6
+ </script>
@@ -0,0 +1,16 @@
1
+ ---
2
+ en:
3
+ spree:
4
+ paypal:
5
+ already_refunded: "This payment has been refunded and no further action can be taken on it."
6
+ transaction: "PayPal Transaction"
7
+ payer_id: "Payer ID"
8
+ transaction_id: "Transaction ID"
9
+ token: "Token"
10
+ refund: "Refund"
11
+ refund_amount: "Amount"
12
+ original_amount: "Original amount: %{amount}"
13
+ refund_successful: "PayPal refund successful"
14
+ refund_unsuccessful: "PayPal refund unsuccessful"
15
+ actions:
16
+ refund: "Refund"
data/config/routes.rb ADDED
@@ -0,0 +1,18 @@
1
+ Spree::Core::Engine.routes.draw do
2
+ post '/paypal', :to => "paypal#express", :as => :paypal_express
3
+ get '/paypal/confirm', :to => "paypal#confirm", :as => :confirm_paypal
4
+ get '/paypal/cancel', :to => "paypal#cancel", :as => :cancel_paypal
5
+ get '/paypal/notify', :to => "paypal#notify", :as => :notify_paypal
6
+
7
+ namespace :admin do
8
+ # Using :only here so it doesn't redraw those routes
9
+ resources :orders, :only => [] do
10
+ resources :payments, :only => [] do
11
+ member do
12
+ get 'paypal_refund'
13
+ post 'paypal_refund'
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ class CreateSpreePaypalExpressCheckouts < ActiveRecord::Migration
2
+ def change
3
+ create_table :spree_paypal_express_checkouts do |t|
4
+ t.string :token
5
+ t.string :payer_id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ class AddTransactionIdToSpreePaypalExpressCheckouts < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_paypal_express_checkouts, :transaction_id, :string
4
+ add_index :spree_paypal_express_checkouts, :transaction_id
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddStateToSpreePaypalExpressCheckouts < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_paypal_express_checkouts, :state, :string, :default => "complete"
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class AddRefundedFieldsToSpreePaypalExpressCheckouts < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_paypal_express_checkouts, :refund_transaction_id, :string
4
+ add_column :spree_paypal_express_checkouts, :refunded_at, :datetime
5
+ add_column :spree_paypal_express_checkouts, :refund_type, :string
6
+ add_column :spree_paypal_express_checkouts, :created_at, :datetime
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ module SpreePaypalExpress
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ class_option :auto_run_migrations, :type => :boolean, :default => false
6
+
7
+ def add_javascripts
8
+ append_file 'app/assets/javascripts/store/all.js', "//= require store/spree_paypal_express\n"
9
+ append_file 'app/assets/javascripts/admin/all.js', "//= require admin/spree_paypal_express\n"
10
+ end
11
+
12
+ def add_stylesheets
13
+ inject_into_file 'app/assets/stylesheets/store/all.css', " *= require store/spree_paypal_express\n", :before => /\*\//, :verbose => true
14
+ inject_into_file 'app/assets/stylesheets/admin/all.css', " *= require admin/spree_paypal_express\n", :before => /\*\//, :verbose => true
15
+ end
16
+
17
+ def add_migrations
18
+ run 'bundle exec rake railties:install:migrations FROM=spree_paypal_express'
19
+ end
20
+
21
+ def run_migrations
22
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
23
+ if run_migrations
24
+ run 'bundle exec rake db:migrate'
25
+ else
26
+ puts 'Skipping rake db:migrate, don\'t forget to run it!'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ require 'spree_core'
2
+ require 'spree_paypal_express/version'
3
+ require 'spree_paypal_express/engine'
@@ -0,0 +1,26 @@
1
+ module SpreePaypalExpress
2
+ class Engine < Rails::Engine
3
+ require 'spree/core'
4
+ isolate_namespace Spree
5
+ engine_name 'spree_paypal_express'
6
+
7
+ config.autoload_paths += %W(#{config.root}/lib)
8
+
9
+ # use rspec for tests
10
+ config.generators do |g|
11
+ g.test_framework :rspec
12
+ end
13
+
14
+ def self.activate
15
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
16
+ Rails.configuration.cache_classes ? require(c) : load(c)
17
+ end
18
+ end
19
+
20
+ config.to_prepare &method(:activate).to_proc
21
+
22
+ initializer "spree.paypal_express.payment_methods", :after => "spree.register.payment_methods" do |app|
23
+ app.config.spree.payment_methods << Spree::Gateway::PayPalExpress
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,6 @@
1
+ FactoryGirl.define do
2
+ # Define your Spree extensions Factories within this file to enable applications, and other extensions to use and override them.
3
+ #
4
+ # Example adding this to your spec_helper will load these Factories for use:
5
+ # require 'spree_paypal_express/factories'
6
+ end
@@ -0,0 +1,3 @@
1
+ module SpreePayPalExpress
2
+ VERSION = '2.0.4'
3
+ end
data/script/rails ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
4
+ ENGINE_PATH = File.expand_path('../../lib/spree_paypal_express/engine', __FILE__)
5
+
6
+ require 'rails/all'
7
+ require 'rails/engine/commands'
@@ -0,0 +1,244 @@
1
+ require 'spec_helper'
2
+
3
+ describe "PayPal", :js => true do
4
+ let!(:product) { FactoryGirl.create(:product, :name => 'iPad') }
5
+ before do
6
+ @gateway = Spree::Gateway::PayPalExpress.create!({
7
+ :preferred_login => "paypal_api1.ryanbigg.com",
8
+ :preferred_password => "1373587879",
9
+ :preferred_signature => "ACOYQHq-aXKftiD4jURhihawsVSsAsaMr4qH4Tz4K17mJoa3K4M0Dvop",
10
+ :name => "PayPal",
11
+ :active => true,
12
+ :environment => Rails.env
13
+ })
14
+ FactoryGirl.create(:shipping_method)
15
+ end
16
+ def fill_in_billing
17
+ within("#billing") do
18
+ fill_in "First Name", :with => "Test"
19
+ fill_in "Last Name", :with => "User"
20
+ fill_in "Street Address", :with => "1 User Lane"
21
+ # City, State and ZIP must all match for PayPal to be happy
22
+ fill_in "City", :with => "Adamsville"
23
+ select "United States of America", :from => "order_bill_address_attributes_country_id"
24
+ select "Alabama", :from => "order_bill_address_attributes_state_id"
25
+ fill_in "Zip", :with => "35005"
26
+ fill_in "Phone", :with => "555-AME-RICA"
27
+ end
28
+ end
29
+
30
+ def switch_to_paypal_login
31
+ # If you go through a payment once in the sandbox, it remembers your preferred setting.
32
+ # It defaults to the *wrong* setting for the first time, so we need to have this method.
33
+ unless page.has_selector?("#login_email")
34
+ find("#loadLogin").click
35
+ end
36
+ end
37
+ it "pays for an order successfully" do
38
+ visit spree.root_path
39
+ click_link 'iPad'
40
+ click_button 'Add To Cart'
41
+ click_button 'Checkout'
42
+ within("#guest_checkout") do
43
+ fill_in "Email", :with => "test@example.com"
44
+ click_button 'Continue'
45
+ end
46
+ fill_in_billing
47
+ click_button "Save and Continue"
48
+ # Delivery step doesn't require any action
49
+ click_button "Save and Continue"
50
+ find("#paypal_button").click
51
+ switch_to_paypal_login
52
+ fill_in "login_email", :with => "pp@spreecommerce.com"
53
+ fill_in "login_password", :with => "thequickbrownfox"
54
+ click_button "Log In"
55
+ find("#continue_abovefold").click # Because there's TWO continue buttons.
56
+ page.should have_content("Your order has been processed successfully")
57
+
58
+ Spree::Payment.last.source.transaction_id.should_not be_blank
59
+ end
60
+
61
+ it "includes adjustments in PayPal summary" do
62
+ visit spree.root_path
63
+ click_link 'iPad'
64
+ click_button 'Add To Cart'
65
+ # TODO: Is there a better way to find this current order?
66
+ order = Spree::Order.last
67
+ order.adjustments.create!(:amount => -5, :label => "$5 off")
68
+ order.adjustments.create!(:amount => 10, :label => "$10 on")
69
+ visit '/cart'
70
+ within("#cart_adjustments") do
71
+ page.should have_content("$5 off")
72
+ page.should have_content("$10 on")
73
+ end
74
+ click_button 'Checkout'
75
+ within("#guest_checkout") do
76
+ fill_in "Email", :with => "test@example.com"
77
+ click_button 'Continue'
78
+ end
79
+ fill_in_billing
80
+ click_button "Save and Continue"
81
+ # Delivery step doesn't require any action
82
+ click_button "Save and Continue"
83
+ find("#paypal_button").click
84
+ within("#miniCart") do
85
+ page.should have_content("$5 off")
86
+ page.should have_content("$10 on")
87
+ end
88
+ end
89
+
90
+ # Regression test for #10
91
+ context "will skip $0 items" do
92
+ let!(:product2) { FactoryGirl.create(:product, :name => 'iPod') }
93
+
94
+ specify do
95
+ visit spree.root_path
96
+ click_link 'iPad'
97
+ click_button 'Add To Cart'
98
+
99
+ visit spree.root_path
100
+ click_link 'iPod'
101
+ click_button 'Add To Cart'
102
+
103
+ # TODO: Is there a better way to find this current order?
104
+ order = Spree::Order.last
105
+ order.line_items.last.update_attribute(:price, 0)
106
+ click_button 'Checkout'
107
+ within("#guest_checkout") do
108
+ fill_in "Email", :with => "test@example.com"
109
+ click_button 'Continue'
110
+ end
111
+ fill_in_billing
112
+ click_button "Save and Continue"
113
+ # Delivery step doesn't require any action
114
+ click_button "Save and Continue"
115
+ find("#paypal_button").click
116
+ within("#miniCart") do
117
+ page.should have_content('iPad')
118
+ page.should_not have_content('iPod')
119
+ end
120
+ end
121
+ end
122
+
123
+ context "can process an order with $0 item total" do
124
+ before do
125
+ # If we didn't do this then the order would be free and skip payment altogether
126
+ calculator = Spree::ShippingMethod.first.calculator
127
+ calculator.preferred_amount = 10
128
+ calculator.save
129
+ end
130
+
131
+ specify do
132
+ visit spree.root_path
133
+ click_link 'iPad'
134
+ click_button 'Add To Cart'
135
+ # TODO: Is there a better way to find this current order?
136
+ order = Spree::Order.last
137
+ order.adjustments.create!(:amount => -order.line_items.last.price, :label => "FREE iPad ZOMG!")
138
+ click_button 'Checkout'
139
+ within("#guest_checkout") do
140
+ fill_in "Email", :with => "test@example.com"
141
+ click_button 'Continue'
142
+ end
143
+ fill_in_billing
144
+ click_button "Save and Continue"
145
+ # Delivery step doesn't require any action
146
+ click_button "Save and Continue"
147
+ find("#paypal_button").click
148
+ within("#miniCart") do
149
+ page.should have_content('Current purchase')
150
+ end
151
+ end
152
+ end
153
+
154
+ context "cannot process a payment with invalid gateway details" do
155
+ before do
156
+ @gateway.preferred_login = nil
157
+ @gateway.save
158
+ end
159
+
160
+ specify do
161
+ visit spree.root_path
162
+ click_link 'iPad'
163
+ click_button 'Add To Cart'
164
+ click_button 'Checkout'
165
+ within("#guest_checkout") do
166
+ fill_in "Email", :with => "test@example.com"
167
+ click_button 'Continue'
168
+ end
169
+ fill_in_billing
170
+ click_button "Save and Continue"
171
+ # Delivery step doesn't require any action
172
+ click_button "Save and Continue"
173
+ find("#paypal_button").click
174
+ page.should have_content("PayPal failed. Security header is not valid")
175
+ end
176
+ end
177
+
178
+ context "as an admin" do
179
+ stub_authorization!
180
+
181
+ context "refunding payments" do
182
+ before do
183
+ visit spree.root_path
184
+ click_link 'iPad'
185
+ click_button 'Add To Cart'
186
+ click_button 'Checkout'
187
+ within("#guest_checkout") do
188
+ fill_in "Email", :with => "test@example.com"
189
+ click_button 'Continue'
190
+ end
191
+ fill_in_billing
192
+ click_button "Save and Continue"
193
+ # Delivery step doesn't require any action
194
+ click_button "Save and Continue"
195
+ find("#paypal_button").click
196
+ switch_to_paypal_login
197
+ fill_in "login_email", :with => "pp@spreecommerce.com"
198
+ fill_in "login_password", :with => "thequickbrownfox"
199
+ click_button "Log In"
200
+ find("#continue_abovefold").click # Because there's TWO continue buttons.
201
+ page.should have_content("Your order has been processed successfully")
202
+
203
+ visit '/admin'
204
+ click_link Spree::Order.last.number
205
+ click_link "Payments"
206
+ click_link "PayPal"
207
+ click_link "Refund"
208
+ end
209
+
210
+ it "can refund payments fully" do
211
+ click_button "Refund"
212
+ page.should have_content("PayPal refund successful")
213
+
214
+ payment = Spree::Payment.last
215
+ source = payment.source
216
+ source.refund_transaction_id.should_not be_blank
217
+ source.refunded_at.should_not be_blank
218
+ source.state.should eql("refunded")
219
+ source.refund_type.should eql("Full")
220
+ end
221
+
222
+ it "can refund payments partially" do
223
+ payment = Spree::Payment.last
224
+ # Take a dollar off, which should cause refund type to be...
225
+ fill_in "Amount", :with => payment.amount - 1
226
+ click_button "Refund"
227
+ page.should have_content("PayPal refund successful")
228
+
229
+ source = payment.source
230
+ source.refund_transaction_id.should_not be_blank
231
+ source.refunded_at.should_not be_blank
232
+ source.state.should eql("refunded")
233
+ # ... a partial refund
234
+ source.refund_type.should eql("Partial")
235
+ end
236
+
237
+ it "errors when given an invalid refund amount" do
238
+ fill_in "Amount", :with => "lol"
239
+ click_button "Refund"
240
+ page.should have_content("PayPal refund unsuccessful (The partial refund amount is not valid)")
241
+ end
242
+ end
243
+ end
244
+ end