solidus_bolt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +41 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.github_changelog_generator +2 -0
  6. data/.gitignore +20 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +11 -0
  9. data/CHANGELOG.md +1 -0
  10. data/Gemfile +33 -0
  11. data/LICENSE +26 -0
  12. data/README.md +175 -0
  13. data/Rakefile +6 -0
  14. data/app/assets/images/bolt_logo_standard.png +0 -0
  15. data/app/assets/javascripts/authorize_account.js +74 -0
  16. data/app/assets/javascripts/solidus_bolt.js +91 -0
  17. data/app/assets/javascripts/spree/backend/solidus_bolt.js +4 -0
  18. data/app/assets/javascripts/spree/frontend/solidus_bolt.js +18 -0
  19. data/app/assets/stylesheets/spree/backend/solidus_bolt.css +4 -0
  20. data/app/assets/stylesheets/spree/frontend/solidus_bolt.css +4 -0
  21. data/app/controllers/solidus_bolt/accounts_controller.rb +17 -0
  22. data/app/controllers/solidus_bolt/base_controller.rb +21 -0
  23. data/app/controllers/solidus_bolt/webhooks_controller.rb +21 -0
  24. data/app/controllers/spree/admin/bolt_webhooks_controller.rb +34 -0
  25. data/app/controllers/spree/admin/bolts_controller.rb +43 -0
  26. data/app/decorators/controllers/solidus_bolt/spree_checkout_controller/add_addresses_to_bolt.rb +23 -0
  27. data/app/decorators/controllers/solidus_bolt/spree_checkout_controller/refresh_bolt_addresses.rb +17 -0
  28. data/app/decorators/controllers/solidus_bolt/spree_checkout_controller/refresh_bolt_payment_source.rb +17 -0
  29. data/app/decorators/models/solidus_bolt/address_decorator.rb +22 -0
  30. data/app/decorators/models/solidus_bolt/log_entry_decorator.rb +11 -0
  31. data/app/decorators/models/solidus_bolt/order_decorator.rb +44 -0
  32. data/app/decorators/models/solidus_bolt/payment_decorator.rb +11 -0
  33. data/app/decorators/omniauth/strategies/bolt_decorator.rb +16 -0
  34. data/app/jobs/solidus_bolt/add_address_job.rb +11 -0
  35. data/app/models/solidus_bolt/bolt_configuration.rb +74 -0
  36. data/app/models/solidus_bolt/gateway.rb +133 -0
  37. data/app/models/solidus_bolt/payment_method.rb +35 -0
  38. data/app/models/solidus_bolt/payment_source.rb +13 -0
  39. data/app/models/solidus_bolt.rb +7 -0
  40. data/app/overrides/spree/shared/_head/add_bolt_embed_script.html.erb.deface +6 -0
  41. data/app/services/solidus_bolt/accounts/add_address_service.rb +55 -0
  42. data/app/services/solidus_bolt/accounts/add_payment_method_service.rb +45 -0
  43. data/app/services/solidus_bolt/accounts/detail_service.rb +38 -0
  44. data/app/services/solidus_bolt/accounts/detect_account_service.rb +34 -0
  45. data/app/services/solidus_bolt/base_service.rb +55 -0
  46. data/app/services/solidus_bolt/oauth/token_service.rb +43 -0
  47. data/app/services/solidus_bolt/payments/capture_sync_service.rb +24 -0
  48. data/app/services/solidus_bolt/payments/credit_sync_service.rb +44 -0
  49. data/app/services/solidus_bolt/payments/void_sync_service.rb +18 -0
  50. data/app/services/solidus_bolt/server_error.rb +6 -0
  51. data/app/services/solidus_bolt/transactions/authorize_service.rb +72 -0
  52. data/app/services/solidus_bolt/transactions/base_service.rb +28 -0
  53. data/app/services/solidus_bolt/transactions/capture_service.rb +46 -0
  54. data/app/services/solidus_bolt/transactions/detail_service.rb +38 -0
  55. data/app/services/solidus_bolt/transactions/refund_service.rb +46 -0
  56. data/app/services/solidus_bolt/transactions/void_service.rb +44 -0
  57. data/app/services/solidus_bolt/users/refresh_access_token_service.rb +44 -0
  58. data/app/services/solidus_bolt/users/sync_addresses_service.rb +49 -0
  59. data/app/services/solidus_bolt/users/sync_payment_sources_service.rb +50 -0
  60. data/app/services/solidus_bolt/webhooks/create_service.rb +52 -0
  61. data/app/views/spree/admin/bolt_webhooks/new.html.erb +22 -0
  62. data/app/views/spree/admin/bolts/_configuration.html.erb +32 -0
  63. data/app/views/spree/admin/bolts/_form.html.erb +29 -0
  64. data/app/views/spree/admin/bolts/edit.html.erb +6 -0
  65. data/app/views/spree/admin/bolts/show.html.erb +21 -0
  66. data/app/views/spree/admin/payments/source_forms/_bolt.html.erb +1 -0
  67. data/app/views/spree/admin/payments/source_views/_bolt.html.erb +2 -0
  68. data/app/views/spree/api/payments/source_views/_bolt.json.jbuilder +4 -0
  69. data/app/views/spree/checkout/existing_payment/_bolt.html.erb +7 -0
  70. data/app/views/spree/checkout/payment/_bolt.html.erb +1 -0
  71. data/app/views/spree/shared/payment/_bolt.html.erb +19 -0
  72. data/app/webhooks/solidus_bolt/handlers/base_handler.rb +27 -0
  73. data/app/webhooks/solidus_bolt/handlers/capture_handler.rb +13 -0
  74. data/app/webhooks/solidus_bolt/handlers/credit_handler.rb +18 -0
  75. data/app/webhooks/solidus_bolt/handlers/void_handler.rb +11 -0
  76. data/app/webhooks/solidus_bolt/sorter.rb +33 -0
  77. data/bin/console +17 -0
  78. data/bin/rails +7 -0
  79. data/bin/rails-engine +13 -0
  80. data/bin/rails-sandbox +16 -0
  81. data/bin/rake +7 -0
  82. data/bin/sandbox +86 -0
  83. data/bin/setup +8 -0
  84. data/config/initializers/menu_items.rb +14 -0
  85. data/config/locales/en.yml +17 -0
  86. data/config/routes.rb +11 -0
  87. data/db/migrate/20220330094232_create_solidus_bolt_payment_sources.rb +16 -0
  88. data/db/migrate/20220413063328_create_solidus_bolt_bolt_configurations.rb +14 -0
  89. data/db/migrate/20220502005041_swap_url_for_env_boolean_in_bolt_configuration.rb +6 -0
  90. data/db/migrate/20220509102309_rework_solidus_bolt_payment_sources.rb +19 -0
  91. data/db/migrate/20220510075227_add_create_bolt_account_to_solidus_bolt_payment_source.rb +5 -0
  92. data/db/migrate/20220519233043_add_user_to_solidus_bolt_payment_source.rb +5 -0
  93. data/db/migrate/20220526005619_remove_user_id_from_solidus_bolt_payment_source.rb +5 -0
  94. data/db/migrate/20220530102107_rename_bolt_configuration_merchant_id_to_division_public_id.rb +5 -0
  95. data/db/migrate/20220531075527_update_bolt_configuration_environment_column_restrictions.rb +6 -0
  96. data/db/seeds.rb +30 -0
  97. data/lib/generators/solidus_bolt/install/install_generator.rb +78 -0
  98. data/lib/generators/solidus_bolt/install/templates/initializer.rb +8 -0
  99. data/lib/solidus_bolt/configuration.rb +19 -0
  100. data/lib/solidus_bolt/engine.rb +62 -0
  101. data/lib/solidus_bolt/testing_support/factories.rb +32 -0
  102. data/lib/solidus_bolt/version.rb +5 -0
  103. data/lib/solidus_bolt.rb +9 -0
  104. data/lib/tasks/db/seed/solidus_bolt.rake +14 -0
  105. data/lib/views/frontend/spree/shared/_login_bar_items.html.erb +18 -0
  106. data/solidus_bolt.gemspec +46 -0
  107. data/spec/decorators/models/solidus_bolt/address_decorator_spec.rb +24 -0
  108. data/spec/decorators/models/solidus_bolt/order_decorator_spec.rb +36 -0
  109. data/spec/decorators/models/solidus_bolt/payment_decorator_spec.rb +30 -0
  110. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddAddressService/_call/with_correct_access_token/receives_a_successful_response.yml +137 -0
  111. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddAddressService/_call/with_existing_address/skips_the_add_address_call.yml +82 -0
  112. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddAddressService/_call/with_wrong_access_token/gives_an_error.yml +55 -0
  113. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddPaymentMethodService/_call/with_correct_access_token/receives_a_successful_response.yml +186 -0
  114. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddPaymentMethodService/_call/with_wrong_access_token/gives_an_error.yml +179 -0
  115. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetailService/_call/with_correct_access_token/receives_a_successful_response.yml +55 -0
  116. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetailService/_call/with_wrong_access_token/gives_an_error.yml +55 -0
  117. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetectAccountService/_call/receives_the_correct_response.yml +50 -0
  118. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetectAccountService/_call/returns_status_200.yml +50 -0
  119. data/spec/fixtures/vcr_cassettes/SolidusBolt_Oauth_TokenService/_call/makes_the_API_call.yml +59 -0
  120. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_AuthorizeService/when_repeat_payment/_call/makes_the_API_call.yml +305 -0
  121. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_AuthorizeService/with_auto_capture/_call/makes_the_API_call.yml +307 -0
  122. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_AuthorizeService/without_auto_capture/_call/makes_the_API_call.yml +252 -0
  123. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_CaptureService/_call/makes_the_API_call.yml +242 -0
  124. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_DetailService/_call/makes_the_API_call.yml +244 -0
  125. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_RefundService/_call/makes_the_API_call.yml +296 -0
  126. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_VoidService/_call/makes_the_API_call.yml +242 -0
  127. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_VoidService/_call/when_transaction_id_is_missing/makes_the_API_call.yml +242 -0
  128. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_VoidService/_call/when_transaction_reference_is_missing/makes_the_API_call.yml +242 -0
  129. data/spec/fixtures/vcr_cassettes/SolidusBolt_Users_SyncAddressesService/_call/adds_the_bill_address_to_the_user.yml +165 -0
  130. data/spec/fixtures/vcr_cassettes/SolidusBolt_Users_SyncAddressesService/_call/adds_the_ship_address_to_the_user.yml +165 -0
  131. data/spec/fixtures/vcr_cassettes/SolidusBolt_Users_SyncPaymentSourcesService/_call/creates_a_new_payment_source_with_card_ID.yml +55 -0
  132. data/spec/fixtures/vcr_cassettes/SolidusBolt_Webhooks_CreateService/_call/with_all_event/returns_a_webhook_id.yml +54 -0
  133. data/spec/fixtures/vcr_cassettes/SolidusBolt_Webhooks_CreateService/_call/with_an_event/returns_a_webhook_id.yml +54 -0
  134. data/spec/fixtures/vcr_cassettes/SolidusBolt_Webhooks_CreateService/_call/with_empty_event/raises_a_server_error.yml +57 -0
  135. data/spec/jobs/solidus_bolt/add_address_job_spec.rb +18 -0
  136. data/spec/models/solidus_bolt/bolt_configuration_spec.rb +173 -0
  137. data/spec/models/solidus_bolt/gateway_spec.rb +130 -0
  138. data/spec/models/solidus_bolt/payment_method_spec.rb +21 -0
  139. data/spec/models/solidus_bolt/payment_source_spec.rb +22 -0
  140. data/spec/requests/solidus_bolt/accounts_controller_spec.rb +41 -0
  141. data/spec/requests/solidus_bolt/webhooks_controller_spec.rb +122 -0
  142. data/spec/requests/spree/admin/bolt_spec.rb +71 -0
  143. data/spec/requests/spree/admin/bolt_webhook_spec.rb +35 -0
  144. data/spec/requests/spree/checkout_controller_spec.rb +117 -0
  145. data/spec/services/solidus_bolt/accounts/add_address_service_spec.rb +45 -0
  146. data/spec/services/solidus_bolt/accounts/add_payment_method_service_spec.rb +47 -0
  147. data/spec/services/solidus_bolt/accounts/detail_service_spec.rb +32 -0
  148. data/spec/services/solidus_bolt/accounts/detect_account_service_spec.rb +15 -0
  149. data/spec/services/solidus_bolt/base_service_spec.rb +49 -0
  150. data/spec/services/solidus_bolt/oauth/token_service_spec.rb +15 -0
  151. data/spec/services/solidus_bolt/payments/capture_sync_service_spec.rb +27 -0
  152. data/spec/services/solidus_bolt/payments/credit_sync_service_spec.rb +38 -0
  153. data/spec/services/solidus_bolt/payments/void_sync_service_spec.rb +25 -0
  154. data/spec/services/solidus_bolt/transactions/authorize_service_spec.rb +117 -0
  155. data/spec/services/solidus_bolt/transactions/base_service_spec.rb +38 -0
  156. data/spec/services/solidus_bolt/transactions/capture_service_spec.rb +37 -0
  157. data/spec/services/solidus_bolt/transactions/detail_service_spec.rb +31 -0
  158. data/spec/services/solidus_bolt/transactions/refund_service_spec.rb +42 -0
  159. data/spec/services/solidus_bolt/transactions/void_service_spec.rb +70 -0
  160. data/spec/services/solidus_bolt/users/refresh_access_token_service_spec.rb +45 -0
  161. data/spec/services/solidus_bolt/users/sync_addresses_service_spec.rb +74 -0
  162. data/spec/services/solidus_bolt/users/sync_payment_sources_service_spec.rb +25 -0
  163. data/spec/services/solidus_bolt/webhooks/create_service_spec.rb +33 -0
  164. data/spec/spec_helper.rb +37 -0
  165. data/spec/support/bolt_configuration.rb +26 -0
  166. data/spec/support/bolt_helper.rb +66 -0
  167. data/spec/support/vcr.rb +29 -0
  168. data/spec/webhooks/solidus_bolt/handlers/base_handler_spec.rb +13 -0
  169. data/spec/webhooks/solidus_bolt/handlers/capture_handler_spec.rb +24 -0
  170. data/spec/webhooks/solidus_bolt/handlers/credit_handler_spec.rb +32 -0
  171. data/spec/webhooks/solidus_bolt/handlers/void_handler_spec.rb +19 -0
  172. data/spec/webhooks/solidus_bolt/sorter_spec.rb +39 -0
  173. metadata +492 -0
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SolidusBolt::Gateway, type: :model do
4
+ let(:order) { create(:order_with_line_items) }
5
+ let(:amount) { (order.total * 100).to_i }
6
+ let(:payment_method) { create(:bolt_payment_method) }
7
+ let(:payment_source) { create(:bolt_payment_source, payment_method: payment_method) }
8
+ let(:payment) {
9
+ create(:payment, order: order, source_id: payment_source.id, source_type: SolidusBolt::PaymentSource,
10
+ payment_method: payment_method)
11
+ }
12
+ let(:gateway_options) {
13
+ {
14
+ order_id: "#{order.number}-123456",
15
+ originator: payment
16
+ }
17
+ }
18
+
19
+ describe '#authorize' do
20
+ subject(:authorize) { described_class.new.authorize(nil, payment_source, gateway_options) }
21
+
22
+ let(:response) { { 'transaction' => { 'reference' => 'fakereference', 'from_credit_card' => { 'id' => '1234' } } } }
23
+
24
+ before { allow(SolidusBolt::Transactions::AuthorizeService).to receive(:call).and_return(response) }
25
+
26
+ it 'updates the card_id of the payment_source' do
27
+ authorize
28
+ expect(payment_source.reload.card_id).to eq('1234')
29
+ end
30
+
31
+ it 'returns an active merchant billing response' do
32
+ expect(authorize).to be_an_instance_of(ActiveMerchant::Billing::Response)
33
+ end
34
+
35
+ it 'stores the transaction reference as response code' do
36
+ expect(authorize.authorization).to eq('fakereference')
37
+ end
38
+ end
39
+
40
+ describe '#capture' do
41
+ subject(:capture) { described_class.new.capture(amount, response_code, gateway_options) }
42
+
43
+ let(:response_code) { 'the_amazing_spiderman' }
44
+ let(:response) {
45
+ { 'reference' => response_code }
46
+ }
47
+
48
+ before do
49
+ allow(SolidusBolt::Transactions::CaptureService).to receive(:call).and_return(response)
50
+ end
51
+
52
+ it 'returns an active merchant billing response' do
53
+ expect(capture).to be_an_instance_of(ActiveMerchant::Billing::Response)
54
+ end
55
+
56
+ it 'stores the transaction reference as response code' do
57
+ expect(capture.authorization).to eq response_code
58
+ end
59
+ end
60
+
61
+ describe '#void' do
62
+ subject(:void) { described_class.new.void(response_code, gateway_options) }
63
+
64
+ let(:response_code) { 'the_amazing_spiderman' }
65
+
66
+ let(:response) {
67
+ { 'id' => "id-#{response_code}", 'reference' => response_code }
68
+ }
69
+
70
+ before do
71
+ allow(SolidusBolt::Transactions::VoidService).to receive(:call).and_return(response)
72
+ end
73
+
74
+ it 'returns an active merchant billing response' do
75
+ expect(void).to be_an_instance_of(ActiveMerchant::Billing::Response)
76
+ end
77
+
78
+ it 'stores the transaction reference as response code' do
79
+ expect(void.authorization).to eq response_code
80
+ end
81
+ end
82
+
83
+ describe '#credit' do
84
+ subject(:credit) { described_class.new.credit(amount, response_code, gateway_options) }
85
+
86
+ let(:gateway_options) { { originator: Spree::Refund.new(payment_id: payment.id, amount: payment.amount) } }
87
+ let(:response_code) { 'the_amazing_spiderman' }
88
+
89
+ # Since reference returned by Refund API Call is different
90
+ # from the reference for the original transaction, the refernce has been
91
+ # randomised in the response here
92
+ let(:response) {
93
+ { 'reference' => SecureRandom.hex }
94
+ }
95
+
96
+ before do
97
+ allow(SolidusBolt::Transactions::RefundService).to receive(:call).and_return(response)
98
+ payment.update(response_code: response_code)
99
+ end
100
+
101
+ it 'returns an active merchant billing response' do
102
+ expect(credit).to be_an_instance_of(ActiveMerchant::Billing::Response)
103
+ end
104
+
105
+ it 'stores the transaction reference as response code' do
106
+ expect(credit.authorization).to eq response['reference']
107
+ end
108
+ end
109
+
110
+ describe '#purchase' do
111
+ subject(:purchase) { described_class.new.purchase(nil, payment_source, gateway_options) }
112
+
113
+ let(:response) { { 'transaction' => { 'reference' => 'fakereference', 'from_credit_card' => { 'id' => '1234' } } } }
114
+
115
+ before { allow(SolidusBolt::Transactions::AuthorizeService).to receive(:call).and_return(response) }
116
+
117
+ it 'updates the card_id of the payment_source' do
118
+ purchase
119
+ expect(payment_source.reload.card_id).to eq('1234')
120
+ end
121
+
122
+ it 'returns an active merchant billing response' do
123
+ expect(purchase).to be_an_instance_of(ActiveMerchant::Billing::Response)
124
+ end
125
+
126
+ it 'stores the transaction reference as response code' do
127
+ expect(purchase.authorization).to eq('fakereference')
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SolidusBolt::PaymentMethod, type: :model do
4
+ describe '#gateway_class' do
5
+ it 'has correct gateway class' do
6
+ expect(described_class.new.gateway_class).to eq SolidusBolt::Gateway
7
+ end
8
+ end
9
+
10
+ describe '#payment_source_class' do
11
+ it 'has correct payment_source class' do
12
+ expect(described_class.new.payment_source_class).to eq SolidusBolt::PaymentSource
13
+ end
14
+ end
15
+
16
+ describe '#partial_name' do
17
+ it 'has correct partial name' do
18
+ expect(described_class.new.partial_name).to eq 'bolt'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SolidusBolt::PaymentSource, type: :model do
4
+ let(:payment_source) { build(:bolt_payment_source) }
5
+
6
+ describe 'validations' do
7
+ context 'with payment_method_id present' do
8
+ it 'is valid' do
9
+ expect(payment_source.valid?).to be(true)
10
+ end
11
+ end
12
+
13
+ context 'with payment_method_id absent' do
14
+ before { payment_source.payment_method_id = nil }
15
+
16
+ it 'is invalid' do
17
+ expect(payment_source.valid?).to be(false)
18
+ expect(payment_source.errors.messages.first.to_a).to eq([:payment_method_id, ["can't be blank"]])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::AccountsController, type: :request do
6
+ describe '#create' do
7
+ subject(:call) do
8
+ post '/api/accounts/bolt', params: params, headers: { 'X-Bolt-Hmac-Sha256' => bolt_hash }, as: :json
9
+ end
10
+
11
+ let(:user) { create(:user, email: 'user@bolt.com') }
12
+ let(:params) { { account: { email: user.email } } }
13
+
14
+ before { call }
15
+
16
+ context 'when valid' do
17
+ let(:bolt_hash) { "+mDzzN0xsvB0UzO0NoAyMJYx/byPs++cccpR4tiEN0c=" }
18
+
19
+ it 'has http status ok' do
20
+ expect(response).to have_http_status(:ok)
21
+ end
22
+ end
23
+
24
+ context 'when not valid' do
25
+ let(:bolt_hash) { "CaAA/XZsO4wl6q/G7cyWY9KVcaWvieH7UWM6XoFcsmU=" }
26
+ let(:params) { { account: { email: 'fake@email.com' } } }
27
+
28
+ it 'has http status not found' do
29
+ expect(response).to have_http_status(:not_found)
30
+ end
31
+ end
32
+
33
+ context 'when not bolt request' do
34
+ let(:bolt_hash) { 'notBoltHash' }
35
+
36
+ it 'has http status unauthorized' do
37
+ expect(response).to have_http_status(:unauthorized)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::WebhooksController, type: :request do
6
+ describe '#update' do
7
+ subject(:endpoint_call) do
8
+ post '/webhooks/bolt', params: params, headers: { 'X-Bolt-Hmac-Sha256' => bolt_hash }, as: :json
9
+ end
10
+
11
+ let(:bolt_hash) { "IvjuqQlACvmK3zaBqfMZI+9rf8ukq7VT2Sgjo+nVwl4=" }
12
+ let(:params) { { webhook: {} } }
13
+ let(:payment) { create(:bolt_payment, response_code: 'V2YW-NYNR-2MYM') }
14
+
15
+ context 'when valid' do
16
+ let(:expected_params) { ActionController::Parameters.new({}) }
17
+
18
+ before do
19
+ allow(::SolidusBolt::Sorter).to receive(:call)
20
+ endpoint_call
21
+ end
22
+
23
+ it 'has http status success' do
24
+ expect(response).to have_http_status(:success)
25
+ end
26
+
27
+ it 'calls the webhook sorter with the correct params' do
28
+ expect(SolidusBolt::Sorter).to have_received(:call).with(expected_params)
29
+ end
30
+ end
31
+
32
+ context 'when webhook type is `capture`' do
33
+ let(:params) do
34
+ {
35
+ webhook: {
36
+ type: 'capture',
37
+ data: { reference: payment.response_code, captures: [{ amount: { amount: 1000 } }] }
38
+ }
39
+ }
40
+ end
41
+ let(:bolt_hash) { "UlgQupKN61uY+5G126Ue0CvOPLtuHux36GZrAA1LKyo=" }
42
+
43
+ before do
44
+ allow(SolidusBolt::Payments::CaptureSyncService).to receive(:call).with(payment: payment, capture_amount: 1000)
45
+ endpoint_call
46
+ end
47
+
48
+ it 'calls the Sorter, which calls the CaptureHandler, which calls the CaptureSyncService with params' do
49
+ expect(SolidusBolt::Payments::CaptureSyncService).to have_received(:call).with(
50
+ payment: payment, capture_amount: 1000
51
+ )
52
+ end
53
+ end
54
+
55
+ context 'when webhook type is `void`' do
56
+ let(:params) { { webhook: { type: 'void', data: { reference: payment.response_code } } } }
57
+ let(:bolt_hash) { "W4+7RvJLQaBkLdddmCnAy59QFPrF3No2olkTcfdNmVE=" }
58
+
59
+ before do
60
+ allow(SolidusBolt::Payments::VoidSyncService).to receive(:call).with(payment: payment)
61
+ endpoint_call
62
+ end
63
+
64
+ it 'calls the Sorter, which calls the VoidHandler, which calls the VoidSyncService with params' do
65
+ expect(SolidusBolt::Payments::VoidSyncService).to have_received(:call).with(payment: payment)
66
+ end
67
+ end
68
+
69
+ context 'when webhook type is `credit`' do
70
+ let(:transaction_id) { 'AAAA-BBBB-CCCC' }
71
+ let(:params) do
72
+ {
73
+ webhook: {
74
+ type: 'credit',
75
+ data: {
76
+ reference: transaction_id,
77
+ source_transaction: { reference: payment.response_code },
78
+ requested_refund_amount: { amount: 1000 }
79
+ }
80
+ }
81
+ }
82
+ end
83
+ let(:bolt_hash) { "My7opJkmglzXpNi1rn/UDmPeaeDHoSm2ebuWwBYJrW0=" }
84
+
85
+ before do
86
+ allow(SolidusBolt::Payments::CreditSyncService).to receive(:call).with(
87
+ payment: payment, amount: 1000, transaction_id: transaction_id
88
+ )
89
+ endpoint_call
90
+ end
91
+
92
+ it 'calls the Sorter, which calls the CreditHandler, which calls the CreditSyncService with params' do
93
+ expect(SolidusBolt::Payments::CreditSyncService).to have_received(:call).with(
94
+ payment: payment, amount: 1000, transaction_id: transaction_id
95
+ )
96
+ end
97
+ end
98
+
99
+ context 'when not valid' do
100
+ before do
101
+ allow(::SolidusBolt::Sorter).to receive(:call).and_raise(StandardError)
102
+ endpoint_call
103
+ end
104
+
105
+ it 'has http status unprocessable entity' do
106
+ expect(response).to have_http_status(:unprocessable_entity)
107
+ end
108
+ end
109
+
110
+ context 'when not bolt request' do
111
+ let(:bolt_hash) { 'notBoltHash' }
112
+
113
+ before do
114
+ endpoint_call
115
+ end
116
+
117
+ it 'has http status unauthorized' do
118
+ expect(response).to have_http_status(:unauthorized)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "Spree::Admin::Bolts", type: :request do
4
+ stub_authorization!
5
+
6
+ let(:bolt_configuration_params) {
7
+ {
8
+ bearer_token: SecureRandom.hex,
9
+ environment: 'sandbox',
10
+ merchant_public_id: SecureRandom.hex,
11
+ division_public_id: SecureRandom.hex,
12
+ api_key: SecureRandom.hex,
13
+ signing_secret: SecureRandom.hex,
14
+ publishable_key: SecureRandom.hex
15
+ }
16
+ }
17
+
18
+ describe "GET /show" do
19
+ it 'returns a successful response' do
20
+ get '/admin/bolt'
21
+ expect(response.status).to eq 200
22
+ end
23
+
24
+ it 'creates a new SolidusBolt::BoltConfiguration record if no records are present' do
25
+ expect { get '/admin/bolt' }.to change { SolidusBolt::BoltConfiguration.count }.by(1)
26
+ end
27
+ end
28
+
29
+ describe "GET /edit" do
30
+ let(:bolt_configuration) { create(:bolt_configuration) }
31
+
32
+ it 'returns a successful response' do
33
+ get '/admin/bolt/edit'
34
+ expect(response.status).to eq 200
35
+ end
36
+ end
37
+
38
+ describe "PUT /update" do
39
+ subject(:request) {
40
+ put '/admin/bolt', params: { solidus_bolt_bolt_configuration: bolt_configuration_params }
41
+ }
42
+
43
+ let(:bolt_configuration) { create(:bolt_configuration) }
44
+
45
+ it 'successfully redirects' do
46
+ request
47
+ expect(response.status).to eq 302
48
+ end
49
+
50
+ it 'redirects to index' do
51
+ request
52
+ expect(response).to redirect_to '/admin/bolt'
53
+ end
54
+
55
+ it 'successfully updates the bolt configuration' do
56
+ request
57
+
58
+ updated_attributes = SolidusBolt::BoltConfiguration.fetch.attributes.slice(
59
+ 'bearer_token',
60
+ 'environment',
61
+ 'merchant_public_id',
62
+ 'division_public_id',
63
+ 'api_key',
64
+ 'signing_secret',
65
+ 'publishable_key'
66
+ )
67
+
68
+ expect(updated_attributes).to eq(bolt_configuration_params.deep_stringify_keys)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "Spree::Admin::BoltWebhooks", type: :request do
4
+ stub_authorization!
5
+
6
+ describe "GET /new" do
7
+ it 'returns a successful response' do
8
+ get '/admin/bolt_webhook/new'
9
+ expect(response.status).to eq 200
10
+ end
11
+ end
12
+
13
+ describe "POST /create" do
14
+ subject(:request) {
15
+ post '/admin/bolt_webhook', params: { bolt_webhook: params }
16
+ }
17
+
18
+ let(:params) { { event: 'all', webhook_url: 'https://solidus-test.com/webhook' } }
19
+
20
+ before do
21
+ allow(SolidusBolt::Webhooks::CreateService).to receive(:call).and_return({ 'webhook_id' => 'BOLT_WEBHOOK_ID' })
22
+ end
23
+
24
+ it 'calls the correct service' do
25
+ request
26
+ expect(
27
+ SolidusBolt::Webhooks::CreateService
28
+ )
29
+ .to have_received(:call)
30
+ .with(
31
+ { event: 'all', url: 'https://solidus-test.com/webhook' }
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "Spree::CheckoutController", type: :request do
4
+ stub_authorization!
5
+
6
+ let(:user) { create(:user_with_addresses) }
7
+
8
+ before do
9
+ # rubocop:disable RSpec/AnyInstance
10
+ allow_any_instance_of(Spree::CheckoutController).to receive(:current_order).and_return(order)
11
+ allow_any_instance_of(Spree::CheckoutController).to receive(:spree_current_user).and_return(user)
12
+ allow_any_instance_of(SolidusBolt::BoltConfiguration).to(
13
+ receive(:embed_js).and_return('https://connect-sandbox.bolt.com/embed.js')
14
+ )
15
+ # rubocop:enable RSpec/AnyInstance
16
+ end
17
+
18
+ describe "GET /checkout/address" do
19
+ let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:address) }
20
+
21
+ before { allow(SolidusBolt::Users::SyncAddressesService).to receive(:call) }
22
+
23
+ it 'returns a successful response' do
24
+ get '/checkout/address'
25
+
26
+ expect(response.status).to eq 200
27
+ end
28
+
29
+ it 'calls the service to sync addresses' do
30
+ get '/checkout/address'
31
+
32
+ expect(SolidusBolt::Users::SyncAddressesService).to have_received(:call)
33
+ end
34
+ end
35
+
36
+ describe "GET /checkout/payment" do
37
+ let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
38
+
39
+ before { allow(SolidusBolt::Users::SyncPaymentSourcesService).to receive(:call) }
40
+
41
+ it 'returns a successful response' do
42
+ get '/checkout/payment'
43
+
44
+ expect(response.status).to eq 200
45
+ end
46
+
47
+ it 'calls the service to sync payment sources' do
48
+ get '/checkout/payment'
49
+
50
+ expect(SolidusBolt::Users::SyncPaymentSourcesService).to have_received(:call)
51
+ end
52
+ end
53
+
54
+ describe 'PATCH /checkout/update/confirm' do
55
+ subject(:confirm_order) { patch '/checkout/update/confirm' }
56
+
57
+ let(:order) { FactoryBot.create(:order_with_totals) }
58
+ let(:payment) { create(:payment, amount: order.total, order: order) }
59
+ let(:session) { { bolt_access_token: access_token } }
60
+ let(:access_token) { nil }
61
+
62
+ before do
63
+ # use test preparation from solidusio/solidus/frontend/spec/controllers/spree/checkout_controller_spec.rb
64
+ # because Spree::TestingSupport::OrderWalkthrough.up_to(:confirm) doesn't work
65
+ order.update! user: user
66
+ order.update(state: 'confirm')
67
+ payment
68
+ order.create_proposed_shipments
69
+ order.payments.reload
70
+
71
+ # request calls Gateway#authorize - need to stub it to test our action
72
+ allow(SolidusBolt::Transactions::AuthorizeService).to(receive(:call).and_return({
73
+ 'transaction' => { 'from_credit_card' => { 'id' => 'CreditCardId' } }
74
+ }))
75
+
76
+ # rubocop:disable RSpec/AnyInstance
77
+ allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(session)
78
+ # rubocop:enable RSpec/AnyInstance
79
+ end
80
+
81
+ it 'redirects to completion route' do
82
+ confirm_order
83
+ expect(response).to redirect_to spree.order_path(order)
84
+ end
85
+
86
+ context 'with logged in Bolt user and Bolt payment' do
87
+ let(:access_token) { 'accesstoken' }
88
+ let(:payment) { create(:bolt_payment, amount: order.total, order: order) }
89
+
90
+ before { allow(SolidusBolt::Users::RefreshAccessTokenService).to receive(:call).and_return(access_token) }
91
+
92
+ it 'calls the job to add addresses' do
93
+ expect { confirm_order }.to(have_enqueued_job(SolidusBolt::AddAddressJob).twice.with { |hash|
94
+ expect(hash[:order]).to eq(order)
95
+ expect(hash[:access_token]).to eq(access_token)
96
+ expect(user.addresses).to include(hash[:address])
97
+ })
98
+ end
99
+ end
100
+
101
+ context 'with logged in Bolt user' do
102
+ let(:access_token) { 'accesstoken' }
103
+
104
+ it 'skips the job call to add addresses' do
105
+ expect { confirm_order }.not_to have_enqueued_job(SolidusBolt::AddAddressJob)
106
+ end
107
+ end
108
+
109
+ context 'with Bolt payment' do
110
+ let(:payment) { create(:bolt_payment, amount: order.total, order: order) }
111
+
112
+ it 'skips the job call to add addresses' do
113
+ expect { confirm_order }.not_to have_enqueued_job(SolidusBolt::AddAddressJob)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Accounts::AddAddressService, :vcr, :bolt_configuration do
6
+ describe '#call', vcr: true do
7
+ subject(:add_address) { described_class.call(access_token: access_token, order: order, address: address) }
8
+
9
+ let(:order) { build(:order) }
10
+ let(:address) { order.bill_address }
11
+
12
+ context 'with wrong access_token' do
13
+ let(:access_token) { 'Bolt Access Token' }
14
+
15
+ it 'gives an error' do
16
+ expect{ add_address }.to raise_error(SolidusBolt::ServerError, 'This action is forbidden.')
17
+ end
18
+ end
19
+
20
+ context 'with correct access_token' do
21
+ let(:access_token) { ENV['BOLT_ACCESS_TOKEN'] }
22
+ let(:address) { build(:address, address1: '6420') }
23
+
24
+ it 'receives a successful response' do
25
+ expect(add_address).to match hash_including('id')
26
+ expect(add_address['street_address1']).to eq(address.address1)
27
+ end
28
+ end
29
+
30
+ context 'with existing address' do
31
+ let(:access_token) { ENV['BOLT_ACCESS_TOKEN'] }
32
+
33
+ let(:address) { build(:address, address1: 'PO Box 1337', zipcode: '10001') }
34
+
35
+ it 'skips the add_address call' do
36
+ expect(add_address).to be(nil)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ # This spec depends on the bolt_access_token that needs to be generated manually
43
+ # and added to the environment variable BOLT_ACCESS_TOKEN.
44
+ # Generate a new access token by logging into an existing user bolt account on
45
+ # development environment and copying the token from the session['bolt_access_token'] key.
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Accounts::AddPaymentMethodService, :vcr, :bolt_configuration do
6
+ describe '#call', vcr: true do
7
+ subject(:add_payment_method) do
8
+ described_class.call(
9
+ access_token: access_token, credit_card: credit_card_payload, address: address, email: 'example@email.com'
10
+ )
11
+ end
12
+
13
+ let(:address) { build(:address) }
14
+ let(:card_number) { '4111111111111004' }
15
+ let(:credit_card_payload) do
16
+ tokenize_credit_card(credit_card_number: card_number, cvv: '111')
17
+ .merge(
18
+ number: card_number,
19
+ expiration: (Time.current + 1.year).strftime('%Y-%m'),
20
+ token_type: 'bolt',
21
+ postal_code: address.zipcode
22
+ )
23
+ end
24
+
25
+ context 'with wrong access_token' do
26
+ let(:access_token) { 'Bolt Access Token' }
27
+
28
+ it 'gives an error' do
29
+ expect{ add_payment_method }.to raise_error(SolidusBolt::ServerError, 'This action is forbidden.')
30
+ end
31
+ end
32
+
33
+ context 'with correct access_token' do
34
+ let(:access_token) { ENV['BOLT_ACCESS_TOKEN'] }
35
+
36
+ it 'receives a successful response' do
37
+ expect(add_payment_method).to match hash_including('id')
38
+ expect(add_payment_method['last4']).to eq('1004')
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # This spec depends on the bolt_access_token that needs to be generated manually
45
+ # and added to the environment variable BOLT_ACCESS_TOKEN.
46
+ # Generate a new access token by logging into an existing user bolt account on
47
+ # development environment and copying the token from the session['bolt_access_token'] key.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Accounts::DetailService, :vcr, :bolt_configuration do
6
+ describe '#call', vcr: true do
7
+ subject(:detail) { described_class.call(access_token: access_token) }
8
+
9
+ context 'with wrong access_token' do
10
+ let(:access_token) { 'Bolt Access Token' }
11
+
12
+ it 'gives an error' do
13
+ expect{ detail }.to raise_error(SolidusBolt::ServerError, 'This action is forbidden.')
14
+ end
15
+ end
16
+
17
+ context 'with correct access_token' do
18
+ let(:access_token) { ENV['BOLT_ACCESS_TOKEN'] }
19
+
20
+ it 'receives a successful response' do
21
+ expect(detail).to match hash_including('profile')
22
+ expect(detail).to match hash_including('addresses')
23
+ expect(detail).to match hash_including('payment_methods')
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # This spec depends on the bolt_access_token that needs to be generated manually
30
+ # and added to the environment variable BOLT_ACCESS_TOKEN.
31
+ # Generate a new access token by logging into an existing user bolt account on
32
+ # development environment and copying the token from the session['bolt_access_token'] key.