@wearejh/m2-pwa-adyen 0.45.0 → 0.50.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.
- package/CHANGELOG.md +67 -0
- package/lib/AdyenProvider.tsx +15 -0
- package/lib/adyen.actions.ts +2 -4
- package/lib/adyen.reducer.ts +2 -7
- package/lib/adyen.types.ts +6 -31
- package/lib/epics/adyenPayment.epic.ts +10 -12
- package/lib/epics/attemptPayment.epic.ts +5 -4
- package/lib/epics/getAdyenConfig.epic.ts +4 -1
- package/lib/epics/utils/processAdyenResponse.ts +53 -19
- package/lib/paymentMethods/3DS/README.md +56 -0
- package/lib/paymentMethods/3DS/ThreeDS2.scss +16 -0
- package/lib/paymentMethods/3DS/ThreeDS2.tsx +130 -0
- package/lib/paymentMethods/ApplePay/ApplePay.tsx +6 -2
- package/lib/paymentMethods/Card/Card.tsx +27 -18
- package/lib/paymentMethods/GooglePay/GooglePay.tsx +10 -8
- package/lib/paymentMethods/Klarna/Klarna.tsx +9 -10
- package/lib/paymentMethods/Klarna/KlarnaOverTime.tsx +10 -11
- package/lib/paymentMethods/Klarna/KlarnaPayNow.tsx +10 -11
- package/lib/paymentMethods/PayPal/PayPal.tsx +12 -5
- package/package.json +8 -8
- package/lib/paymentMethods/3DS/ThreeDS2Challenge.tsx +0 -141
- package/lib/paymentMethods/3DS/ThreeDS2Fingerprint.tsx +0 -101
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,73 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [0.50.0](https://github.com/WeareJH/mage-mono/compare/v0.49.0...v0.50.0) (2026-01-14)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @wearejh/m2-pwa-adyen
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [0.49.0](https://github.com/WeareJH/mage-mono/compare/v0.48.0...v0.49.0) (2026-01-14)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **m2-pwa-adyen:** add tips sections to 3ds documentation ([6711d27](https://github.com/WeareJH/mage-mono/commit/6711d27dc2c6ec1295568853458b7045f939be4b))
|
|
20
|
+
* **m2-pwa-adyen:** re-add documentation for 3ds & card ([3cfbf5f](https://github.com/WeareJH/mage-mono/commit/3cfbf5f5aa1c3ebc3c20435a761fd9bc60db3103))
|
|
21
|
+
* **m2-pwa-adyen:** update Card & 3DSecure payment components (WOOD-3014) ([0c05135](https://github.com/WeareJH/mage-mono/commit/0c05135bcd64af7c9314a76990ca0e518698244d))
|
|
22
|
+
* **WOOD-3014:** Adyen Klarna bug fixes ([39ba2f8](https://github.com/WeareJH/mage-mono/commit/39ba2f821683fad38e4b6935432eea7aae43b7fb))
|
|
23
|
+
* **WOOD-3014:** Adyen Klarna handleAction bug fixe ([169d428](https://github.com/WeareJH/mage-mono/commit/169d428990b9048ce949ec9c6754d5f2ab630f2d))
|
|
24
|
+
* **WOOD-3014:** Adyen onCancel bug fix - no longer used ([2015520](https://github.com/WeareJH/mage-mono/commit/201552013f3ddc4dc1744239d98b1fcc5da69196))
|
|
25
|
+
* lint fixes ([81634f6](https://github.com/WeareJH/mage-mono/commit/81634f638c9e1f8c677da31dd2199fd911041428))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# [0.48.0](https://github.com/WeareJH/mage-mono/compare/v0.47.0...v0.48.0) (2026-01-13)
|
|
32
|
+
|
|
33
|
+
**Note:** Version bump only for package @wearejh/m2-pwa-adyen
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# [0.47.0](https://github.com/WeareJH/mage-mono/compare/v0.46.0...v0.47.0) (2026-01-13)
|
|
40
|
+
|
|
41
|
+
**Note:** Version bump only for package @wearejh/m2-pwa-adyen
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# [0.46.0](https://github.com/WeareJH/mage-mono/compare/v0.45.1...v0.46.0) (2026-01-13)
|
|
48
|
+
|
|
49
|
+
**Note:** Version bump only for package @wearejh/m2-pwa-adyen
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## [0.45.1](https://github.com/WeareJH/mage-mono/compare/v0.45.0...v0.45.1) (2026-01-13)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Bug Fixes
|
|
59
|
+
|
|
60
|
+
* **m2-pwa-adyen:** Card payment input placeholder config (WOOD-3014) ([bb6c1e8](https://github.com/WeareJH/mage-mono/commit/bb6c1e89a99fe403db256cdc94b5df520cc10ef8))
|
|
61
|
+
* **m2-pwa-adyen:** hide Card payment button in favour of Woodies custom button (WOOD-3014) ([156526f](https://github.com/WeareJH/mage-mono/commit/156526ff8b3059f4a6dc4ff8d90e6122843de1cd))
|
|
62
|
+
* **m2-pwa-adyen:** remove early mounting of the Adyen card component. Wait for parent component (WOOD-3014) ([7f90ed9](https://github.com/WeareJH/mage-mono/commit/7f90ed9d625a654e83149c26c35581fb114dad9b))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### Features
|
|
66
|
+
|
|
67
|
+
* **WOOD-3014:** Adyen card validation ([e7619d5](https://github.com/WeareJH/mage-mono/commit/e7619d519c6b6f925aa0f8eb45512ecc3cc96f53))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
6
73
|
# [0.45.0](https://github.com/WeareJH/mage-mono/compare/v0.44.0...v0.45.0) (2026-01-08)
|
|
7
74
|
|
|
8
75
|
**Note:** Version bump only for package @wearejh/m2-pwa-adyen
|
package/lib/AdyenProvider.tsx
CHANGED
|
@@ -44,6 +44,7 @@ interface AdyenContext
|
|
|
44
44
|
children?: any;
|
|
45
45
|
paymentFormSubmit(): void;
|
|
46
46
|
paymentFormSubmitted: boolean;
|
|
47
|
+
resetPaymentFormSubmitted(): void;
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* A promise that resolves/rejects depending on
|
|
@@ -60,6 +61,7 @@ const initialState: AdyenContext = {
|
|
|
60
61
|
checkoutConfig: initialStoreState.checkoutConfig,
|
|
61
62
|
paymentFormSubmit: () => {},
|
|
62
63
|
paymentFormSubmitted: false,
|
|
64
|
+
resetPaymentFormSubmitted: () => {},
|
|
63
65
|
selectedPaymentMethod: initialStoreState.selectedPaymentMethod,
|
|
64
66
|
step: initialStoreState.step,
|
|
65
67
|
supportedPaymentMethods: initialStoreState.supportedPaymentMethods,
|
|
@@ -213,6 +215,17 @@ export function AdyenProvider(props: AdyenProviderProps) {
|
|
|
213
215
|
paymentFormSubmitProp();
|
|
214
216
|
}, [paymentFormSubmitProp]);
|
|
215
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Reset the payment form submitted state.
|
|
220
|
+
* Called by Card component after triggering adyenCard.submit()
|
|
221
|
+
* to allow subsequent submit button clicks to work.
|
|
222
|
+
*/
|
|
223
|
+
const resetPaymentFormSubmitted = useCallback(() => {
|
|
224
|
+
if (setHasFormSubmitted) {
|
|
225
|
+
setHasFormSubmitted(false);
|
|
226
|
+
}
|
|
227
|
+
}, [setHasFormSubmitted]);
|
|
228
|
+
|
|
216
229
|
const attemptPayment = useCallback(
|
|
217
230
|
(props: AttemptPaymentProps) => {
|
|
218
231
|
const newState = {
|
|
@@ -287,6 +300,7 @@ export function AdyenProvider(props: AdyenProviderProps) {
|
|
|
287
300
|
orderId,
|
|
288
301
|
paymentFormSubmit,
|
|
289
302
|
paymentFormSubmitted,
|
|
303
|
+
resetPaymentFormSubmitted,
|
|
290
304
|
selectedPaymentMethod,
|
|
291
305
|
step,
|
|
292
306
|
supportedPaymentMethods: availiablePaymentMethods,
|
|
@@ -303,6 +317,7 @@ export function AdyenProvider(props: AdyenProviderProps) {
|
|
|
303
317
|
orderId,
|
|
304
318
|
paymentFormSubmit,
|
|
305
319
|
paymentFormSubmitted,
|
|
320
|
+
resetPaymentFormSubmitted,
|
|
306
321
|
selectedPaymentMethod,
|
|
307
322
|
step,
|
|
308
323
|
waitForPaymentFormSubmit,
|
package/lib/adyen.actions.ts
CHANGED
|
@@ -33,8 +33,7 @@ export enum ActionTypes {
|
|
|
33
33
|
ActionKlarnaRedirect = 'Adyen.Action.Klarna.Redirect',
|
|
34
34
|
ActionSDKPayPal = 'Adyen.Action.SDK.PayPal',
|
|
35
35
|
ActionGiftCardRedirect = 'Adyen.Action.GiftCard.Redirect',
|
|
36
|
-
|
|
37
|
-
ActionThreeDS2Fingerprint = 'Adyen.Action.3DS2.Fingerprint',
|
|
36
|
+
ActionThreeDS2 = 'Adyen.Action.3DS2',
|
|
38
37
|
Cancel = 'Adyen.Cancel',
|
|
39
38
|
Error = 'Adyen.Error',
|
|
40
39
|
GetConfig = 'Adyen.GetConfig',
|
|
@@ -55,8 +54,7 @@ export type ActionsObject = {
|
|
|
55
54
|
[ActionTypes.ActionKlarnaRedirect]: AdyenPaymentAction;
|
|
56
55
|
[ActionTypes.ActionSDKPayPal]: AdyenPaymentAction;
|
|
57
56
|
[ActionTypes.ActionGiftCardRedirect]: AdyenPaymentAction;
|
|
58
|
-
[ActionTypes.
|
|
59
|
-
[ActionTypes.ActionThreeDS2Fingerprint]: AdyenPaymentAction;
|
|
57
|
+
[ActionTypes.ActionThreeDS2]: AdyenPaymentAction;
|
|
60
58
|
[ActionTypes.Cancel]: undefined;
|
|
61
59
|
[ActionTypes.Error]: AdyenErrorAction;
|
|
62
60
|
[ActionTypes.GetConfig]: undefined;
|
package/lib/adyen.reducer.ts
CHANGED
|
@@ -52,14 +52,9 @@ export function reducer(state = initialState, action: Actions): Store {
|
|
|
52
52
|
newState.step = AdyenSteps.GiftCardRedirect;
|
|
53
53
|
break;
|
|
54
54
|
|
|
55
|
-
case ActionTypes.
|
|
55
|
+
case ActionTypes.ActionThreeDS2:
|
|
56
56
|
newState.action = action.payload;
|
|
57
|
-
newState.step = AdyenSteps.
|
|
58
|
-
break;
|
|
59
|
-
|
|
60
|
-
case ActionTypes.ActionThreeDS2Fingerprint:
|
|
61
|
-
newState.action = action.payload;
|
|
62
|
-
newState.step = AdyenSteps.ThreeDS2Fingerprint;
|
|
57
|
+
newState.step = AdyenSteps.ThreeDS2;
|
|
63
58
|
break;
|
|
64
59
|
|
|
65
60
|
case ActionTypes.Cancel:
|
package/lib/adyen.types.ts
CHANGED
|
@@ -13,8 +13,6 @@ export enum AdyenActionTypes {
|
|
|
13
13
|
Redirect = 'redirect',
|
|
14
14
|
SDK = 'sdk',
|
|
15
15
|
ThreeDS2 = 'threeDS2',
|
|
16
|
-
ThreeDS2Challenge = 'threeDS2Challenge',
|
|
17
|
-
ThreeDS2Fingerprint = 'threeDS2Fingerprint',
|
|
18
16
|
Voucher = 'voucher',
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -51,21 +49,7 @@ export interface AdyenErrorAction {
|
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
export interface AdyenPayload {
|
|
54
|
-
additional_data:
|
|
55
|
-
brand_code?: AdyenBrandCodes;
|
|
56
|
-
cc_type: string;
|
|
57
|
-
cvc: string;
|
|
58
|
-
expiryMonth: string;
|
|
59
|
-
expiryYear: string;
|
|
60
|
-
java_enabled: boolean;
|
|
61
|
-
language: string;
|
|
62
|
-
number: string;
|
|
63
|
-
screen_color_depth: number;
|
|
64
|
-
screen_height: number;
|
|
65
|
-
screen_width: number;
|
|
66
|
-
stateData: string;
|
|
67
|
-
timezone_offset: number;
|
|
68
|
-
};
|
|
52
|
+
additional_data: Record<string, unknown>;
|
|
69
53
|
method: string;
|
|
70
54
|
}
|
|
71
55
|
|
|
@@ -122,9 +106,10 @@ export enum AdyenSDKTypes {
|
|
|
122
106
|
}
|
|
123
107
|
|
|
124
108
|
export type AdyenSubmitParams = {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
109
|
+
browserInfo: Record<string, unknown>;
|
|
110
|
+
clientStateDataIndicator: boolean;
|
|
111
|
+
origin: string;
|
|
112
|
+
paymentMethod: Record<string, unknown>;
|
|
128
113
|
type: string;
|
|
129
114
|
};
|
|
130
115
|
|
|
@@ -134,15 +119,6 @@ export enum AdyenStorageKey {
|
|
|
134
119
|
CartVariant = 'adyen.cartvariant',
|
|
135
120
|
}
|
|
136
121
|
|
|
137
|
-
/**
|
|
138
|
-
* Subtypes of actions defined in the Adyen
|
|
139
|
-
* response from Magento.
|
|
140
|
-
*/
|
|
141
|
-
export enum AdyenActionSubTypes {
|
|
142
|
-
Fingerprint = 'fingerprint',
|
|
143
|
-
Challenge = 'challenge',
|
|
144
|
-
}
|
|
145
|
-
|
|
146
122
|
/**
|
|
147
123
|
* Possible steps within the Adyen flow.
|
|
148
124
|
*/
|
|
@@ -165,8 +141,7 @@ export enum AdyenSteps {
|
|
|
165
141
|
Ready = 'Ready',
|
|
166
142
|
SDKPayPal = 'SDK.PayPal',
|
|
167
143
|
GiftCardRedirect = 'GiftCard.Redirect',
|
|
168
|
-
|
|
169
|
-
ThreeDS2Fingerprint = '3DS2.Fingerprint',
|
|
144
|
+
ThreeDS2 = '3DS2',
|
|
170
145
|
ThreeDSDismiss = '3DS.Dismiss',
|
|
171
146
|
}
|
|
172
147
|
|
|
@@ -92,21 +92,19 @@ export function adyenPayment(action$: Observable<any>, state$: Observable<any>,
|
|
|
92
92
|
return EMPTY;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// Note
|
|
96
|
+
// It's important to make sure this payload matches the payload
|
|
97
|
+
// sent by the Luma integration. Use a local instance of Magento
|
|
98
|
+
// to compare.
|
|
95
99
|
const payloadComposed: AdyenPayload = {
|
|
96
100
|
additional_data: {
|
|
97
|
-
brand_code: payload?.brandCode,
|
|
98
101
|
cc_type: payload?.paymentMethod?.brand,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
screen_color_depth: payload?.browserInfo?.colorDepth || 0,
|
|
106
|
-
screen_height: payload?.browserInfo?.screenHeight || 0,
|
|
107
|
-
screen_width: payload?.browserInfo?.screenWidth || 0,
|
|
108
|
-
stateData: JSON.stringify(payload),
|
|
109
|
-
timezone_offset: payload?.browserInfo?.timeZoneOffset || 0,
|
|
102
|
+
frontendType: 'default',
|
|
103
|
+
guestEmail: pendingPayment.billingAddress?.email,
|
|
104
|
+
stateData: JSON.stringify({
|
|
105
|
+
...payload,
|
|
106
|
+
type: undefined,
|
|
107
|
+
}),
|
|
110
108
|
},
|
|
111
109
|
method: payload?.type,
|
|
112
110
|
};
|
|
@@ -20,10 +20,11 @@ export function attemptPayment(action$: Observable<any>, _state$: Observable<any
|
|
|
20
20
|
|
|
21
21
|
mergeMap(({ payload }) => {
|
|
22
22
|
const output: AdyenSubmitParams = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
browserInfo: payload.browserInfo,
|
|
24
|
+
clientStateDataIndicator: payload.clientStateDataIndicator,
|
|
25
|
+
origin: payload.origin,
|
|
26
|
+
paymentMethod: payload.paymentMethod,
|
|
27
|
+
type: payload.type,
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
return of(AdyenMessage(ActionTypes.PaymentSubmit, output));
|
|
@@ -30,9 +30,12 @@ const adyenWebComponentsApiUrl = 'https://checkoutshopper-live.adyen.com/checkou
|
|
|
30
30
|
/**
|
|
31
31
|
* Adyen web components version.
|
|
32
32
|
*
|
|
33
|
+
* This version matches the version used by the Magento 2 module
|
|
34
|
+
* @link https://github.com/Adyen/adyen-magento2/blob/main/view/base/web/js/adyen.js
|
|
35
|
+
*
|
|
33
36
|
* @url https://github.com/Adyen/adyen-web
|
|
34
37
|
*/
|
|
35
|
-
const adyenWebComponentsVersion = '6.
|
|
38
|
+
const adyenWebComponentsVersion = '6.21.0';
|
|
36
39
|
|
|
37
40
|
const CSS_FILE = `${adyenWebComponentsApiUrl}/${adyenWebComponentsVersion}/adyen.css`;
|
|
38
41
|
const JS_FILE = `${adyenWebComponentsApiUrl}/${adyenWebComponentsVersion}/adyen.js`;
|
|
@@ -3,7 +3,6 @@ import { of, throwError } from 'rxjs';
|
|
|
3
3
|
|
|
4
4
|
import { ActionTypes, AdyenMessage } from '../../adyen.actions';
|
|
5
5
|
import {
|
|
6
|
-
AdyenActionSubTypes,
|
|
7
6
|
AdyenActionTypes,
|
|
8
7
|
AdyenResponseParams,
|
|
9
8
|
AdyenResultCodeTypes,
|
|
@@ -95,7 +94,6 @@ export function processAdyenResponse(response?: AdyenResponseParams, config?: Pr
|
|
|
95
94
|
);
|
|
96
95
|
|
|
97
96
|
case PaymentMethodTypes.Klarna:
|
|
98
|
-
case PaymentMethodTypes.KlarnaOverTime:
|
|
99
97
|
return of(
|
|
100
98
|
/**
|
|
101
99
|
* Klarna uses a redirect to process payment, so
|
|
@@ -121,6 +119,58 @@ export function processAdyenResponse(response?: AdyenResponseParams, config?: Pr
|
|
|
121
119
|
AdyenMessage(ActionTypes.ActionKlarnaRedirect, action),
|
|
122
120
|
);
|
|
123
121
|
|
|
122
|
+
case PaymentMethodTypes.KlarnaOverTime:
|
|
123
|
+
return of(
|
|
124
|
+
/**
|
|
125
|
+
* Klarna Over Time (klarna_account) uses a redirect to process payment, so
|
|
126
|
+
* we need to store the users order id in
|
|
127
|
+
* localstorage, so that it can be restored when
|
|
128
|
+
* the user is returned to the site.
|
|
129
|
+
*/
|
|
130
|
+
StorageMsg(StorageActions.Set, {
|
|
131
|
+
expiry: storageExpiry,
|
|
132
|
+
key: AdyenStorageKey.OrderId,
|
|
133
|
+
value: orderId,
|
|
134
|
+
}),
|
|
135
|
+
StorageMsg(StorageActions.Set, {
|
|
136
|
+
expiry: storageExpiry,
|
|
137
|
+
key: AdyenStorageKey.CartId,
|
|
138
|
+
value: cartId,
|
|
139
|
+
}),
|
|
140
|
+
StorageMsg(StorageActions.Set, {
|
|
141
|
+
expiry: storageExpiry,
|
|
142
|
+
key: AdyenStorageKey.CartVariant,
|
|
143
|
+
value: cartVariant,
|
|
144
|
+
}),
|
|
145
|
+
AdyenMessage(ActionTypes.ActionKlarnaOverTimeRedirect, action),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
case PaymentMethodTypes.KlarnaPayNow:
|
|
149
|
+
return of(
|
|
150
|
+
/**
|
|
151
|
+
* Klarna Over Time (klarna_account) uses a redirect to process payment, so
|
|
152
|
+
* we need to store the users order id in
|
|
153
|
+
* localstorage, so that it can be restored when
|
|
154
|
+
* the user is returned to the site.
|
|
155
|
+
*/
|
|
156
|
+
StorageMsg(StorageActions.Set, {
|
|
157
|
+
expiry: storageExpiry,
|
|
158
|
+
key: AdyenStorageKey.OrderId,
|
|
159
|
+
value: orderId,
|
|
160
|
+
}),
|
|
161
|
+
StorageMsg(StorageActions.Set, {
|
|
162
|
+
expiry: storageExpiry,
|
|
163
|
+
key: AdyenStorageKey.CartId,
|
|
164
|
+
value: cartId,
|
|
165
|
+
}),
|
|
166
|
+
StorageMsg(StorageActions.Set, {
|
|
167
|
+
expiry: storageExpiry,
|
|
168
|
+
key: AdyenStorageKey.CartVariant,
|
|
169
|
+
value: cartVariant,
|
|
170
|
+
}),
|
|
171
|
+
AdyenMessage(ActionTypes.ActionKlarnaPayNowRedirect, action),
|
|
172
|
+
);
|
|
173
|
+
|
|
124
174
|
default:
|
|
125
175
|
break;
|
|
126
176
|
}
|
|
@@ -144,23 +194,7 @@ export function processAdyenResponse(response?: AdyenResponseParams, config?: Pr
|
|
|
144
194
|
break;
|
|
145
195
|
|
|
146
196
|
case AdyenActionTypes.ThreeDS2:
|
|
147
|
-
|
|
148
|
-
* Adyen 3DS2 actions use a `subType` to
|
|
149
|
-
* determine the next step.
|
|
150
|
-
*/
|
|
151
|
-
const subType = action?.subtype;
|
|
152
|
-
|
|
153
|
-
switch (subType) {
|
|
154
|
-
case AdyenActionSubTypes.Fingerprint:
|
|
155
|
-
return of(AdyenMessage(ActionTypes.ActionThreeDS2Fingerprint, action));
|
|
156
|
-
|
|
157
|
-
case AdyenActionSubTypes.Challenge:
|
|
158
|
-
return of(AdyenMessage(ActionTypes.ActionThreeDS2Challenge, action));
|
|
159
|
-
|
|
160
|
-
default:
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
break;
|
|
197
|
+
return of(AdyenMessage(ActionTypes.ActionThreeDS2, action));
|
|
164
198
|
|
|
165
199
|
default:
|
|
166
200
|
break;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<br/>
|
|
2
|
+
|
|
3
|
+
<!-- Title -->
|
|
4
|
+
<h1 align="center">
|
|
5
|
+
3D Secure 2 (3DS2)
|
|
6
|
+
</h1>
|
|
7
|
+
|
|
8
|
+
<!-- Description -->
|
|
9
|
+
<p align="center">Adyen 3D Secure 2 Authentication Component</p>
|
|
10
|
+
|
|
11
|
+
<br/>
|
|
12
|
+
|
|
13
|
+
## :lock: Payment Verification Flow
|
|
14
|
+
|
|
15
|
+
1. User submits payment via the Card component.
|
|
16
|
+
2. Payment details are sent to Magento for processing.
|
|
17
|
+
3. If the card requires 3DS2 verification, Magento returns an action payload.
|
|
18
|
+
4. The 3DS2 component creates a verification modal from the action payload.
|
|
19
|
+
5. User completes the 3DS2 challenge (e.g., biometric, OTP, or bank redirect).
|
|
20
|
+
6. Verification result is sent back to Magento for final payment processing.
|
|
21
|
+
|
|
22
|
+
<br/>
|
|
23
|
+
|
|
24
|
+
## Tips
|
|
25
|
+
|
|
26
|
+
- Use the Magento 2 Adyen module as a reference for how native Magento integrates Adyen Web Components:
|
|
27
|
+
- Adyen Magento 2 module: https://github.com/Adyen/adyen-magento2
|
|
28
|
+
- Card/3DS2 renderer reference: https://github.com/Adyen/adyen-magento2/blob/main/view/frontend/web/js/view/payment/method-renderer/adyen-cc-method.js
|
|
29
|
+
- Compare behavior against Magento’s Luma storefront to validate UX and flows (field validation, 3DS challenge, error states). Testing on Luma helps ensure parity with the native integration:
|
|
30
|
+
- Magento Open Source: https://github.com/magento/magento2
|
|
31
|
+
- Sample Data (includes Luma assets): https://github.com/magento/magento2-sample-data
|
|
32
|
+
|
|
33
|
+
<br/>
|
|
34
|
+
|
|
35
|
+
## FAQ
|
|
36
|
+
|
|
37
|
+
### "Challenge Failed - No creq" Error
|
|
38
|
+
|
|
39
|
+
**Problem:** You receive a "Challenge Failed - No creq" error during 3DS2 verification.
|
|
40
|
+
|
|
41
|
+
**Cause:** The 3DS action data isn't being passed to the Adyen iframes. When inspecting the network tab, you'll see requests to HTML documents such as `challenge.html` that are missing their POST payload.
|
|
42
|
+
|
|
43
|
+
**Solution:** This error occurs when the 3DS2 iframe is loaded within an existing `<form>` element. Adyen's 3DS2 flow uses its own form and multiple form submissions to run the challenge. Nested forms break this process.
|
|
44
|
+
|
|
45
|
+
**Fix:** Ensure the 3DS2 component is rendered in an isolated area, outside of any existing form elements. The modal approach used in this implementation prevents this issue by rendering the component in a portal.
|
|
46
|
+
|
|
47
|
+
<br/>
|
|
48
|
+
|
|
49
|
+
## :vulcan_salute: Useful Resources
|
|
50
|
+
|
|
51
|
+
- Adyen 3D Secure 2 Documentation: https://docs.adyen.com/online-payments/3d-secure/api-reference#threeDS2
|
|
52
|
+
- Adyen 3DS2 Web Component Source Code: https://github.com/Adyen/adyen-web/tree/main/packages/lib/src/components/ThreeDS2
|
|
53
|
+
- Adyen Test Cards with 3DS2: https://docs.adyen.com/development-resources/test-cards/test-card-numbers#test-3d-secure-2-authentication
|
|
54
|
+
- 3D Secure 2 Overview: https://docs.adyen.com/online-payments/3d-secure
|
|
55
|
+
|
|
56
|
+
<br/>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.threeDS2Modal {
|
|
2
|
+
// Ensure the 3DS2 challenge iframe takes up the full height
|
|
3
|
+
// of the modal, without needing to use overflow scrolling.
|
|
4
|
+
:global(.adyen-checkout__threeds2__challenge--01),
|
|
5
|
+
:global(.adyen-checkout__threeds2__challenge--02),
|
|
6
|
+
:global(.adyen-checkout__threeds2__challenge--03),
|
|
7
|
+
:global(.adyen-checkout__threeds2__challenge--04),
|
|
8
|
+
:global(.adyen-checkout__threeds2__challenge--05),
|
|
9
|
+
:global(.adyen-checkout__threeds2__challenge--01 .adyen-checkout__iframe--threeDSIframe),
|
|
10
|
+
:global(.adyen-checkout__threeds2__challenge--02 .adyen-checkout__iframe--threeDSIframe),
|
|
11
|
+
:global(.adyen-checkout__threeds2__challenge--03 .adyen-checkout__iframe--threeDSIframe),
|
|
12
|
+
:global(.adyen-checkout__threeds2__challenge--04 .adyen-checkout__iframe--threeDSIframe),
|
|
13
|
+
:global(.adyen-checkout__threeds2__challenge--05 .adyen-checkout__iframe--threeDSIframe) {
|
|
14
|
+
min-height: inherit;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
import { useDispatch } from 'react-redux';
|
|
3
|
+
import { SimpleModal } from '@wearejh/m2-pwa-checkout/lib/components/Modal/SimpleModal';
|
|
4
|
+
import { PaymentAction } from '@adyen/adyen-web';
|
|
5
|
+
|
|
6
|
+
import { AdyenSteps } from '../../adyen.types';
|
|
7
|
+
import { ActionTypes, AdyenMessage } from '../../adyen.actions';
|
|
8
|
+
import { useAdyen } from '../../AdyenProvider';
|
|
9
|
+
|
|
10
|
+
import classes from './ThreeDS2.scss';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* React wrapper component for the Adyen 3DS2 (3D Secure 2)
|
|
14
|
+
* authentication component.
|
|
15
|
+
*
|
|
16
|
+
* Payment Verification Flow:
|
|
17
|
+
* 1. After the user submits payment via the Card component, Magento
|
|
18
|
+
* processes the payment.
|
|
19
|
+
* 2. If the card requires 3DS2 verification, Magento returns an action
|
|
20
|
+
* payload with type 'threeDS2'.
|
|
21
|
+
* 3. This component creates the 3DS2 component from that action and
|
|
22
|
+
* mounts it in a modal.
|
|
23
|
+
* 4. Adyen handles the 3DS2 challenge flow (e.g., biometric, OTP,
|
|
24
|
+
* or bank redirect).
|
|
25
|
+
* 5. Once verification completes, `onAdditionalDetails` sends the result
|
|
26
|
+
* back to Magento.
|
|
27
|
+
* 6. Magento finalizes the payment based on the verification result.
|
|
28
|
+
*
|
|
29
|
+
* @see Adyen 3DS2 Component Documentation - https://docs.adyen.com/online-payments/3d-secure/api-reference#threeDS2
|
|
30
|
+
* @see Adyen Web Component Source - https://github.com/Adyen/adyen-web/tree/main/packages/lib/src/components/ThreeDS2
|
|
31
|
+
* @see Adyen Magento 2 module Card with 3DS2 - https://github.com/Adyen/adyen-magento2/blob/main/view/frontend/web/js/view/payment/method-renderer/adyen-cc-method.js
|
|
32
|
+
*
|
|
33
|
+
* @returns {JSX.Element | null} The 3DS2 modal when verification is required, otherwise null.
|
|
34
|
+
*/
|
|
35
|
+
export function ThreeDS2() {
|
|
36
|
+
const { action, adyenCheckout, step } = useAdyen();
|
|
37
|
+
const dispatch = useDispatch();
|
|
38
|
+
|
|
39
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
40
|
+
const componentRef = useRef<any>(null);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handle the additional details event from Adyen 3DS2 component.
|
|
44
|
+
*/
|
|
45
|
+
const onAdditionalDetails = useCallback(
|
|
46
|
+
(state) => {
|
|
47
|
+
dispatch(AdyenMessage(ActionTypes.ProcessPaymentDetails, state.data));
|
|
48
|
+
},
|
|
49
|
+
[dispatch],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Handle the errors.
|
|
54
|
+
*/
|
|
55
|
+
const onError = useCallback(
|
|
56
|
+
({ message }) => {
|
|
57
|
+
dispatch(
|
|
58
|
+
AdyenMessage(ActionTypes.Error, {
|
|
59
|
+
cancelPayment: true,
|
|
60
|
+
message,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
[dispatch],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const is3DS2Action = useMemo(() => {
|
|
68
|
+
return action && action.type === 'threeDS2' && adyenCheckout;
|
|
69
|
+
}, [action, adyenCheckout]);
|
|
70
|
+
|
|
71
|
+
const unMount = useCallback(() => {
|
|
72
|
+
if (componentRef.current) {
|
|
73
|
+
componentRef.current.unmount();
|
|
74
|
+
componentRef.current = null;
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!is3DS2Action || !containerRef.current) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
unMount();
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
/**
|
|
87
|
+
* Create the 3DS component from the Action payload that comes
|
|
88
|
+
* from Magento. Adyen will handle the rest and call `onAdditionalDetails` when it needs more information from Magento or the verification is complete.
|
|
89
|
+
*/
|
|
90
|
+
const actionComponent = adyenCheckout?.createFromAction(action as PaymentAction, {
|
|
91
|
+
onAdditionalDetails,
|
|
92
|
+
onError,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
actionComponent?.mount(containerRef.current);
|
|
96
|
+
componentRef.current = actionComponent;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
onError(error);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return () => {
|
|
102
|
+
unMount();
|
|
103
|
+
};
|
|
104
|
+
}, [action, adyenCheckout, is3DS2Action, onAdditionalDetails, onError, unMount]);
|
|
105
|
+
|
|
106
|
+
if (step === AdyenSteps.ThreeDS2) {
|
|
107
|
+
return (
|
|
108
|
+
<SimpleModal
|
|
109
|
+
alwaysRender={true}
|
|
110
|
+
dismiss={() => {}}
|
|
111
|
+
hideModalFooter={true}
|
|
112
|
+
id="adyen_cc_3ds2_modal"
|
|
113
|
+
title="Payment Verification"
|
|
114
|
+
className={classes.threeDS2Modal}
|
|
115
|
+
>
|
|
116
|
+
<div
|
|
117
|
+
ref={containerRef}
|
|
118
|
+
style={{
|
|
119
|
+
// The Adyen 3DS2 has a minimum height of 400px,
|
|
120
|
+
// but it causes a overflow scrollbar to appear on modals.
|
|
121
|
+
// This values adds a bit of padding to prevent that.
|
|
122
|
+
minHeight: '450px',
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
</SimpleModal>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
@@ -104,7 +104,11 @@ export function ApplePay() {
|
|
|
104
104
|
*
|
|
105
105
|
* @link Adyen ApplePay Component Documentation: https://docs.adyen.com/payment-methods/apple-pay/web-component
|
|
106
106
|
*/
|
|
107
|
-
const adyenApplePay = useMemo<ApplePayElement>(() => {
|
|
107
|
+
const adyenApplePay = useMemo<ApplePayElement | undefined>(() => {
|
|
108
|
+
if (!adyenCheckout) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
108
112
|
const { ApplePay } = (window as any).AdyenWeb;
|
|
109
113
|
|
|
110
114
|
return new ApplePay(adyenCheckout, adyenApplePayConfig) as ApplePayElement;
|
|
@@ -128,7 +132,7 @@ export function ApplePay() {
|
|
|
128
132
|
|
|
129
133
|
return (
|
|
130
134
|
<>
|
|
131
|
-
<div ref={container}
|
|
135
|
+
<div ref={container} />
|
|
132
136
|
<button
|
|
133
137
|
aria-hidden={true}
|
|
134
138
|
ref={hiddenFormSubmitButton}
|
|
@@ -4,26 +4,24 @@ import { useDispatch } from 'react-redux';
|
|
|
4
4
|
|
|
5
5
|
import { useAdyen } from '../../AdyenProvider';
|
|
6
6
|
import { ActionTypes, AdyenMessage } from '../../adyen.actions';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { ThreeDS2Fingerprint } from '../3DS/ThreeDS2Fingerprint';
|
|
7
|
+
import { MagentoPaymentTypes } from '../../adyen.types';
|
|
8
|
+
import { ThreeDS2 } from '../3DS/ThreeDS2';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
* React
|
|
11
|
+
* React wrapper component for the Adyen Card component.
|
|
13
12
|
*
|
|
14
13
|
* Payment Process:
|
|
15
14
|
* 1. Mount Card component
|
|
16
|
-
* 2. User enters card details, then
|
|
15
|
+
* 2. User enters card details, then presses submit button.
|
|
17
16
|
* 3. Payment details are sent to Magento for processing.
|
|
18
17
|
* 4. If the payment card supports 3DS2 (security check),
|
|
19
|
-
* the
|
|
18
|
+
* the ThreeDS2 component handles the verification flow.
|
|
20
19
|
* - The verification result is passed back to Magento for processing.
|
|
21
20
|
*
|
|
22
21
|
* @link Adyen Card Component Documentation - https://docs.adyen.com/payment-methods/cards/web-component#show-the-available-cards-in-your-payment-form
|
|
23
22
|
*/
|
|
24
23
|
export function Card() {
|
|
25
|
-
const { adyenCheckout, attemptPayment, paymentFormSubmitted,
|
|
26
|
-
|
|
24
|
+
const { adyenCheckout, attemptPayment, paymentFormSubmitted, resetPaymentFormSubmitted } = useAdyen();
|
|
27
25
|
const dispatch = useDispatch();
|
|
28
26
|
|
|
29
27
|
/**
|
|
@@ -36,13 +34,15 @@ export function Card() {
|
|
|
36
34
|
* Handle the errors.
|
|
37
35
|
*/
|
|
38
36
|
const onError = useCallback(
|
|
39
|
-
({ message }) =>
|
|
37
|
+
({ message }) => {
|
|
40
38
|
dispatch(
|
|
41
39
|
AdyenMessage(ActionTypes.Error, {
|
|
42
40
|
cancelPayment: true,
|
|
43
41
|
message,
|
|
44
42
|
}),
|
|
45
|
-
)
|
|
43
|
+
);
|
|
44
|
+
dispatch(AdyenMessage(ActionTypes.Cancel));
|
|
45
|
+
},
|
|
46
46
|
[dispatch],
|
|
47
47
|
);
|
|
48
48
|
|
|
@@ -76,6 +76,12 @@ export function Card() {
|
|
|
76
76
|
() => ({
|
|
77
77
|
onError,
|
|
78
78
|
onSubmit,
|
|
79
|
+
placeholders: {
|
|
80
|
+
cardNumber: '1234 5678 9012 3456',
|
|
81
|
+
expiryDate: 'MM/YY',
|
|
82
|
+
securityCodeThreeDigits: '3 digits',
|
|
83
|
+
},
|
|
84
|
+
showPayButton: false,
|
|
79
85
|
}),
|
|
80
86
|
[onError, onSubmit],
|
|
81
87
|
);
|
|
@@ -85,10 +91,13 @@ export function Card() {
|
|
|
85
91
|
*
|
|
86
92
|
* @link Adyen Card Component Documentation - https://docs.adyen.com/payment-methods/cards/web-component#page-introduction
|
|
87
93
|
*/
|
|
88
|
-
const adyenCard = useMemo<CardElement>(() => {
|
|
89
|
-
|
|
94
|
+
const adyenCard = useMemo<CardElement | undefined>(() => {
|
|
95
|
+
if (!adyenCheckout) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
90
98
|
|
|
91
|
-
|
|
99
|
+
const { Card } = (window as any).AdyenWeb;
|
|
100
|
+
return new Card(adyenCheckout, adyenCardConfig) as CardElement;
|
|
92
101
|
}, [adyenCheckout, adyenCardConfig]);
|
|
93
102
|
|
|
94
103
|
/**
|
|
@@ -98,7 +107,7 @@ export function Card() {
|
|
|
98
107
|
if (adyenCheckout && adyenCard && cardContainer.current) {
|
|
99
108
|
adyenCard.mount(cardContainer.current);
|
|
100
109
|
}
|
|
101
|
-
}, [adyenCheckout, adyenCard
|
|
110
|
+
}, [adyenCheckout, adyenCard]);
|
|
102
111
|
|
|
103
112
|
/**
|
|
104
113
|
* When the payment form has been submitted,
|
|
@@ -108,14 +117,14 @@ export function Card() {
|
|
|
108
117
|
useEffect(() => {
|
|
109
118
|
if (adyenCard && paymentFormSubmitted) {
|
|
110
119
|
adyenCard.submit();
|
|
120
|
+
resetPaymentFormSubmitted();
|
|
111
121
|
}
|
|
112
|
-
}, [adyenCard, paymentFormSubmitted]);
|
|
122
|
+
}, [adyenCard, paymentFormSubmitted, resetPaymentFormSubmitted]);
|
|
113
123
|
|
|
114
124
|
return (
|
|
115
125
|
<>
|
|
116
|
-
<div ref={cardContainer}
|
|
117
|
-
|
|
118
|
-
{step === AdyenSteps.ThreeDS2Fingerprint && <ThreeDS2Fingerprint />}
|
|
126
|
+
<div ref={cardContainer} />
|
|
127
|
+
<ThreeDS2 />
|
|
119
128
|
</>
|
|
120
129
|
);
|
|
121
130
|
}
|
|
@@ -4,9 +4,8 @@ import { useDispatch } from 'react-redux';
|
|
|
4
4
|
|
|
5
5
|
import { useAdyen } from '../../AdyenProvider';
|
|
6
6
|
import { ActionTypes, AdyenMessage } from '../../adyen.actions';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { ThreeDS2Fingerprint } from '../3DS/ThreeDS2Fingerprint';
|
|
7
|
+
import { MagentoPaymentTypes } from '../../adyen.types';
|
|
8
|
+
import { ThreeDS2 } from '../3DS/ThreeDS2';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* React warapper component for the Adyen Google Pay component.
|
|
@@ -28,7 +27,7 @@ import { ThreeDS2Fingerprint } from '../3DS/ThreeDS2Fingerprint';
|
|
|
28
27
|
*/
|
|
29
28
|
|
|
30
29
|
export function GooglePay() {
|
|
31
|
-
const { adyenCheckout, attemptPayment, waitForPaymentFormSubmit
|
|
30
|
+
const { adyenCheckout, attemptPayment, waitForPaymentFormSubmit } = useAdyen();
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* The HTML Div element reference the Adyen Apple Pay
|
|
@@ -105,7 +104,11 @@ export function GooglePay() {
|
|
|
105
104
|
*
|
|
106
105
|
* @link Adyen GooglePay Component Documentation: https://docs.adyen.com/payment-methods/google-pay/web-component
|
|
107
106
|
*/
|
|
108
|
-
const adyenGooglePay = useMemo<GooglePayElement>(() => {
|
|
107
|
+
const adyenGooglePay = useMemo<GooglePayElement | undefined>(() => {
|
|
108
|
+
if (!adyenCheckout) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
109
112
|
const { GooglePay } = (window as any).AdyenWeb;
|
|
110
113
|
|
|
111
114
|
return new GooglePay(adyenCheckout, adyenGooglePayConfig) as GooglePayElement;
|
|
@@ -129,7 +132,7 @@ export function GooglePay() {
|
|
|
129
132
|
|
|
130
133
|
return (
|
|
131
134
|
<>
|
|
132
|
-
<div ref={container}
|
|
135
|
+
<div ref={container} />
|
|
133
136
|
<button
|
|
134
137
|
aria-hidden={true}
|
|
135
138
|
ref={hiddenFormSubmitButton}
|
|
@@ -138,8 +141,7 @@ export function GooglePay() {
|
|
|
138
141
|
}}
|
|
139
142
|
type="submit"
|
|
140
143
|
/>
|
|
141
|
-
|
|
142
|
-
{step === AdyenSteps.ThreeDS2Fingerprint && <ThreeDS2Fingerprint />}
|
|
144
|
+
<ThreeDS2 />
|
|
143
145
|
</>
|
|
144
146
|
);
|
|
145
147
|
}
|
|
@@ -79,7 +79,11 @@ export function Klarna() {
|
|
|
79
79
|
*
|
|
80
80
|
* @link Adyen Klarna Component Documentation: https://docs.adyen.com/payment-methods/klarna/web-component
|
|
81
81
|
*/
|
|
82
|
-
const adyenKlarna = useMemo<KlarnaElement>(() => {
|
|
82
|
+
const adyenKlarna = useMemo<KlarnaElement | undefined>(() => {
|
|
83
|
+
if (!adyenCheckout) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
const { Klarna } = (window as any).AdyenWeb;
|
|
84
88
|
|
|
85
89
|
return new Klarna(adyenCheckout, adyenKlarnaConfig) as KlarnaElement;
|
|
@@ -121,13 +125,7 @@ export function Klarna() {
|
|
|
121
125
|
*/
|
|
122
126
|
useEffect(() => {
|
|
123
127
|
if (action && adyenKlarna && step === AdyenSteps.KlarnaRedirect) {
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
action.sdkData &&
|
|
127
|
-
typeof action.sdkData === 'object' &&
|
|
128
|
-
'client_token' in action.sdkData &&
|
|
129
|
-
'payment_method_category' in action.sdkData
|
|
130
|
-
) {
|
|
128
|
+
if (action.paymentMethodType === 'klarna') {
|
|
131
129
|
adyenKlarna.handleAction(action as any);
|
|
132
130
|
} else {
|
|
133
131
|
console.error('Invalid Klarna action structure:', action);
|
|
@@ -137,7 +135,8 @@ export function Klarna() {
|
|
|
137
135
|
}, [action, adyenKlarna, onError, step]);
|
|
138
136
|
|
|
139
137
|
return (
|
|
140
|
-
|
|
138
|
+
<>
|
|
139
|
+
<div ref={klarnaContainer} />
|
|
141
140
|
<button
|
|
142
141
|
aria-hidden={true}
|
|
143
142
|
ref={hiddenFormSubmitButton}
|
|
@@ -146,6 +145,6 @@ export function Klarna() {
|
|
|
146
145
|
}}
|
|
147
146
|
type="submit"
|
|
148
147
|
/>
|
|
149
|
-
|
|
148
|
+
</>
|
|
150
149
|
);
|
|
151
150
|
}
|
|
@@ -89,7 +89,11 @@ export function KlarnaOverTime() {
|
|
|
89
89
|
*
|
|
90
90
|
* @link Adyen Klarna Component Documentation: https://docs.adyen.com/payment-methods/klarna/web-component
|
|
91
91
|
*/
|
|
92
|
-
const adyenKlarnaOverTime = useMemo<KlarnaElement>(() => {
|
|
92
|
+
const adyenKlarnaOverTime = useMemo<KlarnaElement | undefined>(() => {
|
|
93
|
+
if (!adyenCheckout) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
const { Klarna } = (window as any).AdyenWeb;
|
|
94
98
|
|
|
95
99
|
return new Klarna(adyenCheckout, adyenKlarnaConfig) as KlarnaElement;
|
|
@@ -130,14 +134,8 @@ export function KlarnaOverTime() {
|
|
|
130
134
|
* payment, before redirecting the user back to PWA.
|
|
131
135
|
*/
|
|
132
136
|
useEffect(() => {
|
|
133
|
-
if (action && adyenKlarnaOverTime && step === AdyenSteps.
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
action.sdkData &&
|
|
137
|
-
typeof action.sdkData === 'object' &&
|
|
138
|
-
'client_token' in action.sdkData &&
|
|
139
|
-
'payment_method_category' in action.sdkData
|
|
140
|
-
) {
|
|
137
|
+
if (action && adyenKlarnaOverTime && step === AdyenSteps.KlarnaOverTimeRedirect) {
|
|
138
|
+
if (action.paymentMethodType === 'klarna_account') {
|
|
141
139
|
adyenKlarnaOverTime.handleAction(action as any);
|
|
142
140
|
} else {
|
|
143
141
|
console.error('Invalid Klarna action structure:', action);
|
|
@@ -147,7 +145,8 @@ export function KlarnaOverTime() {
|
|
|
147
145
|
}, [action, adyenKlarnaOverTime, onError, step]);
|
|
148
146
|
|
|
149
147
|
return (
|
|
150
|
-
|
|
148
|
+
<>
|
|
149
|
+
<div ref={klarnaContainer} />
|
|
151
150
|
<button
|
|
152
151
|
aria-hidden={true}
|
|
153
152
|
ref={hiddenFormSubmitButton}
|
|
@@ -156,6 +155,6 @@ export function KlarnaOverTime() {
|
|
|
156
155
|
}}
|
|
157
156
|
type="submit"
|
|
158
157
|
/>
|
|
159
|
-
|
|
158
|
+
</>
|
|
160
159
|
);
|
|
161
160
|
}
|
|
@@ -79,7 +79,11 @@ export function KlarnaPayNow() {
|
|
|
79
79
|
*
|
|
80
80
|
* @link Adyen Klarna Component Documentation: https://docs.adyen.com/payment-methods/klarna/web-component
|
|
81
81
|
*/
|
|
82
|
-
const adyenKlarnaPayNow = useMemo<KlarnaElement>(() => {
|
|
82
|
+
const adyenKlarnaPayNow = useMemo<KlarnaElement | undefined>(() => {
|
|
83
|
+
if (!adyenCheckout) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
const { Klarna } = (window as any).AdyenWeb;
|
|
84
88
|
|
|
85
89
|
return new Klarna(adyenCheckout, adyenKlarnaConfig) as KlarnaElement;
|
|
@@ -120,14 +124,8 @@ export function KlarnaPayNow() {
|
|
|
120
124
|
* payment, before redirecting the user back to PWA.
|
|
121
125
|
*/
|
|
122
126
|
useEffect(() => {
|
|
123
|
-
if (action && adyenKlarnaPayNow && step === AdyenSteps.
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
action.sdkData &&
|
|
127
|
-
typeof action.sdkData === 'object' &&
|
|
128
|
-
'client_token' in action.sdkData &&
|
|
129
|
-
'payment_method_category' in action.sdkData
|
|
130
|
-
) {
|
|
127
|
+
if (action && adyenKlarnaPayNow && step === AdyenSteps.KlarnaPayNowRedirect) {
|
|
128
|
+
if (action.paymentMethodType === 'klarna_paynow') {
|
|
131
129
|
adyenKlarnaPayNow.handleAction(action as any);
|
|
132
130
|
} else {
|
|
133
131
|
console.error('Invalid Klarna action structure:', action);
|
|
@@ -137,7 +135,8 @@ export function KlarnaPayNow() {
|
|
|
137
135
|
}, [action, adyenKlarnaPayNow, onError, step]);
|
|
138
136
|
|
|
139
137
|
return (
|
|
140
|
-
|
|
138
|
+
<>
|
|
139
|
+
<div ref={klarnaContainer} />
|
|
141
140
|
<button
|
|
142
141
|
aria-hidden={true}
|
|
143
142
|
ref={hiddenFormSubmitButton}
|
|
@@ -146,6 +145,6 @@ export function KlarnaPayNow() {
|
|
|
146
145
|
}}
|
|
147
146
|
type="submit"
|
|
148
147
|
/>
|
|
149
|
-
|
|
148
|
+
</>
|
|
150
149
|
);
|
|
151
150
|
}
|
|
@@ -64,13 +64,15 @@ export function PayPal() {
|
|
|
64
64
|
* Handle the errors.
|
|
65
65
|
*/
|
|
66
66
|
const onError = useCallback(
|
|
67
|
-
({ message }) =>
|
|
67
|
+
({ message }) => {
|
|
68
68
|
dispatch(
|
|
69
69
|
AdyenMessage(ActionTypes.Error, {
|
|
70
70
|
cancelPayment: true,
|
|
71
71
|
message,
|
|
72
72
|
}),
|
|
73
|
-
)
|
|
73
|
+
);
|
|
74
|
+
dispatch(AdyenMessage(ActionTypes.Cancel));
|
|
75
|
+
},
|
|
74
76
|
[dispatch],
|
|
75
77
|
);
|
|
76
78
|
|
|
@@ -125,7 +127,11 @@ export function PayPal() {
|
|
|
125
127
|
*
|
|
126
128
|
* @link Adyen PayPal Component Documentation: https://docs.adyen.com/payment-methods/paypal/web-component
|
|
127
129
|
*/
|
|
128
|
-
const adyenPayPal = useMemo<PayPalElement>(() => {
|
|
130
|
+
const adyenPayPal = useMemo<PayPalElement | undefined>(() => {
|
|
131
|
+
if (!adyenCheckout) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
129
135
|
const { PayPal } = (window as any).AdyenWeb;
|
|
130
136
|
|
|
131
137
|
return new PayPal(adyenCheckout, adyenPayPalConfig) as PayPalElement;
|
|
@@ -168,7 +174,8 @@ export function PayPal() {
|
|
|
168
174
|
}, [action, adyenPayPal, step]);
|
|
169
175
|
|
|
170
176
|
return (
|
|
171
|
-
|
|
177
|
+
<>
|
|
178
|
+
<div ref={payPalContainer} />
|
|
172
179
|
<button
|
|
173
180
|
aria-hidden={true}
|
|
174
181
|
ref={hiddenFormSubmitButton}
|
|
@@ -177,6 +184,6 @@ export function PayPal() {
|
|
|
177
184
|
}}
|
|
178
185
|
type="submit"
|
|
179
186
|
/>
|
|
180
|
-
|
|
187
|
+
</>
|
|
181
188
|
);
|
|
182
189
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wearejh/m2-pwa-adyen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"description": "> TODO: description",
|
|
5
5
|
"author": "Shane Osbourne <shane.osbourne8@gmail.com>",
|
|
6
6
|
"homepage": "",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@adyen/adyen-web": "6.27.1",
|
|
25
|
-
"@wearejh/m2-pwa-cart": "^0.
|
|
26
|
-
"@wearejh/m2-pwa-checkout": "^0.
|
|
27
|
-
"@wearejh/m2-pwa-engine": "^0.
|
|
28
|
-
"@wearejh/react-hooks": "^0.
|
|
29
|
-
"@wearejh/rx-form": "^0.
|
|
30
|
-
"@wearejh/swagger-rxjs": "^0.
|
|
25
|
+
"@wearejh/m2-pwa-cart": "^0.50.0",
|
|
26
|
+
"@wearejh/m2-pwa-checkout": "^0.50.0",
|
|
27
|
+
"@wearejh/m2-pwa-engine": "^0.50.0",
|
|
28
|
+
"@wearejh/react-hooks": "^0.50.0",
|
|
29
|
+
"@wearejh/rx-form": "^0.50.0",
|
|
30
|
+
"@wearejh/swagger-rxjs": "^0.50.0",
|
|
31
31
|
"load-js": "^3.0.3"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "d60d77034a7a62d49b970725d8e07c6d706f1f30"
|
|
34
34
|
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { SimpleModal } from '@wearejh/m2-pwa-checkout/lib/components/Modal/SimpleModal';
|
|
2
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { useDispatch } from 'react-redux';
|
|
4
|
-
import type { ThreeDS2Challenge as ThreeDS2ChallengeElement, ThreeDS2ChallengeConfiguration } from '@adyen/adyen-web';
|
|
5
|
-
|
|
6
|
-
import { useAdyen } from '../../AdyenProvider';
|
|
7
|
-
import { ActionTypes, AdyenMessage } from '../../adyen.actions';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* React warapper component for the Adyen
|
|
11
|
-
* `threeDS2Challenge` component.
|
|
12
|
-
*/
|
|
13
|
-
export function ThreeDS2Challenge() {
|
|
14
|
-
const [isModalReady, setIsModalReady] = useState(false);
|
|
15
|
-
|
|
16
|
-
const { action, adyenCheckout, paymentFormSubmitted } = useAdyen();
|
|
17
|
-
|
|
18
|
-
const dispatch = useDispatch();
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The HTML Div element reference the adyen 3DS2 challenge
|
|
22
|
-
* component will be mounted on.
|
|
23
|
-
*/
|
|
24
|
-
const challengeContainer = useRef<HTMLDivElement>(null);
|
|
25
|
-
|
|
26
|
-
const paymentData = action?.paymentData;
|
|
27
|
-
const token = action?.token;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Cancel 3DS2 Authentication
|
|
31
|
-
* (Not needed at the moment for the Adyen Bug)
|
|
32
|
-
*/
|
|
33
|
-
// const dismiss = useCallback(() => dispatch(AdyenMessage(ActionTypes.Cancel)), [dispatch]);
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The submit function which is called automatically
|
|
37
|
-
* when the user has completed the challenge.
|
|
38
|
-
*/
|
|
39
|
-
const onComplete = useCallback(
|
|
40
|
-
(state) => {
|
|
41
|
-
dispatch(AdyenMessage(ActionTypes.ProcessPaymentDetails, state.data));
|
|
42
|
-
},
|
|
43
|
-
[dispatch],
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Handle the errors.
|
|
48
|
-
*/
|
|
49
|
-
const onError = useCallback(
|
|
50
|
-
({ message }) =>
|
|
51
|
-
dispatch(
|
|
52
|
-
AdyenMessage(ActionTypes.Error, {
|
|
53
|
-
cancelPayment: true,
|
|
54
|
-
message,
|
|
55
|
-
}),
|
|
56
|
-
),
|
|
57
|
-
[dispatch],
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Adyen `threeDS2Challenge`` component configuration.
|
|
62
|
-
*
|
|
63
|
-
* Note: Some configuration options are inherited
|
|
64
|
-
* from the `adyenCheckout` instance, so are
|
|
65
|
-
* not necessary to redefine here.
|
|
66
|
-
*/
|
|
67
|
-
const adyenChallengeConfig = useMemo<ThreeDS2ChallengeConfiguration | undefined>(() => {
|
|
68
|
-
if (paymentData && token) {
|
|
69
|
-
return {
|
|
70
|
-
onComplete,
|
|
71
|
-
onError,
|
|
72
|
-
paymentData,
|
|
73
|
-
token,
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* `useOriginalFlow` indicates we're using the
|
|
77
|
-
* threeDS2Challenge' component which is part
|
|
78
|
-
* of the old Adyen 3DS2 flow.
|
|
79
|
-
*
|
|
80
|
-
* By default the challenge data will be created
|
|
81
|
-
* in the way that the Adyen `/details` - in the
|
|
82
|
-
* v67 API - endpoint expects.
|
|
83
|
-
* `useOriginalFlow` adapts the challenge data to
|
|
84
|
-
* be compatible with the v66 API, which is what
|
|
85
|
-
* we need for Magento.
|
|
86
|
-
*/
|
|
87
|
-
useOriginalFlow: true,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}, [onComplete, onError, paymentData, token]);
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The submit function which is called automatically
|
|
94
|
-
* when the user has completed the challenge.
|
|
95
|
-
*/
|
|
96
|
-
const adyenChallenge = useMemo<ThreeDS2ChallengeElement>(() => {
|
|
97
|
-
const { ThreeDS2Challenge } = (window as any).AdyenWeb;
|
|
98
|
-
|
|
99
|
-
return new ThreeDS2Challenge(adyenCheckout, adyenChallengeConfig) as ThreeDS2ChallengeElement;
|
|
100
|
-
}, [adyenChallengeConfig, adyenCheckout]);
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Wait for the modal 3DS2 form to be ready.
|
|
104
|
-
*/
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
const poll = setInterval(() => {
|
|
107
|
-
if (!isModalReady && challengeContainer.current) {
|
|
108
|
-
setIsModalReady(true);
|
|
109
|
-
clearInterval(poll);
|
|
110
|
-
}
|
|
111
|
-
}, 100);
|
|
112
|
-
|
|
113
|
-
return () => clearInterval(poll);
|
|
114
|
-
}, [isModalReady, setIsModalReady, challengeContainer]);
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Mount the Adyen 3DS2 Challenge instance, when ready.
|
|
118
|
-
*/
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
if (adyenChallenge && adyenCheckout && challengeContainer.current && isModalReady) {
|
|
121
|
-
adyenChallenge.mount(challengeContainer.current);
|
|
122
|
-
}
|
|
123
|
-
}, [adyenChallenge, adyenCheckout, challengeContainer, isModalReady]);
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* When the payment form has been submitted,
|
|
127
|
-
* trigger the submit function of the
|
|
128
|
-
* Adyen card component.
|
|
129
|
-
*/
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (adyenChallenge && paymentFormSubmitted) {
|
|
132
|
-
adyenChallenge.submit();
|
|
133
|
-
}
|
|
134
|
-
}, [adyenChallenge, paymentFormSubmitted]);
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<SimpleModal dismiss={() => {}} title="3DS" hideModalFooter={true}>
|
|
138
|
-
<div ref={challengeContainer} />
|
|
139
|
-
</SimpleModal>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import { useDispatch } from 'react-redux';
|
|
3
|
-
import type { ThreeDS2DeviceFingerprint, ThreeDS2DeviceFingerprintConfiguration } from '@adyen/adyen-web';
|
|
4
|
-
|
|
5
|
-
import { useAdyen } from '../../AdyenProvider';
|
|
6
|
-
import { AdyenSteps } from '../../adyen.types';
|
|
7
|
-
import { ActionTypes, AdyenMessage } from '../../adyen.actions';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* React warapper component for the Adyen
|
|
11
|
-
* `threeDS2Fingerprint` component.
|
|
12
|
-
*/
|
|
13
|
-
export function ThreeDS2Fingerprint() {
|
|
14
|
-
const { action, adyenCheckout, step } = useAdyen();
|
|
15
|
-
|
|
16
|
-
const dispatch = useDispatch();
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The HTML Div element reference the adyen card
|
|
20
|
-
* component will be mounted on.
|
|
21
|
-
*/
|
|
22
|
-
const fingerprintContainer = useRef<HTMLDivElement>(null);
|
|
23
|
-
|
|
24
|
-
const paymentData = action?.paymentData;
|
|
25
|
-
const token = action?.token;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The submit function which is called automatically
|
|
29
|
-
* when the component has generated a fingerprint.
|
|
30
|
-
*/
|
|
31
|
-
const onComplete = useCallback(
|
|
32
|
-
(result) => {
|
|
33
|
-
dispatch(AdyenMessage(ActionTypes.ProcessPaymentDetails, result.data));
|
|
34
|
-
},
|
|
35
|
-
[dispatch],
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const onError = useCallback(
|
|
39
|
-
(error) => {
|
|
40
|
-
if (error.errorCode !== 'timeout') {
|
|
41
|
-
dispatch(AdyenMessage(ActionTypes.ProcessPaymentDetails, error.message));
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
[dispatch],
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Adyen `threeDS2DeviceFingerprint`` component configuration.
|
|
49
|
-
*
|
|
50
|
-
* Note: Some configuration options are inherited
|
|
51
|
-
* from the `adyenCheckout` instance, so are
|
|
52
|
-
* not necessary to redefine here.
|
|
53
|
-
*/
|
|
54
|
-
const deviceFingerprintConfig = useMemo<ThreeDS2DeviceFingerprintConfiguration | undefined>(() => {
|
|
55
|
-
if (paymentData && token) {
|
|
56
|
-
return {
|
|
57
|
-
onComplete,
|
|
58
|
-
onError,
|
|
59
|
-
paymentData,
|
|
60
|
-
token,
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* `useOriginalFlow` indicates we're using the
|
|
64
|
-
* threeDS2Fingerprint' component which is part
|
|
65
|
-
* of the old Adyen 3DS2 flow.
|
|
66
|
-
*
|
|
67
|
-
* By using the original flow, we'll be interacting
|
|
68
|
-
* with different API endpoints compared to the
|
|
69
|
-
* newer flow.
|
|
70
|
-
*/
|
|
71
|
-
useOriginalFlow: true,
|
|
72
|
-
showSpinner: true,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}, [onError, onComplete, paymentData, token]);
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Adyen device fingerprint component instance.
|
|
79
|
-
*/
|
|
80
|
-
const deviceFingerprint = useMemo<ThreeDS2DeviceFingerprint>(() => {
|
|
81
|
-
const { ThreeDS2DeviceFingerprint } = (window as any).AdyenWeb;
|
|
82
|
-
|
|
83
|
-
return new ThreeDS2DeviceFingerprint(adyenCheckout, deviceFingerprintConfig) as ThreeDS2DeviceFingerprint;
|
|
84
|
-
}, [adyenCheckout, deviceFingerprintConfig]);
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Mount the Adyen device fingerprint instance, when ready.
|
|
88
|
-
*/
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (
|
|
91
|
-
adyenCheckout &&
|
|
92
|
-
deviceFingerprint &&
|
|
93
|
-
fingerprintContainer.current &&
|
|
94
|
-
step === AdyenSteps.ThreeDS2Fingerprint
|
|
95
|
-
) {
|
|
96
|
-
deviceFingerprint.mount(fingerprintContainer.current);
|
|
97
|
-
}
|
|
98
|
-
}, [adyenCheckout, deviceFingerprint, fingerprintContainer, step]);
|
|
99
|
-
|
|
100
|
-
return <div ref={fingerprintContainer}></div>;
|
|
101
|
-
}
|