@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 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
@@ -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,
@@ -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
- ActionThreeDS2Challenge = 'Adyen.Action.3DS2.Challenge',
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.ActionThreeDS2Challenge]: AdyenPaymentAction;
59
- [ActionTypes.ActionThreeDS2Fingerprint]: AdyenPaymentAction;
57
+ [ActionTypes.ActionThreeDS2]: AdyenPaymentAction;
60
58
  [ActionTypes.Cancel]: undefined;
61
59
  [ActionTypes.Error]: AdyenErrorAction;
62
60
  [ActionTypes.GetConfig]: undefined;
@@ -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.ActionThreeDS2Challenge:
55
+ case ActionTypes.ActionThreeDS2:
56
56
  newState.action = action.payload;
57
- newState.step = AdyenSteps.ThreeDS2Challenge;
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:
@@ -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
- brandCode?: AdyenBrandCodes;
126
- browserInfo: Record<string, any>;
127
- paymentMethod: Record<string, any>;
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
- ThreeDS2Challenge = '3DS2.Challenge',
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
- cvc: payload?.paymentMethod?.encryptedSecurityCode,
100
- expiryMonth: payload?.paymentMethod?.encryptedExpiryMonth,
101
- expiryYear: payload?.paymentMethod?.encryptedExpiryYear,
102
- java_enabled: payload?.browserInfo?.javaEnabled || false,
103
- language: payload?.browserInfo?.language || '',
104
- number: payload?.paymentMethod?.encryptedCardNumber,
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
- brandCode: payload?.brandCode,
24
- browserInfo: payload?.browserInfo,
25
- paymentMethod: payload?.paymentMethod,
26
- type: payload?.type,
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.27.1';
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}></div>
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 { AdyenSteps, MagentoPaymentTypes } from '../../adyen.types';
8
- import { ThreeDS2Challenge } from '../3DS/ThreeDS2Challenge';
9
- import { ThreeDS2Fingerprint } from '../3DS/ThreeDS2Fingerprint';
7
+ import { MagentoPaymentTypes } from '../../adyen.types';
8
+ import { ThreeDS2 } from '../3DS/ThreeDS2';
10
9
 
11
10
  /**
12
- * React warapper component for the Adyen Card component.
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 presseds submit button.
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 user is shown a verification popup.
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, step } = useAdyen();
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
- const { Card } = (window as any).AdyenWeb;
94
+ const adyenCard = useMemo<CardElement | undefined>(() => {
95
+ if (!adyenCheckout) {
96
+ return undefined;
97
+ }
90
98
 
91
- return new Card(adyenCheckout, adyenCardConfig).mount('#card') as CardElement;
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, cardContainer]);
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}></div>
117
- {step === AdyenSteps.ThreeDS2Challenge && <ThreeDS2Challenge />}
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 { AdyenSteps, MagentoPaymentTypes } from '../../adyen.types';
8
- import { ThreeDS2Challenge } from '../3DS/ThreeDS2Challenge';
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, step } = useAdyen();
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}></div>
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
- {step === AdyenSteps.ThreeDS2Challenge && <ThreeDS2Challenge />}
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
- // adyenKlarnaOverTime?.handleAction(action);
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
- <div ref={klarnaContainer}>
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
- </div>
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.KlarnaRedirect) {
134
- // adyenKlarnaOverTime?.handleAction(action);
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
- <div ref={klarnaContainer}>
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
- </div>
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.KlarnaRedirect) {
124
- // adyenKlarnaOverTime?.handleAction(action);
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
- <div ref={klarnaContainer}>
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
- </div>
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
- <div ref={payPalContainer}>
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
- </div>
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.45.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.45.0",
26
- "@wearejh/m2-pwa-checkout": "^0.45.0",
27
- "@wearejh/m2-pwa-engine": "^0.45.0",
28
- "@wearejh/react-hooks": "^0.45.0",
29
- "@wearejh/rx-form": "^0.45.0",
30
- "@wearejh/swagger-rxjs": "^0.45.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": "f22c9f8583c85902860ec49590cae76752f8d227"
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
- }