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,44 @@
1
+ module Spree
2
+ class PaypalExpressCallbacksController < Spree::BaseController
3
+ include ActiveMerchant::Billing::Integrations
4
+ skip_before_filter :verify_authenticity_token
5
+
6
+ ssl_required
7
+
8
+ def notify
9
+ retrieve_details #need to retreive details first to ensure ActiveMerchant gets configured correctly.
10
+
11
+
12
+ @notification = Paypal::Notification.new(request.raw_post)
13
+
14
+ # we only care about eChecks (for now?)
15
+ if @notification.params["payment_type"] == "echeck" && @notification.acknowledge && @payment && @order.total >= @payment.amount
16
+ @payment.started_processing!
17
+ @payment.log_entries.create(:details => @notification.to_yaml)
18
+
19
+ case @notification.params["payment_status"]
20
+ when "Denied"
21
+ @payment.failure!
22
+
23
+ when "Completed"
24
+ @payment.complete!
25
+ end
26
+
27
+ end
28
+
29
+ render :nothing => true
30
+ end
31
+
32
+ private
33
+ def retrieve_details
34
+ @order = Spree::Order.find_by_number(params["invoice"])
35
+
36
+ if @order
37
+ @payment = @order.payments.where(:state => "pending", :source_type => "PaypalAccount").try(:first)
38
+
39
+ @payment.try(:payment_method).try(:provider) #configures ActiveMerchant
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ Spree::CheckoutHelper.module_eval do
2
+
3
+ def checkout_states
4
+ %w(address delivery payment confirm complete)
5
+ end
6
+
7
+ end
@@ -0,0 +1,3 @@
1
+ class Spree::BillingIntegration::PaypalExpress < Spree::BillingIntegration::PaypalExpressBase
2
+ preference :currency, :string, :default => 'USD'
3
+ end
@@ -0,0 +1,62 @@
1
+ class Spree::BillingIntegration::PaypalExpressBase < Spree::BillingIntegration
2
+ preference :login, :string
3
+ preference :password, :password
4
+ preference :signature, :string
5
+ preference :review, :boolean, :default => false
6
+ preference :no_shipping, :boolean, :default => false
7
+ preference :currency, :string, :default => 'USD'
8
+ preference :allow_guest_checkout, :boolean, :default => false
9
+
10
+ attr_accessible :preferred_login, :preferred_password, :preferred_signature, :preferred_review, :preferred_no_shipping, :preferred_currency, :preferred_allow_guest_checkout, :preferred_server, :preferred_test_mode
11
+
12
+ def provider_class
13
+ ActiveMerchant::Billing::PaypalExpressGateway
14
+ end
15
+
16
+ def payment_profiles_supported?
17
+ !!preferred_review
18
+ end
19
+
20
+ def capture(payment_or_amount, account_or_response_code, gateway_options)
21
+ if payment_or_amount.is_a?(Spree::Payment)
22
+ authorization = find_authorization(payment_or_amount)
23
+ provider.capture(amount_in_cents(payment_or_amount.amount), authorization.params["transaction_id"], :currency => preferred_currency)
24
+ else
25
+ provider.capture(payment_or_amount, account_or_response_code, :currency => preferred_currency)
26
+ end
27
+ end
28
+
29
+ def credit(*args)
30
+ amount = args.shift
31
+ response_code = args.first.is_a?(String) ? args.first : args[1]
32
+ provider.credit(amount, response_code, :currency => preferred_currency)
33
+ end
34
+
35
+ def find_authorization(payment)
36
+ logs = payment.log_entries.all(:order => 'created_at DESC')
37
+ logs.each do |log|
38
+ details = YAML.load(log.details) # return the transaction details
39
+ if (details.params['payment_status'] == 'Pending' && details.params['pending_reason'] == 'authorization')
40
+ return details
41
+ end
42
+ end
43
+ return nil
44
+ end
45
+
46
+ def find_capture(payment)
47
+ #find the transaction associated with the original authorization/capture
48
+ logs = payment.log_entries.all(:order => 'created_at DESC')
49
+ logs.each do |log|
50
+ details = YAML.load(log.details) # return the transaction details
51
+ if details.params['payment_status'] == 'Completed'
52
+ return details
53
+ end
54
+ end
55
+ return nil
56
+ end
57
+
58
+ def amount_in_cents(amount)
59
+ (100 * amount).to_i
60
+ end
61
+
62
+ end
@@ -0,0 +1,3 @@
1
+ class Spree::BillingIntegration::PaypalExpressUk < Spree::BillingIntegration::PaypalExpressBase
2
+ preference :currency, :string, :default => 'GBP'
3
+ end
@@ -0,0 +1,3 @@
1
+ Spree::LogEntry.class_eval do
2
+ attr_accessible :details
3
+ end
@@ -0,0 +1,3 @@
1
+ Spree::Payment.class_eval do
2
+ attr_accessible :source, :source_type, :response_code, :avs_response, :details
3
+ end
@@ -0,0 +1,36 @@
1
+ class Spree::PaypalAccount < ActiveRecord::Base
2
+ attr_accessible :email, :payer_id, :payer_country, :payer_status
3
+ has_many :payments, :as => :source
4
+
5
+ def actions
6
+ %w{capture credit}
7
+ end
8
+
9
+ def can_capture?(payment)
10
+ !echeck?(payment) && payment.state == "pending"
11
+ end
12
+
13
+ def can_credit?(payment)
14
+ return false unless payment.state == "completed"
15
+ return false unless payment.order.payment_state == "credit_owed"
16
+ payment.credit_allowed > 0
17
+ !payment.payment_method.find_capture(payment).nil?
18
+ end
19
+
20
+ # fix for Payment#payment_profiles_supported?
21
+ def payment_gateway
22
+ false
23
+ end
24
+
25
+ def echeck?(payment)
26
+ logs = payment.log_entries.all(:order => 'created_at DESC')
27
+ logs.each do |log|
28
+ details = YAML.load(log.details) # return the transaction details
29
+ if details.params['payment_type'] == 'echeck'
30
+ return true
31
+ end
32
+ end
33
+ return false
34
+ end
35
+
36
+ end
@@ -0,0 +1,16 @@
1
+ <!--
2
+ insert_bottom '.payment-info'
3
+ original '9d4ef160bd508a47f906d7419c33cfc7eefc501d'
4
+ -->
5
+ <% if order.payment && order.payment.source.class.to_s == 'Spree::PaypalAccount' %>
6
+ <span class="cc-type">
7
+ <%= image_tag "paypal.png" %>
8
+ <%= order.payment.source.payer_status.to_s.capitalize %>
9
+ </span>
10
+ <br />
11
+ <span class="full-name">
12
+ <%= order.payment.source.email %>
13
+ </span>
14
+ <% end %>
15
+
16
+
@@ -0,0 +1,9 @@
1
+ <% content_for :head do %>
2
+ <script type="text/javascript">
3
+ jQuery(document).ready(function(){
4
+ $("label:contains('<%= payment_method.name %>')").hide();
5
+ $("label:contains('<%= payment_method.name %>') input").disable();
6
+ });
7
+ </script>
8
+ <% end %>
9
+
@@ -0,0 +1,9 @@
1
+ <% content_for :head do %>
2
+ <script type="text/javascript">
3
+ jQuery(document).ready(function(){
4
+ $("label:contains('<%= payment_method.name %>')").hide();
5
+ $("label:contains('<%= payment_method.name %>') input").disable();
6
+ });
7
+ </script>
8
+ <% end %>
9
+
@@ -0,0 +1,110 @@
1
+ <fieldset>
2
+ <legend><%= t('paypal_account') %></legend>
3
+
4
+ <table class="index">
5
+ <tr>
6
+ <th colspan="6"><%= t('account_details') %></th>
7
+ </tr>
8
+ <tr>
9
+ <td><label><%= t("email") %>:</label></td>
10
+ <td>
11
+ <%= payment.source.email %>
12
+ </td>
13
+ <td><label><%= t("payer_id") %>:</label></td>
14
+ <td>
15
+ <%= payment.source.payer_id %>
16
+ </td>
17
+ <td><label><%= t("payer_country") %>:</label></td>
18
+ <td>
19
+ <%= payment.source.payer_country %>
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <td><label><%= t("payer_status") %>:</label></td>
24
+ <td colspan="5">
25
+ <%= payment.source.payer_status %>
26
+ </td>
27
+ </tr>
28
+ </table>
29
+ </fieldset>
30
+
31
+ <fieldset>
32
+ <legend><%= t('transactions') %></legend>
33
+
34
+ <% payment.log_entries.reverse.each do |log| %>
35
+ <% details = YAML.load(log.details) rescue "" %>
36
+ <table class="index">
37
+
38
+ <% if details.is_a? ActiveMerchant::Billing::PaypalExpressResponse %>
39
+ <tr>
40
+ <th colspan="6"><%= t('transaction') %> <%= details.params["transaction_id"] %> - <%= log.created_at.to_s(:date_time24) %></th>
41
+ </tr>
42
+ <tr>
43
+ <td width="12%;"><label><%= t('type') %>:</label></td>
44
+ <td width="20%;">
45
+ <%= details.params["transaction_type"] %>
46
+ </td>
47
+ <td width="8%;"><label><%= t("result") %>:</label></td>
48
+ <td width="20%;">
49
+ <%= details.message %>
50
+ </td>
51
+ <td width="15%;"><label><%= t("amount") %>:</label></td>
52
+ <td width="20%;">
53
+ <%= number_to_currency details.params["gross_amount"] %>
54
+ </td>
55
+ </tr>
56
+ <tr>
57
+ <td><label><%= t("comment") %>:</label></td>
58
+ <td colspan="3">
59
+ <%= details.params["message"] %>
60
+ </td>
61
+ <td><label><%= t("status") %>:</label></td>
62
+ <td>
63
+ <%= details.params["payment_status"] %>
64
+ </td>
65
+ </tr>
66
+ <% if details.params["payment_status"] == "Pending" %>
67
+ <tr>
68
+ <td><label><%= t("pending_reason") %>:</label></td>
69
+ <td colspan="5">
70
+ <%= details.params["pending_reason"] %>
71
+ </td>
72
+ </tr>
73
+ <% end %>
74
+ <% elsif details.is_a? ActiveMerchant::Billing::Integrations::Paypal::Notification %>
75
+ <tr>
76
+ <th colspan="6"><%= t('ipn_transaction') %> <%= details.params["txn_id"] %> - <%= log.created_at.to_s(:date_time24) %></th>
77
+ </tr>
78
+ <tr>
79
+ <td width="12%;"><label><%= t('type') %>:</label></td>
80
+ <td width="20%;">
81
+ <%= details.params["txn_type"] %>
82
+ </td>
83
+ <td width="8%;"><label><%= t("result") %>:</label></td>
84
+ <td width="20%;">
85
+ <%= details.params["payment_status"] %>
86
+ </td>
87
+ <td width="15%;"><label><%= t("amount") %>:</label></td>
88
+ <td width="20%;">
89
+ <%= number_to_currency details.params["mc_gross"] %>
90
+ </td>
91
+ </tr>
92
+ <tr>
93
+ <td><label><%= t("status") %>:</label></td>
94
+ <td colspan="5">
95
+ <%= details.params["payment_status"] %>
96
+ </td>
97
+ </tr>
98
+ <% else %>
99
+ <tr>
100
+ <th colspan="6"><%= t('unknown_transaction') %> - <%= log.created_at.to_s(:date_time24) %></th>
101
+ </tr>
102
+ <tr>
103
+ <td colspan="6"><pre style="overflow: hidden; width:600px;"><%= log.details %></pre></th>
104
+ </tr>
105
+
106
+ <% end %>
107
+ </table>
108
+ <% end %>
109
+
110
+ </fieldset>
@@ -0,0 +1,110 @@
1
+ <fieldset>
2
+ <legend><%= t('paypal_account') %></legend>
3
+
4
+ <table class="index">
5
+ <tr>
6
+ <th colspan="6"><%= t('account_details') %></th>
7
+ </tr>
8
+ <tr>
9
+ <td><label><%= t("email") %>:</label></td>
10
+ <td>
11
+ <%= payment.source.email %>
12
+ </td>
13
+ <td><label><%= t("payer_id") %>:</label></td>
14
+ <td>
15
+ <%= payment.source.payer_id %>
16
+ </td>
17
+ <td><label><%= t("payer_country") %>:</label></td>
18
+ <td>
19
+ <%= payment.source.payer_country %>
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <td><label><%= t("payer_status") %>:</label></td>
24
+ <td colspan="5">
25
+ <%= payment.source.payer_status %>
26
+ </td>
27
+ </tr>
28
+ </table>
29
+ </fieldset>
30
+
31
+ <fieldset>
32
+ <legend><%= t('transactions') %></legend>
33
+
34
+ <% payment.log_entries.reverse.each do |log| %>
35
+ <% details = YAML.load(log.details) rescue "" %>
36
+ <table class="index">
37
+
38
+ <% if details.is_a? ActiveMerchant::Billing::PaypalExpressResponse %>
39
+ <tr>
40
+ <th colspan="6"><%= t('transaction') %> <%= details.params["transaction_id"] %> - <%= log.created_at.to_s(:date_time24) %></th>
41
+ </tr>
42
+ <tr>
43
+ <td width="12%;"><label><%= t('type') %>:</label></td>
44
+ <td width="20%;">
45
+ <%= details.params["transaction_type"] %>
46
+ </td>
47
+ <td width="8%;"><label><%= t("result") %>:</label></td>
48
+ <td width="20%;">
49
+ <%= details.message %>
50
+ </td>
51
+ <td width="15%;"><label><%= t("amount") %>:</label></td>
52
+ <td width="20%;">
53
+ <%= number_to_currency details.params["gross_amount"] %>
54
+ </td>
55
+ </tr>
56
+ <tr>
57
+ <td><label><%= t("comment") %>:</label></td>
58
+ <td colspan="3">
59
+ <%= details.params["message"] %>
60
+ </td>
61
+ <td><label><%= t("status") %>:</label></td>
62
+ <td>
63
+ <%= details.params["payment_status"] %>
64
+ </td>
65
+ </tr>
66
+ <% if details.params["payment_status"] == "Pending" %>
67
+ <tr>
68
+ <td><label><%= t("pending_reason") %>:</label></td>
69
+ <td colspan="5">
70
+ <%= details.params["pending_reason"] %>
71
+ </td>
72
+ </tr>
73
+ <% end %>
74
+ <% elsif details.is_a? ActiveMerchant::Billing::Integrations::Paypal::Notification %>
75
+ <tr>
76
+ <th colspan="6"><%= t('ipn_transaction') %> <%= details.params["txn_id"] %> - <%= log.created_at.to_s(:date_time24) %></th>
77
+ </tr>
78
+ <tr>
79
+ <td width="12%;"><label><%= t('type') %>:</label></td>
80
+ <td width="20%;">
81
+ <%= details.params["txn_type"] %>
82
+ </td>
83
+ <td width="8%;"><label><%= t("result") %>:</label></td>
84
+ <td width="20%;">
85
+ <%= details.params["payment_status"] %>
86
+ </td>
87
+ <td width="15%;"><label><%= t("amount") %>:</label></td>
88
+ <td width="20%;">
89
+ <%= number_to_currency details.params["mc_gross"] %>
90
+ </td>
91
+ </tr>
92
+ <tr>
93
+ <td><label><%= t("status") %>:</label></td>
94
+ <td colspan="5">
95
+ <%= details.params["payment_status"] %>
96
+ </td>
97
+ </tr>
98
+ <% else %>
99
+ <tr>
100
+ <th colspan="6"><%= t('unknown_transaction') %> - <%= log.created_at.to_s(:date_time24) %></th>
101
+ </tr>
102
+ <tr>
103
+ <td colspan="6"><pre style="overflow: hidden; width:600px;"><%= log.details %></pre></th>
104
+ </tr>
105
+
106
+ <% end %>
107
+ </table>
108
+ <% end %>
109
+
110
+ </fieldset>
@@ -0,0 +1,15 @@
1
+ <%= render :partial => 'spree/admin/shared/order_tabs', :locals => {:current => "Payments"} %>
2
+
3
+ <% form_tag do %>
4
+
5
+ <h3><%= t('refund') %></h3>
6
+ <fieldset>
7
+ <p>
8
+ <label for="amount"><%= t("amount") %></label>
9
+ <%= text_field_tag :amount, @paypal_payment.amount %>
10
+ </p>
11
+ <p class="form-buttons">
12
+ <%= button t("make_refund") %>
13
+ </p>
14
+ </fieldset>
15
+ <% end %>