solidus_bolt 0.0.1

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 (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.