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
@@ -0,0 +1,21 @@
1
+ # see: https://docs.adyen.com/partners/application-information/?tab=partner-built_0_1#application-information-fields
2
+ # some endpoints used in SpreeAdyen does not support applicationInfo (e.g. creating webhook)
3
+ module SpreeAdyen
4
+ class ApplicationInfoPresenter
5
+ def to_h
6
+ {
7
+ applicationInfo: {
8
+ externalPlatform: {
9
+ name: 'Spree Commerce',
10
+ version: Spree.version,
11
+ integrator: 'Vendo Sp. z o.o.'
12
+ },
13
+ merchantApplication: {
14
+ name: defined?(SpreeEnterprise) ? 'Enterprise Edition' : 'Community Edition',
15
+ version: SpreeAdyen.version
16
+ }
17
+ }
18
+ }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ module SpreeAdyen
2
+ class CancelPayloadPresenter
3
+ REFERENCE_SUFFIX = 'cancel'.freeze
4
+
5
+ def initialize(payment:, payment_method:)
6
+ @payment = payment
7
+ @order = payment.order
8
+ @payment_method = payment_method
9
+ end
10
+
11
+ def to_h
12
+ {
13
+ reference: reference,
14
+ merchantAccount: payment_method.preferred_merchant_account
15
+ }.merge!(SpreeAdyen::ApplicationInfoPresenter.new.to_h)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :payment, :order, :payment_method
21
+
22
+ def reference
23
+ [
24
+ order.number,
25
+ payment_method.id,
26
+ payment.response_code,
27
+ REFERENCE_SUFFIX
28
+ ].join('_')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ module SpreeAdyen
2
+ class CapturePayloadPresenter
3
+ REFERENCE_SUFFIX = 'capture'.freeze
4
+
5
+ def initialize(amount_in_cents:, payment:, payment_method:)
6
+ @amount_in_cents = amount_in_cents
7
+ @payment = payment
8
+ @order = payment.order
9
+ @payment_method = payment_method
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ amount: {
15
+ value: amount_in_cents,
16
+ currency: payment.currency
17
+ },
18
+ reference: reference,
19
+ merchantAccount: payment_method.preferred_merchant_account
20
+ }.merge!(SpreeAdyen::ApplicationInfoPresenter.new.to_h)
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :amount_in_cents, :payment, :order, :payment_method
26
+
27
+ def reference
28
+ [
29
+ order.number,
30
+ payment_method.id,
31
+ payment.response_code,
32
+ REFERENCE_SUFFIX
33
+ ].join('_')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,51 @@
1
+ module SpreeAdyen
2
+ # use this serializer to configure the Adyen Drop-in component
3
+ # https://docs.adyen.com/online-payments/build-your-integration/sessions-flow/?platform=Web&integration=Drop-in&version=6.18.1&tab=embed_script_and_stylesheet_1_2#configure
4
+ class CheckoutPresenter
5
+ def initialize(payment_session)
6
+ @payment_session = payment_session
7
+ end
8
+
9
+ def to_json(*_args)
10
+ @to_json ||= to_h.to_json
11
+ end
12
+
13
+ def to_h
14
+ @to_h ||= {
15
+ session: {
16
+ id: payment_session.adyen_id,
17
+ sessionData: payment_session.adyen_data
18
+ },
19
+ environment: payment_session.payment_method.environment,
20
+
21
+ amount: {
22
+ value: Spree::Money.new(payment_session.amount, currency: currency).cents,
23
+ currency: currency
24
+ },
25
+ countryCode: country_iso,
26
+ locale: locale,
27
+ clientKey: payment_session.payment_method.preferred_client_key,
28
+ showPayButton: true
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :payment_session
35
+
36
+ delegate :currency, :order, to: :payment_session
37
+ delegate :store, :user, to: :order
38
+
39
+ def locale
40
+ order.try(:locale) || 'en-US'
41
+ end
42
+
43
+ def country_iso
44
+ address&.country_iso || store.default_country_iso || 'US'
45
+ end
46
+
47
+ def address
48
+ @address ||= order.bill_address || order.ship_address || user&.bill_address || user&.ship_address
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,140 @@
1
+ module SpreeAdyen
2
+ module PaymentSessions
3
+ class RequestPayloadPresenter
4
+ DEFAULT_PARAMS = {
5
+ recurringProcessingModel: 'UnscheduledCardOnFile',
6
+ shopperInteraction: 'Ecommerce',
7
+ storePaymentMethodMode: 'enabled'
8
+ }.freeze
9
+
10
+ def initialize(order:, amount:, user:, merchant_account:, payment_method:, channel:, return_url:)
11
+ @order = order
12
+ @amount = amount
13
+ @user = user
14
+ @merchant_account = merchant_account
15
+ @payment_method = payment_method
16
+ @channel = channel
17
+ @return_url = return_url
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ metadata: { # unfortunately metadata is not always available in webhooks, even for AUTHORISATION events
23
+ spree_payment_method_id: payment_method.id,
24
+ spree_order_id: order_number
25
+ },
26
+ amount: {
27
+ value: Spree::Money.new(amount, currency: currency).cents,
28
+ currency: currency
29
+ },
30
+ returnUrl: return_url,
31
+ reference: reference,
32
+ countryCode: country_iso,
33
+ lineItems: line_items,
34
+ merchantAccount: merchant_account,
35
+ merchantOrderReference: order_number,
36
+ expiresAt: expires_at
37
+ }.merge!(shopper_details, DEFAULT_PARAMS, channel_params, additional_data_params, SpreeAdyen::ApplicationInfoPresenter.new.to_h)
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :order, :amount, :user, :merchant_account, :payment_method, :channel, :return_url
43
+
44
+ delegate :number, to: :order, prefix: true
45
+ delegate :currency, :store, to: :order
46
+
47
+ # since we cannot count on metadata reference is the simplest way to store data for webhooks
48
+ # so let's keep its format as ORDERNUMBER_PAYMENTMETHODID_UNIQGUARANTER
49
+ def reference
50
+ [
51
+ order.number,
52
+ payment_method.id,
53
+ payment_sessions_count + 1
54
+ ].join('_')
55
+ end
56
+
57
+ def payment_sessions_count
58
+ if SpreeAdyen::Config[:use_legacy_adyen_payment_sessions]
59
+ order.adyen_payment_sessions.with_deleted.count
60
+ else
61
+ order.payment_sessions.with_deleted.where(payment_method: payment_method).count
62
+ end
63
+ end
64
+
65
+ def channel_params
66
+ case channel
67
+ when 'iOS'
68
+ { blockedPaymentMethods: ['googlepay'], channel: 'iOS' }
69
+ when 'Android'
70
+ { blockedPaymentMethods: ['applepay'], channel: 'Android' }
71
+ when 'Web'
72
+ { channel: 'Web' }
73
+ else
74
+ {}
75
+ end
76
+ end
77
+
78
+ def additional_data_params
79
+ return {} if payment_method.auto_capture?
80
+
81
+ {
82
+ additionalData: {
83
+ manualCapture: true
84
+ }
85
+ }
86
+ end
87
+
88
+ def shopper_details
89
+ {
90
+ shopperName: {
91
+ firstName: address&.firstname || user&.first_name,
92
+ lastName: address&.lastname || user&.last_name
93
+ },
94
+ shopperEmail: order.email || user&.email,
95
+ shopperReference: shopper_reference
96
+ }
97
+ end
98
+
99
+ # we need to send reference even for guest users, otherwise we can't tokenize the card
100
+ def shopper_reference
101
+ if user.present?
102
+ "customer_#{user.id}"
103
+ else
104
+ "guest_#{order.number}"
105
+ end
106
+ end
107
+
108
+ # Returns the address for the order
109
+ # @return [Spree::Address, nil]
110
+ def address
111
+ @address ||= order.bill_address || order.ship_address || user&.bill_address || user&.ship_address
112
+ end
113
+
114
+ # Returns the country ISO code for the order
115
+ # @return [String]
116
+ def country_iso
117
+ address&.country_iso || store.try(:default_country_iso) || 'US'
118
+ end
119
+
120
+ # Returns Order Line Items
121
+ # @return [Array<Hash>]
122
+ def line_items
123
+ order.line_items.includes(variant: :product).map do |line_item|
124
+ {
125
+ amountExcludingTax: Spree::Money.new(line_item.price - line_item.included_tax_total, currency: currency).cents,
126
+ amountIncludingTax: Spree::Money.new(line_item.price + line_item.additional_tax_total, currency: currency).cents,
127
+ description: line_item.name,
128
+ id: line_item.id,
129
+ sku: line_item.sku,
130
+ quantity: line_item.quantity
131
+ }
132
+ end
133
+ end
134
+
135
+ def expires_at
136
+ SpreeAdyen::Config.payment_session_expiration_in_minutes.minutes.from_now.iso8601
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,85 @@
1
+ module SpreeAdyen
2
+ module Payments
3
+ class RequestPayloadPresenter
4
+ DEFAULT_PARAMS = {
5
+ recurringProcessingModel: 'UnscheduledCardOnFile',
6
+ shopperInteraction: 'ContAuth'
7
+ }.freeze
8
+
9
+ def initialize(source:, amount_in_cents:, manual_capture:, gateway_options:)
10
+ @source = source
11
+ @amount_in_cents = amount_in_cents
12
+ @manual_capture = manual_capture
13
+ @gateway_options = gateway_options.with_indifferent_access
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ metadata: { # unfortunately metadata is not always available in webhooks, even for AUTHORISATION events
19
+ spree_payment_method_id: source.payment_method_id,
20
+ spree_order_id: order_number
21
+ },
22
+ amount: {
23
+ value: amount_in_cents,
24
+ currency: currency
25
+ },
26
+ paymentMethod: {
27
+ type: 'scheme',
28
+ storedPaymentMethodId: source.gateway_payment_profile_id
29
+ },
30
+ reference: reference,
31
+ shopperReference: shopper_reference,
32
+ merchantAccount: source.payment_method.preferred_merchant_account
33
+ }.merge!(DEFAULT_PARAMS, additional_data_params, SpreeAdyen::ApplicationInfoPresenter.new.to_h)
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :source, :amount_in_cents, :manual_capture, :gateway_options
39
+
40
+ delegate :currency, to: :order
41
+ delegate :user, to: :order, allow_nil: true
42
+
43
+ # since we cannot count on metadata reference is the simplest way to store data for webhooks
44
+ # so let's keep its format as ORDERNUMBER_PAYMENTMETHODID_UNIQGUARANTER
45
+ def reference
46
+ [
47
+ order_number,
48
+ source.payment_method_id,
49
+ payment_number
50
+ ].join('_')
51
+ end
52
+
53
+ # we need to send reference even for guest users, otherwise we can't tokenize the card
54
+ def shopper_reference
55
+ if user.present?
56
+ "customer_#{user.id}"
57
+ else
58
+ "guest_#{order_number}"
59
+ end
60
+ end
61
+
62
+ def order_number
63
+ @order_number ||= gateway_options[:order_id].split('-').first
64
+ end
65
+
66
+ def payment_number
67
+ @payment_number ||= gateway_options[:order_id].split('-').last
68
+ end
69
+
70
+ def order
71
+ @order ||= Spree::Order.find_by!(number: order_number)
72
+ end
73
+
74
+ def additional_data_params
75
+ return {} unless manual_capture
76
+
77
+ {
78
+ additionalData: {
79
+ manualCapture: true
80
+ }
81
+ }
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,38 @@
1
+ module SpreeAdyen
2
+ class RefundPayloadPresenter
3
+ REFERENCE_SUFFIX = 'refund'.freeze
4
+
5
+ def initialize(amount_in_cents:, currency:, payment_method:, payment:, refund:)
6
+ @amount_in_cents = amount_in_cents
7
+ @currency = currency
8
+ @payment_method = payment_method
9
+ @payment = payment
10
+ @refund = refund
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ amount: {
16
+ value: amount_in_cents,
17
+ currency: currency
18
+ },
19
+ reference: reference,
20
+ merchantAccount: payment_method.preferred_merchant_account
21
+ }.merge!(SpreeAdyen::ApplicationInfoPresenter.new.to_h)
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :amount_in_cents, :currency, :payment_method, :payment, :refund
27
+
28
+ def reference
29
+ [
30
+ payment.order.number,
31
+ payment_method.id,
32
+ payment.response_code,
33
+ REFERENCE_SUFFIX,
34
+ refund&.id
35
+ ].compact_blank.join('_')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ module SpreeAdyen
2
+ class WebhookPayloadPresenter
3
+ DEFAULT_PARAMS = {
4
+ active: true,
5
+ communicationFormat: 'json',
6
+ type: 'standard'
7
+ }.freeze
8
+
9
+ def initialize(url)
10
+ @url = url
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ url: url,
16
+ description: description
17
+ }.merge!(DEFAULT_PARAMS)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :url
23
+
24
+ def description
25
+ "Webhook created by SpreeAdyen on #{Time.zone.now.strftime('%Y-%m-%d %H:%M:%S')}"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ module SpreeAdyen
2
+ module Webhooks
3
+ class CreditCardPresenter
4
+ CREDIT_CARD_BRANDS = {
5
+ 'mc' => 'master',
6
+ 'mc_googlepay' => 'master',
7
+ 'maestro' => 'master',
8
+ 'maestro_googlepay' => 'master',
9
+ 'amex' => 'american_express',
10
+ 'amex_googlepay' => 'american_express',
11
+ 'cartebancaire' => 'cartes_bancaires',
12
+ 'diners' => 'diners_club',
13
+ 'eftpos_australia' => 'eftpos_au',
14
+ 'googlepay' => 'google_pay',
15
+ 'visa_googlepay' => 'visa'
16
+ }.freeze
17
+
18
+ def initialize(event)
19
+ @event = event
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ name: event.card_details['type'],
25
+ month: event.card_details['expiryDate']&.split('/')&.first,
26
+ year: event.card_details['expiryDate']&.split('/')&.last,
27
+ cc_type: CREDIT_CARD_BRANDS.fetch(payment_method_reference, payment_method_reference),
28
+ last_digits: event.card_details['cardSummary'],
29
+ gateway_customer_profile_id: nil,
30
+ gateway_payment_profile_id: event.stored_payment_method_id
31
+ }
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :event, :payment_session
37
+
38
+ def payment_method_reference
39
+ @payment_method_reference ||= event.payment_method_reference.to_s.downcase
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ module SpreeAdyen
2
+ module Gateways
3
+ class AddAllowedOrigin
4
+ ALREADY_EXISTS_ERROR_CODE = '31_004'.freeze
5
+
6
+ def initialize(record, gateway)
7
+ @record = record
8
+ @gateway = gateway
9
+ end
10
+
11
+ def call
12
+ response = gateway.add_allowed_origin(allowed_origin)
13
+
14
+ if response.success?
15
+ log("added to gateway #{gateway.id}")
16
+ elsif response.message['errorCode'] == ALREADY_EXISTS_ERROR_CODE
17
+ log('already exists', :warn)
18
+ else
19
+ Rails.error.unexpected('Cannot create allowed origin', context: { url: allowed_origin, gateway_id: gateway.id }, source: 'spree_adyen')
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :record, :gateway
26
+
27
+ def log(message, level = :info)
28
+ Rails.logger.send(level, "[SpreeAdyen][AddAllowedOrigin]: Origin #{allowed_origin} #{message}")
29
+ end
30
+
31
+ def allowed_origin
32
+ @allowed_origin ||= URI::HTTPS.build(host: record.url).to_s
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,41 @@
1
+ module SpreeAdyen
2
+ module Gateways
3
+ class Configuration
4
+ def initialize(response_body)
5
+ @response_body = response_body
6
+ end
7
+
8
+ def client_key
9
+ response_body['clientKey']
10
+ end
11
+
12
+ def roles
13
+ response_body['roles']
14
+ end
15
+
16
+ def active?
17
+ response_body['active'].to_s == 'true'
18
+ end
19
+
20
+ def allowed_origins
21
+ response_body['allowedOrigins'].map { |origin| origin['domain'] }
22
+ end
23
+
24
+ def merchant_account
25
+ response_body['associatedMerchantAccounts'].first
26
+ end
27
+
28
+ def id
29
+ response_body['id']
30
+ end
31
+
32
+ def company
33
+ response_body['companyName']
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :response_body
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ module SpreeAdyen
2
+ module Gateways
3
+ class Configure
4
+ def initialize(gateway)
5
+ @gateway = gateway
6
+ end
7
+
8
+ def call
9
+ gateway.preferred_client_key = configuration.client_key || gateway.generate_client_key.params['clientKey']
10
+ gateway.preferred_merchant_account = configuration.merchant_account
11
+
12
+ set_up_allowed_origins
13
+
14
+ set_up_webhook_with_hmac_key unless current_webhook_is_valid?
15
+
16
+ gateway.skip_auto_configuration = true
17
+ gateway.save!
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :gateway
23
+
24
+ def set_up_allowed_origins
25
+ gateway.stores.each do |store|
26
+ SpreeAdyen::AddAllowedOriginJob.perform_later(store.id, gateway.id)
27
+ next unless store.respond_to?(:custom_domains)
28
+
29
+ store.custom_domains.each do |custom_domain|
30
+ SpreeAdyen::AddAllowedOriginJob.perform_later(custom_domain.id, gateway.id, 'custom_domain')
31
+ end
32
+ end
33
+ end
34
+
35
+ def configuration
36
+ @configuration ||= SpreeAdyen::Gateways::Configuration.new(
37
+ gateway.get_api_credential_details.params
38
+ )
39
+ end
40
+
41
+ def current_webhook_is_valid?
42
+ gateway.preferred_webhook_id.present? &&
43
+ gateway.preferred_hmac_key.present? &&
44
+ gateway.test_webhook.success?
45
+ end
46
+
47
+ def set_up_webhook_with_hmac_key
48
+ set_up_webhook_request = gateway.set_up_webhook(gateway.webhook_url)
49
+ return unless set_up_webhook_request.success?
50
+
51
+ gateway.preferred_webhook_id = set_up_webhook_request.authorization
52
+ generate_hmac_key_request = gateway.generate_hmac_key
53
+ return unless generate_hmac_key_request.success?
54
+
55
+ gateway.previous_hmac_key = gateway.preferred_hmac_key if gateway.preferred_hmac_key.present?
56
+ gateway.preferred_hmac_key = generate_hmac_key_request.authorization
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+ module SpreeAdyen
2
+ module PaymentSessions
3
+ class FindOrCreate
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def initialize(order:, user:, amount:, payment_method:, channel: nil, return_url: nil)
7
+ @order = order
8
+ @amount = amount
9
+ @user = user
10
+ @payment_method = payment_method
11
+ @channel = channel || SpreeAdyen::PaymentSession::AVAILABLE_CHANNELS[:web]
12
+ @return_url = return_url
13
+ end
14
+
15
+ def call
16
+ return failure(nil, "Cannot create Adyen payment session for the order in the #{order.state} state") unless order.can_create_adyen_payment_session?
17
+ return success(payment_session) if payment_session.present?
18
+
19
+ create_attributes = {
20
+ order: order,
21
+ amount: amount,
22
+ currency: order.currency,
23
+ user: user,
24
+ payment_method: payment_method,
25
+ channel: channel
26
+ }
27
+ create_attributes[:return_url] = return_url if return_url.present?
28
+
29
+ payment_session = SpreeAdyen::PaymentSession.create(create_attributes)
30
+ payment_session.persisted? ? success(payment_session) : failure(payment_session)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :order, :payment_method, :amount, :user, :channel, :return_url
36
+
37
+ def payment_session
38
+ find_attributes = {
39
+ payment_method: payment_method,
40
+ order: order,
41
+ currency: order.currency,
42
+ user: user,
43
+ amount: amount,
44
+ channel: channel
45
+ }
46
+
47
+ @payment_session ||= PaymentSession.with_status(:initial).not_expired.find_by(find_attributes)
48
+ end
49
+ end
50
+ end
51
+ end