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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Accounts::DetectAccountService, :vcr, :bolt_configuration do
6
+ subject(:api) { described_class.new(email: email) }
7
+
8
+ let(:email) { 'bolt@bolt.com' }
9
+
10
+ describe '#call', vcr: true do
11
+ it 'receives the correct response' do
12
+ expect(api.call).to eq({ "has_bolt_account" => false })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::BaseService do
6
+ let!(:bolt_configuration) { create(:bolt_configuration) }
7
+
8
+ describe '#call' do
9
+ subject(:service) { described_class.new.call }
10
+
11
+ it 'raises a not implemented error' do
12
+ expect { service }.to raise_error(::NotImplementedError)
13
+ end
14
+ end
15
+
16
+ describe '#api_base_url' do
17
+ it 'returns correct url' do
18
+ expect(described_class.new.send(:api_base_url)).to eq bolt_configuration.environment_url
19
+ end
20
+ end
21
+
22
+ describe '#api_version' do
23
+ it 'returns correct api version' do
24
+ expect(described_class.new.send(:api_version)).to eq 'v1'
25
+ end
26
+ end
27
+
28
+ describe '#authentication_header' do
29
+ it 'returns the correct hash' do
30
+ expect(
31
+ described_class.new.send(:authentication_header)
32
+ ).to eq({ 'X-API-KEY' => bolt_configuration.api_key })
33
+ end
34
+ end
35
+
36
+ describe '#generate_nonce' do
37
+ it 'returns a unique nonce everytime' do
38
+ nonce = described_class.new.send(:generate_nonce)
39
+ expect(described_class.new.send(:generate_nonce)).not_to eq(nonce)
40
+ end
41
+ end
42
+
43
+ describe '#publishable_key' do
44
+ it 'returns the correct key' do
45
+ publishable_key = described_class.new.send(:publishable_key)
46
+ expect(described_class.new.send(:publishable_key)).to eq(publishable_key)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Oauth::TokenService, :vcr, :bolt_configuration do
6
+ subject(:api) {
7
+ described_class.new(authorization_code: 'Bolt Authorization Code', scope: 'openid bolt.account.manage')
8
+ }
9
+
10
+ describe '#call', vcr: true do
11
+ it 'makes the API call' do
12
+ expect { api.call }.to raise_error SolidusBolt::ServerError
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Payments::CaptureSyncService do
6
+ subject(:capture) do
7
+ described_class.call(payment: payment, capture_amount: capture_amount)
8
+ end
9
+
10
+ let(:payment) { create(:bolt_payment, amount: 15.00) }
11
+ let(:capture_amount) { 1000 }
12
+
13
+ describe '#call' do
14
+ it 'completes the payment' do
15
+ expect { capture }.to change { payment.reload.state }.from('checkout').to('completed')
16
+ end
17
+
18
+ it 'updates the payment amount' do
19
+ expect { capture }.to change { payment.reload.amount }.from(15.00).to(10.00)
20
+ end
21
+
22
+ it 'creates a capture event' do
23
+ capture
24
+ expect(payment.reload.capture_events.count).to eq(1)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Payments::CreditSyncService do
6
+ subject(:credit) { described_class.call(payment: payment, amount: amount, transaction_id: '123') }
7
+
8
+ let(:payment) { create(:bolt_payment, state: 'completed', amount: 10.0, order: create(:completed_order_with_totals)) }
9
+ let(:amount) { (payment.amount * 100).to_i }
10
+
11
+ describe '#call' do
12
+ before { create(:refund_reason, name: 'Return processing', mutable: false) }
13
+
14
+ context 'when not refunded already' do
15
+ it 'creates a new refund' do
16
+ credit
17
+ expect(payment.reload.refunds.count).to eq(1)
18
+ end
19
+
20
+ it 'creates a refund for the passed amount' do
21
+ credit
22
+ expect(payment.reload.refunds.first.amount).to eq(amount.to_f / 100)
23
+ end
24
+
25
+ it 'recalculates the order amount' do
26
+ expect { credit }.to change { payment.reload.order.payment_total }.from(10.0).to(0.0)
27
+ end
28
+ end
29
+
30
+ context 'when already refunded' do
31
+ before { create(:refund, transaction_id: '123') }
32
+
33
+ it 'silently fails' do
34
+ expect(credit).to be_nil
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Payments::VoidSyncService do
6
+ subject(:void) { described_class.call(payment: payment) }
7
+
8
+ let(:payment) { create(:bolt_payment) }
9
+
10
+ describe '#call' do
11
+ context 'when non-void payment' do
12
+ it 'voids the payment' do
13
+ expect { void }.to change { payment.reload.state }.from('checkout').to('void')
14
+ end
15
+ end
16
+
17
+ context 'when void payment' do
18
+ before { payment.update(state: 'void') }
19
+
20
+ it 'returns nil' do
21
+ expect(void).to be_nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Transactions::AuthorizeService, :vcr, :bolt_configuration do
6
+ let(:credit_card_payload) do
7
+ tokenize_credit_card(credit_card_number: '4111111111111004', cvv: '111').merge(
8
+ expiration: (Time.current + 1.year).strftime('%Y-%m'),
9
+ token_type: 'bolt'
10
+ )
11
+ end
12
+ let(:order) { create(:order_with_line_items) }
13
+ let(:payment_method) { create(:bolt_payment_method) }
14
+
15
+ context 'without auto capture' do
16
+ subject(:authorize) do
17
+ described_class.call(
18
+ order: order, credit_card: credit_card_payload, create_bolt_account: false, payment_method: payment_method
19
+ )
20
+ end
21
+
22
+ describe '#call' do
23
+ it 'makes the API call' do
24
+ response = authorize
25
+
26
+ expect(response).to match hash_including('transaction')
27
+ expect(response['transaction']['status']).to eq('pending')
28
+
29
+ # The Authorize Call with auto_capture takes some time to update the state of the transaction from
30
+ # pending to completed, hence the sleep is necessary to get the correct output from the API Call.
31
+
32
+ sleep(1)
33
+
34
+ reference = response['transaction']['reference']
35
+ transaction_details = SolidusBolt::Transactions::DetailService.call(
36
+ transaction_reference: reference, payment_method: payment_method
37
+ )
38
+ expect(transaction_details['status']).to eq('authorized')
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'with auto capture' do
44
+ subject(:authorize) do
45
+ described_class.call(
46
+ order: order,
47
+ credit_card: credit_card_payload,
48
+ create_bolt_account: false,
49
+ payment_method: payment_method,
50
+ auto_capture: true
51
+ )
52
+ end
53
+
54
+ describe '#call', vcr: true do
55
+ it 'makes the API call' do
56
+ response = authorize
57
+
58
+ expect(response).to match hash_including('transaction')
59
+ expect(response['transaction']['status']).to eq 'pending'
60
+
61
+ # The Authorize Call with auto_capture takes some time to update the state of the transaction from
62
+ # pending to completed, hence the sleep is necessary to get the correct output from the API Call.
63
+
64
+ sleep(1)
65
+
66
+ reference = response['transaction']['reference']
67
+ transaction_details = SolidusBolt::Transactions::DetailService.call(
68
+ transaction_reference: reference, payment_method: payment_method
69
+ )
70
+ expect(transaction_details['order']['cart']['shipments'].first['shipping_address']).to be_present
71
+ expect(transaction_details['status']).to eq('completed')
72
+ end
73
+ end
74
+ end
75
+
76
+ context 'when repeat payment', vcr: true do
77
+ let(:access_token) { ENV['BOLT_ACCESS_TOKEN'] }
78
+
79
+ describe '#call' do
80
+ it 'makes the API call' do
81
+ credit_card_id = SolidusBolt::Accounts::AddPaymentMethodService.call(
82
+ access_token: access_token,
83
+ credit_card: credit_card_payload.merge(number: '4111111111111111', postal_code: order.bill_address.zipcode),
84
+ address: order.bill_address,
85
+ email: order.email
86
+ )['id']
87
+
88
+ # The AddPaymentMethodService call takes some time to create the credit card on Bolt's side, hence the sleep is
89
+ # necessary to get the correct output from the API Call.
90
+
91
+ sleep(1)
92
+
93
+ response = described_class.call(
94
+ order: order,
95
+ credit_card: { id: credit_card_id },
96
+ payment_method: payment_method,
97
+ repeat: true,
98
+ create_bolt_account: false
99
+ )
100
+
101
+ expect(response).to match hash_including('transaction')
102
+ expect(response['transaction']['status']).to eq('pending')
103
+
104
+ # The Authorize Call with auto_capture takes some time to update the state of the transaction from
105
+ # pending to completed, hence the sleep is necessary to get the correct output from the API Call.
106
+
107
+ sleep(1)
108
+
109
+ reference = response['transaction']['reference']
110
+ transaction_details = SolidusBolt::Transactions::DetailService.call(
111
+ transaction_reference: reference, payment_method: payment_method
112
+ )
113
+ expect(transaction_details['status']).to eq('authorized')
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Transactions::BaseService do
6
+ let!(:payment_method) { create(:bolt_payment_method) }
7
+
8
+ describe '#call' do
9
+ subject(:service) { described_class.new(payment_method: payment_method).call }
10
+
11
+ it 'raises a not implemented error' do
12
+ expect { service }.to raise_error(::NotImplementedError)
13
+ end
14
+ end
15
+
16
+ describe '#api_base_url' do
17
+ it 'returns correct url' do
18
+ expect(
19
+ described_class.new(payment_method: payment_method).send(:api_base_url)
20
+ ).to eq payment_method.preferred_bolt_api_url
21
+ end
22
+ end
23
+
24
+ describe '#authentication_header' do
25
+ it 'returns the correct hash' do
26
+ expect(
27
+ described_class.new(payment_method: payment_method).send(:authentication_header)
28
+ ).to eq({ 'X-API-KEY' => payment_method.preferred_bolt_api_key })
29
+ end
30
+ end
31
+
32
+ describe '#publishable_key' do
33
+ it 'returns the correct key' do
34
+ publishable_key = described_class.new(payment_method: payment_method).send(:publishable_key)
35
+ expect(described_class.new(payment_method: payment_method).send(:publishable_key)).to eq(publishable_key)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Transactions::CaptureService, :vcr, :bolt_configuration do
6
+ subject(:api) do
7
+ described_class.new(
8
+ transaction_reference: reference, amount: amount, currency: currency, payment_method: payment_method
9
+ )
10
+ end
11
+
12
+ let(:transaction) do
13
+ SolidusBolt::Transactions::AuthorizeService.call(
14
+ order: order, credit_card: credit_card_payload, create_bolt_account: false, payment_method: payment_method
15
+ )
16
+ end
17
+ let(:credit_card_payload) do
18
+ tokenize_credit_card(credit_card_number: '4111111111111004', cvv: '111').merge(
19
+ expiration: (Time.current + 1.year).strftime('%Y-%m'),
20
+ token_type: 'bolt'
21
+ )
22
+ end
23
+ let(:order) { create(:order_with_line_items) }
24
+ let(:reference) { transaction['transaction']['reference'] }
25
+ let(:amount) { 10 }
26
+ let(:currency) { 'USD' }
27
+ let(:payment_method) { create(:bolt_payment_method) }
28
+
29
+ describe '#call', vcr: true do
30
+ it 'makes the API call' do
31
+ response = api.call
32
+
33
+ expect(response['reference']).to eq(reference)
34
+ expect(response['capture']['status']).to eq 'succeeded'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Transactions::DetailService, :vcr, :bolt_configuration do
6
+ subject(:api) { described_class.new(transaction_reference: reference, payment_method: payment_method) }
7
+
8
+ let(:transaction) do
9
+ SolidusBolt::Transactions::AuthorizeService.call(
10
+ order: order, credit_card: credit_card_payload, create_bolt_account: false, payment_method: payment_method
11
+ )
12
+ end
13
+ let(:credit_card_payload) do
14
+ tokenize_credit_card(credit_card_number: '4111111111111004', cvv: '111').merge(
15
+ expiration: (Time.current + 1.year).strftime('%Y-%m'),
16
+ token_type: 'bolt'
17
+ )
18
+ end
19
+ let(:order) { create(:order_with_line_items) }
20
+ let(:reference) { transaction['transaction']['reference'] }
21
+ let(:payment_method) { create(:bolt_payment_method) }
22
+
23
+ describe '#call', vcr: true do
24
+ it 'makes the API call' do
25
+ response = api.call
26
+
27
+ expect(response['reference']).to eq reference
28
+ expect(response['status']).to eq 'authorized'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Transactions::RefundService, :vcr, :bolt_configuration do
6
+ subject(:api) do
7
+ described_class.new(
8
+ transaction_reference: reference, amount: amount, currency: currency, payment_method: payment_method
9
+ )
10
+ end
11
+
12
+ let(:transaction) do
13
+ SolidusBolt::Transactions::AuthorizeService.call(
14
+ order: order, credit_card: credit_card_payload, create_bolt_account: false, payment_method: payment_method
15
+ )
16
+ end
17
+ let(:credit_card_payload) do
18
+ tokenize_credit_card(credit_card_number: '4111111111111004', cvv: '111').merge(
19
+ expiration: (Time.current + 1.year).strftime('%Y-%m'),
20
+ token_type: 'bolt'
21
+ )
22
+ end
23
+ let(:order) { create(:order_with_line_items) }
24
+ let(:reference) { transaction['transaction']['reference'] }
25
+ let(:amount) { transaction['transaction']['amount']['amount'] }
26
+ let(:currency) { 'USD' }
27
+ let(:payment_method) { create(:bolt_payment_method) }
28
+
29
+ describe '#call', vcr: true do
30
+ before do
31
+ SolidusBolt::Transactions::CaptureService.call(
32
+ transaction_reference: reference, amount: amount, currency: currency, payment_method: payment_method
33
+ )
34
+ end
35
+
36
+ it 'makes the API call' do
37
+ response = api.call
38
+
39
+ expect(response['credit']['status']).to eq 'succeeded'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Transactions::VoidService, :vcr, :bolt_configuration do
6
+ subject(:api) do
7
+ described_class.new(
8
+ transaction_reference: reference, transaction_id: transaction_id, payment_method: payment_method
9
+ )
10
+ end
11
+
12
+ let(:transaction) do
13
+ SolidusBolt::Transactions::AuthorizeService.call(
14
+ order: order, credit_card: credit_card_payload, create_bolt_account: false, payment_method: payment_method
15
+ )
16
+ end
17
+ let(:credit_card_payload) do
18
+ tokenize_credit_card(credit_card_number: '4111111111111004', cvv: '111').merge(
19
+ expiration: (Time.current + 1.year).strftime('%Y-%m'),
20
+ token_type: 'bolt'
21
+ )
22
+ end
23
+ let(:order) { create(:order_with_line_items) }
24
+ let(:reference) { transaction['transaction']['reference'] }
25
+ let(:transaction_id) { transaction['transaction']['id'] }
26
+ let(:payment_method) { create(:bolt_payment_method) }
27
+
28
+ describe '#call', vcr: true do
29
+ it 'makes the API call' do
30
+ response = api.call
31
+
32
+ expect(response['id']).to eq transaction_id
33
+ expect(response['reference']).to eq reference
34
+ expect(response['status']).to eq 'cancelled'
35
+ end
36
+
37
+ context 'when transaction_id is missing' do
38
+ subject(:api) do
39
+ described_class.new(
40
+ transaction_reference: reference, payment_method: payment_method
41
+ )
42
+ end
43
+
44
+ it 'makes the API call' do
45
+ response = api.call
46
+
47
+ expect(response['id']).to eq transaction_id
48
+ expect(response['reference']).to eq reference
49
+ expect(response['status']).to eq 'cancelled'
50
+ end
51
+ end
52
+
53
+ context 'when transaction_reference is missing' do
54
+ subject(:api) do
55
+ described_class.new(
56
+ transaction_id: transaction_id,
57
+ payment_method: payment_method
58
+ )
59
+ end
60
+
61
+ it 'makes the API call' do
62
+ response = api.call
63
+
64
+ expect(response['id']).to eq transaction_id
65
+ expect(response['reference']).to eq reference
66
+ expect(response['status']).to eq 'cancelled'
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Users::RefreshAccessTokenService do
6
+ subject(:refresh_access_token) { described_class.call(session: session) }
7
+
8
+ let(:session) { { bolt_access_token: bolt_access_token, bolt_expiration_time: bolt_expiration_time } }
9
+
10
+ describe '#call' do
11
+ context 'when not bolt user' do
12
+ let(:bolt_access_token) { nil }
13
+ let(:bolt_expiration_time) { nil }
14
+
15
+ it 'returns nil' do
16
+ expect(refresh_access_token).to be_nil
17
+ end
18
+ end
19
+
20
+ context 'with valid token' do
21
+ let(:bolt_access_token) { 'accesstoken' }
22
+ let(:bolt_expiration_time) { Time.now.utc + 10.minutes }
23
+
24
+ it 'returns current access token' do
25
+ expect(refresh_access_token).to eq(bolt_access_token)
26
+ end
27
+ end
28
+
29
+ context 'with expired token' do
30
+ let(:bolt_access_token) { 'accesstoken' }
31
+ let(:bolt_expiration_time) { Time.now.utc - 10.minutes }
32
+ let(:result) { { 'access_token' => 'newaccesstoken', 'expires_in' => 3600 } }
33
+
34
+ before do
35
+ allow(HTTParty).to receive(:post).and_return(result)
36
+ allow(result).to receive(:success?).and_return(true)
37
+ allow(result).to receive(:parsed_response).and_return(result)
38
+ end
39
+
40
+ it 'refreshes the access token' do
41
+ expect(refresh_access_token).to eq('newaccesstoken')
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Users::SyncAddressesService, :vcr, :bolt_configuration do
6
+ subject(:sync_addresses_service) do
7
+ described_class.call(user: user, access_token: ENV.fetch('BOLT_ACCESS_TOKEN', 'Fake_access_token'))
8
+ end
9
+
10
+ let(:user) { create(:user) }
11
+ let(:country) { state.country }
12
+ let(:state) { create(:state) }
13
+ let(:payload) do
14
+ {
15
+ 'profile' => { 'phone' => '987654321' },
16
+ 'addresses' => [{
17
+ 'id' => 'SA98t82gH2Grd',
18
+ 'street_address1' => '380 DEGRAW ST',
19
+ 'locality' => 'Brooklyn',
20
+ 'region' => 'NEW YORK',
21
+ 'region_code' => 'NY',
22
+ 'postal_code' => '11231',
23
+ 'country_code' => 'US',
24
+ 'name' => 'Danny Dove',
25
+ 'first_name' => 'Danny',
26
+ 'last_name' => 'Dove',
27
+ 'default' => true,
28
+ 'phone_number' => phone
29
+ }]
30
+ }
31
+ end
32
+
33
+ before do
34
+ allow(Spree::Country).to receive(:find_by).and_return(country)
35
+ allow(Spree::State).to receive(:find_by).and_return(state)
36
+ allow(country).to receive(:states).and_return(Spree::State)
37
+ end
38
+
39
+ describe '#call', vcr: true do
40
+ it 'adds the ship_address to the user' do
41
+ expect { sync_addresses_service }.to change(user, :ship_address).from(nil)
42
+ end
43
+
44
+ it 'adds the bill_address to the user' do
45
+ expect { sync_addresses_service }.to change(user, :bill_address).from(nil)
46
+ end
47
+
48
+ context 'with address phone number', vcr: false do
49
+ let(:phone) { '123456789' }
50
+
51
+ before do
52
+ allow(SolidusBolt::Accounts::DetailService).to receive(:call).and_return(payload)
53
+ sync_addresses_service
54
+ end
55
+
56
+ it 'creates an address with the address phone' do
57
+ expect(user.bill_address.phone).to eq(phone)
58
+ end
59
+ end
60
+
61
+ context 'without address phone number', vcr: false do
62
+ let(:phone) { nil }
63
+
64
+ before do
65
+ allow(SolidusBolt::Accounts::DetailService).to receive(:call).and_return(payload)
66
+ sync_addresses_service
67
+ end
68
+
69
+ it 'creates an address with the user phone' do
70
+ expect(user.bill_address.phone).to eq('987654321')
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusBolt::Users::SyncPaymentSourcesService, :vcr, :bolt_configuration do
6
+ subject(:sync_payment_sources) do
7
+ described_class.call(user: user, access_token: ENV.fetch('BOLT_ACCESS_TOKEN', 'Fake_access_token'))
8
+ end
9
+
10
+ let(:user) { create(:user) }
11
+ let(:bolt_payment_method) { create(:bolt_payment_method) }
12
+
13
+ describe '#call' do
14
+ before { bolt_payment_method }
15
+
16
+ it 'creates a new payment source with card ID' do
17
+ expect { sync_payment_sources }.to change { SolidusBolt::PaymentSource.count }.by(1)
18
+ bolt_payment_source = SolidusBolt::PaymentSource.last
19
+ expect(bolt_payment_source.card_id).to be_present
20
+ expect(bolt_payment_source.card_expiration).to be_present
21
+ expect(bolt_payment_source.payment_method_id).to eq(bolt_payment_method.id)
22
+ expect(user.wallet_payment_sources.last.payment_source).to eq(bolt_payment_source)
23
+ end
24
+ end
25
+ end