spree_adyen 0.10.0

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +9 -0
  3. data/README.md +59 -0
  4. data/Rakefile +26 -0
  5. data/app/assets/config/spree_adyen_manifest.js +5 -0
  6. data/app/controllers/spree_adyen/apple_pay_domain_verification_controller.rb +11 -0
  7. data/app/controllers/spree_adyen/payment_sessions_controller.rb +67 -0
  8. data/app/controllers/spree_adyen/store_controller_decorator.rb +9 -0
  9. data/app/controllers/spree_adyen/webhooks_controller.rb +46 -0
  10. data/app/helpers/spree_adyen/base_helper.rb +18 -0
  11. data/app/javascript/spree_adyen/application.js +17 -0
  12. data/app/javascript/spree_adyen/controllers/checkout_adyen_controller.js +80 -0
  13. data/app/javascript/spree_adyen/controllers/redirect_adyen_controller.js +35 -0
  14. data/app/jobs/spree_adyen/add_allowed_origin_job.rb +24 -0
  15. data/app/jobs/spree_adyen/base_job.rb +5 -0
  16. data/app/jobs/spree_adyen/webhooks/process_authorisation_event_job.rb +10 -0
  17. data/app/jobs/spree_adyen/webhooks/process_cancellation_event_job.rb +10 -0
  18. data/app/jobs/spree_adyen/webhooks/process_capture_event_job.rb +10 -0
  19. data/app/models/spree/payment_sessions/adyen.rb +59 -0
  20. data/app/models/spree_adyen/base.rb +6 -0
  21. data/app/models/spree_adyen/custom_domain_decorator.rb +17 -0
  22. data/app/models/spree_adyen/gateway/payment_sessions.rb +151 -0
  23. data/app/models/spree_adyen/gateway.rb +487 -0
  24. data/app/models/spree_adyen/order_decorator.rb +23 -0
  25. data/app/models/spree_adyen/payment_decorator.rb +66 -0
  26. data/app/models/spree_adyen/payment_method_decorator.rb +15 -0
  27. data/app/models/spree_adyen/payment_session.rb +121 -0
  28. data/app/models/spree_adyen/payment_sources/ach_direct_debit.rb +17 -0
  29. data/app/models/spree_adyen/payment_sources/affirm.rb +17 -0
  30. data/app/models/spree_adyen/payment_sources/afterpay.rb +17 -0
  31. data/app/models/spree_adyen/payment_sources/alipay.rb +19 -0
  32. data/app/models/spree_adyen/payment_sources/alipay_hk.rb +17 -0
  33. data/app/models/spree_adyen/payment_sources/alma.rb +17 -0
  34. data/app/models/spree_adyen/payment_sources/ancv.rb +17 -0
  35. data/app/models/spree_adyen/payment_sources/apple_pay.rb +19 -0
  36. data/app/models/spree_adyen/payment_sources/atome.rb +17 -0
  37. data/app/models/spree_adyen/payment_sources/bacs.rb +17 -0
  38. data/app/models/spree_adyen/payment_sources/bancontact.rb +19 -0
  39. data/app/models/spree_adyen/payment_sources/bank_transfer.rb +17 -0
  40. data/app/models/spree_adyen/payment_sources/base.rb +7 -0
  41. data/app/models/spree_adyen/payment_sources/bcmc.rb +17 -0
  42. data/app/models/spree_adyen/payment_sources/bcmc_mobile.rb +17 -0
  43. data/app/models/spree_adyen/payment_sources/benefit.rb +17 -0
  44. data/app/models/spree_adyen/payment_sources/billie.rb +17 -0
  45. data/app/models/spree_adyen/payment_sources/bizum.rb +17 -0
  46. data/app/models/spree_adyen/payment_sources/blik.rb +15 -0
  47. data/app/models/spree_adyen/payment_sources/boleto.rb +17 -0
  48. data/app/models/spree_adyen/payment_sources/cash_app_afterpay.rb +17 -0
  49. data/app/models/spree_adyen/payment_sources/cashapp.rb +17 -0
  50. data/app/models/spree_adyen/payment_sources/clearpay.rb +17 -0
  51. data/app/models/spree_adyen/payment_sources/dana.rb +17 -0
  52. data/app/models/spree_adyen/payment_sources/doku.rb +19 -0
  53. data/app/models/spree_adyen/payment_sources/duitnow.rb +17 -0
  54. data/app/models/spree_adyen/payment_sources/ebanking_fi.rb +19 -0
  55. data/app/models/spree_adyen/payment_sources/eft_directdebit_ca.rb +19 -0
  56. data/app/models/spree_adyen/payment_sources/eftpos_australia.rb +19 -0
  57. data/app/models/spree_adyen/payment_sources/elo.rb +19 -0
  58. data/app/models/spree_adyen/payment_sources/eps.rb +19 -0
  59. data/app/models/spree_adyen/payment_sources/fastlane.rb +19 -0
  60. data/app/models/spree_adyen/payment_sources/fpx.rb +19 -0
  61. data/app/models/spree_adyen/payment_sources/gcash.rb +19 -0
  62. data/app/models/spree_adyen/payment_sources/gift_cards.rb +19 -0
  63. data/app/models/spree_adyen/payment_sources/giropay.rb +19 -0
  64. data/app/models/spree_adyen/payment_sources/givex.rb +19 -0
  65. data/app/models/spree_adyen/payment_sources/gopay_wallet.rb +19 -0
  66. data/app/models/spree_adyen/payment_sources/grabpay.rb +19 -0
  67. data/app/models/spree_adyen/payment_sources/grabpay_paylater.rb +19 -0
  68. data/app/models/spree_adyen/payment_sources/hipercard.rb +19 -0
  69. data/app/models/spree_adyen/payment_sources/ideal.rb +15 -0
  70. data/app/models/spree_adyen/payment_sources/kakaopay.rb +19 -0
  71. data/app/models/spree_adyen/payment_sources/kcp_naverpay.rb +19 -0
  72. data/app/models/spree_adyen/payment_sources/klarna.rb +32 -0
  73. data/app/models/spree_adyen/payment_sources/oney.rb +13 -0
  74. data/app/models/spree_adyen/payment_sources/online_banking_czech_republic.rb +15 -0
  75. data/app/models/spree_adyen/payment_sources/online_banking_poland.rb +15 -0
  76. data/app/models/spree_adyen/payment_sources/pay_by_bank.rb +15 -0
  77. data/app/models/spree_adyen/payment_sources/paypal.rb +15 -0
  78. data/app/models/spree_adyen/payment_sources/paypo.rb +15 -0
  79. data/app/models/spree_adyen/payment_sources/paysafecard.rb +15 -0
  80. data/app/models/spree_adyen/payment_sources/rate_pay_direct_debit.rb +15 -0
  81. data/app/models/spree_adyen/payment_sources/riverty.rb +15 -0
  82. data/app/models/spree_adyen/payment_sources/samsung_pay.rb +15 -0
  83. data/app/models/spree_adyen/payment_sources/scalapay.rb +13 -0
  84. data/app/models/spree_adyen/payment_sources/sepa_direct_debit.rb +15 -0
  85. data/app/models/spree_adyen/payment_sources/trustly.rb +15 -0
  86. data/app/models/spree_adyen/payment_sources/unknown.rb +19 -0
  87. data/app/models/spree_adyen/payment_sources/wechat_pay.rb +15 -0
  88. data/app/models/spree_adyen/store_decorator.rb +17 -0
  89. data/app/presenters/spree_adyen/address_presenter.rb +21 -0
  90. data/app/presenters/spree_adyen/application_info_presenter.rb +21 -0
  91. data/app/presenters/spree_adyen/cancel_payload_presenter.rb +31 -0
  92. data/app/presenters/spree_adyen/capture_payload_presenter.rb +36 -0
  93. data/app/presenters/spree_adyen/checkout_presenter.rb +51 -0
  94. data/app/presenters/spree_adyen/payment_sessions/request_payload_presenter.rb +140 -0
  95. data/app/presenters/spree_adyen/payments/request_payload_presenter.rb +85 -0
  96. data/app/presenters/spree_adyen/refund_payload_presenter.rb +38 -0
  97. data/app/presenters/spree_adyen/webhook_payload_presenter.rb +28 -0
  98. data/app/presenters/spree_adyen/webhooks/credit_card_presenter.rb +43 -0
  99. data/app/services/spree_adyen/gateways/add_allowed_origin.rb +36 -0
  100. data/app/services/spree_adyen/gateways/configuration.rb +41 -0
  101. data/app/services/spree_adyen/gateways/configure.rb +60 -0
  102. data/app/services/spree_adyen/payment_sessions/find_or_create.rb +51 -0
  103. data/app/services/spree_adyen/payment_sessions/process_with_result.rb +49 -0
  104. data/app/services/spree_adyen/webhooks/actions/create_source.rb +112 -0
  105. data/app/services/spree_adyen/webhooks/actions/find_or_create_credit_card.rb +33 -0
  106. data/app/services/spree_adyen/webhooks/event.rb +104 -0
  107. data/app/services/spree_adyen/webhooks/event_processors/authorisation_event_processor.rb +69 -0
  108. data/app/services/spree_adyen/webhooks/event_processors/cancellation_event_processor.rb +49 -0
  109. data/app/services/spree_adyen/webhooks/event_processors/capture_event_processor.rb +48 -0
  110. data/app/services/spree_adyen/webhooks/handle_event.rb +43 -0
  111. data/app/services/spree_adyen/webhooks/standard_hmac_validator.rb +37 -0
  112. data/app/views/layouts/spree_adyen/default.html.erb +14 -0
  113. data/app/views/spree/admin/payment_methods/configuration_guides/_spree_adyen.html.erb +14 -0
  114. data/app/views/spree/admin/payment_methods/custom_form_fields/_spree_adyen.html.erb +18 -0
  115. data/app/views/spree/admin/payment_methods/descriptions/_spree_adyen.html.erb +12 -0
  116. data/app/views/spree/admin/payments/source_forms/_spree_adyen.html.erb +7 -0
  117. data/app/views/spree/checkout/payment/_spree_adyen.html.erb +40 -0
  118. data/app/views/spree_adyen/_drop_in.html.erb +12 -0
  119. data/app/views/spree_adyen/_head.html.erb +2 -0
  120. data/app/views/spree_adyen/payment_sessions/redirect.html.erb +5 -0
  121. data/config/importmap.rb +7 -0
  122. data/config/initializers/spree.rb +34 -0
  123. data/config/initializers/spree_permitted_attributes.rb +5 -0
  124. data/config/locales/en.yml +11 -0
  125. data/config/routes.rb +35 -0
  126. data/db/migrate/20250630150000_setup_spree_adyen_models.rb +17 -0
  127. data/db/migrate/20250811140113_add_channel_to_adyen_payment_sessions.rb +10 -0
  128. data/db/migrate/20250813152608_add_return_url_to_spree_adyen_payment_sessions.rb +15 -0
  129. data/lib/generators/spree_adyen/install/install_generator.rb +20 -0
  130. data/lib/spree_adyen/configuration.rb +42 -0
  131. data/lib/spree_adyen/engine.rb +79 -0
  132. data/lib/spree_adyen/factories.rb +3 -0
  133. data/lib/spree_adyen/testing_support/factories/gateway_factory.rb +29 -0
  134. data/lib/spree_adyen/testing_support/factories/payment_session_factory.rb +40 -0
  135. data/lib/spree_adyen/version.rb +7 -0
  136. data/lib/spree_adyen.rb +39 -0
  137. data/lib/spree_api_v2/spree/api/v2/storefront/adyen/base_controller.rb +21 -0
  138. data/lib/spree_api_v2/spree/api/v2/storefront/adyen/payment_sessions_controller.rb +72 -0
  139. data/lib/spree_api_v2/spree_adyen/api/v2/storefront/payment_session_serializer.rb +21 -0
  140. data/vendor/javascript/@adyen--adyen-web.js +4 -0
  141. data/vendor/stylesheets/adyen.css +106 -0
  142. metadata +301 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: efdb74fe9f9d7848da506be961b930c3b2352d6726a1e5fdf81a160ee1688fc6
4
+ data.tar.gz: 997de8336c6d59668a1830c576d26778600bf20dcdf79c4307a64b22ee4ab69f
5
+ SHA512:
6
+ metadata.gz: 9d78966a7463a76cf3dd572d50b09845928be5e6842c30f79cb8139f1c47489cef675c8689ee5b9aaf21a7ec79026d80a081ca8f20e1204ab65f274c5f5f8174
7
+ data.tar.gz: 90c1367fa54b80f8194252cfe03e19157d38933a6ed64cf6d18e00467ad43c643cf97b9974626bdf814ac250174e8dec89e96088df70317d03b4f287dc23b765
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vendo Connect Inc., Vendo Sp. z o.o.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # Spree Adyen
2
+
3
+ Official Adyen payments platform integration for [Spree Commerce](https://spreecommerce.org).
4
+
5
+ ## Installation
6
+
7
+ 1. Add this extension to your Gemfile with this line:
8
+
9
+ ```ruby
10
+ bundle add spree_adyen
11
+ ```
12
+
13
+ 2. Run the install generator
14
+
15
+ ```ruby
16
+ bundle exec rails g spree_adyen:install
17
+ ```
18
+
19
+ 3. Restart your server
20
+
21
+ If your server was running, restart it so that it can find the assets properly.
22
+
23
+ ## Developing
24
+
25
+ 1. Create a dummy app
26
+
27
+ ```bash
28
+ bundle update
29
+ bundle exec rake test_app
30
+ ```
31
+
32
+ 2. Add your new code
33
+ 3. Run tests
34
+
35
+ ```bash
36
+ bundle exec rspec
37
+ ```
38
+
39
+ When testing your applications integration with this extension you may use it's factories.
40
+ Simply add this require statement to your spec_helper:
41
+
42
+ ```ruby
43
+ require 'spree_adyen/factories'
44
+ ```
45
+
46
+ ## Releasing a new version
47
+
48
+ ```shell
49
+ bundle exec gem bump -p -t
50
+ bundle exec gem release
51
+ ```
52
+
53
+ For more options please see [gem-release README](https://github.com/svenfuchs/gem-release)
54
+
55
+ ## Contributing
56
+
57
+ If you'd like to contribute, please take a look at the
58
+ [instructions](CONTRIBUTING.md) for installing dependencies and crafting a good
59
+ pull request.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/extension_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task :default do
10
+ if Dir['spec/dummy'].empty?
11
+ Rake::Task[:test_app].invoke
12
+ Dir.chdir('../../')
13
+ end
14
+ Rake::Task[:spec].invoke
15
+ end
16
+
17
+ desc 'Generates a dummy app for testing'
18
+ task :test_app do
19
+ ENV['LIB_NAME'] = 'spree_adyen'
20
+ Rake::Task['extension:test_app'].execute(
21
+ install_admin: true
22
+ )
23
+ system({ 'BUNDLE_GEMFILE' => File.expand_path('Gemfile', __dir__) }, 'rails g spree_posts:install')
24
+ system({ 'BUNDLE_GEMFILE' => File.expand_path('Gemfile', __dir__) }, 'rails g spree_legacy_product_properties:install')
25
+ system({ 'BUNDLE_GEMFILE' => File.expand_path('Gemfile', __dir__) }, 'rails g spree_legacy_api_v2:install')
26
+ end
@@ -0,0 +1,5 @@
1
+ //= link_tree ../images
2
+ //= link spree_adyen/application.js
3
+ //= link_tree ../../javascript/spree_adyen/controllers .js
4
+ //= link_tree ../../../vendor/javascript .js
5
+ //= link_tree ../../../vendor/stylesheets .css
@@ -0,0 +1,11 @@
1
+ module SpreeAdyen
2
+ class ApplePayDomainVerificationController < ::Spree::BaseController
3
+ def show
4
+ gateway = current_store.adyen_gateway
5
+
6
+ raise ActiveRecord::RecordNotFound if gateway.nil? || !gateway.apple_developer_merchantid_domain_association.attached?
7
+
8
+ render plain: gateway.apple_domain_association_file_content
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ # this is the endpoint that Adyen JS SDK will redirect customer to after payment
2
+ # it will handle the payment session status and process the payment
3
+
4
+ module SpreeAdyen
5
+ class PaymentSessionsController < defined?(Spree::StoreController) ? Spree::StoreController : Spree::BaseController
6
+ include Spree::CheckoutAnalyticsHelper if defined?(Spree::CheckoutAnalyticsHelper)
7
+
8
+ # GET /adyen/payment_sessions/redirect
9
+ def redirect
10
+ @payment_session = SpreeAdyen::PaymentSession.find_by(adyen_id: params[:sessionId])
11
+ render layout: 'spree_adyen/default'
12
+ end
13
+
14
+ # GET /adyen/payment_sessions
15
+ def show
16
+ @payment_session = SpreeAdyen::PaymentSession.find_by!(adyen_id: params[:sessionId])
17
+ @order = @payment_session.order
18
+ # handle duplicated requests or already processed through webhook
19
+ if @payment_session.canceled? || @order.canceled?
20
+ redirect_to spree.cart_path, status: :see_other
21
+ return
22
+ elsif @order.completed?
23
+ redirect_to spree.checkout_complete_path(@order.token), status: :see_other
24
+ return
25
+ end
26
+
27
+ SpreeAdyen::PaymentSessions::ProcessWithResult.new(payment_session: @payment_session, session_result: params[:sessionResult]).call
28
+
29
+ if @payment_session.completed?
30
+ handle_success
31
+ elsif @payment_session.pending?
32
+ handle_pending_payment
33
+ elsif @payment_session.canceled?
34
+ redirect_to spree.checkout_path(@order.token), status: :see_other
35
+ elsif @payment_session.refused?
36
+ handle_failure
37
+ end
38
+ rescue Spree::Core::GatewayError => e
39
+ handle_failure(e.message)
40
+ end
41
+
42
+ private
43
+
44
+ # TODO: handle pending payment
45
+ def handle_pending_payment; end
46
+
47
+ def handle_success
48
+ # update the payment session status
49
+
50
+ # set the session flag to indicate that the order was placed now
51
+ track_checkout_completed if @order.completed? && defined?(track_checkout_completed)
52
+
53
+ redirect_to spree.checkout_complete_path(@order.token), status: :see_other
54
+ end
55
+
56
+ def handle_failure(message = nil)
57
+ flash[:error] = message || Spree.t("adyen.payment_session_errors.#{@payment_session.status}")
58
+
59
+ Rails.logger.error("Payment failed for order #{@order.id}: #{@payment_session.status}")
60
+
61
+ # this should be a rare race condition, but we need to handle it
62
+ @order.payments.valid.find_by(response_code: @payment_session.id)&.void!
63
+
64
+ redirect_to spree.checkout_path(@order.token), status: :see_other
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,9 @@
1
+ module SpreeAdyen
2
+ module StoreControllerDecorator
3
+ def self.prepended(base)
4
+ base.helper SpreeAdyen::BaseHelper
5
+ end
6
+ end
7
+ end
8
+
9
+ Spree::StoreController.prepend(SpreeAdyen::StoreControllerDecorator) if defined?(Spree::StoreController)
@@ -0,0 +1,46 @@
1
+ module SpreeAdyen
2
+ class WebhooksController < ActionController::API
3
+ include Spree::Core::ControllerHelpers::Store
4
+
5
+ before_action :validate_hmac!
6
+
7
+ def create
8
+ SpreeAdyen::Webhooks::HandleEvent.new(event_payload: webhook_params).call
9
+
10
+ head :ok
11
+ end
12
+
13
+ private
14
+
15
+ def validate_hmac!
16
+ if hmac_validator_class.nil?
17
+ Rails.logger.info("[SpreeAdyen][#{event_code}]: Skipping not supported event")
18
+ head :ok
19
+ return
20
+ end
21
+
22
+ validator = hmac_validator_class.new(
23
+ request: request,
24
+ params: webhook_params,
25
+ gateway: current_store.adyen_gateway
26
+ )
27
+
28
+ return if validator.call
29
+
30
+ Rails.logger.error("[SpreeAdyen]: Failed to validate hmac for #{event_code}")
31
+ head :unauthorized
32
+ end
33
+
34
+ def hmac_validator_class
35
+ SpreeAdyen.hmac_validators[event_code]
36
+ end
37
+
38
+ def event_code
39
+ webhook_params.dig('notificationItems', 0, 'NotificationRequestItem', 'eventCode') || webhook_params['type']
40
+ end
41
+
42
+ def webhook_params
43
+ @webhook_params ||= JSON.parse(request.raw_post).with_indifferent_access
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ module SpreeAdyen
2
+ module BaseHelper
3
+ def current_adyen_gateway
4
+ @current_adyen_gateway ||= current_store.adyen_gateway
5
+ end
6
+
7
+ def current_adyen_payment_session
8
+ return if current_adyen_gateway.nil?
9
+
10
+ @current_adyen_payment_session ||= SpreeAdyen::PaymentSessions::FindOrCreate.new(
11
+ order: @order,
12
+ amount: @order.total_minus_store_credits,
13
+ user: @order.user,
14
+ payment_method: current_adyen_gateway
15
+ ).call&.value
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ import '@hotwired/turbo-rails'
2
+
3
+ let application
4
+
5
+ if (typeof window.Stimulus === "undefined") {
6
+ application = Application.start()
7
+ application.debug = false
8
+ window.Stimulus = application
9
+ } else {
10
+ application = window.Stimulus
11
+ }
12
+
13
+ import CheckoutAdyenController from 'spree_adyen/controllers/checkout_adyen_controller'
14
+ import RedirectAdyenController from 'spree_adyen/controllers/redirect_adyen_controller'
15
+
16
+ application.register('checkout-adyen', CheckoutAdyenController);
17
+ application.register('redirect-adyen', RedirectAdyenController);
@@ -0,0 +1,80 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ import { AdyenCheckout, Dropin } from '@adyen/adyen-web/auto'
4
+
5
+ export default class extends Controller {
6
+ async connect() {
7
+ await this.initCheckout();
8
+ this.initEventHandlers();
9
+ if (this.dropinElement().dataset.checkoutAdyenAutoMountValue == 'true') {
10
+ this.initDropin();
11
+ }
12
+ }
13
+
14
+ dropinElement() {
15
+ return document.getElementById('dropin-container');
16
+ }
17
+
18
+ async initCheckout() {
19
+ const session = JSON.parse(this.dropinElement().dataset.checkoutAdyenCheckoutAttrubutes)
20
+ const eventHandlers = {
21
+ onPaymentCompleted: (result, _component) => {
22
+ // idk why 3ds are handled here instead of paymentMethodsConfiguration.card.onPaymentCompleted
23
+ this.redirectToPaymentSession(result);
24
+ },
25
+ onError: (error, component) => {
26
+ console.error(error.name, error.message, error.stack, component);
27
+ }
28
+ }
29
+ const adyenCheckout = await AdyenCheckout(Object.assign(session, eventHandlers));;
30
+ this.adyenCheckout = adyenCheckout;
31
+ }
32
+
33
+ redirectToPaymentSession(result) {
34
+ const url = new URL(this.dropinElement().dataset.checkoutAdyenPaymentSessionPathValue);
35
+ url.searchParams.set('sessionResult', result.sessionResult);
36
+ window.location.replace(url.href);
37
+ }
38
+
39
+ async initDropin() {
40
+ const dropinConfiguration = {
41
+ showStoredPaymentMethods: false,
42
+ paymentMethodsConfiguration: {
43
+ card: {
44
+ onPaymentCompleted: (result, component) => {
45
+ this.redirectToPaymentSession(result);
46
+ },
47
+ onError: (error, component) => {
48
+ console.error(error.name, error.message, error.stack, component);
49
+ },
50
+ showPayButton: false,
51
+ hasHolderName: true,
52
+ }
53
+ }
54
+ };
55
+ const dropin = new Dropin(this.adyenCheckout, dropinConfiguration)
56
+ dropin.mount('#dropin-container');
57
+ this.dropin = dropin;
58
+ }
59
+
60
+ initEventHandlers() {
61
+ document.getElementById('checkout-payment-submit').addEventListener('click', (e) => {
62
+ if (this.dropinElement().classList.contains('hidden')) {
63
+ return;
64
+ }
65
+ e.preventDefault();
66
+ this.dropin.submit();
67
+ });
68
+
69
+ document.querySelectorAll('#existing_cards input[type="radio"]').forEach(elem => elem.addEventListener("change", (e) => {
70
+ if (e.target.dataset.show == 'dropin-container') {
71
+ this.initDropin();
72
+ this.dropinElement().classList.remove('hidden');
73
+ }
74
+ else if (this.dropin) {
75
+ this.dropin.unmount();
76
+ this.dropinElement().classList.add('hidden');
77
+ }
78
+ }))
79
+ }
80
+ }
@@ -0,0 +1,35 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { AdyenCheckout } from '@adyen/adyen-web/auto'
3
+ export default class extends Controller {
4
+ async connect() {
5
+ await this.initCheckout();
6
+ }
7
+
8
+ redirectElement() {
9
+ return document.getElementById('dropin-container');
10
+ }
11
+
12
+ async initCheckout() {
13
+ const session = JSON.parse(this.redirectElement().dataset.checkoutAdyenCheckoutAttrubutes)
14
+ const urlParams = new URLSearchParams(window.location.search);
15
+ const redirectResult = urlParams.get('redirectResult');
16
+
17
+ const eventHandlers = {
18
+ onPaymentCompleted: (result, component) => {
19
+ this.redirectToPaymentSession(result);
20
+ },
21
+ onPaymentFailed: (result, component) => {
22
+ this.redirectToPaymentSession(result);
23
+ },
24
+ }
25
+ const configuration = Object.assign(session, eventHandlers);
26
+ const adyenCheckout = await AdyenCheckout(configuration);
27
+ adyenCheckout.submitDetails({ details: { redirectResult: redirectResult } });
28
+ }
29
+
30
+ redirectToPaymentSession(result) {
31
+ const url = new URL(this.redirectElement().dataset.checkoutAdyenPaymentSessionPathValue);
32
+ url.searchParams.set('sessionResult', result.sessionResult);
33
+ window.location.replace(url.href);
34
+ }
35
+ }
@@ -0,0 +1,24 @@
1
+ module SpreeAdyen
2
+ class AddAllowedOriginJob < SpreeAdyen::BaseJob
3
+ def perform(record_id, gateway_id, klass_type = 'store')
4
+ @klass_type = klass_type.to_s
5
+ return unless klass
6
+
7
+ record = klass.find(record_id)
8
+ gateway = SpreeAdyen::Gateway.find(gateway_id)
9
+
10
+ SpreeAdyen::Gateways::AddAllowedOrigin.new(record, gateway).call
11
+ end
12
+
13
+ private
14
+
15
+ def klass
16
+ @klass ||= case @klass_type
17
+ when 'store' then Spree::Store
18
+ when 'custom_domain' then defined?(Spree::CustomDomain) ? Spree::CustomDomain : nil
19
+ else
20
+ Rails.error.unexpected("Unexpected klass_type: #{@klass_type}")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module SpreeAdyen
2
+ class BaseJob < Spree::BaseJob
3
+ queue_as SpreeAdyen.queue
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeAdyen
2
+ module Webhooks
3
+ class ProcessAuthorisationEventJob < SpreeAdyen::BaseJob
4
+ def perform(payload)
5
+ event = SpreeAdyen::Webhooks::Event.new(event_data: payload)
6
+ SpreeAdyen::Webhooks::EventProcessors::AuthorisationEventProcessor.new(event).call
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeAdyen
2
+ module Webhooks
3
+ class ProcessCancellationEventJob < SpreeAdyen::BaseJob
4
+ def perform(payload)
5
+ event = SpreeAdyen::Webhooks::Event.new(event_data: payload)
6
+ SpreeAdyen::Webhooks::EventProcessors::CancellationEventProcessor.new(event).call
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeAdyen
2
+ module Webhooks
3
+ class ProcessCaptureEventJob < SpreeAdyen::BaseJob
4
+ def perform(payload)
5
+ event = SpreeAdyen::Webhooks::Event.new(event_data: payload)
6
+ SpreeAdyen::Webhooks::EventProcessors::CaptureEventProcessor.new(event).call
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,59 @@
1
+ module Spree
2
+ class PaymentSessions::Adyen < PaymentSession
3
+ AVAILABLE_CHANNELS = {
4
+ ios: 'iOS',
5
+ android: 'Android',
6
+ web: 'Web'
7
+ }.freeze
8
+
9
+ # Adyen-specific accessors from external_data
10
+ def adyen_id
11
+ external_id
12
+ end
13
+
14
+ def session_data
15
+ external_data&.dig('session_data')
16
+ end
17
+
18
+ def client_key
19
+ payment_method.preferred_client_key
20
+ end
21
+
22
+ def channel
23
+ external_data&.dig('channel') || AVAILABLE_CHANNELS[:web]
24
+ end
25
+
26
+ def return_url
27
+ external_data&.dig('return_url')
28
+ end
29
+
30
+ def accepted?
31
+ status == 'completed'
32
+ end
33
+
34
+ def successful?
35
+ status == 'completed'
36
+ end
37
+
38
+ def find_or_create_payment!(metadata = {})
39
+ return unless persisted?
40
+ return payment if payment.present?
41
+
42
+ order.with_lock do
43
+ existing_payment = order.payments.where(
44
+ payment_method: payment_method,
45
+ response_code: external_id
46
+ ).first
47
+
48
+ return existing_payment if existing_payment.present?
49
+
50
+ order.payments.create!(
51
+ payment_method: payment_method,
52
+ amount: amount,
53
+ response_code: external_id,
54
+ skip_source_requirement: true
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module SpreeAdyen
2
+ class Base < Spree.base_class
3
+ self.abstract_class = true
4
+ self.table_name_prefix = 'spree_adyen_'
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ module SpreeAdyen
2
+ module CustomDomainDecorator
3
+ def self.prepended(base)
4
+ base.after_commit :add_adyen_allowed_origin
5
+ end
6
+
7
+ def add_adyen_allowed_origin
8
+ return if store.adyen_gateway.blank?
9
+
10
+ SpreeAdyen::AddAllowedOriginJob.perform_later(id, store.adyen_gateway.id, 'custom_domain')
11
+ end
12
+ end
13
+ end
14
+
15
+ if defined?(Spree::CustomDomain)
16
+ Spree::CustomDomain.prepend(SpreeAdyen::CustomDomainDecorator)
17
+ end