solidus-adyen 0.1.2
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.
- checksums.yaml +7 -0
- data/.gitignore +26 -0
- data/.rubocop.yml +9 -0
- data/.rubocop_todo.yml +325 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +238 -0
- data/Rakefile +16 -0
- data/app/assets/javascripts/spree/backend/solidus-adyen.js +1 -0
- data/app/assets/javascripts/spree/checkout/payment/adyen.js +10 -0
- data/app/assets/javascripts/spree/frontend/solidus-adyen.js +1 -0
- data/app/assets/stylesheets/spree/backend/solidus-adyen/buttons.scss +15 -0
- data/app/assets/stylesheets/spree/backend/solidus-adyen/communication.scss +121 -0
- data/app/assets/stylesheets/spree/backend/solidus-adyen/variables.scss +4 -0
- data/app/assets/stylesheets/spree/backend/solidus-adyen.css +5 -0
- data/app/assets/stylesheets/spree/frontend/solidus-adyen.css +3 -0
- data/app/controllers/concerns/spree/adyen/admin/refunds_controller.rb +59 -0
- data/app/controllers/spree/adyen/hpps_controller.rb +22 -0
- data/app/controllers/spree/adyen_notifications_controller.rb +34 -0
- data/app/controllers/spree/adyen_redirect_controller.rb +90 -0
- data/app/models/adyen_notification.rb +159 -0
- data/app/models/concerns/spree/adyen/order.rb +11 -0
- data/app/models/concerns/spree/adyen/payment.rb +95 -0
- data/app/models/spree/adyen/hpp_source.rb +101 -0
- data/app/models/spree/adyen/notification_processor.rb +102 -0
- data/app/models/spree/adyen/presenters/communication.rb +31 -0
- data/app/models/spree/adyen/presenters/communications/adyen_notification.rb +26 -0
- data/app/models/spree/adyen/presenters/communications/base.rb +50 -0
- data/app/models/spree/adyen/presenters/communications/hpp_source.rb +31 -0
- data/app/models/spree/adyen/presenters/communications/log_entry.rb +28 -0
- data/app/models/spree/adyen/presenters/communications.rb +9 -0
- data/app/models/spree/gateway/adyen_hpp.rb +95 -0
- data/app/overrides/spree/admin/shared/_order_summary.rb +6 -0
- data/app/views/spree/admin/payments/source_forms/_adyen.html.erb +1 -0
- data/app/views/spree/admin/payments/source_views/_adyen.html.erb +14 -0
- data/app/views/spree/adyen/_manual_refund.html.erb +9 -0
- data/app/views/spree/adyen/communication/_communication.html.erb +42 -0
- data/app/views/spree/adyen/hpps/directory.html.erb +10 -0
- data/app/views/spree/checkout/payment/_adyen.html.erb +33 -0
- data/bin/checkout.rb +111 -0
- data/bin/regen.sh +1 -0
- data/config/initializers/solidus_adyen.rb +1 -0
- data/config/locales/en.yml +23 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20131017040945_create_adyen_notifications.rb +29 -0
- data/db/migrate/20150911201942_add_index_adyen_notifications_psp_reference.rb +5 -0
- data/db/migrate/20150914162539_create_spree_adyen_hpp_sources.rb +21 -0
- data/db/migrate/20151007090519_add_days_to_ship_to_config.rb +5 -0
- data/db/migrate/20151020230830_remove_indices_on_adyen_notifications.rb +6 -0
- data/db/migrate/20151106093023_allow_merchant_reference_to_be_null_for_adyen_notification.rb +5 -0
- data/lib/solidus-adyen.rb +1 -0
- data/lib/spree/adyen/engine.rb +32 -0
- data/lib/spree/adyen/form.rb +135 -0
- data/lib/spree/adyen/hpp_check.rb +11 -0
- data/lib/spree/adyen/url.rb +30 -0
- data/lib/spree/adyen/version.rb +5 -0
- data/lib/spree/adyen.rb +6 -0
- data/solidus-adyen.gemspec +51 -0
- data/spec/controllers/concerns/spree/adyen/admin/refunds_controller_spec.rb +76 -0
- data/spec/controllers/spree/adyen/hpps_controller_spec.rb +43 -0
- data/spec/controllers/spree/adyen_notifications_controller_spec.rb +118 -0
- data/spec/controllers/spree/adyen_redirect_controller_spec.rb +154 -0
- data/spec/factories/active_merchant_billing_response.rb +23 -0
- data/spec/factories/adyen_notification.rb +91 -0
- data/spec/factories/spree_adyen_hpp_source.rb +15 -0
- data/spec/factories/spree_gateway_adyen_hpp.rb +23 -0
- data/spec/factories/spree_payment.rb +13 -0
- data/spec/lib/spree/adyen/form_spec.rb +214 -0
- data/spec/models/adyen_notification_spec.rb +86 -0
- data/spec/models/concerns/spree/adyen/order_spec.rb +22 -0
- data/spec/models/concerns/spree/adyen/payment_spec.rb +93 -0
- data/spec/models/spree/adyen/hpp_source_spec.rb +101 -0
- data/spec/models/spree/adyen/notification_processor_spec.rb +205 -0
- data/spec/models/spree/adyen/presenters/communication_spec.rb +62 -0
- data/spec/models/spree/gateway/adyen_hpp_spec.rb +76 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/support/shared_contexts/mock_adyen_api.rb +47 -0
- metadata +463 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Spree::AdyenNotificationsController do
|
|
4
|
+
include_context "mock adyen api", success: true
|
|
5
|
+
|
|
6
|
+
routes { Spree::Core::Engine.routes }
|
|
7
|
+
|
|
8
|
+
let(:order) { create :order }
|
|
9
|
+
let(:params) do
|
|
10
|
+
{ "pspReference" => reference,
|
|
11
|
+
"eventDate" => "2013-10-21T14:45:45.93Z",
|
|
12
|
+
"merchantAccountCode" => "Test",
|
|
13
|
+
"reason" => "41061:1111:6/2016",
|
|
14
|
+
"originalReference" => "",
|
|
15
|
+
"value" => "6999",
|
|
16
|
+
"eventCode" => "AUTHORISATION",
|
|
17
|
+
"merchantReference" => order.number,
|
|
18
|
+
"operations" => "CANCEL,CAPTURE,REFUND",
|
|
19
|
+
"success" => "true",
|
|
20
|
+
"paymentMethod" => "visa",
|
|
21
|
+
"currency" => "USD",
|
|
22
|
+
"live" => "false" }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
let!(:payment) do
|
|
26
|
+
create :payment, response_code: reference,
|
|
27
|
+
payment_method: payment_method
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:payment_method) { create :hpp_gateway }
|
|
31
|
+
|
|
32
|
+
let(:reference) { "8513823667306210" }
|
|
33
|
+
|
|
34
|
+
before do
|
|
35
|
+
ENV["ADYEN_NOTIFY_USER"] = "username"
|
|
36
|
+
ENV["ADYEN_NOTIFY_PASSWD"] = "password"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "POST notify" do
|
|
40
|
+
subject { post :notify, params }
|
|
41
|
+
|
|
42
|
+
shared_examples "success" do
|
|
43
|
+
it "acknowledges the request" do
|
|
44
|
+
subject
|
|
45
|
+
expect(response).to have_http_status(:ok)
|
|
46
|
+
expect(response.body).to eq("[accepted]")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "request authenticated" do
|
|
51
|
+
before { bypass_auth }
|
|
52
|
+
|
|
53
|
+
shared_examples "logs the notification" do
|
|
54
|
+
include_examples "success"
|
|
55
|
+
it "creates a notification" do
|
|
56
|
+
expect{ subject }.to change { AdyenNotification.count }.from(0).to(1)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
include_examples "logs the notification"
|
|
61
|
+
|
|
62
|
+
context "when the system can't find a matching payment" do
|
|
63
|
+
let(:payment) { nil }
|
|
64
|
+
include_examples "logs the notification"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Regression test
|
|
68
|
+
# In the event that a notification cannot be processed we need to still
|
|
69
|
+
# save the notification and acknoweldge it - otherwise Adyen will
|
|
70
|
+
# continue to notify us about the event and it will continue to error it.
|
|
71
|
+
#
|
|
72
|
+
# We cannot use an `ensure` in the controller action because the render
|
|
73
|
+
# actually completes after the action. Doing so still results in a 500.
|
|
74
|
+
#
|
|
75
|
+
# For this reason we need to save the notification on the first attempt,
|
|
76
|
+
# let the controller error, and then on the second attempt from Adyen
|
|
77
|
+
# return 200 [accepted], as the notification has already been saved.
|
|
78
|
+
context "an error occurs during processing" do
|
|
79
|
+
before { payment.void! }
|
|
80
|
+
before { params["success"] = false }
|
|
81
|
+
|
|
82
|
+
it "errors and creates a notification" do
|
|
83
|
+
expect {
|
|
84
|
+
expect { post(:notify, params) }.
|
|
85
|
+
to raise_error(StateMachines::InvalidTransition)
|
|
86
|
+
|
|
87
|
+
expect(post(:notify, params)).
|
|
88
|
+
to have_http_status(:ok).
|
|
89
|
+
and have_attributes(body: "[accepted]")
|
|
90
|
+
}.
|
|
91
|
+
to change { AdyenNotification.count }.by(1)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context "request not authenticated" do
|
|
97
|
+
it { is_expected.to have_http_status 401 }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context "notification has already been received" do
|
|
101
|
+
before { bypass_auth }
|
|
102
|
+
include_examples "success"
|
|
103
|
+
|
|
104
|
+
it "doesn't create a notification" do
|
|
105
|
+
# explict call of subject to avoid memoization
|
|
106
|
+
post :notify, params
|
|
107
|
+
|
|
108
|
+
expect{ post :notify, params }.
|
|
109
|
+
to_not change{ AdyenNotification.count }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def bypass_auth
|
|
115
|
+
@request.env["HTTP_AUTHORIZATION"] = "Basic " +
|
|
116
|
+
Base64::encode64("username:password")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
# https://docs.adyen.com/display/TD/HPP+payment+response
|
|
4
|
+
RSpec.describe Spree::AdyenRedirectController, type: :controller do
|
|
5
|
+
include_context "mock adyen api", success: true
|
|
6
|
+
|
|
7
|
+
let(:order) do
|
|
8
|
+
create(
|
|
9
|
+
:order_with_line_items,
|
|
10
|
+
state: "payment",
|
|
11
|
+
store: store
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:store) { Spree::Store.default }
|
|
16
|
+
let(:gateway) { create :hpp_gateway }
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
allow(controller).to receive(:check_signature)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "GET confirm" do
|
|
23
|
+
subject(:action) { spree_get :confirm, params }
|
|
24
|
+
|
|
25
|
+
let(:psp_reference) { "8813824003752247" }
|
|
26
|
+
let(:payment_method) { "amex" }
|
|
27
|
+
let(:params) do
|
|
28
|
+
{ merchantReference: order.number,
|
|
29
|
+
skinCode: "xxxxxxxx",
|
|
30
|
+
shopperLocale: "en_GB",
|
|
31
|
+
paymentMethod: payment_method,
|
|
32
|
+
authResult: auth_result,
|
|
33
|
+
pspReference: psp_reference,
|
|
34
|
+
merchantSig: "erewrwerewrewrwer",
|
|
35
|
+
merchantReturnData: merchantReturnData
|
|
36
|
+
}
|
|
37
|
+
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
|
+
|
|
46
|
+
shared_examples "payment is successful" do
|
|
47
|
+
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")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "redirects to the order complete page" do
|
|
63
|
+
is_expected.to have_http_status(:redirect).
|
|
64
|
+
and redirect_to order_path(order)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "creates a payment" do
|
|
68
|
+
expect{ subject }.
|
|
69
|
+
to change{ order.payments.count }.
|
|
70
|
+
from(0).
|
|
71
|
+
to(1)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context "and the order cannot complete" do
|
|
75
|
+
before do
|
|
76
|
+
expect(order).to receive(complete).and_return(false)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "voids the payment"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context "when the payment is AUTHORISED" do
|
|
84
|
+
include_examples "payment is successful"
|
|
85
|
+
include_examples "payments are pending"
|
|
86
|
+
let(:auth_result) { "AUTHORISED" }
|
|
87
|
+
|
|
88
|
+
context "and the authorisation notification has already been received" do
|
|
89
|
+
let(:payment_method) { notification.payment_method }
|
|
90
|
+
|
|
91
|
+
let(:notification) do
|
|
92
|
+
create(
|
|
93
|
+
:notification,
|
|
94
|
+
notification_type,
|
|
95
|
+
psp_reference: psp_reference,
|
|
96
|
+
merchant_reference: order.number)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
shared_examples "auth received" do
|
|
100
|
+
include_examples "payment is successful"
|
|
101
|
+
|
|
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
|
|
109
|
+
|
|
110
|
+
context "and payment method is sofort" do
|
|
111
|
+
let(:notification_type) { :sofort_auth }
|
|
112
|
+
include_examples "auth received"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "and payment method is ideal" do
|
|
116
|
+
let(:notification_type) { :ideal_auth }
|
|
117
|
+
include_examples "auth received"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context "and payment method is credit" do
|
|
121
|
+
let(:notification_type) { :auth }
|
|
122
|
+
include_examples "auth received"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context "when the payment is PENDING" do
|
|
128
|
+
include_examples "payment is successful"
|
|
129
|
+
include_examples "payments are pending"
|
|
130
|
+
let(:auth_result) { "PENDING" }
|
|
131
|
+
end
|
|
132
|
+
|
|
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
|
+
context "when the payment is CANCELLED" do
|
|
145
|
+
include_examples "payment is not successful"
|
|
146
|
+
let(:auth_result) { "CANCELLED" }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context "when the payment is REFUSED" do
|
|
150
|
+
include_examples "payment is not successful"
|
|
151
|
+
let(:auth_result) { "REFUSED" }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
FactoryGirl.define do
|
|
2
|
+
factory(
|
|
3
|
+
:active_merchant_billing_response,
|
|
4
|
+
aliases: ["am_response"],
|
|
5
|
+
class: "ActiveMerchant::Billing::Response"
|
|
6
|
+
) do
|
|
7
|
+
skip_create
|
|
8
|
+
params Hash.new
|
|
9
|
+
options Hash.new
|
|
10
|
+
message ""
|
|
11
|
+
|
|
12
|
+
initialize_with { new(success, message, params, options) }
|
|
13
|
+
|
|
14
|
+
trait :success do
|
|
15
|
+
success true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
trait :failure do
|
|
19
|
+
success false
|
|
20
|
+
message "This is an expected failure"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# https://docs.adyen.com/display/TD/Notification+fields
|
|
2
|
+
FactoryGirl.define do
|
|
3
|
+
factory :adyen_notification, aliases: [:notification] do
|
|
4
|
+
live false
|
|
5
|
+
psp_reference { SecureRandom.hex }
|
|
6
|
+
original_reference nil
|
|
7
|
+
merchant_reference "R000000000"
|
|
8
|
+
merchant_account_code "MyMerchantAccount"
|
|
9
|
+
event_date 30.seconds.ago
|
|
10
|
+
success true
|
|
11
|
+
payment_method "amex"
|
|
12
|
+
operations ""
|
|
13
|
+
currency "USD"
|
|
14
|
+
value 2599
|
|
15
|
+
reason ""
|
|
16
|
+
processed false
|
|
17
|
+
created_at DateTime.now
|
|
18
|
+
updated_at DateTime.now
|
|
19
|
+
|
|
20
|
+
transient do
|
|
21
|
+
payment nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
before(:create) do |record, evaluator|
|
|
25
|
+
if evaluator.payment
|
|
26
|
+
record.merchant_reference = evaluator.payment.order.number
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
trait :normal_event do
|
|
31
|
+
before(:create) do |record, evaluator|
|
|
32
|
+
if evaluator.payment
|
|
33
|
+
record.psp_reference = evaluator.payment.response_code
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
trait :modification_event do
|
|
39
|
+
before(:create) do |record, evaluator|
|
|
40
|
+
if evaluator.payment
|
|
41
|
+
record.original_reference = evaluator.payment.response_code
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
trait :auth do
|
|
47
|
+
normal_event
|
|
48
|
+
event_code "AUTHORISATION"
|
|
49
|
+
operations "CANCEL,CAPTURE,REFUND"
|
|
50
|
+
reason "31893:0002:8/2018"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
trait :sofort_auth do
|
|
54
|
+
auth
|
|
55
|
+
event_code "AUTHORISATION"
|
|
56
|
+
payment_method "directEbanking"
|
|
57
|
+
operations nil
|
|
58
|
+
reason nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
trait :bank_auth do
|
|
62
|
+
auth
|
|
63
|
+
operations "REFUND"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
trait :ideal_auth do
|
|
67
|
+
bank_auth
|
|
68
|
+
payment_method "ideal"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
trait :capture do
|
|
72
|
+
modification_event
|
|
73
|
+
event_code "CAPTURE"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
trait :cancel_or_refund do
|
|
77
|
+
modification_event
|
|
78
|
+
event_code "CANCEL_OR_REFUND"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
trait :refund do
|
|
82
|
+
modification_event
|
|
83
|
+
event_code "REFUND"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
trait :pending do
|
|
87
|
+
normal_event
|
|
88
|
+
event_code "PENDING"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
FactoryGirl.define do
|
|
2
|
+
factory :spree_adyen_hpp_source, aliases: [:hpp_source], class: "Spree::Adyen::HppSource" do
|
|
3
|
+
skin_code "XXXXXXXX"
|
|
4
|
+
shopper_locale "en_GB"
|
|
5
|
+
auth_result "AUTHORISED"
|
|
6
|
+
psp_reference { SecureRandom.hex }
|
|
7
|
+
merchant_sig "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
|
8
|
+
payment_method "amex"
|
|
9
|
+
order
|
|
10
|
+
|
|
11
|
+
trait :sofort do
|
|
12
|
+
payment_method "directEbanking"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
FactoryGirl.define do
|
|
2
|
+
factory :spree_gateway_adyen_hpp, aliases: [:hpp_gateway],
|
|
3
|
+
class: "Spree::Gateway::AdyenHPP" do
|
|
4
|
+
name "Adyen"
|
|
5
|
+
environment "test"
|
|
6
|
+
preferences(
|
|
7
|
+
skin_code: "XXXXX",
|
|
8
|
+
shared_secret: "1234",
|
|
9
|
+
merchant_account: "XXXX",
|
|
10
|
+
days_to_ship: 3
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
trait :env_configured do
|
|
14
|
+
preferred_test_mode true
|
|
15
|
+
preferred_days_to_ship 1
|
|
16
|
+
preferred_api_password { ENV.fetch("ADYEN_API_PASSWORD") }
|
|
17
|
+
preferred_api_username { ENV.fetch("ADYEN_API_USERNAME") }
|
|
18
|
+
preferred_merchant_account { ENV.fetch("ADYEN_MERCHANT_ACCOUNT") }
|
|
19
|
+
preferred_shared_secret { ENV.fetch("ADYEN_SKIN_CODE") }
|
|
20
|
+
preferred_skin_code { ENV.fetch("ADYEN_SKIN_CODE") }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
FactoryGirl.define do
|
|
2
|
+
factory :hpp_payment, parent: :payment do
|
|
3
|
+
association :payment_method, factory: :hpp_gateway
|
|
4
|
+
association :source, factory: :hpp_source
|
|
5
|
+
order
|
|
6
|
+
|
|
7
|
+
before :create do |record, _|
|
|
8
|
+
# these associations/keys are awful and are making this difficult
|
|
9
|
+
record.source.order = record.order
|
|
10
|
+
record.source.merchant_reference = record.order.number
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Spree::Adyen::Form do
|
|
4
|
+
let(:order) { create :order, total: 39.98 }
|
|
5
|
+
let(:payment_method) { create :hpp_gateway, preferences: preferences }
|
|
6
|
+
let(:preferences){
|
|
7
|
+
{server: "test",
|
|
8
|
+
test_mode: true,
|
|
9
|
+
api_username: "username",
|
|
10
|
+
api_password: "password",
|
|
11
|
+
merchant_account: "account",
|
|
12
|
+
skin_code: "XXXXXX",
|
|
13
|
+
shared_secret: "1234567890",
|
|
14
|
+
days_to_ship: 3}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe "directory_url" do
|
|
18
|
+
let(:expected) do
|
|
19
|
+
redirect_params = {
|
|
20
|
+
currency_code: order.currency,
|
|
21
|
+
ship_before_date: 3.days.from_now,
|
|
22
|
+
session_validity: 10.minutes.from_now,
|
|
23
|
+
recurring: false,
|
|
24
|
+
merchant_reference: order.number.to_s,
|
|
25
|
+
merchant_account: payment_method.merchant_account,
|
|
26
|
+
skin_code: payment_method.skin_code,
|
|
27
|
+
shared_secret: payment_method.shared_secret,
|
|
28
|
+
country_code: order.billing_address.country.iso,
|
|
29
|
+
merchant_return_data: merchant_return_data,
|
|
30
|
+
payment_amount: 3998
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
::Adyen::Form.redirect_url(redirect_params)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
let(:merchant_return_data) do
|
|
37
|
+
[order.guest_token, payment_method.id].join("|")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subject { described_class.directory_url order, payment_method }
|
|
41
|
+
|
|
42
|
+
it "has the same query options as Adyen gem's" do
|
|
43
|
+
expect(hash_query subject).to eq hash_query expected
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "has the proper protocol" do
|
|
47
|
+
expect(URI(subject).scheme).to eq "https"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "the right host" do
|
|
51
|
+
expect(URI(subject).host).to eq "test.adyen.com"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "when in production" do
|
|
55
|
+
before do
|
|
56
|
+
payment_method.preferences[:server] = "live"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "has the proper protocol and host" do
|
|
60
|
+
expect(URI(subject).host).to eq "live.adyen.com"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "payment_methods" do
|
|
66
|
+
let(:adyen_response) { '{ "test": "response" }' }
|
|
67
|
+
let(:fake_directory_url) { "www.directory-url.com" }
|
|
68
|
+
|
|
69
|
+
before do
|
|
70
|
+
allow(described_class).to receive(:directory_url).
|
|
71
|
+
and_return(fake_directory_url)
|
|
72
|
+
allow(::Net::HTTP).to receive(:get).
|
|
73
|
+
with(fake_directory_url).
|
|
74
|
+
and_return(adyen_response)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
subject { described_class.send(:payment_methods, order, payment_method) }
|
|
78
|
+
|
|
79
|
+
it "calls form_payment_methods_and_urls with adyen response" do
|
|
80
|
+
expect(described_class).to receive(:form_payment_methods_and_urls).
|
|
81
|
+
with({ "test" => "response" }, order, payment_method)
|
|
82
|
+
subject
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe "form_payment_methods_and_urls" do
|
|
87
|
+
let(:payment_url) { "www.test-url.com" }
|
|
88
|
+
let(:issuer_payment_url) { "www.issuer-test-url.com" }
|
|
89
|
+
|
|
90
|
+
before do
|
|
91
|
+
allow(described_class).to receive(:details_url).
|
|
92
|
+
and_return(payment_url)
|
|
93
|
+
allow(described_class).to receive(:details_url_with_issuer).
|
|
94
|
+
and_return(issuer_payment_url)
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
subject { described_class.send(
|
|
99
|
+
:form_payment_methods_and_urls,
|
|
100
|
+
adyen_response,
|
|
101
|
+
order,
|
|
102
|
+
payment_method
|
|
103
|
+
) }
|
|
104
|
+
|
|
105
|
+
context "payment method without issuers" do
|
|
106
|
+
let(:adyen_response) {
|
|
107
|
+
{
|
|
108
|
+
"paymentMethods" => [
|
|
109
|
+
{
|
|
110
|
+
"brandCode" => "paypal",
|
|
111
|
+
"name" => "PayPal"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
it "returns processed response with urls" do
|
|
118
|
+
expect(subject).to eq [
|
|
119
|
+
{
|
|
120
|
+
name: "PayPal",
|
|
121
|
+
brand_code: "paypal",
|
|
122
|
+
payment_url: payment_url,
|
|
123
|
+
issuers: []
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
context "payment method with issuers" do
|
|
130
|
+
let(:adyen_response) {
|
|
131
|
+
{
|
|
132
|
+
"paymentMethods" => [
|
|
133
|
+
{
|
|
134
|
+
"brandCode" => "ideal",
|
|
135
|
+
"name" => "iDEAL",
|
|
136
|
+
"issuers" => [
|
|
137
|
+
{
|
|
138
|
+
"name" => "issuer01",
|
|
139
|
+
"issuerId" => "1157"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name" => "issuer02",
|
|
143
|
+
"issuerId" => "1184"
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
it "returns processed response with urls" do
|
|
152
|
+
expect(subject).to eq [
|
|
153
|
+
{
|
|
154
|
+
name: "iDEAL",
|
|
155
|
+
brand_code: "ideal",
|
|
156
|
+
payment_url: payment_url,
|
|
157
|
+
issuers: [
|
|
158
|
+
{
|
|
159
|
+
name: "issuer01",
|
|
160
|
+
payment_url: issuer_payment_url
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "issuer02",
|
|
164
|
+
payment_url: issuer_payment_url
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe "details_url" do
|
|
174
|
+
let(:brand_code) { "paypal" }
|
|
175
|
+
subject {
|
|
176
|
+
described_class.details_url(order, payment_method, brand_code)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
it "calls endpoint url with the expected params" do
|
|
180
|
+
expect(described_class).to receive(:endpoint_url).
|
|
181
|
+
with("details", order, payment_method, { brandCode: "paypal" })
|
|
182
|
+
subject
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
describe "details_url_with_issuer" do
|
|
187
|
+
let(:issuer_id) { "1654" }
|
|
188
|
+
let(:brand_code) { "paypal" }
|
|
189
|
+
|
|
190
|
+
subject {
|
|
191
|
+
described_class.details_url_with_issuer(
|
|
192
|
+
order,
|
|
193
|
+
payment_method,
|
|
194
|
+
brand_code,
|
|
195
|
+
issuer_id
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
it "calls endpoint url with the expected params" do
|
|
200
|
+
expect(described_class).to receive(:endpoint_url).
|
|
201
|
+
with(
|
|
202
|
+
"details",
|
|
203
|
+
order,
|
|
204
|
+
payment_method,
|
|
205
|
+
{ brandCode: "paypal", issuerId: "1654" }
|
|
206
|
+
)
|
|
207
|
+
subject
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def hash_query url
|
|
212
|
+
CGI::parse URI(url).query
|
|
213
|
+
end
|
|
214
|
+
end
|