solidus_paypal_commerce_platform 0.7.1 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -9
  3. data/.gem_release.yml +1 -1
  4. data/.gitignore +1 -0
  5. data/.rspec +0 -1
  6. data/.rubocop.yml +3 -2
  7. data/CHANGELOG.md +270 -1
  8. data/Gemfile +0 -41
  9. data/README.md +10 -14
  10. data/Rakefile +6 -2
  11. data/app/models/solidus_paypal_commerce_platform/payment_method.rb +1 -18
  12. data/bin/dummy-app +37 -0
  13. data/bin/rails-dummy-app +17 -0
  14. data/bin/rspec +11 -0
  15. data/bin/sandbox +3 -8
  16. data/config/locales/en.yml +0 -1
  17. data/lib/generators/solidus_paypal_commerce_platform/install/install_generator.rb +35 -21
  18. data/lib/generators/solidus_paypal_commerce_platform/install/templates/app/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/button_actions.js +272 -0
  19. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform.js +7 -0
  20. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/paypal_orders_controller.rb +1 -1
  21. data/lib/{views/frontend/spree/checkout → generators/solidus_paypal_commerce_platform/install/templates/app/views/checkouts}/payment/_paypal_commerce_platform.html.erb +5 -4
  22. data/lib/{views/frontend/spree → generators/solidus_paypal_commerce_platform/install/templates/app/views}/orders/payment/_paypal_commerce_platform.html.erb +4 -3
  23. data/lib/{views/frontend/spree → generators/solidus_paypal_commerce_platform/install/templates/app/views}/products/payment/_paypal_commerce_platform.html.erb +1 -1
  24. data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/shared/_javascript_sdk_tag.html.erb +4 -2
  25. data/lib/solidus_paypal_commerce_platform/version.rb +1 -1
  26. data/solidus_paypal_commerce_platform.gemspec +2 -3
  27. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/CHECKOUT.ORDER.COMPLETED.v2.json +121 -0
  28. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/CHECKOUT.ORDER.PROCESSED.v2.json +121 -0
  29. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.COMPLETED.v1.json +50 -0
  30. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.COMPLETED.v2.json +72 -0
  31. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.DENIED.v1.json +50 -0
  32. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.DENIED.v2.json +68 -0
  33. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.REFUNDED.v1.json +51 -0
  34. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.REFUNDED.v2.json +63 -0
  35. data/spec/jobs/solidus_paypal_commerce_platform/webhook_job_spec.rb +44 -0
  36. data/spec/lib/solidus_paypal_commerce_platform/client_spec.rb +21 -0
  37. data/spec/lib/solidus_paypal_commerce_platform/configuration_spec.rb +91 -0
  38. data/spec/models/solidus_paypal_commerce_platform/payment_method_spec.rb +200 -0
  39. data/spec/models/solidus_paypal_commerce_platform/payment_source_spec.rb +119 -0
  40. data/spec/models/solidus_paypal_commerce_platform/paypal_address_spec.rb +67 -0
  41. data/spec/models/solidus_paypal_commerce_platform/paypal_order_spec.rb +80 -0
  42. data/spec/models/solidus_paypal_commerce_platform/state_guesser_spec.rb +38 -0
  43. data/spec/models/solidus_paypal_commerce_platform/wizard_spec.rb +9 -0
  44. data/spec/requests/solidus_paypal_commerce_platform/orders_controller_spec.rb +36 -0
  45. data/spec/requests/solidus_paypal_commerce_platform/shipping_rates_controller_spec.rb +44 -0
  46. data/spec/requests/solidus_paypal_commerce_platform/wizard_controller_spec.rb +59 -0
  47. data/spec/solidus_paypal_commerce_platform_spec_helper.rb +5 -0
  48. data/spec/support/solidus_paypal_commerce_platform/factories.rb +5 -0
  49. data/spec/support/solidus_paypal_commerce_platform/paypal_sdk_script_tag_helper.rb +13 -0
  50. data/spec/system/backend/new_payment_method_spec.rb +40 -0
  51. data/spec/system/frontend/cart_spec.rb +53 -0
  52. data/spec/system/frontend/checkout_spec.rb +104 -0
  53. data/spec/system/frontend/product_spec.rb +98 -0
  54. metadata +54 -44
  55. data/app/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/button_actions.js +0 -206
  56. /data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/buttons.js +0 -0
  57. /data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/namespace.js +0 -0
  58. /data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/stylesheets/spree/frontend/solidus_paypal_commerce_platform.css +0 -0
  59. /data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/orders_controller.rb +0 -0
  60. /data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/payments_controller.rb +0 -0
  61. /data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/shipping_rates_controller.rb +0 -0
  62. /data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/wizard_controller.rb +0 -0
  63. /data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/cart/_cart_buttons.html.erb +0 -0
  64. /data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/payments/_payment.html.erb +0 -0
  65. /data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/product/_product_buttons.html.erb +0 -0
  66. /data/lib/generators/solidus_paypal_commerce_platform/install/templates/{initializer.rb → config/initializers/solidus_paypal_commerce_platform.rb} +0 -0
@@ -0,0 +1,200 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::PaymentMethod, type: :model do
4
+ let(:paypal_payment_method) { create(:paypal_payment_method) }
5
+ let(:payment) { create(:payment) }
6
+ let(:completed_payment) { create(:payment, :completed) }
7
+ let(:response) { Struct(status_code: status_code, result: result, headers: headers) }
8
+ let(:status_code) { 201 }
9
+ let(:result) { nil }
10
+ let(:headers) { {} }
11
+
12
+ def Struct(data) # rubocop:disable Naming/MethodName
13
+ Struct.new(*data.keys, keyword_init: true).new(data)
14
+ end
15
+
16
+ before { allow_any_instance_of(PayPal::PayPalHttpClient).to receive(:execute) { response } }
17
+
18
+ describe 'preferences' do
19
+ context 'with paypal_button_color' do
20
+ before do
21
+ paypal_payment_method.preferences.update(paypal_button_color: 'gold')
22
+ paypal_payment_method.save
23
+ end
24
+
25
+ it 'cannot be gold when Venmo standalone is enabled' do
26
+ expect(paypal_payment_method).to be_valid
27
+ paypal_payment_method.preferences.update(venmo_standalone: 'enabled')
28
+ expect(paypal_payment_method).to be_invalid
29
+ paypal_payment_method.preferences.update(venmo_standalone: 'only render standalone')
30
+ expect(paypal_payment_method).to be_invalid
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "#purchase" do
36
+ let(:result) { Struct(purchase_units: [Struct(payments: payments)]) }
37
+ let(:payments) { Struct(captures: [Struct(id: SecureRandom.hex(4))]) }
38
+
39
+ it "sends a purchase request to paypal" do
40
+ paypal_order_id = SecureRandom.hex(8)
41
+ source = paypal_payment_method.payment_source_class.create(paypal_order_id: paypal_order_id)
42
+ expect_request(:OrdersCaptureRequest).to receive(:new).with(paypal_order_id).and_call_original
43
+ paypal_payment_method.purchase(1000, source, {})
44
+ end
45
+ end
46
+
47
+ describe "#authorize" do
48
+ let(:result) { Struct(purchase_units: [Struct(payments: payments)]) }
49
+ let(:payments) { Struct(authorizations: [Struct(id: SecureRandom.hex(4))]) }
50
+
51
+ it "sends an authorize request to paypal" do
52
+ paypal_order_id = SecureRandom.hex(8)
53
+ source = paypal_payment_method.payment_source_class.create(paypal_order_id: paypal_order_id)
54
+ expect_request(:OrdersAuthorizeRequest).to receive(:new).with(paypal_order_id)
55
+ paypal_payment_method.authorize(1000, source, {})
56
+ end
57
+ end
58
+
59
+ describe "#capture" do
60
+ let(:result) { Struct(id: SecureRandom.hex(4), status: status) }
61
+
62
+ context "when payment COMPLETED" do
63
+ let(:status) { "COMPLETED" }
64
+
65
+ it "sends a capture request to paypal" do
66
+ authorization_id = SecureRandom.hex(8)
67
+ source = paypal_payment_method.payment_source_class.create(authorization_id: authorization_id)
68
+ payment.source = source
69
+ expect_request(:AuthorizationsCaptureRequest).to receive(:new).with(authorization_id).and_call_original
70
+ billing_response = paypal_payment_method.capture(1000, {}, originator: payment)
71
+ expect(billing_response.message).to eq("Authorization captured")
72
+ end
73
+ end
74
+
75
+ context "when payment PENDING" do
76
+ let(:status) { "PENDING" }
77
+
78
+ it "sends a capture request to paypal" do
79
+ authorization_id = SecureRandom.hex(8)
80
+ source = paypal_payment_method.payment_source_class.create(authorization_id: authorization_id)
81
+ payment.source = source
82
+ expect_request(:AuthorizationsCaptureRequest).to receive(:new).with(authorization_id).and_call_original
83
+ billing_response = paypal_payment_method.capture(1000, {}, originator: payment)
84
+ expect(billing_response.message).to eq("Payment is awaiting processing on PayPal's side")
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#void" do
90
+ it "sends a void request to paypal" do
91
+ authorization_id = SecureRandom.hex(8)
92
+ source = paypal_payment_method.payment_source_class.create(authorization_id: authorization_id)
93
+ payment.source = source
94
+ expect_request(:AuthorizationsVoidRequest).to receive(:new).with(authorization_id)
95
+ paypal_payment_method.void(nil, originator: payment)
96
+ end
97
+ end
98
+
99
+ describe "#credit" do
100
+ let(:result) { Struct(id: SecureRandom.hex(4)) }
101
+
102
+ it "sends a refund request to paypal" do
103
+ capture_id = SecureRandom.hex(4)
104
+ source = paypal_payment_method.payment_source_class.create(capture_id: capture_id)
105
+ completed_payment.source = source
106
+ expect_request(:CapturesRefundRequest).to receive(:new).with(capture_id).and_call_original
107
+ paypal_payment_method.credit(1000, {}, originator: completed_payment.refunds.new(amount: 12))
108
+ expect(source.refund_id).not_to be_blank
109
+ end
110
+ end
111
+
112
+ describe '.javascript_sdk_url' do
113
+ subject(:url) { URI(paypal_payment_method.javascript_sdk_url(order: order)) }
114
+
115
+ let(:order) { build_stubbed(:order) }
116
+
117
+ context 'when checkout_steps include "confirm"' do
118
+ it 'sets autocommit' do
119
+ expect(url.query.split("&")).to include("commit=false")
120
+ end
121
+ end
122
+
123
+ context 'when checkout_steps does not include "confirm"' do
124
+ it 'disables autocommit' do
125
+ allow(order).to receive(:checkout_steps).and_return([:address, :delivery, :payment])
126
+ expect(url.query.split("&")).to include("commit=true")
127
+ end
128
+ end
129
+
130
+ context 'when messaging is turned on' do
131
+ it 'includes messaging component' do
132
+ paypal_payment_method.preferences.update(display_credit_messaging: true)
133
+ expect(url.query.split("&")).to include("components=buttons%2Cmessages")
134
+ end
135
+ end
136
+
137
+ context 'when messaging is turned off' do
138
+ it 'only includes buttons components' do
139
+ paypal_payment_method.preferences.update(display_credit_messaging: false)
140
+ expect(url.query.split("&")).not_to include("messages")
141
+ expect(url.query.split("&")).to include("components=buttons")
142
+ end
143
+ end
144
+
145
+ context 'when venmo_standalone is "only render standalone"' do
146
+ before { paypal_payment_method.preferences.update(venmo_standalone: 'only render standalone') }
147
+
148
+ it 'includes "enable-funding=venmo" as a parameter' do
149
+ expect(url.query.split('&')).to include('enable-funding=venmo')
150
+ end
151
+ end
152
+
153
+ context 'when venmo_standalone is "enabled"' do
154
+ before { paypal_payment_method.preferences.update(venmo_standalone: 'enabled') }
155
+
156
+ it 'includes "enable-funding=venmo" as a parameter' do
157
+ expect(url.query.split('&')).to include('enable-funding=venmo')
158
+ end
159
+ end
160
+
161
+ context 'when venmo_standalone is "disabled"' do
162
+ before { paypal_payment_method.preferences.update(venmo_standalone: 'disabled') }
163
+
164
+ it 'does not include the "enable-funding" parameter' do
165
+ expect(url.query.split('&')).not_to include(match 'enable-funding')
166
+ end
167
+ end
168
+
169
+ context 'when force_buyer_country is an empty string' do
170
+ it 'does not include the "buyer-country" parameter' do
171
+ expect(url.query.split('&')).not_to include(match 'buyer-country')
172
+ end
173
+ end
174
+
175
+ context 'when force_buyer_country is "US"' do
176
+ before { paypal_payment_method.preferences.update(force_buyer_country: 'US') }
177
+
178
+ it 'includes "buyer-country=US" as a parameter' do
179
+ expect(url.query.split('&')).to include('buyer-country=US')
180
+ end
181
+ end
182
+
183
+ context 'when force_buyer_country is "US" but the environment is production' do
184
+ before {
185
+ allow(Rails.env).to receive(:production?).and_return(true)
186
+ paypal_payment_method.preferences.update(force_buyer_country: 'US')
187
+ }
188
+
189
+ it 'includes "buyer-country=US" as a parameter' do
190
+ expect(url.query.split('&')).not_to include(match 'buyer-country')
191
+ end
192
+ end
193
+ end
194
+
195
+ private
196
+
197
+ def expect_request(name)
198
+ expect(SolidusPaypalCommercePlatform::Gateway.const_get(name))
199
+ end
200
+ end
@@ -0,0 +1,119 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::PaymentSource, type: :model do
4
+ let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
5
+ let(:paypal_payment_method) { create(:paypal_payment_method) }
6
+ let(:paypal_order_id) { "foo-123" }
7
+ let(:payment_source) {
8
+ described_class.new(
9
+ paypal_order_id: paypal_order_id,
10
+ payment_method: paypal_payment_method,
11
+ )
12
+ }
13
+ let(:payment) {
14
+ order.payments.create!(
15
+ payment_method: paypal_payment_method,
16
+ source: payment_source
17
+ )
18
+ }
19
+ let(:paypal_order_status) { "COMPLETED" }
20
+
21
+ before do
22
+ allow_any_instance_of(SolidusPaypalCommercePlatform::Client).to receive(:execute) do |_client, request|
23
+ expect(request).to be_a(SolidusPaypalCommercePlatform::Gateway::OrdersGetRequest) # rubocop:disable RSpec/ExpectInHook
24
+
25
+ # rubocop:disable RSpec/VerifiedDoubles
26
+ double(
27
+ 'response',
28
+ result: double('result', status: paypal_order_status)
29
+ )
30
+ # rubocop:enable RSpec/VerifiedDoubles
31
+ end
32
+ end
33
+
34
+ describe '#actions' do
35
+ context 'when the payment is not yet completed' do
36
+ it 'shows "capture" and "void"' do
37
+ expect(payment.actions).to contain_exactly("capture", "void")
38
+ end
39
+ end
40
+
41
+ context 'when the payment is completed and captured' do
42
+ before do
43
+ payment.update!(state: :completed, amount: 123)
44
+ payment_source.update!(capture_id: 1234)
45
+ end
46
+
47
+ it 'shows "credit"' do
48
+ expect(payment.actions).to contain_exactly("credit")
49
+ end
50
+
51
+ context 'when the PayPal order status is not COMPLETED' do
52
+ let(:paypal_order_status) { "FOOBAR" }
53
+
54
+ it 'hides the "credit" action' do
55
+ expect(payment.actions).not_to include("credit")
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'when it cannot capture' do
61
+ it 'also cannot void' do
62
+ # One for "capture", and one for "void"
63
+ expect(payment_source).to receive(:can_capture?).and_return(false).twice
64
+ expect(payment.actions).not_to include("void")
65
+ end
66
+ end
67
+
68
+ context 'with #display_paypal_funding_source' do
69
+ context 'when the EN locale exists' do
70
+ it 'translates the funding source' do
71
+ payment_source.paypal_funding_source = 'card'
72
+
73
+ result = payment_source.display_paypal_funding_source
74
+
75
+ expect(result).to eq('Credit or debit card')
76
+ end
77
+ end
78
+
79
+ context "when the locale doesn't exist" do
80
+ it 'returns the paypal_funding_source as the default' do
81
+ allow(payment_source).to receive(:paypal_funding_source).and_return('non-existent')
82
+
83
+ result = payment_source.display_paypal_funding_source
84
+
85
+ expect(result).to eq('non-existent')
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'attributes' do
92
+ context 'with paypal_funding_source' do
93
+ it 'can be nil' do
94
+ payment_source.paypal_funding_source = nil
95
+
96
+ expect(payment_source).to be_valid
97
+ end
98
+
99
+ it 'makes empty strings nil' do
100
+ payment_source.paypal_funding_source = ''
101
+
102
+ result = payment_source.save
103
+
104
+ expect(result).to be(true)
105
+ expect(payment_source.paypal_funding_source).to be_nil
106
+ end
107
+
108
+ it 'gets correctly mapped as an enum' do
109
+ payment_source.paypal_funding_source = 'applepay'
110
+
111
+ result = payment_source.save
112
+
113
+ expect(result).to be(true)
114
+ expect(payment_source.paypal_funding_source).to eq('applepay')
115
+ expect(payment_source.applepay_funding?).to be(true)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,67 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::PaypalAddress, type: :model do
4
+ let(:order) { create(:order) }
5
+ let(:original_address) { create(:address) }
6
+ let(:address) { create(:address, name_attributes) }
7
+ let(:params) {
8
+ {
9
+ updated_address: {
10
+ admin_area_1: address.state.abbr,
11
+ admin_area_2: address.city,
12
+ address_line_1: address.address1,
13
+ address_line_2: address.address2,
14
+ postal_code: address.zipcode,
15
+ country_code: address.country.iso
16
+ },
17
+ recipient: {
18
+ name: {
19
+ given_name: "Alexander",
20
+ surname: "Hamilton"
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ describe "#update_address" do
27
+ subject{ order.ship_address }
28
+
29
+ it "formats PayPal addresses correctly" do
30
+ order.ship_address = original_address
31
+
32
+ described_class.new(order).update(params)
33
+
34
+ expect(subject.state).to eq address.state
35
+ expect(subject.city).to eq address.city
36
+ expect(subject.address1).to eq address.address1
37
+ expect(subject.address2).to eq address.address2
38
+ expect(subject.zipcode).to eq address.zipcode
39
+ expect(subject.country).to eq address.country
40
+ if SolidusSupport.combined_first_and_last_name_in_address?
41
+ expect(subject.name).to eq address.name
42
+ else
43
+ expect(subject.firstname).to eq address.firstname
44
+ expect(subject.lastname).to eq address.lastname
45
+ end
46
+ expect(subject.phone).to eq original_address.phone
47
+ end
48
+
49
+ context "when no address exists" do
50
+ it "produce a valid address" do
51
+ order.ship_address = nil
52
+
53
+ described_class.new(order).update(params)
54
+
55
+ expect(subject).to be_present
56
+ end
57
+ end
58
+ end
59
+
60
+ def name_attributes
61
+ if SolidusSupport.combined_first_and_last_name_in_address?
62
+ { name: "Alexander Hamilton" }
63
+ else
64
+ { firstname: "Alexander", lastname: "Hamilton" }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_paypal_commerce_platform_spec_helper'
4
+
5
+ RSpec.describe SolidusPaypalCommercePlatform::PaypalOrder, type: :model do
6
+ describe '#to_json' do
7
+ subject(:to_json) { described_class.new(order).to_json('intent') }
8
+
9
+ let(:order) { create(:order_ready_to_complete, ship_address: ship_address, bill_address: bill_address) }
10
+ let(:ship_address) { create_address('John', 'Von Doe') }
11
+ let(:bill_address) { create_address('Johnny', 'Vonny Doey') }
12
+
13
+ it { expect { to_json }.not_to raise_error }
14
+
15
+ it 'represents amounts as strings with double decimals' do
16
+ purchase_unit = to_json.dig(:purchase_units, 0)
17
+ expect(purchase_unit.dig(:amount, :breakdown)).to eq(
18
+ item_total: { currency_code: "USD", value: "10.00" },
19
+ shipping: { currency_code: "USD", value: "100.00" },
20
+ tax_total: { currency_code: "USD", value: "0.00" },
21
+ discount: { currency_code: "USD", value: "0.00" }
22
+ )
23
+ expect(purchase_unit.dig(:items, 0, :unit_amount)).to eq(currency_code: "USD", value: "10.00")
24
+ end
25
+
26
+ it 'maps the bill and ship address names correctly' do
27
+ expect(to_json).to match hash_including(
28
+ purchase_units: array_including(
29
+ hash_including(shipping: hash_including(name: { full_name: 'John Von Doe' }))
30
+ ),
31
+ payer: hash_including(name: { given_name: 'Johnny', surname: 'Vonny Doey' })
32
+ )
33
+ end
34
+
35
+ context 'when the item name is greater than 127 characters' do
36
+ let(:variant) { create(:variant, product: create(:product, name: 'a' * 128)) }
37
+ let(:order) do
38
+ create(:order_ready_to_complete, line_items_attributes: [{ variant: variant, quantity: 1, price: 10 }])
39
+ end
40
+
41
+ it 'returns the truncated name' do
42
+ expect(to_json).to match hash_including(
43
+ purchase_units: array_including(
44
+ hash_including(
45
+ items: array_including(hash_including(name: "#{'a' * 124}..."))
46
+ )
47
+ )
48
+ )
49
+ end
50
+ end
51
+
52
+ context 'when checkout_steps does not include "delivery"' do
53
+ let(:old_checkout_flow) { Spree::Order.checkout_flow }
54
+
55
+ before do
56
+ old_checkout_flow
57
+
58
+ Spree::Order.class_eval do
59
+ remove_checkout_step :delivery
60
+ end
61
+ end
62
+
63
+ after do
64
+ Spree::Order.checkout_flow(&old_checkout_flow)
65
+ end
66
+
67
+ it 'disable shipping requirements' do
68
+ expect(to_json).to match hash_including(
69
+ application_context: hash_including(shipping_preference: 'NO_SHIPPING')
70
+ )
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def create_address(firstname, lastname)
78
+ create(:address, name: "#{firstname} #{lastname}")
79
+ end
80
+ end
@@ -0,0 +1,38 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::StateGuesser, type: :model do
4
+ let(:country) { create(:country, iso: "IT") }
5
+ let!(:state) { create(:state, country: country, name: "Abruzzo") }
6
+
7
+ describe "#guess" do
8
+ context "with a guessable state error" do
9
+ it "correctly guesses the state" do
10
+ expect(
11
+ described_class.new("Pescara", country).guess
12
+ ).to eq state
13
+ end
14
+
15
+ it "guesses the state using an abbreviation" do
16
+ expect(
17
+ described_class.new("PE", country).guess
18
+ ).to eq state
19
+ end
20
+ end
21
+
22
+ context "with an unsolvable state error" do
23
+ it "returns nil" do
24
+ expect(
25
+ described_class.new("Gondor", country).guess
26
+ ).to be_nil
27
+ end
28
+ end
29
+
30
+ context "with an already correct state" do
31
+ it "returns the correct state" do
32
+ expect(
33
+ described_class.new("Abruzzo", country).guess
34
+ ).to eq state
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::Wizard do
4
+ describe "#nonce" do
5
+ it 'is between 45 and 128 chars long' do
6
+ expect(subject.nonce.size).to be_between(45, 128)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,36 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::OrdersController, type: :request do
4
+ stub_authorization!
5
+ let(:order) { create(:order_with_line_items) }
6
+
7
+ describe "GET /solidus_paypal_commerce_platform/verify_total" do
8
+ context "when the amount is correct" do
9
+ it "passes" do
10
+ params = {
11
+ order_id: order.number,
12
+ paypal_total: order.total,
13
+ format: :json
14
+ }
15
+
16
+ get solidus_paypal_commerce_platform.verify_total_path, params: params
17
+
18
+ expect(response).to have_http_status(:ok)
19
+ end
20
+ end
21
+
22
+ context "when the amount is incorrect" do
23
+ it "fails" do
24
+ params = {
25
+ order_id: order.number,
26
+ paypal_total: order.total - 1,
27
+ format: :json
28
+ }
29
+
30
+ get solidus_paypal_commerce_platform.verify_total_path, params: params
31
+
32
+ expect(response).to have_http_status(:bad_request)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::ShippingRatesController, type: :request do
4
+ stub_authorization!
5
+ let(:new_country) { create(:country, iso: "IT", states_required: true) }
6
+ let(:new_state) { create(:state, country: new_country) }
7
+ let(:new_address) { build(:address, country: new_country, state: new_state) }
8
+ let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
9
+ let(:paypal_address) {
10
+ {
11
+ country_code: new_country.iso,
12
+ city: new_address.city,
13
+ state: new_state&.abbr,
14
+ postal_code: new_address.zipcode
15
+ }
16
+ }
17
+
18
+ describe "GET /simulate_shipping_rates" do
19
+ before do
20
+ get solidus_paypal_commerce_platform.shipping_rates_path, params: {
21
+ order_id: order.number,
22
+ address: paypal_address
23
+ }
24
+ end
25
+
26
+ it "returns a paypal_order without the simulated address" do
27
+ expect(response.body).not_to include "admin_area_1\":\"#{new_address.state.abbr}\""
28
+ expect(response.body).not_to include "admin_area_1\":\"#{order.ship_address.state.abbr}\""
29
+ end
30
+
31
+ it "does not modify original address" do
32
+ expect(order.ship_address).not_to eq new_address
33
+ end
34
+
35
+ context "with an invalid address" do
36
+ let(:new_state) { nil }
37
+
38
+ it "returns a list of errors" do
39
+ expect(response).to have_http_status(:unprocessable_entity) # unprocessable_entity
40
+ expect(response.body).to include "State can't be blank"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::WizardController, type: :request do
4
+ stub_authorization!
5
+
6
+ let(:wizard) { SolidusPaypalCommercePlatform::Wizard.new }
7
+
8
+ describe "POST /solidus_paypal_commerce_platform/wizard" do
9
+ let(:params) {
10
+ {
11
+ authCode: "PFc4d2vp4DVfHqcnEHbGAA12C_H29U39NM_vmQrZBPzdLcxA12Br-GzjbliqXGu3AG6Gfwo5G9GTal6REkcKGMREc9fPsR_wv",
12
+ sharedId: "awj45zMAy1XonxWfgIhjjGHZGAPStkZFzXp4bfe1QmNWA-9DL6HkpklD0skHig4vVF7zVZD8Uwy5Qop4",
13
+ nonce: wizard.nonce,
14
+ }
15
+ }
16
+
17
+ it "creates a new payment method from data received from PayPal" do
18
+ expect_any_instance_of(SolidusPaypalCommercePlatform::Client).to receive(:execute) do |_client, request|
19
+ case request
20
+ when SolidusPaypalCommercePlatform::AccessTokenAuthorizationRequest
21
+ # rubocop:disable RSpec/VerifiedDoubles
22
+ double(
23
+ 'response',
24
+ result: double(
25
+ 'result',
26
+ access_token: "ACCESS-TOKEN"
27
+ )
28
+ )
29
+ # rubocop:enable RSpec/VerifiedDoubles
30
+ when SolidusPaypalCommercePlatform::FetchMerchantCredentialsRequest
31
+ expect(request.headers.fetch("Authorization")).to eq("Bearer ACCESS-TOKEN")
32
+
33
+ # rubocop:disable RSpec/VerifiedDoubles
34
+ double(
35
+ 'response',
36
+ result: double(
37
+ 'result',
38
+ client_id: "CLIENT-ID",
39
+ client_secret: "CLIENT-SECRET",
40
+ )
41
+ )
42
+ # rubocop:enable RSpec/VerifiedDoubles
43
+ else
44
+ raise "unexpected request: #{request}"
45
+ end
46
+ end.twice
47
+
48
+ expect {
49
+ post solidus_paypal_commerce_platform.wizard_index_path, params: params
50
+ }.to change(SolidusPaypalCommercePlatform::PaymentMethod, :count).from(0).to(1)
51
+
52
+ payment_method = SolidusPaypalCommercePlatform::PaymentMethod.last
53
+
54
+ expect(payment_method.preferred_client_id).to eq("CLIENT-ID")
55
+ expect(payment_method.preferred_client_secret).to eq("CLIENT-SECRET")
56
+ expect(response).to have_http_status(:created)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_starter_frontend_helper'
4
+
5
+ Dir["#{__dir__}/support/solidus_paypal_commerce_platform/**/*.rb"].sort.each { |f| require f }
@@ -0,0 +1,5 @@
1
+ FactoryBot.definition_file_paths << SolidusPaypalCommercePlatform::Engine.root.join(
2
+ 'lib/solidus_paypal_commerce_platform/testing_support/factories'
3
+ ).to_s
4
+
5
+ FactoryBot.reload
@@ -0,0 +1,13 @@
1
+ module PaypalSdkScriptTagHelper
2
+ def js_sdk_script_query
3
+ URI(page.find('script[src*="sdk/js?"]', visible: false)[:src]).query.split('&')
4
+ end
5
+
6
+ def js_sdk_script_partner_id
7
+ page.find('script[src*="sdk/js?"]', visible: false)['data-partner-attribution-id']
8
+ end
9
+ end
10
+
11
+ RSpec.configure do |config|
12
+ config.include PaypalSdkScriptTagHelper, type: :system
13
+ end