solidus_paypal_braintree 0.4.0 → 1.0.0

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +40 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.gitignore +18 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +68 -0
  8. data/CHANGELOG.md +258 -0
  9. data/Gemfile +43 -0
  10. data/LICENSE +2 -2
  11. data/README.md +77 -23
  12. data/Rakefile +4 -25
  13. data/app/assets/javascripts/solidus_paypal_braintree/checkout.js +32 -3
  14. data/app/assets/javascripts/solidus_paypal_braintree/client.js +23 -4
  15. data/app/assets/javascripts/solidus_paypal_braintree/constants.js +19 -0
  16. data/app/assets/javascripts/solidus_paypal_braintree/frontend.js +1 -0
  17. data/app/assets/javascripts/solidus_paypal_braintree/hosted_form.js +15 -5
  18. data/app/assets/javascripts/solidus_paypal_braintree/paypal_button.js +47 -20
  19. data/app/assets/javascripts/solidus_paypal_braintree/paypal_messaging.js +22 -0
  20. data/app/decorators/controllers/solidus_paypal_braintree/admin_payments_controller_decorator.rb +11 -0
  21. data/app/decorators/controllers/solidus_paypal_braintree/checkout_controller_decorator.rb +11 -0
  22. data/app/decorators/controllers/solidus_paypal_braintree/client_tokens_controller.rb +41 -0
  23. data/app/decorators/controllers/solidus_paypal_braintree/orders_controller_decorator.rb +11 -0
  24. data/app/decorators/models/solidus_paypal_braintree/spree/store_decorator.rb +20 -0
  25. data/app/decorators/models/solidus_paypal_braintree/spree/user_decorator.rb +13 -0
  26. data/app/helpers/solidus_paypal_braintree/braintree_admin_helper.rb +23 -0
  27. data/app/helpers/solidus_paypal_braintree/braintree_checkout_helper.rb +46 -0
  28. data/app/models/application_record.rb +2 -0
  29. data/app/models/solidus_paypal_braintree/address.rb +30 -0
  30. data/app/models/solidus_paypal_braintree/configuration.rb +26 -3
  31. data/app/models/solidus_paypal_braintree/customer.rb +7 -3
  32. data/app/models/solidus_paypal_braintree/gateway.rb +52 -20
  33. data/app/models/solidus_paypal_braintree/response.rb +3 -2
  34. data/app/models/solidus_paypal_braintree/source.rb +21 -7
  35. data/app/models/solidus_paypal_braintree/transaction.rb +2 -0
  36. data/app/models/solidus_paypal_braintree/transaction_address.rb +30 -12
  37. data/app/models/solidus_paypal_braintree/transaction_import.rb +13 -9
  38. data/app/views/spree/api/payments/source_views/_paypal_braintree.json.jbuilder +3 -0
  39. data/app/views/spree/checkout/existing_payment/_paypal_braintree.html.erb +10 -0
  40. data/app/views/spree/shared/_apple_pay_button.html.erb +2 -2
  41. data/app/views/spree/shared/_braintree_errors.html.erb +11 -17
  42. data/app/views/spree/shared/_braintree_hosted_fields.html.erb +24 -9
  43. data/app/views/spree/shared/_paypal_braintree_head_scripts.html.erb +9 -6
  44. data/app/views/spree/shared/_paypal_cart_button.html.erb +16 -2
  45. data/app/views/spree/shared/_paypal_messaging.html.erb +13 -0
  46. data/bin/console +17 -0
  47. data/bin/rails +15 -0
  48. data/bin/setup +8 -0
  49. data/config/locales/en.yml +10 -0
  50. data/config/locales/it.yml +51 -8
  51. data/config/routes.rb +2 -0
  52. data/db/migrate/20160906201711_create_solidus_paypal_braintree_customers.rb +3 -1
  53. data/db/migrate/20161125172005_add_braintree_configuration_to_stores.rb +5 -7
  54. data/db/migrate/20170505193712_add_null_constraint_to_sources.rb +3 -1
  55. data/db/migrate/20190705115327_add_paypal_button_preferences_to_braintree_configurations.rb +5 -0
  56. data/db/migrate/20190911141712_add_3d_secure_to_braintree_configuration.rb +5 -0
  57. data/lib/controllers/backend/solidus_paypal_braintree/configurations_controller.rb +20 -5
  58. data/lib/controllers/frontend/solidus_paypal_braintree/checkouts_controller.rb +25 -21
  59. data/lib/controllers/frontend/solidus_paypal_braintree/transactions_controller.rb +55 -51
  60. data/lib/generators/solidus_paypal_braintree/install/install_generator.rb +7 -5
  61. data/lib/solidus_paypal_braintree.rb +4 -0
  62. data/lib/solidus_paypal_braintree/country_mapper.rb +4 -2
  63. data/lib/solidus_paypal_braintree/engine.rb +11 -11
  64. data/lib/solidus_paypal_braintree/factories.rb +8 -4
  65. data/lib/solidus_paypal_braintree/request_protection.rb +3 -0
  66. data/lib/solidus_paypal_braintree/version.rb +3 -1
  67. data/lib/views/backend/solidus_paypal_braintree/configurations/list.html.erb +30 -5
  68. data/lib/views/backend/spree/admin/payments/source_forms/_paypal_braintree.html.erb +2 -2
  69. data/lib/views/backend/spree/admin/payments/source_views/_paypal_braintree.html.erb +2 -2
  70. data/lib/views/backend_v1.2/spree/admin/payments/source_forms/_paypal_braintree.html.erb +2 -2
  71. data/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb +4 -2
  72. data/lib/views/frontend/spree/shared/_paypal_checkout_button.html.erb +30 -0
  73. data/solidus_paypal_braintree.gemspec +42 -0
  74. data/spec/controllers/solidus_paypal_braintree/checkouts_controller_spec.rb +99 -0
  75. data/spec/controllers/solidus_paypal_braintree/client_tokens_controller_spec.rb +55 -0
  76. data/spec/controllers/solidus_paypal_braintree/configurations_controller_spec.rb +73 -0
  77. data/spec/controllers/solidus_paypal_braintree/transactions_controller_spec.rb +183 -0
  78. data/spec/features/backend/configuration_spec.rb +23 -0
  79. data/spec/features/backend/new_payment_spec.rb +137 -0
  80. data/spec/features/frontend/braintree_credit_card_checkout_spec.rb +187 -0
  81. data/spec/features/frontend/paypal_checkout_spec.rb +166 -0
  82. data/spec/fixtures/cassettes/admin/invalid_credit_card.yml +63 -0
  83. data/spec/fixtures/cassettes/admin/resubmit_credit_card.yml +352 -0
  84. data/spec/fixtures/cassettes/admin/valid_credit_card.yml +412 -0
  85. data/spec/fixtures/cassettes/braintree/create_profile.yml +71 -0
  86. data/spec/fixtures/cassettes/braintree/generate_token.yml +63 -0
  87. data/spec/fixtures/cassettes/braintree/token.yml +63 -0
  88. data/spec/fixtures/cassettes/checkout/invalid_credit_card.yml +63 -0
  89. data/spec/fixtures/cassettes/checkout/resubmit_credit_card.yml +216 -0
  90. data/spec/fixtures/cassettes/checkout/update.yml +71 -0
  91. data/spec/fixtures/cassettes/checkout/valid_credit_card.yml +156 -0
  92. data/spec/fixtures/cassettes/gateway/authorize.yml +86 -0
  93. data/spec/fixtures/cassettes/gateway/authorize/credit_card/address.yml +86 -0
  94. data/spec/fixtures/cassettes/gateway/authorize/merchant_account/EUR.yml +154 -0
  95. data/spec/fixtures/cassettes/gateway/authorize/paypal/EUR.yml +90 -0
  96. data/spec/fixtures/cassettes/gateway/authorize/paypal/address.yml +90 -0
  97. data/spec/fixtures/cassettes/gateway/authorized_transaction.yml +73 -0
  98. data/spec/fixtures/cassettes/gateway/cancel/missing.yml +63 -0
  99. data/spec/fixtures/cassettes/gateway/cancel/refunds.yml +272 -0
  100. data/spec/fixtures/cassettes/gateway/cancel/void.yml +201 -0
  101. data/spec/fixtures/cassettes/gateway/capture.yml +141 -0
  102. data/spec/fixtures/cassettes/gateway/complete.yml +157 -0
  103. data/spec/fixtures/cassettes/gateway/credit.yml +208 -0
  104. data/spec/fixtures/cassettes/gateway/purchase.yml +87 -0
  105. data/spec/fixtures/cassettes/gateway/settled_transaction.yml +140 -0
  106. data/spec/fixtures/cassettes/gateway/void.yml +137 -0
  107. data/spec/fixtures/cassettes/source/card_type.yml +267 -0
  108. data/spec/fixtures/cassettes/source/last4.yml +267 -0
  109. data/spec/fixtures/cassettes/transaction/import/valid.yml +71 -0
  110. data/spec/fixtures/cassettes/transaction/import/valid/capture.yml +224 -0
  111. data/spec/fixtures/views/spree/orders/edit.html.erb +50 -0
  112. data/spec/helpers/solidus_paypal_braintree/braintree_admin_helper_spec.rb +17 -0
  113. data/spec/models/solidus_paypal_braintree/address_spec.rb +51 -0
  114. data/spec/models/solidus_paypal_braintree/avs_result_spec.rb +317 -0
  115. data/spec/models/solidus_paypal_braintree/gateway_spec.rb +692 -0
  116. data/spec/models/solidus_paypal_braintree/response_spec.rb +280 -0
  117. data/spec/models/solidus_paypal_braintree/source_spec.rb +360 -0
  118. data/spec/models/solidus_paypal_braintree/transaction_address_spec.rb +253 -0
  119. data/spec/models/solidus_paypal_braintree/transaction_import_spec.rb +283 -0
  120. data/spec/models/solidus_paypal_braintree/transaction_spec.rb +85 -0
  121. data/spec/models/spree/store_spec.rb +14 -0
  122. data/spec/requests/spree/api/orders_controller_spec.rb +36 -0
  123. data/spec/spec_helper.rb +26 -0
  124. data/spec/support/capybara.rb +7 -0
  125. data/spec/support/factories.rb +2 -0
  126. data/spec/support/gateway_helpers.rb +29 -0
  127. data/spec/support/order_ready_for_payment.rb +37 -0
  128. data/spec/support/vcr.rb +42 -0
  129. data/spec/support/views.rb +1 -0
  130. metadata +182 -194
  131. data/app/controllers/solidus_paypal_braintree/client_tokens_controller.rb +0 -22
  132. data/app/helpers/braintree_admin_helper.rb +0 -18
  133. data/app/models/spree/store_decorator.rb +0 -11
  134. data/app/views/spree/shared/_paypal_checkout_button.html.erb +0 -27
  135. data/config/initializers/braintree.rb +0 -1
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe SolidusPaypalBraintree::ConfigurationsController, type: :controller do
4
+ routes { SolidusPaypalBraintree::Engine.routes }
5
+
6
+ let!(:store_1) { create :store }
7
+ let!(:store_2) { create :store }
8
+
9
+ stub_authorization!
10
+
11
+ describe "GET #list" do
12
+ subject { get :list }
13
+
14
+ it "assigns all store's configurations as @configurations" do
15
+ subject
16
+ expect(assigns(:configurations)).
17
+ to eq [store_1.braintree_configuration, store_2.braintree_configuration]
18
+ end
19
+
20
+ it "renders the correct view" do
21
+ expect(subject).to render_template :list
22
+ end
23
+ end
24
+
25
+ describe "POST #update" do
26
+ subject { post :update, params: configurations_params }
27
+
28
+ let(:paypal_button_color) { 'blue' }
29
+ let(:configurations_params) do
30
+ {
31
+ configurations: {
32
+ configuration_fields: {
33
+ store_1.braintree_configuration.id.to_s => { paypal: true, apple_pay: true },
34
+ store_2.braintree_configuration.id.to_s => {
35
+ paypal: true,
36
+ apple_pay: false,
37
+ preferred_paypal_button_color: paypal_button_color
38
+ }
39
+ }
40
+ }
41
+ }
42
+ end
43
+
44
+ context "with valid parameters" do
45
+ it "updates the configuration" do
46
+ expect { subject }.to change { store_1.braintree_configuration.reload.paypal }.
47
+ from(false).to(true)
48
+ end
49
+
50
+ it "displays a success message to the user" do
51
+ subject
52
+ expect(flash[:success]).to eq "Successfully updated Braintree configurations."
53
+ end
54
+
55
+ it "displays all configurations" do
56
+ expect(subject).to redirect_to action: :list
57
+ end
58
+ end
59
+
60
+ context "with invalid parameters" do
61
+ let(:paypal_button_color) { 'invalid-color' }
62
+
63
+ it "displays an error message to the user" do
64
+ subject
65
+ expect(flash[:error]).to eq "An error occurred while updating Braintree configurations."
66
+ end
67
+
68
+ it "returns the user to the edit page" do
69
+ expect(subject).to redirect_to action: :list
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalBraintree::TransactionsController, type: :controller do
4
+ routes { SolidusPaypalBraintree::Engine.routes }
5
+
6
+ let!(:country) { create :country }
7
+ let(:line_item) { create :line_item, price: 50 }
8
+ let(:order) do
9
+ Spree::Order.create!(
10
+ line_items: [line_item],
11
+ email: 'test@example.com',
12
+ bill_address: create(:address, country: country),
13
+ ship_address: create(:address, country: country),
14
+ user: create(:user)
15
+ )
16
+ end
17
+
18
+ let(:payment_method) { create_gateway }
19
+
20
+ before do
21
+ allow(controller).to receive(:spree_current_user) { order.user }
22
+ allow(controller).to receive(:current_order) { order }
23
+ create :shipping_method, cost: 5
24
+ create :state, abbr: "WA", country: country
25
+ end
26
+
27
+ cassette_options = {
28
+ cassette_name: "transactions_controller/create",
29
+ match_requests_on: [:braintree_uri]
30
+ }
31
+ describe "POST create", vcr: cassette_options do
32
+ subject(:post_create) { post :create, params: params }
33
+
34
+ let!(:country) { create :country, iso: 'US' }
35
+
36
+ let(:params) do
37
+ {
38
+ transaction: {
39
+ nonce: "fake-valid-nonce",
40
+ payment_type: SolidusPaypalBraintree::Source::PAYPAL,
41
+ phone: "1112223333",
42
+ email: "batman@example.com",
43
+ address_attributes: {
44
+ name: "Wade Wilson",
45
+ address_line_1: "123 Fake Street",
46
+ city: "Seattle",
47
+ zip: "98101",
48
+ state_code: "WA",
49
+ country_code: "US"
50
+ }
51
+ },
52
+ payment_method_id: payment_method.id
53
+ }
54
+ end
55
+
56
+ before do
57
+ create :state, abbr: "WA", country: country
58
+ end
59
+
60
+ context "when import has invalid address" do
61
+ before { params[:transaction][:address_attributes][:city] = nil }
62
+
63
+ it "raises a validation error" do
64
+ expect { post_create }.to raise_error(
65
+ SolidusPaypalBraintree::TransactionsController::InvalidImportError,
66
+ "Import invalid: " \
67
+ "Address is invalid, " \
68
+ "Address city can't be blank"
69
+ )
70
+ end
71
+ end
72
+
73
+ context "when the transaction is valid", vcr: {
74
+ cassette_name: 'transaction/import/valid',
75
+ match_requests_on: [:braintree_uri]
76
+ } do
77
+ it "imports the payment" do
78
+ expect { post_create }.to change { order.payments.count }.by(1)
79
+ expect(order.payments.first.amount).to eq 55
80
+ end
81
+
82
+ context "when no end state is provided" do
83
+ it "advances the order to confirm" do
84
+ post_create
85
+ expect(order).to be_confirm
86
+ end
87
+ end
88
+
89
+ context "when end state provided is delivery" do
90
+ let(:params) { super().merge(state: 'delivery') }
91
+
92
+ it "advances the order to delivery" do
93
+ post_create
94
+ expect(order).to be_delivery
95
+ end
96
+ end
97
+
98
+ context "with provided address" do
99
+ it "creates a new address" do
100
+ # Creating the order also creates 3 addresses, we want to make sure
101
+ # the transaction import only creates 1 new one
102
+ order
103
+ expect { post_create }.to change { Spree::Address.count }.by(1)
104
+ expect(Spree::Address.last.full_name).to eq "Wade Wilson"
105
+ end
106
+ end
107
+
108
+ context "without country ISO" do
109
+ before do
110
+ params[:transaction][:address_attributes][:country_code] = ""
111
+ params[:transaction][:address_attributes][:country_name] = "United States"
112
+ end
113
+
114
+ it "creates a new address, looking up the ISO by country name" do
115
+ order
116
+ expect { post_create }.to change { Spree::Address.count }.by(1)
117
+ expect(Spree::Address.last.country.iso).to eq "US"
118
+ end
119
+ end
120
+
121
+ context "without transaction address" do
122
+ before { params[:transaction].delete(:address_attributes) }
123
+
124
+ it "does not create a new address" do
125
+ order
126
+ expect { post_create }.not_to(change { Spree::Address.count })
127
+ end
128
+ end
129
+
130
+ context "when format is HTML" do
131
+ context "when import! leaves the order in confirm" do
132
+ it "redirects the user to the confirm page" do
133
+ expect(post_create).to redirect_to spree.checkout_state_path("confirm")
134
+ end
135
+ end
136
+
137
+ context "when import! completes the order" do
138
+ before { allow(order).to receive(:complete?).and_return(true) }
139
+
140
+ it "displays the order to the user" do
141
+ expect(post_create).to redirect_to spree.order_path(order)
142
+ end
143
+ end
144
+ end
145
+
146
+ context "when format is JSON" do
147
+ before { params[:format] = :json }
148
+
149
+ it "has a successful response" do
150
+ post_create
151
+ expect(response).to be_successful
152
+ end
153
+ end
154
+ end
155
+
156
+ context "when the transaction is invalid" do
157
+ before { params[:transaction][:email] = nil }
158
+
159
+ context "when format is HTML" do
160
+ it "raises an error including the validation messages" do
161
+ expect { post_create }.to raise_error(
162
+ SolidusPaypalBraintree::TransactionsController::InvalidImportError,
163
+ "Import invalid: Email can't be blank"
164
+ )
165
+ end
166
+ end
167
+
168
+ context "when format is JSON" do
169
+ before { params[:format] = :json }
170
+
171
+ it "has a failed status" do
172
+ post_create
173
+ expect(response.status).to eq 422
174
+ end
175
+
176
+ it "returns the errors as JSON" do
177
+ post_create
178
+ expect(JSON.parse(response.body)["errors"]["email"]).to eq ["can't be blank"]
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "viewing the configuration interface" do
4
+ stub_authorization!
5
+
6
+ # Regression to ensure this page still renders on old versions of solidus
7
+ it "doesn't raise any errors due to unavailable route helpers" do
8
+ visit "/solidus_paypal_braintree/configurations/list"
9
+ expect(page).to have_content("Braintree Configurations")
10
+ end
11
+
12
+ # Regression to ensure this page renders on Solidus 2.4
13
+ it "doesn't raise any errors due to unavailable preference field partial" do
14
+ Rails.application.config.spree.payment_methods << SolidusPaypalBraintree::Gateway
15
+ Spree::PaymentMethod.create!(
16
+ type: 'SolidusPaypalBraintree::Gateway',
17
+ name: 'Braintree Payments'
18
+ )
19
+ visit '/admin/payment_methods'
20
+ page.find('a[title="Edit"]').click
21
+ expect(page).to have_field 'Name', with: 'Braintree Payments'
22
+ end
23
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+ require 'spree/testing_support/order_walkthrough'
3
+
4
+ shared_context "with backend checkout setup" do
5
+ let(:braintree) { new_gateway(active: true) }
6
+ let!(:gateway) { create :payment_method }
7
+ let(:order) { create(:completed_order_with_totals, number: 'R9999999') }
8
+ let(:pending_case_insensitive) { /pending/i }
9
+ let(:expiration) { "02/#{Date.current.year.next}" }
10
+
11
+ before do
12
+ braintree.save!
13
+ create(:store, payment_methods: [gateway, braintree]).tap do |store|
14
+ store.braintree_configuration.update!(credit_card: true)
15
+ end
16
+
17
+ allow_any_instance_of(SolidusPaypalBraintree::Source).to receive(:nonce).and_return("fake-valid-nonce")
18
+
19
+ # Order and payment numbers must be identical between runs to re-use the VCR
20
+ # cassette
21
+ allow_any_instance_of(Spree::Payment).to receive(:number).and_return("123ABC")
22
+ end
23
+
24
+ around do |example|
25
+ Capybara.using_wait_time(20) { example.run }
26
+ end
27
+ end
28
+
29
+ describe 'creating a new payment', type: :feature, js: true do
30
+ stub_authorization!
31
+
32
+ context "with valid credit card data", vcr: {
33
+ cassette_name: 'admin/valid_credit_card',
34
+ match_requests_on: [:braintree_uri]
35
+ } do
36
+ include_context "with backend checkout setup"
37
+
38
+ it "checks out successfully" do
39
+ visit "/admin/orders/#{order.number}/payments/new"
40
+ choose('Braintree')
41
+ expect(page).to have_selector("#payment_method_#{braintree.id}", visible: :visible)
42
+ expect(page).to have_selector("iframe#braintree-hosted-field-number")
43
+
44
+ within_frame("braintree-hosted-field-number") do
45
+ fill_in("credit-card-number", with: "4111111111111111")
46
+ end
47
+ within_frame("braintree-hosted-field-expirationDate") do
48
+ fill_in("expiration", with: expiration)
49
+ end
50
+ within_frame("braintree-hosted-field-cvv") do
51
+ fill_in("cvv", with: "123")
52
+ end
53
+
54
+ click_button("Update")
55
+
56
+ within('table#payments') do
57
+ expect(page).to have_content('Braintree')
58
+ expect(page).to have_content(pending_case_insensitive)
59
+ end
60
+
61
+ click_icon(:capture)
62
+
63
+ expect(page).not_to have_content('Cannot perform requested operation')
64
+ expect(page).to have_content('Payment Updated')
65
+ end
66
+ end
67
+
68
+ context "with invalid credit card data" do
69
+ include_context "with backend checkout setup"
70
+
71
+ # Attempt to submit an invalid form once
72
+ before do
73
+ visit "/admin/orders/#{order.number}/payments/new"
74
+ choose('Braintree')
75
+
76
+ within_frame("braintree-hosted-field-number") do
77
+ fill_in("credit-card-number", with: "1111111111111111")
78
+ end
79
+ within_frame("braintree-hosted-field-expirationDate") do
80
+ fill_in("expiration", with: expiration)
81
+ end
82
+ within_frame("braintree-hosted-field-cvv") do
83
+ fill_in("cvv", with: "123")
84
+ end
85
+
86
+ click_button "Update"
87
+ end
88
+
89
+ it "displays a meaningful error message" do
90
+ expect(page).to have_text(
91
+ "BraintreeError: Some payment input fields are invalid. Cannot tokenize invalid card fields."
92
+ )
93
+ end
94
+
95
+ # Same error should be produced when submitting an empty form again
96
+ context "when user tries to resubmit another invalid form", vcr: {
97
+ cassette_name: "admin/invalid_credit_card",
98
+ match_requests_on: [:braintree_uri]
99
+ } do
100
+ it "displays a meaningful error message" do
101
+ click_button "Update"
102
+ expect(page).to have_text(
103
+ "BraintreeError: Some payment input fields are invalid. Cannot tokenize invalid card fields."
104
+ )
105
+ end
106
+ end
107
+
108
+ # User should be able to checkout after submit fails once
109
+ context "when user enters valid data", vcr: {
110
+ cassette_name: "admin/resubmit_credit_card",
111
+ match_requests_on: [:braintree_uri]
112
+ } do
113
+ it "creates the payment successfully" do
114
+ within_frame("braintree-hosted-field-number") do
115
+ fill_in("credit-card-number", with: "4111111111111111")
116
+ end
117
+ within_frame("braintree-hosted-field-expirationDate") do
118
+ fill_in("expiration", with: expiration)
119
+ end
120
+ within_frame("braintree-hosted-field-cvv") do
121
+ fill_in("cvv", with: "123")
122
+ end
123
+ click_button("Update")
124
+
125
+ within('table#payments') do
126
+ expect(page).to have_content('Braintree')
127
+ expect(page).to have_content(pending_case_insensitive)
128
+ end
129
+
130
+ click_icon(:capture)
131
+
132
+ expect(page).not_to have_content('Cannot perform requested operation')
133
+ expect(page).to have_content('Payment Updated')
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,187 @@
1
+ require 'spec_helper'
2
+ require 'spree/testing_support/order_walkthrough'
3
+
4
+ shared_context "with frontend checkout setup" do
5
+ let(:braintree) { new_gateway(active: true) }
6
+ let!(:gateway) { create :payment_method }
7
+ let(:three_d_secure_enabled) { false }
8
+ let(:card_number) { "4111111111111111" }
9
+ let(:card_expiration) { "01/#{Time.now.utc.year + 2}" }
10
+
11
+ before do
12
+ braintree.save!
13
+
14
+ create(:store, payment_methods: [gateway, braintree]).tap do |store|
15
+ store.braintree_configuration.update!(
16
+ credit_card: true,
17
+ three_d_secure: three_d_secure_enabled
18
+ )
19
+
20
+ braintree.update(
21
+ preferred_credit_card_fields_style: { input: { 'font-size': '30px' } },
22
+ preferred_placeholder_text: { number: "Enter Your Card Number" }
23
+ )
24
+ end
25
+
26
+ order = if Spree.solidus_gem_version >= Gem::Version.new('2.6.0')
27
+ Spree::TestingSupport::OrderWalkthrough.up_to(:delivery)
28
+ else
29
+ OrderWalkthrough.up_to(:delivery)
30
+ end
31
+
32
+ user = create(:user)
33
+ order.user = user
34
+ order.number = "R9999999"
35
+ order.recalculate
36
+
37
+ allow_any_instance_of(Spree::CheckoutController).to receive_messages(current_order: order)
38
+ allow_any_instance_of(Spree::CheckoutController).to receive_messages(try_spree_current_user: user)
39
+ allow_any_instance_of(Spree::Payment).to receive(:number).and_return("123ABC")
40
+ allow_any_instance_of(SolidusPaypalBraintree::Source).to receive(:nonce).and_return("fake-valid-nonce")
41
+
42
+ visit spree.checkout_state_path(:delivery)
43
+ click_button "Save and Continue"
44
+ choose("Braintree")
45
+ end
46
+
47
+ around do |example|
48
+ Capybara.using_wait_time(20) { example.run }
49
+ end
50
+ end
51
+
52
+ describe 'entering credit card details', type: :feature, js: true do
53
+ context 'when page loads' do
54
+ include_context "with frontend checkout setup"
55
+
56
+ it "selectors display correctly" do
57
+ expect(page).to have_selector("#payment_method_#{braintree.id}", visible: :visible)
58
+ expect(page).to have_selector("iframe#braintree-hosted-field-number")
59
+ expect(page).to have_selector("iframe[type='number']")
60
+ end
61
+
62
+ it "credit card field style variable is set" do
63
+ within_frame("braintree-hosted-field-number") do
64
+ expect(find("#credit-card-number").style("font-size")).to eq({ "font-size" => "30px" })
65
+ end
66
+ end
67
+
68
+ it "sets the placeholder text correctly" do
69
+ within_frame("braintree-hosted-field-number") do
70
+ expect(find("#credit-card-number")['placeholder']).to eq("Enter Your Card Number")
71
+ end
72
+ end
73
+ end
74
+
75
+ context "with valid credit card data", vcr: {
76
+ cassette_name: 'checkout/valid_credit_card',
77
+ match_requests_on: [:braintree_uri]
78
+ } do
79
+ include_context "with frontend checkout setup"
80
+
81
+ before do
82
+ within_frame("braintree-hosted-field-number") do
83
+ fill_in("credit-card-number", with: card_number)
84
+ end
85
+ within_frame("braintree-hosted-field-expirationDate") do
86
+ fill_in("expiration", with: card_expiration)
87
+ end
88
+ within_frame("braintree-hosted-field-cvv") do
89
+ fill_in("cvv", with: "123")
90
+ end
91
+
92
+ click_button("Save and Continue")
93
+ end
94
+
95
+ it "checks out successfully" do
96
+ within("#order_details") do
97
+ expect(page).to have_content("CONFIRM")
98
+ end
99
+ click_button("Place Order")
100
+ expect(page).to have_content("Your order has been processed successfully")
101
+ end
102
+
103
+ context 'with 3D secure enabled' do
104
+ let(:three_d_secure_enabled) { true }
105
+ let(:card_number) { "4000000000000002" }
106
+
107
+ it 'checks out successfully' do
108
+ authenticate_3ds
109
+
110
+ within("#order_details") do
111
+ expect(page).to have_content("CONFIRM")
112
+ end
113
+
114
+ click_button("Place Order")
115
+ expect(page).to have_content("Your order has been processed successfully")
116
+ end
117
+
118
+ context 'with 3ds authentication error' do
119
+ let(:card_number) { "4000000000000028" }
120
+
121
+ it 'shows a 3ds authentication error' do
122
+ authenticate_3ds
123
+ expect(page).to have_content(
124
+ "3D Secure authentication failed. Please try again using a different payment method."
125
+ )
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ context "with invalid credit card data" do
132
+ include_context "with frontend checkout setup"
133
+
134
+ # Attempt to submit an empty form once
135
+ before do
136
+ click_button "Save and Continue"
137
+ end
138
+
139
+ it "displays an alert with a meaningful error message" do
140
+ expect(page).to have_text I18n.t("solidus_paypal_braintree.errors.empty_fields")
141
+ expect(page).to have_selector("input[type='submit']:enabled")
142
+ end
143
+
144
+ # Same error should be produced when submitting an empty form again
145
+ context "when user tries to resubmit an empty form", vcr: { cassette_name: "checkout/invalid_credit_card" } do
146
+ it "displays an alert with a meaningful error message" do
147
+ expect(page).to have_selector("input[type='submit']:enabled")
148
+
149
+ click_button "Save and Continue"
150
+ expect(page).to have_text I18n.t("solidus_paypal_braintree.errors.empty_fields")
151
+ end
152
+ end
153
+
154
+ # User should be able to checkout after submit fails once
155
+ context "when user enters valid data", vcr: {
156
+ cassette_name: "checkout/resubmit_credit_card",
157
+ match_requests_on: [:braintree_uri]
158
+ } do
159
+ it "allows them to resubmit and complete the purchase" do
160
+ within_frame("braintree-hosted-field-number") do
161
+ fill_in("credit-card-number", with: "4111111111111111")
162
+ end
163
+ within_frame("braintree-hosted-field-expirationDate") do
164
+ fill_in("expiration", with: card_expiration)
165
+ end
166
+ within_frame("braintree-hosted-field-cvv") do
167
+ fill_in("cvv", with: "123")
168
+ end
169
+ click_button("Save and Continue")
170
+ within("#order_details") do
171
+ expect(page).to have_content("CONFIRM")
172
+ end
173
+ click_button("Place Order")
174
+ expect(page).to have_content("Your order has been processed successfully")
175
+ end
176
+ end
177
+ end
178
+
179
+ def authenticate_3ds
180
+ within_frame("Cardinal-CCA-IFrame") do
181
+ within_frame("authWindow") do
182
+ fill_in("password", with: "1234")
183
+ click_button("Submit")
184
+ end
185
+ end
186
+ end
187
+ end