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.
- checksums.yaml +7 -0
- data/LICENSE +9 -0
- data/README.md +59 -0
- data/Rakefile +26 -0
- data/app/assets/config/spree_adyen_manifest.js +5 -0
- data/app/controllers/spree_adyen/apple_pay_domain_verification_controller.rb +11 -0
- data/app/controllers/spree_adyen/payment_sessions_controller.rb +67 -0
- data/app/controllers/spree_adyen/store_controller_decorator.rb +9 -0
- data/app/controllers/spree_adyen/webhooks_controller.rb +46 -0
- data/app/helpers/spree_adyen/base_helper.rb +18 -0
- data/app/javascript/spree_adyen/application.js +17 -0
- data/app/javascript/spree_adyen/controllers/checkout_adyen_controller.js +80 -0
- data/app/javascript/spree_adyen/controllers/redirect_adyen_controller.js +35 -0
- data/app/jobs/spree_adyen/add_allowed_origin_job.rb +24 -0
- data/app/jobs/spree_adyen/base_job.rb +5 -0
- data/app/jobs/spree_adyen/webhooks/process_authorisation_event_job.rb +10 -0
- data/app/jobs/spree_adyen/webhooks/process_cancellation_event_job.rb +10 -0
- data/app/jobs/spree_adyen/webhooks/process_capture_event_job.rb +10 -0
- data/app/models/spree/payment_sessions/adyen.rb +59 -0
- data/app/models/spree_adyen/base.rb +6 -0
- data/app/models/spree_adyen/custom_domain_decorator.rb +17 -0
- data/app/models/spree_adyen/gateway/payment_sessions.rb +151 -0
- data/app/models/spree_adyen/gateway.rb +487 -0
- data/app/models/spree_adyen/order_decorator.rb +23 -0
- data/app/models/spree_adyen/payment_decorator.rb +66 -0
- data/app/models/spree_adyen/payment_method_decorator.rb +15 -0
- data/app/models/spree_adyen/payment_session.rb +121 -0
- data/app/models/spree_adyen/payment_sources/ach_direct_debit.rb +17 -0
- data/app/models/spree_adyen/payment_sources/affirm.rb +17 -0
- data/app/models/spree_adyen/payment_sources/afterpay.rb +17 -0
- data/app/models/spree_adyen/payment_sources/alipay.rb +19 -0
- data/app/models/spree_adyen/payment_sources/alipay_hk.rb +17 -0
- data/app/models/spree_adyen/payment_sources/alma.rb +17 -0
- data/app/models/spree_adyen/payment_sources/ancv.rb +17 -0
- data/app/models/spree_adyen/payment_sources/apple_pay.rb +19 -0
- data/app/models/spree_adyen/payment_sources/atome.rb +17 -0
- data/app/models/spree_adyen/payment_sources/bacs.rb +17 -0
- data/app/models/spree_adyen/payment_sources/bancontact.rb +19 -0
- data/app/models/spree_adyen/payment_sources/bank_transfer.rb +17 -0
- data/app/models/spree_adyen/payment_sources/base.rb +7 -0
- data/app/models/spree_adyen/payment_sources/bcmc.rb +17 -0
- data/app/models/spree_adyen/payment_sources/bcmc_mobile.rb +17 -0
- data/app/models/spree_adyen/payment_sources/benefit.rb +17 -0
- data/app/models/spree_adyen/payment_sources/billie.rb +17 -0
- data/app/models/spree_adyen/payment_sources/bizum.rb +17 -0
- data/app/models/spree_adyen/payment_sources/blik.rb +15 -0
- data/app/models/spree_adyen/payment_sources/boleto.rb +17 -0
- data/app/models/spree_adyen/payment_sources/cash_app_afterpay.rb +17 -0
- data/app/models/spree_adyen/payment_sources/cashapp.rb +17 -0
- data/app/models/spree_adyen/payment_sources/clearpay.rb +17 -0
- data/app/models/spree_adyen/payment_sources/dana.rb +17 -0
- data/app/models/spree_adyen/payment_sources/doku.rb +19 -0
- data/app/models/spree_adyen/payment_sources/duitnow.rb +17 -0
- data/app/models/spree_adyen/payment_sources/ebanking_fi.rb +19 -0
- data/app/models/spree_adyen/payment_sources/eft_directdebit_ca.rb +19 -0
- data/app/models/spree_adyen/payment_sources/eftpos_australia.rb +19 -0
- data/app/models/spree_adyen/payment_sources/elo.rb +19 -0
- data/app/models/spree_adyen/payment_sources/eps.rb +19 -0
- data/app/models/spree_adyen/payment_sources/fastlane.rb +19 -0
- data/app/models/spree_adyen/payment_sources/fpx.rb +19 -0
- data/app/models/spree_adyen/payment_sources/gcash.rb +19 -0
- data/app/models/spree_adyen/payment_sources/gift_cards.rb +19 -0
- data/app/models/spree_adyen/payment_sources/giropay.rb +19 -0
- data/app/models/spree_adyen/payment_sources/givex.rb +19 -0
- data/app/models/spree_adyen/payment_sources/gopay_wallet.rb +19 -0
- data/app/models/spree_adyen/payment_sources/grabpay.rb +19 -0
- data/app/models/spree_adyen/payment_sources/grabpay_paylater.rb +19 -0
- data/app/models/spree_adyen/payment_sources/hipercard.rb +19 -0
- data/app/models/spree_adyen/payment_sources/ideal.rb +15 -0
- data/app/models/spree_adyen/payment_sources/kakaopay.rb +19 -0
- data/app/models/spree_adyen/payment_sources/kcp_naverpay.rb +19 -0
- data/app/models/spree_adyen/payment_sources/klarna.rb +32 -0
- data/app/models/spree_adyen/payment_sources/oney.rb +13 -0
- data/app/models/spree_adyen/payment_sources/online_banking_czech_republic.rb +15 -0
- data/app/models/spree_adyen/payment_sources/online_banking_poland.rb +15 -0
- data/app/models/spree_adyen/payment_sources/pay_by_bank.rb +15 -0
- data/app/models/spree_adyen/payment_sources/paypal.rb +15 -0
- data/app/models/spree_adyen/payment_sources/paypo.rb +15 -0
- data/app/models/spree_adyen/payment_sources/paysafecard.rb +15 -0
- data/app/models/spree_adyen/payment_sources/rate_pay_direct_debit.rb +15 -0
- data/app/models/spree_adyen/payment_sources/riverty.rb +15 -0
- data/app/models/spree_adyen/payment_sources/samsung_pay.rb +15 -0
- data/app/models/spree_adyen/payment_sources/scalapay.rb +13 -0
- data/app/models/spree_adyen/payment_sources/sepa_direct_debit.rb +15 -0
- data/app/models/spree_adyen/payment_sources/trustly.rb +15 -0
- data/app/models/spree_adyen/payment_sources/unknown.rb +19 -0
- data/app/models/spree_adyen/payment_sources/wechat_pay.rb +15 -0
- data/app/models/spree_adyen/store_decorator.rb +17 -0
- data/app/presenters/spree_adyen/address_presenter.rb +21 -0
- data/app/presenters/spree_adyen/application_info_presenter.rb +21 -0
- data/app/presenters/spree_adyen/cancel_payload_presenter.rb +31 -0
- data/app/presenters/spree_adyen/capture_payload_presenter.rb +36 -0
- data/app/presenters/spree_adyen/checkout_presenter.rb +51 -0
- data/app/presenters/spree_adyen/payment_sessions/request_payload_presenter.rb +140 -0
- data/app/presenters/spree_adyen/payments/request_payload_presenter.rb +85 -0
- data/app/presenters/spree_adyen/refund_payload_presenter.rb +38 -0
- data/app/presenters/spree_adyen/webhook_payload_presenter.rb +28 -0
- data/app/presenters/spree_adyen/webhooks/credit_card_presenter.rb +43 -0
- data/app/services/spree_adyen/gateways/add_allowed_origin.rb +36 -0
- data/app/services/spree_adyen/gateways/configuration.rb +41 -0
- data/app/services/spree_adyen/gateways/configure.rb +60 -0
- data/app/services/spree_adyen/payment_sessions/find_or_create.rb +51 -0
- data/app/services/spree_adyen/payment_sessions/process_with_result.rb +49 -0
- data/app/services/spree_adyen/webhooks/actions/create_source.rb +112 -0
- data/app/services/spree_adyen/webhooks/actions/find_or_create_credit_card.rb +33 -0
- data/app/services/spree_adyen/webhooks/event.rb +104 -0
- data/app/services/spree_adyen/webhooks/event_processors/authorisation_event_processor.rb +69 -0
- data/app/services/spree_adyen/webhooks/event_processors/cancellation_event_processor.rb +49 -0
- data/app/services/spree_adyen/webhooks/event_processors/capture_event_processor.rb +48 -0
- data/app/services/spree_adyen/webhooks/handle_event.rb +43 -0
- data/app/services/spree_adyen/webhooks/standard_hmac_validator.rb +37 -0
- data/app/views/layouts/spree_adyen/default.html.erb +14 -0
- data/app/views/spree/admin/payment_methods/configuration_guides/_spree_adyen.html.erb +14 -0
- data/app/views/spree/admin/payment_methods/custom_form_fields/_spree_adyen.html.erb +18 -0
- data/app/views/spree/admin/payment_methods/descriptions/_spree_adyen.html.erb +12 -0
- data/app/views/spree/admin/payments/source_forms/_spree_adyen.html.erb +7 -0
- data/app/views/spree/checkout/payment/_spree_adyen.html.erb +40 -0
- data/app/views/spree_adyen/_drop_in.html.erb +12 -0
- data/app/views/spree_adyen/_head.html.erb +2 -0
- data/app/views/spree_adyen/payment_sessions/redirect.html.erb +5 -0
- data/config/importmap.rb +7 -0
- data/config/initializers/spree.rb +34 -0
- data/config/initializers/spree_permitted_attributes.rb +5 -0
- data/config/locales/en.yml +11 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20250630150000_setup_spree_adyen_models.rb +17 -0
- data/db/migrate/20250811140113_add_channel_to_adyen_payment_sessions.rb +10 -0
- data/db/migrate/20250813152608_add_return_url_to_spree_adyen_payment_sessions.rb +15 -0
- data/lib/generators/spree_adyen/install/install_generator.rb +20 -0
- data/lib/spree_adyen/configuration.rb +42 -0
- data/lib/spree_adyen/engine.rb +79 -0
- data/lib/spree_adyen/factories.rb +3 -0
- data/lib/spree_adyen/testing_support/factories/gateway_factory.rb +29 -0
- data/lib/spree_adyen/testing_support/factories/payment_session_factory.rb +40 -0
- data/lib/spree_adyen/version.rb +7 -0
- data/lib/spree_adyen.rb +39 -0
- data/lib/spree_api_v2/spree/api/v2/storefront/adyen/base_controller.rb +21 -0
- data/lib/spree_api_v2/spree/api/v2/storefront/adyen/payment_sessions_controller.rb +72 -0
- data/lib/spree_api_v2/spree_adyen/api/v2/storefront/payment_session_serializer.rb +21 -0
- data/vendor/javascript/@adyen--adyen-web.js +4 -0
- data/vendor/stylesheets/adyen.css +106 -0
- 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
|