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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ class BaseService
5
+ def initialize(*)
6
+ @config = SolidusBolt::BoltConfiguration.fetch
7
+ end
8
+
9
+ def call
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def self.call(*args)
14
+ new(*args).call
15
+ end
16
+
17
+ private
18
+
19
+ def handle_result(result)
20
+ return result.parsed_response if result.success?
21
+
22
+ raise ServerError, error_message(result)
23
+ end
24
+
25
+ def error_message(result)
26
+ return result['errors'].map { |e| e['message'] }.join(', ') if result['errors']
27
+
28
+ "#{result['error']}: #{result['error_description']}"
29
+ end
30
+
31
+ def api_base_url
32
+ @config.environment_url
33
+ end
34
+
35
+ def api_version
36
+ 'v1'
37
+ end
38
+
39
+ def generate_nonce
40
+ SecureRandom.hex
41
+ end
42
+
43
+ def authentication_header
44
+ { 'X-API-KEY' => @config.api_key }
45
+ end
46
+
47
+ def publishable_key
48
+ @config.publishable_key
49
+ end
50
+
51
+ def api_key
52
+ @config.api_key
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Oauth
5
+ class TokenService < SolidusBolt::BaseService
6
+ attr_reader :authorization_code, :scope
7
+
8
+ def initialize(authorization_code:, scope:)
9
+ @authorization_code = authorization_code
10
+ @scope = scope
11
+ super
12
+ end
13
+
14
+ def call
15
+ token
16
+ end
17
+
18
+ private
19
+
20
+ def token
21
+ options = build_options
22
+ handle_result(
23
+ HTTParty.post(
24
+ "#{api_base_url}/#{api_version}/oauth/token",
25
+ options
26
+ )
27
+ )
28
+ end
29
+
30
+ def build_options
31
+ {
32
+ body: {
33
+ grant_type: 'authorization_code',
34
+ code: authorization_code,
35
+ scope: scope,
36
+ client_id: publishable_key,
37
+ client_secret: api_key
38
+ }
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Payments
5
+ class CaptureSyncService < SolidusBolt::BaseService
6
+ attr_reader :payment, :capture_amount
7
+
8
+ def initialize(payment:, capture_amount:)
9
+ @payment = payment
10
+ @capture_amount = capture_amount
11
+ super
12
+ end
13
+
14
+ def call
15
+ return if payment.completed?
16
+
17
+ amount = Money.new(capture_amount).to_d
18
+
19
+ payment.capture_events.create!(amount: amount)
20
+ payment.update!(amount: amount, state: 'completed')
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Payments
5
+ class CreditSyncService < BaseService
6
+ attr_reader :payment, :amount, :transaction_id, :refund_reason, :order
7
+
8
+ def initialize(payment:, amount:, transaction_id:)
9
+ @payment = payment
10
+ @amount = amount
11
+ @transaction_id = transaction_id
12
+ @refund_reason = Spree::RefundReason.return_processing_reason&.id
13
+ @order = payment.order
14
+ super
15
+ end
16
+
17
+ def call
18
+ return if Spree::Refund.find_by(transaction_id: transaction_id)
19
+
20
+ create_refund
21
+ order.recalculate
22
+ end
23
+
24
+ private
25
+
26
+ def create_refund
27
+ payload = {
28
+ payment_id: payment.id,
29
+ amount: amount.to_f / 100,
30
+ transaction_id: transaction_id,
31
+ refund_reason_id: refund_reason
32
+ }
33
+
34
+ if Spree.solidus_gem_version >= Gem::Version.create('3.0')
35
+ Spree::Refund.create!(payload)
36
+ else
37
+ # Skip after_save callbacks that call the Gateway. Callbacks removed from solidus 3.0 up
38
+ # https://github.com/solidusio/solidus/blob/v2.11/core/app/models/spree/refund.rb#L20-L22
39
+ Spree::Refund.insert!(payload) # rubocop:disable Rails/SkipsModelValidations
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Payments
5
+ class VoidSyncService < BaseService
6
+ attr_reader :payment
7
+
8
+ def initialize(payment:)
9
+ @payment = payment
10
+ super
11
+ end
12
+
13
+ def call
14
+ payment.void! unless payment.void?
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ class ServerError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Transactions
5
+ class AuthorizeService < SolidusBolt::Transactions::BaseService
6
+ attr_reader :order, :create_bolt_account, :credit_card, :auto_capture, :repeat
7
+
8
+ def initialize(order:, create_bolt_account:, credit_card:, payment_method:, auto_capture: false, repeat: false)
9
+ @order = order
10
+ @create_bolt_account = create_bolt_account
11
+ @credit_card = credit_card
12
+ @auto_capture = auto_capture
13
+ @repeat = repeat
14
+ super
15
+ end
16
+
17
+ def call
18
+ authorize
19
+ end
20
+
21
+ private
22
+
23
+ def authorize
24
+ url = "#{api_base_url}/#{api_version}/merchant/transactions/authorize"
25
+ handle_result(
26
+ HTTParty.post(
27
+ url, headers: headers, body: (repeat ? repeat_authorization_body : new_authorization_body).to_json
28
+ )
29
+ )
30
+ end
31
+
32
+ def new_authorization_body
33
+ body.merge(
34
+ credit_card: credit_card,
35
+ division_id: '', # used to be defined, now needs to stay empty for call to work
36
+ shipping_address: shipping_address,
37
+ create_bolt_account: create_bolt_account
38
+ )
39
+ end
40
+
41
+ def repeat_authorization_body
42
+ body.merge(
43
+ credit_card_id: credit_card[:id]
44
+ )
45
+ end
46
+
47
+ def body
48
+ {
49
+ auto_capture: auto_capture,
50
+ cart: order.bolt_cart,
51
+ source: 'direct_payments',
52
+ user_identifier: order.bolt_user_identifier,
53
+ user_identity: order.bolt_user_identity
54
+ }
55
+ end
56
+
57
+ def shipping_address
58
+ order.shipping_address.yield_self do |address|
59
+ address.bolt_address(order.email)
60
+ end
61
+ end
62
+
63
+ def headers
64
+ {
65
+ 'Content-Type' => 'application/json',
66
+ 'X-Nonce' => generate_nonce,
67
+ 'X-Publishable-Key' => publishable_key
68
+ }.merge(authentication_header)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Transactions
5
+ class BaseService < SolidusBolt::BaseService
6
+ attr_reader :payment_method
7
+
8
+ def initialize(payment_method:, **args)
9
+ @payment_method = payment_method
10
+ super
11
+ end
12
+
13
+ private
14
+
15
+ def api_base_url
16
+ payment_method.preferred_bolt_api_url
17
+ end
18
+
19
+ def authentication_header
20
+ { 'X-API-KEY' => payment_method.preferred_bolt_api_key }
21
+ end
22
+
23
+ def publishable_key
24
+ payment_method.preferred_bolt_publishable_key
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Transactions
5
+ class CaptureService < SolidusBolt::Transactions::BaseService
6
+ attr_reader :transaction_reference, :amount, :currency
7
+
8
+ def initialize(transaction_reference:, amount:, currency:, payment_method:)
9
+ @transaction_reference = transaction_reference
10
+ @amount = amount
11
+ @currency = currency
12
+ super
13
+ end
14
+
15
+ def call
16
+ capture
17
+ end
18
+
19
+ private
20
+
21
+ def capture
22
+ options = build_options
23
+ handle_result(
24
+ HTTParty.post(
25
+ "#{api_base_url}/#{api_version}/merchant/transactions/capture",
26
+ options
27
+ )
28
+ )
29
+ end
30
+
31
+ def build_options
32
+ {
33
+ body: {
34
+ transaction_reference: transaction_reference,
35
+ amount: amount,
36
+ currency: currency
37
+ }.to_json,
38
+ headers: {
39
+ 'X-Nonce' => generate_nonce,
40
+ 'Content-Type' => 'application/json'
41
+ }.merge(authentication_header)
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Transactions
5
+ class DetailService < SolidusBolt::Transactions::BaseService
6
+ attr_reader :transaction_reference
7
+
8
+ def initialize(transaction_reference:, payment_method:)
9
+ @transaction_reference = transaction_reference
10
+ super
11
+ end
12
+
13
+ def call
14
+ detail
15
+ end
16
+
17
+ private
18
+
19
+ def detail
20
+ options = build_options
21
+ handle_result(
22
+ HTTParty.get(
23
+ "#{api_base_url}/#{api_version}/merchant/transactions/#{transaction_reference}",
24
+ options
25
+ )
26
+ )
27
+ end
28
+
29
+ def build_options
30
+ {
31
+ headers: {
32
+ 'X-Nonce' => generate_nonce
33
+ }.merge(authentication_header)
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Transactions
5
+ class RefundService < SolidusBolt::Transactions::BaseService
6
+ attr_reader :transaction_reference, :amount, :currency
7
+
8
+ def initialize(transaction_reference:, amount:, currency:, payment_method:)
9
+ @transaction_reference = transaction_reference
10
+ @amount = amount
11
+ @currency = currency
12
+ super
13
+ end
14
+
15
+ def call
16
+ refund
17
+ end
18
+
19
+ private
20
+
21
+ def refund
22
+ options = build_options
23
+ handle_result(
24
+ HTTParty.post(
25
+ "#{api_base_url}/#{api_version}/merchant/transactions/credit",
26
+ options
27
+ )
28
+ )
29
+ end
30
+
31
+ def build_options
32
+ {
33
+ body: {
34
+ transaction_reference: transaction_reference,
35
+ amount: amount,
36
+ currency: currency
37
+ }.to_json,
38
+ headers: {
39
+ 'X-Nonce' => generate_nonce,
40
+ 'Content-Type' => 'application/json'
41
+ }.merge(authentication_header)
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Transactions
5
+ class VoidService < SolidusBolt::Transactions::BaseService
6
+ attr_reader :transaction_reference, :transaction_id
7
+
8
+ def initialize(payment_method:, transaction_reference: nil, transaction_id: nil)
9
+ @transaction_reference = transaction_reference
10
+ @transaction_id = transaction_id
11
+ super
12
+ end
13
+
14
+ def call
15
+ void
16
+ end
17
+
18
+ private
19
+
20
+ def void
21
+ options = build_options
22
+ handle_result(
23
+ HTTParty.post(
24
+ "#{api_base_url}/#{api_version}/merchant/transactions/void",
25
+ options
26
+ )
27
+ )
28
+ end
29
+
30
+ def build_options
31
+ {
32
+ body: {
33
+ transaction_reference: transaction_reference,
34
+ transaction_id: transaction_id,
35
+ }.to_json,
36
+ headers: {
37
+ 'X-Nonce' => generate_nonce,
38
+ 'Content-Type' => 'application/json'
39
+ }.merge(authentication_header)
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Users
5
+ class RefreshAccessTokenService < SolidusBolt::BaseService
6
+ attr_reader :session
7
+
8
+ def initialize(session:)
9
+ @session = session
10
+ super
11
+ end
12
+
13
+ def call
14
+ return if session[:bolt_access_token].nil?
15
+ return session[:bolt_access_token] if session[:bolt_expiration_time] >= Time.now.utc
16
+
17
+ refresh_access_token
18
+ end
19
+
20
+ private
21
+
22
+ def refresh_access_token
23
+ response = handle_result(HTTParty.post("#{api_base_url}/#{api_version}/oauth/token", build_options))
24
+
25
+ session[:bolt_expiration_time] = Time.now.utc + response['expires_in']
26
+ session[:bolt_refresh_token] = response['refresh_token']
27
+ session[:bolt_refresh_token_scope] = response['refresh_token_scope']
28
+ session[:bolt_access_token] = response['access_token']
29
+ end
30
+
31
+ def build_options
32
+ {
33
+ body: {
34
+ grant_type: 'refresh_token',
35
+ refresh_token: session[:bolt_refresh_token],
36
+ client_id: publishable_key,
37
+ scope: session[:bolt_refresh_token_scope],
38
+ client_secret: api_key
39
+ }
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Users
5
+ class SyncAddressesService < SolidusBolt::BaseService
6
+ attr_reader :user, :access_token
7
+
8
+ def initialize(user:, access_token:)
9
+ @access_token = access_token
10
+ @user = user
11
+ super
12
+ end
13
+
14
+ def call
15
+ return if user.nil? || access_token.nil?
16
+
17
+ bolt_addresses.each do |bolt_address|
18
+ default = bolt_address['default']
19
+ address = convert_to_solidus(bolt_address)
20
+ user.save_in_address_book(address, default, :shipping)
21
+ user.save_in_address_book(address, default, :billing)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def user_info
28
+ @user_info ||= Accounts::DetailService.call(access_token: access_token)
29
+ end
30
+
31
+ def bolt_addresses
32
+ user_info['addresses']
33
+ end
34
+
35
+ def convert_to_solidus(bolt_address)
36
+ country = Spree::Country.find_by(iso: bolt_address['country_code'])
37
+ {
38
+ address1: bolt_address['street_address1'],
39
+ city: bolt_address['locality'],
40
+ state: country.states.find_by(abbr: bolt_address['region_code']),
41
+ zipcode: bolt_address['postal_code'],
42
+ name: bolt_address['name'],
43
+ country: country,
44
+ phone: bolt_address['phone_number'] || user_info['profile']['phone']
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Users
5
+ class SyncPaymentSourcesService < SolidusBolt::BaseService
6
+ attr_reader :user, :access_token, :bolt_payment_method
7
+
8
+ def initialize(user:, access_token:)
9
+ @user = user
10
+ @access_token = access_token
11
+ @bolt_payment_method = SolidusBolt::PaymentMethod.last
12
+ super
13
+ end
14
+
15
+ def call
16
+ return if user.nil? || access_token.nil? || bolt_payment_method.nil?
17
+
18
+ payment_methods.each do |payment_method|
19
+ add_payment_source(payment_method)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def user_info
26
+ @user_info ||= Accounts::DetailService.call(access_token: access_token)
27
+ end
28
+
29
+ def payment_methods
30
+ user_info['payment_methods']
31
+ end
32
+
33
+ def spree_wallet
34
+ @spree_wallet ||= Spree::Wallet.new(user)
35
+ end
36
+
37
+ def add_payment_source(payment_method)
38
+ # format of card_expiration: '2022-04'
39
+ card_expiration = "#{payment_method['exp_year']}-#{payment_method['exp_month'].to_s.rjust(2, '0')}"
40
+ payment_source = SolidusBolt::PaymentSource.find_or_create_by!(
41
+ payment_method_id: bolt_payment_method.id,
42
+ card_last4: payment_method['last4'],
43
+ card_expiration: card_expiration,
44
+ card_id: payment_method['id']
45
+ )
46
+ spree_wallet.add(payment_source)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Webhooks
5
+ class CreateService < SolidusBolt::BaseService
6
+ attr_reader :division_public_id, :event, :url
7
+
8
+ def initialize(event:, url:)
9
+ @event = event
10
+ @url = url
11
+ @division_public_id = SolidusBolt::BoltConfiguration.fetch.division_public_id
12
+ super
13
+ end
14
+
15
+ def call
16
+ create
17
+ end
18
+
19
+ private
20
+
21
+ def create
22
+ options = build_options
23
+ handle_result(
24
+ HTTParty.post(
25
+ "#{api_base_url}/#{api_version}/webhooks",
26
+ options
27
+ )
28
+ )
29
+ end
30
+
31
+ def build_options
32
+ {
33
+ body: {
34
+ division_id: division_public_id,
35
+ url: url
36
+ }.merge(event_fields).to_json,
37
+ headers: {
38
+ 'Content-Type' => 'application/json'
39
+ }.merge(authentication_header)
40
+ }
41
+ end
42
+
43
+ def event_fields
44
+ if event == 'all'
45
+ { event_group: event }
46
+ else
47
+ { events: [event] }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ <% admin_breadcrumb(plural_resource_name(SolidusBolt::BoltConfiguration)) %>
2
+
3
+ <%= form_for :bolt_webhook, url: admin_bolt_webhook_path do |f| %>
4
+ <fieldset class="form-group no-border-bottom no-border-top">
5
+ <div class="row">
6
+ <div id="general_fields" class="col-9">
7
+ <div class="row">
8
+ <div class="col-12">
9
+ <%= f.label :event %>
10
+ <%= f.select :event, SolidusBolt::BoltConfiguration::EVENTS %><br />
11
+ <%= f.label :webhook_url %>
12
+ <%= f.text_field :webhook_url, class: 'fullwidth', value: 'https://domain.com/webhooks/bolt' %>
13
+ </div>
14
+ </div>
15
+ <div class="row p-2 justify-content-center">
16
+ <%= f.submit 'Create', class: 'btn btn-primary', data: { disable_with: 'Creating..' } %>
17
+ <%= link_to 'Cancel', admin_bolt_path, class: 'button' %>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </fieldset>
22
+ <% end %>