solidus_bolt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +41 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.github_changelog_generator +2 -0
  6. data/.gitignore +20 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +11 -0
  9. data/CHANGELOG.md +1 -0
  10. data/Gemfile +33 -0
  11. data/LICENSE +26 -0
  12. data/README.md +175 -0
  13. data/Rakefile +6 -0
  14. data/app/assets/images/bolt_logo_standard.png +0 -0
  15. data/app/assets/javascripts/authorize_account.js +74 -0
  16. data/app/assets/javascripts/solidus_bolt.js +91 -0
  17. data/app/assets/javascripts/spree/backend/solidus_bolt.js +4 -0
  18. data/app/assets/javascripts/spree/frontend/solidus_bolt.js +18 -0
  19. data/app/assets/stylesheets/spree/backend/solidus_bolt.css +4 -0
  20. data/app/assets/stylesheets/spree/frontend/solidus_bolt.css +4 -0
  21. data/app/controllers/solidus_bolt/accounts_controller.rb +17 -0
  22. data/app/controllers/solidus_bolt/base_controller.rb +21 -0
  23. data/app/controllers/solidus_bolt/webhooks_controller.rb +21 -0
  24. data/app/controllers/spree/admin/bolt_webhooks_controller.rb +34 -0
  25. data/app/controllers/spree/admin/bolts_controller.rb +43 -0
  26. data/app/decorators/controllers/solidus_bolt/spree_checkout_controller/add_addresses_to_bolt.rb +23 -0
  27. data/app/decorators/controllers/solidus_bolt/spree_checkout_controller/refresh_bolt_addresses.rb +17 -0
  28. data/app/decorators/controllers/solidus_bolt/spree_checkout_controller/refresh_bolt_payment_source.rb +17 -0
  29. data/app/decorators/models/solidus_bolt/address_decorator.rb +22 -0
  30. data/app/decorators/models/solidus_bolt/log_entry_decorator.rb +11 -0
  31. data/app/decorators/models/solidus_bolt/order_decorator.rb +44 -0
  32. data/app/decorators/models/solidus_bolt/payment_decorator.rb +11 -0
  33. data/app/decorators/omniauth/strategies/bolt_decorator.rb +16 -0
  34. data/app/jobs/solidus_bolt/add_address_job.rb +11 -0
  35. data/app/models/solidus_bolt/bolt_configuration.rb +74 -0
  36. data/app/models/solidus_bolt/gateway.rb +133 -0
  37. data/app/models/solidus_bolt/payment_method.rb +35 -0
  38. data/app/models/solidus_bolt/payment_source.rb +13 -0
  39. data/app/models/solidus_bolt.rb +7 -0
  40. data/app/overrides/spree/shared/_head/add_bolt_embed_script.html.erb.deface +6 -0
  41. data/app/services/solidus_bolt/accounts/add_address_service.rb +55 -0
  42. data/app/services/solidus_bolt/accounts/add_payment_method_service.rb +45 -0
  43. data/app/services/solidus_bolt/accounts/detail_service.rb +38 -0
  44. data/app/services/solidus_bolt/accounts/detect_account_service.rb +34 -0
  45. data/app/services/solidus_bolt/base_service.rb +55 -0
  46. data/app/services/solidus_bolt/oauth/token_service.rb +43 -0
  47. data/app/services/solidus_bolt/payments/capture_sync_service.rb +24 -0
  48. data/app/services/solidus_bolt/payments/credit_sync_service.rb +44 -0
  49. data/app/services/solidus_bolt/payments/void_sync_service.rb +18 -0
  50. data/app/services/solidus_bolt/server_error.rb +6 -0
  51. data/app/services/solidus_bolt/transactions/authorize_service.rb +72 -0
  52. data/app/services/solidus_bolt/transactions/base_service.rb +28 -0
  53. data/app/services/solidus_bolt/transactions/capture_service.rb +46 -0
  54. data/app/services/solidus_bolt/transactions/detail_service.rb +38 -0
  55. data/app/services/solidus_bolt/transactions/refund_service.rb +46 -0
  56. data/app/services/solidus_bolt/transactions/void_service.rb +44 -0
  57. data/app/services/solidus_bolt/users/refresh_access_token_service.rb +44 -0
  58. data/app/services/solidus_bolt/users/sync_addresses_service.rb +49 -0
  59. data/app/services/solidus_bolt/users/sync_payment_sources_service.rb +50 -0
  60. data/app/services/solidus_bolt/webhooks/create_service.rb +52 -0
  61. data/app/views/spree/admin/bolt_webhooks/new.html.erb +22 -0
  62. data/app/views/spree/admin/bolts/_configuration.html.erb +32 -0
  63. data/app/views/spree/admin/bolts/_form.html.erb +29 -0
  64. data/app/views/spree/admin/bolts/edit.html.erb +6 -0
  65. data/app/views/spree/admin/bolts/show.html.erb +21 -0
  66. data/app/views/spree/admin/payments/source_forms/_bolt.html.erb +1 -0
  67. data/app/views/spree/admin/payments/source_views/_bolt.html.erb +2 -0
  68. data/app/views/spree/api/payments/source_views/_bolt.json.jbuilder +4 -0
  69. data/app/views/spree/checkout/existing_payment/_bolt.html.erb +7 -0
  70. data/app/views/spree/checkout/payment/_bolt.html.erb +1 -0
  71. data/app/views/spree/shared/payment/_bolt.html.erb +19 -0
  72. data/app/webhooks/solidus_bolt/handlers/base_handler.rb +27 -0
  73. data/app/webhooks/solidus_bolt/handlers/capture_handler.rb +13 -0
  74. data/app/webhooks/solidus_bolt/handlers/credit_handler.rb +18 -0
  75. data/app/webhooks/solidus_bolt/handlers/void_handler.rb +11 -0
  76. data/app/webhooks/solidus_bolt/sorter.rb +33 -0
  77. data/bin/console +17 -0
  78. data/bin/rails +7 -0
  79. data/bin/rails-engine +13 -0
  80. data/bin/rails-sandbox +16 -0
  81. data/bin/rake +7 -0
  82. data/bin/sandbox +86 -0
  83. data/bin/setup +8 -0
  84. data/config/initializers/menu_items.rb +14 -0
  85. data/config/locales/en.yml +17 -0
  86. data/config/routes.rb +11 -0
  87. data/db/migrate/20220330094232_create_solidus_bolt_payment_sources.rb +16 -0
  88. data/db/migrate/20220413063328_create_solidus_bolt_bolt_configurations.rb +14 -0
  89. data/db/migrate/20220502005041_swap_url_for_env_boolean_in_bolt_configuration.rb +6 -0
  90. data/db/migrate/20220509102309_rework_solidus_bolt_payment_sources.rb +19 -0
  91. data/db/migrate/20220510075227_add_create_bolt_account_to_solidus_bolt_payment_source.rb +5 -0
  92. data/db/migrate/20220519233043_add_user_to_solidus_bolt_payment_source.rb +5 -0
  93. data/db/migrate/20220526005619_remove_user_id_from_solidus_bolt_payment_source.rb +5 -0
  94. data/db/migrate/20220530102107_rename_bolt_configuration_merchant_id_to_division_public_id.rb +5 -0
  95. data/db/migrate/20220531075527_update_bolt_configuration_environment_column_restrictions.rb +6 -0
  96. data/db/seeds.rb +30 -0
  97. data/lib/generators/solidus_bolt/install/install_generator.rb +78 -0
  98. data/lib/generators/solidus_bolt/install/templates/initializer.rb +8 -0
  99. data/lib/solidus_bolt/configuration.rb +19 -0
  100. data/lib/solidus_bolt/engine.rb +62 -0
  101. data/lib/solidus_bolt/testing_support/factories.rb +32 -0
  102. data/lib/solidus_bolt/version.rb +5 -0
  103. data/lib/solidus_bolt.rb +9 -0
  104. data/lib/tasks/db/seed/solidus_bolt.rake +14 -0
  105. data/lib/views/frontend/spree/shared/_login_bar_items.html.erb +18 -0
  106. data/solidus_bolt.gemspec +46 -0
  107. data/spec/decorators/models/solidus_bolt/address_decorator_spec.rb +24 -0
  108. data/spec/decorators/models/solidus_bolt/order_decorator_spec.rb +36 -0
  109. data/spec/decorators/models/solidus_bolt/payment_decorator_spec.rb +30 -0
  110. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddAddressService/_call/with_correct_access_token/receives_a_successful_response.yml +137 -0
  111. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddAddressService/_call/with_existing_address/skips_the_add_address_call.yml +82 -0
  112. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddAddressService/_call/with_wrong_access_token/gives_an_error.yml +55 -0
  113. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddPaymentMethodService/_call/with_correct_access_token/receives_a_successful_response.yml +186 -0
  114. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_AddPaymentMethodService/_call/with_wrong_access_token/gives_an_error.yml +179 -0
  115. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetailService/_call/with_correct_access_token/receives_a_successful_response.yml +55 -0
  116. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetailService/_call/with_wrong_access_token/gives_an_error.yml +55 -0
  117. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetectAccountService/_call/receives_the_correct_response.yml +50 -0
  118. data/spec/fixtures/vcr_cassettes/SolidusBolt_Accounts_DetectAccountService/_call/returns_status_200.yml +50 -0
  119. data/spec/fixtures/vcr_cassettes/SolidusBolt_Oauth_TokenService/_call/makes_the_API_call.yml +59 -0
  120. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_AuthorizeService/when_repeat_payment/_call/makes_the_API_call.yml +305 -0
  121. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_AuthorizeService/with_auto_capture/_call/makes_the_API_call.yml +307 -0
  122. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_AuthorizeService/without_auto_capture/_call/makes_the_API_call.yml +252 -0
  123. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_CaptureService/_call/makes_the_API_call.yml +242 -0
  124. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_DetailService/_call/makes_the_API_call.yml +244 -0
  125. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_RefundService/_call/makes_the_API_call.yml +296 -0
  126. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_VoidService/_call/makes_the_API_call.yml +242 -0
  127. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_VoidService/_call/when_transaction_id_is_missing/makes_the_API_call.yml +242 -0
  128. data/spec/fixtures/vcr_cassettes/SolidusBolt_Transactions_VoidService/_call/when_transaction_reference_is_missing/makes_the_API_call.yml +242 -0
  129. data/spec/fixtures/vcr_cassettes/SolidusBolt_Users_SyncAddressesService/_call/adds_the_bill_address_to_the_user.yml +165 -0
  130. data/spec/fixtures/vcr_cassettes/SolidusBolt_Users_SyncAddressesService/_call/adds_the_ship_address_to_the_user.yml +165 -0
  131. data/spec/fixtures/vcr_cassettes/SolidusBolt_Users_SyncPaymentSourcesService/_call/creates_a_new_payment_source_with_card_ID.yml +55 -0
  132. data/spec/fixtures/vcr_cassettes/SolidusBolt_Webhooks_CreateService/_call/with_all_event/returns_a_webhook_id.yml +54 -0
  133. data/spec/fixtures/vcr_cassettes/SolidusBolt_Webhooks_CreateService/_call/with_an_event/returns_a_webhook_id.yml +54 -0
  134. data/spec/fixtures/vcr_cassettes/SolidusBolt_Webhooks_CreateService/_call/with_empty_event/raises_a_server_error.yml +57 -0
  135. data/spec/jobs/solidus_bolt/add_address_job_spec.rb +18 -0
  136. data/spec/models/solidus_bolt/bolt_configuration_spec.rb +173 -0
  137. data/spec/models/solidus_bolt/gateway_spec.rb +130 -0
  138. data/spec/models/solidus_bolt/payment_method_spec.rb +21 -0
  139. data/spec/models/solidus_bolt/payment_source_spec.rb +22 -0
  140. data/spec/requests/solidus_bolt/accounts_controller_spec.rb +41 -0
  141. data/spec/requests/solidus_bolt/webhooks_controller_spec.rb +122 -0
  142. data/spec/requests/spree/admin/bolt_spec.rb +71 -0
  143. data/spec/requests/spree/admin/bolt_webhook_spec.rb +35 -0
  144. data/spec/requests/spree/checkout_controller_spec.rb +117 -0
  145. data/spec/services/solidus_bolt/accounts/add_address_service_spec.rb +45 -0
  146. data/spec/services/solidus_bolt/accounts/add_payment_method_service_spec.rb +47 -0
  147. data/spec/services/solidus_bolt/accounts/detail_service_spec.rb +32 -0
  148. data/spec/services/solidus_bolt/accounts/detect_account_service_spec.rb +15 -0
  149. data/spec/services/solidus_bolt/base_service_spec.rb +49 -0
  150. data/spec/services/solidus_bolt/oauth/token_service_spec.rb +15 -0
  151. data/spec/services/solidus_bolt/payments/capture_sync_service_spec.rb +27 -0
  152. data/spec/services/solidus_bolt/payments/credit_sync_service_spec.rb +38 -0
  153. data/spec/services/solidus_bolt/payments/void_sync_service_spec.rb +25 -0
  154. data/spec/services/solidus_bolt/transactions/authorize_service_spec.rb +117 -0
  155. data/spec/services/solidus_bolt/transactions/base_service_spec.rb +38 -0
  156. data/spec/services/solidus_bolt/transactions/capture_service_spec.rb +37 -0
  157. data/spec/services/solidus_bolt/transactions/detail_service_spec.rb +31 -0
  158. data/spec/services/solidus_bolt/transactions/refund_service_spec.rb +42 -0
  159. data/spec/services/solidus_bolt/transactions/void_service_spec.rb +70 -0
  160. data/spec/services/solidus_bolt/users/refresh_access_token_service_spec.rb +45 -0
  161. data/spec/services/solidus_bolt/users/sync_addresses_service_spec.rb +74 -0
  162. data/spec/services/solidus_bolt/users/sync_payment_sources_service_spec.rb +25 -0
  163. data/spec/services/solidus_bolt/webhooks/create_service_spec.rb +33 -0
  164. data/spec/spec_helper.rb +37 -0
  165. data/spec/support/bolt_configuration.rb +26 -0
  166. data/spec/support/bolt_helper.rb +66 -0
  167. data/spec/support/vcr.rb +29 -0
  168. data/spec/webhooks/solidus_bolt/handlers/base_handler_spec.rb +13 -0
  169. data/spec/webhooks/solidus_bolt/handlers/capture_handler_spec.rb +24 -0
  170. data/spec/webhooks/solidus_bolt/handlers/credit_handler_spec.rb +32 -0
  171. data/spec/webhooks/solidus_bolt/handlers/void_handler_spec.rb +19 -0
  172. data/spec/webhooks/solidus_bolt/sorter_spec.rb +39 -0
  173. metadata +492 -0
@@ -0,0 +1,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 %>