spree_paypal_express 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %>