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,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ class BoltsController < Spree::Admin::BaseController
6
+ before_action :bolt_configuration
7
+
8
+ def show; end
9
+
10
+ def edit; end
11
+
12
+ def update
13
+ if @bolt_configuration.update(bolt_configuration_params)
14
+ flash[:success] = t('spree.admin.bolt.updated_successfully')
15
+ redirect_to admin_bolt_path
16
+ else
17
+ flash[:error] = @bolt_configuration.errors.full_messages.to_sentence
18
+ render :edit
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def bolt_configuration
25
+ @bolt_configuration = SolidusBolt::BoltConfiguration.fetch
26
+ end
27
+
28
+ def bolt_configuration_params
29
+ params
30
+ .require(:solidus_bolt_bolt_configuration)
31
+ .permit(
32
+ :bearer_token,
33
+ :environment,
34
+ :merchant_public_id,
35
+ :division_public_id,
36
+ :api_key,
37
+ :signing_secret,
38
+ :publishable_key
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module SpreeCheckoutController
5
+ module AddAddressesToBolt
6
+ def finalize_order
7
+ if session[:bolt_access_token] && current_order.payments.last&.source_type == "SolidusBolt::PaymentSource"
8
+ spree_current_user.addresses.each do |address|
9
+ SolidusBolt::AddAddressJob.perform_later(
10
+ order: current_order,
11
+ access_token: SolidusBolt::Users::RefreshAccessTokenService.call(session: session),
12
+ address: address
13
+ )
14
+ end
15
+ end
16
+
17
+ super
18
+ end
19
+
20
+ Spree::CheckoutController.prepend self
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module SpreeCheckoutController
5
+ module RefreshBoltAddresses
6
+ def before_address
7
+ SolidusBolt::Users::SyncAddressesService.call(
8
+ user: spree_current_user, access_token: SolidusBolt::Users::RefreshAccessTokenService.call(session: session)
9
+ )
10
+
11
+ super
12
+ end
13
+
14
+ Spree::CheckoutController.prepend self
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module SpreeCheckoutController
5
+ module RefreshBoltPaymentSource
6
+ def before_payment
7
+ SolidusBolt::Users::SyncPaymentSourcesService.call(
8
+ user: spree_current_user, access_token: SolidusBolt::Users::RefreshAccessTokenService.call(session: session)
9
+ )
10
+
11
+ super
12
+ end
13
+
14
+ Spree::CheckoutController.prepend self
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module AddressDecorator
5
+ def bolt_address(email)
6
+ {
7
+ street_address1: address1,
8
+ street_address2: address2,
9
+ locality: city,
10
+ region: state.abbr,
11
+ postal_code: zipcode,
12
+ country_code: country.iso,
13
+ first_name: Spree::Address::Name.new(name).first_name,
14
+ last_name: Spree::Address::Name.new(name).last_name,
15
+ phone: phone,
16
+ email: email
17
+ }
18
+ end
19
+
20
+ Spree::Address.prepend(self)
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module LogEntryDecorator
5
+ def parsed_details
6
+ @parsed_details ||= YAML.load(details) # rubocop:disable Security/YAMLLoad
7
+ end
8
+
9
+ Spree::LogEntry.prepend self
10
+ end
11
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module OrderDecorator
5
+ def bolt_cart
6
+ {
7
+ total_amount: display_total.cents,
8
+ order_reference: number,
9
+ currency: currency,
10
+ items: line_items.map do |line_item|
11
+ {
12
+ sku: line_item.sku,
13
+ name: line_item.name,
14
+ unit_price: cents(line_item.price),
15
+ quantity: line_item.quantity
16
+ }
17
+ end
18
+ }
19
+ end
20
+
21
+ def bolt_user_identifier
22
+ {
23
+ email: email,
24
+ phone: bill_address.phone
25
+ }
26
+ end
27
+
28
+ def bolt_user_identity
29
+ name = bill_address.name.split(' ')
30
+ {
31
+ first_name: name.first,
32
+ last_name: name.last
33
+ }
34
+ end
35
+
36
+ private
37
+
38
+ def cents(float)
39
+ (float * 100).to_i
40
+ end
41
+
42
+ Spree::Order.prepend(self)
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module PaymentDecorator
5
+ def can_void?
6
+ super && state == 'pending'
7
+ end
8
+
9
+ Spree::Payment.prepend(self)
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module OmniAuth::Strategies::BoltDecorator # rubocop:disable Style/ClassAndModuleChildren
5
+ def callback_phase
6
+ super.tap do
7
+ session[:bolt_access_token] = @access_token
8
+ session[:bolt_expiration_time] = @expiration_time
9
+ session[:bolt_refresh_token] = @refresh_token
10
+ session[:bolt_refresh_token_scope] = @refresh_token_scope
11
+ end
12
+ end
13
+
14
+ ::OmniAuth::Strategies::Bolt.prepend self
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ class AddAddressJob < ApplicationJob
5
+ def perform(order:, access_token:, address:)
6
+ SolidusBolt::Accounts::AddAddressService.call(
7
+ order: order, access_token: access_token, address: address
8
+ )
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'solidus_bolt'
4
+
5
+ module SolidusBolt
6
+ class BoltConfiguration < ApplicationRecord
7
+ after_commit :update_bolt_config_credentials
8
+
9
+ REGISTER_URL = 'https://merchant.bolt.com/register'
10
+ EVENTS = %w[all payment credit capture void auth pending rejected_irreversible rejected_reversible no_op
11
+ failed_payment newsletter_subscription risk_insights credit_card_deleted].freeze
12
+
13
+ enum environment: { production: 0, sandbox: 1, staging: 2 }
14
+
15
+ validate :config_can_be_created, on: :create
16
+
17
+ def self.fetch
18
+ first_or_create
19
+ end
20
+
21
+ def self.can_create?
22
+ count.zero?
23
+ end
24
+
25
+ def self.config_empty?
26
+ whitelist = %w[id created_at updated_at environment]
27
+
28
+ fetch.attributes.all? do |attr, val|
29
+ whitelist.include?(attr) || val.blank?
30
+ end
31
+ end
32
+
33
+ def environment_url
34
+ case BoltConfiguration.environments[environment]
35
+ when 0 then 'https://api.bolt.com'
36
+ when 1 then 'https://api-sandbox.bolt.com'
37
+ when 2 then 'https://api-staging.bolt.com'
38
+ end
39
+ end
40
+
41
+ def embed_js
42
+ case BoltConfiguration.environments[environment]
43
+ when 0 then 'https://connect.bolt.com/embed.js'
44
+ else 'https://connect-sandbox.bolt.com/embed.js'
45
+ end
46
+ end
47
+
48
+ def account_js
49
+ case BoltConfiguration.environments[environment]
50
+ when 0 then 'https://connect.bolt.com/account.js'
51
+ else
52
+ 'https://connect-sandbox.bolt.com/account.js'
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def update_bolt_config_credentials
59
+ bolt_config_credentials = Spree::Config
60
+ .static_model_preferences
61
+ .for_class(SolidusBolt::PaymentMethod)['bolt_config_credentials']
62
+ &.preferences
63
+ return unless bolt_config_credentials
64
+
65
+ bolt_config_credentials[:bolt_api_key] = api_key
66
+ bolt_config_credentials[:bolt_signing_secret] = signing_secret
67
+ bolt_config_credentials[:bolt_publishable_key] = publishable_key
68
+ end
69
+
70
+ def config_can_be_created
71
+ errors.add(:base, 'Can create only one record for this Model') unless SolidusBolt::BoltConfiguration.can_create?
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ class Gateway
5
+ def initialize(options = {}); end
6
+
7
+ def authorize(_amount, payment_source, gateway_options)
8
+ order_number = gateway_options[:order_id].split('-').first
9
+ order = fetch_spree_order(order_number)
10
+
11
+ authorization_response = ::SolidusBolt::Transactions::AuthorizeService.call(
12
+ order: order,
13
+ create_bolt_account: payment_source.create_bolt_account,
14
+ credit_card: credit_card_params(payment_source),
15
+ payment_method: payment_source.payment_method,
16
+ repeat: payment_source.card_id.present?
17
+ )
18
+
19
+ unless payment_source.card_id
20
+ payment_source.update(card_id: authorization_response['transaction']['from_credit_card']['id'])
21
+ end
22
+
23
+ ActiveMerchant::Billing::Response.new(true, 'Transaction approved', payment_source.attributes,
24
+ authorization: authorization_response['transaction']['reference'])
25
+ rescue ServerError => e
26
+ ActiveMerchant::Billing::Response.new(false, e, {})
27
+ end
28
+
29
+ def capture(float_amount, response_code, gateway_options)
30
+ payment = gateway_options[:originator]
31
+ payment_source = payment.source
32
+ payment_method = payment.payment_method
33
+ currency = gateway_options[:currency]
34
+
35
+ capture_response = SolidusBolt::Transactions::CaptureService.call(
36
+ transaction_reference: response_code,
37
+ amount: float_amount,
38
+ currency: currency,
39
+ payment_method: payment_method
40
+ )
41
+
42
+ ActiveMerchant::Billing::Response.new(
43
+ true,
44
+ 'Transaction Captured', payment_source.attributes,
45
+ authorization: capture_response['reference']
46
+ )
47
+ rescue ServerError => e
48
+ ActiveMerchant::Billing::Response.new(false, e, {})
49
+ end
50
+
51
+ def void(response_code, gateway_options)
52
+ payment = gateway_options[:originator]
53
+ payment_source = payment.source
54
+ payment_method = payment.payment_method
55
+
56
+ void_response = SolidusBolt::Transactions::VoidService.call(
57
+ transaction_reference: response_code,
58
+ payment_method: payment_method
59
+ )
60
+
61
+ ActiveMerchant::Billing::Response.new(
62
+ true,
63
+ 'Transaction Void', payment_source.attributes,
64
+ authorization: void_response['reference']
65
+ )
66
+ rescue ServerError => e
67
+ ActiveMerchant::Billing::Response.new(false, e, {})
68
+ end
69
+
70
+ def credit(amount, response_code, _gateway_options)
71
+ payment = Spree::Payment.find_by(response_code: response_code)
72
+ payment_source = payment.source
73
+ payment_method = payment.payment_method
74
+ currency = payment.currency
75
+
76
+ credit_response = SolidusBolt::Transactions::RefundService.call(
77
+ transaction_reference: response_code,
78
+ amount: amount,
79
+ currency: currency,
80
+ payment_method: payment_method
81
+ )
82
+
83
+ ActiveMerchant::Billing::Response.new(
84
+ true,
85
+ 'Transaction Refunded', payment_source.attributes,
86
+ authorization: credit_response['reference']
87
+ )
88
+ rescue ServerError => e
89
+ ActiveMerchant::Billing::Response.new(false, e, {})
90
+ end
91
+
92
+ def purchase(_float_amount, payment_source, gateway_options)
93
+ order_number = gateway_options[:order_id].split('-').first
94
+ order = fetch_spree_order(order_number)
95
+
96
+ authorization_response = ::SolidusBolt::Transactions::AuthorizeService.call(
97
+ order: order,
98
+ create_bolt_account: payment_source.create_bolt_account,
99
+ credit_card: credit_card_params(payment_source),
100
+ payment_method: payment_source.payment_method,
101
+ auto_capture: true,
102
+ repeat: payment_source.card_id.present?
103
+ )
104
+
105
+ unless payment_source.card_id
106
+ payment_source.update(card_id: authorization_response['transaction']['from_credit_card']['id'])
107
+ end
108
+
109
+ ActiveMerchant::Billing::Response.new(true, 'Transaction approved and captured', payment_source.attributes,
110
+ authorization: authorization_response['transaction']['reference'])
111
+ rescue ServerError => e
112
+ ActiveMerchant::Billing::Response.new(false, e, {})
113
+ end
114
+
115
+ private
116
+
117
+ def fetch_spree_order(order_number)
118
+ Spree::Order.find_by(number: order_number)
119
+ end
120
+
121
+ def credit_card_params(payment_source)
122
+ card_params = { token_type: 'bolt' }
123
+
124
+ payment_source.attributes.each do |k, v|
125
+ next unless k.include?('card_')
126
+
127
+ card_params[k.gsub('card_', '').to_sym] = v unless v.nil?
128
+ end
129
+
130
+ card_params
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ class PaymentMethod < SolidusSupport.payment_method_parent_class
5
+ preference :bolt_api_key, :string
6
+ preference :bolt_signing_secret, :string
7
+ preference :bolt_publishable_key, :string
8
+
9
+ def gateway_class
10
+ ::SolidusBolt::Gateway
11
+ end
12
+
13
+ def payment_source_class
14
+ ::SolidusBolt::PaymentSource
15
+ end
16
+
17
+ def partial_name
18
+ 'bolt'
19
+ end
20
+
21
+ def preferred_bolt_base_url
22
+ bolt_config.embed_js
23
+ end
24
+
25
+ def preferred_bolt_api_url
26
+ bolt_config.environment_url
27
+ end
28
+
29
+ private
30
+
31
+ def bolt_config
32
+ @bolt_config = SolidusBolt::BoltConfiguration.fetch
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'solidus_bolt'
4
+
5
+ module SolidusBolt
6
+ class PaymentSource < SolidusSupport.payment_source_parent_class
7
+ validates :payment_method_id, presence: true
8
+
9
+ def reusable?
10
+ card_id.present?
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ def self.table_name_prefix
5
+ 'solidus_bolt_'
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ <!-- insert_after 'meta[name="viewport"]' -->
2
+
3
+ <meta name="bolt-publishable-key" content="<%= SolidusBolt::BoltConfiguration.fetch.publishable_key %>" />
4
+ <meta name="bolt-domain" content="<%= SolidusBolt::BoltConfiguration.fetch.environment_url%>" />
5
+ <script id="bolt-embedded" type="text/javascript" src="<%= SolidusBolt::BoltConfiguration.fetch.embed_js %>"></script>
6
+ <script id="bolt-account" type="text/javascript" async="" src="<%= SolidusBolt::BoltConfiguration.fetch.account_js %>" data-publishable-key="<%= SolidusBolt::BoltConfiguration.fetch.publishable_key %>"></script>
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Accounts
5
+ class AddAddressService < SolidusBolt::BaseService
6
+ attr_reader :order, :address, :access_token
7
+
8
+ def initialize(order:, address:, access_token:)
9
+ @order = order
10
+ @address = address
11
+ @access_token = access_token
12
+ super
13
+ end
14
+
15
+ def call
16
+ return if address_already_uploaded?
17
+
18
+ add_address
19
+ end
20
+
21
+ private
22
+
23
+ def bolt_addresses
24
+ @bolt_addresses ||= SolidusBolt::Accounts::DetailService.call(access_token: access_token)['addresses']
25
+ end
26
+
27
+ def address_already_uploaded?
28
+ bolt_addresses.any? do |bolt_address|
29
+ bolt_address['street_address1'] == address.address1 &&
30
+ bolt_address['locality'] == address.city &&
31
+ bolt_address['region'] == address.state&.abbr &&
32
+ bolt_address['postal_code'] == address.zipcode
33
+ end
34
+ end
35
+
36
+ def add_address
37
+ handle_result(HTTParty.post("#{api_base_url}/#{api_version}/account/addresses", build_options))
38
+ end
39
+
40
+ def address_body
41
+ address.bolt_address(order.email).to_json
42
+ end
43
+
44
+ def build_options
45
+ {
46
+ body: address_body,
47
+ headers: {
48
+ 'Authorization' => "Bearer #{access_token}",
49
+ 'Content-Type' => 'application/json'
50
+ }.merge(authentication_header)
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Accounts
5
+ class AddPaymentMethodService < SolidusBolt::BaseService
6
+ attr_reader :access_token, :credit_card, :address, :email
7
+
8
+ def initialize(access_token:, credit_card:, address:, email:)
9
+ @access_token = access_token
10
+ @credit_card = credit_card
11
+ @address = address
12
+ @email = email
13
+ super
14
+ end
15
+
16
+ def call
17
+ add_payment_method
18
+ end
19
+
20
+ private
21
+
22
+ def add_payment_method
23
+ handle_result(HTTParty.post("#{api_base_url}/#{api_version}/account/payment_methods", build_options))
24
+ end
25
+
26
+ def body
27
+ credit_card.merge(billing_address: address_body).to_json
28
+ end
29
+
30
+ def address_body
31
+ address.bolt_address(email)
32
+ end
33
+
34
+ def build_options
35
+ {
36
+ headers: {
37
+ 'Authorization' => "Bearer #{access_token}",
38
+ 'Content-Type' => 'application/json'
39
+ }.merge(authentication_header),
40
+ body: body
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Accounts
5
+ class DetailService < SolidusBolt::BaseService
6
+ attr_reader :access_token
7
+
8
+ def initialize(access_token:)
9
+ @access_token = access_token
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}/account",
24
+ options
25
+ )
26
+ )
27
+ end
28
+
29
+ def build_options
30
+ {
31
+ headers: {
32
+ 'Authorization' => "Bearer #{access_token}",
33
+ }.merge(authentication_header)
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusBolt
4
+ module Accounts
5
+ class DetectAccountService < SolidusBolt::BaseService
6
+ attr_reader :email
7
+
8
+ def initialize(email:)
9
+ @email = email
10
+ super
11
+ end
12
+
13
+ def call
14
+ detect_account
15
+ end
16
+
17
+ private
18
+
19
+ def detect_account
20
+ options = build_options
21
+ handle_result(
22
+ HTTParty.get(
23
+ "#{api_base_url}/#{api_version}/account/exists",
24
+ options
25
+ )
26
+ )
27
+ end
28
+
29
+ def build_options
30
+ { query: { email: email } }
31
+ end
32
+ end
33
+ end
34
+ end