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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rubocop.yml +9 -0
  4. data/.rubocop_todo.yml +325 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +238 -0
  8. data/Rakefile +16 -0
  9. data/app/assets/javascripts/spree/backend/solidus-adyen.js +1 -0
  10. data/app/assets/javascripts/spree/checkout/payment/adyen.js +10 -0
  11. data/app/assets/javascripts/spree/frontend/solidus-adyen.js +1 -0
  12. data/app/assets/stylesheets/spree/backend/solidus-adyen/buttons.scss +15 -0
  13. data/app/assets/stylesheets/spree/backend/solidus-adyen/communication.scss +121 -0
  14. data/app/assets/stylesheets/spree/backend/solidus-adyen/variables.scss +4 -0
  15. data/app/assets/stylesheets/spree/backend/solidus-adyen.css +5 -0
  16. data/app/assets/stylesheets/spree/frontend/solidus-adyen.css +3 -0
  17. data/app/controllers/concerns/spree/adyen/admin/refunds_controller.rb +59 -0
  18. data/app/controllers/spree/adyen/hpps_controller.rb +22 -0
  19. data/app/controllers/spree/adyen_notifications_controller.rb +34 -0
  20. data/app/controllers/spree/adyen_redirect_controller.rb +90 -0
  21. data/app/models/adyen_notification.rb +159 -0
  22. data/app/models/concerns/spree/adyen/order.rb +11 -0
  23. data/app/models/concerns/spree/adyen/payment.rb +95 -0
  24. data/app/models/spree/adyen/hpp_source.rb +101 -0
  25. data/app/models/spree/adyen/notification_processor.rb +102 -0
  26. data/app/models/spree/adyen/presenters/communication.rb +31 -0
  27. data/app/models/spree/adyen/presenters/communications/adyen_notification.rb +26 -0
  28. data/app/models/spree/adyen/presenters/communications/base.rb +50 -0
  29. data/app/models/spree/adyen/presenters/communications/hpp_source.rb +31 -0
  30. data/app/models/spree/adyen/presenters/communications/log_entry.rb +28 -0
  31. data/app/models/spree/adyen/presenters/communications.rb +9 -0
  32. data/app/models/spree/gateway/adyen_hpp.rb +95 -0
  33. data/app/overrides/spree/admin/shared/_order_summary.rb +6 -0
  34. data/app/views/spree/admin/payments/source_forms/_adyen.html.erb +1 -0
  35. data/app/views/spree/admin/payments/source_views/_adyen.html.erb +14 -0
  36. data/app/views/spree/adyen/_manual_refund.html.erb +9 -0
  37. data/app/views/spree/adyen/communication/_communication.html.erb +42 -0
  38. data/app/views/spree/adyen/hpps/directory.html.erb +10 -0
  39. data/app/views/spree/checkout/payment/_adyen.html.erb +33 -0
  40. data/bin/checkout.rb +111 -0
  41. data/bin/regen.sh +1 -0
  42. data/config/initializers/solidus_adyen.rb +1 -0
  43. data/config/locales/en.yml +23 -0
  44. data/config/routes.rb +13 -0
  45. data/db/migrate/20131017040945_create_adyen_notifications.rb +29 -0
  46. data/db/migrate/20150911201942_add_index_adyen_notifications_psp_reference.rb +5 -0
  47. data/db/migrate/20150914162539_create_spree_adyen_hpp_sources.rb +21 -0
  48. data/db/migrate/20151007090519_add_days_to_ship_to_config.rb +5 -0
  49. data/db/migrate/20151020230830_remove_indices_on_adyen_notifications.rb +6 -0
  50. data/db/migrate/20151106093023_allow_merchant_reference_to_be_null_for_adyen_notification.rb +5 -0
  51. data/lib/solidus-adyen.rb +1 -0
  52. data/lib/spree/adyen/engine.rb +32 -0
  53. data/lib/spree/adyen/form.rb +135 -0
  54. data/lib/spree/adyen/hpp_check.rb +11 -0
  55. data/lib/spree/adyen/url.rb +30 -0
  56. data/lib/spree/adyen/version.rb +5 -0
  57. data/lib/spree/adyen.rb +6 -0
  58. data/solidus-adyen.gemspec +51 -0
  59. data/spec/controllers/concerns/spree/adyen/admin/refunds_controller_spec.rb +76 -0
  60. data/spec/controllers/spree/adyen/hpps_controller_spec.rb +43 -0
  61. data/spec/controllers/spree/adyen_notifications_controller_spec.rb +118 -0
  62. data/spec/controllers/spree/adyen_redirect_controller_spec.rb +154 -0
  63. data/spec/factories/active_merchant_billing_response.rb +23 -0
  64. data/spec/factories/adyen_notification.rb +91 -0
  65. data/spec/factories/spree_adyen_hpp_source.rb +15 -0
  66. data/spec/factories/spree_gateway_adyen_hpp.rb +23 -0
  67. data/spec/factories/spree_payment.rb +13 -0
  68. data/spec/lib/spree/adyen/form_spec.rb +214 -0
  69. data/spec/models/adyen_notification_spec.rb +86 -0
  70. data/spec/models/concerns/spree/adyen/order_spec.rb +22 -0
  71. data/spec/models/concerns/spree/adyen/payment_spec.rb +93 -0
  72. data/spec/models/spree/adyen/hpp_source_spec.rb +101 -0
  73. data/spec/models/spree/adyen/notification_processor_spec.rb +205 -0
  74. data/spec/models/spree/adyen/presenters/communication_spec.rb +62 -0
  75. data/spec/models/spree/gateway/adyen_hpp_spec.rb +76 -0
  76. data/spec/spec_helper.rb +65 -0
  77. data/spec/support/shared_contexts/mock_adyen_api.rb +47 -0
  78. 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