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,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