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,49 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
module PaymentSessions
|
|
3
|
+
class ProcessWithResult
|
|
4
|
+
def initialize(payment_session:, session_result:)
|
|
5
|
+
@payment_session = payment_session
|
|
6
|
+
@session_result = session_result
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
response = payment_session.payment_method.payment_session_result(payment_session.adyen_id, session_result)
|
|
11
|
+
status = response.params.fetch('status')
|
|
12
|
+
|
|
13
|
+
order.with_lock do
|
|
14
|
+
payment = order.payments.where(
|
|
15
|
+
payment_method: payment_session.payment_method,
|
|
16
|
+
response_code: payment_session.adyen_id
|
|
17
|
+
).first_or_initialize
|
|
18
|
+
|
|
19
|
+
payment.update!(amount: payment_session.amount, skip_source_requirement: true)
|
|
20
|
+
payment.started_processing! if payment.checkout? # it can be already changed by webhook
|
|
21
|
+
|
|
22
|
+
case status
|
|
23
|
+
when 'completed'
|
|
24
|
+
payment_session.complete! if payment_session.can_complete?
|
|
25
|
+
payment.confirm!
|
|
26
|
+
Spree::Dependencies.checkout_complete_service.constantize.call(order: order) unless order.completed?
|
|
27
|
+
when 'canceled'
|
|
28
|
+
payment.void! if payment.can_void?
|
|
29
|
+
payment_session.cancel! unless payment_session.canceled?
|
|
30
|
+
when 'refused', 'expired'
|
|
31
|
+
payment.failure! unless payment.failed?
|
|
32
|
+
payment_session.refuse! unless payment_session.refused?
|
|
33
|
+
when 'paymentPending'
|
|
34
|
+
payment_session.pending! if payment_session.can_pending? # this can have other status after
|
|
35
|
+
else
|
|
36
|
+
Rails.error.unexpected('Unexpected payment status', context: { order_id: order.id, status: status },
|
|
37
|
+
source: 'spree_adyen')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :payment_session, :session_result
|
|
45
|
+
|
|
46
|
+
delegate :order, to: :payment_session
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
module Webhooks
|
|
3
|
+
module Actions
|
|
4
|
+
class CreateSource
|
|
5
|
+
SOURCE_KLASS_MAP = {
|
|
6
|
+
affirm: SpreeAdyen::PaymentSources::Affirm,
|
|
7
|
+
alipay: SpreeAdyen::PaymentSources::Alipay,
|
|
8
|
+
bacs: SpreeAdyen::PaymentSources::Bacs,
|
|
9
|
+
bankTransfer_IBAN: SpreeAdyen::PaymentSources::BankTransfer,
|
|
10
|
+
klarna_b2b: SpreeAdyen::PaymentSources::Billie,
|
|
11
|
+
blik: SpreeAdyen::PaymentSources::Blik,
|
|
12
|
+
clearpay: SpreeAdyen::PaymentSources::Clearpay,
|
|
13
|
+
eps: SpreeAdyen::PaymentSources::Eps,
|
|
14
|
+
ideal: SpreeAdyen::PaymentSources::Ideal,
|
|
15
|
+
facilypay_3x: SpreeAdyen::PaymentSources::Oney,
|
|
16
|
+
facilypay_4x: SpreeAdyen::PaymentSources::Oney,
|
|
17
|
+
facilypay_6x: SpreeAdyen::PaymentSources::Oney,
|
|
18
|
+
facilypay_10x: SpreeAdyen::PaymentSources::Oney,
|
|
19
|
+
facilypay_12x: SpreeAdyen::PaymentSources::Oney,
|
|
20
|
+
scalapay_3x: SpreeAdyen::PaymentSources::Scalapay,
|
|
21
|
+
klarna: SpreeAdyen::PaymentSources::Klarna,
|
|
22
|
+
klarna_account: SpreeAdyen::PaymentSources::Klarna,
|
|
23
|
+
klarna_paynow: SpreeAdyen::PaymentSources::Klarna,
|
|
24
|
+
klarna_paylater: SpreeAdyen::PaymentSources::Klarna,
|
|
25
|
+
klarna_payovertime: SpreeAdyen::PaymentSources::Klarna,
|
|
26
|
+
onlineBanking_CZ: SpreeAdyen::PaymentSources::OnlineBankingCzechRepublic,
|
|
27
|
+
onlineBanking_PL: SpreeAdyen::PaymentSources::OnlineBankingPoland,
|
|
28
|
+
paybybank: SpreeAdyen::PaymentSources::PayByBank,
|
|
29
|
+
paypal: SpreeAdyen::PaymentSources::Paypal,
|
|
30
|
+
paypo: SpreeAdyen::PaymentSources::Paypo,
|
|
31
|
+
paysafecard: SpreeAdyen::PaymentSources::Paysafecard,
|
|
32
|
+
ratepay_directdebit: SpreeAdyen::PaymentSources::RatePayDirectDebit,
|
|
33
|
+
riverty: SpreeAdyen::PaymentSources::Riverty,
|
|
34
|
+
samsungpay: SpreeAdyen::PaymentSources::SamsungPay,
|
|
35
|
+
sepadirectdebit: SpreeAdyen::PaymentSources::SepaDirectDebit,
|
|
36
|
+
trustly: SpreeAdyen::PaymentSources::Trustly,
|
|
37
|
+
wechatpaySDK: SpreeAdyen::PaymentSources::WechatPay,
|
|
38
|
+
wechatpayQR: SpreeAdyen::PaymentSources::WechatPay,
|
|
39
|
+
ach: SpreeAdyen::PaymentSources::AchDirectDebit,
|
|
40
|
+
afterpaytouch: SpreeAdyen::PaymentSources::Afterpay,
|
|
41
|
+
afterpaytouch_US: SpreeAdyen::PaymentSources::CashAppAfterpay,
|
|
42
|
+
alipay_hk: SpreeAdyen::PaymentSources::AlipayHk,
|
|
43
|
+
alma: SpreeAdyen::PaymentSources::Alma,
|
|
44
|
+
ancv: SpreeAdyen::PaymentSources::Ancv,
|
|
45
|
+
atome: SpreeAdyen::PaymentSources::Atome,
|
|
46
|
+
benefit: SpreeAdyen::PaymentSources::Benefit,
|
|
47
|
+
bcmc: SpreeAdyen::PaymentSources::Bancontact,
|
|
48
|
+
bcmc_mobile: SpreeAdyen::PaymentSources::Bancontact,
|
|
49
|
+
bizum: SpreeAdyen::PaymentSources::Bizum,
|
|
50
|
+
boleto: SpreeAdyen::PaymentSources::Boleto,
|
|
51
|
+
cashapp: SpreeAdyen::PaymentSources::Cashapp,
|
|
52
|
+
doku_alfamart: SpreeAdyen::PaymentSources::Doku,
|
|
53
|
+
doku_indomaret: SpreeAdyen::PaymentSources::Doku,
|
|
54
|
+
dana: SpreeAdyen::PaymentSources::Dana,
|
|
55
|
+
duitnow: SpreeAdyen::PaymentSources::Duitnow,
|
|
56
|
+
fastlane: SpreeAdyen::PaymentSources::Fastlane,
|
|
57
|
+
molpay_ebanking_fpx_MY: SpreeAdyen::PaymentSources::Fpx,
|
|
58
|
+
gcash: SpreeAdyen::PaymentSources::Gcash,
|
|
59
|
+
givex: SpreeAdyen::PaymentSources::GiftCards,
|
|
60
|
+
genericgiftcard: SpreeAdyen::PaymentSources::GiftCards,
|
|
61
|
+
valuelink: SpreeAdyen::PaymentSources::GiftCards,
|
|
62
|
+
svs: SpreeAdyen::PaymentSources::GiftCards,
|
|
63
|
+
giropay: SpreeAdyen::PaymentSources::Giropay,
|
|
64
|
+
grabpay_MY: SpreeAdyen::PaymentSources::Grabpay,
|
|
65
|
+
grabpay_PH: SpreeAdyen::PaymentSources::Grabpay,
|
|
66
|
+
grabpay_SG: SpreeAdyen::PaymentSources::Grabpay
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
def initialize(event:, payment_method:, user:)
|
|
70
|
+
@event = event
|
|
71
|
+
@payment_method = payment_method
|
|
72
|
+
@user = user
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def call
|
|
76
|
+
if event.payment_method_reference.in?(SpreeAdyen::Config.credit_card_sources)
|
|
77
|
+
find_or_create_credit_card
|
|
78
|
+
else
|
|
79
|
+
find_or_create_source
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_or_create_source
|
|
84
|
+
source_klass_factory.find_or_create_by(
|
|
85
|
+
gateway_payment_profile_id: event.stored_payment_method_id.presence || event.psp_reference,
|
|
86
|
+
payment_method: payment_method
|
|
87
|
+
) do |source|
|
|
88
|
+
source.user = user
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def find_or_create_credit_card
|
|
93
|
+
SpreeAdyen::Webhooks::Actions::FindOrCreateCreditCard.new(
|
|
94
|
+
event: event,
|
|
95
|
+
gateway: payment_method,
|
|
96
|
+
user: user
|
|
97
|
+
).call
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
attr_reader :event, :payment_method, :user
|
|
103
|
+
|
|
104
|
+
delegate :payment_method_reference, to: :event
|
|
105
|
+
|
|
106
|
+
def source_klass_factory
|
|
107
|
+
SOURCE_KLASS_MAP[event.payment_method_reference] || SpreeAdyen::PaymentSources::Unknown
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
module Webhooks
|
|
3
|
+
module Actions
|
|
4
|
+
class FindOrCreateCreditCard
|
|
5
|
+
def initialize(event:, gateway:, user:)
|
|
6
|
+
@event = event
|
|
7
|
+
@gateway = gateway
|
|
8
|
+
@user = user
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
gateway.credit_cards.capturable.find_or_create_by(find_by_hash) do |cc|
|
|
13
|
+
cc.assign_attributes(credit_card_attributes)
|
|
14
|
+
cc.user = user
|
|
15
|
+
cc.payment_method = gateway
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :event, :gateway, :user
|
|
22
|
+
|
|
23
|
+
def credit_card_attributes
|
|
24
|
+
@credit_card_attributes ||= SpreeAdyen::Webhooks::CreditCardPresenter.new(event).to_h
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_by_hash
|
|
28
|
+
credit_card_attributes.slice(:gateway_payment_profile_id).compact
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
module Webhooks
|
|
3
|
+
class Event
|
|
4
|
+
def initialize(event_data:)
|
|
5
|
+
@event_data = event_data.to_h.with_indifferent_access
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def id
|
|
9
|
+
@id ||= body['pspReference']
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def payload
|
|
13
|
+
event_data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def order_number
|
|
17
|
+
@order_number ||= merchant_reference.split('_')[0]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def payment_method_id
|
|
21
|
+
@payment_method_id ||= merchant_reference.split('_')[1]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def code
|
|
25
|
+
@code ||= body['eventCode']
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def success?
|
|
29
|
+
body['success'] == 'true'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def billing_address
|
|
33
|
+
@billing_address ||= begin
|
|
34
|
+
return {} unless additional_data['billingAddress']
|
|
35
|
+
|
|
36
|
+
SpreeAdyen::AddressPresenter.new(additional_data['billingAddress']).to_h
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def shipping_address
|
|
41
|
+
@shipping_address ||= begin
|
|
42
|
+
return {} unless additional_data['deliveryAddress']
|
|
43
|
+
|
|
44
|
+
SpreeAdyen::AddressPresenter.new(additional_data['deliveryAddress']).to_h
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def payment_method_reference
|
|
49
|
+
@payment_method_reference ||= body['paymentMethod'].to_sym
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def stored_payment_method_id
|
|
53
|
+
@stored_payment_method_id ||= additional_data['tokenization.storedPaymentMethodId'] ||
|
|
54
|
+
additional_data['storedPaymentMethodId'] ||
|
|
55
|
+
additional_data['recurring.recurringDetailReference']
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def card_details
|
|
59
|
+
@card_details ||= additional_data.slice('expiryDate', 'cardSummary', 'type')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def amount
|
|
63
|
+
@amount ||= begin
|
|
64
|
+
amount_data = body['amount']
|
|
65
|
+
return nil unless amount_data&.key?('value') && amount_data.key?('currency')
|
|
66
|
+
|
|
67
|
+
Spree::Money.new(amount_data['value'] / 100.0, currency: amount_data['currency'])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def merchant_reference
|
|
72
|
+
@merchant_reference ||= body['merchantReference']
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def session_id
|
|
76
|
+
@session_id ||= additional_data['checkoutSessionId']
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def event_date
|
|
80
|
+
@event_date ||= body['eventDate'].to_datetime
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def psp_reference
|
|
84
|
+
@psp_reference ||= body['pspReference']
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def fetch(attribute)
|
|
88
|
+
body[attribute]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
attr_reader :event_data
|
|
94
|
+
|
|
95
|
+
def body
|
|
96
|
+
@body ||= event_data.dig('notificationItems', 0, 'NotificationRequestItem') || {}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def additional_data
|
|
100
|
+
@additional_data ||= body.fetch('additionalData', {})
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
module Webhooks
|
|
3
|
+
module EventProcessors
|
|
4
|
+
class AuthorisationEventProcessor
|
|
5
|
+
def initialize(event)
|
|
6
|
+
@event = event
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Started processing")
|
|
11
|
+
order = Spree::Order.find_by!(number: event.order_number)
|
|
12
|
+
|
|
13
|
+
order.with_lock do
|
|
14
|
+
payment_method = SpreeAdyen::Gateway.find(event.payment_method_id)
|
|
15
|
+
# session_id is available only for session based payments so payment_session can be nil
|
|
16
|
+
payment_session = order.adyen_payment_sessions.find_by(adyen_id: event.session_id)
|
|
17
|
+
source = SpreeAdyen::Webhooks::Actions::CreateSource.new(event: event, payment_method: payment_method, user: order.user).call
|
|
18
|
+
# create or find payment
|
|
19
|
+
# atm payment should be already created for web channel (but there is no payment for mobile channels)
|
|
20
|
+
# for web channel payment response code is updated from session_id to psp_reference
|
|
21
|
+
# psp_reference is required for refund flow but is not available before this event
|
|
22
|
+
payment_session&.lock!
|
|
23
|
+
|
|
24
|
+
payment = find_or_initialize_payment(payment_method)
|
|
25
|
+
payment.assign_attributes(
|
|
26
|
+
response_code: event.psp_reference,
|
|
27
|
+
amount: event.amount.to_d,
|
|
28
|
+
order: order,
|
|
29
|
+
source: source
|
|
30
|
+
)
|
|
31
|
+
payment.save!
|
|
32
|
+
|
|
33
|
+
payment.started_processing! if payment.checkout?
|
|
34
|
+
|
|
35
|
+
if event.success?
|
|
36
|
+
payment_session&.complete
|
|
37
|
+
payment.confirm!
|
|
38
|
+
Spree::Dependencies.checkout_complete_service.constantize.call(order: order) unless order.completed?
|
|
39
|
+
else
|
|
40
|
+
payment.failure!
|
|
41
|
+
payment_session&.refuse
|
|
42
|
+
if order.completed?
|
|
43
|
+
Rails.error.unexpected('Payment failed for previously completed order', context: { order_id: order.id, event: event.payload },
|
|
44
|
+
source: 'spree_adyen')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Finished processing")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
attr_reader :event
|
|
54
|
+
|
|
55
|
+
delegate :id, to: :event, prefix: true
|
|
56
|
+
|
|
57
|
+
def find_or_initialize_payment(payment_method)
|
|
58
|
+
if event.session_id.present?
|
|
59
|
+
Spree::Payment.where(response_code: event.session_id, payment_method: payment_method).or(
|
|
60
|
+
Spree::Payment.where(response_code: event.psp_reference, payment_method: payment_method)
|
|
61
|
+
).first_or_initialize
|
|
62
|
+
else
|
|
63
|
+
Spree::Payment.where(response_code: event.psp_reference, payment_method: payment_method).first_or_initialize
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
class CancellationError < StandardError; end
|
|
3
|
+
|
|
4
|
+
module Webhooks
|
|
5
|
+
module EventProcessors
|
|
6
|
+
class CancellationEventProcessor
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
def initialize(event)
|
|
10
|
+
@event = event
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Started processing")
|
|
15
|
+
order = Spree::Order.find_by!(number: event.order_number)
|
|
16
|
+
|
|
17
|
+
order.with_lock do
|
|
18
|
+
payment_method = SpreeAdyen::Gateway.find(event.payment_method_id)
|
|
19
|
+
payment = Spree::Payment.find_by!(response_code: event.fetch('originalReference'), payment_method: payment_method)
|
|
20
|
+
|
|
21
|
+
if event.success?
|
|
22
|
+
payment.set_metafield(SpreeAdyen::Gateway::CANCELLATION_PSP_REFERENCE_METAFIELD_KEY, event.psp_reference)
|
|
23
|
+
payment.started_processing! if payment.can_started_processing?
|
|
24
|
+
payment.void! unless payment.void?
|
|
25
|
+
else
|
|
26
|
+
payment.add_gateway_processing_error("Cancellation failed: #{event.fetch('reason')}")
|
|
27
|
+
payment.started_processing! if payment.can_started_processing?
|
|
28
|
+
payment.pend! if payment.can_pend?
|
|
29
|
+
|
|
30
|
+
Rails.error.report(
|
|
31
|
+
SpreeAdyen::CancellationError.new(event.fetch('reason')),
|
|
32
|
+
context: { order_id: order.id, event: event.payload },
|
|
33
|
+
source: 'spree_adyen'
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Finished processing")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :event
|
|
44
|
+
|
|
45
|
+
delegate :id, to: :event, prefix: true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
class CaptureError < StandardError; end
|
|
3
|
+
|
|
4
|
+
module Webhooks
|
|
5
|
+
module EventProcessors
|
|
6
|
+
class CaptureEventProcessor
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
def initialize(event)
|
|
10
|
+
@event = event
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Started processing")
|
|
15
|
+
order = Spree::Order.find_by!(number: event.order_number)
|
|
16
|
+
|
|
17
|
+
order.with_lock do
|
|
18
|
+
payment_method = SpreeAdyen::Gateway.find(event.payment_method_id)
|
|
19
|
+
payment = Spree::Payment.find_by!(response_code: event.fetch('originalReference'), payment_method: payment_method)
|
|
20
|
+
|
|
21
|
+
if event.success?
|
|
22
|
+
payment.set_metafield(SpreeAdyen::Gateway::CAPTURE_PSP_REFERENCE_METAFIELD_KEY, event.psp_reference)
|
|
23
|
+
payment.capture!
|
|
24
|
+
else
|
|
25
|
+
payment.add_gateway_processing_error("Capture failed: #{event.fetch('reason')}")
|
|
26
|
+
payment.started_processing! if payment.can_started_processing?
|
|
27
|
+
payment.failure! if payment.can_failure?
|
|
28
|
+
|
|
29
|
+
Rails.error.report(
|
|
30
|
+
SpreeAdyen::CaptureError.new(event.fetch('reason')),
|
|
31
|
+
context: { order_id: order.id, event: event.payload },
|
|
32
|
+
source: 'spree_adyen'
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Finished processing")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :event
|
|
43
|
+
|
|
44
|
+
delegate :id, to: :event, prefix: true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
module Webhooks
|
|
3
|
+
class HandleEvent
|
|
4
|
+
def initialize(event_payload:)
|
|
5
|
+
@event_payload = event_payload
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
# event not supported - skip
|
|
10
|
+
if event_class.nil?
|
|
11
|
+
Rails.logger.info("[SpreeAdyen][#{event_code}]: Skipping not supported event")
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Event received")
|
|
16
|
+
return unless event.code.in?(SpreeAdyen.event_handlers.keys)
|
|
17
|
+
|
|
18
|
+
Rails.logger.info("[SpreeAdyen][#{event_id}]: Event queued")
|
|
19
|
+
SpreeAdyen.event_handlers[event.code]
|
|
20
|
+
.set(wait: SpreeAdyen::Config.webhook_delay_in_seconds.seconds)
|
|
21
|
+
.perform_later(event.payload)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def event
|
|
25
|
+
@event ||= event_class.new(event_data: event_payload)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :event_payload
|
|
31
|
+
|
|
32
|
+
delegate :id, to: :event, prefix: true
|
|
33
|
+
|
|
34
|
+
def event_class
|
|
35
|
+
@event_class ||= SpreeAdyen.events[event_code]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def event_code
|
|
39
|
+
@event_code ||= event_payload.dig('notificationItems', 0, 'NotificationRequestItem', 'eventCode') || event_payload['type']
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module SpreeAdyen
|
|
2
|
+
module Webhooks
|
|
3
|
+
class StandardHmacValidator
|
|
4
|
+
def initialize(request:, params:, gateway:)
|
|
5
|
+
@request = request
|
|
6
|
+
@params = params
|
|
7
|
+
@gateway = gateway
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
return false if gateway.nil?
|
|
12
|
+
|
|
13
|
+
hmac_keys.any? do |hmac_key|
|
|
14
|
+
Adyen::Utils::HmacValidator.new.valid_webhook_hmac?(
|
|
15
|
+
webhook_request_item,
|
|
16
|
+
hmac_key
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :request, :params, :gateway
|
|
24
|
+
|
|
25
|
+
def hmac_keys
|
|
26
|
+
[
|
|
27
|
+
gateway.preferred_hmac_key,
|
|
28
|
+
gateway.previous_hmac_key
|
|
29
|
+
].compact
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def webhook_request_item
|
|
33
|
+
params.dig('notificationItems', 0, 'NotificationRequestItem') || {}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<% content_for(:head) do %>
|
|
2
|
+
<meta name="robots" content="noindex, nofollow">
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<!doctype html>
|
|
6
|
+
<html lang="<%= I18n.locale %>">
|
|
7
|
+
<head>
|
|
8
|
+
<%= render 'spree/shared/head' %>
|
|
9
|
+
<%= render 'spree/shared/custom_head' %>
|
|
10
|
+
</head>
|
|
11
|
+
<body id="checkout-page" class="theme-<%= current_theme.class.name.demodulize.underscore %> w-full bg-background text-text">
|
|
12
|
+
<%= yield %>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="alert alert-info">
|
|
2
|
+
<div>
|
|
3
|
+
Please follow
|
|
4
|
+
<%= external_link_to 'how to configure Adyen payment method in Spree Commerce guide', 'https://spreecommerce.org/docs/integrations/payments/adyen', class: 'alert-link' %>.
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="alert alert-info">
|
|
9
|
+
<div>
|
|
10
|
+
<strong>Live URL Prefix</strong> is required for live payments. You can find it in your
|
|
11
|
+
<%= external_link_to 'Adyen Customer Area', 'https://ca-live.adyen.com', class: 'alert-link' %>
|
|
12
|
+
under Developers > API URLs > Prefix.
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% if f.object.persisted? %>
|
|
2
|
+
<div class="form-group mb-0">
|
|
3
|
+
<%= f.label :apple_developer_merchantid_domain_association, 'Apple Pay certificate', class: 'font-weight-bold' %>
|
|
4
|
+
|
|
5
|
+
<% if f.object.apple_developer_merchantid_domain_association.attached? %>
|
|
6
|
+
<div class="form-control d-flex align-items-center justify-content-between pr-1 mb-3 bg-light">
|
|
7
|
+
Certificate file uploaded!
|
|
8
|
+
|
|
9
|
+
<%= link_to main_app.rails_blob_path(f.object.apple_developer_merchantid_domain_association, disposition: 'attachment'), class: 'btn btn-light btn-sm' do %>
|
|
10
|
+
<%= icon 'download' %>
|
|
11
|
+
Download
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<%= f.file_field :apple_developer_merchantid_domain_association, class: "form-control-file" %>
|
|
17
|
+
</div>
|
|
18
|
+
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
End-to-end payments, data, and financial management in a single solution. Meet the financial technology platform that helps you realize your ambitions faster.
|
|
2
|
+
|
|
3
|
+
<div class="d-flex align-items-center">
|
|
4
|
+
<%= payment_method_icon_tag 'visa', class: 'm-1' %>
|
|
5
|
+
<%= payment_method_icon_tag 'master', class: 'm-1' %>
|
|
6
|
+
<%= payment_method_icon_tag 'american_express', class: 'm-1' %>
|
|
7
|
+
<%= payment_method_icon_tag 'apple_pay', class: 'm-1' %>
|
|
8
|
+
<%= payment_method_icon_tag 'google_pay', class: 'm-1' %>
|
|
9
|
+
<%= payment_method_icon_tag 'klarna', class: 'm-1' %>
|
|
10
|
+
<%= payment_method_icon_tag 'afterpay', class: 'm-1' %>
|
|
11
|
+
<%= payment_method_icon_tag 'affirm', class: 'm-1' %>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
<% payment_sources = current_user&.credit_cards&.not_removed %>
|
|
3
|
+
|
|
4
|
+
<div data-controller="checkout-adyen"></div>
|
|
5
|
+
|
|
6
|
+
<% if payment_sources.present? %>
|
|
7
|
+
<div id="existing_cards">
|
|
8
|
+
<div class="form-group">
|
|
9
|
+
<ul class="list-group mb-5 border rounded-md" data-controller="reveal">
|
|
10
|
+
<% payment_sources.each_with_index do |card, index| %>
|
|
11
|
+
<li class="p-0 m-0 border-b" id="<%= spree_dom_id(card) %>">
|
|
12
|
+
<div class="custom-control custom-radio px-5 py-4 flex items-center">
|
|
13
|
+
<label class="flex flex-row items-center cursor-pointer">
|
|
14
|
+
<%= radio_button_tag "order[existing_card]", card.id, index == 0, { data: { hide: "dropin-container", checkout_adyen_target: index == 0 ? "defaultCard" : nil}, class: "mr-5" } %>
|
|
15
|
+
<div class="flex space-x-4 items-center">
|
|
16
|
+
<%= payment_method_icon_tag(card.cc_type) %>
|
|
17
|
+
<span>****<%= card.last_digits %></span>
|
|
18
|
+
<span><%= card.month.to_s.rjust(2, '0') %>/<%= card.year %></span>
|
|
19
|
+
</div>
|
|
20
|
+
</label>
|
|
21
|
+
</div>
|
|
22
|
+
</li>
|
|
23
|
+
<% end %>
|
|
24
|
+
<li>
|
|
25
|
+
<div class="custom-control custom-radio px-5 py-4 flex items-center w-full">
|
|
26
|
+
<label class="flex flex-row items-center w-full pr-8">
|
|
27
|
+
<%= radio_button_tag "order[existing_card]", nil, false, { data: { show: "dropin-container" }, class: "mr-5" } %>
|
|
28
|
+
<div class="w-full flex flex-col items-start space-y-4 justify-center">
|
|
29
|
+
<span class="cursor-pointer"><%= Spree.t(:add_new_credit_card) %></span>
|
|
30
|
+
<%= render 'spree_adyen/drop_in', payment_session: current_adyen_payment_session %>
|
|
31
|
+
</div>
|
|
32
|
+
</label>
|
|
33
|
+
</div>
|
|
34
|
+
</li>
|
|
35
|
+
</ul>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<% else %>
|
|
39
|
+
<%= render 'spree_adyen/drop_in', payment_session: current_adyen_payment_session, auto_mount: true %>
|
|
40
|
+
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<% auto_mount ||= false %>
|
|
2
|
+
|
|
3
|
+
<% if payment_session.present? %>
|
|
4
|
+
<div
|
|
5
|
+
class="<%= !auto_mount ? 'hidden' : '' %>"
|
|
6
|
+
id="dropin-container"
|
|
7
|
+
data-checkout-adyen-checkout-attrubutes="<%= SpreeAdyen::CheckoutPresenter.new(payment_session).to_json %>"
|
|
8
|
+
data-checkout-adyen-payment-session-path-value="<%= spree.adyen_payment_session_url(sessionId: payment_session.adyen_id) %>"
|
|
9
|
+
data-checkout-adyen-checkout-path-value="<%= spree.api_v2_storefront_checkout_path %>"
|
|
10
|
+
data-checkout-adyen-auto-mount-value="<%= auto_mount %>"
|
|
11
|
+
></div>
|
|
12
|
+
<% end %>
|