solidus_six_saferpay 0.1.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/README.md +3 -0
  4. data/app/assets/javascripts/solidus_six_saferpay/saferpay_payment.js +4 -1
  5. data/app/controllers/spree/solidus_six_saferpay/checkout_controller.rb +65 -41
  6. data/app/controllers/spree/solidus_six_saferpay/transaction/checkout_controller.rb +1 -1
  7. data/app/models/spree/payment_method/saferpay_payment_method.rb +1 -1
  8. data/app/models/spree/payment_method/saferpay_payment_page.rb +2 -2
  9. data/app/models/spree/payment_method/saferpay_transaction.rb +2 -2
  10. data/app/services/spree/solidus_six_saferpay/cancel_authorized_payment.rb +33 -0
  11. data/app/services/spree/solidus_six_saferpay/initialize_payment.rb +0 -1
  12. data/app/services/spree/solidus_six_saferpay/initialize_transaction.rb +0 -1
  13. data/app/services/spree/solidus_six_saferpay/inquire_payment.rb +27 -1
  14. data/app/services/spree/solidus_six_saferpay/order_not_found_handler.rb +28 -0
  15. data/app/services/spree/solidus_six_saferpay/payment_not_found_handler.rb +28 -0
  16. data/app/services/spree/solidus_six_saferpay/payment_processing_success_handler.rb +26 -0
  17. data/app/views/spree/checkout/payment/_saferpay_payment.html.erb +2 -2
  18. data/app/views/spree/solidus_six_saferpay/checkout/{iframe_breakout_redirect.html.erb → iframe_breakout_redirect.erb} +1 -1
  19. data/config/locales/de.yml +3 -0
  20. data/config/locales/en.yml +3 -0
  21. data/config/locales/fr.yml +3 -0
  22. data/config/routes.rb +6 -6
  23. data/lib/solidus_six_saferpay/configuration.rb +0 -2
  24. data/lib/solidus_six_saferpay/gateway.rb +11 -11
  25. data/lib/solidus_six_saferpay/payment_page_gateway.rb +8 -9
  26. data/lib/solidus_six_saferpay/transaction_gateway.rb +8 -9
  27. data/lib/solidus_six_saferpay/version.rb +1 -1
  28. data/solidus_six_saferpay.gemspec +2 -1
  29. data/spec/controllers/spree/solidus_six_saferpay/payment_page/checkout_controller_spec.rb +8 -197
  30. data/spec/controllers/spree/solidus_six_saferpay/transaction/checkout_controller_spec.rb +8 -220
  31. data/spec/services/spree/solidus_six_saferpay/assert_payment_page_spec.rb +2 -128
  32. data/spec/services/spree/solidus_six_saferpay/authorize_transaction_spec.rb +3 -127
  33. data/spec/services/spree/solidus_six_saferpay/cancel_authorized_payment_spec.rb +58 -0
  34. data/spec/services/spree/solidus_six_saferpay/initialize_payment_page_spec.rb +0 -2
  35. data/spec/services/spree/solidus_six_saferpay/initialize_transaction_spec.rb +0 -1
  36. data/spec/services/spree/solidus_six_saferpay/inquire_payment_page_payment_spec.rb +2 -98
  37. data/spec/services/spree/solidus_six_saferpay/inquire_transaction_payment_spec.rb +2 -98
  38. data/spec/services/spree/solidus_six_saferpay/order_not_found_handler_spec.rb +30 -0
  39. data/spec/services/spree/solidus_six_saferpay/payment_not_found_handler_spec.rb +30 -0
  40. data/spec/services/spree/solidus_six_saferpay/payment_processing_success_handler_spec.rb +34 -0
  41. data/spec/services/spree/solidus_six_saferpay/process_payment_page_payment_spec.rb +1 -206
  42. data/spec/services/spree/solidus_six_saferpay/process_transaction_payment_spec.rb +1 -200
  43. data/spec/solidus_six_saferpay/configuration_spec.rb +0 -3
  44. data/spec/solidus_six_saferpay/gateway_spec.rb +1 -14
  45. data/spec/solidus_six_saferpay/payment_page_gateway_spec.rb +16 -14
  46. data/spec/solidus_six_saferpay/transaction_gateway_spec.rb +17 -14
  47. data/spec/support/shared_examples/authorize_payment.rb +132 -0
  48. data/spec/support/shared_examples/checkout_controller.rb +294 -0
  49. data/spec/support/shared_examples/inquire_payment.rb +118 -0
  50. data/spec/support/shared_examples/process_authorized_payment.rb +202 -0
  51. metadata +41 -10
  52. data/app/services/spree/solidus_six_saferpay/inquire_transaction.rb +0 -7
  53. data/spec/controllers/spree/solidus_six_saferpay/checkout_controller_spec.rb +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96b400cf874bdda08cfb33f3030d266f58444ec30e8e002ece965b5cb3ae83e3
4
- data.tar.gz: c1b05c8a67b4399814684d412c0fed4f60903a62d102bbda5d2929ba5ac81a4d
3
+ metadata.gz: 2bf3c36e740b9b6d5735b4ab0a9d9e2a3166c832cc25539f8df4af656b772ea9
4
+ data.tar.gz: 664c704fe719d59044d8f4566cfcbb48320cc2c5bfbc7619fff4de70c4639495
5
5
  SHA512:
6
- metadata.gz: dc50f1d3e2be9d70f340b78a9f12f9fbd2cd7b531862fa56c0fbe1c9717cf44e067d1b74f31899532988bea30280ad7e25688348534e02c4712e4634cada9b0b
7
- data.tar.gz: 545277b46dbb2bb516e04c90903b3b8878b031a0a741f29361bd1af71c9d9395e055035d512c4c2f1207c58a5416dc831b0ee3f0ccb459cf6a7b12c1317d72f4
6
+ metadata.gz: 9236e3a2555bfbcc2934d556cf1cfcdacc1f7735b04f9452b5de7826fd5839c073c016b996acb47c9a3f93bacb33b62fad7077180c7ecdb63b6e7c99a8ea9cc2
7
+ data.tar.gz: '01816590075677c47c4d4c0c9b0bd24e81115acc4437a231f54a57da60db1a26d0c38e0283b1d0da3a8bafbd2a5c6c319156f43b426505cf70a4fcc2af0a0c23'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.2
data/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  The `solidus_six_saferpay` engine adds checkout options for the Saferpay Payment Page ([Integration Guide](https://saferpay.github.io/sndbx/Integration_PP.html), [JSON API documentation](http://saferpay.github.io/jsonapi/#ChapterPaymentPage)) and the Saferpay Transaction ([Integration Guide](https://saferpay.github.io/sndbx/Integration_trx.html), [JSON API documentation](https://saferpay.github.io/sndbx/Integration_trx.html)).
4
4
 
5
+ ### Disclaimer
6
+ This gem is built to be a general-purpose integration of the Six Saferpay payment interface. However due to lack of resources and because we are (as far as we know) the only users of this gem, we are only testing our use cases (PaymentPage). Therefore we can not guarantee that this will work in any other solidus shop. If you consider using this gem, please test everything thoroughly.
7
+
5
8
  ## Status
6
9
  Travis CI status: [![Build Status](https://travis-ci.org/fadendaten/solidus_six_saferpay.svg?branch=master)](https://travis-ci.org/fadendaten/solidus_six_saferpay)
7
10
 
@@ -18,7 +18,10 @@ let SaferpayPayment = {
18
18
  },
19
19
 
20
20
  error: function(xhr) {
21
- console.error(xhr.responseText);
21
+ // We have no other option for displaying this message, so to inform
22
+ // the user we must alert
23
+ alert(xhr.responseJSON.errors);
24
+ window.location.replace(xhr.responseJSON.redirect_url);
22
25
  return false;
23
26
  },
24
27
  })
@@ -3,11 +3,25 @@ module Spree
3
3
  class CheckoutController < StoreController
4
4
 
5
5
  def init
6
- # loading the order is not shared between actions because the success
7
- # action must break out of the iframe
8
- @order = current_order
9
- redirect_to(spree.cart_path) && return unless @order
10
-
6
+ order_number = params[:order_number]
7
+ @order = Spree::Order.find_by(number: order_number)
8
+
9
+ # We must make sure that the order for which the user requests a
10
+ # payment is still their `current_order`.
11
+ # This can be false if the user tries to add something to the cart
12
+ # after getting to the payment checkout step, and then switches back to
13
+ # the payment step and starts the payment process by selecting a
14
+ # payment method.
15
+ # In that case, we redirect to the checkout path so the user needs to
16
+ # go through the checkout process again to ensure that the real
17
+ # `current_order` contains all necessary information.
18
+ if @order.nil? || @order != current_order
19
+ render json: {
20
+ redirect_url: spree.cart_path,
21
+ errors: t('.order_was_modified_after_confirmation')
22
+ }, status: 422
23
+ return
24
+ end
11
25
 
12
26
  payment_method = Spree::PaymentMethod.find(params[:payment_method_id])
13
27
  initialized_payment = initialize_payment(@order, payment_method)
@@ -16,16 +30,35 @@ module Spree
16
30
  redirect_url = initialized_payment.redirect_url
17
31
  render json: { redirect_url: redirect_url }
18
32
  else
19
- render json: { errors: t('.checkout_not_initialized') }, status: 422
33
+ render json: {
34
+ redirect_url: spree.cart_path,
35
+ errors: t('.checkout_not_initialized')
36
+ }, status: 422
20
37
  end
21
38
  end
22
39
 
23
40
  def success
24
- # loading the order is not shared between actions because the success
25
- # action must break out of the iframe
26
- @order = current_order
41
+ order_number = params[:order_number]
42
+ @order = Spree::Order.find_by(number: order_number)
43
+
27
44
  if @order.nil?
28
- @redirect_path = spree.cart_path
45
+ OrderNotFoundHandler.call(controller_context: self, order_number: order_number)
46
+
47
+ flash[:error] = t('.error_while_processing_payment')
48
+ @redirect_path ||= spree.cart_path
49
+
50
+ render :iframe_breakout_redirect, layout: false
51
+ return
52
+ end
53
+
54
+ saferpay_payment = Spree::SixSaferpayPayment.where(order_id: @order.id).order(:created_at).last
55
+
56
+ if saferpay_payment.nil?
57
+ PaymentNotFoundHandler.call(controller_context: self, order: @order)
58
+
59
+ flash[:error] = t('.error_while_processing_payment')
60
+ @redirect_path ||= spree.cart_path
61
+
29
62
  render :iframe_breakout_redirect, layout: false
30
63
  return
31
64
  end
@@ -33,17 +66,14 @@ module Spree
33
66
  # ensure that completed orders don't try to reprocess the
34
67
  # authorization. This could happen if a user presses the back button
35
68
  # after completing an order.
69
+ # There is no error handling because it should look like you are simply
70
+ # redirected to the cart page.
36
71
  if @order.completed?
37
72
  @redirect_path = spree.cart_path
38
73
  render :iframe_breakout_redirect, layout: false
39
74
  return
40
75
  end
41
76
 
42
- saferpay_payment = Spree::SixSaferpayPayment.where(order_id: @order.id).order(:created_at).last
43
-
44
- if saferpay_payment.nil?
45
- raise Spree::Core::GatewayError, t('.saferpay_payment_not_found')
46
- end
47
77
 
48
78
  # NOTE: PaymentPage payments are authorized directly. Instead, we
49
79
  # perform an ASSERT here to gather the necessary details.
@@ -60,7 +90,7 @@ module Spree
60
90
 
61
91
  processed_authorization = process_authorization(saferpay_payment)
62
92
  if processed_authorization.success?
63
- handle_payment_processing_success
93
+ PaymentProcessingSuccessHandler.call(controller_context: self, order: @order)
64
94
  else
65
95
  flash[:error] = processed_authorization.user_message
66
96
  end
@@ -75,24 +105,34 @@ module Spree
75
105
  end
76
106
 
77
107
  def fail
78
- # loading the order is not shared between actions because the success
79
- # action must break out of the iframe
80
- @order = current_order
108
+ order_number = params[:order_number]
109
+ @order = Spree::Order.find_by(number: order_number)
110
+
81
111
  if @order.nil?
82
- @redirect_path = spree.cart_path
112
+ OrderNotFoundHandler.call(controller_context: self, order_number: order_number)
113
+
114
+ flash[:error] = t('.error_while_processing_payment')
115
+ @redirect_path ||= spree.cart_path
116
+
83
117
  render :iframe_breakout_redirect, layout: false
84
118
  return
85
119
  end
86
120
 
87
121
  saferpay_payment = Spree::SixSaferpayPayment.where(order_id: @order.id).order(:created_at).last
88
122
 
89
- if saferpay_payment
90
- payment_inquiry = inquire_payment(saferpay_payment)
91
- flash[:error] = payment_inquiry.user_message
92
- else
93
- flash[:error] = I18n.t(:general_error, scope: [:solidus_six_saferpay, :errors])
123
+ if saferpay_payment.nil?
124
+ PaymentNotFoundHandler.call(controller_context: self, order: @order)
125
+
126
+ flash[:error] = t('.error_while_processing_payment')
127
+ @redirect_path = spree.cart_path
128
+ render :iframe_breakout_redirect, layout: false
129
+ return
94
130
  end
95
131
 
132
+
133
+ payment_inquiry = inquire_payment(saferpay_payment)
134
+ flash[:error] = payment_inquiry.user_message
135
+
96
136
  @redirect_path = order_checkout_path(:payment)
97
137
  render :iframe_breakout_redirect, layout: false
98
138
  end
@@ -115,22 +155,6 @@ module Spree
115
155
  raise NotImplementedError, "Must be implemented in PaymentPageCheckoutController or TransactionCheckoutController"
116
156
  end
117
157
 
118
- # Allows overriding of success behaviour in host application by setting
119
- # SolidusSixSaferpay.config.payment_processing_success_handler
120
- #
121
- # By default, it will ensure that the order state is no longer "payment"
122
- #
123
- # Example
124
- # config.payment_processing_success_handler = Proc.new { |order| puts "Order #{order} has been successfully paid!" }
125
- #
126
- def handle_payment_processing_success
127
- if success_handler = ::SolidusSixSaferpay.config.payment_processing_success_handler.presence
128
- success_handler.call(self, @order)
129
- else
130
- @order.next! if @order.payment?
131
- end
132
- end
133
-
134
158
  def order_checkout_path(state)
135
159
  Spree::Core::Engine.routes.url_helpers.checkout_state_path(state)
136
160
  end
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  module SolidusSixSaferpay
3
- # explicit parent must be stated, otherwise Spree::CheckoutController has precendence
4
3
  module Transaction
4
+ # explicit parent must be stated, otherwise Spree::CheckoutController has precendence
5
5
  class CheckoutController < SolidusSixSaferpay::CheckoutController
6
6
 
7
7
  private
@@ -39,7 +39,7 @@ module Spree
39
39
  'saferpay_payment'
40
40
  end
41
41
 
42
- def init_path
42
+ def init_path(order)
43
43
  raise NotImplementedError, "Must be implemented in SaferpayPaymentPage or SaferpayTransaction"
44
44
  end
45
45
  end
@@ -6,8 +6,8 @@ module Spree
6
6
  ::SolidusSixSaferpay::PaymentPageGateway
7
7
  end
8
8
 
9
- def init_path
10
- url_helpers.solidus_six_saferpay_payment_page_init_path
9
+ def init_path(order)
10
+ url_helpers.solidus_six_saferpay_payment_page_init_path(order.number)
11
11
  end
12
12
  end
13
13
  end
@@ -5,8 +5,8 @@ module Spree
5
5
  ::SolidusSixSaferpay::TransactionGateway
6
6
  end
7
7
 
8
- def init_path
9
- url_helpers.solidus_six_saferpay_transaction_init_path
8
+ def init_path(order)
9
+ url_helpers.solidus_six_saferpay_transaction_init_path(order.number)
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ module SolidusSixSaferpay
3
+ class CancelAuthorizedPayment
4
+
5
+ attr_reader :saferpay_payment
6
+
7
+ def self.call(saferpay_payment)
8
+ new(saferpay_payment).call
9
+ end
10
+
11
+ def initialize(saferpay_payment)
12
+ @saferpay_payment = saferpay_payment
13
+ end
14
+
15
+ def call
16
+ if transaction_id = saferpay_payment.transaction_id
17
+ gateway.void(saferpay_payment.transaction_id)
18
+ else
19
+ ::SolidusSixSaferpay::ErrorHandler.handle(
20
+ ::SolidusSixSaferpay::InvalidSaferpayPayment.new(
21
+ details: "Can not cancel payment #{saferpay_payment.id} because it has no transaction ID.")
22
+ )
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def gateway
29
+ ::SolidusSixSaferpay::Gateway.new
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,6 +1,5 @@
1
1
  module Spree
2
2
  module SolidusSixSaferpay
3
-
4
3
  class InitializePayment
5
4
 
6
5
  attr_reader :order, :payment_method, :redirect_url, :success
@@ -1,6 +1,5 @@
1
1
  module Spree
2
2
  module SolidusSixSaferpay
3
-
4
3
  class InitializeTransaction < InitializePayment
5
4
  include UseTransactionGateway
6
5
 
@@ -19,8 +19,9 @@ module Spree
19
19
  inquiry = gateway.inquire(saferpay_payment)
20
20
 
21
21
  if inquiry.success?
22
- saferpay_payment.update_attributes(response_hash: inquiry.api_response.to_h)
22
+ saferpay_payment.update_attributes(saferpay_payment_attributes(inquiry.api_response))
23
23
  else
24
+ saferpay_payment.update_attributes(response_hash: saferpay_payment.response_hash.merge(error: "#{inquiry.error_name}"))
24
25
  general_error = I18n.t(:general_error, scope: [:solidus_six_saferpay, :errors])
25
26
  specific_error = I18n.t(inquiry.error_name, scope: [:six_saferpay, :error_names])
26
27
  @user_message = "#{general_error}: #{specific_error}"
@@ -38,6 +39,31 @@ module Spree
38
39
  def gateway
39
40
  raise NotImplementedError, "Must be implemented in AssertPaymentPage or AuthorizeTransaction with UsePaymentPageGateway or UseTransactionGateway"
40
41
  end
42
+
43
+ private
44
+
45
+ def saferpay_payment_attributes(saferpay_response)
46
+ payment_means = saferpay_response.payment_means
47
+ brand = payment_means.brand
48
+ card = payment_means.card
49
+
50
+ attributes = {
51
+ transaction_id: saferpay_response.transaction.id,
52
+ transaction_status: saferpay_response.transaction.status,
53
+ transaction_date: DateTime.parse(saferpay_response.transaction.date),
54
+ six_transaction_reference: saferpay_response.transaction.six_transaction_reference,
55
+ display_text: saferpay_response.payment_means.display_text,
56
+ response_hash: saferpay_response.to_h
57
+ }
58
+
59
+ if card
60
+ attributes[:masked_number] = card.masked_number
61
+ attributes[:expiration_year] = card.exp_year
62
+ attributes[:expiration_month] = card.exp_month
63
+ end
64
+
65
+ attributes
66
+ end
41
67
  end
42
68
  end
43
69
  end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module SolidusSixSaferpay
3
+
4
+ # This handler can be overridden by host applications to manage control
5
+ # flow if no order can be found when SIX Saferpay performs the callback
6
+ # request after the user submits a payment.
7
+ # If not overridden, the handler will simply trigger an error.
8
+ class OrderNotFoundHandler
9
+
10
+ attr_reader :controller_context, :order_number
11
+
12
+ def self.call(controller_context:, order_number:)
13
+ new(controller_context: controller_context, order_number: order_number).call
14
+ end
15
+
16
+ def initialize(controller_context:, order_number:)
17
+ @controller_context = controller_context
18
+ @order_number = order_number
19
+ end
20
+
21
+ def call
22
+ ::SolidusSixSaferpay::ErrorHandler.handle(
23
+ StandardError.new("No solidus order could be found for number #{order_number}")
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module SolidusSixSaferpay
3
+
4
+ # This handler can be overridden by host applications to manage control
5
+ # flow if no payment can be found when SIX Saferpay performs the callback
6
+ # request after the user submits a payment.
7
+ # If not overridden, the handler will simply trigger an error.
8
+ class PaymentNotFoundHandler
9
+
10
+ attr_reader :controller_context, :order
11
+
12
+ def self.call(controller_context:, order:)
13
+ new(controller_context: controller_context, order: order).call
14
+ end
15
+
16
+ def initialize(controller_context:, order:)
17
+ @controller_context = controller_context
18
+ @order = order
19
+ end
20
+
21
+ def call
22
+ ::SolidusSixSaferpay::ErrorHandler.handle(
23
+ StandardError.new("No Saferpay Payment found for order #{order.number}")
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module Spree
2
+ module SolidusSixSaferpay
3
+
4
+ # This handler can be overridden by host applications to manage control
5
+ # flow after the payment authorization was successful and the payment was verified.
6
+ # If not overridden, the handler will simply ensure that the order has
7
+ # moved from the "payment" state to the next state.
8
+ class PaymentProcessingSuccessHandler
9
+
10
+ attr_reader :controller_context, :order
11
+
12
+ def self.call(controller_context:, order:)
13
+ new(controller_context: controller_context, order: order).call
14
+ end
15
+
16
+ def initialize(controller_context:, order:)
17
+ @controller_context = controller_context
18
+ @order = order
19
+ end
20
+
21
+ def call
22
+ order.next! if order.payment?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -3,10 +3,10 @@
3
3
  <iframe class="saferpay-iframe" width='100%' height='550px' id="<%= payment_container_id %>" src="" frameborder="0">
4
4
  </iframe>
5
5
  <script charset="utf-8">
6
- SaferpayPayment.registerIframePaymentMethod({id: "<%= payment_method.id %>", initUrl: "<%= payment_method.init_path %>", containerId: "#<%= payment_container_id %>"})
6
+ SaferpayPayment.registerIframePaymentMethod({id: "<%= payment_method.id %>", initUrl: "<%= payment_method.init_path(@order) %>", containerId: "#<%= payment_container_id %>"})
7
7
  </script>
8
8
  <% else %>
9
9
  <script charset="utf-8">
10
- SaferpayPayment.registerExternalRedirectPaymentMethod({id: "<%= payment_method.id %>", initUrl: "<%= payment_method.init_path %>"})
10
+ SaferpayPayment.registerExternalRedirectPaymentMethod({id: "<%= payment_method.id %>", initUrl: "<%= payment_method.init_path(@order) %>"})
11
11
  </script>
12
12
  <% end %>