solidus-adyen 0.1.3 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 08b088acce113a3bfb9cbbb3e9c2ef2537440a0f
4
- data.tar.gz: efe31205a5b93b5901c4a7c0d31d9a862dfd46dd
3
+ metadata.gz: 8d5a390a36ec18d1af12c1f0d506bbb43d2bb26a
4
+ data.tar.gz: 7d2ddb172391f7a153bf8208bea04a63f8f3618c
5
5
  SHA512:
6
- metadata.gz: 7d6a081a47bf73716edc97270f2ea7319b58960a1b6eba7764b6403488bacdc8159904e46027c8914b2e7a4ed3e6d4cb9528fb1f15d73667c514229433084369
7
- data.tar.gz: 1ee9ade8ed1b00679ee61c7e27d120be94945dcd1af96cbdb995c3d8d8ade8e279d5cfe445fe97effaf3398b9de58c9fbac22733324d420a0d0aeac979c7d554
6
+ metadata.gz: 4a40f1640428be6f878c1ed88cd3c1b560e8d0675ecf04e985f8eaa28524d14e6ac017e5c2ed72f9ad0999e07aa6742349d4c9761c8a42cc23c41127876948d5
7
+ data.tar.gz: 4824b60671ed21f56d2a0e4d489aafb6154d4c896ddf4e4155fe6903e927190df990c7345b854c2188c3b9210158aacb921ed6f6afaf5b1ef0c8fb2446eaee63
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  module Adyen
3
- class HppsController < ::ActionController::Base
3
+ class HppsController < StoreController
4
4
  load_resource :order, class: "Spree::Order", id_param: :order_id
5
5
  load_resource(
6
6
  :payment_method,
@@ -7,7 +7,17 @@ module Spree
7
7
 
8
8
  # This is the entry point after an Adyen HPP payment is completed
9
9
  def confirm
10
- source = Adyen::HppSource.new(source_params(params))
10
+ if @order.complete?
11
+ confirm_order_already_completed
12
+ else
13
+ confirm_order_incomplete
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def confirm_order_incomplete
20
+ source = Adyen::HppSource.new(source_params)
11
21
 
12
22
  unless source.authorised?
13
23
  flash.notice = Spree.t(:payment_processing_failed)
@@ -18,40 +28,39 @@ module Spree
18
28
  # payment is created in a 'checkout' state so that the payment method
19
29
  # can attempt to auth it. The payment of course is already auth'd and
20
30
  # adyen hpp's authorize implementation just returns a dummy response.
21
- payment =
22
- @order.payments.create!(
23
- amount: @order.total,
24
- payment_method: @payment_method,
25
- source: source,
26
- response_code: params[:pspReference],
27
- state: "checkout",
28
- # Order is explicitly defined here because as of writing the
29
- # Order -> Payments association does not have the inverse of defined
30
- # when we call `order.complete` below payment.order will still
31
- # refer to a previous state of the record.
32
- #
33
- # If the payment is auto captured only then the payment will completed
34
- # in `process_outstanding!`, and because Payment calls
35
- # .order.update_totals after save the order is saved with its
36
- # previous values, causing payment_state and shipment_state to revert
37
- # to nil.
38
- order: @order
39
- )
31
+ @order.payments.create!(
32
+ amount: @order.total,
33
+ payment_method: @payment_method,
34
+ source: source,
35
+ response_code: psp_reference,
36
+ state: "checkout"
37
+ )
40
38
 
41
39
  if @order.complete
42
- # We may have already recieved the authorization notification, so process
43
- # it now
44
- Spree::Adyen::NotificationProcessor.process_outstanding!(payment)
45
-
46
- flash.notice = Spree.t(:order_processed_successfully)
47
- redirect_to order_path(@order)
40
+ redirect_to_order
48
41
  else
49
42
  #TODO void/cancel payment
50
43
  redirect_to checkout_state_path(@order.state)
51
44
  end
52
45
  end
53
46
 
54
- private
47
+ # If an authorization notification is received before the redirection the
48
+ # payment is created there.In this case we just need to assign the addition
49
+ # parameters received about the source.
50
+ #
51
+ # We do this because there is a chance that we never get redirected back
52
+ # so we need to make sure we complete the payment and order.
53
+ def confirm_order_already_completed
54
+ payment = @order.payments.find_by!(response_code: psp_reference)
55
+ payment.source.update(source_params)
56
+
57
+ redirect_to_order
58
+ end
59
+
60
+ def redirect_to_order
61
+ flash.notice = Spree.t(:order_processed_successfully)
62
+ redirect_to order_path(@order)
63
+ end
55
64
 
56
65
  def check_signature
57
66
  unless ::Adyen::Form.redirect_signature_check(params, @payment_method.shared_secret)
@@ -71,11 +80,10 @@ module Spree
71
80
 
72
81
  @order =
73
82
  Spree::Order.
74
- incomplete.
75
83
  find_by!(guest_token: cookies.signed[:guest_token])
76
84
  end
77
85
 
78
- def source_params params
86
+ def source_params
79
87
  params.permit(
80
88
  :authResult,
81
89
  :pspReference,
@@ -86,5 +94,9 @@ module Spree
86
94
  :shopperLocale,
87
95
  :merchantReturnData)
88
96
  end
97
+
98
+ def psp_reference
99
+ params[:pspReference]
100
+ end
89
101
  end
90
102
  end
@@ -1,11 +1,21 @@
1
1
  module Spree
2
2
  module Adyen
3
+ # Class responsible for taking in a notification from Adyen and applying
4
+ # some form of modification to the associated payment.
5
+ #
6
+ # I would in the future like to refactor this by breaking this into
7
+ # separate classes that are only aware of how to process specific kinds of
8
+ # notifications (auth, capture, refund, etc.).
3
9
  class NotificationProcessor
4
10
  attr_accessor :notification, :payment
5
11
 
6
12
  def initialize(notification, payment = nil)
7
13
  self.notification = notification
8
14
  self.payment = payment ? payment : notification.payment
15
+
16
+ if self.payment.nil?
17
+ self.payment = create_missing_payment
18
+ end
9
19
  end
10
20
 
11
21
  # for the given payment, process all notifications that are currently
@@ -97,6 +107,34 @@ module Spree
97
107
  payment.update!(amount: payment.captured_amount)
98
108
  payment.complete!
99
109
  end
110
+
111
+ # At this point the auth was received before the redirect, we create
112
+ # the payment here with the information we have available so that if
113
+ # the user is not redirected to back for some reason we still have a
114
+ # record of the payment.
115
+ def create_missing_payment
116
+ order = notification.order
117
+
118
+ source = Spree::Adyen::HppSource.new(
119
+ auth_result: "unknown",
120
+ order: order,
121
+ payment_method: notification.payment_method,
122
+ psp_reference: notification.psp_reference
123
+ )
124
+
125
+ payment = order.payments.create!(
126
+ amount: notification.money.dollars,
127
+ # We have no idea what payment method they used, this will be
128
+ # updated when/if they get redirected
129
+ payment_method: Spree::Gateway::AdyenHPP.last,
130
+ response_code: notification.psp_reference,
131
+ source: source,
132
+ order: order
133
+ )
134
+
135
+ order.complete
136
+ payment
137
+ end
100
138
  end
101
139
  end
102
140
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
2
  module Adyen
3
- VERSION = "0.1.3"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
@@ -5,7 +5,6 @@ describe Spree::AdyenNotificationsController do
5
5
 
6
6
  routes { Spree::Core::Engine.routes }
7
7
 
8
- let(:order) { create :order }
9
8
  let(:params) do
10
9
  { "pspReference" => reference,
11
10
  "eventDate" => "2013-10-21T14:45:45.93Z",
@@ -22,12 +21,14 @@ describe Spree::AdyenNotificationsController do
22
21
  "live" => "false" }
23
22
  end
24
23
 
24
+ let!(:order) { create :completed_order_with_totals }
25
+
25
26
  let!(:payment) do
26
- create :payment, response_code: reference,
27
- payment_method: payment_method
27
+ create :hpp_payment, response_code: reference,
28
+ payment_method: payment_method, order: order
28
29
  end
29
30
 
30
- let(:payment_method) { create :hpp_gateway }
31
+ let!(:payment_method) { create :hpp_gateway }
31
32
 
32
33
  let(:reference) { "8513823667306210" }
33
34
 
@@ -12,8 +12,8 @@ RSpec.describe Spree::AdyenRedirectController, type: :controller do
12
12
  )
13
13
  end
14
14
 
15
- let(:store) { Spree::Store.default }
16
- let(:gateway) { create :hpp_gateway }
15
+ let!(:store) { Spree::Store.default }
16
+ let!(:gateway) { create :hpp_gateway }
17
17
 
18
18
  before do
19
19
  allow(controller).to receive(:check_signature)
@@ -24,6 +24,7 @@ RSpec.describe Spree::AdyenRedirectController, type: :controller do
24
24
 
25
25
  let(:psp_reference) { "8813824003752247" }
26
26
  let(:payment_method) { "amex" }
27
+ let(:merchantReturnData) { "#{order.guest_token}|#{gateway.id}" }
27
28
  let(:params) do
28
29
  { merchantReference: order.number,
29
30
  skinCode: "xxxxxxxx",
@@ -35,28 +36,20 @@ RSpec.describe Spree::AdyenRedirectController, type: :controller do
35
36
  merchantReturnData: merchantReturnData
36
37
  }
37
38
  end
38
- let(:merchantReturnData) { [order.guest_token, gateway.id].join("|") }
39
-
40
- shared_examples "payments are pending" do
41
- it "has pending payments" do
42
- expect(order.payments).to all be_pending
43
- end
44
- end
45
39
 
46
40
  shared_examples "payment is successful" do
47
41
  it "changes the order state to completed" do
48
- expect { subject }.
49
- to change { order.reload.state }.
50
- from("payment").
51
- to("complete").
52
-
53
- and change { order.payment_state }.
54
- from(nil).
55
- to("balance_due").
56
-
57
- and change { order.shipment_state }.
58
- from(nil).
59
- to("pending")
42
+ subject
43
+ order.reload
44
+ expect(order).to have_attributes(
45
+ state: "complete",
46
+ payment_state: "balance_due",
47
+ shipment_state: "pending"
48
+ )
49
+ end
50
+
51
+ it "has pending payments" do
52
+ expect(order.payments).to all be_pending
60
53
  end
61
54
 
62
55
  it "redirects to the order complete page" do
@@ -65,10 +58,8 @@ RSpec.describe Spree::AdyenRedirectController, type: :controller do
65
58
  end
66
59
 
67
60
  it "creates a payment" do
68
- expect{ subject }.
69
- to change{ order.payments.count }.
70
- from(0).
71
- to(1)
61
+ subject
62
+ expect(order.reload.payments.count).to eq 1
72
63
  end
73
64
 
74
65
  context "and the order cannot complete" do
@@ -80,9 +71,20 @@ RSpec.describe Spree::AdyenRedirectController, type: :controller do
80
71
  end
81
72
  end
82
73
 
74
+ shared_examples "payment is not successful" do
75
+ it "does not change order state" do
76
+ expect{ subject }.to_not change{ order.state }
77
+ end
78
+
79
+ it "redirects to the order payment page" do
80
+ is_expected.to have_http_status(:redirect).
81
+ and redirect_to checkout_state_path("payment")
82
+ end
83
+ end
84
+
83
85
  context "when the payment is AUTHORISED" do
84
86
  include_examples "payment is successful"
85
- include_examples "payments are pending"
87
+
86
88
  let(:auth_result) { "AUTHORISED" }
87
89
 
88
90
  context "and the authorisation notification has already been received" do
@@ -91,56 +93,40 @@ RSpec.describe Spree::AdyenRedirectController, type: :controller do
91
93
  let(:notification) do
92
94
  create(
93
95
  :notification,
94
- notification_type,
96
+ :auth,
97
+ processed: true,
95
98
  psp_reference: psp_reference,
96
99
  merchant_reference: order.number)
97
100
  end
98
101
 
99
- shared_examples "auth received" do
100
- include_examples "payment is successful"
102
+ # there will already be a payment and source created at this point
103
+ before do
104
+ source =
105
+ create(:hpp_source, psp_reference: psp_reference, order: order)
101
106
 
102
- it "processes the notification" do
103
- expect { subject }.
104
- to change { notification.reload.processed }.
105
- from(false).
106
- to(true)
107
- end
108
- end
107
+ create(:hpp_payment, source: source, order: order)
109
108
 
110
- context "and payment method is sofort" do
111
- let(:notification_type) { :sofort_auth }
112
- include_examples "auth received"
109
+ order.complete
113
110
  end
114
111
 
115
- context "and payment method is ideal" do
116
- let(:notification_type) { :ideal_auth }
117
- include_examples "auth received"
118
- end
112
+ it { expect { subject }.to_not change { order.payments.count }.from 1 }
119
113
 
120
- context "and payment method is credit" do
121
- let(:notification_type) { :auth }
122
- include_examples "auth received"
114
+ it "updates the source" do
115
+ expect(order.payments.last.source).to have_attributes(
116
+ auth_result: "AUTHORISED",
117
+ skin_code: "XXXXXXXX"
118
+ )
123
119
  end
120
+
121
+ include_examples "payment is successful"
124
122
  end
125
123
  end
126
124
 
127
125
  context "when the payment is PENDING" do
128
126
  include_examples "payment is successful"
129
- include_examples "payments are pending"
130
127
  let(:auth_result) { "PENDING" }
131
128
  end
132
129
 
133
- shared_examples "payment is not successful" do
134
- it "does not change order state" do
135
- expect{ subject }.to_not change{ order.state }
136
- end
137
-
138
- it "redirects to the order payment page" do
139
- is_expected.to have_http_status(:redirect).
140
- and redirect_to checkout_state_path("payment")
141
- end
142
- end
143
-
144
130
  context "when the payment is CANCELLED" do
145
131
  include_examples "payment is not successful"
146
132
  let(:auth_result) { "CANCELLED" }
@@ -6,6 +6,7 @@ FactoryGirl.define do
6
6
 
7
7
  before :create do |record, _|
8
8
  # these associations/keys are awful and are making this difficult
9
+ record.response_code = record.source.psp_reference
9
10
  record.source.order = record.order
10
11
  record.source.merchant_reference = record.order.number
11
12
  end
@@ -12,10 +12,18 @@ RSpec.describe Spree::Adyen::NotificationProcessor do
12
12
  amount: 23.99,
13
13
  state: payment_state,
14
14
  payment_method: hpp_gateway,
15
- order: create(:order, currency: "EUR")
15
+ order: order
16
16
  )
17
17
  end
18
18
 
19
+ let!(:order) do
20
+ # spree factories suck, it's not easy to get something to payment state
21
+ create(:order_with_line_items).tap do |order|
22
+ order.contents.advance
23
+ expect(order.state).to eq "payment"
24
+ end
25
+ end
26
+
19
27
  let!(:hpp_gateway) do
20
28
  create(:hpp_gateway)
21
29
  end
@@ -26,8 +34,9 @@ RSpec.describe Spree::Adyen::NotificationProcessor do
26
34
  event_type, # these are registered traits, refer to the factory
27
35
  success: success,
28
36
  value: 2399,
29
- currency: "EUR",
30
- payment: payment
37
+ currency: "USD",
38
+ payment: payment,
39
+ order: order
31
40
  )
32
41
  end
33
42
 
@@ -66,7 +75,7 @@ RSpec.describe Spree::Adyen::NotificationProcessor do
66
75
  end
67
76
 
68
77
  it "completes the payment" do
69
- expect{ subject }.
78
+ expect { subject }.
70
79
  to change{ payment.reload.state }.
71
80
  from("pending").
72
81
  to("completed")
@@ -99,8 +108,6 @@ RSpec.describe Spree::Adyen::NotificationProcessor do
99
108
  context "when event is AUTHORISATION" do
100
109
  let(:event_type) { :auth }
101
110
 
102
- it "changes the available actions"
103
-
104
111
  context "and payment method was c_cash", pending: true do
105
112
  pending "completes payment"
106
113
  end
@@ -109,6 +116,31 @@ RSpec.describe Spree::Adyen::NotificationProcessor do
109
116
  pending "completes payment"
110
117
  end
111
118
 
119
+ # this is for the situation where we can the notification before the
120
+ # redirect
121
+ context "and the payment doesn't exist yet" do
122
+ let!(:payment) { nil }
123
+
124
+ it "processes the notification" do
125
+ expect { subject }.
126
+ to change { notification.processed }.
127
+ to true
128
+ end
129
+
130
+ it "completes the order" do
131
+ subject
132
+ expect(order.reload).to have_attributes(
133
+ state: "complete"
134
+ )
135
+ end
136
+
137
+ it "creates a payment" do
138
+ expect { subject }.
139
+ to change { order.payments.count }.
140
+ to 1
141
+ end
142
+ end
143
+
112
144
  context "and payment method was ideal" do
113
145
  let(:event_type) { :ideal_auth }
114
146
  include_examples "completes payment"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus-adyen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dylan Kendal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-10 00:00:00.000000000 Z
11
+ date: 2015-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: adyen