From 94afef0d7fa0619ac51c929db6ec51b00f3baa2c Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Mon, 13 Nov 2023 10:56:33 +0100
Subject: [PATCH 01/55] draft: async submit

---
 packages/lib/src/components/Dropin/Dropin.tsx |  10 +-
 .../src/components/GooglePay/GooglePay.tsx    |  97 +++++++---
 .../components/GooglePay/GooglePayService.ts  |   6 +-
 .../src/components/GooglePay/defaultProps.ts  |   4 +-
 .../lib/src/components/GooglePay/types.ts     |   1 -
 .../lib/src/components/GooglePay/utils.ts     |  30 +--
 packages/lib/src/components/UIElement.tsx     | 175 +++++++++++++-----
 packages/lib/src/components/types.ts          |   2 +-
 packages/lib/src/core/types.ts                |   4 +-
 .../playground/src/pages/Dropin/manual.js     |  80 +++++++-
 10 files changed, 306 insertions(+), 103 deletions(-)

diff --git a/packages/lib/src/components/Dropin/Dropin.tsx b/packages/lib/src/components/Dropin/Dropin.tsx
index fa5745a8ad..8b2a8b6525 100644
--- a/packages/lib/src/components/Dropin/Dropin.tsx
+++ b/packages/lib/src/components/Dropin/Dropin.tsx
@@ -84,11 +84,19 @@ class DropinElement extends UIElement<DropinElementProps> {
     /**
      * Calls the onSubmit event with the state of the activePaymentMethod
      */
-    public submit(): void {
+    public async submit(): Promise<void> {
         if (!this.activePaymentMethod) {
             throw new Error('No active payment method.');
         }
 
+        if (!this.activePaymentMethod.isValid) {
+            this.activePaymentMethod.showValidation();
+        }
+
+        if (this.activePaymentMethod.isInstantPayment) {
+            this.closeActivePaymentMethod();
+        }
+
         this.activePaymentMethod.submit();
     }
 
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 1f4d2e1f58..df644f3c16 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -1,10 +1,10 @@
 import { h } from 'preact';
-import UIElement from '../UIElement';
+import UIElement, { SubmitReject } from '../UIElement';
 import GooglePayService from './GooglePayService';
 import GooglePayButton from './components/GooglePayButton';
 import defaultProps from './defaultProps';
 import { GooglePayProps } from './types';
-import { mapBrands, getGooglePayLocale } from './utils';
+import { getGooglePayLocale } from './utils';
 import collectBrowserInfo from '../../utils/browserInfo';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
@@ -14,23 +14,33 @@ class GooglePay extends UIElement<GooglePayProps> {
     public static txVariants = [TxVariants.googlepay, TxVariants.paywithgoogle];
     public static defaultProps = defaultProps;
 
-    protected googlePay = new GooglePayService(this.props);
+    protected readonly googlePay;
 
-    /**
-     * Formats the component data input
-     * For legacy support - maps configuration.merchantIdentifier to configuration.merchantId
-     */
-    formatProps(props): GooglePayProps {
-        const allowedCardNetworks = props.brands?.length ? mapBrands(props.brands) : props.allowedCardNetworks;
+    constructor(props) {
+        super(props);
+
+        this.googlePay = new GooglePayService({
+            ...this.props,
+            paymentDataCallbacks: {
+                ...this.props.paymentDataCallbacks,
+                onPaymentAuthorized: this.onPaymentAuthorized
+            }
+        });
+    }
+
+    formatProps(props: GooglePayProps): GooglePayProps {
+        // const allowedCardNetworks = props.brands?.length ? mapBrands(props.brands) : props.allowedCardNetworks; BRANDS not documented
         const buttonSizeMode = props.buttonSizeMode ?? (props.isDropin ? 'fill' : 'static');
         const buttonLocale = getGooglePayLocale(props.buttonLocale ?? props.i18n?.locale);
+
+        const callbackIntents: google.payments.api.CallbackIntent[] = [...props.callbackIntents, 'PAYMENT_AUTHORIZATION'];
+
         return {
             ...props,
-            showButton: props.showPayButton === true,
             configuration: props.configuration,
-            allowedCardNetworks,
             buttonSizeMode,
-            buttonLocale
+            buttonLocale,
+            callbackIntents
         };
     }
 
@@ -41,25 +51,18 @@ class GooglePay extends UIElement<GooglePayProps> {
         return {
             paymentMethod: {
                 type: this.type,
-                ...this.state
+                googlePayCardNetwork: this.state.googlePayCardNetwork,
+                googlePayToken: '{}'
             },
             browserInfo: this.browserInfo
         };
     }
 
     public submit = () => {
-        const { onAuthorized = () => {} } = this.props;
-
         return new Promise((resolve, reject) => this.props.onClick(resolve, reject))
             .then(() => this.googlePay.initiatePayment(this.props))
-            .then(paymentData => {
-                this.setState({
-                    googlePayToken: paymentData.paymentMethodData.tokenizationData.token,
-                    googlePayCardNetwork: paymentData.paymentMethodData.info.cardNetwork
-                });
-                super.submit();
-
-                return onAuthorized(paymentData);
+            .then(() => {
+                console.log('HERE');
             })
             .catch((error: google.payments.api.PaymentsError) => {
                 if (error.statusCode === 'CANCELED') {
@@ -70,6 +73,54 @@ class GooglePay extends UIElement<GooglePayProps> {
             });
     };
 
+    /**
+     * Method called when the payment is authorized in the payment sheet
+     *
+     * @see https://developers.google.com/pay/api/web/reference/client#onPaymentAuthorized
+     **/
+    private onPaymentAuthorized = async (paymentData: google.payments.api.PaymentData): Promise<google.payments.api.PaymentAuthorizationResult> => {
+        this.setState({
+            authorizedData: paymentData,
+            googlePayToken: paymentData.paymentMethodData.tokenizationData.token,
+            googlePayCardNetwork: paymentData.paymentMethodData.info.cardNetwork
+        });
+
+        return new Promise<google.payments.api.PaymentAuthorizationResult>(resolve => {
+            super
+                .submit()
+                .then(result => {
+                    console.log('Resolving');
+
+                    resolve({ transactionState: 'SUCCESS' });
+                    this.props.onPaymentCompleted(result, this.elementRef);
+                })
+                .catch(googlePayError => {
+                    console.log('Caught error');
+
+                    resolve({
+                        transactionState: 'ERROR',
+                        error: {
+                            intent: 'PAYMENT_AUTHORIZATION',
+                            message: googlePayError?.message || 'Something went wrong',
+                            reason: googlePayError?.reason || 'OTHER_ERROR'
+                        }
+                    });
+                });
+        });
+    };
+
+    protected override throwPaymentMethodErrorIfNeeded = (error?: SubmitReject): never => {
+        this.setElementStatus('ready');
+
+        const googleError: google.payments.api.PaymentDataError = {
+            intent: 'PAYMENT_AUTHORIZATION',
+            message: error?.googlePayError?.message || 'Something went wrong',
+            reason: error?.googlePayError?.reason || 'OTHER_ERROR'
+        };
+
+        throw googleError;
+    };
+
     /**
      * Validation
      */
diff --git a/packages/lib/src/components/GooglePay/GooglePayService.ts b/packages/lib/src/components/GooglePay/GooglePayService.ts
index 61ac959ead..c6b8c6db77 100644
--- a/packages/lib/src/components/GooglePay/GooglePayService.ts
+++ b/packages/lib/src/components/GooglePay/GooglePayService.ts
@@ -3,10 +3,14 @@ import { resolveEnvironment } from './utils';
 import Script from '../../utils/Script';
 import config from './config';
 
+interface GooglePayServiceProps {
+    [key: string]: any;
+}
+
 class GooglePayService {
     public readonly paymentsClient: Promise<google.payments.api.PaymentsClient>;
 
-    constructor(props) {
+    constructor(props: GooglePayServiceProps) {
         const environment = resolveEnvironment(props.environment);
         if (environment === 'TEST' && process.env.NODE_ENV === 'development') {
             console.warn('Google Pay initiated in TEST mode. Request non-chargeable payment methods suitable for testing.');
diff --git a/packages/lib/src/components/GooglePay/defaultProps.ts b/packages/lib/src/components/GooglePay/defaultProps.ts
index 5662ad1e41..cf4e646aaa 100644
--- a/packages/lib/src/components/GooglePay/defaultProps.ts
+++ b/packages/lib/src/components/GooglePay/defaultProps.ts
@@ -48,5 +48,7 @@ export default {
     shippingAddressParameters: undefined, // https://developers.google.com/pay/api/web/reference/object#ShippingAddressParameters
     shippingOptionRequired: false,
     shippingOptionParameters: undefined,
-    paymentMethods: []
+    paymentMethods: [],
+
+    callbackIntents: []
 };
diff --git a/packages/lib/src/components/GooglePay/types.ts b/packages/lib/src/components/GooglePay/types.ts
index ec0ad1b216..5e0bff0570 100644
--- a/packages/lib/src/components/GooglePay/types.ts
+++ b/packages/lib/src/components/GooglePay/types.ts
@@ -35,7 +35,6 @@ export interface GooglePayPropsConfiguration {
 export interface GooglePayProps extends UIElementProps {
     type?: 'googlepay' | 'paywithgoogle';
 
-    environment?: google.payments.api.Environment | string;
     configuration?: GooglePayPropsConfiguration;
 
     /**
diff --git a/packages/lib/src/components/GooglePay/utils.ts b/packages/lib/src/components/GooglePay/utils.ts
index aec6d7b700..737a75e748 100644
--- a/packages/lib/src/components/GooglePay/utils.ts
+++ b/packages/lib/src/components/GooglePay/utils.ts
@@ -16,21 +16,21 @@ export function resolveEnvironment(env = 'TEST'): google.payments.api.Environmen
     }
 }
 
-export function mapBrands(brands) {
-    const brandMapping = {
-        mc: 'MASTERCARD',
-        amex: 'AMEX',
-        visa: 'VISA',
-        interac: 'INTERAC',
-        discover: 'DISCOVER'
-    };
-    return brands.reduce((accumulator, item) => {
-        if (!!brandMapping[item] && !accumulator.includes(brandMapping[item])) {
-            accumulator.push(brandMapping[item]);
-        }
-        return accumulator;
-    }, []);
-}
+// export function mapBrands(brands) {
+//     const brandMapping = {
+//         mc: 'MASTERCARD',
+//         amex: 'AMEX',
+//         visa: 'VISA',
+//         interac: 'INTERAC',
+//         discover: 'DISCOVER'
+//     };
+//     return brands.reduce((accumulator, item) => {
+//         if (!!brandMapping[item] && !accumulator.includes(brandMapping[item])) {
+//             accumulator.push(brandMapping[item]);
+//         }
+//         return accumulator;
+//     }, []);
+// }
 
 const supportedLocales = [
     'en',
diff --git a/packages/lib/src/components/UIElement.tsx b/packages/lib/src/components/UIElement.tsx
index 8a83a23822..75991529e0 100644
--- a/packages/lib/src/components/UIElement.tsx
+++ b/packages/lib/src/components/UIElement.tsx
@@ -1,6 +1,6 @@
 import { h } from 'preact';
 import BaseElement from './BaseElement';
-import { PaymentAction } from '../types';
+import { CheckoutSessionPaymentResponse, PaymentAction } from '../types';
 import { ComponentMethodsRef, PaymentResponse } from './types';
 import PayButton from './internal/PayButton';
 import { IUIElement, PayButtonFunctionProps, RawPaymentResponse, UIElementProps } from './types';
@@ -8,12 +8,22 @@ import { getSanitizedResponse, resolveFinalResult } from './utils';
 import AdyenCheckoutError from '../core/Errors/AdyenCheckoutError';
 import { UIElementStatus } from './types';
 import { hasOwnProperty } from '../utils/hasOwnProperty';
-import DropinElement from './Dropin';
 import { CoreOptions, ICore } from '../core/types';
 import { Resources } from '../core/Context/Resources';
 import { NewableComponent } from '../core/core.registry';
 import './UIElement.scss';
 
+export type SubmitReject = {
+    googlePayError?: {
+        message?: string;
+        reason?: google.payments.api.ErrorReason;
+    };
+    applePayError?: {
+        // TOOD
+        [key: string]: any;
+    };
+};
+
 export abstract class UIElement<P extends UIElementProps = UIElementProps> extends BaseElement<P> implements IUIElement {
     protected componentRef: any;
 
@@ -40,10 +50,10 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         this.setState = this.setState.bind(this);
         this.onValid = this.onValid.bind(this);
         this.onComplete = this.onComplete.bind(this);
-        this.onSubmit = this.onSubmit.bind(this);
+        this.makePaymentsCall = this.makePaymentsCall.bind(this);
         this.handleAction = this.handleAction.bind(this);
         this.handleOrder = this.handleOrder.bind(this);
-        this.handleResponse = this.handleResponse.bind(this);
+        this.handleSessionsResponse = this.handleSessionsResponse.bind(this);
         this.setElementStatus = this.setElementStatus.bind(this);
 
         this.elementRef = (props && props.elementRef) || this;
@@ -82,6 +92,9 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
 
     public setState(newState: object): void {
         this.state = { ...this.state, ...newState };
+
+        console.log('new state', this.state);
+
         this.onChange();
     }
 
@@ -94,41 +107,106 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         return state;
     }
 
-    private onSubmit(): void {
-        //TODO: refactor this, instant payment methods are part of Dropin logic not UIElement
-        if (this.props.isInstantPayment) {
-            const dropinElementRef = this.elementRef as unknown as DropinElement;
-            dropinElementRef.closeActivePaymentMethod();
-        }
-
+    /**
+     * Triggers the payment flow
+     */
+    private async makePaymentsCall(): Promise<void> {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('loading');
         }
 
         if (this.props.onSubmit) {
-            // Classic flow
-            this.props.onSubmit({ data: this.data, isValid: this.isValid }, this.elementRef);
-        } else if (this.core.session) {
-            // Session flow
-            // wrap beforeSubmit callback in a promise
-            const beforeSubmitEvent = this.props.beforeSubmit
-                ? new Promise((resolve, reject) =>
-                      this.props.beforeSubmit(this.data, this.elementRef, {
-                          resolve,
-                          reject
-                      })
-                  )
-                : Promise.resolve(this.data);
-
-            beforeSubmitEvent
-                .then(data => this.submitPayment(data))
-                .catch(() => {
-                    // set state as ready to submit if the merchant cancels the action
-                    this.elementRef.setStatus('ready');
-                });
-        } else {
-            this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Could not submit the payment'));
+            return this.submitUsingAdvancedFlow();
+        }
+
+        if (this.core.session) {
+            return this.submitUsingSessionsFlow();
         }
+
+        this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Could not submit the payment'));
+    }
+
+    private async submitUsingAdvancedFlow() {
+        return (
+            new Promise((resolve, reject) => {
+                this.props.onSubmit(
+                    {
+                        data: this.data,
+                        isValid: this.isValid,
+                        ...(this.state.authorizedData && { authorizedData: this.state.authorizedData })
+                    },
+                    this.elementRef,
+                    { resolve, reject }
+                );
+            })
+                .then((data: any) => {
+                    if (data.action) {
+                        this.elementRef.handleAction(data.action, ...data.actionProps);
+                        return;
+                    }
+                    if (data.order) {
+                        const { order, paymentMethodsResponse } = data;
+                        // @ts-ignore Just testing
+                        this.core.update({ paymentMethodsResponse, order, amount: data.order.remainingAmount });
+                        return;
+                    }
+
+                    this.handleFinalResult(data);
+                })
+                // action.reject got called OR something fail above. TODO: add proper checks
+                .catch(error => {
+                    this.throwPaymentMethodErrorIfNeeded(error);
+                })
+        );
+    }
+
+    private async submitUsingSessionsFlow() {
+        const beforeSubmitEvent = this.props.beforeSubmit
+            ? new Promise((resolve, reject) =>
+                  this.props.beforeSubmit(this.data, this.elementRef, {
+                      resolve,
+                      reject
+                  })
+              )
+            : Promise.resolve(this.data);
+
+        let data;
+
+        try {
+            data = await beforeSubmitEvent;
+        } catch {
+            // set state as ready to submit if the merchant cancels the action
+            this.elementRef.setStatus('ready');
+            return;
+        }
+
+        return this.makeSessionPaymentsCall(data);
+    }
+
+    /**
+     * Method used to break the /payments flow and feed the error data back to the component in case the
+     * payment fails.
+     *
+     * Example: GooglePay / ApplePay accepts data from merchant in order to display custom errors
+     *
+     * @param error - Error object that can be passed back by the merchant
+     */
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    protected throwPaymentMethodErrorIfNeeded(error?: SubmitReject): void | never {
+        return;
+    }
+
+    private async makeSessionPaymentsCall(data): Promise<void> {
+        let paymentsResponse: CheckoutSessionPaymentResponse = null;
+
+        try {
+            paymentsResponse = await this.core.session.submitPayment(data);
+        } catch (error) {
+            this.handleError(error);
+            this.throwPaymentMethodErrorIfNeeded();
+        }
+
+        this.handleSessionsResponse(paymentsResponse);
     }
 
     private onValid() {
@@ -144,13 +222,13 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     /**
      * Submit payment method data. If the form is not valid, it will trigger validation.
      */
-    public submit(): void {
+    public async submit(): Promise<void> {
         if (!this.isValid) {
             this.showValidation();
             return;
         }
 
-        this.onSubmit();
+        return this.makePaymentsCall();
     }
 
     public showValidation(): this {
@@ -170,15 +248,8 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         return this;
     }
 
-    private submitPayment(data): Promise<void> {
-        return this.core.session
-            .submitPayment(data)
-            .then(this.handleResponse)
-            .catch(error => this.handleError(error));
-    }
-
     private submitAdditionalDetails(data): Promise<void> {
-        return this.core.session.submitDetails(data).then(this.handleResponse).catch(this.handleError);
+        return this.core.session.submitDetails(data).then(this.handleSessionsResponse).catch(this.handleError);
     }
 
     protected handleError = (error: AdyenCheckoutError): void => {
@@ -236,13 +307,16 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     };
 
     protected handleFinalResult = (result: PaymentResponse) => {
-        if (this.props.setStatusAutomatically) {
-            const [status, statusProps] = resolveFinalResult(result);
-            if (status) this.setElementStatus(status, statusProps);
+        const [status, statusProps] = resolveFinalResult(result);
+
+        if (this.props.setStatusAutomatically && status) {
+            this.setElementStatus(status, statusProps);
         }
 
-        if (this.props.onPaymentCompleted) this.props.onPaymentCompleted(result, this.elementRef);
-        return result;
+        if (this.props.onPaymentCompleted) {
+            this.props.onPaymentCompleted(result, this.elementRef);
+        }
+        // return result;
     };
 
     /**
@@ -250,7 +324,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
      * The component will handle automatically actions, orders, and final results.
      * @param rawResponse -
      */
-    protected handleResponse(rawResponse: RawPaymentResponse): void {
+    protected handleSessionsResponse(rawResponse: RawPaymentResponse): void {
         const response = getSanitizedResponse(rawResponse);
 
         if (response.action) {
@@ -260,7 +334,8 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
             // we do this way so the logic on handlingOrder is associated with payment method
             this.handleOrder(response);
         } else {
-            this.elementRef.handleFinalResult(response);
+            this.handleFinalResult(response);
+            // this.elementRef.handleFinalResult(response);
         }
     }
 
diff --git a/packages/lib/src/components/types.ts b/packages/lib/src/components/types.ts
index 065e1bbedc..3dce7c2a15 100644
--- a/packages/lib/src/components/types.ts
+++ b/packages/lib/src/components/types.ts
@@ -373,7 +373,7 @@ export interface UIElementProps extends BaseElementProps {
     onChange?: (state: any, element: UIElement) => void;
     onValid?: (state: any, element: UIElement) => void;
     beforeSubmit?: (state: any, element: UIElement, actions: any) => Promise<void>;
-    onSubmit?: (state: any, element: UIElement) => void;
+    onSubmit?: (state: any, element: UIElement, actions: any) => void;
     onComplete?: (state, element: UIElement) => void;
     onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
     onAdditionalDetails?: (state: any, element: UIElement) => void;
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index f8e402f3ae..d576585fbc 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -30,12 +30,14 @@ export interface ICore {
     session?: Session;
 }
 
+export type AdyenEnvironment = 'test' | 'live' | 'live-us' | 'live-au' | 'live-apse' | 'live-in' | string;
+
 export interface CoreOptions {
     session?: any;
     /**
      * Use test. When you're ready to accept live payments, change the value to one of our {@link https://docs.adyen.com/checkout/drop-in-web#testing-your-integration | live environments}.
      */
-    environment?: 'test' | 'live' | 'live-us' | 'live-au' | 'live-apse' | 'live-in' | string;
+    environment?: AdyenEnvironment;
 
     /**
      * Used internally by Pay By Link in order to set its own URL's instead of using the ones mapped in our codebase.
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 281691e6ae..e33136c979 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -19,27 +19,89 @@ export async function initManual() {
         // translationFile: nl_NL,
 
         environment: process.env.__CLIENT_ENV__,
-        onSubmit: async (state, component) => {
+
+        onSubmit: async (state, component, actions) => {
+            const { authorizedData } = state;
+            console.log('authorizedData', authorizedData);
+
             const result = await makePayment(state.data);
 
-            // handle actions
             if (result.action) {
-                // demo only - store paymentData & order
-                if (result.action.paymentData) localStorage.setItem('storedPaymentData', result.action.paymentData);
-                component.handleAction(result.action, { challengeWindowSize: '01' });
-            } else if (result.order && result.order?.remainingAmount?.value > 0) {
-                // handle orders
+                actions.resolve({
+                    action: result.action,
+                    actionProps: {
+                        challengeWindowSize: '01'
+                    }
+                });
+                return;
+            }
+
+            if (result.order && result.order?.remainingAmount?.value > 0) {
                 const order = {
                     orderData: result.order.orderData,
                     pspReference: result.order.pspReference
                 };
 
                 const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-                checkout.update({ paymentMethodsResponse: orderPaymentMethods, order, amount: result.order.remainingAmount });
+
+                actions.resolve({
+                    order,
+                    paymentMethodsResponse: orderPaymentMethods
+                });
+
+                return;
+            }
+
+            if (result.resultCode === 'Authorised' || result.resultCode === 'Received') {
+                actions.resolve(result); // DO I NEED FULL RESULT?
             } else {
-                handleFinalState(result.resultCode, component);
+                actions.reject(result)
             }
+
+
+            //
+            // // Trigger Error for GooglePay
+            // // actions.reject({
+            // //     googlePayError: {
+            // //         message: 'Not sufficient funds',
+            // //         reason: 'OTHER_ERROR,'
+            // //     }
+            // // });
+            //
+            // actions.resolve({ resultCode: result.resultCode });
+        },
+
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
         },
+        //
+        //
+        // // payments call
+        // if (result === AUTHORIZED)
+        //     return action.resolve({ orderTrackng: { /// details }})}
+        // }
+        //
+        //
+        // action.reject(new ApplePayError('')));
+
+        // // handle actions
+        // if (result.action) {
+        //     // demo only - store paymentData & order
+        //     if (result.action.paymentData) localStorage.setItem('storedPaymentData', result.action.paymentData);
+        //     component.handleAction(result.action, { challengeWindowSize: '01' });
+        // } else if (result.order && result.order?.remainingAmount?.value > 0) {
+        //     // handle orders
+        //     const order = {
+        //         orderData: result.order.orderData,
+        //         pspReference: result.order.pspReference
+        //     };
+        //
+        //     const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
+        //     checkout.update({ paymentMethodsResponse: orderPaymentMethods, order, amount: result.order.remainingAmount });
+        // } else {
+        //     handleFinalState(result.resultCode, component);
+        // }
+        // },
         // srConfig: { showPanel: true },
         // onChange: state => {
         //     console.log('onChange', state);

From 0011106c254bd25f3131303343d84dc5cd123777 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Wed, 29 Nov 2023 11:45:29 +0100
Subject: [PATCH 02/55] temp: disabling errors

---
 .../lib/src/components/ApplePay/ApplePay.tsx  |  5 +-
 .../src/components/CashAppPay/CashAppPay.tsx  |  2 +-
 packages/lib/src/components/PayPal/Paypal.tsx |  2 +-
 yarn.lock                                     | 66 ++-----------------
 4 files changed, 11 insertions(+), 64 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index eced99ddf4..f7e4b18073 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -53,9 +53,10 @@ class ApplePayElement extends UIElement<ApplePayElementProps> {
         };
     }
 
-    submit() {
+    // @ts-ignore FIX THIS
+    public submit = async () => {
         return this.startSession(this.props.onAuthorized);
-    }
+    };
 
     private startSession(onPaymentAuthorized: OnAuthorizedCallback) {
         const { version, onValidateMerchant, onPaymentMethodSelected, onShippingMethodSelected, onShippingContactSelected } = this.props;
diff --git a/packages/lib/src/components/CashAppPay/CashAppPay.tsx b/packages/lib/src/components/CashAppPay/CashAppPay.tsx
index 9784c7fcbe..526eb1f03b 100644
--- a/packages/lib/src/components/CashAppPay/CashAppPay.tsx
+++ b/packages/lib/src/components/CashAppPay/CashAppPay.tsx
@@ -93,7 +93,7 @@ export class CashAppPay extends UIElement<CashAppPayElementProps> {
         return this.props.storedPaymentMethodId ? 'Cash App Pay' : '';
     }
 
-    public submit = () => {
+    public submit = async () => {
         const { onClick, storedPaymentMethodId } = this.props;
 
         if (storedPaymentMethodId) {
diff --git a/packages/lib/src/components/PayPal/Paypal.tsx b/packages/lib/src/components/PayPal/Paypal.tsx
index f1e48fafc5..02a5abba0d 100644
--- a/packages/lib/src/components/PayPal/Paypal.tsx
+++ b/packages/lib/src/components/PayPal/Paypal.tsx
@@ -42,7 +42,7 @@ class PaypalElement extends UIElement<PayPalElementProps> {
         };
     }
 
-    public submit = () => {
+    public submit = async () => {
         this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', ERRORS.SUBMIT_NOT_SUPPORTED));
     };
 
diff --git a/yarn.lock b/yarn.lock
index a4a5b8e9bb..cf6da97917 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2506,14 +2506,7 @@
     "@types/node" "*"
     jest-mock "^29.7.0"
 
-"@jest/expect-utils@^29.6.3":
-  version "29.6.3"
-  resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.3.tgz#5ef1a9689fdaa348da837c8be8d1219f56940ea3"
-  integrity sha512-nvOEW4YoqRKD9HBJ9OJ6przvIvP9qilp5nAn1462P5ZlL/MM9SgPEZFyjTGPfs7QkocdUsJa6KjHhyRn4ueItA==
-  dependencies:
-    jest-get-type "^29.6.3"
-
-"@jest/expect-utils@^29.7.0":
+"@jest/expect-utils@^29.5.0", "@jest/expect-utils@^29.7.0":
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6"
   integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==
@@ -9969,16 +9962,6 @@ jest-config@^29.7.0:
     slash "^3.0.0"
     strip-json-comments "^3.1.1"
 
-jest-diff@^29.6.3:
-  version "29.6.3"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.3.tgz#365c6b037ea8e67d2f2af68bc018fc18d44311f0"
-  integrity sha512-3sw+AdWnwH9sSNohMRKA7JiYUJSRr/WS6+sEFfBuhxU5V5GlEVKfvUn8JuMHE0wqKowemR1C2aHy8VtXbaV8dQ==
-  dependencies:
-    chalk "^4.0.0"
-    diff-sequences "^29.6.3"
-    jest-get-type "^29.6.3"
-    pretty-format "^29.6.3"
-
 jest-diff@^29.7.0:
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a"
@@ -10033,7 +10016,7 @@ jest-environment-node@^29.7.0:
     jest-mock "^29.7.0"
     jest-util "^29.7.0"
 
-jest-get-type@^29.6.3:
+jest-get-type@^29.4.3, jest-get-type@^29.6.3:
   version "29.6.3"
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
   integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
@@ -10084,17 +10067,7 @@ jest-leak-detector@^29.7.0:
     jest-get-type "^29.6.3"
     pretty-format "^29.7.0"
 
-jest-matcher-utils@^29.6.3:
-  version "29.6.3"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.3.tgz#a7574092b635d96a38fa0a22d015fb596b9c2efc"
-  integrity sha512-6ZrMYINZdwduSt5Xu18/n49O1IgXdjsfG7NEZaQws9k69eTKWKcVbJBw/MZsjOZe2sSyJFmuzh8042XWwl54Zg==
-  dependencies:
-    chalk "^4.0.0"
-    jest-diff "^29.6.3"
-    jest-get-type "^29.6.3"
-    pretty-format "^29.6.3"
-
-jest-matcher-utils@^29.7.0:
+jest-matcher-utils@^29.5.0, jest-matcher-utils@^29.7.0:
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12"
   integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==
@@ -10104,22 +10077,7 @@ jest-matcher-utils@^29.7.0:
     jest-get-type "^29.6.3"
     pretty-format "^29.7.0"
 
-jest-message-util@^29.6.3:
-  version "29.6.3"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.3.tgz#bce16050d86801b165f20cfde34dc01d3cf85fbf"
-  integrity sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==
-  dependencies:
-    "@babel/code-frame" "^7.12.13"
-    "@jest/types" "^29.6.3"
-    "@types/stack-utils" "^2.0.0"
-    chalk "^4.0.0"
-    graceful-fs "^4.2.9"
-    micromatch "^4.0.4"
-    pretty-format "^29.6.3"
-    slash "^3.0.0"
-    stack-utils "^2.0.3"
-
-jest-message-util@^29.7.0:
+jest-message-util@^29.5.0, jest-message-util@^29.7.0:
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3"
   integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==
@@ -10264,7 +10222,7 @@ jest-snapshot@^29.7.0:
     pretty-format "^29.7.0"
     semver "^7.5.3"
 
-jest-util@^29.0.0, jest-util@^29.7.0:
+jest-util@^29.0.0, jest-util@^29.5.0, jest-util@^29.7.0:
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc"
   integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
@@ -10288,18 +10246,6 @@ jest-util@^29.6.0:
     graceful-fs "^4.2.9"
     picomatch "^2.2.3"
 
-jest-util@^29.6.3:
-  version "29.6.3"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.3.tgz#e15c3eac8716440d1ed076f09bc63ace1aebca63"
-  integrity sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==
-  dependencies:
-    "@jest/types" "^29.6.3"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    ci-info "^3.2.0"
-    graceful-fs "^4.2.9"
-    picomatch "^2.2.3"
-
 jest-validate@^29.7.0:
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c"
@@ -12517,7 +12463,7 @@ pretty-format@^27.0.2:
     ansi-styles "^5.0.0"
     react-is "^17.0.1"
 
-pretty-format@^29.0.0, pretty-format@^29.6.3:
+pretty-format@^29.0.0:
   version "29.6.3"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7"
   integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==

From 1372f66a8a6f61977e3c64d5214c75c333838841 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Wed, 29 Nov 2023 15:08:24 +0100
Subject: [PATCH 03/55] feat: advanced flow with googlepay

---
 packages/lib/src/components/Dropin/Dropin.tsx |  2 +
 .../src/components/GooglePay/GooglePay.tsx    | 57 ++++++++++++-------
 .../playground/src/pages/Dropin/manual.js     | 10 +++-
 3 files changed, 48 insertions(+), 21 deletions(-)

diff --git a/packages/lib/src/components/Dropin/Dropin.tsx b/packages/lib/src/components/Dropin/Dropin.tsx
index 8b2a8b6525..72e0c95ace 100644
--- a/packages/lib/src/components/Dropin/Dropin.tsx
+++ b/packages/lib/src/components/Dropin/Dropin.tsx
@@ -123,6 +123,8 @@ class DropinElement extends UIElement<DropinElementProps> {
     };
 
     public handleAction(action: PaymentAction, props = {}): this | null {
+        debugger;
+
         if (!action || !action.type) {
             if (hasOwnProperty(action, 'action') && hasOwnProperty(action, 'resultCode')) {
                 throw new Error(
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index df644f3c16..9cdec4b6e5 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -52,7 +52,7 @@ class GooglePay extends UIElement<GooglePayProps> {
             paymentMethod: {
                 type: this.type,
                 googlePayCardNetwork: this.state.googlePayCardNetwork,
-                googlePayToken: '{}'
+                googlePayToken: this.state.googlePayToken
             },
             browserInfo: this.browserInfo
         };
@@ -88,38 +88,55 @@ class GooglePay extends UIElement<GooglePayProps> {
         return new Promise<google.payments.api.PaymentAuthorizationResult>(resolve => {
             super
                 .submit()
-                .then(result => {
-                    console.log('Resolving');
-
+                // TODO: add action.resolve type
+                .then((result: any) => {
                     resolve({ transactionState: 'SUCCESS' });
-                    this.props.onPaymentCompleted(result, this.elementRef);
+                    return result;
+                })
+                .then(result => {
+                    debugger;
+                    if (result.action) {
+                        this.elementRef.handleAction(result.action, result.actionProps);
+                        return;
+                    }
+                    //
+                    // // if (data.order) {
+                    // //     const { order, paymentMethodsResponse } = data;
+                    // //     // @ts-ignore Just testing
+                    // //     this.core.update({ paymentMethodsResponse, order, amount: data.order.remainingAmount });
+                    // //     return;
+                    // // }
+                    //
+                    this.handleFinalResult(result);
                 })
-                .catch(googlePayError => {
-                    console.log('Caught error');
+                .catch(error => {
+                    this.setElementStatus('ready');
 
                     resolve({
                         transactionState: 'ERROR',
                         error: {
                             intent: 'PAYMENT_AUTHORIZATION',
-                            message: googlePayError?.message || 'Something went wrong',
-                            reason: googlePayError?.reason || 'OTHER_ERROR'
+                            message: error?.googlePayError?.message || 'Something went wrong',
+                            reason: error?.googlePayError?.reason || 'OTHER_ERROR'
                         }
                     });
                 });
         });
     };
 
-    protected override throwPaymentMethodErrorIfNeeded = (error?: SubmitReject): never => {
-        this.setElementStatus('ready');
-
-        const googleError: google.payments.api.PaymentDataError = {
-            intent: 'PAYMENT_AUTHORIZATION',
-            message: error?.googlePayError?.message || 'Something went wrong',
-            reason: error?.googlePayError?.reason || 'OTHER_ERROR'
-        };
-
-        throw googleError;
-    };
+    private override async submitUsingAdvancedFlow(): Promise<any> {
+        return new Promise((resolve, reject) => {
+            this.props.onSubmit(
+                {
+                    data: this.data,
+                    isValid: this.isValid,
+                    ...(this.state.authorizedData && { authorizedData: this.state.authorizedData })
+                },
+                this.elementRef,
+                { resolve, reject }
+            );
+        });
+    }
 
     /**
      * Validation
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index e33136c979..cb99ead530 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -26,6 +26,8 @@ export async function initManual() {
 
             const result = await makePayment(state.data);
 
+            // actions.reject();
+            //
             if (result.action) {
                 actions.resolve({
                     action: result.action,
@@ -55,9 +57,15 @@ export async function initManual() {
             if (result.resultCode === 'Authorised' || result.resultCode === 'Received') {
                 actions.resolve(result); // DO I NEED FULL RESULT?
             } else {
-                actions.reject(result)
+                actions.reject(result);
             }
 
+            // return {
+            //     googlePayError: {
+            //         message: 'Not sufficient funds',
+            //             reason: 'OTHER_ERROR,'
+            //     }
+            // }
 
             //
             // // Trigger Error for GooglePay

From 9a05b85252d9de884c10bc9b60b30e21cc6bee1f Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Thu, 30 Nov 2023 11:04:10 +0100
Subject: [PATCH 04/55] draft gp sessions flow

---
 .../src/components/GooglePay/GooglePay.tsx    | 92 +++++++++++++------
 packages/lib/src/components/UIElement.tsx     |  8 +-
 2 files changed, 70 insertions(+), 30 deletions(-)

diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 9cdec4b6e5..5c216c9224 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -1,5 +1,5 @@
 import { h } from 'preact';
-import UIElement, { SubmitReject } from '../UIElement';
+import UIElement from '../UIElement';
 import GooglePayService from './GooglePayService';
 import GooglePayButton from './components/GooglePayButton';
 import defaultProps from './defaultProps';
@@ -8,6 +8,9 @@ import { getGooglePayLocale } from './utils';
 import collectBrowserInfo from '../../utils/browserInfo';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
+import { CheckoutSessionPaymentResponse } from '../../types';
+import { PaymentResponse } from '../types';
+import { resolveFinalResult } from '../utils';
 
 class GooglePay extends UIElement<GooglePayProps> {
     public static type = TxVariants.googlepay;
@@ -90,40 +93,66 @@ class GooglePay extends UIElement<GooglePayProps> {
                 .submit()
                 // TODO: add action.resolve type
                 .then((result: any) => {
-                    resolve({ transactionState: 'SUCCESS' });
-                    return result;
-                })
-                .then(result => {
-                    debugger;
+                    // close for 3ds flow
                     if (result.action) {
-                        this.elementRef.handleAction(result.action, result.actionProps);
-                        return;
+                        resolve({ transactionState: 'SUCCESS' });
+                        return result;
+                    }
+                })
+                .then(async result => {
+                    return this.handleOnPaymentAuthorizedResponse(result);
+                })
+                .then(status => {
+                    if (status && status === 'success') {
+                        if (status == 'success') {
+                            resolve({ transactionState: 'SUCCESS' });
+                        } else if (status == 'error') {
+                            resolve({
+                                transactionState: 'ERROR',
+                                error: {
+                                    intent: 'PAYMENT_AUTHORIZATION',
+                                    message: error?.googlePayError?.message || 'Something went wrong',
+                                    reason: error?.googlePayError?.reason || 'OTHER_ERROR'
+                                }
+                            });
+                        }
                     }
-                    //
-                    // // if (data.order) {
-                    // //     const { order, paymentMethodsResponse } = data;
-                    // //     // @ts-ignore Just testing
-                    // //     this.core.update({ paymentMethodsResponse, order, amount: data.order.remainingAmount });
-                    // //     return;
-                    // // }
-                    //
-                    this.handleFinalResult(result);
                 })
                 .catch(error => {
                     this.setElementStatus('ready');
-
-                    resolve({
-                        transactionState: 'ERROR',
-                        error: {
-                            intent: 'PAYMENT_AUTHORIZATION',
-                            message: error?.googlePayError?.message || 'Something went wrong',
-                            reason: error?.googlePayError?.reason || 'OTHER_ERROR'
-                        }
-                    });
+                    console.log(error);
                 });
         });
     };
 
+    // TODO types
+    private handleOnPaymentAuthorizedResponse = async result => {
+        // TODO check is best away to check for sessions/
+        if (this.props.onSubmit) {
+            if (result.action) {
+                this.elementRef.handleAction(result.action, result.actionProps);
+                return;
+            }
+            return this.handleFinalResult(result);
+        } else {
+            return this.handleSessionsResponse(result);
+        }
+    };
+
+    protected handleFinalResult = (result: PaymentResponse) => {
+        const [status, statusProps] = resolveFinalResult(result);
+
+        if (this.props.setStatusAutomatically && status) {
+            this.setElementStatus(status, statusProps);
+        }
+
+        if (this.props.onPaymentCompleted) {
+            this.props.onPaymentCompleted(result, this.elementRef);
+        }
+
+        return result;
+    };
+
     private override async submitUsingAdvancedFlow(): Promise<any> {
         return new Promise((resolve, reject) => {
             this.props.onSubmit(
@@ -138,6 +167,17 @@ class GooglePay extends UIElement<GooglePayProps> {
         });
     }
 
+    protected async makeSessionPaymentsCall(data): Promise<void> {
+        let paymentsResponse: CheckoutSessionPaymentResponse = null;
+        try {
+            paymentsResponse = await this.core.session.submitPayment(data);
+        } catch (error) {
+            // TODO resolve with error
+            this.handleError(error);
+        }
+        return paymentsResponse;
+    }
+
     /**
      * Validation
      */
diff --git a/packages/lib/src/components/UIElement.tsx b/packages/lib/src/components/UIElement.tsx
index 75991529e0..6271ceabbd 100644
--- a/packages/lib/src/components/UIElement.tsx
+++ b/packages/lib/src/components/UIElement.tsx
@@ -196,7 +196,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         return;
     }
 
-    private async makeSessionPaymentsCall(data): Promise<void> {
+    protected async makeSessionPaymentsCall(data): Promise<void> {
         let paymentsResponse: CheckoutSessionPaymentResponse = null;
 
         try {
@@ -316,7 +316,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         if (this.props.onPaymentCompleted) {
             this.props.onPaymentCompleted(result, this.elementRef);
         }
-        // return result;
+        return result;
     };
 
     /**
@@ -324,7 +324,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
      * The component will handle automatically actions, orders, and final results.
      * @param rawResponse -
      */
-    protected handleSessionsResponse(rawResponse: RawPaymentResponse): void {
+    protected handleSessionsResponse(rawResponse: RawPaymentResponse) {
         const response = getSanitizedResponse(rawResponse);
 
         if (response.action) {
@@ -334,7 +334,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
             // we do this way so the logic on handlingOrder is associated with payment method
             this.handleOrder(response);
         } else {
-            this.handleFinalResult(response);
+            return this.handleFinalResult(response);
             // this.elementRef.handleFinalResult(response);
         }
     }

From 66354318e1eabf7c97b8abb006b92f17261dc734 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 30 Nov 2023 17:38:04 +0100
Subject: [PATCH 05/55] feat: googlepay adjustments

---
 .../src/components/GooglePay/GooglePay.tsx    | 108 ++---------
 packages/lib/src/components/UIElement.tsx     | 180 ++++++++----------
 packages/lib/src/core/types.ts                |  16 +-
 .../playground/src/pages/Dropin/manual.js     |  77 ++++----
 4 files changed, 161 insertions(+), 220 deletions(-)

diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 5c216c9224..ec5c9ad81d 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -8,9 +8,8 @@ import { getGooglePayLocale } from './utils';
 import collectBrowserInfo from '../../utils/browserInfo';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
-import { CheckoutSessionPaymentResponse } from '../../types';
 import { PaymentResponse } from '../types';
-import { resolveFinalResult } from '../utils';
+import { onSubmitReject } from '../../core/types';
 
 class GooglePay extends UIElement<GooglePayProps> {
     public static type = TxVariants.googlepay;
@@ -61,12 +60,9 @@ class GooglePay extends UIElement<GooglePayProps> {
         };
     }
 
-    public submit = () => {
-        return new Promise((resolve, reject) => this.props.onClick(resolve, reject))
+    public override submit = () => {
+        new Promise((resolve, reject) => this.props.onClick(resolve, reject))
             .then(() => this.googlePay.initiatePayment(this.props))
-            .then(() => {
-                console.log('HERE');
-            })
             .catch((error: google.payments.api.PaymentsError) => {
                 if (error.statusCode === 'CANCELED') {
                     this.handleError(new AdyenCheckoutError('CANCEL', error.toString(), { cause: error }));
@@ -89,95 +85,29 @@ class GooglePay extends UIElement<GooglePayProps> {
         });
 
         return new Promise<google.payments.api.PaymentAuthorizationResult>(resolve => {
-            super
-                .submit()
-                // TODO: add action.resolve type
-                .then((result: any) => {
-                    // close for 3ds flow
-                    if (result.action) {
-                        resolve({ transactionState: 'SUCCESS' });
-                        return result;
-                    }
-                })
-                .then(async result => {
-                    return this.handleOnPaymentAuthorizedResponse(result);
+            this.makePaymentsCall()
+                .then((paymentResponse: PaymentResponse) => {
+                    resolve({ transactionState: 'SUCCESS' });
+                    return paymentResponse;
                 })
-                .then(status => {
-                    if (status && status === 'success') {
-                        if (status == 'success') {
-                            resolve({ transactionState: 'SUCCESS' });
-                        } else if (status == 'error') {
-                            resolve({
-                                transactionState: 'ERROR',
-                                error: {
-                                    intent: 'PAYMENT_AUTHORIZATION',
-                                    message: error?.googlePayError?.message || 'Something went wrong',
-                                    reason: error?.googlePayError?.reason || 'OTHER_ERROR'
-                                }
-                            });
-                        }
-                    }
+                .then(async paymentResponse => {
+                    this.handleResponse(paymentResponse);
                 })
-                .catch(error => {
+                .catch((error: onSubmitReject) => {
                     this.setElementStatus('ready');
-                    console.log(error);
+
+                    resolve({
+                        transactionState: 'ERROR',
+                        error: {
+                            intent: error?.error?.googlePayError?.intent || 'PAYMENT_AUTHORIZATION',
+                            message: error?.error?.googlePayError?.message || 'Something went wrong',
+                            reason: error?.error?.googlePayError?.reason || 'OTHER_ERROR'
+                        }
+                    });
                 });
         });
     };
 
-    // TODO types
-    private handleOnPaymentAuthorizedResponse = async result => {
-        // TODO check is best away to check for sessions/
-        if (this.props.onSubmit) {
-            if (result.action) {
-                this.elementRef.handleAction(result.action, result.actionProps);
-                return;
-            }
-            return this.handleFinalResult(result);
-        } else {
-            return this.handleSessionsResponse(result);
-        }
-    };
-
-    protected handleFinalResult = (result: PaymentResponse) => {
-        const [status, statusProps] = resolveFinalResult(result);
-
-        if (this.props.setStatusAutomatically && status) {
-            this.setElementStatus(status, statusProps);
-        }
-
-        if (this.props.onPaymentCompleted) {
-            this.props.onPaymentCompleted(result, this.elementRef);
-        }
-
-        return result;
-    };
-
-    private override async submitUsingAdvancedFlow(): Promise<any> {
-        return new Promise((resolve, reject) => {
-            this.props.onSubmit(
-                {
-                    data: this.data,
-                    isValid: this.isValid,
-                    ...(this.state.authorizedData && { authorizedData: this.state.authorizedData })
-                },
-                this.elementRef,
-                { resolve, reject }
-            );
-        });
-    }
-
-    protected async makeSessionPaymentsCall(data): Promise<void> {
-        let paymentsResponse: CheckoutSessionPaymentResponse = null;
-        try {
-            paymentsResponse = await this.core.session.submitPayment(data);
-        } catch (error) {
-            // TODO resolve with error
-            this.handleError(error);
-        }
-        return paymentsResponse;
-    }
-
     /**
      * Validation
      */
diff --git a/packages/lib/src/components/UIElement.tsx b/packages/lib/src/components/UIElement.tsx
index 6271ceabbd..b9a0ac2314 100644
--- a/packages/lib/src/components/UIElement.tsx
+++ b/packages/lib/src/components/UIElement.tsx
@@ -1,7 +1,7 @@
 import { h } from 'preact';
 import BaseElement from './BaseElement';
-import { CheckoutSessionPaymentResponse, PaymentAction } from '../types';
-import { ComponentMethodsRef, PaymentResponse } from './types';
+import { CheckoutSessionDetailsResponse, CheckoutSessionPaymentResponse, PaymentAction } from '../types';
+import { ComponentMethodsRef, PaymentData, PaymentResponse } from './types';
 import PayButton from './internal/PayButton';
 import { IUIElement, PayButtonFunctionProps, RawPaymentResponse, UIElementProps } from './types';
 import { getSanitizedResponse, resolveFinalResult } from './utils';
@@ -53,9 +53,11 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         this.makePaymentsCall = this.makePaymentsCall.bind(this);
         this.handleAction = this.handleAction.bind(this);
         this.handleOrder = this.handleOrder.bind(this);
-        this.handleSessionsResponse = this.handleSessionsResponse.bind(this);
+        this.handleResponse = this.handleResponse.bind(this);
         this.setElementStatus = this.setElementStatus.bind(this);
 
+        this.submitUsingSessionsFlow = this.submitUsingSessionsFlow.bind(this);
+
         this.elementRef = (props && props.elementRef) || this;
         this.resources = this.props.modules ? this.props.modules.resources : undefined;
 
@@ -92,9 +94,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
 
     public setState(newState: object): void {
         this.state = { ...this.state, ...newState };
-
-        console.log('new state', this.state);
-
         this.onChange();
     }
 
@@ -107,10 +106,26 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         return state;
     }
 
+    /**
+     * Submit payment method data. If the form is not valid, it will trigger validation.
+     */
+    public submit(): void {
+        if (!this.isValid) {
+            this.showValidation();
+            return;
+        }
+
+        this.makePaymentsCall()
+            .then(this.handleResponse)
+            .catch(() => {
+                this.elementRef.setStatus('ready');
+            });
+    }
+
     /**
      * Triggers the payment flow
      */
-    private async makePaymentsCall(): Promise<void> {
+    protected makePaymentsCall(): Promise<PaymentResponse | CheckoutSessionPaymentResponse> {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('loading');
         }
@@ -120,93 +135,64 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         }
 
         if (this.core.session) {
-            return this.submitUsingSessionsFlow();
+            const beforeSubmitEvent: Promise<PaymentData> = this.props.beforeSubmit
+                ? new Promise((resolve, reject) =>
+                      this.props.beforeSubmit(this.data, this.elementRef, {
+                          resolve,
+                          reject
+                      })
+                  )
+                : Promise.resolve(this.data);
+
+            return beforeSubmitEvent.then(this.submitUsingSessionsFlow);
         }
 
         this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Could not submit the payment'));
     }
 
-    private async submitUsingAdvancedFlow() {
-        return (
-            new Promise((resolve, reject) => {
-                this.props.onSubmit(
-                    {
-                        data: this.data,
-                        isValid: this.isValid,
-                        ...(this.state.authorizedData && { authorizedData: this.state.authorizedData })
-                    },
-                    this.elementRef,
-                    { resolve, reject }
-                );
-            })
-                .then((data: any) => {
-                    if (data.action) {
-                        this.elementRef.handleAction(data.action, ...data.actionProps);
-                        return;
-                    }
-                    if (data.order) {
-                        const { order, paymentMethodsResponse } = data;
-                        // @ts-ignore Just testing
-                        this.core.update({ paymentMethodsResponse, order, amount: data.order.remainingAmount });
-                        return;
-                    }
-
-                    this.handleFinalResult(data);
-                })
-                // action.reject got called OR something fail above. TODO: add proper checks
-                .catch(error => {
-                    this.throwPaymentMethodErrorIfNeeded(error);
-                })
-        );
-    }
-
-    private async submitUsingSessionsFlow() {
-        const beforeSubmitEvent = this.props.beforeSubmit
-            ? new Promise((resolve, reject) =>
-                  this.props.beforeSubmit(this.data, this.elementRef, {
-                      resolve,
-                      reject
-                  })
-              )
-            : Promise.resolve(this.data);
-
-        let data;
-
-        try {
-            data = await beforeSubmitEvent;
-        } catch {
-            // set state as ready to submit if the merchant cancels the action
-            this.elementRef.setStatus('ready');
-            return;
-        }
-
-        return this.makeSessionPaymentsCall(data);
-    }
+    private async submitUsingAdvancedFlow(): Promise<PaymentResponse> {
+        return new Promise<PaymentResponse>((resolve, reject) => {
+            this.props.onSubmit(
+                {
+                    data: this.data,
+                    isValid: this.isValid,
+                    ...(this.state.authorizedData && { authorizedData: this.state.authorizedData })
+                },
+                this.elementRef,
+                { resolve, reject }
+            );
+        });
 
-    /**
-     * Method used to break the /payments flow and feed the error data back to the component in case the
-     * payment fails.
-     *
-     * Example: GooglePay / ApplePay accepts data from merchant in order to display custom errors
-     *
-     * @param error - Error object that can be passed back by the merchant
-     */
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    protected throwPaymentMethodErrorIfNeeded(error?: SubmitReject): void | never {
-        return;
+        //     .then((paymentsResponse: PaymentResponse) => {
+        //     // Handle result?
+        //     if (paymentsResponse.action) {
+        //         // @ts-ignore Fix props
+        //         this.elementRef.handleAction(paymentsResponse.action, ...paymentsResponse.actionProps);
+        //         return paymentsResponse;
+        //     }
+        //     if (paymentsResponse.order) {
+        //         // @ts-ignore Just testing
+        //         const { order, paymentMethodsResponse } = paymentsResponse;
+        //         // @ts-ignore Just testing
+        //         this.core.update({ paymentMethodsResponse, order, amount: data.order.remainingAmount });
+        //         return paymentsResponse;
+        //     }
+        //
+        //     this.handleFinalResult(paymentsResponse);
+        //     return paymentsResponse;
+        // });
     }
 
-    protected async makeSessionPaymentsCall(data): Promise<void> {
+    private async submitUsingSessionsFlow(data: PaymentData): Promise<CheckoutSessionPaymentResponse> {
         let paymentsResponse: CheckoutSessionPaymentResponse = null;
 
         try {
             paymentsResponse = await this.core.session.submitPayment(data);
         } catch (error) {
             this.handleError(error);
-            this.throwPaymentMethodErrorIfNeeded();
         }
 
-        this.handleSessionsResponse(paymentsResponse);
+        return paymentsResponse;
     }
 
     private onValid() {
@@ -219,18 +205,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         if (this.props.onComplete) this.props.onComplete(state, this.elementRef);
     }
 
-    /**
-     * Submit payment method data. If the form is not valid, it will trigger validation.
-     */
-    public async submit(): Promise<void> {
-        if (!this.isValid) {
-            this.showValidation();
-            return;
-        }
-
-        return this.makePaymentsCall();
-    }
-
     public showValidation(): this {
         if (this.componentRef && this.componentRef.showValidation) this.componentRef.showValidation();
         return this;
@@ -248,8 +222,13 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         return this;
     }
 
-    private submitAdditionalDetails(data): Promise<void> {
-        return this.core.session.submitDetails(data).then(this.handleSessionsResponse).catch(this.handleError);
+    private async submitAdditionalDetails(data): Promise<void> {
+        try {
+            const response: CheckoutSessionDetailsResponse = await this.core.session.submitDetails(data);
+            this.handleResponse(response);
+        } catch (error) {
+            this.handleError(error);
+        }
     }
 
     protected handleError = (error: AdyenCheckoutError): void => {
@@ -322,21 +301,28 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     /**
      * Handles a session /payments or /payments/details response.
      * The component will handle automatically actions, orders, and final results.
+     *
      * @param rawResponse -
      */
-    protected handleSessionsResponse(rawResponse: RawPaymentResponse) {
+    protected handleResponse(rawResponse: RawPaymentResponse): void {
         const response = getSanitizedResponse(rawResponse);
 
         if (response.action) {
             this.elementRef.handleAction(response.action);
-        } else if (response.order?.remainingAmount?.value > 0) {
+            return;
+        }
+
+        /**
+         * TODO: handle order properly on advanced flow.
+         */
+        if (response.order?.remainingAmount?.value > 0) {
             // we don't want to call elementRef here, use the component handler
             // we do this way so the logic on handlingOrder is associated with payment method
             this.handleOrder(response);
-        } else {
-            return this.handleFinalResult(response);
-            // this.elementRef.handleFinalResult(response);
+            return;
         }
+
+        this.handleFinalResult(response);
     }
 
     /**
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index d576585fbc..aac829d5f5 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -32,6 +32,13 @@ export interface ICore {
 
 export type AdyenEnvironment = 'test' | 'live' | 'live-us' | 'live-au' | 'live-apse' | 'live-in' | string;
 
+export type onSubmitReject = {
+    error?: {
+        googlePayError?: Partial<google.payments.api.PaymentDataError>;
+        applePayError: {};
+    };
+};
+
 export interface CoreOptions {
     session?: any;
     /**
@@ -150,7 +157,14 @@ export interface CoreOptions {
 
     onPaymentCompleted?(data: OnPaymentCompletedData, element?: UIElement): void;
 
-    onSubmit?(state: any, element: UIElement): void;
+    onSubmit?(
+        state: any,
+        element: UIElement,
+        actions: {
+            resolve: () => void;
+            reject: (error: onSubmitReject) => void;
+        }
+    ): void;
 
     onAdditionalDetails?(state: any, element?: UIElement): void;
 
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index cb99ead530..6028d27df8 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -24,41 +24,52 @@ export async function initManual() {
             const { authorizedData } = state;
             console.log('authorizedData', authorizedData);
 
-            const result = await makePayment(state.data);
-
-            // actions.reject();
-            //
-            if (result.action) {
-                actions.resolve({
-                    action: result.action,
-                    actionProps: {
-                        challengeWindowSize: '01'
-                    }
-                });
-                return;
-            }
-
-            if (result.order && result.order?.remainingAmount?.value > 0) {
-                const order = {
-                    orderData: result.order.orderData,
-                    pspReference: result.order.pspReference
-                };
-
-                const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-
-                actions.resolve({
-                    order,
-                    paymentMethodsResponse: orderPaymentMethods
-                });
-
-                return;
+            try {
+                const result = await makePayment(state.data);
+
+                // happpy flow
+                if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
+                    action.reject({
+                        error: {
+                            googlePayError: {},
+                            applePayError: {}
+                        }
+                    });
+                } else {
+                    actions.resolve({
+                        action: result.action,
+                        order: result.order,
+                        resultCode: result.resultCode
+                    });
+                }
+            } catch (error) {
+                // Something failed in the request
+                actions.reject();
             }
 
-            if (result.resultCode === 'Authorised' || result.resultCode === 'Received') {
-                actions.resolve(result); // DO I NEED FULL RESULT?
-            } else {
-                actions.reject(result);
-            }
+            //
+            //
+            // if (result.order && result.order?.remainingAmount?.value > 0) {
+            //     const order = {
+            //         orderData: result.order.orderData,
+            //         pspReference: result.order.pspReference
+            //     };
+            //
+            //     const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
+            //
+            //     actions.resolve({
+            //         order,
+            //         paymentMethodsResponse: orderPaymentMethods
+            //     });
+            //
+            //     return;
+            // }
+            //
+            // if (result.resultCode === 'Authorised' || result.resultCode === 'Received') {
+            //     actions.resolve(result); // DO I NEED FULL RESULT?
+            // } else {
+            //     actions.reject(result);
+            // }
 
             // return {
             //     googlePayError: {

From d47f03a85f8f3fa12860791bb94d94ba9d17c1e7 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 1 Dec 2023 16:21:06 +0100
Subject: [PATCH 06/55] apple pay adjustments

---
 .../lib/src/components/ApplePay/ApplePay.tsx  | 42 ++++++++++++++-----
 .../ApplePay/components/ApplePayButton.tsx    |  2 +-
 packages/lib/src/components/ApplePay/types.ts |  2 -
 .../src/components/GooglePay/GooglePay.tsx    |  2 +-
 packages/lib/src/components/UIElement.tsx     |  5 +++
 packages/lib/src/core/types.ts                |  4 +-
 6 files changed, 40 insertions(+), 17 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index f7e4b18073..d93179b500 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -8,9 +8,11 @@ import { httpPost } from '../../core/Services/http';
 import { APPLEPAY_SESSION_ENDPOINT } from './config';
 import { preparePaymentRequest } from './payment-request';
 import { resolveSupportedVersion, mapBrands } from './utils';
-import { ApplePayElementProps, ApplePayElementData, ApplePaySessionRequest, OnAuthorizedCallback } from './types';
+import { ApplePayElementProps, ApplePayElementData, ApplePaySessionRequest } from './types';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
+import { PaymentResponse } from '../types';
+import { onSubmitReject } from '../../core/types';
 
 const latestSupportedVersion = 14;
 
@@ -48,17 +50,17 @@ class ApplePayElement extends UIElement<ApplePayElementProps> {
         return {
             paymentMethod: {
                 type: ApplePayElement.type,
-                ...this.state
+                applePayToken: this.state.applePayToken
             }
         };
     }
 
-    // @ts-ignore FIX THIS
-    public submit = async () => {
-        return this.startSession(this.props.onAuthorized);
+    public submit = (): void => {
+        this.startSession();
     };
 
-    private startSession(onPaymentAuthorized: OnAuthorizedCallback) {
+    // private startSession(onPaymentAuthorized: OnAuthorizedCallback) {
+    private startSession() {
         const { version, onValidateMerchant, onPaymentMethodSelected, onShippingMethodSelected, onShippingContactSelected } = this.props;
 
         const paymentRequest = preparePaymentRequest({
@@ -79,11 +81,29 @@ class ApplePayElement extends UIElement<ApplePayElementProps> {
             onShippingContactSelected,
             onValidateMerchant: onValidateMerchant || this.validateMerchant,
             onPaymentAuthorized: (resolve, reject, event) => {
-                if (event?.payment?.token?.paymentData) {
-                    this.setState({ applePayToken: btoa(JSON.stringify(event.payment.token.paymentData)) });
-                }
-                super.submit();
-                onPaymentAuthorized(resolve, reject, event);
+                this.setState({
+                    applePayToken: btoa(JSON.stringify(event.payment.token.paymentData))
+                });
+
+                this.makePaymentsCall()
+                    .then((paymentResponse: PaymentResponse) => {
+                        // check the order part here
+
+                        resolve();
+                        return paymentResponse;
+                    })
+                    .then(paymentResponse => {
+                        this.handleResponse(paymentResponse);
+                    })
+                    .catch((error: onSubmitReject) => {
+                        this.setElementStatus('ready');
+                        const errors = error?.error?.applePayError;
+
+                        reject({
+                            status: ApplePaySession.STATUS_FAILURE,
+                            errors: errors ? (Array.isArray(errors) ? errors : [errors]) : undefined
+                        });
+                    });
             }
         });
 
diff --git a/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx b/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx
index 02de70bba2..8f6d27be04 100644
--- a/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx
+++ b/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx
@@ -32,7 +32,7 @@ class ApplePayButton extends Component<ApplePayButtonProps> {
                     'apple-pay',
                     'apple-pay-button',
                     `apple-pay-button-${buttonColor}`,
-                    `apple-pay-button--type-add-money`
+                    `apple-pay-button--type-${buttonType}`
                 )}
                 onClick={this.props.onClick}
             />
diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts
index eb633a0115..fe58c542dd 100644
--- a/packages/lib/src/components/ApplePay/types.ts
+++ b/packages/lib/src/components/ApplePay/types.ts
@@ -143,8 +143,6 @@ export interface ApplePayElementProps extends UIElementProps {
 
     onClick?: (resolve, reject) => void;
 
-    onAuthorized?: OnAuthorizedCallback;
-
     onValidateMerchant?: (resolve, reject, validationURL: string) => void;
 
     /**
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index ec5c9ad81d..5c767597cb 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -90,7 +90,7 @@ class GooglePay extends UIElement<GooglePayProps> {
                     resolve({ transactionState: 'SUCCESS' });
                     return paymentResponse;
                 })
-                .then(async paymentResponse => {
+                .then(paymentResponse => {
                     this.handleResponse(paymentResponse);
                 })
                 .catch((error: onSubmitReject) => {
diff --git a/packages/lib/src/components/UIElement.tsx b/packages/lib/src/components/UIElement.tsx
index b9a0ac2314..db2bdbc138 100644
--- a/packages/lib/src/components/UIElement.tsx
+++ b/packages/lib/src/components/UIElement.tsx
@@ -190,6 +190,11 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
             paymentsResponse = await this.core.session.submitPayment(data);
         } catch (error) {
             this.handleError(error);
+            /**
+             * Re-throw the error, so this Promise gets rejected. This keeps the same behavior as the
+             * 'submitUsingAdvancedFlow'
+             */
+            throw error;
         }
 
         return paymentsResponse;
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index aac829d5f5..a56c8cdcf7 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -35,7 +35,7 @@ export type AdyenEnvironment = 'test' | 'live' | 'live-us' | 'live-au' | 'live-a
 export type onSubmitReject = {
     error?: {
         googlePayError?: Partial<google.payments.api.PaymentDataError>;
-        applePayError: {};
+        applePayError?: ApplePayJS.ApplePayError[] | ApplePayJS.ApplePayError;
     };
 };
 
@@ -162,7 +162,7 @@ export interface CoreOptions {
         element: UIElement,
         actions: {
             resolve: () => void;
-            reject: (error: onSubmitReject) => void;
+            reject: (error?: onSubmitReject) => void;
         }
     ): void;
 

From 76625a995873e8bfe7671179f5482ab7c68469c2 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 1 Dec 2023 17:02:58 +0100
Subject: [PATCH 07/55] feat: clean up

---
 .../src/components/CashAppPay/CashAppPay.tsx  |  2 +-
 packages/lib/src/components/Dropin/Dropin.tsx |  4 +-
 packages/lib/src/components/PayPal/Paypal.tsx |  2 +-
 .../playground/src/pages/Dropin/manual.js     | 46 -------------------
 4 files changed, 3 insertions(+), 51 deletions(-)

diff --git a/packages/lib/src/components/CashAppPay/CashAppPay.tsx b/packages/lib/src/components/CashAppPay/CashAppPay.tsx
index a27ef22ed3..86e9c6887a 100644
--- a/packages/lib/src/components/CashAppPay/CashAppPay.tsx
+++ b/packages/lib/src/components/CashAppPay/CashAppPay.tsx
@@ -93,7 +93,7 @@ export class CashAppPay extends UIElement<CashAppPayConfiguration> {
         return this.props.storedPaymentMethodId ? 'Cash App Pay' : '';
     }
 
-    public submit = async () => {
+    public submit = () => {
         const { onClick, storedPaymentMethodId } = this.props;
 
         if (storedPaymentMethodId) {
diff --git a/packages/lib/src/components/Dropin/Dropin.tsx b/packages/lib/src/components/Dropin/Dropin.tsx
index 84751a3f6e..77f5097d5a 100644
--- a/packages/lib/src/components/Dropin/Dropin.tsx
+++ b/packages/lib/src/components/Dropin/Dropin.tsx
@@ -83,7 +83,7 @@ class DropinElement extends UIElement<DropinConfiguration> {
     /**
      * Calls the onSubmit event with the state of the activePaymentMethod
      */
-    public async submit(): Promise<void> {
+    public override submit(): void {
         if (!this.activePaymentMethod) {
             throw new Error('No active payment method.');
         }
@@ -122,8 +122,6 @@ class DropinElement extends UIElement<DropinConfiguration> {
     };
 
     public handleAction(action: PaymentAction, props = {}): this | null {
-        debugger;
-
         if (!action || !action.type) {
             if (hasOwnProperty(action, 'action') && hasOwnProperty(action, 'resultCode')) {
                 throw new Error(
diff --git a/packages/lib/src/components/PayPal/Paypal.tsx b/packages/lib/src/components/PayPal/Paypal.tsx
index 9c8091df33..55517faf0f 100644
--- a/packages/lib/src/components/PayPal/Paypal.tsx
+++ b/packages/lib/src/components/PayPal/Paypal.tsx
@@ -42,7 +42,7 @@ class PaypalElement extends UIElement<PayPalConfiguration> {
         };
     }
 
-    public submit = async () => {
+    public submit = () => {
         this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', ERRORS.SUBMIT_NOT_SUPPORTED));
     };
 
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 6028d27df8..b73fae229d 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -16,7 +16,6 @@ export async function initManual() {
 
         locale: 'pt-BR',
         translationFile: getTranslationFile(shopperLocale),
-        // translationFile: nl_NL,
 
         environment: process.env.__CLIENT_ENV__,
 
@@ -64,19 +63,6 @@ export async function initManual() {
             //
             //     return;
             // }
-            //
-            // if (result.resultCode === 'Authorised' || result.resultCode === 'Received') {
-            //     actions.resolve(result); // DO I NEED FULL RESULT?
-            // } else {
-            //     actions.reject(result);
-            // }
-
-            // return {
-            //     googlePayError: {
-            //         message: 'Not sufficient funds',
-            //             reason: 'OTHER_ERROR,'
-            //     }
-            // }
 
             //
             // // Trigger Error for GooglePay
@@ -93,38 +79,6 @@ export async function initManual() {
         onPaymentCompleted(result, element) {
             console.log('onPaymentCompleted', result, element);
         },
-        //
-        //
-        // // payments call
-        // if (result === AUTHORIZED)
-        //     return action.resolve({ orderTrackng: { /// details }})}
-        // }
-        //
-        //
-        // action.reject(new ApplePayError('')));
-
-        // // handle actions
-        // if (result.action) {
-        //     // demo only - store paymentData & order
-        //     if (result.action.paymentData) localStorage.setItem('storedPaymentData', result.action.paymentData);
-        //     component.handleAction(result.action, { challengeWindowSize: '01' });
-        // } else if (result.order && result.order?.remainingAmount?.value > 0) {
-        //     // handle orders
-        //     const order = {
-        //         orderData: result.order.orderData,
-        //         pspReference: result.order.pspReference
-        //     };
-        //
-        //     const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-        //     checkout.update({ paymentMethodsResponse: orderPaymentMethods, order, amount: result.order.remainingAmount });
-        // } else {
-        //     handleFinalState(result.resultCode, component);
-        // }
-        // },
-        // srConfig: { showPanel: true },
-        // onChange: state => {
-        //     console.log('onChange', state);
-        // },
         onAdditionalDetails: async (state, component) => {
             const result = await makeDetailsCall(state.data);
 

From 57561e947c4da1a0568a4680c2949ccf8ae703ca Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Mon, 4 Dec 2023 12:36:33 +0100
Subject: [PATCH 08/55] sanitizing response and checking resultCode

---
 .../lib/src/components/ApplePay/ApplePay.tsx  |   2 +
 .../src/components/GooglePay/GooglePay.tsx    |   2 +
 .../internal/UIElement/UIElement.tsx          |  62 +++----
 .../components/internal/UIElement/types.ts    | 158 +++++++++---------
 packages/lib/src/core/types.ts                |   5 +-
 packages/lib/src/types/global-types.ts        |   6 +
 .../playground/src/pages/Dropin/manual.js     |   2 +-
 7 files changed, 117 insertions(+), 120 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index 991fb9836b..71a02557e5 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -86,6 +86,8 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                 });
 
                 this.makePaymentsCall()
+                    .then(this.sanitizeResponse)
+                    .then(this.verifyPaymentDidNotFail)
                     .then((paymentResponse: PaymentResponseData) => {
                         // check the order part here
                         resolve();
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index a5d41cf8c0..b520ee850e 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -90,6 +90,8 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
 
         return new Promise<google.payments.api.PaymentAuthorizationResult>(resolve => {
             this.makePaymentsCall()
+                .then(this.sanitizeResponse)
+                .then(this.verifyPaymentDidNotFail)
                 .then((paymentResponse: PaymentResponseData) => {
                     resolve({ transactionState: 'SUCCESS' });
                     return paymentResponse;
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 1841aa95b8..a19c0736bd 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -8,21 +8,10 @@ import { CoreConfiguration, ICore } from '../../../core/types';
 import { Resources } from '../../../core/Context/Resources';
 import { NewableComponent } from '../../../core/core.registry';
 import { ComponentMethodsRef, IUIElement, PayButtonFunctionProps, UIElementProps, UIElementStatus } from './types';
-import { PaymentAction, PaymentResponseData, PaymentData, RawPaymentResponse } from '../../../types/global-types';
+import { PaymentAction, PaymentResponseData, PaymentData, RawPaymentResponse, PaymentResponseAdvancedFlow } from '../../../types/global-types';
 import './UIElement.scss';
 import { CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
 
-export type SubmitReject = {
-    googlePayError?: {
-        message?: string;
-        reason?: google.payments.api.ErrorReason;
-    };
-    applePayError?: {
-        // TOOD
-        [key: string]: any;
-    };
-};
-
 export abstract class UIElement<P extends UIElementProps = UIElementProps> extends BaseElement<P> implements IUIElement {
     protected componentRef: any;
 
@@ -115,6 +104,8 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         }
 
         this.makePaymentsCall()
+            .then(this.sanitizeResponse)
+            .then(this.verifyPaymentDidNotFail)
             .then(this.handleResponse)
             .catch(() => {
                 this.elementRef.setStatus('ready');
@@ -124,7 +115,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     /**
      * Triggers the payment flow
      */
-    protected makePaymentsCall(): Promise<PaymentResponseData | CheckoutSessionPaymentResponse> {
+    protected makePaymentsCall(): Promise<PaymentResponseAdvancedFlow | CheckoutSessionPaymentResponse> {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('loading');
         }
@@ -149,8 +140,8 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Could not submit the payment'));
     }
 
-    private async submitUsingAdvancedFlow(): Promise<PaymentResponseData> {
-        return new Promise<PaymentResponseData>((resolve, reject) => {
+    private async submitUsingAdvancedFlow(): Promise<PaymentResponseAdvancedFlow> {
+        return new Promise<PaymentResponseAdvancedFlow>((resolve, reject) => {
             this.props.onSubmit(
                 {
                     data: this.data,
@@ -161,25 +152,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
                 { resolve, reject }
             );
         });
-
-        //     .then((paymentsResponse: PaymentResponse) => {
-        //     // Handle result?
-        //     if (paymentsResponse.action) {
-        //         // @ts-ignore Fix props
-        //         this.elementRef.handleAction(paymentsResponse.action, ...paymentsResponse.actionProps);
-        //         return paymentsResponse;
-        //     }
-        //     if (paymentsResponse.order) {
-        //         // @ts-ignore Just testing
-        //         const { order, paymentMethodsResponse } = paymentsResponse;
-        //         // @ts-ignore Just testing
-        //         this.core.update({ paymentMethodsResponse, order, amount: data.order.remainingAmount });
-        //         return paymentsResponse;
-        //     }
-        //
-        //     this.handleFinalResult(paymentsResponse);
-        //     return paymentsResponse;
-        // });
     }
 
     private async submitUsingSessionsFlow(data: PaymentData): Promise<CheckoutSessionPaymentResponse> {
@@ -189,16 +161,28 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
             paymentsResponse = await this.core.session.submitPayment(data);
         } catch (error) {
             this.handleError(error);
-            /**
-             * Re-throw the error, so this Promise gets rejected. This keeps the same behavior as the
-             * 'submitUsingAdvancedFlow'
-             */
-            throw error;
+            return Promise.reject(error);
         }
 
         return paymentsResponse;
     }
 
+    protected sanitizeResponse(rawResponse: RawPaymentResponse): PaymentResponseData {
+        return getSanitizedResponse(rawResponse);
+    }
+
+    protected verifyPaymentDidNotFail(response: PaymentResponseData): Promise<PaymentResponseData> {
+        // Testing response with Refused
+        response.resultCode = 'Refused';
+
+        const [status] = resolveFinalResult(response);
+
+        if (status !== 'error') {
+            return Promise.resolve(response);
+        }
+        return Promise.reject();
+    }
+
     private onValid() {
         const state = { data: this.data };
         if (this.props.onValid) this.props.onValid(state, this.elementRef);
diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts
index 1d326ab11e..e58beda21d 100644
--- a/packages/lib/src/components/internal/UIElement/types.ts
+++ b/packages/lib/src/components/internal/UIElement/types.ts
@@ -9,84 +9,86 @@ import { CoreConfiguration, ICore } from '../../../core/types';
 
 export type PayButtonFunctionProps = Omit<PayButtonProps, 'amount'>;
 
-export interface UIElementProps extends BaseElementProps {
-    environment?: string;
-    session?: Session;
-    onChange?: (state: any, element: UIElement) => void;
-    onValid?: (state: any, element: UIElement) => void;
-    beforeSubmit?: (state: any, element: UIElement, actions: any) => Promise<void>;
-    // TODO: fix actions type
-    onSubmit?: (state: any, element: UIElement, actions: any) => void;
-    onComplete?: (state, element: UIElement) => void;
-    onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
-    onAdditionalDetails?: (state: any, element: UIElement) => void;
-    onError?: (error, element?: UIElement) => void;
-    onPaymentCompleted?: (result: any, element: UIElement) => void;
-    beforeRedirect?: (resolve, reject, redirectData, element: UIElement) => void;
-
-    isInstantPayment?: boolean;
-
-    /**
-     * Flags if the element is Stored payment method
-     * @internal
-     */
-    isStoredPaymentMethod?: boolean;
-
-    /**
-     * Flag if the element is Stored payment method.
-     * Perhaps can be deprecated and we use the one above?
-     * @internal
-     */
-    oneClick?: boolean;
-
-    /**
-     * Stored payment method id
-     * @internal
-     */
-    storedPaymentMethodId?: string;
-
-    /**
-     * Status set when creating the Component from action
-     * @internal
-     */
-    statusType?: 'redirect' | 'loading' | 'custom';
-
-    type?: string;
-    name?: string;
-    icon?: string;
-    amount?: PaymentAmount;
-    secondaryAmount?: PaymentAmountExtended;
-
-    /**
-     * Show/Hide pay button
-     * @defaultValue true
-     */
-    showPayButton?: boolean;
-
-    /**
-     *  Set to false to not set the Component status to 'loading' when onSubmit is triggered.
-     *  @defaultValue true
-     */
-    setStatusAutomatically?: boolean;
-
-    /** @internal */
-    payButton?: (options: PayButtonFunctionProps) => h.JSX.Element;
-
-    /** @internal */
-    loadingContext?: string;
-
-    /** @internal */
-    createFromAction?: (action: PaymentAction, props: object) => UIElement;
-
-    /** @internal */
-    clientKey?: string;
-
-    /** @internal */
-    elementRef?: any;
-
-    /** @internal */
-    i18n?: Language;
-}
+type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit'>;
+
+export type UIElementProps = BaseElementProps &
+    CoreCallbacks & {
+        environment?: string;
+        session?: Session;
+        onChange?: (state: any, element: UIElement) => void;
+        onValid?: (state: any, element: UIElement) => void;
+        beforeSubmit?: (state: any, element: UIElement, actions: any) => Promise<void>;
+
+        onComplete?: (state, element: UIElement) => void;
+        onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
+        onAdditionalDetails?: (state: any, element: UIElement) => void;
+        onError?: (error, element?: UIElement) => void;
+        onPaymentCompleted?: (result: any, element: UIElement) => void;
+        beforeRedirect?: (resolve, reject, redirectData, element: UIElement) => void;
+
+        isInstantPayment?: boolean;
+
+        /**
+         * Flags if the element is Stored payment method
+         * @internal
+         */
+        isStoredPaymentMethod?: boolean;
+
+        /**
+         * Flag if the element is Stored payment method.
+         * Perhaps can be deprecated and we use the one above?
+         * @internal
+         */
+        oneClick?: boolean;
+
+        /**
+         * Stored payment method id
+         * @internal
+         */
+        storedPaymentMethodId?: string;
+
+        /**
+         * Status set when creating the Component from action
+         * @internal
+         */
+        statusType?: 'redirect' | 'loading' | 'custom';
+
+        type?: string;
+        name?: string;
+        icon?: string;
+        amount?: PaymentAmount;
+        secondaryAmount?: PaymentAmountExtended;
+
+        /**
+         * Show/Hide pay button
+         * @defaultValue true
+         */
+        showPayButton?: boolean;
+
+        /**
+         *  Set to false to not set the Component status to 'loading' when onSubmit is triggered.
+         *  @defaultValue true
+         */
+        setStatusAutomatically?: boolean;
+
+        /** @internal */
+        payButton?: (options: PayButtonFunctionProps) => h.JSX.Element;
+
+        /** @internal */
+        loadingContext?: string;
+
+        /** @internal */
+        createFromAction?: (action: PaymentAction, props: object) => UIElement;
+
+        /** @internal */
+        clientKey?: string;
+
+        /** @internal */
+        elementRef?: any;
+
+        /** @internal */
+        i18n?: Language;
+    };
 
 export interface IUIElement extends IBaseElement {
     isValid: boolean;
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 6a1c1fb15c..76625d9af8 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -6,7 +6,8 @@ import {
     PaymentMethodsResponse,
     ActionHandledReturnObject,
     OnPaymentCompletedData,
-    PaymentData
+    PaymentData,
+    PaymentResponseAdvancedFlow
 } from '../types/global-types';
 import { AnalyticsOptions } from './Analytics/types';
 import { RiskModuleOptions } from './RiskModule/RiskModule';
@@ -168,7 +169,7 @@ export interface CoreConfiguration {
         state: any,
         element: UIElement,
         actions: {
-            resolve: () => void;
+            resolve: (response: PaymentResponseAdvancedFlow) => void;
             reject: (error?: onSubmitReject) => void;
         }
     ): void;
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index 1e9aaa5077..9c9beb86b8 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -335,6 +335,12 @@ export interface OnPaymentCompletedData {
     resultCode: ResultCode;
 }
 
+export interface PaymentResponseAdvancedFlow {
+    resultCode: ResultCode;
+    action?: PaymentAction;
+    order?: Order;
+}
+
 export interface PaymentResponseData {
     type?: string;
     action?: PaymentAction;
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index b73fae229d..070608b2c5 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -28,7 +28,7 @@ export async function initManual() {
 
                 // happpy flow
                 if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
-                    action.reject({
+                    actions.reject({
                         error: {
                             googlePayError: {},
                             applePayError: {}

From fde4db9792cc850c5a33f8055103f3c27fac42c6 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Mon, 4 Dec 2023 14:27:54 +0100
Subject: [PATCH 09/55] fixed bug checking resultCode

---
 .../internal/UIElement/UIElement.tsx           | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index a19c0736bd..978d43c213 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -108,7 +108,11 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
             .then(this.verifyPaymentDidNotFail)
             .then(this.handleResponse)
             .catch(() => {
-                this.elementRef.setStatus('ready');
+                // two scenarios when code reaches here:
+                // - adv flow: merchant used reject passing error back or empty object
+                // - adv flow: merchant resolved passed resultCode: Refused,Cancelled,etc
+                // - on sessions, payment failed with resultCode Refused, Cancelled, etc
+                this.setElementStatus('ready');
             });
     }
 
@@ -172,15 +176,11 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     }
 
     protected verifyPaymentDidNotFail(response: PaymentResponseData): Promise<PaymentResponseData> {
-        // Testing response with Refused
-        response.resultCode = 'Refused';
-
-        const [status] = resolveFinalResult(response);
-
-        if (status !== 'error') {
-            return Promise.resolve(response);
+        if (['Cancelled', 'Error', 'Refused'].includes(response.resultCode)) {
+            return Promise.reject();
         }
-        return Promise.reject();
+
+        return Promise.resolve(response);
     }
 
     private onValid() {

From b5c8862cc40db769498c4087cf4a91996d31f273 Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Tue, 5 Dec 2023 13:33:55 +0100
Subject: [PATCH 10/55] add: onPaymentFailed callback

---
 .../internal/UIElement/UIElement.tsx          | 29 ++++++++++------
 .../components/internal/UIElement/types.ts    |  3 +-
 packages/lib/src/core/config.ts               |  1 +
 packages/lib/src/core/types.ts                |  5 ++-
 packages/lib/src/types/global-types.ts        |  7 ++++
 packages/playground/src/handlers.js           | 33 ++++++++++++++-----
 packages/playground/src/pages/Cards/Cards.js  |  6 ++++
 .../playground/src/pages/Dropin/manual.js     |  4 +++
 .../playground/src/pages/Dropin/session.js    |  3 ++
 9 files changed, 70 insertions(+), 21 deletions(-)

diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 978d43c213..2854351ab8 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -1,14 +1,21 @@
 import { h } from 'preact';
 import BaseElement from '../BaseElement/BaseElement';
 import PayButton from '../PayButton';
-import { getSanitizedResponse, resolveFinalResult } from './utils';
+import { getSanitizedResponse } from './utils';
 import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError';
 import { hasOwnProperty } from '../../../utils/hasOwnProperty';
 import { CoreConfiguration, ICore } from '../../../core/types';
 import { Resources } from '../../../core/Context/Resources';
 import { NewableComponent } from '../../../core/core.registry';
 import { ComponentMethodsRef, IUIElement, PayButtonFunctionProps, UIElementProps, UIElementStatus } from './types';
-import { PaymentAction, PaymentResponseData, PaymentData, RawPaymentResponse, PaymentResponseAdvancedFlow } from '../../../types/global-types';
+import {
+    PaymentAction,
+    PaymentResponseData,
+    PaymentData,
+    RawPaymentResponse,
+    PaymentResponseAdvancedFlow,
+    OnPaymentFailedData
+} from '../../../types/global-types';
 import './UIElement.scss';
 import { CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
 
@@ -107,12 +114,15 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
             .then(this.sanitizeResponse)
             .then(this.verifyPaymentDidNotFail)
             .then(this.handleResponse)
-            .catch(() => {
+            .catch((exception: OnPaymentFailedData) => {
                 // two scenarios when code reaches here:
                 // - adv flow: merchant used reject passing error back or empty object
                 // - adv flow: merchant resolved passed resultCode: Refused,Cancelled,etc
-                // - on sessions, payment failed with resultCode Refused, Cancelled, etc
-                this.setElementStatus('ready');
+                // - on sessions, payment failed with resultCode Refused, Cancelled, etcthis.
+                this.props.onPaymentFailed?.(exception, this.elementRef);
+                if (this.props.setStatusAutomatically) {
+                    this.setElementStatus('error');
+                }
             });
     }
 
@@ -177,7 +187,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
 
     protected verifyPaymentDidNotFail(response: PaymentResponseData): Promise<PaymentResponseData> {
         if (['Cancelled', 'Error', 'Refused'].includes(response.resultCode)) {
-            return Promise.reject();
+            return Promise.reject(response);
         }
 
         return Promise.resolve(response);
@@ -281,10 +291,9 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         if (this.props.onPaymentCompleted) this.props.onPaymentCompleted(response, this.elementRef);
     };
 
-    protected handleFinalResult = (result: PaymentResponseData) => {
+    protected handleSuccessResult = (result: PaymentResponseData) => {
         if (this.props.setStatusAutomatically) {
-            const [status, statusProps] = resolveFinalResult(result);
-            if (status) this.setElementStatus(status, statusProps);
+            this.setElementStatus('success');
         }
 
         if (this.props.onPaymentCompleted) {
@@ -317,7 +326,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
             return;
         }
 
-        this.handleFinalResult(response);
+        this.handleSuccessResult(response);
     }
 
     /**
diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts
index e58beda21d..c10faa9689 100644
--- a/packages/lib/src/components/internal/UIElement/types.ts
+++ b/packages/lib/src/components/internal/UIElement/types.ts
@@ -9,7 +9,8 @@ import { CoreConfiguration, ICore } from '../../../core/types';
 
 export type PayButtonFunctionProps = Omit<PayButtonProps, 'amount'>;
 
-type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit'>;
+// TODO add onPaymentCompleted
+type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit' | 'onPaymentFailed'>;
 
 export type UIElementProps = BaseElementProps &
     CoreCallbacks & {
diff --git a/packages/lib/src/core/config.ts b/packages/lib/src/core/config.ts
index b51f719a5a..1b8aa91996 100644
--- a/packages/lib/src/core/config.ts
+++ b/packages/lib/src/core/config.ts
@@ -19,6 +19,7 @@ export const GENERIC_OPTIONS = [
 
     // Events
     'onPaymentCompleted',
+    'onPaymentFailed',
     'beforeRedirect',
     'beforeSubmit',
     'onSubmit',
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 76625d9af8..a7387eba0a 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -7,7 +7,8 @@ import {
     ActionHandledReturnObject,
     OnPaymentCompletedData,
     PaymentData,
-    PaymentResponseAdvancedFlow
+    PaymentResponseAdvancedFlow,
+    OnPaymentFailedData
 } from '../types/global-types';
 import { AnalyticsOptions } from './Analytics/types';
 import { RiskModuleOptions } from './RiskModule/RiskModule';
@@ -165,6 +166,8 @@ export interface CoreConfiguration {
 
     onPaymentCompleted?(data: OnPaymentCompletedData, element?: UIElement): void;
 
+    onPaymentFailed?(data?: OnPaymentFailedData, element?: UIElement): void;
+
     onSubmit?(
         state: any,
         element: UIElement,
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index 9c9beb86b8..bdb98e6678 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -1,5 +1,6 @@
 import { ADDRESS_SCHEMA } from '../components/internal/Address/constants';
 import actionTypes from '../core/ProcessResponse/PaymentAction/actionTypes';
+import { onSubmitReject } from '../core/types';
 
 export type PaymentActionsType = keyof typeof actionTypes;
 
@@ -335,6 +336,12 @@ export interface OnPaymentCompletedData {
     resultCode: ResultCode;
 }
 
+export type OnPaymentFailedData =
+    | OnPaymentCompletedData
+    | (onSubmitReject & {
+          resultCode: ResultCode;
+      });
+
 export interface PaymentResponseAdvancedFlow {
     resultCode: ResultCode;
     action?: PaymentAction;
diff --git a/packages/playground/src/handlers.js b/packages/playground/src/handlers.js
index 6abe14c848..71a527100b 100644
--- a/packages/playground/src/handlers.js
+++ b/packages/playground/src/handlers.js
@@ -29,17 +29,32 @@ export function handleError(obj) {
     }
 }
 
-export function handleSubmit(state, component) {
+export async function handleSubmit(state, component, actions) {
     component.setStatus('loading');
 
-    return makePayment(state.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
-        });
+    try {
+        const result = await makePayment(state.data);
+
+        // happpy flow
+        if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
+            actions.reject({
+                resultCode: result.resultCode,
+                error: {
+                    googlePayError: {},
+                    applePayError: {}
+                }
+            });
+        } else {
+            actions.resolve({
+                action: result.action,
+                order: result.order,
+                resultCode: result.resultCode
+            });
+        }
+    } catch (error) {
+        // Something failed in the request
+        actions.reject();
+    }
 }
 
 export function handleAdditionalDetails(details, component) {
diff --git a/packages/playground/src/pages/Cards/Cards.js b/packages/playground/src/pages/Cards/Cards.js
index d65c7fc89f..598378f354 100644
--- a/packages/playground/src/pages/Cards/Cards.js
+++ b/packages/playground/src/pages/Cards/Cards.js
@@ -45,6 +45,12 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
         onError: handleError,
         risk: {
             enabled: false
+        },
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
         }
     });
 
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 070608b2c5..7690fb50ce 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -29,6 +29,7 @@ export async function initManual() {
                 // happpy flow
                 if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
                     actions.reject({
+                        resultCode: result.resultCode,
                         error: {
                             googlePayError: {},
                             applePayError: {}
@@ -79,6 +80,9 @@ export async function initManual() {
         onPaymentCompleted(result, element) {
             console.log('onPaymentCompleted', result, element);
         },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        },
         onAdditionalDetails: async (state, component) => {
             const result = await makeDetailsCall(state.data);
 
diff --git a/packages/playground/src/pages/Dropin/session.js b/packages/playground/src/pages/Dropin/session.js
index 08012e6cd2..e80d853e58 100644
--- a/packages/playground/src/pages/Dropin/session.js
+++ b/packages/playground/src/pages/Dropin/session.js
@@ -30,6 +30,9 @@ export async function initSession() {
         onPaymentCompleted: (result, component) => {
             console.info(result, component);
         },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        },
         onError: (error, component) => {
             console.info(JSON.stringify(error), component);
         },

From 62f4518d447fd77ff3068cf08258c992ae4976f6 Mon Sep 17 00:00:00 2001
From: vagrant <m1aw@users.noreply.github.com>
Date: Wed, 6 Dec 2023 04:41:03 -0600
Subject: [PATCH 11/55] refactor: change handle order, adds
 onPaymentMethodsRequest

---
 packages/lib/src/components/ANCV/ANCV.tsx     | 16 --------
 .../lib/src/components/Giftcard/Giftcard.tsx  |  9 +----
 .../internal/UIElement/UIElement.tsx          | 39 +++++++++++++++++--
 .../components/internal/UIElement/types.ts    |  2 +-
 packages/lib/src/core/config.ts               |  3 +-
 packages/lib/src/core/types.ts                | 16 +++++++-
 packages/lib/src/types/global-types.ts        |  8 ++++
 7 files changed, 61 insertions(+), 32 deletions(-)

diff --git a/packages/lib/src/components/ANCV/ANCV.tsx b/packages/lib/src/components/ANCV/ANCV.tsx
index 2cae6954a3..66be096e2d 100644
--- a/packages/lib/src/components/ANCV/ANCV.tsx
+++ b/packages/lib/src/components/ANCV/ANCV.tsx
@@ -8,8 +8,6 @@ import SRPanelProvider from '../../core/Errors/SRPanelProvider';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import PayButton from '../internal/PayButton';
 import { ANCVConfiguration } from './types';
-import { PaymentResponseData } from '../../types/global-types';
-
 export class ANCVElement extends UIElement<ANCVConfiguration> {
     public static type = 'ancv';
 
@@ -36,20 +34,6 @@ export class ANCVElement extends UIElement<ANCVConfiguration> {
         }
     };
 
-    /**
-     * Called when the /paymentDetails endpoint returns PartiallyAuthorised. The /paymentDetails happens once the /status
-     * returns PartiallyAuthorised
-     *
-     * @param order
-     */
-    protected handleOrder = ({ order }: PaymentResponseData) => {
-        this.updateParent({ order });
-
-        if (this.props.session && this.props.onOrderCreated) {
-            return this.props.onOrderCreated(order);
-        }
-    };
-
     public createOrder = () => {
         if (!this.isValid) {
             this.showValidation();
diff --git a/packages/lib/src/components/Giftcard/Giftcard.tsx b/packages/lib/src/components/Giftcard/Giftcard.tsx
index 76e0abef19..527a1dd5fa 100644
--- a/packages/lib/src/components/Giftcard/Giftcard.tsx
+++ b/packages/lib/src/components/Giftcard/Giftcard.tsx
@@ -4,7 +4,7 @@ import GiftcardComponent from './components/GiftcardComponent';
 import CoreProvider from '../../core/Context/CoreProvider';
 import PayButton from '../internal/PayButton';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
-import { PaymentAmount, PaymentResponseData } from '../../types//global-types';
+import { PaymentAmount } from '../../types//global-types';
 import { GiftCardElementData, GiftCardConfiguration } from './types';
 import { TxVariants } from '../tx-variants';
 
@@ -68,13 +68,6 @@ export class GiftcardElement extends UIElement<GiftCardConfiguration> {
         }
     };
 
-    protected handleOrder = ({ order }: PaymentResponseData) => {
-        this.updateParent({ order });
-        if (this.props.session && this.props.onOrderCreated) {
-            return this.props.onOrderCreated(order);
-        }
-    };
-
     public balanceCheck() {
         return this.onBalanceCheck();
     }
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 2854351ab8..277c0362ac 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -14,7 +14,9 @@ import {
     PaymentData,
     RawPaymentResponse,
     PaymentResponseAdvancedFlow,
-    OnPaymentFailedData
+    OnPaymentFailedData,
+    PaymentMethodsResponse,
+    Order
 } from '../../../types/global-types';
 import './UIElement.scss';
 import { CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
@@ -286,9 +288,16 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     }
 
     protected handleOrder = (response: PaymentResponseData): void => {
-        this.updateParent({ order: response.order });
-        // in case we receive an order in any other component then a GiftCard trigger handleFinalResult
-        if (this.props.onPaymentCompleted) this.props.onPaymentCompleted(response, this.elementRef);
+        const { order } = response;
+
+        const updateCorePromise =
+            !this.props.session && this.props.onPaymentMethodsRequest
+                ? this.handleAdvanceFlowPaymentMethodsUpdate(order)
+                : this.core.update({ order });
+
+        updateCorePromise.then(() => {
+            this.props.onOrderCreated?.({ order });
+        });
     };
 
     protected handleSuccessResult = (result: PaymentResponseData) => {
@@ -384,6 +393,28 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     protected payButton = (props: PayButtonFunctionProps) => {
         return <PayButton {...props} amount={this.props.amount} secondaryAmount={this.props.secondaryAmount} onClick={this.submit} />;
     };
+
+    private async handleAdvanceFlowPaymentMethodsUpdate(order: Order) {
+        return new Promise<PaymentMethodsResponse>((resolve, reject) => {
+            const data = {
+                order: {
+                    orderData: order.orderData,
+                    pspReference: order.pspReference
+                },
+                amount: this.props.amount,
+                locale: this.core.options.locale
+            };
+            this.props.onPaymentMethodsRequest(resolve, reject, data);
+        })
+            .then(paymentMethodsResponse => {
+                return this.core.update({ paymentMethodsResponse, order, amount: order.remainingAmount });
+            })
+            .catch(error => {
+                this.handleError(
+                    new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Payment methods be updated after partial payment.', { cause: error })
+                );
+            });
+    }
 }
 
 export default UIElement;
diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts
index c10faa9689..d4a358f012 100644
--- a/packages/lib/src/components/internal/UIElement/types.ts
+++ b/packages/lib/src/components/internal/UIElement/types.ts
@@ -10,7 +10,7 @@ import { CoreConfiguration, ICore } from '../../../core/types';
 export type PayButtonFunctionProps = Omit<PayButtonProps, 'amount'>;
 
 // TODO add onPaymentCompleted
-type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit' | 'onPaymentFailed'>;
+type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit' | 'onPaymentFailed' | 'onOrderCreated' | 'onPaymentMethodsRequest'>;
 
 export type UIElementProps = BaseElementProps &
     CoreCallbacks & {
diff --git a/packages/lib/src/core/config.ts b/packages/lib/src/core/config.ts
index 1b8aa91996..64690d1631 100644
--- a/packages/lib/src/core/config.ts
+++ b/packages/lib/src/core/config.ts
@@ -31,7 +31,8 @@ export const GENERIC_OPTIONS = [
     'onBalanceCheck',
     'onOrderRequest',
     'onOrderCreated',
-    'setStatusAutomatically'
+    'setStatusAutomatically',
+    'onPaymentMethodsRequest'
 ];
 
 export default {
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index a7387eba0a..16d8c50258 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -8,7 +8,8 @@ import {
     OnPaymentCompletedData,
     PaymentData,
     PaymentResponseAdvancedFlow,
-    OnPaymentFailedData
+    OnPaymentFailedData,
+    PaymentMethodsRequestData
 } from '../types/global-types';
 import { AnalyticsOptions } from './Analytics/types';
 import { RiskModuleOptions } from './RiskModule/RiskModule';
@@ -26,14 +27,23 @@ type PromiseReject = typeof Promise.reject;
 
 export interface ICore {
     initialize(): Promise<ICore>;
+
     register(...items: NewableComponent[]): void;
-    update({ order }: { order?: Order }): Promise<ICore>;
+
+    update(options: CoreConfiguration): Promise<ICore>;
+
     remove(component): ICore;
+
     submitDetails(details: any): void;
+
     getCorePropsForComponent(): any;
+
     getComponent(txVariant: string): NewableComponent | undefined;
+
     createFromAction(action: PaymentAction, options: any): any;
+
     storeElementReference(element: UIElement): void;
+
     options: CoreConfiguration;
     paymentMethodsResponse: PaymentMethods;
     session?: Session;
@@ -189,6 +199,8 @@ export interface CoreConfiguration {
 
     onOrderRequest?(resolve: PromiseResolve, reject: PromiseReject, data: PaymentData): Promise<void>;
 
+    onPaymentMethodsRequest?(resolve: (response: PaymentMethodsResponse) => void, reject: () => void, data: PaymentMethodsRequestData): void;
+
     onOrderCancel?(order: Order): void;
 
     /**
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index bdb98e6678..8364a7b589 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -342,6 +342,14 @@ export type OnPaymentFailedData =
           resultCode: ResultCode;
       });
 
+//TODO double check these values
+export interface PaymentMethodsRequestData {
+    order: Order;
+    amount: PaymentAmount;
+    locale: string;
+    countryCode?: string;
+}
+
 export interface PaymentResponseAdvancedFlow {
     resultCode: ResultCode;
     action?: PaymentAction;

From 4cf1e54fea1658c5d7ea0f1e314cd9daa77534b2 Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Wed, 6 Dec 2023 15:40:43 +0100
Subject: [PATCH 12/55] refactor: rename onOrderCreated to onOrderUpdated

---
 .../e2e-playwright/app/src/pages/ANCV/ANCV.js |  2 +-
 .../e2e-playwright/tests/ancv/ancv.spec.ts    |  2 +-
 .../GiftCardsSessions/GiftCardsSessions.js    |  4 ++--
 .../onOrderUpdated.clientScripts.js}          |  0
 .../onOrderUpdated.mocks.js}                  |  0
 .../onOrderUpdated.test.js}                   |  0
 .../onRequiringConfirmation.mocks.js          |  2 +-
 packages/lib/src/components/ANCV/types.ts     |  2 +-
 packages/lib/src/components/Giftcard/types.ts |  2 +-
 .../internal/UIElement/UIElement.tsx          |  5 ++---
 .../components/internal/UIElement/types.ts    |  2 +-
 packages/lib/src/core/config.ts               |  2 +-
 packages/lib/src/core/types.ts                | 11 +++++++----
 packages/lib/src/types/global-types.ts        |  5 ++---
 .../playground/src/pages/Dropin/manual.js     | 19 ++++++++++++++++---
 .../src/pages/GiftCards/GiftCards.js          |  4 ++--
 packages/playground/src/services.js           |  6 +++++-
 17 files changed, 43 insertions(+), 25 deletions(-)
 rename packages/e2e/tests/giftcards/{onOrderCreated/onOrderCreated.clientScripts.js => onOrderUpdated/onOrderUpdated.clientScripts.js} (100%)
 rename packages/e2e/tests/giftcards/{onOrderCreated/onOrderCreated.mocks.js => onOrderUpdated/onOrderUpdated.mocks.js} (100%)
 rename packages/e2e/tests/giftcards/{onOrderCreated/onOrderCreated.test.js => onOrderUpdated/onOrderUpdated.test.js} (100%)

diff --git a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js b/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js
index a43bcff1d5..5c0c7df19e 100644
--- a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js
+++ b/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js
@@ -27,7 +27,7 @@ const initCheckout = async () => {
         locale: shopperLocale,
         countryCode,
         showPayButton: true,
-        onOrderCreated: data => {
+        onOrderUpdated: data => {
             showAuthorised('Partially Authorised');
         },
         onError: handleError
diff --git a/packages/e2e-playwright/tests/ancv/ancv.spec.ts b/packages/e2e-playwright/tests/ancv/ancv.spec.ts
index bdc56da0d8..a806f52949 100644
--- a/packages/e2e-playwright/tests/ancv/ancv.spec.ts
+++ b/packages/e2e-playwright/tests/ancv/ancv.spec.ts
@@ -11,7 +11,7 @@ import { setupMock } from '../../mocks/setup/setup.mock';
 import { statusMock } from '../../mocks/status/status.mock';
 
 test.describe('ANCV - Sessions', () => {
-    test('should call onOrderCreated when payment is partially authorised (Sessions flow)', async ({ ancvPage }) => {
+    test('should call onOrderUpdated when payment is partially authorised (Sessions flow)', async ({ ancvPage }) => {
         const { ancv, page } = ancvPage;
 
         await createOrderMock(page, orderCreatedMockData);
diff --git a/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.js b/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.js
index f6c12f812f..796980bfa1 100644
--- a/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.js
+++ b/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.js
@@ -37,8 +37,8 @@ const initCheckout = async () => {
         core: window.sessionCheckout,
         type: 'giftcard',
         brand: 'valuelink',
-        onOrderCreated: data => {
-            window.onOrderCreatedTestData = data;
+        onOrderUpdated: data => {
+            window.onOrderUpdatedTestData = data;
         },
         onRequiringConfirmation: () => {
             window.onRequiringConfirmationTestData = true;
diff --git a/packages/e2e/tests/giftcards/onOrderCreated/onOrderCreated.clientScripts.js b/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.clientScripts.js
similarity index 100%
rename from packages/e2e/tests/giftcards/onOrderCreated/onOrderCreated.clientScripts.js
rename to packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.clientScripts.js
diff --git a/packages/e2e/tests/giftcards/onOrderCreated/onOrderCreated.mocks.js b/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.mocks.js
similarity index 100%
rename from packages/e2e/tests/giftcards/onOrderCreated/onOrderCreated.mocks.js
rename to packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.mocks.js
diff --git a/packages/e2e/tests/giftcards/onOrderCreated/onOrderCreated.test.js b/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
similarity index 100%
rename from packages/e2e/tests/giftcards/onOrderCreated/onOrderCreated.test.js
rename to packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
diff --git a/packages/e2e/tests/giftcards/onRequiringConfirmation/onRequiringConfirmation.mocks.js b/packages/e2e/tests/giftcards/onRequiringConfirmation/onRequiringConfirmation.mocks.js
index 2cdf353281..28daf29ae5 100644
--- a/packages/e2e/tests/giftcards/onRequiringConfirmation/onRequiringConfirmation.mocks.js
+++ b/packages/e2e/tests/giftcards/onRequiringConfirmation/onRequiringConfirmation.mocks.js
@@ -1,7 +1,7 @@
 import { RequestMock, RequestLogger } from 'testcafe';
 import { BASE_URL } from '../../pages';
 
-import { mock, loggers } from '../onOrderCreated/onOrderCreated.mocks';
+import { mock, loggers } from '../onOrderUpdated/onOrderUpdated.mocks';
 
 const path = require('path');
 require('dotenv').config({ path: path.resolve('../../', '.env') });
diff --git a/packages/lib/src/components/ANCV/types.ts b/packages/lib/src/components/ANCV/types.ts
index a500dfd92d..4621bf55f0 100644
--- a/packages/lib/src/components/ANCV/types.ts
+++ b/packages/lib/src/components/ANCV/types.ts
@@ -4,7 +4,7 @@ export interface ANCVConfiguration extends UIElementProps {
     paymentData?: any;
     data: ANCVDataState;
     onOrderRequest?: any;
-    onOrderCreated?: any;
+    onOrderUpdated?: any;
 }
 
 export interface ANCVDataState {
diff --git a/packages/lib/src/components/Giftcard/types.ts b/packages/lib/src/components/Giftcard/types.ts
index 36f5c7cf35..f9976e5296 100644
--- a/packages/lib/src/components/Giftcard/types.ts
+++ b/packages/lib/src/components/Giftcard/types.ts
@@ -17,7 +17,7 @@ export interface GiftCardConfiguration extends UIElementProps {
     expiryDateRequired?: boolean;
     brandsConfiguration?: any;
     brand?: string;
-    onOrderCreated?(data): void;
+    onOrderUpdated?(data): void;
     onOrderRequest?(resolve, reject, data): void;
     onBalanceCheck?(resolve, reject, data): void;
     onRequiringConfirmation?(): void;
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 277c0362ac..d9cebea2a8 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -296,7 +296,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
                 : this.core.update({ order });
 
         updateCorePromise.then(() => {
-            this.props.onOrderCreated?.({ order });
+            this.props.onOrderUpdated?.({ order });
         });
     };
 
@@ -401,10 +401,9 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
                     orderData: order.orderData,
                     pspReference: order.pspReference
                 },
-                amount: this.props.amount,
                 locale: this.core.options.locale
             };
-            this.props.onPaymentMethodsRequest(resolve, reject, data);
+            this.props.onPaymentMethodsRequest(data, { resolve, reject });
         })
             .then(paymentMethodsResponse => {
                 return this.core.update({ paymentMethodsResponse, order, amount: order.remainingAmount });
diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts
index d4a358f012..d5db40edbb 100644
--- a/packages/lib/src/components/internal/UIElement/types.ts
+++ b/packages/lib/src/components/internal/UIElement/types.ts
@@ -10,7 +10,7 @@ import { CoreConfiguration, ICore } from '../../../core/types';
 export type PayButtonFunctionProps = Omit<PayButtonProps, 'amount'>;
 
 // TODO add onPaymentCompleted
-type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit' | 'onPaymentFailed' | 'onOrderCreated' | 'onPaymentMethodsRequest'>;
+type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit' | 'onPaymentFailed' | 'onOrderUpdated' | 'onPaymentMethodsRequest'>;
 
 export type UIElementProps = BaseElementProps &
     CoreCallbacks & {
diff --git a/packages/lib/src/core/config.ts b/packages/lib/src/core/config.ts
index 64690d1631..4b2daf6564 100644
--- a/packages/lib/src/core/config.ts
+++ b/packages/lib/src/core/config.ts
@@ -30,7 +30,7 @@ export const GENERIC_OPTIONS = [
     'onError',
     'onBalanceCheck',
     'onOrderRequest',
-    'onOrderCreated',
+    'onOrderUpdated',
     'setStatusAutomatically',
     'onPaymentMethodsRequest'
 ];
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 16d8c50258..b4d7b098d9 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -199,16 +199,19 @@ export interface CoreConfiguration {
 
     onOrderRequest?(resolve: PromiseResolve, reject: PromiseReject, data: PaymentData): Promise<void>;
 
-    onPaymentMethodsRequest?(resolve: (response: PaymentMethodsResponse) => void, reject: () => void, data: PaymentMethodsRequestData): void;
+    onPaymentMethodsRequest?(
+        data: PaymentMethodsRequestData,
+        actions: { resolve: (response: PaymentMethodsResponse) => void; reject: () => void }
+    ): void;
 
     onOrderCancel?(order: Order): void;
 
     /**
-     * Only used in Components combined with Sessions flow
-     * Callback used to inform when the order is created.
+     * Called when the gift card balance is less than the transaction amount.
+     * Returns an Order object that includes the remaining amount to be paid.
      * https://docs.adyen.com/payment-methods/gift-cards/web-component?tab=config-sessions_1
      */
-    onOrderCreated?(data: { order: Order }): void;
+    onOrderUpdated?(data: { order: Order }): void;
 
     /**
      * Used only in the Donation Component when shopper declines to donate
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index 8364a7b589..a76289dbb7 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -344,9 +344,8 @@ export type OnPaymentFailedData =
 
 //TODO double check these values
 export interface PaymentMethodsRequestData {
-    order: Order;
-    amount: PaymentAmount;
-    locale: string;
+    order?: Order;
+    locale?: string;
     countryCode?: string;
 }
 
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 7690fb50ce..e4744ccc7e 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -1,7 +1,7 @@
 import { AdyenCheckout, Dropin, Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
 import { getPaymentMethods, makePayment, checkBalance, createOrder, cancelOrder, makeDetailsCall } from '../../services';
-import { amount, shopperLocale, countryCode, returnUrl } from '../../config/commonConfig';
+import { amount, shopperLocale, countryCode } from '../../config/commonConfig';
 import { getSearchParameters } from '../../utils';
 import getTranslationFile from '../../config/getTranslation';
 
@@ -102,20 +102,33 @@ export async function initManual() {
             }
         },
         onBalanceCheck: async (resolve, reject, data) => {
+            console.log('onBalanceCheck', data);
             resolve(await checkBalance(data));
         },
-        onOrderRequest: async (resolve, reject) => {
+        onOrderRequest: async resolve => {
+            console.log('onOrderRequested');
             resolve(await createOrder({ amount }));
         },
+        onOrderUpdated: data => {
+            console.log('onOrderUpdated', data);
+        },
         onOrderCancel: async order => {
             await cancelOrder(order);
-            checkout.update({ paymentMethodsResponse: await getPaymentMethods({ amount, shopperLocale }), order: null, amount });
+            checkout.update({
+                paymentMethodsResponse: await getPaymentMethods({ amount, shopperLocale }),
+                order: null,
+                amount
+            });
         },
         onError: (error, component) => {
             console.info(error.name, error.message, error.stack, component);
         },
         onActionHandled: rtnObj => {
             console.log('onActionHandled', rtnObj);
+        },
+        onPaymentMethodsRequest: async (data, { resolve, reject }) => {
+            console.log('onPaymentMethodsRequest', data);
+            resolve(await getPaymentMethods({ amount, shopperLocale: data.locale, order: data.order }));
         }
     });
 
diff --git a/packages/playground/src/pages/GiftCards/GiftCards.js b/packages/playground/src/pages/GiftCards/GiftCards.js
index 2ecd10d1b7..4ea568be34 100644
--- a/packages/playground/src/pages/GiftCards/GiftCards.js
+++ b/packages/playground/src/pages/GiftCards/GiftCards.js
@@ -96,8 +96,8 @@ import getTranslationFile from '../../config/getTranslation';
         core: sessionCheckout,
         type: 'giftcard',
         brand: 'svs',
-        onOrderCreated: () => {
-            console.log('onOrderCreated');
+        onOrderUpdated: () => {
+            console.log('onOrderUpdated');
         },
         onRequiringConfirmation: () => {
             console.log('onRequiringConfirmation');
diff --git a/packages/playground/src/services.js b/packages/playground/src/services.js
index 340b8eac9a..0ba6dd2554 100644
--- a/packages/playground/src/services.js
+++ b/packages/playground/src/services.js
@@ -54,7 +54,11 @@ export const getOriginKey = (originKeyOrigin = document.location.origin) =>
     httpPost('originKeys', { originDomains: [originKeyOrigin] }).then(response => response.originKeys[originKeyOrigin]);
 
 export const checkBalance = data => {
-    return httpPost('paymentMethods/balance', data)
+    const payload = {
+        ...data,
+        amount: paymentMethodsConfig.amount
+    };
+    return httpPost('paymentMethods/balance', payload)
         .then(response => {
             if (response.error) throw 'Balance call failed';
             return response;

From 95519d72452f943348b81d733880bd2b8f8426b0 Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Wed, 6 Dec 2023 17:22:52 +0100
Subject: [PATCH 13/55] fix: handle reject onPaymentMethodsRequest

---
 .prettierrc.json                              |  2 +-
 .../internal/UIElement/UIElement.tsx          | 63 +++++++++++++------
 .../playground/src/pages/Dropin/manual.js     | 28 ++++++++-
 3 files changed, 71 insertions(+), 22 deletions(-)

diff --git a/.prettierrc.json b/.prettierrc.json
index 3f1d5d07ce..f3ca00a53b 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -3,6 +3,6 @@
     "bracketSpacing": true,
     "trailingComma": "none",
     "tabWidth": 4,
-    "printWidth": 150,
+    "printWidth": 120,
     "singleQuote": true
 }
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index d9cebea2a8..0816ca7d83 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -21,7 +21,10 @@ import {
 import './UIElement.scss';
 import { CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
 
-export abstract class UIElement<P extends UIElementProps = UIElementProps> extends BaseElement<P> implements IUIElement {
+export abstract class UIElement<P extends UIElementProps = UIElementProps>
+    extends BaseElement<P>
+    implements IUIElement
+{
     protected componentRef: any;
 
     protected resources: Resources;
@@ -290,10 +293,9 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
     protected handleOrder = (response: PaymentResponseData): void => {
         const { order } = response;
 
-        const updateCorePromise =
-            !this.props.session && this.props.onPaymentMethodsRequest
-                ? this.handleAdvanceFlowPaymentMethodsUpdate(order)
-                : this.core.update({ order });
+        const updateCorePromise = this.core.session
+            ? this.core.update({ order })
+            : this.handleAdvanceFlowPaymentMethodsUpdate(order);
 
         updateCorePromise.then(() => {
             this.props.onOrderUpdated?.({ order });
@@ -369,7 +371,9 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
      * Get the element's displayable name
      */
     public get displayName(): string {
-        const paymentMethodFromResponse = this.core.paymentMethodsResponse?.paymentMethods?.find(pm => pm.type === this.type);
+        const paymentMethodFromResponse = this.core.paymentMethodsResponse?.paymentMethods?.find(
+            pm => pm.type === this.type
+        );
         return this.props.name || paymentMethodFromResponse?.name || this.type;
     }
 
@@ -391,27 +395,50 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
      * Get the payButton component for the current element
      */
     protected payButton = (props: PayButtonFunctionProps) => {
-        return <PayButton {...props} amount={this.props.amount} secondaryAmount={this.props.secondaryAmount} onClick={this.submit} />;
+        return (
+            <PayButton
+                {...props}
+                amount={this.props.amount}
+                secondaryAmount={this.props.secondaryAmount}
+                onClick={this.submit}
+            />
+        );
     };
 
     private async handleAdvanceFlowPaymentMethodsUpdate(order: Order) {
         return new Promise<PaymentMethodsResponse>((resolve, reject) => {
-            const data = {
-                order: {
-                    orderData: order.orderData,
-                    pspReference: order.pspReference
+            if (!this.props.onPaymentMethodsRequest) {
+                return reject();
+            }
+
+            this.props.onPaymentMethodsRequest(
+                {
+                    order: {
+                        orderData: order.orderData,
+                        pspReference: order.pspReference
+                    },
+                    locale: this.core.options.locale
                 },
-                locale: this.core.options.locale
-            };
-            this.props.onPaymentMethodsRequest(data, { resolve, reject });
+                { resolve, reject }
+            );
         })
-            .then(paymentMethodsResponse => {
-                return this.core.update({ paymentMethodsResponse, order, amount: order.remainingAmount });
-            })
             .catch(error => {
                 this.handleError(
-                    new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Payment methods be updated after partial payment.', { cause: error })
+                    new AdyenCheckoutError(
+                        'IMPLEMENTATION_ERROR',
+                        'Something failed during payment methods update or onPaymentMethodsRequest was not implemented',
+                        {
+                            cause: error
+                        }
+                    )
                 );
+            })
+            .then(paymentMethodsResponse => {
+                return this.core.update({
+                    ...(paymentMethodsResponse && { paymentMethodsResponse }),
+                    order,
+                    amount: order.remainingAmount
+                });
             });
     }
 }
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index e4744ccc7e..7e092aa8cf 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -1,6 +1,24 @@
-import { AdyenCheckout, Dropin, Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay } from '@adyen/adyen-web';
+import {
+    AdyenCheckout,
+    Dropin,
+    Card,
+    GooglePay,
+    PayPal,
+    Ach,
+    Affirm,
+    WeChat,
+    Giftcard,
+    AmazonPay
+} from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { getPaymentMethods, makePayment, checkBalance, createOrder, cancelOrder, makeDetailsCall } from '../../services';
+import {
+    getPaymentMethods,
+    makePayment,
+    checkBalance,
+    createOrder,
+    cancelOrder,
+    makeDetailsCall
+} from '../../services';
 import { amount, shopperLocale, countryCode } from '../../config/commonConfig';
 import { getSearchParameters } from '../../utils';
 import getTranslationFile from '../../config/getTranslation';
@@ -96,7 +114,11 @@ export async function initManual() {
                 };
 
                 const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-                checkout.update({ paymentMethodsResponse: orderPaymentMethods, order, amount: result.order.remainingAmount });
+                checkout.update({
+                    paymentMethodsResponse: orderPaymentMethods,
+                    order,
+                    amount: result.order.remainingAmount
+                });
             } else {
                 handleFinalState(result.resultCode, component);
             }

From 39a92c93f39681c0e5809265cc2e83f9b1fe15fa Mon Sep 17 00:00:00 2001
From: vagrant <m1aw@users.noreply.github.com>
Date: Thu, 7 Dec 2023 09:22:01 -0600
Subject: [PATCH 14/55] fixes UIelement props overwritten on update

---
 .../internal/BaseElement/BaseElement.ts       |  8 ++-
 .../components/internal/UIElement/utils.ts    |  3 +-
 packages/lib/src/core/core.ts                 | 29 +++++---
 .../helpers/create-advanced-checkout.ts       | 19 +++++-
 .../stories/giftcards/Gifcards.stories.tsx    | 67 +++++++++++++++++++
 5 files changed, 111 insertions(+), 15 deletions(-)
 create mode 100644 packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx

diff --git a/packages/lib/src/components/internal/BaseElement/BaseElement.ts b/packages/lib/src/components/internal/BaseElement/BaseElement.ts
index 5bc03bf696..7b675b54b2 100644
--- a/packages/lib/src/components/internal/BaseElement/BaseElement.ts
+++ b/packages/lib/src/components/internal/BaseElement/BaseElement.ts
@@ -66,7 +66,9 @@ class BaseElement<P extends BaseElementProps> implements IBaseElement {
     public get data(): PaymentData {
         const clientData = getProp(this.props, 'modules.risk.data');
         const useAnalytics = !!getProp(this.props, 'modules.analytics.props.enabled');
-        const checkoutAttemptId = useAnalytics ? getProp(this.props, 'modules.analytics.checkoutAttemptId') : 'do-not-track';
+        const checkoutAttemptId = useAnalytics
+            ? getProp(this.props, 'modules.analytics.checkoutAttemptId')
+            : 'do-not-track';
         const order = this.state.order || this.props.order;
 
         const componentData = this.formatData();
@@ -126,8 +128,8 @@ class BaseElement<P extends BaseElementProps> implements IBaseElement {
      * @param props - props to update
      * @returns this - the element instance
      */
-    public update(props: P): this {
-        this.buildElementProps({ ...this.props, ...props });
+    public update(props: Partial<P>): this {
+        this.props = this.formatProps({ ...this.props, ...props });
         this.state = {};
 
         return this.unmount().mount(this._node); // for new mount fny
diff --git a/packages/lib/src/components/internal/UIElement/utils.ts b/packages/lib/src/components/internal/UIElement/utils.ts
index ace5355f64..7af89b9ff8 100644
--- a/packages/lib/src/components/internal/UIElement/utils.ts
+++ b/packages/lib/src/components/internal/UIElement/utils.ts
@@ -15,7 +15,8 @@ export function getSanitizedResponse(response: RawPaymentResponse): PaymentRespo
         return acc;
     }, {});
 
-    if (removedProperties.length) console.warn(`The following properties should not be passed to the client: ${removedProperties.join(', ')}`);
+    if (removedProperties.length)
+        console.warn(`The following properties should not be passed to the client: ${removedProperties.join(', ')}`);
 
     return sanitizedObject as PaymentResponseData;
 }
diff --git a/packages/lib/src/core/core.ts b/packages/lib/src/core/core.ts
index 8e43602d02..7817addeaf 100644
--- a/packages/lib/src/core/core.ts
+++ b/packages/lib/src/core/core.ts
@@ -57,12 +57,18 @@ class Core implements ICore {
         this.setOptions(props);
 
         this.loadingContext = resolveEnvironment(this.options.environment, this.options.environmentUrls?.api);
-        this.cdnContext = resolveCDNEnvironment(this.options.resourceEnvironment || this.options.environment, this.options.environmentUrls?.api);
-        this.session = this.options.session && new Session(this.options.session, this.options.clientKey, this.loadingContext);
+        this.cdnContext = resolveCDNEnvironment(
+            this.options.resourceEnvironment || this.options.environment,
+            this.options.environmentUrls?.api
+        );
+        this.session =
+            this.options.session && new Session(this.options.session, this.options.clientKey, this.loadingContext);
 
         const clientKeyType = this.options.clientKey?.substr(0, 4);
         if ((clientKeyType === 'test' || clientKeyType === 'live') && !this.loadingContext.includes(clientKeyType)) {
-            throw new Error(`Error: you are using a '${clientKeyType}' clientKey against the '${this.options.environment}' environment`);
+            throw new Error(
+                `Error: you are using a '${clientKeyType}' clientKey against the '${this.options.environment}' environment`
+            );
         }
 
         // Expose version number for npm builds
@@ -135,7 +141,9 @@ class Core implements ICore {
                         'a "resultCode": have you passed in the whole response object by mistake?'
                 );
             }
-            throw new Error('createFromAction::Invalid Action - the passed action object does not have a "type" property');
+            throw new Error(
+                'createFromAction::Invalid Action - the passed action object does not have a "type" property'
+            );
         }
 
         if (action.type) {
@@ -156,12 +164,13 @@ class Core implements ICore {
      * @param options - props to update
      * @returns this - the element instance
      */
-    public update = (options: CoreConfiguration = {}): Promise<this> => {
+    public update = (options: Partial<CoreConfiguration> = {}): Promise<this> => {
         this.setOptions(options);
 
         return this.initialize().then(() => {
             // Update each component under this instance
-            this.components.forEach(c => c.update(this.getCorePropsForComponent()));
+            // here we should update only the new options that have been received from core
+            this.components.forEach(c => c.update(options));
             return this;
         });
     };
@@ -224,7 +233,8 @@ class Core implements ICore {
      * @internal
      */
     private handleCreateError(paymentMethod?): never {
-        const paymentMethodName = paymentMethod && paymentMethod.name ? paymentMethod.name : 'The passed payment method';
+        const paymentMethodName =
+            paymentMethod && paymentMethod.name ? paymentMethod.name : 'The passed payment method';
         const errorMessage = paymentMethod
             ? `${paymentMethodName} is not a valid Checkout Component. What was passed as a txVariant was: ${JSON.stringify(
                   paymentMethod
@@ -235,7 +245,10 @@ class Core implements ICore {
     }
 
     private createPaymentMethodsList(paymentMethodsResponse?: PaymentMethods): void {
-        this.paymentMethodsResponse = new PaymentMethods(this.options.paymentMethodsResponse || paymentMethodsResponse, this.options);
+        this.paymentMethodsResponse = new PaymentMethods(
+            this.options.paymentMethodsResponse || paymentMethodsResponse,
+            this.options
+        );
     }
 
     private createCoreModules(): void {
diff --git a/packages/lib/storybook/helpers/create-advanced-checkout.ts b/packages/lib/storybook/helpers/create-advanced-checkout.ts
index 0af291ab9c..fda07a31b4 100644
--- a/packages/lib/storybook/helpers/create-advanced-checkout.ts
+++ b/packages/lib/storybook/helpers/create-advanced-checkout.ts
@@ -6,13 +6,22 @@ import { AdyenCheckoutProps } from '../stories/types';
 import Checkout from '../../src/core/core';
 import { PaymentMethodsResponse } from '../../src/types';
 
-async function createAdvancedFlowCheckout({ showPayButton, countryCode, shopperLocale, amount }: AdyenCheckoutProps): Promise<Checkout> {
+async function createAdvancedFlowCheckout({
+    showPayButton,
+    countryCode,
+    shopperLocale,
+    amount
+}: AdyenCheckoutProps): Promise<Checkout> {
     const paymentAmount = {
         currency: getCurrency(countryCode),
         value: Number(amount)
     };
 
-    const paymentMethodsResponse: PaymentMethodsResponse = await getPaymentMethods({ amount: paymentAmount, shopperLocale, countryCode });
+    const paymentMethodsResponse: PaymentMethodsResponse = await getPaymentMethods({
+        amount: paymentAmount,
+        shopperLocale,
+        countryCode
+    });
 
     const checkout = await AdyenCheckout({
         clientKey: process.env.CLIENT_KEY,
@@ -41,8 +50,12 @@ async function createAdvancedFlowCheckout({ showPayButton, countryCode, shopperL
         },
 
         onBalanceCheck: async (resolve, reject, data) => {
+            const payload = {
+                amount: paymentAmount,
+                ...data
+            };
             try {
-                const res = await checkBalance(data);
+                const res = await checkBalance(payload);
                 resolve(res);
             } catch (e) {
                 reject(e);
diff --git a/packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx b/packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx
new file mode 100644
index 0000000000..49f07220f1
--- /dev/null
+++ b/packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx
@@ -0,0 +1,67 @@
+import { Meta, StoryObj } from '@storybook/preact';
+import { PaymentMethodStoryProps } from '../types';
+import { getStoryContextCheckout } from '../../utils/get-story-context-checkout';
+import { Container } from '../Container';
+import { ANCVConfiguration } from '../../../src/components/ANCV/types';
+import Giftcard from '../../../src/components/Giftcard';
+import { GiftCardConfiguration } from '../../../src/components/Giftcard/types';
+import { makePayment } from '../../helpers/checkout-api-calls';
+
+type GifcardStory = StoryObj<PaymentMethodStoryProps<GiftCardConfiguration>>;
+
+const meta: Meta<PaymentMethodStoryProps<ANCVConfiguration>> = {
+    title: 'Giftcards/Generic Giftcard'
+};
+
+export const Default: GifcardStory = {
+    render: (args, context) => {
+        const { componentConfiguration } = args;
+        const checkout = getStoryContextCheckout(context);
+        const ancv = new Giftcard({ core: checkout, ...componentConfiguration });
+        return <Container element={ancv} />;
+    },
+    args: {
+        countryCode: 'NL',
+        amount: 200000,
+        useSessions: false,
+        componentConfiguration: {
+            brand: 'genericgiftcard',
+            onSubmit: async (state, element, actions) => {
+                try {
+                    const paymentData = {
+                        amount: {
+                            value: 200000,
+                            currency: 'EUR'
+                        },
+                        countryCode: 'NL',
+                        shopperLocale: 'en-GB'
+                    };
+                    const result = await makePayment(state.data, paymentData);
+
+                    // happpy flow
+                    if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
+                        actions.reject({
+                            error: {
+                                googlePayError: {}
+                            }
+                        });
+                    } else {
+                        actions.resolve({
+                            action: result.action,
+                            order: result.order,
+                            resultCode: result.resultCode
+                        });
+                    }
+                } catch (error) {
+                    // Something failed in the request
+                    actions.reject();
+                }
+            },
+            onOrderUpdated(data) {
+                // TODO render another component
+                alert(JSON.stringify(data));
+            }
+        }
+    }
+};
+export default meta;

From ad6b2b532cdee067e1678c9145ac83a7a354eaf3 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 8 Dec 2023 14:53:27 -0300
Subject: [PATCH 15/55] feat: applepay order tracking untested changes

---
 packages/lib/package.json                     |  4 +-
 .../lib/src/components/ApplePay/ApplePay.tsx  | 80 ++++++++++++++++---
 .../components/ApplePay/ApplePayService.ts    | 37 +++++----
 packages/lib/src/components/ApplePay/types.ts | 29 +++++--
 yarn.lock                                     | 23 +++---
 5 files changed, 127 insertions(+), 46 deletions(-)

diff --git a/packages/lib/package.json b/packages/lib/package.json
index 19bfe50df9..ac39561f46 100644
--- a/packages/lib/package.json
+++ b/packages/lib/package.json
@@ -140,8 +140,8 @@
     },
     "dependencies": {
         "@adyen/bento-design-tokens": "0.5.4",
-        "@types/applepayjs": "3.0.4",
-        "@types/googlepay": "0.7.0",
+        "@types/applepayjs": "14.0.3",
+        "@types/googlepay": "0.7.5",
         "classnames": "2.3.1",
         "preact": "10.13.2"
     },
diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index 71a02557e5..c413d479b6 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -8,7 +8,12 @@ import { httpPost } from '../../core/Services/http';
 import { APPLEPAY_SESSION_ENDPOINT } from './config';
 import { preparePaymentRequest } from './payment-request';
 import { resolveSupportedVersion, mapBrands } from './utils';
-import { ApplePayConfiguration, ApplePayElementData, ApplePaySessionRequest } from './types';
+import {
+    ApplePayConfiguration,
+    ApplePayElementData,
+    ApplePayPaymentOrderDetails,
+    ApplePaySessionRequest
+} from './types';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
 import { onSubmitReject } from '../../core/types';
@@ -56,12 +61,17 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
     }
 
     public submit = (): void => {
-        this.startSession();
+        void this.startSession();
     };
 
-    // private startSession(onPaymentAuthorized: OnAuthorizedCallback) {
     private startSession() {
-        const { version, onValidateMerchant, onPaymentMethodSelected, onShippingMethodSelected, onShippingContactSelected } = this.props;
+        const {
+            version,
+            onValidateMerchant,
+            onPaymentMethodSelected,
+            onShippingMethodSelected,
+            onShippingContactSelected
+        } = this.props;
 
         const paymentRequest = preparePaymentRequest({
             companyName: this.props.configuration.merchantName,
@@ -71,7 +81,11 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
         const session = new ApplePayService(paymentRequest, {
             version,
             onError: (error: unknown) => {
-                this.handleError(new AdyenCheckoutError('ERROR', 'ApplePay - Something went wrong on ApplePayService', { cause: error }));
+                this.handleError(
+                    new AdyenCheckoutError('ERROR', 'ApplePay - Something went wrong on ApplePayService', {
+                        cause: error
+                    })
+                );
             },
             onCancel: event => {
                 this.handleError(new AdyenCheckoutError('CANCEL', 'ApplePay UI dismissed', { cause: event }));
@@ -88,9 +102,12 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                 this.makePaymentsCall()
                     .then(this.sanitizeResponse)
                     .then(this.verifyPaymentDidNotFail)
-                    .then((paymentResponse: PaymentResponseData) => {
-                        // check the order part here
-                        resolve();
+                    .then(this.collectOrderTrackingDetailsIfNeeded)
+                    .then(({ paymentResponse, orderDetails }) => {
+                        resolve({
+                            status: ApplePaySession.STATUS_SUCCESS,
+                            ...(orderDetails && { orderDetails })
+                        });
                         return paymentResponse;
                     })
                     .then(paymentResponse => {
@@ -117,13 +134,45 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
             }));
     }
 
+    /**
+     * Verify if the 'onOrderTrackingRequest' is provided. If so, triggers the callback expecting an
+     * Apple Pay order details back
+     *
+     * @private
+     */
+    private async collectOrderTrackingDetailsIfNeeded(
+        paymentResponse: PaymentResponseData
+    ): Promise<{ orderDetails?: ApplePayPaymentOrderDetails; paymentResponse: PaymentResponseData }> {
+        return new Promise<ApplePayPaymentOrderDetails | void>((resolve, reject) => {
+            if (!this.props.onOrderTrackingRequest) {
+                return resolve();
+            }
+
+            this.props.onOrderTrackingRequest(resolve, reject);
+        })
+            .then(orderDetails => {
+                return {
+                    paymentResponse,
+                    ...(orderDetails && { orderDetails })
+                };
+            })
+            .catch(() => {
+                return { paymentResponse };
+            });
+    }
+
     private async validateMerchant(resolve, reject) {
         const { hostname: domainName } = window.location;
         const { clientKey, configuration, loadingContext, initiative } = this.props;
         const { merchantName, merchantId } = configuration;
         const path = `${APPLEPAY_SESSION_ENDPOINT}?clientKey=${clientKey}`;
         const options = { loadingContext, path };
-        const request: ApplePaySessionRequest = { displayName: merchantName, domainName, initiative, merchantIdentifier: merchantId };
+        const request: ApplePaySessionRequest = {
+            displayName: merchantName,
+            domainName,
+            initiative,
+            merchantIdentifier: merchantId
+        };
 
         try {
             const response = await httpPost(options, request);
@@ -152,7 +201,12 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
      */
     public override async isAvailable(): Promise<void> {
         if (document.location.protocol !== 'https:') {
-            return Promise.reject(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Trying to start an Apple Pay session from an insecure document'));
+            return Promise.reject(
+                new AdyenCheckoutError(
+                    'IMPLEMENTATION_ERROR',
+                    'Trying to start an Apple Pay session from an insecure document'
+                )
+            );
         }
 
         if (!this.props.onValidateMerchant && !this.props.clientKey) {
@@ -160,7 +214,11 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
         }
 
         try {
-            if (window.ApplePaySession && ApplePaySession.canMakePayments() && ApplePaySession.supportsVersion(this.props.version)) {
+            if (
+                window.ApplePaySession &&
+                ApplePaySession.canMakePayments() &&
+                ApplePaySession.supportsVersion(this.props.version)
+            ) {
                 return Promise.resolve();
             }
         } catch (error) {
diff --git a/packages/lib/src/components/ApplePay/ApplePayService.ts b/packages/lib/src/components/ApplePay/ApplePayService.ts
index 891fef6c09..eb27517007 100644
--- a/packages/lib/src/components/ApplePay/ApplePayService.ts
+++ b/packages/lib/src/components/ApplePay/ApplePayService.ts
@@ -1,4 +1,4 @@
-import { OnAuthorizedCallback } from './types';
+import { ApplePayPaymentAuthorizationResult } from './types';
 
 interface ApplePayServiceOptions {
     version: number;
@@ -8,7 +8,11 @@ interface ApplePayServiceOptions {
     onPaymentMethodSelected?: (resolve, reject, event: ApplePayJS.ApplePayPaymentMethodSelectedEvent) => void;
     onShippingMethodSelected?: (resolve, reject, event: ApplePayJS.ApplePayShippingMethodSelectedEvent) => void;
     onShippingContactSelected?: (resolve, reject, event: ApplePayJS.ApplePayShippingContactSelectedEvent) => void;
-    onPaymentAuthorized?: OnAuthorizedCallback;
+    onPaymentAuthorized: (
+        resolve: (result: ApplePayPaymentAuthorizationResult) => void,
+        reject: (result: ApplePayPaymentAuthorizationResult) => void,
+        event: ApplePayJS.ApplePayPaymentAuthorizedEvent
+    ) => void;
 }
 
 class ApplePayService {
@@ -24,15 +28,18 @@ class ApplePayService {
         this.session.oncancel = event => this.oncancel(event, options.onCancel);
 
         if (typeof options.onPaymentMethodSelected === 'function') {
-            this.session.onpaymentmethodselected = event => this.onpaymentmethodselected(event, options.onPaymentMethodSelected);
+            this.session.onpaymentmethodselected = event =>
+                this.onpaymentmethodselected(event, options.onPaymentMethodSelected);
         }
 
         if (typeof options.onShippingContactSelected === 'function') {
-            this.session.onshippingcontactselected = event => this.onshippingcontactselected(event, options.onShippingContactSelected);
+            this.session.onshippingcontactselected = event =>
+                this.onshippingcontactselected(event, options.onShippingContactSelected);
         }
 
         if (typeof options.onShippingMethodSelected === 'function') {
-            this.session.onshippingmethodselected = event => this.onshippingmethodselected(event, options.onShippingMethodSelected);
+            this.session.onshippingmethodselected = event =>
+                this.onshippingmethodselected(event, options.onShippingMethodSelected);
         }
     }
 
@@ -72,19 +79,16 @@ class ApplePayService {
      * @param onPaymentAuthorized - A promise that will complete the payment when resolved. Use this promise to process the payment.
      * @see {@link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778020-onpaymentauthorized}
      */
-    onpaymentauthorized(event: ApplePayJS.ApplePayPaymentAuthorizedEvent, onPaymentAuthorized: OnAuthorizedCallback): Promise<void> {
+    onpaymentauthorized(
+        event: ApplePayJS.ApplePayPaymentAuthorizedEvent,
+        onPaymentAuthorized: ApplePayServiceOptions['onPaymentAuthorized']
+    ): Promise<void> {
         return new Promise((resolve, reject) => onPaymentAuthorized(resolve, reject, event))
-            .then((result: ApplePayJS.ApplePayPaymentAuthorizationResult) => {
-                this.session.completePayment({
-                    ...result,
-                    status: result?.status ?? ApplePaySession.STATUS_SUCCESS
-                });
+            .then((result: ApplePayPaymentAuthorizationResult) => {
+                this.session.completePayment(result);
             })
-            .catch((result?: ApplePayJS.ApplePayPaymentAuthorizationResult) => {
-                this.session.completePayment({
-                    ...result,
-                    status: result?.status ?? ApplePaySession.STATUS_FAILURE
-                });
+            .catch((result: ApplePayPaymentAuthorizationResult) => {
+                this.session.completePayment(result);
             });
     }
 
@@ -99,7 +103,6 @@ class ApplePayService {
     onpaymentmethodselected(event: ApplePayJS.ApplePayPaymentMethodSelectedEvent, onPaymentMethodSelected) {
         return new Promise((resolve, reject) => onPaymentMethodSelected(resolve, reject, event))
             .then((paymentMethodUpdate: ApplePayJS.ApplePayPaymentMethodUpdate) => {
-                console.log('onpaymentmethodselected', paymentMethodUpdate);
                 this.session.completePaymentMethodSelection(paymentMethodUpdate);
             })
             .catch((paymentMethodUpdate: ApplePayJS.ApplePayPaymentMethodUpdate) => {
diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts
index 7746bb7fa7..526c5fc654 100644
--- a/packages/lib/src/components/ApplePay/types.ts
+++ b/packages/lib/src/components/ApplePay/types.ts
@@ -1,4 +1,3 @@
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
 import { UIElementProps } from '../internal/UIElement/types';
 
 declare global {
@@ -9,6 +8,18 @@ declare global {
 
 type Initiative = 'web' | 'messaging';
 
+export type ApplePayPaymentOrderDetails = {
+    orderTypeIdentifier: string;
+    orderIdentifier: string;
+    webServiceURL: string;
+    authenticationToken: string;
+};
+
+// @types/applepayjs package does not contain 'orderDetails' yet, so we create our own type
+export type ApplePayPaymentAuthorizationResult = ApplePayJS.ApplePayPaymentAuthorizationResult & {
+    orderDetails?: ApplePayPaymentOrderDetails;
+};
+
 export type ApplePayButtonType =
     | 'plain'
     | 'buy'
@@ -25,12 +36,6 @@ export type ApplePayButtonType =
     | 'tip'
     | 'top-up';
 
-export type OnAuthorizedCallback = (
-    resolve: (result?: ApplePayJS.ApplePayPaymentAuthorizationResult) => void,
-    reject: (result?: ApplePayJS.ApplePayPaymentAuthorizationResult) => void,
-    event: ApplePayJS.ApplePayPaymentAuthorizedEvent
-) => void;
-
 export interface ApplePayConfiguration extends UIElementProps {
     /**
      * The Apple Pay version number your website supports.
@@ -143,6 +148,16 @@ export interface ApplePayConfiguration extends UIElementProps {
 
     onClick?: (resolve, reject) => void;
 
+    /**
+     * Collect the order tracking details if available.
+     * This callback is invoked when a successfull payment is resolved
+     *
+     * {@link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentorderdetails}
+     * @param resolve - Must be called with the orderDetails fields
+     * @param reject - Must be called if something failed during the order creation
+     */
+    onOrderTrackingRequest?: (resolve: (orderDetails: ApplePayPaymentOrderDetails) => void, reject: () => void) => void;
+
     onValidateMerchant?: (resolve, reject, validationURL: string) => void;
 
     /**
diff --git a/yarn.lock b/yarn.lock
index 030939b98a..cbc372f86f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4350,10 +4350,10 @@
   resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
   integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
 
-"@types/applepayjs@3.0.4":
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/@types/applepayjs/-/applepayjs-3.0.4.tgz#9806a4b3ccd73dcf169c61a34be7a39f91d77540"
-  integrity sha512-RqaVZWy1Kj4e1PoUoOI8uA+4UuuLpicQFxfU9Y/xWJFZFT6mFB4PiiY911iDxFk7pdvaj5HKH7VsWRisRca1Rg==
+"@types/applepayjs@14.0.3":
+  version "14.0.3"
+  resolved "https://registry.yarnpkg.com/@types/applepayjs/-/applepayjs-14.0.3.tgz#3983c596385d8bd35379b5f5cb0287b46f723624"
+  integrity sha512-3ketgiX96+ZbFpK1/aCvEAEHUlPsuBt7cv1VCUVYZfWEekvn6oKaTtzK4G+CUINOQwh+1U5QwFb270xhOn94gA==
 
 "@types/aria-query@^5.0.1":
   version "5.0.1"
@@ -4514,10 +4514,10 @@
     "@types/minimatch" "*"
     "@types/node" "*"
 
-"@types/googlepay@0.7.0":
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/@types/googlepay/-/googlepay-0.7.0.tgz#eb11aca185a64cd413952bcb7c16ea25217ac6bc"
-  integrity sha512-jC7ViexJeV8LlTKLiUBfNs5GICbm0PYsm5Y30JCEBkreY0bMNA+F4KnTEz+WtqBTRldTAxKBKqRstlOUFpW+dA==
+"@types/googlepay@0.7.5":
+  version "0.7.5"
+  resolved "https://registry.yarnpkg.com/@types/googlepay/-/googlepay-0.7.5.tgz#b944cd0e4c49f4661c9b966cb45614eb7ae87a26"
+  integrity sha512-158egcRaqkMSpW6unyGV4uG4FpoCklRf3J5emCzOXSRVAohMfIuZ481JNvp4X6+KxoNjxWiGtMx5vb1YfQADPw==
 
 "@types/graceful-fs@^4.1.3":
   version "4.1.9"
@@ -13233,11 +13233,16 @@ regenerate@^1.4.2:
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
   integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
 
-regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.9, regenerator-runtime@^0.14.0:
+regenerator-runtime@^0.13.11:
   version "0.13.11"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
   integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
 
+regenerator-runtime@^0.14.0:
+  version "0.14.0"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
+  integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
+
 regenerator-transform@^0.15.1:
   version "0.15.1"
   resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"

From a96dc2abfc82b7a07491d4f310a5edf6a2029add Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Mon, 11 Dec 2023 13:35:59 -0300
Subject: [PATCH 16/55] applepay: exposing authorization event

---
 .../lib/src/components/ApplePay/ApplePay.tsx  |  4 +++-
 packages/lib/src/components/Dropin/Dropin.tsx | 24 +++++++++++++++----
 .../src/components/GooglePay/GooglePay.tsx    | 11 ++++++---
 .../internal/UIElement/UIElement.tsx          | 12 ++++++++--
 .../wallets/ApplePayExpress.stories.tsx       |  8 ++++++-
 5 files changed, 48 insertions(+), 11 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index c413d479b6..60351c7423 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -30,6 +30,7 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
         this.startSession = this.startSession.bind(this);
         this.submit = this.submit.bind(this);
         this.validateMerchant = this.validateMerchant.bind(this);
+        this.collectOrderTrackingDetailsIfNeeded = this.collectOrderTrackingDetailsIfNeeded.bind(this);
     }
 
     /**
@@ -96,7 +97,8 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
             onValidateMerchant: onValidateMerchant || this.validateMerchant,
             onPaymentAuthorized: (resolve, reject, event) => {
                 this.setState({
-                    applePayToken: btoa(JSON.stringify(event.payment.token.paymentData))
+                    applePayToken: btoa(JSON.stringify(event.payment.token.paymentData)),
+                    authorizedEvent: event
                 });
 
                 this.makePaymentsCall()
diff --git a/packages/lib/src/components/Dropin/Dropin.tsx b/packages/lib/src/components/Dropin/Dropin.tsx
index 77f5097d5a..286bd72053 100644
--- a/packages/lib/src/components/Dropin/Dropin.tsx
+++ b/packages/lib/src/components/Dropin/Dropin.tsx
@@ -47,8 +47,16 @@ class DropinElement extends UIElement<DropinConfiguration> {
         };
     }
 
+    public override get authorizedEvent(): any {
+        return this.dropinRef?.state?.activePaymentMethod?.authorizedEvent;
+    }
+
     get isValid() {
-        return !!this.dropinRef && !!this.dropinRef.state.activePaymentMethod && !!this.dropinRef.state.activePaymentMethod.isValid;
+        return (
+            !!this.dropinRef &&
+            !!this.dropinRef.state.activePaymentMethod &&
+            !!this.dropinRef.state.activePaymentMethod.isValid
+        );
     }
 
     showValidation() {
@@ -103,7 +111,8 @@ class DropinElement extends UIElement<DropinConfiguration> {
      * Creates the Drop-in elements
      */
     private handleCreate = () => {
-        const { paymentMethodsConfiguration, showStoredPaymentMethods, showPaymentMethods, instantPaymentTypes } = this.props;
+        const { paymentMethodsConfiguration, showStoredPaymentMethods, showPaymentMethods, instantPaymentTypes } =
+            this.props;
 
         const { paymentMethods, storedPaymentMethods, instantPaymentMethods } = splitPaymentMethods(
             this.core.paymentMethodsResponse,
@@ -115,8 +124,15 @@ class DropinElement extends UIElement<DropinConfiguration> {
         const storedElements = showStoredPaymentMethods
             ? createStoredElements(storedPaymentMethods, paymentMethodsConfiguration, commonProps, this.core)
             : [];
-        const elements = showPaymentMethods ? createElements(paymentMethods, paymentMethodsConfiguration, commonProps, this.core) : [];
-        const instantPaymentElements = createInstantPaymentElements(instantPaymentMethods, paymentMethodsConfiguration, commonProps, this.core);
+        const elements = showPaymentMethods
+            ? createElements(paymentMethods, paymentMethodsConfiguration, commonProps, this.core)
+            : [];
+        const instantPaymentElements = createInstantPaymentElements(
+            instantPaymentMethods,
+            paymentMethodsConfiguration,
+            commonProps,
+            this.core
+        );
 
         return [storedElements, elements, instantPaymentElements];
     };
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index b520ee850e..f157dcd2b8 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -39,7 +39,10 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
         const buttonSizeMode = props.buttonSizeMode ?? (props.isDropin ? 'fill' : 'static');
         const buttonLocale = getGooglePayLocale(props.buttonLocale ?? props.i18n?.locale);
 
-        const callbackIntents: google.payments.api.CallbackIntent[] = [...props.callbackIntents, 'PAYMENT_AUTHORIZATION'];
+        const callbackIntents: google.payments.api.CallbackIntent[] = [
+            ...props.callbackIntents,
+            'PAYMENT_AUTHORIZATION'
+        ];
 
         return {
             ...props,
@@ -81,9 +84,11 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
      *
      * @see https://developers.google.com/pay/api/web/reference/client#onPaymentAuthorized
      **/
-    private onPaymentAuthorized = async (paymentData: google.payments.api.PaymentData): Promise<google.payments.api.PaymentAuthorizationResult> => {
+    private onPaymentAuthorized = async (
+        paymentData: google.payments.api.PaymentData
+    ): Promise<google.payments.api.PaymentAuthorizationResult> => {
         this.setState({
-            authorizedData: paymentData,
+            authorizedEvent: paymentData,
             googlePayToken: paymentData.paymentMethodData.tokenizationData.token,
             googlePayCardNetwork: paymentData.paymentMethodData.info.cardNetwork
         });
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 0816ca7d83..c4117c9300 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -92,6 +92,15 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         return Promise.resolve();
     }
 
+    //  ApplePayJS.ApplePayPayment | google.payments.api.PaymentData | undefined
+    /**
+     * Certain payment methods have data returned after the shopper authorization step (Ex: GooglePay, ApplePay)
+     * This getter returns the event data in case it is available
+     */
+    public get authorizedEvent(): any {
+        return this.state.authorizedEvent;
+    }
+
     public setState(newState: object): void {
         this.state = { ...this.state, ...newState };
         this.onChange();
@@ -164,8 +173,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
             this.props.onSubmit(
                 {
                     data: this.data,
-                    isValid: this.isValid,
-                    ...(this.state.authorizedData && { authorizedData: this.state.authorizedData })
+                    isValid: this.isValid
                 },
                 this.elementRef,
                 { resolve, reject }
diff --git a/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx b/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx
index 0c13445d89..5261405de2 100644
--- a/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx
+++ b/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx
@@ -171,7 +171,13 @@ export const Express: ApplePayStory = {
                 if (countryCode === 'BR') {
                     update = {
                         newTotal: ApplePayAmountHelper.getApplePayTotal(),
-                        errors: [new ApplePayError('shippingContactInvalid', 'countryCode', 'Cannot ship to the selected address')]
+                        errors: [
+                            new ApplePayError(
+                                'shippingContactInvalid',
+                                'countryCode',
+                                'Cannot ship to the selected address'
+                            )
+                        ]
                     };
                     resolve(update);
                     return;

From 077870fcacd46bec56a083d0d4517c4778b5f239 Mon Sep 17 00:00:00 2001
From: guilhermer <>
Date: Tue, 12 Dec 2023 10:31:51 -0300
Subject: [PATCH 17/55] untested applepay address format

---
 .../lib/src/components/ApplePay/ApplePay.tsx  | 15 ++++--
 packages/lib/src/components/ApplePay/types.ts |  4 ++
 packages/lib/src/components/ApplePay/utils.ts | 47 +++++++++++++++++++
 3 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index 60351c7423..e24353c8a6 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -7,7 +7,7 @@ import defaultProps from './defaultProps';
 import { httpPost } from '../../core/Services/http';
 import { APPLEPAY_SESSION_ENDPOINT } from './config';
 import { preparePaymentRequest } from './payment-request';
-import { resolveSupportedVersion, mapBrands } from './utils';
+import { resolveSupportedVersion, mapBrands, formatApplePayContactToAdyenAddressFormat } from './utils';
 import {
     ApplePayConfiguration,
     ApplePayElementData,
@@ -53,10 +53,14 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
      * Formats the component data output
      */
     protected formatData(): ApplePayElementData {
+        const { applePayToken, billingAddress, shippingAddress } = this.state;
+
         return {
             paymentMethod: {
                 type: ApplePayElement.type,
-                applePayToken: this.state.applePayToken
+                applePayToken,
+                ...(billingAddress && { billingAddress }),
+                ...(shippingAddress && { shippingAddress })
             }
         };
     }
@@ -96,9 +100,14 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
             onShippingContactSelected,
             onValidateMerchant: onValidateMerchant || this.validateMerchant,
             onPaymentAuthorized: (resolve, reject, event) => {
+                const billingAddress = formatApplePayContactToAdyenAddressFormat(event.payment.billingContact);
+                const shippingAddress = formatApplePayContactToAdyenAddressFormat(event.payment.shippingContact);
+
                 this.setState({
                     applePayToken: btoa(JSON.stringify(event.payment.token.paymentData)),
-                    authorizedEvent: event
+                    authorizedEvent: event,
+                    ...(billingAddress && { billingAddress }),
+                    ...(shippingAddress && { shippingAddress })
                 });
 
                 this.makePaymentsCall()
diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts
index 526c5fc654..212f1df0b1 100644
--- a/packages/lib/src/components/ApplePay/types.ts
+++ b/packages/lib/src/components/ApplePay/types.ts
@@ -1,4 +1,6 @@
 import { UIElementProps } from '../internal/UIElement/types';
+import { AddressSchema } from '../internal/Address/types';
+import { AddressData } from '../../types/global-types';
 
 declare global {
     interface Window {
@@ -198,6 +200,8 @@ export interface ApplePayElementData {
     paymentMethod: {
         type: string;
         applePayToken: string;
+        billingAddress?: AddressData;
+        shippingAddress?: AddressData;
     };
 }
 
diff --git a/packages/lib/src/components/ApplePay/utils.ts b/packages/lib/src/components/ApplePay/utils.ts
index 14d0fbc7ee..db3bdfbd54 100644
--- a/packages/lib/src/components/ApplePay/utils.ts
+++ b/packages/lib/src/components/ApplePay/utils.ts
@@ -1,3 +1,5 @@
+import { AddressData } from '../../types/global-types';
+
 export function resolveSupportedVersion(latestVersion: number): number | null {
     const versions = [];
     for (let i = latestVersion; i > 0; i--) {
@@ -36,3 +38,48 @@ export function mapBrands(brands) {
         return accumulator;
     }, []);
 }
+
+/**
+ * ApplePay formats address into two lines (US format). First line includes house number and street name.
+ * Second line includes unit/suite/apartment number if applicable.
+ * This function formats it into Adyen's Address format (house number separate from street).
+ */
+export function formatApplePayContactToAdyenAddressFormat(
+    paymentContact: ApplePayJS.ApplePayPaymentContact
+): AddressData | undefined {
+    if (!paymentContact) {
+        return;
+    }
+
+    let street = '';
+    let houseNumberOrName = '';
+    if (paymentContact.addressLines && paymentContact.addressLines.length) {
+        const splitAddress = splitAddressLine(paymentContact.addressLines[0]);
+        street = splitAddress.streetAddress;
+        houseNumberOrName = splitAddress.houseNumber;
+    }
+
+    if (paymentContact.addressLines && paymentContact.addressLines.length > 1) {
+        street += ` ${paymentContact.addressLines[1]}`;
+    }
+
+    return {
+        city: paymentContact.locality,
+        country: paymentContact.countryCode,
+        houseNumberOrName,
+        postalCode: paymentContact.postalCode,
+        stateOrProvince: paymentContact.administrativeArea,
+        street: street
+    };
+}
+
+const splitAddressLine = (addressLine: string) => {
+    // The \d+ captures the digits of the house number, and \w* allows for any letter suffixes (like "123B")
+    // Everything after the space is considered the street address.
+    const parts = addressLine.match(/^(\d+\w*)\s+(.+)/);
+    if (parts) {
+        return { houseNumber: parts[1] || '', streetAddress: parts[2] || addressLine };
+    } else {
+        return { houseNumber: '', streetAddress: addressLine };
+    }
+};

From 582001668afb4ec9b85e769d892cd588adc566d2 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 14 Dec 2023 15:31:55 -0300
Subject: [PATCH 18/55] applepay - formatting data accordingly

---
 .../lib/src/components/ApplePay/ApplePay.tsx  | 14 ++---
 packages/lib/src/components/ApplePay/types.ts |  1 -
 .../lib/src/components/ApplePay/utils.test.ts | 58 +++++++++++++++++++
 packages/lib/src/components/ApplePay/utils.ts | 43 +++++---------
 4 files changed, 78 insertions(+), 38 deletions(-)
 create mode 100644 packages/lib/src/components/ApplePay/utils.test.ts

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index e24353c8a6..50c46a5d62 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -53,15 +53,15 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
      * Formats the component data output
      */
     protected formatData(): ApplePayElementData {
-        const { applePayToken, billingAddress, shippingAddress } = this.state;
+        const { applePayToken, billingAddress, deliveryAddress } = this.state;
 
         return {
             paymentMethod: {
                 type: ApplePayElement.type,
-                applePayToken,
-                ...(billingAddress && { billingAddress }),
-                ...(shippingAddress && { shippingAddress })
-            }
+                applePayToken
+            },
+            ...(billingAddress && { billingAddress }),
+            ...(deliveryAddress && { deliveryAddress })
         };
     }
 
@@ -101,13 +101,13 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
             onValidateMerchant: onValidateMerchant || this.validateMerchant,
             onPaymentAuthorized: (resolve, reject, event) => {
                 const billingAddress = formatApplePayContactToAdyenAddressFormat(event.payment.billingContact);
-                const shippingAddress = formatApplePayContactToAdyenAddressFormat(event.payment.shippingContact);
+                const deliveryAddress = formatApplePayContactToAdyenAddressFormat(event.payment.shippingContact, true);
 
                 this.setState({
                     applePayToken: btoa(JSON.stringify(event.payment.token.paymentData)),
                     authorizedEvent: event,
                     ...(billingAddress && { billingAddress }),
-                    ...(shippingAddress && { shippingAddress })
+                    ...(deliveryAddress && { deliveryAddress })
                 });
 
                 this.makePaymentsCall()
diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts
index 212f1df0b1..15a4e3178b 100644
--- a/packages/lib/src/components/ApplePay/types.ts
+++ b/packages/lib/src/components/ApplePay/types.ts
@@ -1,5 +1,4 @@
 import { UIElementProps } from '../internal/UIElement/types';
-import { AddressSchema } from '../internal/Address/types';
 import { AddressData } from '../../types/global-types';
 
 declare global {
diff --git a/packages/lib/src/components/ApplePay/utils.test.ts b/packages/lib/src/components/ApplePay/utils.test.ts
new file mode 100644
index 0000000000..a59b9a9d48
--- /dev/null
+++ b/packages/lib/src/components/ApplePay/utils.test.ts
@@ -0,0 +1,58 @@
+import { formatApplePayContactToAdyenAddressFormat } from './utils';
+
+describe('formatApplePayContactToAdyenAddressFormat()', () => {
+    test('should build the street by merging the address lines and set houseNumberOrName to ZZ', () => {
+        const billingContact = {
+            addressLines: ['1 Infinite Loop', 'Unit 100'],
+            locality: 'Cupertino',
+            administrativeArea: 'CA',
+            postalCode: '95014',
+            countryCode: 'US',
+            country: 'United States',
+            givenName: 'John',
+            familyName: 'Appleseed',
+            phoneticFamilyName: '',
+            phoneticGivenName: '',
+            subAdministrativeArea: '',
+            subLocality: ''
+        };
+
+        const billingAddress = formatApplePayContactToAdyenAddressFormat(billingContact);
+
+        expect(billingAddress.houseNumberOrName).toEqual('ZZ');
+        expect(billingAddress.street).toEqual('1 Infinite Loop Unit 100');
+        expect(billingAddress.city).toEqual('Cupertino');
+        expect(billingAddress.postalCode).toEqual('95014');
+        expect(billingAddress.country).toEqual('US');
+        expect(billingAddress.stateOrProvince).toEqual('CA');
+    });
+    test('should return only postal code if available', () => {
+        const billingContact = {
+            addressLines: [],
+            locality: '',
+            administrativeArea: '',
+            postalCode: '95014',
+            countryCode: '',
+            country: '',
+            givenName: '',
+            familyName: '',
+            phoneticFamilyName: '',
+            phoneticGivenName: '',
+            subAdministrativeArea: '',
+            subLocality: ''
+        };
+
+        const billingAddress = formatApplePayContactToAdyenAddressFormat(billingContact);
+
+        expect(billingAddress.houseNumberOrName).toEqual('');
+        expect(billingAddress.street).toEqual('');
+        expect(billingAddress.city).toEqual('');
+        expect(billingAddress.postalCode).toEqual('95014');
+        expect(billingAddress.country).toEqual('');
+        expect(billingAddress.stateOrProvince).toEqual('');
+    });
+
+    test.todo('should return firstName and lastName if the contact is for delivery address');
+
+    test.todo('should omit stateOrProvince field if not available');
+});
diff --git a/packages/lib/src/components/ApplePay/utils.ts b/packages/lib/src/components/ApplePay/utils.ts
index db3bdfbd54..71208aa9a0 100644
--- a/packages/lib/src/components/ApplePay/utils.ts
+++ b/packages/lib/src/components/ApplePay/utils.ts
@@ -40,46 +40,29 @@ export function mapBrands(brands) {
 }
 
 /**
- * ApplePay formats address into two lines (US format). First line includes house number and street name.
- * Second line includes unit/suite/apartment number if applicable.
- * This function formats it into Adyen's Address format (house number separate from street).
+ * This function formats Apple Pay contact format to Adyen address format
+ *
+ * Setting 'houseNumberOrName' to ZZ won't affect the AVS check, and it will make the algorithm take the
+ * house number from the 'street' property.
  */
 export function formatApplePayContactToAdyenAddressFormat(
-    paymentContact: ApplePayJS.ApplePayPaymentContact
+    paymentContact: ApplePayJS.ApplePayPaymentContact,
+    isDeliveryAddress?: boolean
 ): AddressData | undefined {
     if (!paymentContact) {
         return;
     }
 
-    let street = '';
-    let houseNumberOrName = '';
-    if (paymentContact.addressLines && paymentContact.addressLines.length) {
-        const splitAddress = splitAddressLine(paymentContact.addressLines[0]);
-        street = splitAddress.streetAddress;
-        houseNumberOrName = splitAddress.houseNumber;
-    }
-
-    if (paymentContact.addressLines && paymentContact.addressLines.length > 1) {
-        street += ` ${paymentContact.addressLines[1]}`;
-    }
-
     return {
         city: paymentContact.locality,
         country: paymentContact.countryCode,
-        houseNumberOrName,
+        houseNumberOrName: 'ZZ',
         postalCode: paymentContact.postalCode,
-        stateOrProvince: paymentContact.administrativeArea,
-        street: street
+        street: paymentContact.addressLines.join(' ').trim(),
+        ...(paymentContact.administrativeArea && { stateOrProvince: paymentContact.administrativeArea }),
+        ...(isDeliveryAddress && {
+            firstName: paymentContact.givenName,
+            lastName: paymentContact.familyName
+        })
     };
 }
-
-const splitAddressLine = (addressLine: string) => {
-    // The \d+ captures the digits of the house number, and \w* allows for any letter suffixes (like "123B")
-    // Everything after the space is considered the street address.
-    const parts = addressLine.match(/^(\d+\w*)\s+(.+)/);
-    if (parts) {
-        return { houseNumber: parts[1] || '', streetAddress: parts[2] || addressLine };
-    } else {
-        return { houseNumber: '', streetAddress: addressLine };
-    }
-};

From 1d0d7bb28acfe32b0b58b74fea45d438574dc81b Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 15 Dec 2023 09:28:11 -0300
Subject: [PATCH 19/55] applepay address formatter unit tests

---
 .../lib/src/components/ApplePay/utils.test.ts | 60 ++++++++++++++-----
 1 file changed, 45 insertions(+), 15 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/utils.test.ts b/packages/lib/src/components/ApplePay/utils.test.ts
index a59b9a9d48..c58dc90f80 100644
--- a/packages/lib/src/components/ApplePay/utils.test.ts
+++ b/packages/lib/src/components/ApplePay/utils.test.ts
@@ -19,15 +19,18 @@ describe('formatApplePayContactToAdyenAddressFormat()', () => {
 
         const billingAddress = formatApplePayContactToAdyenAddressFormat(billingContact);
 
-        expect(billingAddress.houseNumberOrName).toEqual('ZZ');
-        expect(billingAddress.street).toEqual('1 Infinite Loop Unit 100');
-        expect(billingAddress.city).toEqual('Cupertino');
-        expect(billingAddress.postalCode).toEqual('95014');
-        expect(billingAddress.country).toEqual('US');
-        expect(billingAddress.stateOrProvince).toEqual('CA');
+        expect(billingAddress).toStrictEqual({
+            postalCode: '95014',
+            houseNumberOrName: 'ZZ',
+            street: '1 Infinite Loop Unit 100',
+            city: 'Cupertino',
+            country: 'US',
+            stateOrProvince: 'CA'
+        });
     });
+
     test('should return only postal code if available', () => {
-        const billingContact = {
+        const billingContact: ApplePayJS.ApplePayPaymentContact = {
             addressLines: [],
             locality: '',
             administrativeArea: '',
@@ -44,15 +47,42 @@ describe('formatApplePayContactToAdyenAddressFormat()', () => {
 
         const billingAddress = formatApplePayContactToAdyenAddressFormat(billingContact);
 
-        expect(billingAddress.houseNumberOrName).toEqual('');
-        expect(billingAddress.street).toEqual('');
-        expect(billingAddress.city).toEqual('');
-        expect(billingAddress.postalCode).toEqual('95014');
-        expect(billingAddress.country).toEqual('');
-        expect(billingAddress.stateOrProvince).toEqual('');
+        expect(billingAddress).toStrictEqual({
+            postalCode: '95014',
+            houseNumberOrName: 'ZZ',
+            street: '',
+            city: '',
+            country: ''
+        });
     });
 
-    test.todo('should return firstName and lastName if the contact is for delivery address');
+    test('should return firstName and lastName if the contact is for delivery address', () => {
+        const deliveryContact: ApplePayJS.ApplePayPaymentContact = {
+            addressLines: ['802 Richardon Drive', 'Brooklyn'],
+            locality: 'New York',
+            administrativeArea: 'NY',
+            postalCode: '11213',
+            countryCode: 'US',
+            country: 'United States',
+            givenName: 'Jonny',
+            familyName: 'Smithson',
+            phoneticFamilyName: '',
+            phoneticGivenName: '',
+            subAdministrativeArea: '',
+            subLocality: ''
+        };
+
+        const deliveryAddress = formatApplePayContactToAdyenAddressFormat(deliveryContact, true);
 
-    test.todo('should omit stateOrProvince field if not available');
+        expect(deliveryAddress).toStrictEqual({
+            street: '802 Richardon Drive Brooklyn',
+            houseNumberOrName: 'ZZ',
+            city: 'New York',
+            postalCode: '11213',
+            country: 'US',
+            stateOrProvince: 'NY',
+            firstName: 'Jonny',
+            lastName: 'Smithson'
+        });
+    });
 });

From 324a3ae0ae4afd34dc6a53baae92e1cd24e9302f Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 15 Dec 2023 12:08:50 -0300
Subject: [PATCH 20/55] feat: parsing googlepay address

---
 .../src/components/GooglePay/GooglePay.tsx    | 27 +++++--
 .../src/components/GooglePay/utils.test.ts    | 74 +++++++++++++++++++
 .../lib/src/components/GooglePay/utils.ts     | 29 ++++++++
 3 files changed, 124 insertions(+), 6 deletions(-)
 create mode 100644 packages/lib/src/components/GooglePay/utils.test.ts

diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index f157dcd2b8..3f47d2a566 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -4,12 +4,12 @@ import GooglePayService from './GooglePayService';
 import GooglePayButton from './components/GooglePayButton';
 import defaultProps from './defaultProps';
 import { GooglePayConfiguration } from './types';
-import { getGooglePayLocale } from './utils';
+import { formatGooglePayContactToAdyenAddressFormat, getGooglePayLocale } from './utils';
 import collectBrowserInfo from '../../utils/browserInfo';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
 import { onSubmitReject } from '../../core/types';
-import { PaymentResponseData } from '../../types/global-types';
+import { AddressData, PaymentResponseData } from '../../types/global-types';
 
 class GooglePay extends UIElement<GooglePayConfiguration> {
     public static type = TxVariants.googlepay;
@@ -57,13 +57,18 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
      * Formats the component data output
      */
     formatData() {
+        const { googlePayCardNetwork, googlePayToken, billingAddress, deliveryAddress } = this.state;
+
         return {
             paymentMethod: {
                 type: this.type,
-                googlePayCardNetwork: this.state.googlePayCardNetwork,
-                googlePayToken: this.state.googlePayToken
+                googlePayCardNetwork,
+                googlePayToken
             },
-            browserInfo: this.browserInfo
+            browserInfo: this.browserInfo,
+            origin: !!window && window.location.origin,
+            ...(billingAddress && { billingAddress }),
+            ...(deliveryAddress && { deliveryAddress })
         };
     }
 
@@ -87,10 +92,20 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
     private onPaymentAuthorized = async (
         paymentData: google.payments.api.PaymentData
     ): Promise<google.payments.api.PaymentAuthorizationResult> => {
+        const billingAddress: AddressData = formatGooglePayContactToAdyenAddressFormat(
+            paymentData.paymentMethodData.info.billingAddress
+        );
+        const deliveryAddress: AddressData = formatGooglePayContactToAdyenAddressFormat(
+            paymentData.shippingAddress,
+            true
+        );
+
         this.setState({
             authorizedEvent: paymentData,
             googlePayToken: paymentData.paymentMethodData.tokenizationData.token,
-            googlePayCardNetwork: paymentData.paymentMethodData.info.cardNetwork
+            googlePayCardNetwork: paymentData.paymentMethodData.info.cardNetwork,
+            ...(billingAddress && { billingAddress }),
+            ...(deliveryAddress && { deliveryAddress })
         });
 
         return new Promise<google.payments.api.PaymentAuthorizationResult>(resolve => {
diff --git a/packages/lib/src/components/GooglePay/utils.test.ts b/packages/lib/src/components/GooglePay/utils.test.ts
new file mode 100644
index 0000000000..f14685ca92
--- /dev/null
+++ b/packages/lib/src/components/GooglePay/utils.test.ts
@@ -0,0 +1,74 @@
+import { formatGooglePayContactToAdyenAddressFormat } from './utils';
+
+describe('formatGooglePayContactToAdyenAddressFormat()', () => {
+    test('should build the street by merging the addresses and set houseNumberOrName to ZZ', () => {
+        const billingContact: google.payments.api.Address = {
+            phoneNumber: '+1 650-555-5555',
+            address1: '1600 Amphitheatre Parkway',
+            address2: 'Brooklyn',
+            address3: '',
+            sortingCode: '',
+            countryCode: 'US',
+            postalCode: '94043',
+            name: 'Card Holder Name',
+            locality: 'Mountain View',
+            administrativeArea: 'CA'
+        };
+
+        const billingAddress = formatGooglePayContactToAdyenAddressFormat(billingContact);
+
+        expect(billingAddress).toStrictEqual({
+            postalCode: '94043',
+            houseNumberOrName: 'ZZ',
+            street: '1600 Amphitheatre Parkway Brooklyn',
+            city: 'Mountain View',
+            country: 'US',
+            stateOrProvince: 'CA'
+        });
+    });
+
+    test('should return postal code and available fields', () => {
+        const billingContact: Partial<google.payments.api.Address> = {
+            countryCode: 'US',
+            postalCode: '94043',
+            name: 'Card Holder Name'
+        };
+
+        const billingAddress = formatGooglePayContactToAdyenAddressFormat(billingContact);
+
+        expect(billingAddress).toStrictEqual({
+            postalCode: '94043',
+            houseNumberOrName: 'ZZ',
+            country: 'US',
+            street: '',
+            city: ''
+        });
+    });
+
+    test('should return firstName and lastName if the contact is for delivery address', () => {
+        const deliveryContact: Partial<google.payments.api.Address> = {
+            phoneNumber: '+61 2 9374 4000',
+            address1: '48 Pirrama Road',
+            address2: '',
+            address3: '',
+            sortingCode: '',
+            countryCode: 'AU',
+            postalCode: '2009',
+            name: 'Australian User',
+            locality: 'Sydney',
+            administrativeArea: 'NSW'
+        };
+
+        const deliveryAddress = formatGooglePayContactToAdyenAddressFormat(deliveryContact, true);
+
+        expect(deliveryAddress).toStrictEqual({
+            postalCode: '2009',
+            country: 'AU',
+            street: '48 Pirrama Road',
+            houseNumberOrName: 'ZZ',
+            city: 'Sydney',
+            stateOrProvince: 'NSW',
+            firstName: 'Australian User'
+        });
+    });
+});
diff --git a/packages/lib/src/components/GooglePay/utils.ts b/packages/lib/src/components/GooglePay/utils.ts
index 737a75e748..8c767d96dd 100644
--- a/packages/lib/src/components/GooglePay/utils.ts
+++ b/packages/lib/src/components/GooglePay/utils.ts
@@ -1,3 +1,5 @@
+import { AddressData } from '../../types/global-types';
+
 /**
  *
  */
@@ -16,6 +18,33 @@ export function resolveEnvironment(env = 'TEST'): google.payments.api.Environmen
     }
 }
 
+/**
+ * This function formats Google Pay contact format to Adyen address format
+ *
+ * Setting 'houseNumberOrName' to ZZ won't affect the AVS check, and it will make the algorithm take the
+ * house number from the 'street' property.
+ */
+export function formatGooglePayContactToAdyenAddressFormat(
+    paymentContact?: Partial<google.payments.api.Address>,
+    isDeliveryAddress?: boolean
+): AddressData | undefined {
+    if (!paymentContact) {
+        return;
+    }
+
+    return {
+        postalCode: paymentContact.postalCode,
+        country: paymentContact.countryCode,
+        street: [paymentContact.address1, paymentContact.address2, paymentContact.address3].join(' ').trim(),
+        houseNumberOrName: 'ZZ',
+        city: paymentContact.locality || '',
+        ...(paymentContact.administrativeArea && { stateOrProvince: paymentContact.administrativeArea }),
+        ...(isDeliveryAddress && {
+            firstName: paymentContact.name
+        })
+    };
+}
+
 // export function mapBrands(brands) {
 //     const brandMapping = {
 //         mc: 'MASTERCARD',

From 0c7c16dc1fb82d239c4764e269ba6b26791e911a Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Mon, 18 Dec 2023 12:41:44 -0300
Subject: [PATCH 21/55] using onAuthorized on gpay,applepay,paypal

---
 .../lib/src/components/ApplePay/ApplePay.tsx  | 31 +++++++-
 .../src/components/ApplePay/defaultProps.ts   |  1 -
 packages/lib/src/components/ApplePay/types.ts | 18 ++++-
 packages/lib/src/components/Dropin/Dropin.tsx |  4 -
 .../src/components/GooglePay/GooglePay.tsx    | 33 ++++++--
 .../src/components/GooglePay/defaultProps.ts  |  1 -
 .../lib/src/components/GooglePay/types.ts     | 18 ++++-
 packages/lib/src/components/PayPal/Paypal.tsx | 44 ++++++++---
 .../lib/src/components/PayPal/defaultProps.ts |  1 -
 packages/lib/src/components/PayPal/types.ts   | 18 +++--
 .../utils/create-shopper-details.test.ts      | 79 -------------------
 .../PayPal/utils/create-shopper-details.ts    | 63 ---------------
 ...at-paypal-order-contact-to-adyen-format.ts | 33 ++++++++
 .../internal/UIElement/UIElement.tsx          |  9 ---
 packages/lib/src/types/global-types.ts        |  6 +-
 .../playground/src/pages/Dropin/manual.js     | 59 ++++++++------
 .../playground/src/pages/Dropin/session.js    |  9 ++-
 .../playground/src/pages/Wallets/Wallets.js   | 19 +++--
 18 files changed, 226 insertions(+), 220 deletions(-)
 delete mode 100644 packages/lib/src/components/PayPal/utils/create-shopper-details.test.ts
 delete mode 100644 packages/lib/src/components/PayPal/utils/create-shopper-details.ts
 create mode 100644 packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index 50c46a5d62..64195141f2 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -31,6 +31,7 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
         this.submit = this.submit.bind(this);
         this.validateMerchant = this.validateMerchant.bind(this);
         this.collectOrderTrackingDetailsIfNeeded = this.collectOrderTrackingDetailsIfNeeded.bind(this);
+        this.handleAuthorization = this.handleAuthorization.bind(this);
     }
 
     /**
@@ -110,7 +111,8 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                     ...(deliveryAddress && { deliveryAddress })
                 });
 
-                this.makePaymentsCall()
+                this.handleAuthorization()
+                    .then(this.makePaymentsCall)
                     .then(this.sanitizeResponse)
                     .then(this.verifyPaymentDidNotFail)
                     .then(this.collectOrderTrackingDetailsIfNeeded)
@@ -125,6 +127,8 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                         this.handleResponse(paymentResponse);
                     })
                     .catch((error: onSubmitReject) => {
+                        console.error(error);
+
                         this.setElementStatus('ready');
                         const errors = error?.error?.applePayError;
 
@@ -145,6 +149,31 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
             }));
     }
 
+    /**
+     * Call the 'onAuthorized' callback if available.
+     * Must be resolved/reject for the payment flow to continue
+     *
+     * @private
+     */
+    private async handleAuthorization(): Promise<void> {
+        return new Promise<void>((resolve, reject) => {
+            if (!this.props.onAuthorized) {
+                resolve();
+            }
+
+            const { authorizedEvent, billingAddress, deliveryAddress } = this.state;
+
+            this.props.onAuthorized(
+                {
+                    authorizedEvent,
+                    ...(billingAddress && { billingAddress }),
+                    ...(deliveryAddress && { deliveryAddress })
+                },
+                { resolve, reject }
+            );
+        });
+    }
+
     /**
      * Verify if the 'onOrderTrackingRequest' is provided. If so, triggers the callback expecting an
      * Apple Pay order details back
diff --git a/packages/lib/src/components/ApplePay/defaultProps.ts b/packages/lib/src/components/ApplePay/defaultProps.ts
index 23f59463cc..4411073d07 100644
--- a/packages/lib/src/components/ApplePay/defaultProps.ts
+++ b/packages/lib/src/components/ApplePay/defaultProps.ts
@@ -76,7 +76,6 @@ const defaultProps = {
 
     // Events
     onClick: resolve => resolve(),
-    onAuthorized: resolve => resolve(),
     onPaymentMethodSelected: null,
     onShippingContactSelected: null,
     onShippingMethodSelected: null,
diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts
index 15a4e3178b..7ce2bfdd4a 100644
--- a/packages/lib/src/components/ApplePay/types.ts
+++ b/packages/lib/src/components/ApplePay/types.ts
@@ -149,13 +149,29 @@ export interface ApplePayConfiguration extends UIElementProps {
 
     onClick?: (resolve, reject) => void;
 
+    /**
+     * Callback called when ApplePay authorize the payment.
+     * Must be resolved/rejected with the action object.
+     *
+     * @param paymentData
+     * @returns
+     */
+    onAuthorized?: (
+        data: {
+            authorizedEvent: ApplePayJS.ApplePayPaymentAuthorizedEvent;
+            billingAddress?: Partial<AddressData>;
+            deliveryAddress?: Partial<AddressData>;
+        },
+        actions: { resolve: () => void; reject: () => void }
+    ) => void;
+
     /**
      * Collect the order tracking details if available.
      * This callback is invoked when a successfull payment is resolved
      *
      * {@link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentorderdetails}
      * @param resolve - Must be called with the orderDetails fields
-     * @param reject - Must be called if something failed during the order creation
+     * @param reject - Must be called if something failed during the order creation. Calling 'reject' won't cancel the payment flow
      */
     onOrderTrackingRequest?: (resolve: (orderDetails: ApplePayPaymentOrderDetails) => void, reject: () => void) => void;
 
diff --git a/packages/lib/src/components/Dropin/Dropin.tsx b/packages/lib/src/components/Dropin/Dropin.tsx
index 286bd72053..91d318cd6a 100644
--- a/packages/lib/src/components/Dropin/Dropin.tsx
+++ b/packages/lib/src/components/Dropin/Dropin.tsx
@@ -47,10 +47,6 @@ class DropinElement extends UIElement<DropinConfiguration> {
         };
     }
 
-    public override get authorizedEvent(): any {
-        return this.dropinRef?.state?.activePaymentMethod?.authorizedEvent;
-    }
-
     get isValid() {
         return (
             !!this.dropinRef &&
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 3f47d2a566..a6fe87b032 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -20,6 +20,7 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
 
     constructor(props) {
         super(props);
+        this.handleAuthorization = this.handleAuthorization.bind(this);
 
         this.googlePay = new GooglePayService({
             ...this.props,
@@ -30,10 +31,6 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
         });
     }
 
-    /**
-     * Formats the component data input
-     * For legacy support - maps configuration.merchantIdentifier to configuration.merchantId
-     */
     formatProps(props): GooglePayConfiguration {
         // const allowedCardNetworks = props.brands?.length ? mapBrands(props.brands) : props.allowedCardNetworks;
         const buttonSizeMode = props.buttonSizeMode ?? (props.isDropin ? 'fill' : 'static');
@@ -109,7 +106,8 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
         });
 
         return new Promise<google.payments.api.PaymentAuthorizationResult>(resolve => {
-            this.makePaymentsCall()
+            this.handleAuthorization()
+                .then(this.makePaymentsCall)
                 .then(this.sanitizeResponse)
                 .then(this.verifyPaymentDidNotFail)
                 .then((paymentResponse: PaymentResponseData) => {
@@ -126,7 +124,7 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
                         transactionState: 'ERROR',
                         error: {
                             intent: error?.error?.googlePayError?.intent || 'PAYMENT_AUTHORIZATION',
-                            message: error?.error?.googlePayError?.message || 'Something went wrong',
+                            message: error?.error?.googlePayError?.message || 'Payment failed',
                             reason: error?.error?.googlePayError?.reason || 'OTHER_ERROR'
                         }
                     });
@@ -134,6 +132,29 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
         });
     };
 
+    /**
+     * Call the 'onAuthorized' callback if available.
+     * Must be resolved/reject for the payment flow to continue
+     */
+    private async handleAuthorization(): Promise<void> {
+        return new Promise<void>((resolve, reject) => {
+            if (!this.props.onAuthorized) {
+                resolve();
+            }
+
+            const { authorizedEvent, billingAddress, deliveryAddress } = this.state;
+
+            this.props.onAuthorized(
+                {
+                    authorizedEvent,
+                    ...(billingAddress && { billingAddress }),
+                    ...(deliveryAddress && { deliveryAddress })
+                },
+                { resolve, reject }
+            );
+        });
+    }
+
     /**
      * Validation
      */
diff --git a/packages/lib/src/components/GooglePay/defaultProps.ts b/packages/lib/src/components/GooglePay/defaultProps.ts
index cf4e646aaa..0c62139100 100644
--- a/packages/lib/src/components/GooglePay/defaultProps.ts
+++ b/packages/lib/src/components/GooglePay/defaultProps.ts
@@ -30,7 +30,6 @@ export default {
     totalPriceStatus: 'FINAL' as google.payments.api.TotalPriceStatus,
 
     // Callbacks
-    onAuthorized: params => params,
     onClick: resolve => resolve(),
 
     // CardParameters
diff --git a/packages/lib/src/components/GooglePay/types.ts b/packages/lib/src/components/GooglePay/types.ts
index 088910e7a0..ada06be4c4 100644
--- a/packages/lib/src/components/GooglePay/types.ts
+++ b/packages/lib/src/components/GooglePay/types.ts
@@ -1,3 +1,4 @@
+import { AddressData } from '../../types';
 import { UIElementProps } from '../internal/UIElement/types';
 
 export interface GooglePayPropsConfiguration {
@@ -149,7 +150,22 @@ export interface GooglePayConfiguration extends UIElementProps {
 
     // Events
     onClick?: (resolve, reject) => void;
-    onAuthorized?: (paymentData: google.payments.api.PaymentData) => void;
+
+    /**
+     * Callback called when GooglePay authorize the payment.
+     * Must be resolved/rejected with the action object.
+     *
+     * @param paymentData
+     * @returns
+     */
+    onAuthorized?: (
+        data: {
+            authorizedEvent: google.payments.api.PaymentData;
+            billingAddress?: Partial<AddressData>;
+            deliveryAddress?: Partial<AddressData>;
+        },
+        actions: { resolve: () => void; reject: () => void }
+    ) => void;
 }
 
 // Used to add undocumented google payment options
diff --git a/packages/lib/src/components/PayPal/Paypal.tsx b/packages/lib/src/components/PayPal/Paypal.tsx
index 55517faf0f..cff4b34629 100644
--- a/packages/lib/src/components/PayPal/Paypal.tsx
+++ b/packages/lib/src/components/PayPal/Paypal.tsx
@@ -8,8 +8,8 @@ import './Paypal.scss';
 import CoreProvider from '../../core/Context/CoreProvider';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { ERRORS } from './constants';
-import { createShopperDetails } from './utils/create-shopper-details';
 import { TxVariants } from '../tx-variants';
+import { formatPaypalOrderContatcToAdyenFormat } from './utils/format-paypal-order-contact-to-adyen-format';
 
 class PaypalElement extends UIElement<PayPalConfiguration> {
     public static type = TxVariants.paypal;
@@ -90,27 +90,47 @@ class PaypalElement extends UIElement<PayPalConfiguration> {
         return true;
     }
 
-    private handleCancel = () => {
-        this.handleError(new AdyenCheckoutError('CANCEL'));
-    };
-
     private handleOnApprove = (data: any, actions: any): Promise<void> | void => {
-        const { onShopperDetails } = this.props;
+        const { onAuthorized } = this.props;
         const state = { data: { details: data, paymentData: this.paymentData } };
 
-        if (!onShopperDetails) {
+        if (!onAuthorized) {
             this.handleAdditionalDetails(state);
             return;
         }
 
         return actions.order
             .get()
-            .then(paypalOrder => {
-                const shopperDetails = createShopperDetails(paypalOrder);
-                return new Promise<void>((resolve, reject) => onShopperDetails(shopperDetails, paypalOrder, { resolve, reject }));
+            .then((paypalOrder: any) => {
+                const billingAddress = formatPaypalOrderContatcToAdyenFormat(paypalOrder?.payer);
+                const deliveryAddress = formatPaypalOrderContatcToAdyenFormat(
+                    paypalOrder?.purchase_units?.[0].shipping,
+                    true
+                );
+
+                this.setState({
+                    authorizedEvent: paypalOrder,
+                    ...(billingAddress && { billingAddress }),
+                    ...(deliveryAddress && { deliveryAddress })
+                });
+
+                return new Promise<void>((resolve, reject) =>
+                    onAuthorized(
+                        {
+                            authorizedEvent: paypalOrder,
+                            ...(billingAddress && { billingAddress }),
+                            ...(deliveryAddress && { deliveryAddress })
+                        },
+                        { resolve, reject }
+                    )
+                );
             })
             .then(() => this.handleAdditionalDetails(state))
-            .catch(error => this.handleError(new AdyenCheckoutError('ERROR', 'Something went wrong while parsing PayPal Order', { cause: error })));
+            .catch(error =>
+                this.handleError(
+                    new AdyenCheckoutError('ERROR', 'Something went wrong while parsing PayPal Order', { cause: error })
+                )
+            );
     };
 
     handleResolve(token: string) {
@@ -142,7 +162,7 @@ class PaypalElement extends UIElement<PayPalConfiguration> {
                         this.componentRef = ref;
                     }}
                     {...this.props}
-                    onCancel={this.handleCancel}
+                    onCancel={() => this.handleError(new AdyenCheckoutError('CANCEL'))}
                     onChange={this.setState}
                     onApprove={this.handleOnApprove}
                     onError={error => {
diff --git a/packages/lib/src/components/PayPal/defaultProps.ts b/packages/lib/src/components/PayPal/defaultProps.ts
index db140fa7cb..8772547731 100644
--- a/packages/lib/src/components/PayPal/defaultProps.ts
+++ b/packages/lib/src/components/PayPal/defaultProps.ts
@@ -64,7 +64,6 @@ const defaultProps: Partial<PayPalConfiguration> = {
     // Events
     onInit: () => {},
     onClick: () => {},
-    onCancel: () => {},
     onError: () => {},
     onShippingChange: () => {}
 };
diff --git a/packages/lib/src/components/PayPal/types.ts b/packages/lib/src/components/PayPal/types.ts
index 232c5c2df7..5c4d9896d0 100644
--- a/packages/lib/src/components/PayPal/types.ts
+++ b/packages/lib/src/components/PayPal/types.ts
@@ -1,5 +1,4 @@
-import { PaymentAmount, PaymentMethod, ShopperDetails } from '../../types/global-types';
-import UIElement from '../internal/UIElement/UIElement';
+import { AddressData, PaymentAmount, PaymentMethod } from '../../types/global-types';
 import { SUPPORTED_LOCALES } from './config';
 import { UIElementProps } from '../internal/UIElement/types';
 
@@ -169,12 +168,15 @@ export interface PayPalConfig {
 }
 
 export interface PayPalConfiguration extends PayPalCommonProps, UIElementProps {
-    onSubmit?: (state: any, element: UIElement) => void;
-    onComplete?: (state, element?: UIElement) => void;
-    onAdditionalDetails?: (state: any, element: UIElement) => void;
-    onCancel?: (state: any, element: UIElement) => void;
-    onError?: (state: any, element?: UIElement) => void;
-    onShopperDetails?(shopperDetails: ShopperDetails, rawData: any, actions: { resolve: () => void; reject: () => void }): void;
+    /**
+     * Callback called when PayPal authorizes the payment.
+     * Must be resolved/rejected with the action object. If resolved, the additional details will be invoked. Otherwise it will be skipped
+     */
+    onAuthorized?: (
+        data: { authorizedEvent: any; billingAddress?: Partial<AddressData>; deliveryAddress?: Partial<AddressData> },
+        actions: { resolve: () => void; reject: () => void }
+    ) => void;
+
     paymentMethods?: PaymentMethod[];
     showPayButton?: boolean;
 }
diff --git a/packages/lib/src/components/PayPal/utils/create-shopper-details.test.ts b/packages/lib/src/components/PayPal/utils/create-shopper-details.test.ts
deleted file mode 100644
index aa83da700f..0000000000
--- a/packages/lib/src/components/PayPal/utils/create-shopper-details.test.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { createShopperDetails } from './create-shopper-details';
-import { ShopperDetails } from '../../../types';
-
-const expectedShopperDetails: ShopperDetails = {
-    shopperName: { firstName: 'Test', lastName: 'User' },
-    shopperEmail: 'shopper@example.com',
-    telephoneNumber: '7465704409',
-    countryCode: 'US',
-    billingAddress: { street: '1 Main St', stateOrProvince: 'CA', city: 'San Jose', postalCode: '95131', country: 'US' },
-    shippingAddress: { street: '6-50 Simon Carmiggeltstraat', city: 'Amsterdam', postalCode: '1011DJ', country: 'NL' }
-};
-
-const paypalOrderData = {
-    create_time: '2023-03-09T10:08:54Z',
-    id: '7KN17760594192819',
-    intent: 'AUTHORIZE',
-    status: 'APPROVED',
-    payer: {
-        email_address: 'shopper@example.com',
-        payer_id: '8QNW9UFUKS9ZC',
-        address: {
-            address_line_1: '1 Main St',
-            admin_area_2: 'San Jose',
-            admin_area_1: 'CA',
-            postal_code: '95131',
-            country_code: 'US'
-        },
-        name: {
-            given_name: 'Test',
-            surname: 'User'
-        },
-        phone: {
-            phone_type: 'HOME',
-            phone_number: {
-                national_number: '7465704409'
-            }
-        }
-    },
-    purchase_units: [
-        {
-            reference_id: 'default',
-            custom_id: 'TestMerchantCheckout:pub.v2.8115658705713940.a4Emqe6G-EueTjFogWUNHbARjs036ujUj8pgkf0Qbnw:645042:N6SWRQ7CQGNG5S82:paypal',
-            invoice_id: 'N6SWRQ7CQGNG5S82',
-            soft_descriptor: '150-checkout-component',
-            amount: {
-                value: '259.00',
-                currency_code: 'USD'
-            },
-            payee: {
-                email_address: 'seller_1306503918_biz@adyen.com',
-                merchant_id: 'QSXMR9W7GV8NY',
-                display_data: {
-                    brand_name: 'TestMerchantCheckout'
-                }
-            },
-            shipping: {
-                name: {
-                    full_name: 'Jaap Stam'
-                },
-                address: {
-                    address_line_1: '6-50 Simon Carmiggeltstraat',
-                    admin_area_2: 'Amsterdam',
-                    postal_code: '1011DJ',
-                    country_code: 'NL'
-                }
-            }
-        }
-    ]
-};
-
-test('should format Paypal Order v2 payload to Adyen ShopperDetails format ', () => {
-    expect(createShopperDetails(paypalOrderData)).toEqual(expectedShopperDetails);
-});
-
-test('should return null if Paypal order is empty', () => {
-    expect(createShopperDetails({})).toBeNull();
-    expect(createShopperDetails(null)).toBeNull();
-    expect(createShopperDetails(undefined)).toBeNull();
-});
diff --git a/packages/lib/src/components/PayPal/utils/create-shopper-details.ts b/packages/lib/src/components/PayPal/utils/create-shopper-details.ts
deleted file mode 100644
index edbd788669..0000000000
--- a/packages/lib/src/components/PayPal/utils/create-shopper-details.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { AddressData, ShopperDetails } from '../../../types/global-types';
-
-/**
- * Parses the Order data from PayPal, and create the shopper details object according to how Adyen expects
- */
-const createShopperDetails = (order: any): ShopperDetails | null => {
-    if (!order) {
-        return null;
-    }
-
-    const shopperName = {
-        firstName: order?.payer?.name?.given_name,
-        lastName: order?.payer?.name?.surname
-    };
-    const shopperEmail = order?.payer?.email_address;
-    const countryCode = order?.payer?.address?.country_code;
-    const telephoneNumber = order?.payer?.phone?.phone_number?.national_number;
-    const dateOfBirth = order?.payer?.birth_date;
-
-    const billingAddress = mapPayPalAddressToAdyenAddressFormat({
-        paypalAddressObject: order?.payer?.address
-    });
-    const shippingAddress = mapPayPalAddressToAdyenAddressFormat({
-        paypalAddressObject: order?.purchase_units?.[0].shipping?.address
-    });
-
-    const shopperDetails = {
-        ...(shopperName.firstName && { shopperName }),
-        ...(shopperEmail && { shopperEmail }),
-        ...(dateOfBirth && { dateOfBirth }),
-        ...(telephoneNumber && { telephoneNumber }),
-        ...(countryCode && { countryCode }),
-        ...(billingAddress && { billingAddress }),
-        ...(shippingAddress && { shippingAddress })
-    };
-
-    return Object.keys(shopperDetails).length > 0 ? shopperDetails : null;
-};
-
-const mapPayPalAddressToAdyenAddressFormat = ({ paypalAddressObject }): Partial<AddressData> | null => {
-    const getStreet = (addressPart1 = null, addressPart2 = null): string | null => {
-        if (addressPart1 && addressPart2) return `${addressPart1}, ${addressPart2}`;
-        if (addressPart1) return addressPart1;
-        if (addressPart2) return addressPart2;
-        return null;
-    };
-
-    if (!paypalAddressObject) return null;
-
-    const street = getStreet(paypalAddressObject.address_line_1, paypalAddressObject.address_line_2);
-
-    const address = {
-        ...(street && { street }),
-        ...(paypalAddressObject.admin_area_1 && { stateOrProvince: paypalAddressObject.admin_area_1 }),
-        ...(paypalAddressObject.admin_area_2 && { city: paypalAddressObject.admin_area_2 }),
-        ...(paypalAddressObject.postal_code && { postalCode: paypalAddressObject.postal_code }),
-        ...(paypalAddressObject.country_code && { country: paypalAddressObject.country_code })
-    };
-
-    return Object.keys(address).length > 0 ? address : null;
-};
-
-export { createShopperDetails };
diff --git a/packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts b/packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts
new file mode 100644
index 0000000000..753d3a38d7
--- /dev/null
+++ b/packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts
@@ -0,0 +1,33 @@
+import { AddressData } from '../../../types/global-types';
+
+/**
+ * This function formats PayPal contact format to Adyen address format
+ */
+export const formatPaypalOrderContatcToAdyenFormat = (
+    paymentContact: any,
+    isDeliveryAddress?: boolean
+): AddressData | null => {
+    const getStreet = (addressPart1 = null, addressPart2 = null): string | null => {
+        if (addressPart1 && addressPart2) return `${addressPart1}, ${addressPart2}`;
+        if (addressPart1) return addressPart1;
+        if (addressPart2) return addressPart2;
+        return null;
+    };
+
+    if (!paymentContact || !paymentContact.address) return null;
+
+    const { address, name } = paymentContact;
+    const street = getStreet(address.address_line_1, address.address_line_2);
+
+    return {
+        houseNumberOrName: 'ZZ',
+        ...(street && { street }),
+        ...(address.admin_area_1 && { stateOrProvince: address.admin_area_1 }),
+        ...(address.admin_area_2 && { city: address.admin_area_2 }),
+        ...(address.postal_code && { postalCode: address.postal_code }),
+        ...(address.country_code && { country: address.country_code }),
+        ...(isDeliveryAddress && {
+            firstName: name.full_name
+        })
+    };
+};
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index c4117c9300..9904cb8c98 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -92,15 +92,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         return Promise.resolve();
     }
 
-    //  ApplePayJS.ApplePayPayment | google.payments.api.PaymentData | undefined
-    /**
-     * Certain payment methods have data returned after the shopper authorization step (Ex: GooglePay, ApplePay)
-     * This getter returns the event data in case it is available
-     */
-    public get authorizedEvent(): any {
-        return this.state.authorizedEvent;
-    }
-
     public setState(newState: object): void {
         this.state = { ...this.state, ...newState };
         this.onChange();
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index a76289dbb7..97cacfbce5 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -368,7 +368,11 @@ export interface RawPaymentResponse extends PaymentResponseData {
     [key: string]: any;
 }
 
-export type ActionDescriptionType = 'qr-code-loaded' | 'polling-started' | 'fingerprint-iframe-loaded' | 'challenge-iframe-loaded';
+export type ActionDescriptionType =
+    | 'qr-code-loaded'
+    | 'polling-started'
+    | 'fingerprint-iframe-loaded'
+    | 'challenge-iframe-loaded';
 
 export interface ActionHandledReturnObject {
     componentType: string;
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 7e092aa8cf..65f3e361b1 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -38,8 +38,7 @@ export async function initManual() {
         environment: process.env.__CLIENT_ENV__,
 
         onSubmit: async (state, component, actions) => {
-            const { authorizedData } = state;
-            console.log('authorizedData', authorizedData);
+            console.log('onSubmit', state, component.authorizedEvent);
 
             try {
                 const result = await makePayment(state.data);
@@ -95,6 +94,10 @@ export async function initManual() {
             // actions.resolve({ resultCode: result.resultCode });
         },
 
+        onChange(state, element) {
+            console.log('onChange', state, element);
+        },
+
         onPaymentCompleted(result, element) {
             console.log('onPaymentCompleted', result, element);
         },
@@ -210,30 +213,40 @@ export async function initManual() {
         return Promise.resolve(true);
     }
 
-    const dropin = new Dropin({
+    const gpay = new GooglePay({
         core: checkout,
-        paymentMethodComponents: [Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay],
-        instantPaymentTypes: ['googlepay'],
-        paymentMethodsConfiguration: {
-            card: {
-                challengeWindowSize: '03',
-                enableStoreDetails: true,
-                hasHolderName: true,
-                holderNameRequired: true
-            },
-            paywithgoogle: {
-                buttonType: 'plain'
-            },
-            klarna: {
-                useKlarnaWidget: true
-            }
-            // storedCard: {
-            //     hideCVC: true
-            // }
-        }
+        shippingAddressRequired: true,
+        shippingAddressParameters: {
+            phoneNumberRequired: true
+        },
+
+        billingAddressRequired: true
     }).mount('#dropin-container');
 
+    // const dropin = new Dropin({
+    //     core: checkout,
+    //     paymentMethodComponents: [Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay],
+    //     instantPaymentTypes: ['googlepay'],
+    //     paymentMethodsConfiguration: {
+    //         card: {
+    //             challengeWindowSize: '03',
+    //             enableStoreDetails: true,
+    //             hasHolderName: true,
+    //             holderNameRequired: true
+    //         },
+    //         paywithgoogle: {
+    //             buttonType: 'plain'
+    //         },
+    //         klarna: {
+    //             useKlarnaWidget: true
+    //         }
+    //         // storedCard: {
+    //         //     hideCVC: true
+    //         // }
+    //     }
+    // }).mount('#dropin-container');
+
     handleRedirectResult();
 
-    return [checkout, dropin];
+    return [checkout, gpay];
 }
diff --git a/packages/playground/src/pages/Dropin/session.js b/packages/playground/src/pages/Dropin/session.js
index e80d853e58..f786939bc1 100644
--- a/packages/playground/src/pages/Dropin/session.js
+++ b/packages/playground/src/pages/Dropin/session.js
@@ -46,8 +46,13 @@ export async function initSession() {
         instantPaymentTypes: ['googlepay'],
         paymentMethodComponents: [Card, WeChat, Giftcard, PayPal, Ach, GooglePay],
         paymentMethodsConfiguration: {
-            paywithgoogle: {
-                buttonType: 'plain'
+            googlepay: {
+                buttonType: 'plain',
+
+                onAuthorized(data, actions) {
+                    console.log(data, actions);
+                    actions.reject();
+                }
             },
             card: {
                 hasHolderName: true,
diff --git a/packages/playground/src/pages/Wallets/Wallets.js b/packages/playground/src/pages/Wallets/Wallets.js
index 96ee37a6c5..28c95fa6bc 100644
--- a/packages/playground/src/pages/Wallets/Wallets.js
+++ b/packages/playground/src/pages/Wallets/Wallets.js
@@ -142,13 +142,11 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
     // PAYPAL
     window.paypalButtons = new PayPal({
         core: window.checkout,
-        onShopperDetails: (shopperDetails, rawData, actions) => {
-            console.log('Shopper details', shopperDetails);
-            console.log('Raw data', rawData);
+        onAuthorized(data, actions) {
+            console.log('onAuthorized', data, actions);
             actions.resolve();
         },
         onError: (error, component) => {
-            component.setStatus('ready');
             console.log('paypal onError', error);
         }
     }).mount('.paypal-field');
@@ -160,7 +158,11 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
         environment: 'TEST',
 
         // Callbacks
-        onAuthorized: console.info,
+        onAuthorized(data, actions) {
+            console.log(data, actions);
+            actions.resolve();
+        },
+
         // onError: console.error,
 
         // Payment info
@@ -175,8 +177,11 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
 
         // Shopper info (optional)
         emailRequired: true,
-        shippingAddressRequired: true,
-        shippingAddressParameters: {}, // https://developers.google.com/pay/api/web/reference/object#ShippingAddressParameters
+
+        // billingAddressRequired: true,
+
+        // shippingAddressRequired: true,
+        // shippingAddressParameters: {}, // https://developers.google.com/pay/api/web/reference/object#ShippingAddressParameters
 
         // Button config (optional)
         buttonType: 'long', // https://developers.google.com/pay/api/web/reference/object#ButtonOptions

From a4aa928d35eac3a1d134c8c9ae6ae0c5cf2ee334 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 19 Dec 2023 09:49:04 -0300
Subject: [PATCH 22/55] feat: handling failure

---
 .../lib/src/components/ApplePay/ApplePay.tsx  |   4 +-
 .../src/components/GooglePay/GooglePay.tsx    |   2 +
 .../internal/UIElement/UIElement.tsx          |  53 +++++++---
 .../components/internal/UIElement/types.ts    |  14 ++-
 .../components/internal/UIElement/utils.ts    |   2 +-
 .../lib/src/core/CheckoutSession/types.ts     |   1 +
 packages/lib/src/core/types.ts                |  17 +++
 packages/lib/src/types/global-types.ts        |  13 ++-
 packages/playground/src/handlers.js           |   8 +-
 .../playground/src/pages/Dropin/manual.js     | 100 +++++++-----------
 .../playground/src/pages/Wallets/Wallets.js   |   8 ++
 11 files changed, 127 insertions(+), 95 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index 64195141f2..f176f320fa 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -128,14 +128,14 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                     })
                     .catch((error: onSubmitReject) => {
                         console.error(error);
-
-                        this.setElementStatus('ready');
                         const errors = error?.error?.applePayError;
 
                         reject({
                             status: ApplePaySession.STATUS_FAILURE,
                             errors: errors ? (Array.isArray(errors) ? errors : [errors]) : undefined
                         });
+
+                        this.handleFailedResult(error);
                     });
             }
         });
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index a6fe87b032..9be4309ca9 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -128,6 +128,8 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
                             reason: error?.error?.googlePayError?.reason || 'OTHER_ERROR'
                         }
                     });
+
+                    this.handleFailedResult(error);
                 });
         });
     };
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 9904cb8c98..bdb66ea58d 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -119,16 +119,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
             .then(this.sanitizeResponse)
             .then(this.verifyPaymentDidNotFail)
             .then(this.handleResponse)
-            .catch((exception: OnPaymentFailedData) => {
-                // two scenarios when code reaches here:
-                // - adv flow: merchant used reject passing error back or empty object
-                // - adv flow: merchant resolved passed resultCode: Refused,Cancelled,etc
-                // - on sessions, payment failed with resultCode Refused, Cancelled, etcthis.
-                this.props.onPaymentFailed?.(exception, this.elementRef);
-                if (this.props.setStatusAutomatically) {
-                    this.setElementStatus('error');
-                }
-            });
+            .catch(this.handleFailedResult);
     }
 
     /**
@@ -176,12 +167,22 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         let paymentsResponse: CheckoutSessionPaymentResponse = null;
 
         try {
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
             paymentsResponse = await this.core.session.submitPayment(data);
         } catch (error) {
             this.handleError(error);
             return Promise.reject(error);
         }
 
+        // // Uncomment to simulate failed
+        // return {
+        //     resultCode: 'Refused',
+        //     sessionData:
+        //         'Ab02b4c0!BQABAgBKGgqfEz8uQlU4yCIOWjA8bkEwmbJ7Qt4r+x5IPXREu1rMjwNk5MDoHFNlv+MWvinS6nXIDniXgRzXCdSC4ksw9CNDBAjOa+B88wRoj/rLTieuWh/0leR88qkV24vtIkjsIsbJTDB78Pd8wX8MEDsXhaAdEIyX9E8eqxuQ3bwPbvLs1Dlgo1ZrfkQRzaNiuVM8ejRG0IWE1bGThJzY+sJvZZHvlDMXIlxhZcDoQvsMj/WwE6+nFJxBiC3oRzmvVn3AbkLQGtvwq16UUSfYbPzG9dXypJMtcrZAQYq2g/2+BSibCcmee9AXq/wij11BERrYmjbDt5NkkdUnDVgAB7pdqbnWX0A2sxBKeYtLSP2kxp+5LoU/Wty3fmcVA3VKVkHfgmIihkeL8lY++5hvHjnkzOE4tyx/sheiKS4zqoWE43TD6n8mpFskAzwMHq4G2o6vkXqvaKFEq7y/R2fVrCypenmRhkPASizpM265rKLU+L4E/C+LMHfN0LYKRMCrLr0gI2GAp+1PZLHgh0tCtiJC/zcJJtJs6sHNQxLUN+kxJuELUHOcuL3ivjG+mWteUnBENZu7KqOSZYetiWYRiyLOXDiBHqbxuQwTuO54L15VLkS/mYB20etibM1nn+fRmbo+1IJkCSalhwi5D7fSrpjbQTmAsOpJT1N8lC1MSNmAvAwG1kWL4JxYwXDKYyYASnsia2V5IjoiQUYwQUFBMTAzQ0E1MzdFQUVEODdDMjRERDUzOTA5QjgwQTc4QTkyM0UzODIzRDY4REFDQzk0QjlGRjgzMDVEQyJ98uZI4thGveOByYbomCeeP2Gy2rzs99FOBoDYVeWIUjyM+gfnW89DdJZAhxe74Tv0TnL5DRQYPCTRQPOoLbQ21NaeSho70FNE+n8XYKlVK5Ore6BoB6IVCaal5MkM27VmZPMmGflgcPx+pakx+EmRsYGdvYNImYxJYrRk3CI+l3T3ZiVpPPqebaVSLaSkEfu0iOFPjjLUhWN6QW6c18heE5vq/pcoeBf7p0Jgr9I5aBFY0avYG57BDGHzU1ZiQ9LLMTis2BA7Ap9pdNq8FVXL4fnoVHNZiiANOf3uvSknPKBID8sdOXUStA0crmO322FYjDqh1n6FG+D7+OJSayNsXIz6Zoy0eFn4HbT8nt8L2X2tdzkMayCYHXRwKh13Xyleqxt4WoEZmhwTmB3p9d1F0SylWnjcC6o/DnshJ9mMW/8D3oWS30Z7BwRODqKGVahRD0YGRzwMbVnEe5JFRfNvJZdLGl35L9632DVmuFQ0lr/8WNL/NrAJNtI6PXrZMNiza0/omPwPfe5ZYuD1Jgq59TX4h9d+3fdkArcJYL7AdoMZON1YEiWY5EzazQwtHd9yzdty9ZHPxAfuOfCh4OhbhFNp+v5YQ+PzKZ+UpM1VxV863+9XgWEURPNvX7qq1cpUSRzrSGq01QBBM3MKzRh5mAgqIdXgtl7L0EXAep0MECc7QY0/o3tW3VR8eEJGsSzrNxpFItqj0SEaIWo25dRfkl5zuw47GQrN9Qzxl2WV3A38MQPUqFtIr/71Rjkphgg49ZGWEYCwgFmm8jJc2/5qTabSGk4bzwiETCTzeydq30bUGqCwglj8CrFViAuQeTJm7dp+PYKMkUNvQRpnSXMj6Kz7rvAMzhzJgK62ltN2idqKxLC7WtivCUgejuQUvNreCYBQCaKwTwP02lZsJpGF9yw8gbyuoB+2aB7IZmgIB8GP4qVQ/ht5B9z/FLohK/8cSPV/4i32SNNdcwhV',
+        //     sessionResult:
+        //         'X3XtfGC7!H4sIAAAAAAAA/6tWykxRslJyDjaxNDMyM3E2MXIyNDUys3RU0lHKTS1KzkjMK3FMTs4vzSsBKgtJLS7xhYo6Z6QmZ+eXlgAVFpcklpQWA+WLUtNKi1NTlGoBMEEbz1cAAAA=iMsCaEJ5LcnsqIUtmNxjm8HtfQ8gZW8JewEU3wHz4qg='
+        // };
+
         return paymentsResponse;
     }
 
@@ -301,15 +302,37 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         });
     };
 
-    protected handleSuccessResult = (result: PaymentResponseData) => {
+    /**
+     * Handles when the payment fails. The payment fails when:
+     * - adv flow: the merchant rejects the payment
+     * - adv flow: the merchant resolves the payment with a failed resultCode
+     * - sessions: an error occurs during session when making the payment
+     * - sessions: the payment fail
+     *
+     * @param result
+     */
+    protected handleFailedResult = (result: OnPaymentFailedData): void => {
         if (this.props.setStatusAutomatically) {
-            this.setElementStatus('success');
+            this.setElementStatus('error');
         }
 
-        if (this.props.onPaymentCompleted) {
-            this.props.onPaymentCompleted(result, this.elementRef);
+        this.props.onPaymentFailed?.(result, this.elementRef);
+    };
+
+    protected handleSuccessResult = (result: PaymentResponseData): void => {
+        const sanitizeResult = (result: PaymentResponseData) => {
+            delete result.order;
+            delete result.action;
+            if (!result.donationToken || result.donationToken.length === 0) delete result.donationToken;
+        };
+
+        if (this.props.setStatusAutomatically) {
+            this.setElementStatus('success');
         }
-        return result;
+
+        sanitizeResult(result);
+
+        this.props.onPaymentCompleted?.(result, this.elementRef);
     };
 
     /**
diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts
index d5db40edbb..fdf087805c 100644
--- a/packages/lib/src/components/internal/UIElement/types.ts
+++ b/packages/lib/src/components/internal/UIElement/types.ts
@@ -1,7 +1,12 @@
 import { h } from 'preact';
 import Session from '../../../core/CheckoutSession';
 import UIElement from './UIElement';
-import { ActionHandledReturnObject, PaymentAction, PaymentAmount, PaymentAmountExtended } from '../../../types/global-types';
+import {
+    ActionHandledReturnObject,
+    PaymentAction,
+    PaymentAmount,
+    PaymentAmountExtended
+} from '../../../types/global-types';
 import Language from '../../../language';
 import { BaseElementProps, IBaseElement } from '../BaseElement/types';
 import { PayButtonProps } from '../PayButton/PayButton';
@@ -9,8 +14,10 @@ import { CoreConfiguration, ICore } from '../../../core/types';
 
 export type PayButtonFunctionProps = Omit<PayButtonProps, 'amount'>;
 
-// TODO add onPaymentCompleted
-type CoreCallbacks = Pick<CoreConfiguration, 'onSubmit' | 'onPaymentFailed' | 'onOrderUpdated' | 'onPaymentMethodsRequest'>;
+type CoreCallbacks = Pick<
+    CoreConfiguration,
+    'onSubmit' | 'onPaymentFailed' | 'onPaymentCompleted' | 'onOrderUpdated' | 'onPaymentMethodsRequest'
+>;
 
 export type UIElementProps = BaseElementProps &
     CoreCallbacks & {
@@ -24,7 +31,6 @@ export type UIElementProps = BaseElementProps &
         onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
         onAdditionalDetails?: (state: any, element: UIElement) => void;
         onError?: (error, element?: UIElement) => void;
-        onPaymentCompleted?: (result: any, element: UIElement) => void;
         beforeRedirect?: (resolve, reject, redirectData, element: UIElement) => void;
 
         isInstantPayment?: boolean;
diff --git a/packages/lib/src/components/internal/UIElement/utils.ts b/packages/lib/src/components/internal/UIElement/utils.ts
index 7af89b9ff8..dca466ac28 100644
--- a/packages/lib/src/components/internal/UIElement/utils.ts
+++ b/packages/lib/src/components/internal/UIElement/utils.ts
@@ -1,7 +1,7 @@
 import { UIElementStatus } from './types';
 import { RawPaymentResponse, PaymentResponseData } from '../../../types/global-types';
 
-const ALLOWED_PROPERTIES = ['action', 'resultCode', 'sessionData', 'order', 'sessionResult'];
+const ALLOWED_PROPERTIES = ['action', 'resultCode', 'sessionData', 'order', 'sessionResult', 'donationToken'];
 
 export function getSanitizedResponse(response: RawPaymentResponse): PaymentResponseData {
     const removedProperties = [];
diff --git a/packages/lib/src/core/CheckoutSession/types.ts b/packages/lib/src/core/CheckoutSession/types.ts
index 592327e534..5bfa0f40a4 100644
--- a/packages/lib/src/core/CheckoutSession/types.ts
+++ b/packages/lib/src/core/CheckoutSession/types.ts
@@ -32,6 +32,7 @@ export type CheckoutSessionSetupResponse = {
 
 export type CheckoutSessionPaymentResponse = {
     sessionData: string;
+    sessionResult: string;
     status?: string;
     resultCode: ResultCode;
     action?: PaymentAction;
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index b4d7b098d9..abb087a2ea 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -174,8 +174,25 @@ export interface CoreConfiguration {
         }
     ): Promise<void>;
 
+    /**
+     * Called when the payment succeeds.
+     *
+     * The first parameter is the sessions response (when using sessions flow), or the result code.
+     *
+     * @param data
+     * @param element
+     */
     onPaymentCompleted?(data: OnPaymentCompletedData, element?: UIElement): void;
 
+    /**
+     * Called when the payment fails.
+     *
+     * The first parameter is poppulated when merchant is using sessions, or when the payment was rejected
+     * with an object. (Ex: 'action.reject(obj)' ). Otherwise, it will be empty.
+     *
+     * @param data
+     * @param element
+     */
     onPaymentFailed?(data?: OnPaymentFailedData, element?: UIElement): void;
 
     onSubmit?(
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index 97cacfbce5..e78fc5699f 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -330,17 +330,14 @@ export type ResultCode =
     | 'RedirectShopper'
     | 'Refused';
 
-export interface OnPaymentCompletedData {
+export type SessionsResponse = {
     sessionData: string;
     sessionResult: string;
     resultCode: ResultCode;
-}
+};
+export type OnPaymentCompletedData = SessionsResponse | { resultCode: ResultCode };
 
-export type OnPaymentFailedData =
-    | OnPaymentCompletedData
-    | (onSubmitReject & {
-          resultCode: ResultCode;
-      });
+export type OnPaymentFailedData = SessionsResponse | onSubmitReject;
 
 //TODO double check these values
 export interface PaymentMethodsRequestData {
@@ -353,6 +350,7 @@ export interface PaymentResponseAdvancedFlow {
     resultCode: ResultCode;
     action?: PaymentAction;
     order?: Order;
+    donationToken?: string;
 }
 
 export interface PaymentResponseData {
@@ -362,6 +360,7 @@ export interface PaymentResponseData {
     sessionData?: string;
     sessionResult?: string;
     order?: Order;
+    donationToken?: string;
 }
 
 export interface RawPaymentResponse extends PaymentResponseData {
diff --git a/packages/playground/src/handlers.js b/packages/playground/src/handlers.js
index 71a527100b..ccaae50d34 100644
--- a/packages/playground/src/handlers.js
+++ b/packages/playground/src/handlers.js
@@ -35,7 +35,8 @@ export async function handleSubmit(state, component, actions) {
     try {
         const result = await makePayment(state.data);
 
-        // happpy flow
+        if (!result.resultCode) actions.reject();
+
         if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
             actions.reject({
                 resultCode: result.resultCode,
@@ -48,11 +49,12 @@ export async function handleSubmit(state, component, actions) {
             actions.resolve({
                 action: result.action,
                 order: result.order,
-                resultCode: result.resultCode
+                resultCode: result.resultCode,
+                donationToken: result.donationToken
             });
         }
     } catch (error) {
-        // Something failed in the request
+        console.error('## onSubmit - critical error', error);
         actions.reject();
     }
 }
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 65f3e361b1..1d881dd5e0 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -43,7 +43,8 @@ export async function initManual() {
             try {
                 const result = await makePayment(state.data);
 
-                // happpy flow
+                if (!result.resultCode) actions.reject();
+
                 if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
                     actions.reject({
                         resultCode: result.resultCode,
@@ -56,42 +57,14 @@ export async function initManual() {
                     actions.resolve({
                         action: result.action,
                         order: result.order,
-                        resultCode: result.resultCode
+                        resultCode: result.resultCode,
+                        donationToken: result.donationToken
                     });
                 }
             } catch (error) {
-                // Something failed in the request
+                console.error('## onSubmit - critical error', error);
                 actions.reject();
             }
-
-            //
-            //
-            // if (result.order && result.order?.remainingAmount?.value > 0) {
-            //     const order = {
-            //         orderData: result.order.orderData,
-            //         pspReference: result.order.pspReference
-            //     };
-            //
-            //     const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-            //
-            //     actions.resolve({
-            //         order,
-            //         paymentMethodsResponse: orderPaymentMethods
-            //     });
-            //
-            //     return;
-            // }
-
-            //
-            // // Trigger Error for GooglePay
-            // // actions.reject({
-            // //     googlePayError: {
-            // //         message: 'Not sufficient funds',
-            // //         reason: 'OTHER_ERROR,'
-            // //     }
-            // // });
-            //
-            // actions.resolve({ resultCode: result.resultCode });
         },
 
         onChange(state, element) {
@@ -104,6 +77,7 @@ export async function initManual() {
         onPaymentFailed(result, element) {
             console.log('onPaymentFailed', result, element);
         },
+
         onAdditionalDetails: async (state, component) => {
             const result = await makeDetailsCall(state.data);
 
@@ -213,40 +187,40 @@ export async function initManual() {
         return Promise.resolve(true);
     }
 
-    const gpay = new GooglePay({
-        core: checkout,
-        shippingAddressRequired: true,
-        shippingAddressParameters: {
-            phoneNumberRequired: true
-        },
-
-        billingAddressRequired: true
-    }).mount('#dropin-container');
-
-    // const dropin = new Dropin({
+    // const gpay = new GooglePay({
     //     core: checkout,
-    //     paymentMethodComponents: [Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay],
-    //     instantPaymentTypes: ['googlepay'],
-    //     paymentMethodsConfiguration: {
-    //         card: {
-    //             challengeWindowSize: '03',
-    //             enableStoreDetails: true,
-    //             hasHolderName: true,
-    //             holderNameRequired: true
-    //         },
-    //         paywithgoogle: {
-    //             buttonType: 'plain'
-    //         },
-    //         klarna: {
-    //             useKlarnaWidget: true
-    //         }
-    //         // storedCard: {
-    //         //     hideCVC: true
-    //         // }
-    //     }
+    //     shippingAddressRequired: true,
+    //     shippingAddressParameters: {
+    //         phoneNumberRequired: true
+    //     },
+
+    //     billingAddressRequired: true
     // }).mount('#dropin-container');
 
+    const dropin = new Dropin({
+        core: checkout,
+        paymentMethodComponents: [Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay],
+        instantPaymentTypes: ['googlepay'],
+        paymentMethodsConfiguration: {
+            card: {
+                challengeWindowSize: '03',
+                enableStoreDetails: true,
+                hasHolderName: true,
+                holderNameRequired: true
+            },
+            paywithgoogle: {
+                buttonType: 'plain'
+            },
+            klarna: {
+                useKlarnaWidget: true
+            }
+            // storedCard: {
+            //     hideCVC: true
+            // }
+        }
+    }).mount('#dropin-container');
+
     handleRedirectResult();
 
-    return [checkout, gpay];
+    return [checkout];
 }
diff --git a/packages/playground/src/pages/Wallets/Wallets.js b/packages/playground/src/pages/Wallets/Wallets.js
index 28c95fa6bc..d912669053 100644
--- a/packages/playground/src/pages/Wallets/Wallets.js
+++ b/packages/playground/src/pages/Wallets/Wallets.js
@@ -22,6 +22,14 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
         onError(error) {
             console.log(error);
         },
+
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        },
+
         showPayButton: true
     });
 

From 75ddaa3c1decedc4b87542a0719ee3ecb5e9ccda Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Tue, 19 Dec 2023 20:34:52 +0000
Subject: [PATCH 23/55] feat: giftcard in storybook

---
 .../helpers/create-advanced-checkout.ts       | 43 ++++++++---
 .../stories/giftcards/Gifcards.stories.tsx    | 56 ++------------
 .../stories/giftcards/GiftcardExample.tsx     | 75 +++++++++++++++++++
 yarn.lock                                     |  7 +-
 4 files changed, 117 insertions(+), 64 deletions(-)
 create mode 100644 packages/lib/storybook/stories/giftcards/GiftcardExample.tsx

diff --git a/packages/lib/storybook/helpers/create-advanced-checkout.ts b/packages/lib/storybook/helpers/create-advanced-checkout.ts
index fda07a31b4..c2f3efc25f 100644
--- a/packages/lib/storybook/helpers/create-advanced-checkout.ts
+++ b/packages/lib/storybook/helpers/create-advanced-checkout.ts
@@ -1,6 +1,6 @@
 import { AdyenCheckout } from '../../src/index';
-import { cancelOrder, checkBalance, createOrder, getPaymentMethods } from './checkout-api-calls';
-import { handleAdditionalDetails, handleChange, handleError, handleSubmit } from './checkout-handlers';
+import { cancelOrder, checkBalance, createOrder, getPaymentMethods, makePayment } from './checkout-api-calls';
+import { handleAdditionalDetails, handleChange, handleError, handleFinalState } from './checkout-handlers';
 import getCurrency from '../utils/get-currency';
 import { AdyenCheckoutProps } from '../stories/types';
 import Checkout from '../../src/core/core';
@@ -32,13 +32,34 @@ async function createAdvancedFlowCheckout({
         locale: shopperLocale,
         showPayButton,
 
-        onSubmit: (state, component) => {
-            const paymentData = {
-                amount: paymentAmount,
-                countryCode,
-                shopperLocale
-            };
-            handleSubmit(state, component, checkout, paymentData);
+        onSubmit: async (state, component, actions) => {
+            try {
+                const paymentData = {
+                    amount: paymentAmount,
+                    countryCode,
+                    shopperLocale
+                };
+
+                const result = await makePayment(state.data, paymentData);
+
+                // happpy flow
+                if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
+                    actions.reject({
+                        error: {
+                            googlePayError: {}
+                        }
+                    });
+                } else {
+                    actions.resolve({
+                        action: result.action,
+                        order: result.order,
+                        resultCode: result.resultCode
+                    });
+                }
+            } catch (error) {
+                // Something failed in the request
+                actions.reject();
+            }
         },
 
         onChange: (state, component) => {
@@ -82,6 +103,10 @@ async function createAdvancedFlowCheckout({
 
         onError: (error, component) => {
             handleError(error, component);
+        },
+
+        onPaymentCompleted: (result, component) => {
+            handleFinalState(result, component);
         }
     });
 
diff --git a/packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx b/packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx
index 49f07220f1..0e9143139a 100644
--- a/packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx
+++ b/packages/lib/storybook/stories/giftcards/Gifcards.stories.tsx
@@ -1,66 +1,24 @@
 import { Meta, StoryObj } from '@storybook/preact';
 import { PaymentMethodStoryProps } from '../types';
-import { getStoryContextCheckout } from '../../utils/get-story-context-checkout';
-import { Container } from '../Container';
-import { ANCVConfiguration } from '../../../src/components/ANCV/types';
-import Giftcard from '../../../src/components/Giftcard';
 import { GiftCardConfiguration } from '../../../src/components/Giftcard/types';
-import { makePayment } from '../../helpers/checkout-api-calls';
+import { GiftcardExample } from './GiftcardExample';
 
 type GifcardStory = StoryObj<PaymentMethodStoryProps<GiftCardConfiguration>>;
 
-const meta: Meta<PaymentMethodStoryProps<ANCVConfiguration>> = {
-    title: 'Giftcards/Generic Giftcard'
+const meta: Meta<PaymentMethodStoryProps<GiftCardConfiguration>> = {
+    title: 'Partial Payments/Givex(Giftcard) with Card'
 };
 
 export const Default: GifcardStory = {
-    render: (args, context) => {
-        const { componentConfiguration } = args;
-        const checkout = getStoryContextCheckout(context);
-        const ancv = new Giftcard({ core: checkout, ...componentConfiguration });
-        return <Container element={ancv} />;
+    render: args => {
+        return <GiftcardExample contextArgs={args} />;
     },
     args: {
         countryCode: 'NL',
-        amount: 200000,
+        amount: 20000,
         useSessions: false,
         componentConfiguration: {
-            brand: 'genericgiftcard',
-            onSubmit: async (state, element, actions) => {
-                try {
-                    const paymentData = {
-                        amount: {
-                            value: 200000,
-                            currency: 'EUR'
-                        },
-                        countryCode: 'NL',
-                        shopperLocale: 'en-GB'
-                    };
-                    const result = await makePayment(state.data, paymentData);
-
-                    // happpy flow
-                    if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
-                        actions.reject({
-                            error: {
-                                googlePayError: {}
-                            }
-                        });
-                    } else {
-                        actions.resolve({
-                            action: result.action,
-                            order: result.order,
-                            resultCode: result.resultCode
-                        });
-                    }
-                } catch (error) {
-                    // Something failed in the request
-                    actions.reject();
-                }
-            },
-            onOrderUpdated(data) {
-                // TODO render another component
-                alert(JSON.stringify(data));
-            }
+            brand: 'givex'
         }
     }
 };
diff --git a/packages/lib/storybook/stories/giftcards/GiftcardExample.tsx b/packages/lib/storybook/stories/giftcards/GiftcardExample.tsx
new file mode 100644
index 0000000000..3f20b7edd5
--- /dev/null
+++ b/packages/lib/storybook/stories/giftcards/GiftcardExample.tsx
@@ -0,0 +1,75 @@
+import { useEffect, useRef, useState } from 'preact/hooks';
+import { createSessionsCheckout } from '../../helpers/create-sessions-checkout';
+import { createAdvancedFlowCheckout } from '../../helpers/create-advanced-checkout';
+import Giftcard from '../../../src/components/Giftcard';
+import Card from '../../../src/components/Card';
+import { GiftCardConfiguration } from '../../../src/components/Giftcard/types';
+import { PaymentMethodStoryProps } from '../types';
+
+interface GiftcardExampleProps {
+    contextArgs: PaymentMethodStoryProps<GiftCardConfiguration>;
+}
+
+export const GiftcardExample = ({ contextArgs }: GiftcardExampleProps) => {
+    const container = useRef(null);
+    const checkout = useRef(null);
+    const [element, setElement] = useState(null);
+    const [errorMessage, setErrorMessage] = useState(null);
+
+    const createCheckout = async () => {
+        const { useSessions, showPayButton, countryCode, shopperLocale, amount } = contextArgs;
+
+        checkout.current = useSessions
+            ? await createSessionsCheckout({ showPayButton, countryCode, shopperLocale, amount })
+            : await createAdvancedFlowCheckout({
+                  showPayButton,
+                  countryCode,
+                  shopperLocale,
+                  amount
+              });
+
+        const onOrderUpdated = () => {
+            const card = new Card({
+                core: checkout.current,
+                _disableClickToPay: true
+            });
+            setElement(card);
+        };
+
+        const giftcardElement = new Giftcard({
+            core: checkout.current,
+            ...contextArgs.componentConfiguration,
+            onOrderUpdated: onOrderUpdated
+        });
+        setElement(giftcardElement);
+    };
+
+    useEffect(() => {
+        void createCheckout();
+    }, [contextArgs]);
+
+    useEffect(() => {
+        if (element?.isAvailable) {
+            element
+                .isAvailable()
+                .then(() => {
+                    element.mount(container.current);
+                })
+                .catch(error => {
+                    setErrorMessage(error.toString());
+                });
+        } else if (element) {
+            element.mount(container.current);
+        }
+    }, [element]);
+
+    return (
+        <div>
+            {errorMessage ? (
+                <div>{errorMessage}</div>
+            ) : (
+                <div ref={container} id="component-root" className="component-wrapper" />
+            )}
+        </div>
+    );
+};
diff --git a/yarn.lock b/yarn.lock
index cbc372f86f..8364510b8a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13233,16 +13233,11 @@ regenerate@^1.4.2:
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
   integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
 
-regenerator-runtime@^0.13.11:
+regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.9, regenerator-runtime@^0.14.0:
   version "0.13.11"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
   integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
 
-regenerator-runtime@^0.14.0:
-  version "0.14.0"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
-  integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
-
 regenerator-transform@^0.15.1:
   version "0.15.1"
   resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"

From 83dd8408e40568fbc0948876c16f7a0bd5785b71 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Wed, 20 Dec 2023 14:43:28 -0300
Subject: [PATCH 24/55] additional details part

---
 .../lib/src/components/ApplePay/ApplePay.tsx  |   3 +-
 .../src/components/GooglePay/GooglePay.tsx    |   3 +-
 .../internal/UIElement/UIElement.tsx          | 142 +++++++++---------
 .../components/internal/UIElement/types.ts    |   8 +-
 .../internal/UIElement/utils.test.ts          |   4 +-
 .../components/internal/UIElement/utils.ts    |   2 +-
 packages/lib/src/core/core.ts                 |   7 +-
 packages/lib/src/core/types.ts                |  16 +-
 .../playground/src/pages/Dropin/manual.js     |  55 ++++---
 .../playground/src/pages/Dropin/session.js    |   2 +-
 10 files changed, 138 insertions(+), 104 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index f176f320fa..a89befe775 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -18,6 +18,7 @@ import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
 import { onSubmitReject } from '../../core/types';
 import { PaymentResponseData } from '../../types/global-types';
+import { sanitizeResponse } from '../internal/UIElement/utils';
 
 const latestSupportedVersion = 14;
 
@@ -113,7 +114,7 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
 
                 this.handleAuthorization()
                     .then(this.makePaymentsCall)
-                    .then(this.sanitizeResponse)
+                    .then(sanitizeResponse)
                     .then(this.verifyPaymentDidNotFail)
                     .then(this.collectOrderTrackingDetailsIfNeeded)
                     .then(({ paymentResponse, orderDetails }) => {
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 9be4309ca9..5bad204efb 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -10,6 +10,7 @@ import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
 import { onSubmitReject } from '../../core/types';
 import { AddressData, PaymentResponseData } from '../../types/global-types';
+import { sanitizeResponse } from '../internal/UIElement/utils';
 
 class GooglePay extends UIElement<GooglePayConfiguration> {
     public static type = TxVariants.googlepay;
@@ -108,7 +109,7 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
         return new Promise<google.payments.api.PaymentAuthorizationResult>(resolve => {
             this.handleAuthorization()
                 .then(this.makePaymentsCall)
-                .then(this.sanitizeResponse)
+                .then(sanitizeResponse)
                 .then(this.verifyPaymentDidNotFail)
                 .then((paymentResponse: PaymentResponseData) => {
                     resolve({ transactionState: 'SUCCESS' });
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index bdb66ea58d..f919b9aec1 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -1,7 +1,7 @@
 import { h } from 'preact';
 import BaseElement from '../BaseElement/BaseElement';
 import PayButton from '../PayButton';
-import { getSanitizedResponse } from './utils';
+import { sanitizeResponse } from './utils';
 import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError';
 import { hasOwnProperty } from '../../../utils/hasOwnProperty';
 import { CoreConfiguration, ICore } from '../../../core/types';
@@ -9,17 +9,17 @@ import { Resources } from '../../../core/Context/Resources';
 import { NewableComponent } from '../../../core/core.registry';
 import { ComponentMethodsRef, IUIElement, PayButtonFunctionProps, UIElementProps, UIElementStatus } from './types';
 import {
+    OnPaymentFailedData,
+    Order,
     PaymentAction,
-    PaymentResponseData,
     PaymentData,
-    RawPaymentResponse,
-    PaymentResponseAdvancedFlow,
-    OnPaymentFailedData,
     PaymentMethodsResponse,
-    Order
+    PaymentResponseAdvancedFlow,
+    PaymentResponseData,
+    RawPaymentResponse
 } from '../../../types/global-types';
 import './UIElement.scss';
-import { CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
+import { CheckoutSessionDetailsResponse, CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
 
 export abstract class UIElement<P extends UIElementProps = UIElementProps>
     extends BaseElement<P>
@@ -50,12 +50,15 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         this.setState = this.setState.bind(this);
         this.onValid = this.onValid.bind(this);
         this.onComplete = this.onComplete.bind(this);
-        this.makePaymentsCall = this.makePaymentsCall.bind(this);
         this.handleAction = this.handleAction.bind(this);
         this.handleOrder = this.handleOrder.bind(this);
+        this.handleAdditionalDetails = this.handleAdditionalDetails.bind(this);
         this.handleResponse = this.handleResponse.bind(this);
         this.setElementStatus = this.setElementStatus.bind(this);
 
+        this.makePaymentsCall = this.makePaymentsCall.bind(this);
+        this.makeAdditionalDetailsCall = this.makeAdditionalDetailsCall.bind(this);
+
         this.submitUsingSessionsFlow = this.submitUsingSessionsFlow.bind(this);
 
         this.elementRef = (props && props.elementRef) || this;
@@ -97,6 +100,23 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         this.onChange();
     }
 
+    public showValidation(): this {
+        if (this.componentRef && this.componentRef.showValidation) this.componentRef.showValidation();
+        return this;
+    }
+
+    public setElementStatus(status: UIElementStatus, props?: any): this {
+        this.elementRef?.setStatus(status, props);
+        return this;
+    }
+
+    public setStatus(status: UIElementStatus, props?): this {
+        if (this.componentRef?.setStatus) {
+            this.componentRef.setStatus(status, props);
+        }
+        return this;
+    }
+
     protected onChange(): object {
         const isValid = this.isValid;
         const state = { data: this.data, errors: this.state.errors, valid: this.state.valid, isValid };
@@ -106,9 +126,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         return state;
     }
 
-    /**
-     * Submit payment method data. If the form is not valid, it will trigger validation.
-     */
     public submit(): void {
         if (!this.isValid) {
             this.showValidation();
@@ -116,15 +133,12 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         }
 
         this.makePaymentsCall()
-            .then(this.sanitizeResponse)
+            .then(sanitizeResponse)
             .then(this.verifyPaymentDidNotFail)
             .then(this.handleResponse)
             .catch(this.handleFailedResult);
     }
 
-    /**
-     * Triggers the payment flow
-     */
     protected makePaymentsCall(): Promise<PaymentResponseAdvancedFlow | CheckoutSessionPaymentResponse> {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('loading');
@@ -147,7 +161,12 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
             return beforeSubmitEvent.then(this.submitUsingSessionsFlow);
         }
 
-        this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Could not submit the payment'));
+        this.handleError(
+            new AdyenCheckoutError(
+                'IMPLEMENTATION_ERROR',
+                'Could not perform /payments call. Callback "onSubmit" is missing or Checkout session is not available'
+            )
+        );
     }
 
     private async submitUsingAdvancedFlow(): Promise<PaymentResponseAdvancedFlow> {
@@ -164,11 +183,8 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
     }
 
     private async submitUsingSessionsFlow(data: PaymentData): Promise<CheckoutSessionPaymentResponse> {
-        let paymentsResponse: CheckoutSessionPaymentResponse = null;
-
         try {
-            // eslint-disable-next-line @typescript-eslint/no-unused-vars
-            paymentsResponse = await this.core.session.submitPayment(data);
+            return await this.core.session.submitPayment(data);
         } catch (error) {
             this.handleError(error);
             return Promise.reject(error);
@@ -182,12 +198,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         //     sessionResult:
         //         'X3XtfGC7!H4sIAAAAAAAA/6tWykxRslJyDjaxNDMyM3E2MXIyNDUys3RU0lHKTS1KzkjMK3FMTs4vzSsBKgtJLS7xhYo6Z6QmZ+eXlgAVFpcklpQWA+WLUtNKi1NTlGoBMEEbz1cAAAA=iMsCaEJ5LcnsqIUtmNxjm8HtfQ8gZW8JewEU3wHz4qg='
         // };
-
-        return paymentsResponse;
-    }
-
-    protected sanitizeResponse(rawResponse: RawPaymentResponse): PaymentResponseData {
-        return getSanitizedResponse(rawResponse);
     }
 
     protected verifyPaymentDidNotFail(response: PaymentResponseData): Promise<PaymentResponseData> {
@@ -208,40 +218,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         if (this.props.onComplete) this.props.onComplete(state, this.elementRef);
     }
 
-    public showValidation(): this {
-        if (this.componentRef && this.componentRef.showValidation) this.componentRef.showValidation();
-        return this;
-    }
-
-    public setElementStatus(status: UIElementStatus, props?: any): this {
-        this.elementRef?.setStatus(status, props);
-        return this;
-    }
-
-    public setStatus(status: UIElementStatus, props?): this {
-        if (this.componentRef?.setStatus) {
-            this.componentRef.setStatus(status, props);
-        }
-        return this;
-    }
-
-    /**
-     * Submit the payment using sessions flow
-     *
-     * @param data
-     * @private
-     */
-    private submitPayment(data): Promise<void> {
-        return this.core.session
-            .submitPayment(data)
-            .then(this.handleResponse)
-            .catch(error => this.handleError(error));
-    }
-
-    private submitAdditionalDetails(data): Promise<void> {
-        return this.core.session.submitDetails(data).then(this.handleResponse).catch(this.handleError);
-    }
-
     protected handleError = (error: AdyenCheckoutError): void => {
         /**
          * Set status using elementRef, which:
@@ -255,15 +231,47 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         }
     };
 
-    protected handleAdditionalDetails = state => {
+    protected handleAdditionalDetails(state: any): void {
+        this.makeAdditionalDetailsCall(state)
+            .then(sanitizeResponse)
+            .then(this.verifyPaymentDidNotFail)
+            .then(this.handleResponse)
+            .catch(this.handleFailedResult);
+    }
+
+    private makeAdditionalDetailsCall(
+        state: any
+    ): Promise<CheckoutSessionDetailsResponse | PaymentResponseAdvancedFlow> {
+        if (this.props.setStatusAutomatically) {
+            this.setElementStatus('loading');
+        }
+
         if (this.props.onAdditionalDetails) {
-            this.props.onAdditionalDetails(state, this.elementRef);
-        } else if (this.props.session) {
-            this.submitAdditionalDetails(state.data);
+            return new Promise<PaymentResponseAdvancedFlow>((resolve, reject) => {
+                this.props.onAdditionalDetails(state, this.elementRef, { resolve, reject });
+            });
         }
 
-        return state;
-    };
+        if (this.props.session) {
+            return this.submitAdditionalDetailsUsingSessionsFlow(state.data);
+        }
+
+        this.handleError(
+            new AdyenCheckoutError(
+                'IMPLEMENTATION_ERROR',
+                'Could not perform /payments/details call. Callback "onAdditionalDetails" is missing or Checkout session is not available'
+            )
+        );
+    }
+
+    private async submitAdditionalDetailsUsingSessionsFlow(data: any): Promise<CheckoutSessionDetailsResponse> {
+        try {
+            return this.core.session.submitDetails(data);
+        } catch (error) {
+            this.handleError(error);
+            return Promise.reject(error);
+        }
+    }
 
     public handleAction(action: PaymentAction, props = {}): UIElement<P> | null {
         if (!action || !action.type) {
@@ -342,7 +350,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
      * @param rawResponse -
      */
     protected handleResponse(rawResponse: RawPaymentResponse): void {
-        const response = getSanitizedResponse(rawResponse);
+        const response = sanitizeResponse(rawResponse);
 
         if (response.action) {
             this.elementRef.handleAction(response.action);
diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts
index fdf087805c..2f57ef45fb 100644
--- a/packages/lib/src/components/internal/UIElement/types.ts
+++ b/packages/lib/src/components/internal/UIElement/types.ts
@@ -16,7 +16,12 @@ export type PayButtonFunctionProps = Omit<PayButtonProps, 'amount'>;
 
 type CoreCallbacks = Pick<
     CoreConfiguration,
-    'onSubmit' | 'onPaymentFailed' | 'onPaymentCompleted' | 'onOrderUpdated' | 'onPaymentMethodsRequest'
+    | 'onSubmit'
+    | 'onAdditionalDetails'
+    | 'onPaymentFailed'
+    | 'onPaymentCompleted'
+    | 'onOrderUpdated'
+    | 'onPaymentMethodsRequest'
 >;
 
 export type UIElementProps = BaseElementProps &
@@ -29,7 +34,6 @@ export type UIElementProps = BaseElementProps &
 
         onComplete?: (state, element: UIElement) => void;
         onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
-        onAdditionalDetails?: (state: any, element: UIElement) => void;
         onError?: (error, element?: UIElement) => void;
         beforeRedirect?: (resolve, reject, redirectData, element: UIElement) => void;
 
diff --git a/packages/lib/src/components/internal/UIElement/utils.test.ts b/packages/lib/src/components/internal/UIElement/utils.test.ts
index 48be59824d..a67c7584dd 100644
--- a/packages/lib/src/components/internal/UIElement/utils.test.ts
+++ b/packages/lib/src/components/internal/UIElement/utils.test.ts
@@ -1,4 +1,4 @@
-import { getSanitizedResponse } from './utils';
+import { sanitizeResponse } from './utils';
 
 describe('components utils', () => {
     describe('getSanitizedResponse', () => {
@@ -13,7 +13,7 @@ describe('components utils', () => {
                 sessionResult: 'XYZ123'
             };
 
-            const sanitizedResponse = getSanitizedResponse(rawResponse);
+            const sanitizedResponse = sanitizeResponse(rawResponse);
             expect(sanitizedResponse.resultCode).toBeTruthy();
             expect(sanitizedResponse.sessionResult).toBeTruthy();
             expect((sanitizedResponse as any).someBackendProperty).toBeUndefined();
diff --git a/packages/lib/src/components/internal/UIElement/utils.ts b/packages/lib/src/components/internal/UIElement/utils.ts
index dca466ac28..7f57ffab5a 100644
--- a/packages/lib/src/components/internal/UIElement/utils.ts
+++ b/packages/lib/src/components/internal/UIElement/utils.ts
@@ -3,7 +3,7 @@ import { RawPaymentResponse, PaymentResponseData } from '../../../types/global-t
 
 const ALLOWED_PROPERTIES = ['action', 'resultCode', 'sessionData', 'order', 'sessionResult', 'donationToken'];
 
-export function getSanitizedResponse(response: RawPaymentResponse): PaymentResponseData {
+export function sanitizeResponse(response: RawPaymentResponse): PaymentResponseData {
     const removedProperties = [];
 
     const sanitizedObject = Object.keys(response).reduce((acc, cur) => {
diff --git a/packages/lib/src/core/core.ts b/packages/lib/src/core/core.ts
index 7817addeaf..b3542fc246 100644
--- a/packages/lib/src/core/core.ts
+++ b/packages/lib/src/core/core.ts
@@ -110,9 +110,10 @@ class Core implements ICore {
      * @param details -
      */
     public submitDetails(details): void {
-        if (this.options.onAdditionalDetails) {
-            return this.options.onAdditionalDetails(details);
-        }
+        // TODO: Check this
+        // if (this.options.onAdditionalDetails) {
+        //     return this.options.onAdditionalDetails(details);
+        // }
 
         if (this.session) {
             this.session
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index abb087a2ea..2fc0c74f55 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -204,7 +204,21 @@ export interface CoreConfiguration {
         }
     ): void;
 
-    onAdditionalDetails?(state: any, element?: UIElement): void;
+    /**
+     * Callback used in the Advanced flow to perform the /payments/details API call.
+     *
+     * @param state
+     * @param element
+     * @param actions
+     */
+    onAdditionalDetails?(
+        state: any,
+        element: UIElement,
+        actions: {
+            resolve: (response: PaymentResponseAdvancedFlow) => void;
+            reject: (error?: onSubmitReject) => void;
+        }
+    ): void;
 
     onActionHandled?(data: ActionHandledReturnObject): void;
 
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 1d881dd5e0..baca6af945 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -47,11 +47,11 @@ export async function initManual() {
 
                 if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
                     actions.reject({
-                        resultCode: result.resultCode,
-                        error: {
-                            googlePayError: {},
-                            applePayError: {}
-                        }
+                        resultCode: result.resultCode
+                        // error: {
+                        //     googlePayError: {},
+                        //     applePayError: {}
+                        // }
                     });
                 } else {
                     actions.resolve({
@@ -78,26 +78,31 @@ export async function initManual() {
             console.log('onPaymentFailed', result, element);
         },
 
-        onAdditionalDetails: async (state, component) => {
-            const result = await makeDetailsCall(state.data);
-
-            if (result.action) {
-                component.handleAction(result.action);
-            } else if (result.order && result.order?.remainingAmount?.value > 0) {
-                // handle orders
-                const order = {
-                    orderData: result.order.orderData,
-                    pspReference: result.order.pspReference
-                };
-
-                const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-                checkout.update({
-                    paymentMethodsResponse: orderPaymentMethods,
-                    order,
-                    amount: result.order.remainingAmount
-                });
-            } else {
-                handleFinalState(result.resultCode, component);
+        onAdditionalDetails: async (state, component, actions) => {
+            try {
+                const result = await makeDetailsCall(state.data);
+
+                if (!result.resultCode) actions.reject();
+
+                if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
+                    actions.reject({
+                        resultCode: result.resultCode
+                        // error: {
+                        //     googlePayError: {},
+                        //     applePayError: {}
+                        // }
+                    });
+                } else {
+                    actions.resolve({
+                        action: result.action,
+                        order: result.order,
+                        resultCode: result.resultCode,
+                        donationToken: result.donationToken
+                    });
+                }
+            } catch (error) {
+                console.error('## onAdditionalDetails - critical error', error);
+                actions.reject();
             }
         },
         onBalanceCheck: async (resolve, reject, data) => {
diff --git a/packages/playground/src/pages/Dropin/session.js b/packages/playground/src/pages/Dropin/session.js
index f786939bc1..912f3790ca 100644
--- a/packages/playground/src/pages/Dropin/session.js
+++ b/packages/playground/src/pages/Dropin/session.js
@@ -28,7 +28,7 @@ export async function initSession() {
             actions.resolve(data);
         },
         onPaymentCompleted: (result, component) => {
-            console.info(result, component);
+            console.info('onPaymentCompleted', result, component);
         },
         onPaymentFailed(result, element) {
             console.log('onPaymentFailed', result, element);

From 689b362e781609cc3ee0be8195f29914d6caede5 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 21 Dec 2023 09:57:05 -0300
Subject: [PATCH 25/55] checkout.submitDetails fix

---
 .../lib/src/components/ApplePay/ApplePay.tsx  |  4 +-
 .../src/components/GooglePay/GooglePay.tsx    |  4 +-
 .../internal/UIElement/UIElement.tsx          | 26 ++-------
 .../components/internal/UIElement/utils.ts    | 21 +++++++
 packages/lib/src/core/core.ts                 | 57 +++++++++++++------
 packages/lib/src/core/types.ts                |  2 +-
 .../playground/src/pages/Dropin/manual.js     | 25 ++------
 .../playground/src/pages/Dropin/session.js    |  4 +-
 .../playground/src/pages/Result/Result.js     |  4 ++
 9 files changed, 83 insertions(+), 64 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index a89befe775..9c7a75156a 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -18,7 +18,7 @@ import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
 import { onSubmitReject } from '../../core/types';
 import { PaymentResponseData } from '../../types/global-types';
-import { sanitizeResponse } from '../internal/UIElement/utils';
+import { sanitizeResponse, verifyPaymentDidNotFail } from '../internal/UIElement/utils';
 
 const latestSupportedVersion = 14;
 
@@ -115,7 +115,7 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                 this.handleAuthorization()
                     .then(this.makePaymentsCall)
                     .then(sanitizeResponse)
-                    .then(this.verifyPaymentDidNotFail)
+                    .then(verifyPaymentDidNotFail)
                     .then(this.collectOrderTrackingDetailsIfNeeded)
                     .then(({ paymentResponse, orderDetails }) => {
                         resolve({
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 5bad204efb..b77b696593 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -10,7 +10,7 @@ import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
 import { onSubmitReject } from '../../core/types';
 import { AddressData, PaymentResponseData } from '../../types/global-types';
-import { sanitizeResponse } from '../internal/UIElement/utils';
+import { sanitizeResponse, verifyPaymentDidNotFail } from '../internal/UIElement/utils';
 
 class GooglePay extends UIElement<GooglePayConfiguration> {
     public static type = TxVariants.googlepay;
@@ -110,7 +110,7 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
             this.handleAuthorization()
                 .then(this.makePaymentsCall)
                 .then(sanitizeResponse)
-                .then(this.verifyPaymentDidNotFail)
+                .then(verifyPaymentDidNotFail)
                 .then((paymentResponse: PaymentResponseData) => {
                     resolve({ transactionState: 'SUCCESS' });
                     return paymentResponse;
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index f919b9aec1..5efcc788bf 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -1,7 +1,7 @@
 import { h } from 'preact';
 import BaseElement from '../BaseElement/BaseElement';
 import PayButton from '../PayButton';
-import { sanitizeResponse } from './utils';
+import { cleanupFinalResult, sanitizeResponse, verifyPaymentDidNotFail } from './utils';
 import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError';
 import { hasOwnProperty } from '../../../utils/hasOwnProperty';
 import { CoreConfiguration, ICore } from '../../../core/types';
@@ -134,7 +134,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
 
         this.makePaymentsCall()
             .then(sanitizeResponse)
-            .then(this.verifyPaymentDidNotFail)
+            .then(verifyPaymentDidNotFail)
             .then(this.handleResponse)
             .catch(this.handleFailedResult);
     }
@@ -164,7 +164,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         this.handleError(
             new AdyenCheckoutError(
                 'IMPLEMENTATION_ERROR',
-                'Could not perform /payments call. Callback "onSubmit" is missing or Checkout session is not available'
+                'It can not perform /payments call. Callback "onSubmit" is missing or Checkout session is not available'
             )
         );
     }
@@ -200,14 +200,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         // };
     }
 
-    protected verifyPaymentDidNotFail(response: PaymentResponseData): Promise<PaymentResponseData> {
-        if (['Cancelled', 'Error', 'Refused'].includes(response.resultCode)) {
-            return Promise.reject(response);
-        }
-
-        return Promise.resolve(response);
-    }
-
     private onValid() {
         const state = { data: this.data };
         if (this.props.onValid) this.props.onValid(state, this.elementRef);
@@ -234,7 +226,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
     protected handleAdditionalDetails(state: any): void {
         this.makeAdditionalDetailsCall(state)
             .then(sanitizeResponse)
-            .then(this.verifyPaymentDidNotFail)
+            .then(verifyPaymentDidNotFail)
             .then(this.handleResponse)
             .catch(this.handleFailedResult);
     }
@@ -259,7 +251,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         this.handleError(
             new AdyenCheckoutError(
                 'IMPLEMENTATION_ERROR',
-                'Could not perform /payments/details call. Callback "onAdditionalDetails" is missing or Checkout session is not available'
+                'It can not perform /payments/details call. Callback "onAdditionalDetails" is missing or Checkout session is not available'
             )
         );
     }
@@ -328,17 +320,11 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
     };
 
     protected handleSuccessResult = (result: PaymentResponseData): void => {
-        const sanitizeResult = (result: PaymentResponseData) => {
-            delete result.order;
-            delete result.action;
-            if (!result.donationToken || result.donationToken.length === 0) delete result.donationToken;
-        };
-
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('success');
         }
 
-        sanitizeResult(result);
+        cleanupFinalResult(result);
 
         this.props.onPaymentCompleted?.(result, this.elementRef);
     };
diff --git a/packages/lib/src/components/internal/UIElement/utils.ts b/packages/lib/src/components/internal/UIElement/utils.ts
index 7f57ffab5a..eb3d030570 100644
--- a/packages/lib/src/components/internal/UIElement/utils.ts
+++ b/packages/lib/src/components/internal/UIElement/utils.ts
@@ -21,6 +21,19 @@ export function sanitizeResponse(response: RawPaymentResponse): PaymentResponseD
     return sanitizedObject as PaymentResponseData;
 }
 
+/**
+ * Remove not relevant properties in the final payment result object
+ *
+ * @param paymentResponse
+ */
+export function cleanupFinalResult(paymentResponse: PaymentResponseData): void {
+    delete paymentResponse.order;
+    delete paymentResponse.action;
+    if (!paymentResponse.donationToken || paymentResponse.donationToken.length === 0) {
+        delete paymentResponse.donationToken;
+    }
+}
+
 export function resolveFinalResult(result: PaymentResponseData): [status: UIElementStatus, statusProps?: any] {
     switch (result.resultCode) {
         case 'Authorised':
@@ -35,3 +48,11 @@ export function resolveFinalResult(result: PaymentResponseData): [status: UIElem
         default:
     }
 }
+
+export function verifyPaymentDidNotFail(response: PaymentResponseData): Promise<PaymentResponseData> {
+    if (['Cancelled', 'Error', 'Refused'].includes(response.resultCode)) {
+        return Promise.reject(response);
+    }
+
+    return Promise.resolve(response);
+}
diff --git a/packages/lib/src/core/core.ts b/packages/lib/src/core/core.ts
index b3542fc246..c8d0de5a3b 100644
--- a/packages/lib/src/core/core.ts
+++ b/packages/lib/src/core/core.ts
@@ -5,7 +5,7 @@ import PaymentMethods from './ProcessResponse/PaymentMethods';
 import getComponentForAction from './ProcessResponse/PaymentAction';
 import { resolveEnvironment, resolveCDNEnvironment } from './Environment';
 import Analytics from './Analytics';
-import { PaymentAction } from '../types/global-types';
+import { OnPaymentFailedData, PaymentAction, PaymentResponseData } from '../types/global-types';
 import { CoreConfiguration, ICore } from './types';
 import { processGlobalOptions } from './utils';
 import Session from './CheckoutSession';
@@ -14,6 +14,8 @@ import { Resources } from './Context/Resources';
 import { SRPanel } from './Errors/SRPanel';
 import registry, { NewableComponent } from './core.registry';
 import { DEFAULT_LOCALE } from '../language/config';
+import { cleanupFinalResult, sanitizeResponse, verifyPaymentDidNotFail } from '../components/internal/UIElement/utils';
+import AdyenCheckoutError from './Errors/AdyenCheckoutError';
 
 class Core implements ICore {
     public session?: Session;
@@ -106,25 +108,48 @@ class Core implements ICore {
     }
 
     /**
-     * Submits details using onAdditionalDetails or the session flow if available
-     * @param details -
+     * Method used when handling redirects. It submits details using 'onAdditionalDetails' or the Sessions flow if available.
+     *
+     * @public
+     * @see {https://docs.adyen.com/online-payments/build-your-integration/?platform=Web&integration=Components&version=5.55.1#handle-the-redirect}
+     * @param details - Details object containing the redirectResult
      */
-    public submitDetails(details): void {
-        // TODO: Check this
-        // if (this.options.onAdditionalDetails) {
-        //     return this.options.onAdditionalDetails(details);
-        // }
+    public submitDetails(details: { details: { redirectResult: string } }): void {
+        let promise = null;
+
+        if (this.options.onAdditionalDetails) {
+            promise = new Promise((resolve, reject) => {
+                this.options.onAdditionalDetails({ data: details }, undefined, { resolve, reject });
+            });
+        }
 
         if (this.session) {
-            this.session
-                .submitDetails(details)
-                .then(response => {
-                    this.options.onPaymentCompleted?.(response);
-                })
-                .catch(error => {
-                    this.options.onError?.(error);
-                });
+            promise = this.session.submitDetails(details).catch(error => {
+                this.options.onError?.(error);
+                return Promise.reject(error);
+            });
         }
+
+        if (!promise) {
+            this.options.onError?.(
+                new AdyenCheckoutError(
+                    'IMPLEMENTATION_ERROR',
+                    'It can not submit the details. The callback "onAdditionalDetails" or the Session is not setup correctly.'
+                )
+            );
+            return;
+        }
+
+        promise
+            .then(sanitizeResponse)
+            .then(verifyPaymentDidNotFail)
+            .then((response: PaymentResponseData) => {
+                cleanupFinalResult(response);
+                this.options.onPaymentCompleted?.(response);
+            })
+            .catch((result: OnPaymentFailedData) => {
+                this.options.onPaymentFailed?.(result);
+            });
     }
 
     /**
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 2fc0c74f55..945c4daa64 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -208,7 +208,7 @@ export interface CoreConfiguration {
      * Callback used in the Advanced flow to perform the /payments/details API call.
      *
      * @param state
-     * @param element
+     * @param element - Component submitting details. It is undefined when using checkout.submitDetails()
      * @param actions
      */
     onAdditionalDetails?(
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index baca6af945..c21c2c4304 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -1,6 +1,7 @@
 import {
     AdyenCheckout,
     Dropin,
+    Ideal,
     Card,
     GooglePay,
     PayPal,
@@ -137,8 +138,6 @@ export async function initManual() {
     });
 
     function handleFinalState(resultCode, dropin) {
-        localStorage.removeItem('storedPaymentData');
-
         if (resultCode === 'Authorised' || resultCode === 'Received') {
             dropin.setStatus('success');
         } else {
@@ -147,26 +146,10 @@ export async function initManual() {
     }
 
     function handleRedirectResult() {
-        const storedPaymentData = localStorage.getItem('storedPaymentData');
         const { amazonCheckoutSessionId, redirectResult, payload } = getSearchParameters(window.location.search);
 
-        if (redirectResult || payload) {
-            dropin.setStatus('loading');
-            return makeDetailsCall({
-                ...(storedPaymentData && { paymentData: storedPaymentData }),
-                details: {
-                    ...(redirectResult && { redirectResult }),
-                    ...(payload && { payload })
-                }
-            }).then(result => {
-                if (result.action) {
-                    dropin.handleAction(result.action);
-                } else {
-                    handleFinalState(result.resultCode, dropin);
-                }
-
-                return true;
-            });
+        if (redirectResult) {
+            window.checkout.submitDetails({ details: { redirectResult } });
         }
 
         // Handle Amazon Pay redirect result
@@ -204,7 +187,7 @@ export async function initManual() {
 
     const dropin = new Dropin({
         core: checkout,
-        paymentMethodComponents: [Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay],
+        paymentMethodComponents: [Card, Ideal, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay],
         instantPaymentTypes: ['googlepay'],
         paymentMethodsConfiguration: {
             card: {
diff --git a/packages/playground/src/pages/Dropin/session.js b/packages/playground/src/pages/Dropin/session.js
index 912f3790ca..b71f10cb16 100644
--- a/packages/playground/src/pages/Dropin/session.js
+++ b/packages/playground/src/pages/Dropin/session.js
@@ -1,4 +1,4 @@
-import { AdyenCheckout, Dropin, Card, WeChat, Giftcard, PayPal, Ach, GooglePay } from '@adyen/adyen-web';
+import { AdyenCheckout, Dropin, Card, WeChat, Giftcard, PayPal, Ach, GooglePay, Ideal } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
 import { createSession } from '../../services';
 import { amount, shopperLocale, shopperReference, countryCode, returnUrl } from '../../config/commonConfig';
@@ -44,7 +44,7 @@ export async function initSession() {
     const dropin = new Dropin({
         core: checkout,
         instantPaymentTypes: ['googlepay'],
-        paymentMethodComponents: [Card, WeChat, Giftcard, PayPal, Ach, GooglePay],
+        paymentMethodComponents: [Card, WeChat, Giftcard, PayPal, Ach, GooglePay, Ideal],
         paymentMethodsConfiguration: {
             googlepay: {
                 buttonType: 'plain',
diff --git a/packages/playground/src/pages/Result/Result.js b/packages/playground/src/pages/Result/Result.js
index f65cc00c5b..52001c524e 100644
--- a/packages/playground/src/pages/Result/Result.js
+++ b/packages/playground/src/pages/Result/Result.js
@@ -14,6 +14,10 @@ async function handleRedirectResult(redirectResult, sessionId) {
             console.log('onPaymentCompleted', result);
             document.querySelector('#result-container > pre').innerHTML = JSON.stringify(result, null, '\t');
         },
+        onPaymentFailed: result => {
+            console.log('onPaymentFailed', result);
+            document.querySelector('#result-container > pre').innerHTML = JSON.stringify(result, null, '\t');
+        },
         onError: obj => {
             console.log('checkout level merchant defined onError handler obj=', obj);
         }

From 5879bc3b8d27ac83a220f15a79cc5f6cf1a009f1 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 21 Dec 2023 13:26:02 -0300
Subject: [PATCH 26/55] always resolving

---
 .../internal/UIElement/UIElement.tsx          |  5 +-
 packages/lib/src/core/core.ts                 |  7 +-
 packages/lib/src/core/types.ts                | 28 ++++----
 packages/lib/src/types/global-types.ts        |  9 +--
 packages/playground/src/handlers.js           | 55 ++++++++--------
 .../playground/src/pages/Dropin/manual.js     | 65 ++++++++-----------
 6 files changed, 82 insertions(+), 87 deletions(-)

diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 5efcc788bf..96b63cd0c9 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -9,7 +9,6 @@ import { Resources } from '../../../core/Context/Resources';
 import { NewableComponent } from '../../../core/core.registry';
 import { ComponentMethodsRef, IUIElement, PayButtonFunctionProps, UIElementProps, UIElementStatus } from './types';
 import {
-    OnPaymentFailedData,
     Order,
     PaymentAction,
     PaymentData,
@@ -311,11 +310,13 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
      *
      * @param result
      */
-    protected handleFailedResult = (result: OnPaymentFailedData): void => {
+    protected handleFailedResult = (result: PaymentResponseData): void => {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('error');
         }
 
+        cleanupFinalResult(result);
+
         this.props.onPaymentFailed?.(result, this.elementRef);
     };
 
diff --git a/packages/lib/src/core/core.ts b/packages/lib/src/core/core.ts
index c8d0de5a3b..ca4cbf1e6b 100644
--- a/packages/lib/src/core/core.ts
+++ b/packages/lib/src/core/core.ts
@@ -5,7 +5,7 @@ import PaymentMethods from './ProcessResponse/PaymentMethods';
 import getComponentForAction from './ProcessResponse/PaymentAction';
 import { resolveEnvironment, resolveCDNEnvironment } from './Environment';
 import Analytics from './Analytics';
-import { OnPaymentFailedData, PaymentAction, PaymentResponseData } from '../types/global-types';
+import { PaymentAction, PaymentResponseData } from '../types/global-types';
 import { CoreConfiguration, ICore } from './types';
 import { processGlobalOptions } from './utils';
 import Session from './CheckoutSession';
@@ -147,8 +147,9 @@ class Core implements ICore {
                 cleanupFinalResult(response);
                 this.options.onPaymentCompleted?.(response);
             })
-            .catch((result: OnPaymentFailedData) => {
-                this.options.onPaymentFailed?.(result);
+            .catch((response: PaymentResponseData) => {
+                cleanupFinalResult(response);
+                this.options.onPaymentFailed?.(response);
             });
     }
 
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 945c4daa64..190e6cdbd6 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -5,11 +5,11 @@ import {
     PaymentAction,
     PaymentMethodsResponse,
     ActionHandledReturnObject,
-    OnPaymentCompletedData,
     PaymentData,
     PaymentResponseAdvancedFlow,
-    OnPaymentFailedData,
-    PaymentMethodsRequestData
+    PaymentMethodsRequestData,
+    SessionsResponse,
+    ResultCode
 } from '../types/global-types';
 import { AnalyticsOptions } from './Analytics/types';
 import { RiskModuleOptions } from './RiskModule/RiskModule';
@@ -51,12 +51,12 @@ export interface ICore {
 
 export type AdyenEnvironment = 'test' | 'live' | 'live-us' | 'live-au' | 'live-apse' | 'live-in' | string;
 
-export type onSubmitReject = {
-    error?: {
-        googlePayError?: Partial<google.payments.api.PaymentDataError>;
-        applePayError?: ApplePayJS.ApplePayError[] | ApplePayJS.ApplePayError;
-    };
-};
+// export type onSubmitReject = {
+//     error?: {
+//         googlePayError?: Partial<google.payments.api.PaymentDataError>;
+//         applePayError?: ApplePayJS.ApplePayError[] | ApplePayJS.ApplePayError;
+//     };
+// };
 
 export interface CoreConfiguration {
     session?: any;
@@ -182,7 +182,7 @@ export interface CoreConfiguration {
      * @param data
      * @param element
      */
-    onPaymentCompleted?(data: OnPaymentCompletedData, element?: UIElement): void;
+    onPaymentCompleted?(data: SessionsResponse | { resultCode: ResultCode }, element?: UIElement): void;
 
     /**
      * Called when the payment fails.
@@ -193,14 +193,15 @@ export interface CoreConfiguration {
      * @param data
      * @param element
      */
-    onPaymentFailed?(data?: OnPaymentFailedData, element?: UIElement): void;
+    onPaymentFailed?(data?: SessionsResponse | { resultCode: ResultCode }, element?: UIElement): void;
 
     onSubmit?(
         state: any,
         element: UIElement,
         actions: {
             resolve: (response: PaymentResponseAdvancedFlow) => void;
-            reject: (error?: onSubmitReject) => void;
+            reject: () => void;
+            // reject: (error?: onSubmitReject) => void;
         }
     ): void;
 
@@ -216,7 +217,8 @@ export interface CoreConfiguration {
         element: UIElement,
         actions: {
             resolve: (response: PaymentResponseAdvancedFlow) => void;
-            reject: (error?: onSubmitReject) => void;
+            reject: () => void;
+            // reject: (error?: onSubmitReject) => void;
         }
     ): void;
 
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index e78fc5699f..2619a51dfa 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -1,6 +1,6 @@
 import { ADDRESS_SCHEMA } from '../components/internal/Address/constants';
 import actionTypes from '../core/ProcessResponse/PaymentAction/actionTypes';
-import { onSubmitReject } from '../core/types';
+// import { onSubmitReject } from '../core/types';
 
 export type PaymentActionsType = keyof typeof actionTypes;
 
@@ -335,9 +335,6 @@ export type SessionsResponse = {
     sessionResult: string;
     resultCode: ResultCode;
 };
-export type OnPaymentCompletedData = SessionsResponse | { resultCode: ResultCode };
-
-export type OnPaymentFailedData = SessionsResponse | onSubmitReject;
 
 //TODO double check these values
 export interface PaymentMethodsRequestData {
@@ -351,6 +348,10 @@ export interface PaymentResponseAdvancedFlow {
     action?: PaymentAction;
     order?: Order;
     donationToken?: string;
+    error?: {
+        googlePayError?: Partial<google.payments.api.PaymentDataError>;
+        applePayError?: ApplePayJS.ApplePayError[] | ApplePayJS.ApplePayError;
+    };
 }
 
 export interface PaymentResponseData {
diff --git a/packages/playground/src/handlers.js b/packages/playground/src/handlers.js
index ccaae50d34..487fee13dc 100644
--- a/packages/playground/src/handlers.js
+++ b/packages/playground/src/handlers.js
@@ -32,42 +32,43 @@ export function handleError(obj) {
 export async function handleSubmit(state, component, actions) {
     component.setStatus('loading');
 
+    console.log('onSubmit', state, actions);
+
     try {
-        const result = await makePayment(state.data);
+        const { action, order, resultCode, donationToken } = await makePayment(state.data);
 
-        if (!result.resultCode) actions.reject();
+        if (!resultCode) actions.reject();
 
-        if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
-            actions.reject({
-                resultCode: result.resultCode,
-                error: {
-                    googlePayError: {},
-                    applePayError: {}
-                }
-            });
-        } else {
-            actions.resolve({
-                action: result.action,
-                order: result.order,
-                resultCode: result.resultCode,
-                donationToken: result.donationToken
-            });
-        }
+        actions.resolve({
+            resultCode,
+            action,
+            order,
+            donationToken
+        });
     } catch (error) {
         console.error('## onSubmit - critical error', error);
         actions.reject();
     }
 }
 
-export function handleAdditionalDetails(details, component) {
-    // component.setStatus('processing');
+export async function handleAdditionalDetails(state, component, actions) {
+    try {
+        console.log('onAdditionalDetails', state, component, actions);
+
+        const { resultCode, action, order, resultCode, donationToken } = await makeDetailsCall(state.data);
 
-    return makeDetailsCall(details.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
+        if (!resultCode) actions.reject();
+
+        actions.resolve({
+            resultCode,
+            action,
+            order,
+            donationToken
+            // error: {},
         });
+        return;
+    } catch (error) {
+        console.error('## onAdditionalDetails - critical error', error);
+        actions.reject();
+    }
 }
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index c21c2c4304..aaa4e81c0f 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -42,26 +42,21 @@ export async function initManual() {
             console.log('onSubmit', state, component.authorizedEvent);
 
             try {
-                const result = await makePayment(state.data);
-
-                if (!result.resultCode) actions.reject();
-
-                if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
-                    actions.reject({
-                        resultCode: result.resultCode
-                        // error: {
-                        //     googlePayError: {},
-                        //     applePayError: {}
-                        // }
-                    });
-                } else {
-                    actions.resolve({
-                        action: result.action,
-                        order: result.order,
-                        resultCode: result.resultCode,
-                        donationToken: result.donationToken
-                    });
-                }
+                const { action, order, resultCode, donationToken } = await makePayment(state.data);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                    // error: {
+                    //              googlePayError: {},
+                    //              applePayError: {}
+                    //          }
+                    // }
+                });
             } catch (error) {
                 console.error('## onSubmit - critical error', error);
                 actions.reject();
@@ -81,26 +76,20 @@ export async function initManual() {
 
         onAdditionalDetails: async (state, component, actions) => {
             try {
-                const result = await makeDetailsCall(state.data);
+                console.log('onAdditionalDetails', state, component, actions);
 
-                if (!result.resultCode) actions.reject();
+                const { resultCode, action, order, resultCode, donationToken } = await makeDetailsCall(state.data);
 
-                if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
-                    actions.reject({
-                        resultCode: result.resultCode
-                        // error: {
-                        //     googlePayError: {},
-                        //     applePayError: {}
-                        // }
-                    });
-                } else {
-                    actions.resolve({
-                        action: result.action,
-                        order: result.order,
-                        resultCode: result.resultCode,
-                        donationToken: result.donationToken
-                    });
-                }
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                    // error: {},
+                });
+                return;
             } catch (error) {
                 console.error('## onAdditionalDetails - critical error', error);
                 actions.reject();

From ab0e7ba5e99ad6161228dfcf78dbd5f63d3d6fa4 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 21 Dec 2023 14:41:33 -0300
Subject: [PATCH 27/55] rejecting googlepay onauthorize with error

---
 .../src/components/GooglePay/GooglePay.tsx    | 32 +++++++++++++------
 .../lib/src/components/GooglePay/types.ts     |  7 ++--
 .../internal/UIElement/UIElement.tsx          | 12 +++----
 .../components/internal/UIElement/utils.ts    |  2 +-
 packages/lib/src/core/types.ts                | 16 +++++-----
 packages/lib/src/types/global-types.ts        | 13 ++++----
 .../playground/src/pages/Wallets/Wallets.js   | 13 ++++++--
 7 files changed, 58 insertions(+), 37 deletions(-)

diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index b77b696593..66d94a45c5 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -8,8 +8,7 @@ import { formatGooglePayContactToAdyenAddressFormat, getGooglePayLocale } from '
 import collectBrowserInfo from '../../utils/browserInfo';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
-import { onSubmitReject } from '../../core/types';
-import { AddressData, PaymentResponseData } from '../../types/global-types';
+import { AddressData, PaymentResponseData, RawPaymentResponse } from '../../types/global-types';
 import { sanitizeResponse, verifyPaymentDidNotFail } from '../internal/UIElement/utils';
 
 class GooglePay extends UIElement<GooglePayConfiguration> {
@@ -118,19 +117,30 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
                 .then(paymentResponse => {
                     this.handleResponse(paymentResponse);
                 })
-                .catch((error: onSubmitReject) => {
+                .catch((paymentResponse: RawPaymentResponse) => {
                     this.setElementStatus('ready');
 
+                    const googlePayError = paymentResponse.error?.googlePayError;
+
+                    const error: google.payments.api.PaymentDataError =
+                        typeof googlePayError === 'string' || undefined
+                            ? {
+                                  intent: 'PAYMENT_AUTHORIZATION',
+                                  reason: 'OTHER_ERROR',
+                                  message: (googlePayError as string) || 'Payment failed'
+                              }
+                            : {
+                                  intent: googlePayError?.intent || 'PAYMENT_AUTHORIZATION',
+                                  reason: googlePayError?.reason || 'OTHER_ERROR',
+                                  message: googlePayError?.message || 'Payment failed'
+                              };
+
                     resolve({
                         transactionState: 'ERROR',
-                        error: {
-                            intent: error?.error?.googlePayError?.intent || 'PAYMENT_AUTHORIZATION',
-                            message: error?.error?.googlePayError?.message || 'Payment failed',
-                            reason: error?.error?.googlePayError?.reason || 'OTHER_ERROR'
-                        }
+                        error
                     });
 
-                    this.handleFailedResult(error);
+                    this.handleFailedResult(paymentResponse);
                 });
         });
     };
@@ -155,6 +165,10 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
                 },
                 { resolve, reject }
             );
+        }).catch((error?: google.payments.api.PaymentDataError | string) => {
+            // Format error in a way that the 'catch' of the 'onPaymentAuthorize' block accepts it
+            const data = { error: { googlePayError: error } };
+            return Promise.reject(data);
         });
     }
 
diff --git a/packages/lib/src/components/GooglePay/types.ts b/packages/lib/src/components/GooglePay/types.ts
index ada06be4c4..33d8be26cb 100644
--- a/packages/lib/src/components/GooglePay/types.ts
+++ b/packages/lib/src/components/GooglePay/types.ts
@@ -152,11 +152,8 @@ export interface GooglePayConfiguration extends UIElementProps {
     onClick?: (resolve, reject) => void;
 
     /**
-     * Callback called when GooglePay authorize the payment.
+     * Callback called when GooglePay authorizes the payment.
      * Must be resolved/rejected with the action object.
-     *
-     * @param paymentData
-     * @returns
      */
     onAuthorized?: (
         data: {
@@ -164,7 +161,7 @@ export interface GooglePayConfiguration extends UIElementProps {
             billingAddress?: Partial<AddressData>;
             deliveryAddress?: Partial<AddressData>;
         },
-        actions: { resolve: () => void; reject: () => void }
+        actions: { resolve: () => void; reject: (error?: google.payments.api.PaymentDataError | string) => void }
     ) => void;
 }
 
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 96b63cd0c9..78b642d837 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -13,7 +13,7 @@ import {
     PaymentAction,
     PaymentData,
     PaymentMethodsResponse,
-    PaymentResponseAdvancedFlow,
+    CheckoutAdvancedFlowResponse,
     PaymentResponseData,
     RawPaymentResponse
 } from '../../../types/global-types';
@@ -138,7 +138,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
             .catch(this.handleFailedResult);
     }
 
-    protected makePaymentsCall(): Promise<PaymentResponseAdvancedFlow | CheckoutSessionPaymentResponse> {
+    protected makePaymentsCall(): Promise<CheckoutAdvancedFlowResponse | CheckoutSessionPaymentResponse> {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('loading');
         }
@@ -168,8 +168,8 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         );
     }
 
-    private async submitUsingAdvancedFlow(): Promise<PaymentResponseAdvancedFlow> {
-        return new Promise<PaymentResponseAdvancedFlow>((resolve, reject) => {
+    private async submitUsingAdvancedFlow(): Promise<CheckoutAdvancedFlowResponse> {
+        return new Promise<CheckoutAdvancedFlowResponse>((resolve, reject) => {
             this.props.onSubmit(
                 {
                     data: this.data,
@@ -232,13 +232,13 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
 
     private makeAdditionalDetailsCall(
         state: any
-    ): Promise<CheckoutSessionDetailsResponse | PaymentResponseAdvancedFlow> {
+    ): Promise<CheckoutSessionDetailsResponse | CheckoutAdvancedFlowResponse> {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('loading');
         }
 
         if (this.props.onAdditionalDetails) {
-            return new Promise<PaymentResponseAdvancedFlow>((resolve, reject) => {
+            return new Promise<CheckoutAdvancedFlowResponse>((resolve, reject) => {
                 this.props.onAdditionalDetails(state, this.elementRef, { resolve, reject });
             });
         }
diff --git a/packages/lib/src/components/internal/UIElement/utils.ts b/packages/lib/src/components/internal/UIElement/utils.ts
index eb3d030570..2466e68767 100644
--- a/packages/lib/src/components/internal/UIElement/utils.ts
+++ b/packages/lib/src/components/internal/UIElement/utils.ts
@@ -1,7 +1,7 @@
 import { UIElementStatus } from './types';
 import { RawPaymentResponse, PaymentResponseData } from '../../../types/global-types';
 
-const ALLOWED_PROPERTIES = ['action', 'resultCode', 'sessionData', 'order', 'sessionResult', 'donationToken'];
+const ALLOWED_PROPERTIES = ['action', 'resultCode', 'sessionData', 'order', 'sessionResult', 'donationToken', 'error'];
 
 export function sanitizeResponse(response: RawPaymentResponse): PaymentResponseData {
     const removedProperties = [];
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 190e6cdbd6..05066ff8d8 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -6,7 +6,7 @@ import {
     PaymentMethodsResponse,
     ActionHandledReturnObject,
     PaymentData,
-    PaymentResponseAdvancedFlow,
+    CheckoutAdvancedFlowResponse,
     PaymentMethodsRequestData,
     SessionsResponse,
     ResultCode
@@ -156,23 +156,23 @@ export interface CoreConfiguration {
     setStatusAutomatically?: boolean;
 
     beforeRedirect?(
-        resolve: PromiseResolve,
+        resolve: () => void,
         reject: PromiseReject,
         redirectData: {
             url: string;
             method: string;
             data?: any;
         }
-    ): Promise<void>;
+    ): void;
 
     beforeSubmit?(
         state: any,
         element: UIElement,
         actions: {
-            resolve: PromiseResolve;
-            reject: PromiseReject;
+            resolve: (data: any) => void;
+            reject: () => void;
         }
-    ): Promise<void>;
+    ): void;
 
     /**
      * Called when the payment succeeds.
@@ -199,7 +199,7 @@ export interface CoreConfiguration {
         state: any,
         element: UIElement,
         actions: {
-            resolve: (response: PaymentResponseAdvancedFlow) => void;
+            resolve: (response: CheckoutAdvancedFlowResponse) => void;
             reject: () => void;
             // reject: (error?: onSubmitReject) => void;
         }
@@ -216,7 +216,7 @@ export interface CoreConfiguration {
         state: any,
         element: UIElement,
         actions: {
-            resolve: (response: PaymentResponseAdvancedFlow) => void;
+            resolve: (response: CheckoutAdvancedFlowResponse) => void;
             reject: () => void;
             // reject: (error?: onSubmitReject) => void;
         }
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index 2619a51dfa..09ac0cd8e4 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -343,30 +343,31 @@ export interface PaymentMethodsRequestData {
     countryCode?: string;
 }
 
-export interface PaymentResponseAdvancedFlow {
+export interface CheckoutAdvancedFlowResponse {
     resultCode: ResultCode;
     action?: PaymentAction;
     order?: Order;
     donationToken?: string;
     error?: {
-        googlePayError?: Partial<google.payments.api.PaymentDataError>;
+        googlePayError?: google.payments.api.PaymentDataError | string;
         applePayError?: ApplePayJS.ApplePayError[] | ApplePayJS.ApplePayError;
     };
 }
 
 export interface PaymentResponseData {
+    resultCode: ResultCode;
     type?: string;
     action?: PaymentAction;
-    resultCode: ResultCode;
     sessionData?: string;
     sessionResult?: string;
     order?: Order;
     donationToken?: string;
 }
 
-export interface RawPaymentResponse extends PaymentResponseData {
-    [key: string]: any;
-}
+export type RawPaymentResponse = PaymentResponseData &
+    CheckoutAdvancedFlowResponse & {
+        [key: string]: any;
+    };
 
 export type ActionDescriptionType =
     | 'qr-code-loaded'
diff --git a/packages/playground/src/pages/Wallets/Wallets.js b/packages/playground/src/pages/Wallets/Wallets.js
index d912669053..a227c7fa94 100644
--- a/packages/playground/src/pages/Wallets/Wallets.js
+++ b/packages/playground/src/pages/Wallets/Wallets.js
@@ -167,8 +167,17 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
 
         // Callbacks
         onAuthorized(data, actions) {
-            console.log(data, actions);
-            actions.resolve();
+            console.log('onAuthorized', data, actions);
+
+            actions.reject();
+
+            // actions.reject('Failed with string');
+
+            // actions.reject({
+            //     intent: 'PAYMENT_AUTHORIZATION',
+            //     reason: 'OTHER_ERROR',
+            //     message: 'Failed with object'
+            // });
         },
 
         // onError: console.error,

From 3c930e9deb41683ba1a21ed0256196c60ff0273d Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 22 Dec 2023 10:02:53 -0300
Subject: [PATCH 28/55] applepay rejecting onauthorized

---
 .../lib/src/components/ApplePay/ApplePay.tsx  | 14 ++++++-----
 packages/lib/src/components/ApplePay/types.ts | 10 +++++---
 packages/lib/src/components/Card/Card.tsx     | 22 +++++++++++++-----
 packages/lib/src/components/Card/types.ts     |  1 +
 .../src/components/CustomCard/CustomCard.tsx  |  4 +++-
 .../components/ThreeDS2/ThreeDS2Challenge.tsx |  6 ++++-
 .../ThreeDS2/ThreeDS2DeviceFingerprint.tsx    |  8 ++++++-
 .../internal/UIElement/UIElement.tsx          |  3 ---
 .../components/internal/UIElement/types.ts    | 17 +++++---------
 packages/lib/src/core/config.ts               |  1 -
 packages/lib/src/core/core.registry.ts        |  1 +
 packages/lib/src/core/types.ts                | 23 +++----------------
 12 files changed, 57 insertions(+), 53 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index 9c7a75156a..d949d221a8 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -16,8 +16,7 @@ import {
 } from './types';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
-import { onSubmitReject } from '../../core/types';
-import { PaymentResponseData } from '../../types/global-types';
+import { PaymentResponseData, RawPaymentResponse } from '../../types/global-types';
 import { sanitizeResponse, verifyPaymentDidNotFail } from '../internal/UIElement/utils';
 
 const latestSupportedVersion = 14;
@@ -127,16 +126,15 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                     .then(paymentResponse => {
                         this.handleResponse(paymentResponse);
                     })
-                    .catch((error: onSubmitReject) => {
-                        console.error(error);
-                        const errors = error?.error?.applePayError;
+                    .catch((paymentResponse: RawPaymentResponse) => {
+                        const errors = paymentResponse?.error?.applePayError;
 
                         reject({
                             status: ApplePaySession.STATUS_FAILURE,
                             errors: errors ? (Array.isArray(errors) ? errors : [errors]) : undefined
                         });
 
-                        this.handleFailedResult(error);
+                        this.handleFailedResult(paymentResponse);
                     });
             }
         });
@@ -172,6 +170,10 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
                 },
                 { resolve, reject }
             );
+        }).catch((error?: ApplePayJS.ApplePayError) => {
+            // Format error in a way that the 'catch' of the 'onPaymentAuthorize' block accepts it
+            const data = { error: { applePayError: error } };
+            return Promise.reject(data);
         });
     }
 
diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts
index 7ce2bfdd4a..b443b4b704 100644
--- a/packages/lib/src/components/ApplePay/types.ts
+++ b/packages/lib/src/components/ApplePay/types.ts
@@ -153,8 +153,12 @@ export interface ApplePayConfiguration extends UIElementProps {
      * Callback called when ApplePay authorize the payment.
      * Must be resolved/rejected with the action object.
      *
-     * @param paymentData
-     * @returns
+     * @param data - Authorization event from ApplePay, along with formatted billingAddress and deliveryAddress
+     * @param actions - Object to continue/stop with the payment flow
+     *
+     * @remarks
+     * If actions.resolve() is called, the payment flow will be triggered.
+     * If actions.reject() is called, the overlay will display an error
      */
     onAuthorized?: (
         data: {
@@ -162,7 +166,7 @@ export interface ApplePayConfiguration extends UIElementProps {
             billingAddress?: Partial<AddressData>;
             deliveryAddress?: Partial<AddressData>;
         },
-        actions: { resolve: () => void; reject: () => void }
+        actions: { resolve: () => void; reject: (error?: ApplePayJS.ApplePayError) => void }
     ) => void;
 
     /**
diff --git a/packages/lib/src/components/Card/Card.tsx b/packages/lib/src/components/Card/Card.tsx
index cf8064da37..c425ccbc89 100644
--- a/packages/lib/src/components/Card/Card.tsx
+++ b/packages/lib/src/components/Card/Card.tsx
@@ -15,6 +15,7 @@ import SRPanelProvider from '../../core/Errors/SRPanelProvider';
 import { TxVariants } from '../tx-variants';
 import { UIElementStatus } from '../internal/UIElement/types';
 
+// @ts-ignore TODO: Check with nick
 export class CardElement extends UIElement<CardConfiguration> {
     public static type = TxVariants.scheme;
 
@@ -29,7 +30,11 @@ export class CardElement extends UIElement<CardConfiguration> {
         super(props);
 
         if (props && !props._disableClickToPay) {
-            this.clickToPayService = createClickToPayService(this.props.configuration, this.props.clickToPayConfiguration, this.props.environment);
+            this.clickToPayService = createClickToPayService(
+                this.props.configuration,
+                this.props.clickToPayConfiguration,
+                this.props.environment
+            );
             this.clickToPayService?.initialize();
         }
     }
@@ -85,8 +90,10 @@ export class CardElement extends UIElement<CardConfiguration> {
             clickToPayConfiguration: {
                 ...props.clickToPayConfiguration,
                 disableOtpAutoFocus: props.clickToPayConfiguration?.disableOtpAutoFocus || false,
-                shopperEmail: props.clickToPayConfiguration?.shopperEmail || props?.core?.options?.session?.shopperEmail,
-                telephoneNumber: props.clickToPayConfiguration?.telephoneNumber || props?.core?.options?.session?.telephoneNumber,
+                shopperEmail:
+                    props.clickToPayConfiguration?.shopperEmail || props?.core?.options?.session?.shopperEmail,
+                telephoneNumber:
+                    props.clickToPayConfiguration?.telephoneNumber || props?.core?.options?.session?.telephoneNumber,
                 locale: props.clickToPayConfiguration?.locale || props.i18n?.locale?.replace('-', '_')
             }
         };
@@ -103,7 +110,8 @@ export class CardElement extends UIElement<CardConfiguration> {
          *  - or, in the case of a storedCard
          */
         const cardBrand = this.state.selectedBrandValue;
-        const includeStorePaymentMethod = this.props.enableStoreDetails && typeof this.state.storePaymentMethod !== 'undefined';
+        const includeStorePaymentMethod =
+            this.props.enableStoreDetails && typeof this.state.storePaymentMethod !== 'undefined';
 
         return {
             paymentMethod: {
@@ -137,7 +145,8 @@ export class CardElement extends UIElement<CardConfiguration> {
     };
 
     processBinLookupResponse(binLookupResponse: BinLookupResponse, isReset = false) {
-        if (this.componentRef?.processBinLookupResponse) this.componentRef.processBinLookupResponse(binLookupResponse, isReset);
+        if (this.componentRef?.processBinLookupResponse)
+            this.componentRef.processBinLookupResponse(binLookupResponse, isReset);
         return this;
     }
 
@@ -194,7 +203,8 @@ export class CardElement extends UIElement<CardConfiguration> {
         return (
             (this.props.name || CardElement.type) +
             (this.props.storedPaymentMethodId
-                ? ' ' + this.props.i18n.get('creditCard.storedCard.description.ariaLabel').replace('%@', this.props.lastFour)
+                ? ' ' +
+                  this.props.i18n.get('creditCard.storedCard.description.ariaLabel').replace('%@', this.props.lastFour)
                 : '')
         );
     }
diff --git a/packages/lib/src/components/Card/types.ts b/packages/lib/src/components/Card/types.ts
index a9d0d63523..7dd84e28a3 100644
--- a/packages/lib/src/components/Card/types.ts
+++ b/packages/lib/src/components/Card/types.ts
@@ -17,6 +17,7 @@ import { DisclaimerMsgObject } from '../internal/DisclaimerMessage/DisclaimerMes
 import { Placeholders } from './components/CardInput/types';
 import { UIElementProps } from '../internal/UIElement/types';
 
+// @ts-ignore TODO: Check with nick
 export interface CardConfiguration extends UIElementProps {
     /**
      * Only set for a stored card,
diff --git a/packages/lib/src/components/CustomCard/CustomCard.tsx b/packages/lib/src/components/CustomCard/CustomCard.tsx
index c7c32d9c6f..14b25e4a5c 100644
--- a/packages/lib/src/components/CustomCard/CustomCard.tsx
+++ b/packages/lib/src/components/CustomCard/CustomCard.tsx
@@ -15,6 +15,7 @@ import { CustomCardConfiguration } from './types';
 // type
 // countryCode
 
+// @ts-ignore TODO: Check with nick
 export class CustomCard extends UIElement<CustomCardConfiguration> {
     public static type = TxVariants.customCard;
 
@@ -80,7 +81,8 @@ export class CustomCard extends UIElement<CustomCardConfiguration> {
         if (!nuObj.isReset) {
             // Add brandImage urls, first checking if the merchant has configured their own one for the brand
             nuObj.supportedBrandsRaw = obj.supportedBrandsRaw?.map((item: BrandObject) => {
-                item.brandImageUrl = this.props.brandsConfiguration[item.brand]?.icon ?? getCardImageUrl(item.brand, this.resources);
+                item.brandImageUrl =
+                    this.props.brandsConfiguration[item.brand]?.icon ?? getCardImageUrl(item.brand, this.resources);
                 return item;
             });
         }
diff --git a/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx b/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx
index a386f96a77..e16b956501 100644
--- a/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx
+++ b/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx
@@ -7,6 +7,7 @@ import { hasOwnProperty } from '../../utils/hasOwnProperty';
 import { TxVariants } from '../tx-variants';
 import { ThreeDS2ChallengeConfiguration } from './types';
 
+// @ts-ignore TODO: Check with nick
 class ThreeDS2Challenge extends UIElement<ThreeDS2ChallengeConfiguration> {
     public static type = TxVariants.threeDS2Challenge;
 
@@ -30,7 +31,10 @@ class ThreeDS2Challenge extends UIElement<ThreeDS2ChallengeConfiguration> {
              */
             const dataTypeForError = hasOwnProperty(this.props, 'isMDFlow') ? 'paymentData' : 'authorisationToken';
 
-            this.props.onError({ errorCode: 'threeds2.challenge', message: `No ${dataTypeForError} received. Challenge cannot proceed` });
+            this.props.onError({
+                errorCode: 'threeds2.challenge',
+                message: `No ${dataTypeForError} received. Challenge cannot proceed`
+            });
             return null;
         }
 
diff --git a/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx b/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx
index 5949051e9b..4503c2ee64 100644
--- a/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx
+++ b/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx
@@ -6,6 +6,7 @@ import { existy } from '../internal/SecuredFields/lib/utilities/commonUtils';
 import { TxVariants } from '../tx-variants';
 import { ThreeDS2DeviceFingerprintConfiguration } from './types';
 
+// @ts-ignore TODO: Check with nick
 class ThreeDS2DeviceFingerprint extends UIElement<ThreeDS2DeviceFingerprintConfiguration> {
     public static type = TxVariants.threeDS2Fingerprint;
 
@@ -38,7 +39,12 @@ class ThreeDS2DeviceFingerprint extends UIElement<ThreeDS2DeviceFingerprintConfi
          * this.props.isMDFlow indicates a threeds2InMDFlow process. It means the action to create this component came from the threeds2InMDFlow process
          * and upon completion should call the passed onComplete callback (instead of the /submitThreeDS2Fingerprint endpoint for the regular, "native" flow)
          */
-        return <PrepareFingerprint {...this.props} onComplete={this.props.isMDFlow ? this.onComplete : this.callSubmit3DS2Fingerprint} />;
+        return (
+            <PrepareFingerprint
+                {...this.props}
+                onComplete={this.props.isMDFlow ? this.onComplete : this.callSubmit3DS2Fingerprint}
+            />
+        );
     }
 }
 
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 78b642d837..5dab328a73 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -344,9 +344,6 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
             return;
         }
 
-        /**
-         * TODO: handle order properly on advanced flow.
-         */
         if (response.order?.remainingAmount?.value > 0) {
             // we don't want to call elementRef here, use the component handler
             // we do this way so the logic on handlingOrder is associated with payment method
diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts
index 2f57ef45fb..111e25d6d8 100644
--- a/packages/lib/src/components/internal/UIElement/types.ts
+++ b/packages/lib/src/components/internal/UIElement/types.ts
@@ -1,12 +1,7 @@
 import { h } from 'preact';
 import Session from '../../../core/CheckoutSession';
 import UIElement from './UIElement';
-import {
-    ActionHandledReturnObject,
-    PaymentAction,
-    PaymentAmount,
-    PaymentAmountExtended
-} from '../../../types/global-types';
+import { PaymentAction, PaymentAmount, PaymentAmountExtended } from '../../../types/global-types';
 import Language from '../../../language';
 import { BaseElementProps, IBaseElement } from '../BaseElement/types';
 import { PayButtonProps } from '../PayButton/PayButton';
@@ -16,26 +11,26 @@ export type PayButtonFunctionProps = Omit<PayButtonProps, 'amount'>;
 
 type CoreCallbacks = Pick<
     CoreConfiguration,
+    | 'beforeRedirect'
+    | 'beforeSubmit'
     | 'onSubmit'
     | 'onAdditionalDetails'
     | 'onPaymentFailed'
     | 'onPaymentCompleted'
     | 'onOrderUpdated'
     | 'onPaymentMethodsRequest'
+    | 'onChange'
+    | 'onActionHandled'
+    | 'onError'
 >;
 
 export type UIElementProps = BaseElementProps &
     CoreCallbacks & {
         environment?: string;
         session?: Session;
-        onChange?: (state: any, element: UIElement) => void;
         onValid?: (state: any, element: UIElement) => void;
-        beforeSubmit?: (state: any, element: UIElement, actions: any) => Promise<void>;
 
         onComplete?: (state, element: UIElement) => void;
-        onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
-        onError?: (error, element?: UIElement) => void;
-        beforeRedirect?: (resolve, reject, redirectData, element: UIElement) => void;
 
         isInstantPayment?: boolean;
 
diff --git a/packages/lib/src/core/config.ts b/packages/lib/src/core/config.ts
index 4b2daf6564..ad415499c4 100644
--- a/packages/lib/src/core/config.ts
+++ b/packages/lib/src/core/config.ts
@@ -25,7 +25,6 @@ export const GENERIC_OPTIONS = [
     'onSubmit',
     'onActionHandled',
     'onAdditionalDetails',
-    'onCancel',
     'onChange',
     'onError',
     'onBalanceCheck',
diff --git a/packages/lib/src/core/core.registry.ts b/packages/lib/src/core/core.registry.ts
index 49481aa11d..35b421b1bb 100644
--- a/packages/lib/src/core/core.registry.ts
+++ b/packages/lib/src/core/core.registry.ts
@@ -23,6 +23,7 @@ const defaultComponents = {
 };
 
 class Registry implements IRegistry {
+    // @ts-ignore TODO: Check with nick
     public componentsMap: Record<string, NewableComponent> = defaultComponents;
 
     public supportedTxVariants: Set<string> = new Set(Object.values(TxVariants));
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 05066ff8d8..ab25bba2bf 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -21,10 +21,6 @@ import { NewableComponent } from './core.registry';
 import Session from './CheckoutSession';
 import PaymentMethods from './ProcessResponse/PaymentMethods';
 
-type PromiseResolve = typeof Promise.resolve;
-
-type PromiseReject = typeof Promise.reject;
-
 export interface ICore {
     initialize(): Promise<ICore>;
 
@@ -51,13 +47,6 @@ export interface ICore {
 
 export type AdyenEnvironment = 'test' | 'live' | 'live-us' | 'live-au' | 'live-apse' | 'live-in' | string;
 
-// export type onSubmitReject = {
-//     error?: {
-//         googlePayError?: Partial<google.payments.api.PaymentDataError>;
-//         applePayError?: ApplePayJS.ApplePayError[] | ApplePayJS.ApplePayError;
-//     };
-// };
-
 export interface CoreConfiguration {
     session?: any;
     /**
@@ -157,7 +146,7 @@ export interface CoreConfiguration {
 
     beforeRedirect?(
         resolve: () => void,
-        reject: PromiseReject,
+        reject: () => void,
         redirectData: {
             url: string;
             method: string;
@@ -228,9 +217,9 @@ export interface CoreConfiguration {
 
     onError?(error: AdyenCheckoutError, element?: UIElement): void;
 
-    onBalanceCheck?(resolve: PromiseResolve, reject: PromiseReject, data: GiftCardElementData): Promise<void>;
+    onBalanceCheck?(resolve: () => void, reject: () => void, data: GiftCardElementData): Promise<void>;
 
-    onOrderRequest?(resolve: PromiseResolve, reject: PromiseReject, data: PaymentData): Promise<void>;
+    onOrderRequest?(resolve: () => void, reject: () => void, data: PaymentData): Promise<void>;
 
     onPaymentMethodsRequest?(
         data: PaymentMethodsRequestData,
@@ -246,12 +235,6 @@ export interface CoreConfiguration {
      */
     onOrderUpdated?(data: { order: Order }): void;
 
-    /**
-     * Used only in the Donation Component when shopper declines to donate
-     * https://docs.adyen.com/online-payments/donations/web-component
-     */
-    onCancel?(): void;
-
     /**
      * @internal
      */

From 638c0f8b6fb3cb5e6e6bbc30bb38ef4bf5de5ceb Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 16 Jan 2024 10:25:50 -0300
Subject: [PATCH 29/55] fix: current tests

---
 .../lib/src/components/Dropin/Dropin.test.ts  | 26 ++++++++++++++-----
 .../internal/UIElement/UIElement.test.ts      | 22 ++++------------
 2 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/packages/lib/src/components/Dropin/Dropin.test.ts b/packages/lib/src/components/Dropin/Dropin.test.ts
index f01661740c..e66ba07684 100644
--- a/packages/lib/src/components/Dropin/Dropin.test.ts
+++ b/packages/lib/src/components/Dropin/Dropin.test.ts
@@ -63,7 +63,9 @@ describe('Dropin', () => {
 
             dropin.handleAction(fingerprintAction);
             expect(dropin.componentFromAction instanceof ThreeDS2DeviceFingerprint).toEqual(true);
-            expect((dropin.componentFromAction as ThreeDS2DeviceFingerprint).props.showSpinner).toEqual(false);
+            expect((dropin.componentFromAction as unknown as ThreeDS2DeviceFingerprint).props.showSpinner).toEqual(
+                false
+            );
             expect(dropin.componentFromAction.props.statusType).toEqual('loading');
             expect(dropin.componentFromAction.props.isDropin).toBe(true);
         });
@@ -83,7 +85,7 @@ describe('Dropin', () => {
             expect(dropin.componentFromAction instanceof ThreeDS2Challenge).toEqual(true);
             expect(dropin.componentFromAction.props.statusType).toEqual('custom');
             expect(dropin.componentFromAction.props.isDropin).toBe(true);
-            expect((dropin.componentFromAction as ThreeDS2Challenge).props.size).toEqual('02');
+            expect((dropin.componentFromAction as unknown as ThreeDS2Challenge).props.size).toEqual('02');
         });
 
         test('new challenge action gets challengeWindowSize from paymentMethodsConfiguration', async () => {
@@ -101,12 +103,17 @@ describe('Dropin', () => {
                 analytics: { enabled: false }
             });
 
-            const dropin = new Dropin({ core: checkout, paymentMethodsConfiguration: { card: { challengeWindowSize: '02' } } });
+            const dropin = new Dropin({
+                core: checkout,
+                paymentMethodsConfiguration: { card: { challengeWindowSize: '02' } }
+            });
             jest.spyOn(dropin, 'activePaymentMethod', 'get').mockReturnValue({ props: { challengeWindowSize: '02' } });
 
             dropin.handleAction(challengeAction);
             expect(dropin.componentFromAction instanceof ThreeDS2Challenge).toEqual(true);
-            expect((dropin.componentFromAction as ThreeDS2Challenge).props.challengeWindowSize).toEqual('02');
+            expect((dropin.componentFromAction as unknown as ThreeDS2Challenge).props.challengeWindowSize).toEqual(
+                '02'
+            );
         });
 
         test('new challenge action gets challengeWindowSize from handleAction config', async () => {
@@ -124,14 +131,19 @@ describe('Dropin', () => {
                 challengeWindowSize: '03'
             });
             expect(dropin.componentFromAction instanceof ThreeDS2Challenge).toEqual(true);
-            expect((dropin.componentFromAction as ThreeDS2Challenge).props.challengeWindowSize).toEqual('03');
+            expect((dropin.componentFromAction as unknown as ThreeDS2Challenge).props.challengeWindowSize).toEqual(
+                '03'
+            );
         });
     });
 
     describe('Instant Payments feature', () => {
         test('formatProps formats instantPaymentTypes removing duplicates and invalid values', async () => {
-            // @ts-ignore Testing invalid interface
-            const dropin = new Dropin({ core: checkout, instantPaymentTypes: ['paywithgoogle', 'paywithgoogle', 'paypal', 'alipay'] });
+            const dropin = new Dropin({
+                core: checkout,
+                // @ts-ignore Valid test case
+                instantPaymentTypes: ['paywithgoogle', 'paywithgoogle', 'paypal', 'alipay']
+            });
             expect(dropin.props.instantPaymentTypes).toStrictEqual(['paywithgoogle']);
         });
     });
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.test.ts b/packages/lib/src/components/internal/UIElement/UIElement.test.ts
index 5ec1c42f7d..8218d3b110 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.test.ts
+++ b/packages/lib/src/components/internal/UIElement/UIElement.test.ts
@@ -164,7 +164,7 @@ describe('UIElement', () => {
             expect(actionComponent instanceof ThreeDS2DeviceFingerprint).toEqual(true);
 
             expect(actionComponent.props.elementRef).not.toBeDefined();
-            expect((actionComponent as ThreeDS2DeviceFingerprint).props.showSpinner).toEqual(true);
+            expect((actionComponent as unknown as ThreeDS2DeviceFingerprint).props.showSpinner).toEqual(true);
             expect(actionComponent.props.statusType).toEqual('loading');
             expect(actionComponent.props.isDropin).toBe(false);
         });
@@ -192,7 +192,7 @@ describe('UIElement', () => {
             expect(actionComponent.props.elementRef).not.toBeDefined();
             expect(actionComponent.props.statusType).toEqual('custom');
             expect(actionComponent.props.isDropin).toBe(false);
-            expect((actionComponent as ThreeDS2Challenge).props.challengeWindowSize).toEqual('02');
+            expect((actionComponent as unknown as ThreeDS2Challenge).props.challengeWindowSize).toEqual('02');
         });
 
         test('should throw Error if merchant passes the whole response object', async () => {
@@ -231,25 +231,13 @@ describe('UIElement', () => {
     });
 
     describe('submit()', () => {
-        test('should close active payment method if submit is called by instant payment method', () => {
-            const onSubmit = jest.fn();
-            const elementRef = { closeActivePaymentMethod: jest.fn(), setStatus: jest.fn() };
-            const element = new MyElement({ core, isInstantPayment: true, onSubmit, elementRef });
-
-            jest.spyOn(element, 'isValid', 'get').mockReturnValue(true);
-
-            element.submit();
-
-            expect(elementRef.closeActivePaymentMethod).toHaveBeenCalledTimes(1);
-        });
-
-        test('should trigger showValidation() and not call onSubmit() if component is not valid', () => {
+        test('should trigger showValidation() and not call makePaymentsCall() if component is not valid', () => {
             const showValidation = jest.fn();
 
             const element = new MyElement({ core: core });
 
             // @ts-ignore Checking that internal method is not reached
-            const onSubmitSpy = jest.spyOn(element, 'onSubmit');
+            const makePaymentsCallSpy = jest.spyOn(element, 'makePaymentsCall');
 
             const componentRef = {
                 showValidation
@@ -259,7 +247,7 @@ describe('UIElement', () => {
             element.submit();
 
             expect(showValidation).toBeCalledTimes(1);
-            expect(onSubmitSpy).not.toHaveBeenCalled();
+            expect(makePaymentsCallSpy).not.toHaveBeenCalled();
         });
     });
 });

From 02756efb717b9476e69e058d2b22ccf90dd58aa5 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Wed, 17 Jan 2024 07:40:52 -0300
Subject: [PATCH 30/55] tests: applepay

---
 .../src/components/ApplePay/ApplePay.test.ts  | 350 +++++++++++++++++-
 packages/lib/src/components/ApplePay/utils.ts |   2 +-
 2 files changed, 350 insertions(+), 2 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.test.ts b/packages/lib/src/components/ApplePay/ApplePay.test.ts
index 69a5605e0c..93815334ec 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.test.ts
+++ b/packages/lib/src/components/ApplePay/ApplePay.test.ts
@@ -1,6 +1,350 @@
 import ApplePay from './ApplePay';
+import ApplePayService from './ApplePayService';
+import { mock } from 'jest-mock-extended';
+
+jest.mock('./ApplePayService');
+
+beforeEach(() => {
+    // @ts-ignore 'mockClear' is provided by jest.mock
+    ApplePayService.mockClear();
+    jest.resetModules();
+    jest.resetAllMocks();
+
+    window.ApplePaySession = {
+        // @ts-ignore Mock ApplePaySession.STATUS_SUCCESS STATUS_FAILURE
+        STATUS_SUCCESS: 1,
+        STATUS_FAILURE: 0,
+        supportsVersion: () => true
+    };
+});
 
 describe('ApplePay', () => {
+    describe('submit()', () => {
+        test('should forward apple pay error (if available) to ApplePay if payment fails', async () => {
+            const onPaymentFailedMock = jest.fn();
+            const error = mock<ApplePayJS.ApplePayError>();
+            const event = mock<ApplePayJS.ApplePayPaymentAuthorizedEvent>({
+                payment: {
+                    token: {
+                        paymentData: 'payment-data'
+                    }
+                }
+            });
+
+            const applepay = new ApplePay({
+                core: global.core,
+                countryCode: 'US',
+                amount: { currency: 'EUR', value: 2000 },
+                onPaymentFailed: onPaymentFailedMock,
+                onSubmit(state, component, actions) {
+                    actions.resolve({
+                        resultCode: 'Refused',
+                        error: {
+                            applePayError: error
+                        }
+                    });
+                }
+            });
+
+            applepay.submit();
+
+            // Session initialized
+            await new Promise(process.nextTick);
+            expect(jest.spyOn(ApplePayService.prototype, 'begin')).toHaveBeenCalledTimes(1);
+
+            // Trigger ApplePayService onPaymentAuthorized property
+            // @ts-ignore ApplePayService is mocked
+            const onPaymentAuthorized = ApplePayService.mock.calls[0][1].onPaymentAuthorized;
+            const resolveMock = jest.fn();
+            const rejectMock = jest.fn();
+            onPaymentAuthorized(resolveMock, rejectMock, event);
+
+            await new Promise(process.nextTick);
+            expect(rejectMock).toHaveBeenCalledWith({
+                errors: [error],
+                status: 0
+            });
+
+            expect(onPaymentFailedMock).toHaveBeenCalledTimes(1);
+        });
+    });
+    describe('onOrderTrackingRequest()', () => {
+        test('should collect order details and pass it to Apple', async () => {
+            const event = mock<ApplePayJS.ApplePayPaymentAuthorizedEvent>({
+                payment: {
+                    token: {
+                        paymentData: 'payment-data'
+                    }
+                }
+            });
+            const onPaymentCompletedMock = jest.fn();
+            const onOrderTrackingRequestMock = jest.fn().mockImplementation(resolve => {
+                const orderDetails = {
+                    orderTypeIdentifier: 'orderTypeIdentifier',
+                    orderIdentifier: 'orderIdentifier',
+                    webServiceURL: 'webServiceURL',
+                    authenticationToken: 'authenticationToken'
+                };
+                resolve(orderDetails);
+            });
+
+            const applepay = new ApplePay({
+                core: global.core,
+                countryCode: 'US',
+                amount: { currency: 'EUR', value: 2000 },
+                onOrderTrackingRequest: onOrderTrackingRequestMock,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            jest.spyOn(applepay as any, 'makePaymentsCall').mockResolvedValue({
+                resultCode: 'Authorized'
+            });
+
+            applepay.submit();
+
+            await new Promise(process.nextTick);
+
+            // Trigger onPaymentAuthorized callback
+            // @ts-ignore ApplePayService IS mocked
+            const onPaymentAuthorized = ApplePayService.mock.calls[0][1].onPaymentAuthorized;
+            const resolveMock = jest.fn();
+            const rejectMock = jest.fn();
+            onPaymentAuthorized(resolveMock, rejectMock, event);
+
+            await new Promise(process.nextTick);
+            expect(onOrderTrackingRequestMock).toHaveBeenCalledTimes(1);
+
+            expect(resolveMock).toHaveBeenCalledWith({
+                status: 1,
+                orderDetails: {
+                    orderTypeIdentifier: 'orderTypeIdentifier',
+                    orderIdentifier: 'orderIdentifier',
+                    webServiceURL: 'webServiceURL',
+                    authenticationToken: 'authenticationToken'
+                }
+            });
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledTimes(1);
+        });
+
+        test('should continue the payment if order details is omitted when resolving', async () => {
+            const event = mock<ApplePayJS.ApplePayPaymentAuthorizedEvent>({
+                payment: {
+                    token: {
+                        paymentData: 'payment-data'
+                    }
+                }
+            });
+            const onPaymentCompletedMock = jest.fn();
+            const onOrderTrackingRequestMock = jest.fn().mockImplementation(resolve => {
+                resolve();
+            });
+
+            const applepay = new ApplePay({
+                core: global.core,
+                countryCode: 'US',
+                amount: { currency: 'EUR', value: 2000 },
+                onOrderTrackingRequest: onOrderTrackingRequestMock,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            jest.spyOn(applepay as any, 'makePaymentsCall').mockResolvedValue({
+                resultCode: 'Authorized'
+            });
+
+            applepay.submit();
+
+            await new Promise(process.nextTick);
+
+            // Trigger onPaymentAuthorized callback
+            // @ts-ignore ApplePayService IS mocked
+            const onPaymentAuthorized = ApplePayService.mock.calls[0][1].onPaymentAuthorized;
+            const resolveMock = jest.fn();
+            const rejectMock = jest.fn();
+            onPaymentAuthorized(resolveMock, rejectMock, event);
+
+            await new Promise(process.nextTick);
+            expect(onOrderTrackingRequestMock).toHaveBeenCalledTimes(1);
+
+            expect(resolveMock).toHaveBeenCalledWith({
+                status: 1
+            });
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledTimes(1);
+        });
+
+        test('should continue the payment if something goes wrong and order details callback is rejected', async () => {
+            const event = mock<ApplePayJS.ApplePayPaymentAuthorizedEvent>({
+                payment: {
+                    token: {
+                        paymentData: 'payment-data'
+                    }
+                }
+            });
+            const onPaymentCompletedMock = jest.fn();
+            const onOrderTrackingRequestMock = jest.fn().mockImplementation((resolve, reject) => {
+                reject();
+            });
+
+            const applepay = new ApplePay({
+                core: global.core,
+                countryCode: 'US',
+                amount: { currency: 'EUR', value: 2000 },
+                onOrderTrackingRequest: onOrderTrackingRequestMock,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            jest.spyOn(applepay as any, 'makePaymentsCall').mockResolvedValue({
+                resultCode: 'Authorized'
+            });
+
+            applepay.submit();
+
+            await new Promise(process.nextTick);
+
+            // Trigger onPaymentAuthorized callback
+            // @ts-ignore ApplePayService IS mocked
+            const onPaymentAuthorized = ApplePayService.mock.calls[0][1].onPaymentAuthorized;
+            const resolveMock = jest.fn();
+            const rejectMock = jest.fn();
+            onPaymentAuthorized(resolveMock, rejectMock, event);
+
+            await new Promise(process.nextTick);
+            expect(onOrderTrackingRequestMock).toHaveBeenCalledTimes(1);
+
+            expect(resolveMock).toHaveBeenCalledWith({
+                status: 1
+            });
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledTimes(1);
+        });
+    });
+
+    describe('onAuthorized()', () => {
+        test('should provide event and formatted data, then reject payment', async () => {
+            const event = mock<ApplePayJS.ApplePayPaymentAuthorizedEvent>({
+                payment: {
+                    billingContact: {
+                        addressLines: ['802 Richardon Drive', 'Brooklyn'],
+                        locality: 'New York',
+                        administrativeArea: 'NY',
+                        postalCode: '11213',
+                        countryCode: 'US',
+                        country: 'United States',
+                        givenName: 'Jonny',
+                        familyName: 'Smithson',
+                        phoneticFamilyName: '',
+                        phoneticGivenName: '',
+                        subAdministrativeArea: '',
+                        subLocality: ''
+                    },
+                    shippingContact: {
+                        addressLines: ['1 Infinite Loop', 'Unit 100'],
+                        locality: 'Cupertino',
+                        administrativeArea: 'CA',
+                        postalCode: '95014',
+                        countryCode: 'US',
+                        country: 'United States',
+                        givenName: 'John',
+                        familyName: 'Appleseed',
+                        phoneticFamilyName: '',
+                        phoneticGivenName: '',
+                        subAdministrativeArea: '',
+                        subLocality: ''
+                    },
+                    token: {
+                        paymentData: 'payment-data'
+                    }
+                }
+            });
+
+            const onPaymentFailedMock = jest.fn();
+            const onChangeMock = jest.fn();
+            const onAuthorizedMock = jest.fn().mockImplementation((_data, actions) => {
+                actions.reject();
+            });
+
+            const applepay = new ApplePay({
+                core: global.core,
+                countryCode: 'US',
+                amount: { currency: 'EUR', value: 2000 },
+                onAuthorized: onAuthorizedMock,
+                onChange: onChangeMock,
+                onPaymentFailed: onPaymentFailedMock
+            });
+
+            applepay.submit();
+
+            // Session initialized
+            await new Promise(process.nextTick);
+            expect(jest.spyOn(ApplePayService.prototype, 'begin')).toHaveBeenCalledTimes(1);
+
+            // Trigger onPaymentAuthorized callback
+            // @ts-ignore ApplePayService IS mocked
+            const onPaymentAuthorized = ApplePayService.mock.calls[0][1].onPaymentAuthorized;
+            const resolveMock = jest.fn();
+            const rejectMock = jest.fn();
+            onPaymentAuthorized(resolveMock, rejectMock, event);
+
+            expect(onChangeMock).toHaveBeenCalledTimes(1);
+            expect(onChangeMock.mock.calls[0][0].data).toStrictEqual({
+                billingAddress: {
+                    city: 'New York',
+                    country: 'US',
+                    houseNumberOrName: 'ZZ',
+                    postalCode: '11213',
+                    stateOrProvince: 'NY',
+                    street: '802 Richardon Drive Brooklyn'
+                },
+                clientStateDataIndicator: true,
+                deliveryAddress: {
+                    city: 'Cupertino',
+                    country: 'US',
+                    firstName: 'John',
+                    houseNumberOrName: 'ZZ',
+                    lastName: 'Appleseed',
+                    postalCode: '95014',
+                    stateOrProvince: 'CA',
+                    street: '1 Infinite Loop Unit 100'
+                },
+                paymentMethod: {
+                    applePayToken: 'InBheW1lbnQtZGF0YSI=',
+                    checkoutAttemptId: 'do-not-track',
+                    type: 'applepay'
+                }
+            });
+
+            const data = onAuthorizedMock.mock.calls[0][0];
+            expect(data.authorizedEvent).toBe(event);
+            expect(data.billingAddress).toStrictEqual({
+                city: 'New York',
+                country: 'US',
+                houseNumberOrName: 'ZZ',
+                postalCode: '11213',
+                stateOrProvince: 'NY',
+                street: '802 Richardon Drive Brooklyn'
+            });
+            expect(data.deliveryAddress).toStrictEqual({
+                city: 'Cupertino',
+                country: 'US',
+                firstName: 'John',
+                houseNumberOrName: 'ZZ',
+                lastName: 'Appleseed',
+                postalCode: '95014',
+                stateOrProvince: 'CA',
+                street: '1 Infinite Loop Unit 100'
+            });
+
+            await new Promise(process.nextTick);
+            expect(rejectMock).toHaveBeenCalledWith({
+                errors: undefined,
+                status: 0
+            });
+
+            expect(onPaymentFailedMock).toHaveBeenCalledTimes(1);
+        });
+    });
+
     describe('formatProps', () => {
         test('accepts an amount in a regular format', () => {
             const applepay = new ApplePay({
@@ -19,7 +363,11 @@ describe('ApplePay', () => {
         });
 
         test('uses merchantName if no totalPriceLabel was defined', () => {
-            const applepay = new ApplePay({ core: global.core, countryCode: 'US', configuration: { merchantName: 'Test' } });
+            const applepay = new ApplePay({
+                core: global.core,
+                countryCode: 'US',
+                configuration: { merchantName: 'Test' }
+            });
             expect(applepay.props.totalPriceLabel).toEqual('Test');
         });
 
diff --git a/packages/lib/src/components/ApplePay/utils.ts b/packages/lib/src/components/ApplePay/utils.ts
index 71208aa9a0..85532d8dec 100644
--- a/packages/lib/src/components/ApplePay/utils.ts
+++ b/packages/lib/src/components/ApplePay/utils.ts
@@ -58,7 +58,7 @@ export function formatApplePayContactToAdyenAddressFormat(
         country: paymentContact.countryCode,
         houseNumberOrName: 'ZZ',
         postalCode: paymentContact.postalCode,
-        street: paymentContact.addressLines.join(' ').trim(),
+        street: paymentContact.addressLines?.join(' ').trim(),
         ...(paymentContact.administrativeArea && { stateOrProvince: paymentContact.administrativeArea }),
         ...(isDeliveryAddress && {
             firstName: paymentContact.givenName,

From 163dbcdabc00ac1cbde07fe8a451ad5167969bac Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Wed, 17 Jan 2024 12:03:04 -0300
Subject: [PATCH 31/55] tests: googlepay

---
 .../components/GooglePay/GooglePay.test.ts    | 328 +++++++++++++++++-
 .../playground/src/pages/Wallets/Wallets.js   |   4 +-
 2 files changed, 325 insertions(+), 7 deletions(-)

diff --git a/packages/lib/src/components/GooglePay/GooglePay.test.ts b/packages/lib/src/components/GooglePay/GooglePay.test.ts
index 74032bb339..a5e3fd83d5 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.test.ts
+++ b/packages/lib/src/components/GooglePay/GooglePay.test.ts
@@ -1,10 +1,325 @@
 import GooglePay from './GooglePay';
+import GooglePayService from './GooglePayService';
+
+jest.mock('./GooglePayService');
+
+beforeEach(() => {
+    // @ts-ignore 'mockClear' is provided by jest.mock
+    GooglePayService.mockClear();
+    jest.resetModules();
+    jest.resetAllMocks();
+});
+
+let googlePaymentData: Partial<google.payments.api.PaymentData> = {};
+
+beforeEach(() => {
+    googlePaymentData = {
+        apiVersionMinor: 0,
+        apiVersion: 2,
+        paymentMethodData: {
+            description: 'Visa •••• 1111',
+            tokenizationData: {
+                type: 'PAYMENT_GATEWAY',
+                token: 'google-pay-token'
+            },
+            type: 'CARD',
+            info: {
+                cardNetwork: 'VISA',
+                cardDetails: '1111',
+                // @ts-ignore Complaining about missing fields, although Google returned only these
+                billingAddress: {
+                    countryCode: 'US',
+                    postalCode: '94043',
+                    name: 'Card Holder Name'
+                }
+            }
+        },
+        shippingAddress: {
+            address3: '',
+            sortingCode: '',
+            address2: '',
+            countryCode: 'US',
+            address1: '1600 Amphitheatre Parkway1',
+            postalCode: '94043',
+            name: 'US User',
+            locality: 'Mountain View',
+            administrativeArea: 'CA'
+        },
+        email: 'shopper@gmail.com'
+    };
+});
 
 describe('GooglePay', () => {
-    describe('get data', () => {
-        test('always returns a type', () => {
-            const gpay = new GooglePay({ core: global.core });
-            expect(gpay.data.paymentMethod.type).toBe('googlepay');
+    describe('submit()', () => {
+        test('should make the payments call passing deliveryAddress and billingAddress', async () => {
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Authorized'
+                });
+            });
+            const onPaymentCompletedMock = jest.fn();
+
+            const gpay = new GooglePay({
+                core: global.core,
+                onSubmit: onSubmitMock,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            // @ts-ignore GooglePayService is mocked
+            const onPaymentAuthorized = GooglePayService.mock.calls[0][0].paymentDataCallbacks.onPaymentAuthorized;
+            const promise = onPaymentAuthorized(googlePaymentData);
+
+            await new Promise(process.nextTick);
+            expect(onSubmitMock).toHaveBeenCalledTimes(1);
+
+            const state = onSubmitMock.mock.calls[0][0];
+
+            expect(state.data.origin).toBe('http://localhost');
+            expect(state.data.paymentMethod).toStrictEqual({
+                checkoutAttemptId: 'do-not-track',
+                googlePayCardNetwork: 'VISA',
+                googlePayToken: 'google-pay-token',
+                type: 'googlepay'
+            });
+            expect(state.data.deliveryAddress).toStrictEqual({
+                city: 'Mountain View',
+                country: 'US',
+                firstName: 'US User',
+                houseNumberOrName: 'ZZ',
+                postalCode: '94043',
+                stateOrProvince: 'CA',
+                street: '1600 Amphitheatre Parkway1'
+            });
+            expect(state.data.billingAddress).toStrictEqual({
+                city: '',
+                country: 'US',
+                houseNumberOrName: 'ZZ',
+                postalCode: '94043',
+                street: ''
+            });
+            expect(state.data.browserInfo).toStrictEqual({
+                acceptHeader: '*/*',
+                colorDepth: 24,
+                javaEnabled: false,
+                language: 'en-US',
+                screenHeight: '',
+                screenWidth: '',
+                timeZoneOffset: 360,
+                userAgent: 'Mozilla/5.0 (linux) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/20.0.3'
+            });
+
+            await new Promise(process.nextTick);
+
+            expect(promise).resolves.toEqual({
+                transactionState: 'SUCCESS'
+            });
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledWith({ resultCode: 'Authorized' }, gpay);
+        });
+
+        test('should not add deliveryAddress and billingAddress if they are not available', async () => {
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Authorized'
+                });
+            });
+
+            new GooglePay({
+                core: global.core,
+                onSubmit: onSubmitMock
+            });
+
+            // @ts-ignore GooglePayService is mocked
+            const onPaymentAuthorized = GooglePayService.mock.calls[0][0].paymentDataCallbacks.onPaymentAuthorized;
+            const googlePaymentDataWithoutAddresses = { ...googlePaymentData };
+            delete googlePaymentDataWithoutAddresses.shippingAddress;
+            delete googlePaymentDataWithoutAddresses.paymentMethodData.info.billingAddress;
+            onPaymentAuthorized(googlePaymentDataWithoutAddresses);
+
+            await new Promise(process.nextTick);
+            expect(onSubmitMock).toHaveBeenCalledTimes(1);
+
+            const state = onSubmitMock.mock.calls[0][0];
+
+            expect(state.data.origin).toBe('http://localhost');
+            expect(state.data.paymentMethod).toStrictEqual({
+                checkoutAttemptId: 'do-not-track',
+                googlePayCardNetwork: 'VISA',
+                googlePayToken: 'google-pay-token',
+                type: 'googlepay'
+            });
+            expect(state.data.deliveryAddress).toBeUndefined();
+            expect(state.data.billingAddress).toBeUndefined();
+        });
+
+        test('should pass error to GooglePay if payment failed', async () => {
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Refused',
+                    error: {
+                        googlePayError: 'Insufficient funds'
+                    }
+                });
+            });
+            const onPaymentFailedMock = jest.fn();
+
+            const gpay = new GooglePay({
+                core: global.core,
+                onSubmit: onSubmitMock,
+                onPaymentFailed: onPaymentFailedMock
+            });
+
+            // @ts-ignore GooglePayService is mocked
+            const onPaymentAuthorized = GooglePayService.mock.calls[0][0].paymentDataCallbacks.onPaymentAuthorized;
+            const promise = onPaymentAuthorized(googlePaymentData);
+
+            await new Promise(process.nextTick);
+
+            expect(promise).resolves.toEqual({
+                error: {
+                    intent: 'PAYMENT_AUTHORIZATION',
+                    message: 'Insufficient funds',
+                    reason: 'OTHER_ERROR'
+                },
+                transactionState: 'ERROR'
+            });
+
+            expect(onPaymentFailedMock).toHaveBeenCalledWith(
+                { resultCode: 'Refused', error: { googlePayError: 'Insufficient funds' } },
+                gpay
+            );
+        });
+    });
+
+    describe('onAuthorized()', () => {
+        const event = {
+            authorizedEvent: {
+                apiVersionMinor: 0,
+                apiVersion: 2,
+                paymentMethodData: {
+                    description: 'Visa •••• 1111',
+                    tokenizationData: {
+                        type: 'PAYMENT_GATEWAY',
+                        token: 'google-pay-token'
+                    },
+                    type: 'CARD',
+                    info: {
+                        cardNetwork: 'VISA',
+                        cardDetails: '1111',
+                        billingAddress: {
+                            countryCode: 'US',
+                            postalCode: '94043',
+                            name: 'Card Holder Name'
+                        }
+                    }
+                },
+                shippingAddress: {
+                    address3: '',
+                    sortingCode: '',
+                    address2: '',
+                    countryCode: 'US',
+                    address1: '1600 Amphitheatre Parkway1',
+                    postalCode: '94043',
+                    name: 'US User',
+                    locality: 'Mountain View',
+                    administrativeArea: 'CA'
+                },
+                email: 'shopper@gmail.com'
+            },
+            billingAddress: {
+                postalCode: '94043',
+                country: 'US',
+                street: '',
+                houseNumberOrName: 'ZZ',
+                city: ''
+            },
+            deliveryAddress: {
+                postalCode: '94043',
+                country: 'US',
+                street: '1600 Amphitheatre Parkway1',
+                houseNumberOrName: 'ZZ',
+                city: 'Mountain View',
+                stateOrProvince: 'CA',
+                firstName: 'US User'
+            }
+        };
+
+        test('should provide GooglePay auth event and formatted data', () => {
+            const onAuthorizedMock = jest.fn();
+            new GooglePay({ core: global.core, onAuthorized: onAuthorizedMock });
+
+            // @ts-ignore GooglePayService is mocked
+            const onPaymentAuthorized = GooglePayService.mock.calls[0][0].paymentDataCallbacks.onPaymentAuthorized;
+            onPaymentAuthorized(googlePaymentData);
+
+            expect(onAuthorizedMock.mock.calls[0][0]).toStrictEqual(event);
+        });
+
+        test('should pass error to GooglePay if the action.reject happens on onAuthorized', async () => {
+            const onAuthorizedMock = jest.fn().mockImplementation((_data, actions) => {
+                console.log('reject');
+                actions.reject('Not supported network scheme');
+            });
+            const onPaymentFailedMock = jest.fn();
+
+            new GooglePay({
+                core: global.core,
+                onAuthorized: onAuthorizedMock,
+                onPaymentFailed: onPaymentFailedMock
+            });
+
+            // @ts-ignore GooglePayService is mocked
+            const onPaymentAuthorized = GooglePayService.mock.calls[0][0].paymentDataCallbacks.onPaymentAuthorized;
+            const promise = onPaymentAuthorized(googlePaymentData);
+
+            expect(promise).resolves.toEqual({
+                error: {
+                    intent: 'PAYMENT_AUTHORIZATION',
+                    message: 'Not supported network scheme',
+                    reason: 'OTHER_ERROR'
+                },
+                transactionState: 'ERROR'
+            });
+
+            await new Promise(process.nextTick);
+            expect(onPaymentFailedMock).toHaveBeenCalledTimes(1);
+        });
+
+        test('should continue the payment flow if action.resolve happens on onAuthorized', async () => {
+            const onAuthorizedMock = jest.fn().mockImplementation((_data, actions) => {
+                actions.resolve();
+            });
+            const onPaymentCompletedMock = jest.fn();
+
+            const gpay = new GooglePay({
+                core: global.core,
+                onAuthorized: onAuthorizedMock,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            const paymentCall = jest.spyOn(gpay as any, 'makePaymentsCall');
+
+            // @ts-ignore GooglePayService is mocked
+            const onPaymentAuthorized = GooglePayService.mock.calls[0][0].paymentDataCallbacks.onPaymentAuthorized;
+            onPaymentAuthorized(googlePaymentData);
+
+            await new Promise(process.nextTick);
+            expect(paymentCall).toHaveBeenCalledTimes(1);
+        });
+
+        test('should make the payments call if onAuthorized is not provided', async () => {
+            const gpay = new GooglePay({
+                core: global.core
+            });
+
+            const paymentCall = jest.spyOn(gpay as any, 'makePaymentsCall');
+
+            // @ts-ignore GooglePayService is mocked
+            const onPaymentAuthorized = GooglePayService.mock.calls[0][0].paymentDataCallbacks.onPaymentAuthorized;
+            onPaymentAuthorized(googlePaymentData);
+
+            await new Promise(process.nextTick);
+            expect(paymentCall).toHaveBeenCalledTimes(1);
         });
     });
 
@@ -44,7 +359,10 @@ describe('GooglePay', () => {
         });
 
         test('Retrieves merchantId from configuration', () => {
-            const gpay = new GooglePay({ core: global.core, configuration: { merchantId: 'abcdef', gatewayMerchantId: 'TestMerchant' } });
+            const gpay = new GooglePay({
+                core: global.core,
+                configuration: { merchantId: 'abcdef', gatewayMerchantId: 'TestMerchant' }
+            });
             expect(gpay.props.configuration.merchantId).toEqual('abcdef');
         });
 
diff --git a/packages/playground/src/pages/Wallets/Wallets.js b/packages/playground/src/pages/Wallets/Wallets.js
index a227c7fa94..fb9c3a31d5 100644
--- a/packages/playground/src/pages/Wallets/Wallets.js
+++ b/packages/playground/src/pages/Wallets/Wallets.js
@@ -195,9 +195,9 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
         // Shopper info (optional)
         emailRequired: true,
 
-        // billingAddressRequired: true,
+        billingAddressRequired: true,
 
-        // shippingAddressRequired: true,
+        shippingAddressRequired: true,
         // shippingAddressParameters: {}, // https://developers.google.com/pay/api/web/reference/object#ShippingAddressParameters
 
         // Button config (optional)

From 7e863262c64fd10a811abe2ae5b055c2ba7ca5d8 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Wed, 17 Jan 2024 12:20:46 -0300
Subject: [PATCH 32/55] fix: temp stuff

---
 .../components/internal/UIElement/UIElement.test.ts | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/packages/lib/src/components/internal/UIElement/UIElement.test.ts b/packages/lib/src/components/internal/UIElement/UIElement.test.ts
index 8218d3b110..05e43229f9 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.test.ts
+++ b/packages/lib/src/components/internal/UIElement/UIElement.test.ts
@@ -249,5 +249,18 @@ describe('UIElement', () => {
             expect(showValidation).toBeCalledTimes(1);
             expect(makePaymentsCallSpy).not.toHaveBeenCalled();
         });
+
+        // test.todo('should make successfull payment using advanced flow');
+        // test.todo('should make successfull payment using sessions flow');
+        // test.todo('should call onPaymentFailed if payment contains resultCode non-sucessfull result code');
+        // test.todo('should call onPaymentFailed if payment is rejected');
+        // test.todo('should call component.handleAction if payment is resolved with action');
     });
+
+    // describe('Handling additional details', () => {
+    //     test.todo('should make successfull payment using advanced flow');
+    //     test.todo('should make successfull payment using sessions flow');
+    //     test.todo('should call onPaymentFailed if payment contains resultCode non-sucessfull result code');
+    //     test.todo('should call onPaymentFailed if payment is rejected');
+    // });
 });

From cc3327fa2832f2f05f7e610f7d8870614baa413e Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 19 Jan 2024 12:16:52 -0300
Subject: [PATCH 33/55] test: uielement, handling order, actions, additional
 details

---
 .../internal/UIElement/UIElement.test.ts      | 469 +++++++++++++++++-
 .../internal/UIElement/UIElement.tsx          |  17 +-
 .../components/internal/UIElement/utils.ts    |   4 +-
 .../core/CheckoutSession/CheckoutSession.ts   |   3 +-
 packages/lib/src/core/core.ts                 |   4 +-
 packages/lib/src/core/types.ts                |   9 +-
 packages/lib/src/types/global-types.ts        |  12 +
 7 files changed, 487 insertions(+), 31 deletions(-)

diff --git a/packages/lib/src/components/internal/UIElement/UIElement.test.ts b/packages/lib/src/components/internal/UIElement/UIElement.test.ts
index 05e43229f9..f887d77622 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.test.ts
+++ b/packages/lib/src/components/internal/UIElement/UIElement.test.ts
@@ -1,8 +1,9 @@
-import UIElement from './UIElement';
+import { UIElement } from './UIElement';
 import { ICore } from '../../../core/types';
-import { mockDeep, mockReset } from 'jest-mock-extended';
+import { any, mockDeep } from 'jest-mock-extended';
 import { AdyenCheckout, ThreeDS2Challenge, ThreeDS2DeviceFingerprint } from '../../../index';
 import { UIElementProps } from './types';
+import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError';
 
 interface MyElementProps extends UIElementProps {
     challengeWindowSize?: string;
@@ -27,9 +28,9 @@ class MyElement extends UIElement<MyElementProps> {
 const submitMock = jest.fn();
 (global as any).HTMLFormElement.prototype.submit = () => submitMock;
 
-const core = mockDeep<ICore>();
+let core;
 beforeEach(() => {
-    mockReset(core);
+    core = mockDeep<ICore>();
 });
 
 describe('UIElement', () => {
@@ -250,17 +251,455 @@ describe('UIElement', () => {
             expect(makePaymentsCallSpy).not.toHaveBeenCalled();
         });
 
-        // test.todo('should make successfull payment using advanced flow');
-        // test.todo('should make successfull payment using sessions flow');
-        // test.todo('should call onPaymentFailed if payment contains resultCode non-sucessfull result code');
-        // test.todo('should call onPaymentFailed if payment is rejected');
-        // test.todo('should call component.handleAction if payment is resolved with action');
+        test('should make successfully payment using advanced flow', async () => {
+            const onPaymentCompletedMock = jest.fn();
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Authorized'
+                });
+            });
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+            jest.spyOn(MyElement.prototype, 'data', 'get').mockReturnValue({
+                clientStateDataIndicator: true,
+                paymentMethod: {
+                    type: 'payment-type'
+                }
+            });
+
+            const element = new MyElement({
+                core: core,
+                onSubmit: onSubmitMock,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentCompletedMock).toHaveBeenCalledWith({ resultCode: 'Authorized' }, element);
+        });
+
+        test('should make successfull payment using sessions flow', async () => {
+            const onPaymentCompletedMock = jest.fn();
+
+            core.session.submitPayment.calledWith(any()).mockResolvedValue({
+                resultCode: 'Authorised',
+                sessionData: 'session-data',
+                sessionResult: 'session-result'
+            });
+
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+            jest.spyOn(MyElement.prototype, 'data', 'get').mockReturnValue({
+                clientStateDataIndicator: true,
+                paymentMethod: {
+                    type: 'payment-type'
+                }
+            });
+
+            const element = new MyElement({
+                core: core,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentCompletedMock).toHaveBeenCalledWith(
+                {
+                    resultCode: 'Authorised',
+                    sessionData: 'session-data',
+                    sessionResult: 'session-result'
+                },
+                element
+            );
+        });
+
+        test('should call onPaymentFailed if payment contains non-successful result code', async () => {
+            const onPaymentFailedMock = jest.fn();
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Refused'
+                });
+            });
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+            jest.spyOn(MyElement.prototype, 'data', 'get').mockReturnValue({
+                clientStateDataIndicator: true,
+                paymentMethod: {
+                    type: 'payment-type'
+                }
+            });
+
+            const element = new MyElement({
+                core: core,
+                onSubmit: onSubmitMock,
+                onPaymentFailed: onPaymentFailedMock
+            });
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(onPaymentFailedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentFailedMock).toHaveBeenCalledWith({ resultCode: 'Refused' }, element);
+        });
+
+        test('should call onPaymentFailed if payment is rejected', async () => {
+            const onPaymentFailedMock = jest.fn();
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.reject();
+            });
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+
+            const element = new MyElement({
+                core: core,
+                onSubmit: onSubmitMock,
+                onPaymentFailed: onPaymentFailedMock
+            });
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(onPaymentFailedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentFailedMock).toHaveBeenCalledWith(undefined, element);
+        });
+
+        test('should call component.handleAction if payment is resolved with action', async () => {
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Pending',
+                    action: {
+                        type: 'sdk',
+                        paymentMethodType: 'payment-type',
+                        paymentData: 'payment-data'
+                    }
+                });
+            });
+
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+
+            const element = new MyElement({
+                core: core,
+                onSubmit: onSubmitMock
+            });
+
+            const handleActionSpy = jest.spyOn(element, 'handleAction');
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(handleActionSpy).toHaveBeenCalledTimes(1);
+            expect(handleActionSpy).toHaveBeenCalledWith({
+                type: 'sdk',
+                paymentMethodType: 'payment-type',
+                paymentData: 'payment-data'
+            });
+        });
+
+        test('should trigger core.update if there is pending order when using sessions', async () => {
+            const order = {
+                amount: {
+                    currency: 'EUR',
+                    value: 2001
+                },
+                expiresAt: '2023-10-10T13:12:59.00Z',
+                orderData: 'order-mock',
+                pspReference: 'MHCDBZCH4NF96292',
+                reference: 'ABC123',
+                remainingAmount: {
+                    currency: 'EUR',
+                    value: 100
+                }
+            };
+
+            const onOrderUpdatedMock = jest.fn();
+
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+            core.update.calledWith(any()).mockResolvedValue(core);
+            core.session.submitPayment.calledWith(any()).mockResolvedValue({
+                resultCode: 'Pending',
+                // @ts-ignore  ADD ORDER TO SESSION CHECKOUT RESPONSE
+                order,
+                sessionData: 'session-data',
+                sessionResult: 'session-result'
+            });
+
+            const element = new MyElement({
+                core: core,
+                onOrderUpdated: onOrderUpdatedMock
+            });
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(core.update).toHaveBeenCalledTimes(1);
+            expect(core.update).toHaveBeenCalledWith({ order });
+
+            expect(onOrderUpdatedMock).toHaveBeenCalledTimes(1);
+            expect(onOrderUpdatedMock).toHaveBeenCalledWith({ order });
+        });
+
+        test('should trigger onPaymentMethodsRequest if there is a pending order when using advanced flow', async () => {
+            const order = {
+                amount: {
+                    currency: 'EUR',
+                    value: 2001
+                },
+                expiresAt: '2023-10-10T13:12:59.00Z',
+                orderData: 'order-mock',
+                pspReference: 'MHCDBZCH4NF96292',
+                reference: 'ABC123',
+                remainingAmount: {
+                    currency: 'EUR',
+                    value: 100
+                }
+            };
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Pending',
+                    order
+                });
+            });
+            const onPaymentMethodsRequestMock = jest.fn().mockImplementation((data, actions) => {
+                actions.resolve({
+                    paymentMethods: [],
+                    storedPaymentMethods: []
+                });
+            });
+            const onOrderUpdatedMock = jest.fn();
+
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+            core.update.calledWith(any()).mockResolvedValue(core);
+            core.options.locale = 'en-US';
+            core.session = null;
+
+            const element = new MyElement({
+                core: core,
+                onSubmit: onSubmitMock,
+                onPaymentMethodsRequest: onPaymentMethodsRequestMock,
+                onOrderUpdated: onOrderUpdatedMock
+            });
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(onPaymentMethodsRequestMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentMethodsRequestMock.mock.calls[0][0]).toStrictEqual({
+                order: {
+                    orderData: 'order-mock',
+                    pspReference: 'MHCDBZCH4NF96292'
+                },
+                locale: 'en-US'
+            });
+
+            expect(core.update).toHaveBeenCalledTimes(1);
+            expect(core.update).toHaveBeenCalledWith({
+                paymentMethodsResponse: {
+                    paymentMethods: [],
+                    storedPaymentMethods: []
+                },
+                order,
+                amount: order.remainingAmount
+            });
+
+            expect(onOrderUpdatedMock).toHaveBeenCalledTimes(1);
+            expect(onOrderUpdatedMock).toHaveBeenCalledWith({ order });
+        });
+
+        test('should throw an error if onPaymentMethodsRequest is not implemented, although the flow will continue', async () => {
+            const order = {
+                amount: {
+                    currency: 'EUR',
+                    value: 2001
+                },
+                expiresAt: '2023-10-10T13:12:59.00Z',
+                orderData: 'order-mock',
+                pspReference: 'MHCDBZCH4NF96292',
+                reference: 'ABC123',
+                remainingAmount: {
+                    currency: 'EUR',
+                    value: 100
+                }
+            };
+            const onSubmitMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Pending',
+                    order
+                });
+            });
+            const onOrderUpdatedMock = jest.fn();
+            const onErrorMock = jest.fn();
+
+            jest.spyOn(MyElement.prototype, 'isValid', 'get').mockReturnValue(true);
+            core.update.calledWith(any()).mockResolvedValue(core);
+            core.options.locale = 'en-US';
+            core.session = null;
+
+            const element = new MyElement({
+                core: core,
+                onSubmit: onSubmitMock,
+                onOrderUpdated: onOrderUpdatedMock,
+                onError: onErrorMock
+            });
+
+            element.submit();
+
+            await new Promise(process.nextTick);
+
+            expect(onErrorMock).toHaveBeenCalledTimes(1);
+            expect(onErrorMock.mock.calls[0][0]).toBeInstanceOf(AdyenCheckoutError);
+
+            expect(core.update).toHaveBeenCalledTimes(1);
+            expect(core.update).toHaveBeenCalledWith({
+                order,
+                amount: order.remainingAmount
+            });
+
+            expect(onOrderUpdatedMock).toHaveBeenCalledTimes(1);
+            expect(onOrderUpdatedMock).toHaveBeenCalledWith({ order });
+        });
     });
 
-    // describe('Handling additional details', () => {
-    //     test.todo('should make successfull payment using advanced flow');
-    //     test.todo('should make successfull payment using sessions flow');
-    //     test.todo('should call onPaymentFailed if payment contains resultCode non-sucessfull result code');
-    //     test.todo('should call onPaymentFailed if payment is rejected');
-    // });
+    describe('[Internal] handleAdditionalDetails()', () => {
+        test('should make successfully payment/details using advanced flow', async () => {
+            const onPaymentCompletedMock = jest.fn();
+            const onAdditionalDetailsMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Authorized'
+                });
+            });
+
+            const element = new MyElement({
+                core: core,
+                onAdditionalDetails: onAdditionalDetailsMock,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            const data = {
+                data: {
+                    details: {
+                        paymentSource: 'paypal'
+                    },
+                    paymentData: 'payment-data'
+                }
+            };
+
+            // @ts-ignore Testing internal implementation
+            element.handleAdditionalDetails(data);
+
+            await new Promise(process.nextTick);
+
+            expect(onAdditionalDetailsMock).toHaveBeenCalledTimes(1);
+            expect(onAdditionalDetailsMock.mock.calls[0][0]).toEqual(data);
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentCompletedMock).toHaveBeenCalledWith({ resultCode: 'Authorized' }, element);
+        });
+
+        test('should make successfully payment/details using sessions flow', async () => {
+            const onPaymentCompletedMock = jest.fn();
+
+            const element = new MyElement({
+                core: core,
+                onPaymentCompleted: onPaymentCompletedMock
+            });
+
+            core.session.submitDetails.calledWith(any()).mockResolvedValue({
+                resultCode: 'Authorised',
+                sessionData: 'session-data',
+                sessionResult: 'session-result'
+            });
+
+            const state = {
+                data: {
+                    details: {
+                        paymentSource: 'paypal'
+                    },
+                    paymentData: 'payment-data'
+                }
+            };
+
+            // @ts-ignore Testing internal implementation
+            element.handleAdditionalDetails(state);
+
+            await new Promise(process.nextTick);
+
+            expect(core.session.submitDetails).toHaveBeenCalledTimes(1);
+            expect(core.session.submitDetails).toHaveBeenCalledWith(state.data);
+
+            expect(onPaymentCompletedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentCompletedMock).toHaveBeenCalledWith(
+                { resultCode: 'Authorised', sessionData: 'session-data', sessionResult: 'session-result' },
+                element
+            );
+        });
+
+        test('should call onPaymentFailed if payment/details contains non-successful result code', async () => {
+            const onPaymentFailedMock = jest.fn();
+            const onAdditionalDetailsMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.resolve({
+                    resultCode: 'Refused'
+                });
+            });
+
+            const element = new MyElement({
+                core: core,
+                onAdditionalDetails: onAdditionalDetailsMock,
+                onPaymentFailed: onPaymentFailedMock
+            });
+
+            const data = {
+                data: {
+                    details: {
+                        paymentSource: 'paypal'
+                    },
+                    paymentData: 'payment-data'
+                }
+            };
+
+            // @ts-ignore Testing internal implementation
+            element.handleAdditionalDetails(data);
+
+            await new Promise(process.nextTick);
+
+            expect(onPaymentFailedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentFailedMock).toHaveBeenCalledWith({ resultCode: 'Refused' }, element);
+        });
+
+        test('should call onPaymentFailed if payment is rejected', async () => {
+            const onPaymentFailedMock = jest.fn();
+            const onAdditionalDetailsMock = jest.fn().mockImplementation((data, component, actions) => {
+                actions.reject();
+            });
+
+            const element = new MyElement({
+                core: core,
+                onAdditionalDetails: onAdditionalDetailsMock,
+                onPaymentFailed: onPaymentFailedMock
+            });
+
+            const data = {
+                data: {
+                    details: {
+                        paymentSource: 'paypal'
+                    },
+                    paymentData: 'payment-data'
+                }
+            };
+
+            // @ts-ignore Testing internal implementation
+            element.handleAdditionalDetails(data);
+
+            await new Promise(process.nextTick);
+
+            expect(onPaymentFailedMock).toHaveBeenCalledTimes(1);
+            expect(onPaymentFailedMock).toHaveBeenCalledWith(undefined, element);
+        });
+    });
 });
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index 5dab328a73..93f0c27c5f 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -15,7 +15,8 @@ import {
     PaymentMethodsResponse,
     CheckoutAdvancedFlowResponse,
     PaymentResponseData,
-    RawPaymentResponse
+    RawPaymentResponse,
+    AdditionalDetailsStateData
 } from '../../../types/global-types';
 import './UIElement.scss';
 import { CheckoutSessionDetailsResponse, CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
@@ -222,7 +223,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
         }
     };
 
-    protected handleAdditionalDetails(state: any): void {
+    protected handleAdditionalDetails(state: AdditionalDetailsStateData): void {
         this.makeAdditionalDetailsCall(state)
             .then(sanitizeResponse)
             .then(verifyPaymentDidNotFail)
@@ -231,7 +232,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
     }
 
     private makeAdditionalDetailsCall(
-        state: any
+        state: AdditionalDetailsStateData
     ): Promise<CheckoutSessionDetailsResponse | CheckoutAdvancedFlowResponse> {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('loading');
@@ -243,7 +244,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
             });
         }
 
-        if (this.props.session) {
+        if (this.core.session) {
             return this.submitAdditionalDetailsUsingSessionsFlow(state.data);
         }
 
@@ -303,14 +304,14 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps>
 
     /**
      * Handles when the payment fails. The payment fails when:
-     * - adv flow: the merchant rejects the payment
+     * - adv flow: the merchant rejects the payment due to a critical error
      * - adv flow: the merchant resolves the payment with a failed resultCode
-     * - sessions: an error occurs during session when making the payment
-     * - sessions: the payment fail
+     * - sessions: a network error occurs when making the payment
+     * - sessions: the payment fails with a failed resultCode
      *
      * @param result
      */
-    protected handleFailedResult = (result: PaymentResponseData): void => {
+    protected handleFailedResult = (result?: PaymentResponseData): void => {
         if (this.props.setStatusAutomatically) {
             this.setElementStatus('error');
         }
diff --git a/packages/lib/src/components/internal/UIElement/utils.ts b/packages/lib/src/components/internal/UIElement/utils.ts
index 2466e68767..02941f4685 100644
--- a/packages/lib/src/components/internal/UIElement/utils.ts
+++ b/packages/lib/src/components/internal/UIElement/utils.ts
@@ -26,7 +26,9 @@ export function sanitizeResponse(response: RawPaymentResponse): PaymentResponseD
  *
  * @param paymentResponse
  */
-export function cleanupFinalResult(paymentResponse: PaymentResponseData): void {
+export function cleanupFinalResult(paymentResponse?: PaymentResponseData): void {
+    if (!paymentResponse) return;
+
     delete paymentResponse.order;
     delete paymentResponse.action;
     if (!paymentResponse.donationToken || paymentResponse.donationToken.length === 0) {
diff --git a/packages/lib/src/core/CheckoutSession/CheckoutSession.ts b/packages/lib/src/core/CheckoutSession/CheckoutSession.ts
index 6cc17a4b0c..647d03b03c 100644
--- a/packages/lib/src/core/CheckoutSession/CheckoutSession.ts
+++ b/packages/lib/src/core/CheckoutSession/CheckoutSession.ts
@@ -16,6 +16,7 @@ import {
 } from './types';
 import cancelOrder from '../Services/sessions/cancel-order';
 import { onOrderCancelData } from '../../components/Dropin/types';
+import { AdditionalDetailsStateData } from '../../types/global-types';
 
 class Session {
     private readonly session: CheckoutSession;
@@ -91,7 +92,7 @@ class Session {
     /**
      * Submits session payment additional details
      */
-    submitDetails(data): Promise<CheckoutSessionDetailsResponse> {
+    submitDetails(data: AdditionalDetailsStateData['data']): Promise<CheckoutSessionDetailsResponse> {
         return submitDetails(data, this).then(response => {
             if (response.sessionData) {
                 this.updateSessionData(response.sessionData);
diff --git a/packages/lib/src/core/core.ts b/packages/lib/src/core/core.ts
index ca4cbf1e6b..bb9795c9c7 100644
--- a/packages/lib/src/core/core.ts
+++ b/packages/lib/src/core/core.ts
@@ -5,7 +5,7 @@ import PaymentMethods from './ProcessResponse/PaymentMethods';
 import getComponentForAction from './ProcessResponse/PaymentAction';
 import { resolveEnvironment, resolveCDNEnvironment } from './Environment';
 import Analytics from './Analytics';
-import { PaymentAction, PaymentResponseData } from '../types/global-types';
+import { AdditionalDetailsStateData, PaymentAction, PaymentResponseData } from '../types/global-types';
 import { CoreConfiguration, ICore } from './types';
 import { processGlobalOptions } from './utils';
 import Session from './CheckoutSession';
@@ -114,7 +114,7 @@ class Core implements ICore {
      * @see {https://docs.adyen.com/online-payments/build-your-integration/?platform=Web&integration=Components&version=5.55.1#handle-the-redirect}
      * @param details - Details object containing the redirectResult
      */
-    public submitDetails(details: { details: { redirectResult: string } }): void {
+    public submitDetails(details: AdditionalDetailsStateData['data']): void {
         let promise = null;
 
         if (this.options.onAdditionalDetails) {
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index ab25bba2bf..a16553e454 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -9,7 +9,8 @@ import {
     CheckoutAdvancedFlowResponse,
     PaymentMethodsRequestData,
     SessionsResponse,
-    ResultCode
+    ResultCode,
+    AdditionalDetailsStateData
 } from '../types/global-types';
 import { AnalyticsOptions } from './Analytics/types';
 import { RiskModuleOptions } from './RiskModule/RiskModule';
@@ -176,10 +177,10 @@ export interface CoreConfiguration {
     /**
      * Called when the payment fails.
      *
-     * The first parameter is poppulated when merchant is using sessions, or when the payment was rejected
+     * The first parameter is populated when merchant is using sessions, or when the payment was rejected
      * with an object. (Ex: 'action.reject(obj)' ). Otherwise, it will be empty.
      *
-     * @param data
+     * @param data - session response or resultCode. It can also be undefined if payment was rejected without argument ('action.reject()')
      * @param element
      */
     onPaymentFailed?(data?: SessionsResponse | { resultCode: ResultCode }, element?: UIElement): void;
@@ -202,7 +203,7 @@ export interface CoreConfiguration {
      * @param actions
      */
     onAdditionalDetails?(
-        state: any,
+        state: AdditionalDetailsStateData,
         element: UIElement,
         actions: {
             resolve: (response: CheckoutAdvancedFlowResponse) => void;
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index 09ac0cd8e4..a9b028b7b0 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -379,3 +379,15 @@ export interface ActionHandledReturnObject {
     componentType: string;
     actionDescription: ActionDescriptionType;
 }
+
+export type AdditionalDetailsStateData = {
+    data: {
+        details: {
+            redirectResult?: string;
+            threeDSResult?: string;
+            [key: string]: any;
+        };
+        paymentData?: string;
+        sessionData?: string;
+    };
+};

From 135c9bc6ac3f40ae609462f1cadeed55f3c4341d Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Fri, 19 Jan 2024 13:12:30 -0300
Subject: [PATCH 34/55] fix: reverting prettier config

---
 .prettierrc.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.prettierrc.json b/.prettierrc.json
index f3ca00a53b..3f1d5d07ce 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -3,6 +3,6 @@
     "bracketSpacing": true,
     "trailingComma": "none",
     "tabWidth": 4,
-    "printWidth": 120,
+    "printWidth": 150,
     "singleQuote": true
 }

From b8c03f6f5df4f3f9c09037b54033e36d5ed99b5f Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Mon, 22 Jan 2024 14:51:04 -0300
Subject: [PATCH 35/55] fixing old playground

---
 packages/playground/src/handlers.js           | 19 +---
 .../src/pages/Components/Components.js        |  6 ++
 .../playground/src/pages/Dropin/manual.js     | 33 ++-----
 .../playground/src/pages/DropinAuto/manual.js | 88 ++++++++----------
 .../src/pages/DropinAuto/session.js           |  9 +-
 .../playground/src/pages/DropinUMD/manual.js  | 89 ++++++++-----------
 .../playground/src/pages/DropinUMD/session.js |  6 +-
 .../src/pages/GiftCards/GiftCards.js          | 13 ++-
 .../src/pages/OpenInvoices/OpenInvoices.js    |  8 +-
 .../playground/src/pages/QRCodes/QRCodes.js   | 58 +++++++-----
 .../playground/src/pages/Result/Result.js     |  8 +-
 .../playground/src/pages/ThreeDS/ThreeDS.js   | 21 +++--
 .../playground/src/pages/Wallets/Wallets.js   | 30 +------
 13 files changed, 172 insertions(+), 216 deletions(-)

diff --git a/packages/playground/src/handlers.js b/packages/playground/src/handlers.js
index 487fee13dc..38ad68e9d2 100644
--- a/packages/playground/src/handlers.js
+++ b/packages/playground/src/handlers.js
@@ -1,16 +1,5 @@
 import { makePayment, makeDetailsCall } from './services';
 
-export function handleResponse(response, component) {
-    const type = component.data.paymentMethod ? component.data.paymentMethod.type : component.constructor.name;
-    console.log('\ntype=', type, 'response=', response);
-
-    if (response.action) {
-        component.handleAction(response.action);
-    } else if (response.resultCode) {
-        alert(response.resultCode);
-    }
-}
-
 export function handleChange(state, component) {
     console.group(`onChange - ${state.data.paymentMethod.type}`);
     console.log('isValid', state.isValid);
@@ -32,8 +21,6 @@ export function handleError(obj) {
 export async function handleSubmit(state, component, actions) {
     component.setStatus('loading');
 
-    console.log('onSubmit', state, actions);
-
     try {
         const { action, order, resultCode, donationToken } = await makePayment(state.data);
 
@@ -53,9 +40,7 @@ export async function handleSubmit(state, component, actions) {
 
 export async function handleAdditionalDetails(state, component, actions) {
     try {
-        console.log('onAdditionalDetails', state, component, actions);
-
-        const { resultCode, action, order, resultCode, donationToken } = await makeDetailsCall(state.data);
+        const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
 
         if (!resultCode) actions.reject();
 
@@ -64,9 +49,7 @@ export async function handleAdditionalDetails(state, component, actions) {
             action,
             order,
             donationToken
-            // error: {},
         });
-        return;
     } catch (error) {
         console.error('## onAdditionalDetails - critical error', error);
         actions.reject();
diff --git a/packages/playground/src/pages/Components/Components.js b/packages/playground/src/pages/Components/Components.js
index 8a93a448b4..3d3d9662c2 100644
--- a/packages/playground/src/pages/Components/Components.js
+++ b/packages/playground/src/pages/Components/Components.js
@@ -34,6 +34,12 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
         onChange: handleChange,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        },
         onError: (error, component) => {
             console.info(error, component);
         },
diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js
index 134f4f02c6..82bf645d86 100644
--- a/packages/playground/src/pages/Dropin/manual.js
+++ b/packages/playground/src/pages/Dropin/manual.js
@@ -1,7 +1,6 @@
 import { AdyenCheckout, Dropin, Ideal, Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
 import { getPaymentMethods, makePayment, checkBalance, createOrder, cancelOrder, makeDetailsCall } from '../../services';
-
 import { amount, shopperLocale, countryCode } from '../../config/commonConfig';
 import { getSearchParameters } from '../../utils';
 import getTranslationFile from '../../config/getTranslation';
@@ -21,8 +20,6 @@ export async function initManual() {
         environment: process.env.__CLIENT_ENV__,
 
         onSubmit: async (state, component, actions) => {
-            console.log('onSubmit', state, component.authorizedEvent);
-
             try {
                 const { action, order, resultCode, donationToken } = await makePayment(state.data);
 
@@ -33,33 +30,14 @@ export async function initManual() {
                     action,
                     order,
                     donationToken
-                    // error: {
-                    //              googlePayError: {},
-                    //              applePayError: {}
-                    //          }
-                    // }
                 });
             } catch (error) {
                 console.error('## onSubmit - critical error', error);
                 actions.reject();
             }
         },
-
-        onChange(state, element) {
-            console.log('onChange', state, element);
-        },
-
-        onPaymentCompleted(result, element) {
-            console.log('onPaymentCompleted', result, element);
-        },
-        onPaymentFailed(result, element) {
-            console.log('onPaymentFailed', result, element);
-        },
-
         onAdditionalDetails: async (state, component, actions) => {
             try {
-                console.log('onAdditionalDetails', state, component, actions);
-
                 const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
 
                 if (!resultCode) actions.reject();
@@ -69,14 +47,18 @@ export async function initManual() {
                     action,
                     order,
                     donationToken
-                    // error: {},
                 });
-                return;
             } catch (error) {
                 console.error('## onAdditionalDetails - critical error', error);
                 actions.reject();
             }
         },
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        },
         onBalanceCheck: async (resolve, reject, data) => {
             console.log('onBalanceCheck', data);
             resolve(await checkBalance(data));
@@ -173,9 +155,6 @@ export async function initManual() {
             klarna: {
                 useKlarnaWidget: true
             }
-            // storedCard: {
-            //     hideCVC: true
-            // }
         }
     }).mount('#dropin-container');
 
diff --git a/packages/playground/src/pages/DropinAuto/manual.js b/packages/playground/src/pages/DropinAuto/manual.js
index f14138efca..379abbe798 100644
--- a/packages/playground/src/pages/DropinAuto/manual.js
+++ b/packages/playground/src/pages/DropinAuto/manual.js
@@ -23,39 +23,46 @@ export async function initManual() {
                 values: [1, 2, 3, 4]
             }
         },
-        onSubmit: async (state, component) => {
-            const result = await makePayment(state.data);
-
-            // handle actions
-            if (result.action) {
-                // demo only - store paymentData & order
-                if (result.action.paymentData) localStorage.setItem('storedPaymentData', result.action.paymentData);
-                component.handleAction(result.action);
-            } else if (result.order && result.order?.remainingAmount?.value > 0) {
-                // handle orders
-                const order = {
-                    orderData: result.order.orderData,
-                    pspReference: result.order.pspReference
-                };
-
-                const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-                checkout.update({ paymentMethodsResponse: orderPaymentMethods, order, amount: result.order.remainingAmount });
-            } else {
-                handleFinalState(result.resultCode, component);
+        onSubmit: async (state, component, actions) => {
+            try {
+                const { action, order, resultCode, donationToken } = await makePayment(state.data);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                });
+            } catch (error) {
+                console.error('## onSubmit - critical error', error);
+                actions.reject();
             }
         },
-        // onChange: state => {
-        //     console.log('onChange', state);
-        // },
-        onAdditionalDetails: async (state, component) => {
-            const result = await makeDetailsCall(state.data);
-
-            if (result.action) {
-                component.handleAction(result.action);
-            } else {
-                handleFinalState(result.resultCode, component);
+        onAdditionalDetails: async (state, component, actions) => {
+            try {
+                const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                });
+            } catch (error) {
+                console.error('## onAdditionalDetails - critical error', error);
+                actions.reject();
             }
         },
+        onPaymentCompleted(data, component) {
+            component.setStatus('success');
+        },
+        onPaymentFailed(data, component) {
+            component.setStatus('error');
+        },
         onBalanceCheck: async (resolve, reject, data) => {
             resolve(await checkBalance(data));
         },
@@ -107,26 +114,6 @@ export async function initManual() {
             });
         }
 
-        // Handle Amazon Pay redirect result
-        if (amazonCheckoutSessionId) {
-            window.amazonpay = new AmazonPay({
-                core: checkout,
-                amazonCheckoutSessionId,
-                showOrderButton: false,
-                onSubmit: state => {
-                    makePayment(state.data).then(result => {
-                        if (result.action) {
-                            dropin.handleAction(result.action);
-                        } else {
-                            handleFinalState(result.resultCode, dropin);
-                        }
-                    });
-                }
-            }).mount('body');
-
-            window.amazonpay.submit();
-        }
-
         return Promise.resolve(true);
     }
 
@@ -142,9 +129,6 @@ export async function initManual() {
             paywithgoogle: {
                 buttonType: 'plain'
             },
-            // storedCard: {
-            //     hideCVC: true
-            // }
             klarna: {
                 useKlarnaWidget: true
             }
diff --git a/packages/playground/src/pages/DropinAuto/session.js b/packages/playground/src/pages/DropinAuto/session.js
index 45d0fd53be..c752a3837e 100644
--- a/packages/playground/src/pages/DropinAuto/session.js
+++ b/packages/playground/src/pages/DropinAuto/session.js
@@ -28,8 +28,13 @@ export async function initSession() {
         beforeSubmit: (data, component, actions) => {
             actions.resolve(data);
         },
-        onPaymentCompleted: (result, component) => {
-            console.info(result, component);
+        onPaymentCompleted(data, component) {
+            console.info('onPaymentCompleted', data, component);
+            component.setStatus('success');
+        },
+        onPaymentFailed(data, component) {
+            console.info('onPaymentFailed', data, component);
+            component.setStatus('error');
         },
         onError: (error, component) => {
             console.info(JSON.stringify(error), component);
diff --git a/packages/playground/src/pages/DropinUMD/manual.js b/packages/playground/src/pages/DropinUMD/manual.js
index 900097cf10..afffcef5dc 100644
--- a/packages/playground/src/pages/DropinUMD/manual.js
+++ b/packages/playground/src/pages/DropinUMD/manual.js
@@ -17,39 +17,46 @@ export async function initManual() {
         locale: shopperLocale,
         translationFile: getTranslationFile(shopperLocale),
         environment: process.env.__CLIENT_ENV__,
-        onSubmit: async (state, component) => {
-            const result = await makePayment(state.data);
-
-            // handle actions
-            if (result.action) {
-                // demo only - store paymentData & order
-                if (result.action.paymentData) localStorage.setItem('storedPaymentData', result.action.paymentData);
-                component.handleAction(result.action);
-            } else if (result.order && result.order?.remainingAmount?.value > 0) {
-                // handle orders
-                const order = {
-                    orderData: result.order.orderData,
-                    pspReference: result.order.pspReference
-                };
-
-                const orderPaymentMethods = await getPaymentMethods({ order, amount, shopperLocale });
-                checkout.update({ paymentMethodsResponse: orderPaymentMethods, order, amount: result.order.remainingAmount });
-            } else {
-                handleFinalState(result.resultCode, component);
+        onSubmit: async (state, component, actions) => {
+            try {
+                const { action, order, resultCode, donationToken } = await makePayment(state.data);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                });
+            } catch (error) {
+                console.error('## onSubmit - critical error', error);
+                actions.reject();
             }
         },
-        // onChange: state => {
-        //     console.log('onChange', state);
-        // },
-        onAdditionalDetails: async (state, component) => {
-            const result = await makeDetailsCall(state.data);
-
-            if (result.action) {
-                component.handleAction(result.action);
-            } else {
-                handleFinalState(result.resultCode, component);
+        onAdditionalDetails: async (state, component, actions) => {
+            try {
+                const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                });
+            } catch (error) {
+                console.error('## onAdditionalDetails - critical error', error);
+                actions.reject();
             }
         },
+        onPaymentCompleted(data, component) {
+            component.setStatus('success');
+        },
+        onPaymentFailed(data, component) {
+            component.setStatus('error');
+        },
         onBalanceCheck: async (resolve, reject, data) => {
             resolve(await checkBalance(data));
         },
@@ -100,27 +107,6 @@ export async function initManual() {
                 return true;
             });
         }
-
-        // Handle Amazon Pay redirect result
-        if (amazonCheckoutSessionId) {
-            window.amazonpay = new AmazonPay({
-                core: checkout,
-                amazonCheckoutSessionId,
-                showOrderButton: false,
-                onSubmit: state => {
-                    makePayment(state.data).then(result => {
-                        if (result.action) {
-                            dropin.handleAction(result.action);
-                        } else {
-                            handleFinalState(result.resultCode, dropin);
-                        }
-                    });
-                }
-            }).mount('body');
-
-            window.amazonpay.submit();
-        }
-
         return Promise.resolve(true);
     }
 
@@ -136,9 +122,6 @@ export async function initManual() {
             paywithgoogle: {
                 buttonType: 'plain'
             },
-            // storedCard: {
-            //     hideCVC: true
-            // }
             klarna: {
                 useKlarnaWidget: true
             }
diff --git a/packages/playground/src/pages/DropinUMD/session.js b/packages/playground/src/pages/DropinUMD/session.js
index c83f4f054f..a1c211356c 100644
--- a/packages/playground/src/pages/DropinUMD/session.js
+++ b/packages/playground/src/pages/DropinUMD/session.js
@@ -24,12 +24,14 @@ export async function initSession() {
         locale: shopperLocale,
         translationFile: getTranslationFile(shopperLocale),
 
-        // Events
         beforeSubmit: (data, component, actions) => {
             actions.resolve(data);
         },
         onPaymentCompleted: (result, component) => {
-            console.info(result, component);
+            console.info('onPaymentCompleted', result, component);
+        },
+        onPaymentFailed: (result, component) => {
+            console.info('onPaymentFailed', result, component);
         },
         onError: (error, component) => {
             console.info(JSON.stringify(error), component);
diff --git a/packages/playground/src/pages/GiftCards/GiftCards.js b/packages/playground/src/pages/GiftCards/GiftCards.js
index 4ea568be34..61e2931542 100644
--- a/packages/playground/src/pages/GiftCards/GiftCards.js
+++ b/packages/playground/src/pages/GiftCards/GiftCards.js
@@ -17,6 +17,12 @@ import getTranslationFile from '../../config/getTranslation';
         environment: process.env.__CLIENT_ENV__,
         onChange: handleChange,
         onSubmit: handleSubmit,
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        },
         showPayButton: true,
         amount
     });
@@ -70,8 +76,11 @@ import getTranslationFile from '../../config/getTranslation';
         beforeSubmit: (data, component, actions) => {
             actions.resolve(data);
         },
-        onPaymentCompleted: (result, component) => {
-            console.info(result, component);
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
         },
         onError: (error, component) => {
             console.error(error.message, component);
diff --git a/packages/playground/src/pages/OpenInvoices/OpenInvoices.js b/packages/playground/src/pages/OpenInvoices/OpenInvoices.js
index f7947a2c51..a6534a1743 100644
--- a/packages/playground/src/pages/OpenInvoices/OpenInvoices.js
+++ b/packages/playground/src/pages/OpenInvoices/OpenInvoices.js
@@ -28,6 +28,12 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsData => {
         environment: process.env.__CLIENT_ENV__,
         onChange: handleChange,
         onSubmit: handleSubmit,
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        },
         onError: console.error,
         showPayButton: true,
         amount // Optional. Used to display the amount in the Pay Button.
@@ -111,7 +117,7 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsData => {
             core: window.core,
             countryCode: 'US', // 'US' / 'CA'
             visibility: {
-                personalDetails: 'editable', // editable [default] / readOnly / hidden
+                personalDetails: 'hidden', // editable [default] / readOnly / hidden
                 billingAddress: 'editable',
                 deliveryAddress: 'editable'
             },
diff --git a/packages/playground/src/pages/QRCodes/QRCodes.js b/packages/playground/src/pages/QRCodes/QRCodes.js
index 6fa7b99940..27ff0e20ea 100644
--- a/packages/playground/src/pages/QRCodes/QRCodes.js
+++ b/packages/playground/src/pages/QRCodes/QRCodes.js
@@ -6,22 +6,30 @@ import '../../../config/polyfills';
 import '../../utils';
 import '../../style.scss';
 import './QRCodes.scss';
-import { handleResponse } from '../../handlers';
 import getCurrency from '../../config/getCurrency';
 import getTranslationFile from '../../config/getTranslation';
 
-const makeQRCodePayment = (state, component, countryCode) => {
+const handleQRCodePayment = async (state, component, actions, countryCode) => {
     const currency = getCurrency(countryCode);
     const config = { countryCode, amount: { currency, value: 25940 } };
 
-    return makePayment(state.data, config)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
+    component.setStatus('loading');
+
+    try {
+        const { action, order, resultCode, donationToken } = await makePayment(state.data, config);
+
+        if (!resultCode) actions.reject();
+
+        actions.resolve({
+            resultCode,
+            action,
+            order,
+            donationToken
         });
+    } catch (error) {
+        console.error('## onSubmit - critical error', error);
+        actions.reject();
+    }
 };
 
 (async () => {
@@ -30,51 +38,57 @@ const makeQRCodePayment = (state, component, countryCode) => {
         locale: shopperLocale,
         translationFile: getTranslationFile(shopperLocale),
         environment: process.env.__CLIENT_ENV__,
-        risk: { node: 'body', onError: console.error }
+        risk: { node: 'body', onError: console.error },
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+        },
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+        }
     });
 
     // WechatPay QR
     new WeChat({
         core: checkout,
         type: 'wechatpayQR',
-        onSubmit: (state, component) => {
-            return makeQRCodePayment(state, component, 'CN');
+        onSubmit: (state, component, actions) => {
+            handleQRCodePayment(state, component, actions, 'CN');
         }
     }).mount('#wechatpayqr-container');
 
     // BCMC Mobile
     new BcmcMobile({
         core: checkout,
-        onSubmit: (state, component) => {
-            return makeQRCodePayment(state, component, 'BE');
+        onSubmit: (state, component, actions) => {
+            handleQRCodePayment(state, component, actions, 'BE');
         }
     }).mount('#bcmcqr-container');
 
     new Swish({
         core: checkout,
-        onSubmit: (state, component) => {
-            return makeQRCodePayment(state, component, 'SE');
+        onSubmit: (state, component, actions) => {
+            handleQRCodePayment(state, component, actions, 'SE');
         }
     }).mount('#swish-container');
 
     new PromptPay({
         core: checkout,
-        onSubmit: (state, component) => {
-            return makeQRCodePayment(state, component, 'TH');
+        onSubmit: (state, component, actions) => {
+            handleQRCodePayment(state, component, actions, 'TH');
         }
     }).mount('#promptpay-container');
 
     new PayNow({
         core: checkout,
-        onSubmit: (state, component) => {
-            return makeQRCodePayment(state, component, 'SG');
+        onSubmit: (state, component, actions) => {
+            handleQRCodePayment(state, component, actions, 'SG');
         }
     }).mount('#paynow-container');
 
     new DuitNow({
         core: checkout,
-        onSubmit: (state, component) => {
-            return makeQRCodePayment(state, component, 'MY');
+        onSubmit: (state, component, actions) => {
+            handleQRCodePayment(state, component, actions, 'MY');
         }
     }).mount('#duitnow-container');
 })();
diff --git a/packages/playground/src/pages/Result/Result.js b/packages/playground/src/pages/Result/Result.js
index 52001c524e..f5e63aecd0 100644
--- a/packages/playground/src/pages/Result/Result.js
+++ b/packages/playground/src/pages/Result/Result.js
@@ -10,12 +10,12 @@ async function handleRedirectResult(redirectResult, sessionId) {
         session: { id: sessionId },
         clientKey: process.env.__CLIENT_KEY__,
         environment: process.env.__CLIENT_ENV__,
-        onPaymentCompleted: result => {
-            console.log('onPaymentCompleted', result);
+        onPaymentCompleted: (result, element) => {
+            console.log('onPaymentCompleted', result, element);
             document.querySelector('#result-container > pre').innerHTML = JSON.stringify(result, null, '\t');
         },
-        onPaymentFailed: result => {
-            console.log('onPaymentFailed', result);
+        onPaymentFailed: (result, element) => {
+            console.log('onPaymentFailed', result, element);
             document.querySelector('#result-container > pre').innerHTML = JSON.stringify(result, null, '\t');
         },
         onError: obj => {
diff --git a/packages/playground/src/pages/ThreeDS/ThreeDS.js b/packages/playground/src/pages/ThreeDS/ThreeDS.js
index ca8fed6587..bfdfb31819 100644
--- a/packages/playground/src/pages/ThreeDS/ThreeDS.js
+++ b/packages/playground/src/pages/ThreeDS/ThreeDS.js
@@ -12,13 +12,22 @@ import getTranslationFile from '../../config/getTranslation';
         translationFile: getTranslationFile(shopperLocale),
         environment: 'test',
         clientKey: process.env.__CLIENT_KEY__,
-        onAdditionalDetails: async (state, element) => {
-            const result = await makeDetailsCall(state.data);
 
-            if (result.action) {
-                component.handleAction(result.action);
-            } else {
-                alert(result.resultCode);
+        onAdditionalDetails: async (state, element, actions) => {
+            try {
+                const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                });
+            } catch (error) {
+                console.error('## onAdditionalDetails - critical error', error);
+                actions.reject();
             }
         }
     });
diff --git a/packages/playground/src/pages/Wallets/Wallets.js b/packages/playground/src/pages/Wallets/Wallets.js
index fb9c3a31d5..b526e10d8b 100644
--- a/packages/playground/src/pages/Wallets/Wallets.js
+++ b/packages/playground/src/pages/Wallets/Wallets.js
@@ -22,14 +22,12 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
         onError(error) {
             console.log(error);
         },
-
         onPaymentCompleted(result, element) {
             console.log('onPaymentCompleted', result, element);
         },
         onPaymentFailed(result, element) {
             console.log('onPaymentFailed', result, element);
         },
-
         showPayButton: true
     });
 
@@ -162,43 +160,21 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
     // GOOGLE PAY
     const googlepay = new GooglePay({
         core: window.checkout,
-        // environment: 'PRODUCTION',
         environment: 'TEST',
 
         // Callbacks
         onAuthorized(data, actions) {
             console.log('onAuthorized', data, actions);
-
-            actions.reject();
-
-            // actions.reject('Failed with string');
-
-            // actions.reject({
-            //     intent: 'PAYMENT_AUTHORIZATION',
-            //     reason: 'OTHER_ERROR',
-            //     message: 'Failed with object'
-            // });
+            actions.resolve();
         },
 
-        // onError: console.error,
-
         // Payment info
         countryCode: 'NL',
 
-        // Merchant config (required)
-        //            configuration: {
-        //                gatewayMerchantId: 'TestMerchant', // name of MerchantAccount
-        //                merchantName: 'Adyen Test merchant', // Name to be displayed
-        //                merchantId: '06946223745213860250' // Required in Production environment. Google's merchantId: https://developers.google.com/pay/api/web/guides/test-and-deploy/deploy-production-environment#obtain-your-merchantID
-        //            },
-
         // Shopper info (optional)
         emailRequired: true,
-
         billingAddressRequired: true,
-
         shippingAddressRequired: true,
-        // shippingAddressParameters: {}, // https://developers.google.com/pay/api/web/reference/object#ShippingAddressParameters
 
         // Button config (optional)
         buttonType: 'long', // https://developers.google.com/pay/api/web/reference/object#ButtonOptions
@@ -222,9 +198,9 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
             console.log('Apple Pay - Button clicked');
             resolve();
         },
-        onAuthorized: (resolve, reject, event) => {
+        onAuthorized: (data, actions) => {
             console.log('Apple Pay onAuthorized', event);
-            resolve();
+            actions.resolve();
         },
         buttonType: 'buy'
     });

From be9b5919fd744625f3cb0139282450dc3c589d66 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 23 Jan 2024 09:15:14 -0300
Subject: [PATCH 36/55] fixing storybook

---
 packages/lib/storybook/helpers/checkout-handlers.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/lib/storybook/helpers/checkout-handlers.ts b/packages/lib/storybook/helpers/checkout-handlers.ts
index fa99b2780e..00d4fcfc25 100644
--- a/packages/lib/storybook/helpers/checkout-handlers.ts
+++ b/packages/lib/storybook/helpers/checkout-handlers.ts
@@ -72,11 +72,11 @@ export async function handleResponse(response, component, checkout?, paymentData
     handleFinalState(response, component);
 }
 
-export function handleChange(state: any, component: UIElement) {
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function handleChange(state: any, _component: UIElement) {
     console.groupCollapsed(`onChange - ${state.data.paymentMethod.type}`);
     console.log('isValid', state.isValid);
     console.log('data', state.data);
-    console.log('node', component._node);
     console.log('state', state);
     console.groupEnd();
 }

From d1df1a3c81c2703f69905b34e6d7e819cd7a6acd Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 23 Jan 2024 09:18:12 -0300
Subject: [PATCH 37/55] storybook

---
 packages/lib/src/types/global-types.ts        |  6 +-
 .../storybook/helpers/checkout-api-calls.ts   | 19 +++--
 .../helpers/create-advanced-checkout.ts       | 75 +++++++++++--------
 .../helpers/create-sessions-checkout.ts       | 12 ++-
 packages/lib/storybook/stories/Container.tsx  |  2 +-
 .../storybook/stories/cards/Card.stories.tsx  |  1 -
 .../wallets/ApplePayExpress.stories.tsx       | 42 +++++++----
 .../wallets/GooglePayExpress.stories.tsx      | 44 +++++++----
 8 files changed, 127 insertions(+), 74 deletions(-)

diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index a9b028b7b0..91890daa18 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -369,11 +369,7 @@ export type RawPaymentResponse = PaymentResponseData &
         [key: string]: any;
     };
 
-export type ActionDescriptionType =
-    | 'qr-code-loaded'
-    | 'polling-started'
-    | 'fingerprint-iframe-loaded'
-    | 'challenge-iframe-loaded';
+export type ActionDescriptionType = 'qr-code-loaded' | 'polling-started' | 'fingerprint-iframe-loaded' | 'challenge-iframe-loaded';
 
 export interface ActionHandledReturnObject {
     componentType: string;
diff --git a/packages/lib/storybook/helpers/checkout-api-calls.ts b/packages/lib/storybook/helpers/checkout-api-calls.ts
index 8254f1b17f..5bbaa9ebb5 100644
--- a/packages/lib/storybook/helpers/checkout-api-calls.ts
+++ b/packages/lib/storybook/helpers/checkout-api-calls.ts
@@ -1,8 +1,17 @@
 import paymentMethodsConfig from '../config/paymentMethodsConfig';
 import paymentsConfig from '../config/paymentsConfig';
 import { httpPost } from '../utils/http-post';
-import { RawPaymentResponse } from '../../src/components/types';
-import { CheckoutSessionSetupResponse, Order, OrderStatus, PaymentAction, PaymentAmount, PaymentMethodsResponse } from '../../src/types';
+import {
+    Order,
+    OrderStatus,
+    PaymentAction,
+    PaymentAmount,
+    PaymentMethodsResponse,
+    RawPaymentResponse,
+    AdditionalDetailsStateData,
+    ResultCode
+} from '../../src/types';
+import { CheckoutSessionSetupResponse } from '../../src/core/CheckoutSession/types';
 
 export const getPaymentMethods = async (configuration?: any): Promise<PaymentMethodsResponse> =>
     await httpPost('paymentMethods', { ...paymentMethodsConfig, ...configuration });
@@ -13,9 +22,9 @@ export const makePayment = async (stateData: any, paymentData: any): Promise<Raw
     return await httpPost('payments', paymentRequest);
 };
 
-export const makeDetailsCall = async (detailsData: {
-    details: { redirectResult: string };
-}): Promise<{ resultCode: string; action?: PaymentAction }> => await httpPost('details', detailsData);
+export const makeDetailsCall = async (
+    detailsData: AdditionalDetailsStateData['data']
+): Promise<{ resultCode: ResultCode; action?: PaymentAction; order?: Order; donationToken?: string }> => await httpPost('details', detailsData);
 
 export const createSession = async (data: any): Promise<CheckoutSessionSetupResponse> => {
     return await httpPost('sessions', { ...data, lineItems: paymentsConfig.lineItems });
diff --git a/packages/lib/storybook/helpers/create-advanced-checkout.ts b/packages/lib/storybook/helpers/create-advanced-checkout.ts
index c2f3efc25f..cc7d8d0c05 100644
--- a/packages/lib/storybook/helpers/create-advanced-checkout.ts
+++ b/packages/lib/storybook/helpers/create-advanced-checkout.ts
@@ -1,17 +1,12 @@
 import { AdyenCheckout } from '../../src/index';
-import { cancelOrder, checkBalance, createOrder, getPaymentMethods, makePayment } from './checkout-api-calls';
-import { handleAdditionalDetails, handleChange, handleError, handleFinalState } from './checkout-handlers';
+import { cancelOrder, checkBalance, createOrder, getPaymentMethods, makeDetailsCall, makePayment } from './checkout-api-calls';
+import { handleChange, handleError, handleFinalState } from './checkout-handlers';
 import getCurrency from '../utils/get-currency';
 import { AdyenCheckoutProps } from '../stories/types';
 import Checkout from '../../src/core/core';
 import { PaymentMethodsResponse } from '../../src/types';
 
-async function createAdvancedFlowCheckout({
-    showPayButton,
-    countryCode,
-    shopperLocale,
-    amount
-}: AdyenCheckoutProps): Promise<Checkout> {
+async function createAdvancedFlowCheckout({ showPayButton, countryCode, shopperLocale, amount }: AdyenCheckoutProps): Promise<Checkout> {
     const paymentAmount = {
         currency: getCurrency(countryCode),
         value: Number(amount)
@@ -40,34 +35,52 @@ async function createAdvancedFlowCheckout({
                     shopperLocale
                 };
 
-                const result = await makePayment(state.data, paymentData);
-
-                // happpy flow
-                if (result.resultCode.includes('Refused', 'Cancelled', 'Error')) {
-                    actions.reject({
-                        error: {
-                            googlePayError: {}
-                        }
-                    });
-                } else {
-                    actions.resolve({
-                        action: result.action,
-                        order: result.order,
-                        resultCode: result.resultCode
-                    });
-                }
+                const { action, order, resultCode, donationToken } = await makePayment(state.data, paymentData);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                });
             } catch (error) {
-                // Something failed in the request
+                console.error('## onSubmit - critical error', error);
                 actions.reject();
             }
         },
 
-        onChange: (state, component) => {
-            handleChange(state, component);
+        onAdditionalDetails: async (state, component, actions) => {
+            try {
+                const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
+
+                if (!resultCode) actions.reject();
+
+                actions.resolve({
+                    resultCode,
+                    action,
+                    order,
+                    donationToken
+                });
+            } catch (error) {
+                console.error('## onAdditionalDetails - critical error', error);
+                actions.reject();
+            }
+        },
+
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+            handleFinalState(result, element);
         },
 
-        onAdditionalDetails: async (state, component) => {
-            await handleAdditionalDetails(state, component, checkout);
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+            handleFinalState(result, element);
+        },
+
+        onChange: (state, component) => {
+            handleChange(state, component);
         },
 
         onBalanceCheck: async (resolve, reject, data) => {
@@ -103,10 +116,6 @@ async function createAdvancedFlowCheckout({
 
         onError: (error, component) => {
             handleError(error, component);
-        },
-
-        onPaymentCompleted: (result, component) => {
-            handleFinalState(result, component);
         }
     });
 
diff --git a/packages/lib/storybook/helpers/create-sessions-checkout.ts b/packages/lib/storybook/helpers/create-sessions-checkout.ts
index d4fcfd988e..1b2a3f5b5a 100644
--- a/packages/lib/storybook/helpers/create-sessions-checkout.ts
+++ b/packages/lib/storybook/helpers/create-sessions-checkout.ts
@@ -25,13 +25,19 @@ async function createSessionsCheckout({ showPayButton, countryCode, shopperLocal
         environment: process.env.CLIENT_ENV,
         session,
         showPayButton,
-        // @ts-ignore TODO: Fix beforeSubmit type
+
         beforeSubmit: (data, component, actions) => {
             actions.resolve(data);
         },
 
-        onPaymentCompleted: (result, component) => {
-            handleFinalState(result, component);
+        onPaymentCompleted(result, element) {
+            console.log('onPaymentCompleted', result, element);
+            handleFinalState(result, element);
+        },
+
+        onPaymentFailed(result, element) {
+            console.log('onPaymentFailed', result, element);
+            handleFinalState(result, element);
         },
 
         onError: (error, component) => {
diff --git a/packages/lib/storybook/stories/Container.tsx b/packages/lib/storybook/stories/Container.tsx
index f0e1f4798e..ef0a635a41 100644
--- a/packages/lib/storybook/stories/Container.tsx
+++ b/packages/lib/storybook/stories/Container.tsx
@@ -1,5 +1,5 @@
 import { useEffect, useRef, useState } from 'preact/hooks';
-import { IUIElement } from '../../src/components/types';
+import { IUIElement } from '../../src/components/internal/UIElement/types';
 
 interface IContainer {
     element: IUIElement;
diff --git a/packages/lib/storybook/stories/cards/Card.stories.tsx b/packages/lib/storybook/stories/cards/Card.stories.tsx
index 78e6060738..72164d8302 100644
--- a/packages/lib/storybook/stories/cards/Card.stories.tsx
+++ b/packages/lib/storybook/stories/cards/Card.stories.tsx
@@ -64,7 +64,6 @@ export const WithInstallments: CardStory = {
     args: {
         componentConfiguration: {
             _disableClickToPay: true,
-            showBrandsUnderCardNumber: true,
             showInstallmentAmounts: true,
             installmentOptions: {
                 mc: {
diff --git a/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx b/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx
index 3687e3d160..5715049d46 100644
--- a/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx
+++ b/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx
@@ -2,9 +2,9 @@ import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '
 import { getStoryContextCheckout } from '../../utils/get-story-context-checkout';
 import { Container } from '../Container';
 import { ApplePayConfiguration } from '../../../src/components/ApplePay/types';
-import { handleSubmit } from '../../helpers/checkout-handlers';
 import getCurrency from '../../utils/get-currency';
 import { ApplePay } from '../../../src';
+import { makePayment } from '../../helpers/checkout-api-calls';
 
 type ApplePayStory = StoryConfiguration<ApplePayConfiguration>;
 
@@ -154,20 +154,36 @@ export const Express: ApplePayStory = {
         componentConfiguration: {
             countryCode: COUNTRY_CODE,
 
-            onSubmit: (state, component) => {
-                const paymentData = {
-                    amount: {
-                        currency: getCurrency(COUNTRY_CODE),
-                        value: ApplePayAmountHelper.getFinalAdyenAmount()
-                    },
-                    countryCode: COUNTRY_CODE,
-                    shopperLocale: SHOPPER_LOCALE
-                };
-                handleSubmit(state, component, null, paymentData);
+            onSubmit: async (state, component, actions) => {
+                try {
+                    const paymentData = {
+                        amount: {
+                            currency: getCurrency(COUNTRY_CODE),
+                            value: ApplePayAmountHelper.getFinalAdyenAmount()
+                        },
+                        countryCode: COUNTRY_CODE,
+                        shopperLocale: SHOPPER_LOCALE
+                    };
+
+                    const { action, order, resultCode, donationToken } = await makePayment(state.data, paymentData);
+
+                    if (!resultCode) actions.reject();
+
+                    actions.resolve({
+                        resultCode,
+                        action,
+                        order,
+                        donationToken
+                    });
+                } catch (error) {
+                    console.error('## onSubmit - critical error', error);
+                    actions.reject();
+                }
             },
 
-            onAuthorized: paymentData => {
-                console.log('Shopper details', paymentData);
+            onAuthorized: (data, actions) => {
+                console.log('Authorized event', data);
+                actions.resolve();
             },
 
             onShippingContactSelected: async (resolve, reject, event) => {
diff --git a/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx b/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx
index 0a61a09004..03226ecdc2 100644
--- a/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx
+++ b/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx
@@ -2,9 +2,9 @@ import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '
 import { getStoryContextCheckout } from '../../utils/get-story-context-checkout';
 import { Container } from '../Container';
 import { GooglePayConfiguration } from '../../../src/components/GooglePay/types';
-import { handleSubmit } from '../../helpers/checkout-handlers';
 import getCurrency from '../../utils/get-currency';
 import { GooglePay } from '../../../src';
+import { makePayment } from '../../helpers/checkout-api-calls';
 
 type GooglePayStory = StoryConfiguration<GooglePayConfiguration>;
 
@@ -171,20 +171,38 @@ export const Express: GooglePayStory = {
         amount: INITIAL_AMOUNT,
         shopperLocale: SHOPPER_LOCALE,
         componentConfiguration: {
-            onSubmit: (state, component) => {
-                const paymentData = {
-                    amount: {
-                        currency: getCurrency(COUNTRY_CODE),
-                        value: finalAmount
-                    },
-                    countryCode: COUNTRY_CODE,
-                    shopperLocale: SHOPPER_LOCALE
-                };
-                handleSubmit(state, component, null, paymentData);
+            onSubmit: async (state, component, actions) => {
+                try {
+                    const paymentData = {
+                        amount: {
+                            currency: getCurrency(COUNTRY_CODE),
+                            value: finalAmount
+                        },
+                        countryCode: COUNTRY_CODE,
+                        shopperLocale: SHOPPER_LOCALE
+                    };
+
+                    const { action, order, resultCode, donationToken } = await makePayment(state.data, paymentData);
+
+                    if (!resultCode) actions.reject();
+
+                    actions.resolve({
+                        resultCode,
+                        action,
+                        order,
+                        donationToken
+                    });
+                } catch (error) {
+                    console.error('## onSubmit - critical error', error);
+                    actions.reject();
+                }
             },
-            onAuthorized: paymentData => {
-                console.log('Shopper details', paymentData);
+
+            onAuthorized: (data, actions) => {
+                console.log('Authorized data', data);
+                actions.resolve();
             },
+
             transactionInfo: getTransactionInfo(),
 
             callbackIntents: ['SHIPPING_ADDRESS', 'SHIPPING_OPTION'],

From 20b273f8a62ec95fb2680a223f92601013e91b34 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 23 Jan 2024 09:40:17 -0300
Subject: [PATCH 38/55] fix googletest

---
 .../components/GooglePay/GooglePay.test.ts    | 25 ++++++++-----------
 1 file changed, 11 insertions(+), 14 deletions(-)

diff --git a/packages/lib/src/components/GooglePay/GooglePay.test.ts b/packages/lib/src/components/GooglePay/GooglePay.test.ts
index a5e3fd83d5..4e42386dc0 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.test.ts
+++ b/packages/lib/src/components/GooglePay/GooglePay.test.ts
@@ -97,16 +97,16 @@ describe('GooglePay', () => {
                 postalCode: '94043',
                 street: ''
             });
-            expect(state.data.browserInfo).toStrictEqual({
-                acceptHeader: '*/*',
-                colorDepth: 24,
-                javaEnabled: false,
-                language: 'en-US',
-                screenHeight: '',
-                screenWidth: '',
-                timeZoneOffset: 360,
-                userAgent: 'Mozilla/5.0 (linux) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/20.0.3'
-            });
+
+            const browserInfo = state.data.browserInfo;
+
+            expect(browserInfo.colorDepth).toEqual(expect.any(Number));
+            expect(browserInfo.javaEnabled).toEqual(expect.any(Boolean));
+            expect(browserInfo.language).toEqual(expect.any(String));
+            expect(browserInfo.screenHeight).toEqual('');
+            expect(browserInfo.screenWidth).toEqual('');
+            expect(browserInfo.timeZoneOffset).toEqual(expect.any(Number));
+            expect(browserInfo.userAgent).toEqual(expect.any(String));
 
             await new Promise(process.nextTick);
 
@@ -184,10 +184,7 @@ describe('GooglePay', () => {
                 transactionState: 'ERROR'
             });
 
-            expect(onPaymentFailedMock).toHaveBeenCalledWith(
-                { resultCode: 'Refused', error: { googlePayError: 'Insufficient funds' } },
-                gpay
-            );
+            expect(onPaymentFailedMock).toHaveBeenCalledWith({ resultCode: 'Refused', error: { googlePayError: 'Insufficient funds' } }, gpay);
         });
     });
 

From 0aaa38916277ddd3ca91fdeac39413a5dce1d800 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 23 Jan 2024 10:56:53 -0300
Subject: [PATCH 39/55] fix: updating e2e playwright

---
 packages/e2e-playwright/app/src/handlers.js   | 64 ++++++++++---------
 .../e2e-playwright/app/src/pages/ANCV/ANCV.js |  3 +-
 .../app/src/pages/Cards/Cards.js              |  3 +-
 .../app/src/pages/CustomCards/CustomCards.js  | 18 ++++--
 .../app/src/pages/Dropin/Dropin.js            |  3 +-
 .../app/src/pages/IssuerLists/IssuerLists.js  |  3 +-
 6 files changed, 55 insertions(+), 39 deletions(-)

diff --git a/packages/e2e-playwright/app/src/handlers.js b/packages/e2e-playwright/app/src/handlers.js
index 6020ee05f6..15c1f15a87 100644
--- a/packages/e2e-playwright/app/src/handlers.js
+++ b/packages/e2e-playwright/app/src/handlers.js
@@ -1,24 +1,11 @@
 import { makePayment, makeDetailsCall, createOrder } from './services';
 
-function removeComponent(component) {
-    component.remove();
-}
-
 export function showAuthorised(message = 'Authorised') {
     const resultElement = document.getElementById('result-message');
     resultElement.classList.remove('hide');
     resultElement.innerText = message;
 }
 
-export function handleResponse(response, component) {
-    if (response.action) {
-        component.handleAction(response.action, window.actionConfigObject || {});
-    } else if (response.resultCode) {
-        component.remove();
-        showAuthorised();
-    }
-}
-
 export function handleError(obj) {
     // SecuredField related errors should not go straight to console.error
     if (obj.type === 'card') {
@@ -28,28 +15,45 @@ export function handleError(obj) {
     }
 }
 
-export function handleSubmit(state, component) {
-    component.setStatus('loading');
+export async function handleSubmit(state, component, actions) {
+    try {
+        const { action, order, resultCode, donationToken } = await makePayment(state.data);
 
-    return makePayment(state.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
+        if (!resultCode) actions.reject();
+
+        actions.resolve({
+            resultCode,
+            action,
+            order,
+            donationToken
         });
+    } catch (error) {
+        console.error('## onSubmit - critical error', error);
+        actions.reject();
+    }
 }
 
-export function handleAdditionalDetails(details, component) {
-    return makeDetailsCall(details.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
+export async function handleAdditionalDetails(state, component, actions) {
+    try {
+        const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
+
+        if (!resultCode) actions.reject();
+
+        actions.resolve({
+            resultCode,
+            action,
+            order,
+            donationToken
         });
+    } catch (error) {
+        console.error('## onAdditionalDetails - critical error', error);
+        actions.reject();
+    }
+}
+
+export function handlePaymentCompleted(data, component) {
+    component.remove();
+    showAuthorised();
 }
 
 export function handleOrderRequest(resolve, reject, data) {
diff --git a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js b/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js
index 5c0c7df19e..878b63663b 100644
--- a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js
+++ b/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, ANCV } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleError, showAuthorised } from '../../handlers';
+import { handleError, handlePaymentCompleted, showAuthorised } from '../../handlers';
 import { shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 import { createSession } from '../../services';
@@ -27,6 +27,7 @@ const initCheckout = async () => {
         locale: shopperLocale,
         countryCode,
         showPayButton: true,
+        onPaymentCompleted: handlePaymentCompleted,
         onOrderUpdated: data => {
             showAuthorised('Partially Authorised');
         },
diff --git a/packages/e2e-playwright/app/src/pages/Cards/Cards.js b/packages/e2e-playwright/app/src/pages/Cards/Cards.js
index d6c5777ab7..09feb6fd5e 100644
--- a/packages/e2e-playwright/app/src/pages/Cards/Cards.js
+++ b/packages/e2e-playwright/app/src/pages/Cards/Cards.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, Card } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 
@@ -15,6 +15,7 @@ const initCheckout = async () => {
         showPayButton: true,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError,
         ...window.mainConfiguration
     });
diff --git a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.js b/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.js
index 1366581bdc..9248945ebe 100644
--- a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.js
+++ b/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.js
@@ -1,10 +1,11 @@
 import { AdyenCheckout, CustomCard } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handlePaymentCompleted, showAuthorised } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 import './customcards.style.scss';
 import { setFocus, onBrand, onConfigSuccess, onBinLookup, onChange } from './customCards.config';
+import { makePayment } from '../../services';
 
 const initCheckout = async () => {
     // window.TextEncoder = null; // Comment in to force use of "compat" version
@@ -15,8 +16,7 @@ const initCheckout = async () => {
         countryCode,
         environment: 'test',
         showPayButton: true,
-        onSubmit: handleSubmit,
-        onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         ...window.mainConfiguration
     });
 
@@ -56,7 +56,7 @@ const initCheckout = async () => {
         payBtn.setAttribute('data-testid', `pay-${attribute}`);
         payBtn.classList.add('adyen-checkout__button', 'js-components-button--one-click', `js-pay-${attribute}`);
 
-        payBtn.addEventListener('click', e => {
+        payBtn.addEventListener('click', async e => {
             e.preventDefault();
 
             console.log('### CustomCards::createPayButton:: click attribut', attribute);
@@ -70,7 +70,15 @@ const initCheckout = async () => {
             };
             component.state.data = { paymentMethod };
 
-            handleSubmit(component.state, component);
+            const response = await makePayment(component.state.data);
+            component.setStatus('ready');
+
+            if (response.action) {
+                component.handleAction(response.action, window.actionConfigObject || {});
+            } else if (response.resultCode) {
+                component.remove();
+                showAuthorised();
+            }
         });
 
         document.querySelector(parent).appendChild(payBtn);
diff --git a/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js b/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js
index b57600d2a1..20d3da7869 100644
--- a/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js
+++ b/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js
@@ -2,7 +2,7 @@ import { AdyenCheckout, Dropin } from '@adyen/adyen-web/auto';
 import '@adyen/adyen-web/styles/adyen.css';
 import { getPaymentMethods } from '../../services';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import '../../style.scss';
 
 const initCheckout = async () => {
@@ -19,6 +19,7 @@ const initCheckout = async () => {
         environment: 'test',
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError,
         ...window.mainConfiguration
     });
diff --git a/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.js b/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.js
index 7886d24b1f..c94c3ba257 100644
--- a/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.js
+++ b/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, Ideal } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 import { getPaymentMethods } from '../../services';
@@ -24,6 +24,7 @@ const initCheckout = async () => {
         showPayButton: true,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError
         // ...window.mainConfiguration
     });

From cd8561da6eb3240006030b6b601c7db99aa0da1a Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 23 Jan 2024 11:04:02 -0300
Subject: [PATCH 40/55] updating size-limit

---
 packages/lib/.size-limit.cjs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/packages/lib/.size-limit.cjs b/packages/lib/.size-limit.cjs
index e899827a15..72647072d3 100644
--- a/packages/lib/.size-limit.cjs
+++ b/packages/lib/.size-limit.cjs
@@ -5,7 +5,7 @@ module.exports = [
     {
         name: 'UMD',
         path: 'dist/umd/adyen.js',
-        limit: '215 KB',
+        limit: '230 KB',
         running: false,
     },
     /**
@@ -15,7 +15,7 @@ module.exports = [
         name: 'Auto',
         path: 'auto/auto.js',
         import: "{ AdyenCheckout, Dropin }",
-        limit: '130 KB',
+        limit: '135 KB',
         running: false,
     },
     /**
@@ -32,21 +32,21 @@ module.exports = [
         name: 'ESM - Core + Card',
         path: 'dist/es/index.js',
         import: "{ AdyenCheckout, Card }",
-        limit: '70 KB',
+        limit: '75 KB',
         running: false,
     },
     {
         name: 'ESM - Core + Dropin with Card and Ideal',
         path: 'dist/es/index.js',
         import: "{ AdyenCheckout, Dropin, Card, Ideal }",
-        limit: '75 KB',
+        limit: '80 KB',
         running: false,
     },
     {
         name: 'ESM - Core + Dropin with Card and multiple languages',
         path: 'dist/es/index.js',
         import: "{ AdyenCheckout, Dropin, Card, pt_BR, nl_NL, es_ES }",
-        limit: '90 KB',
+        limit: '95 KB',
         running: false,
     },
 ]

From c198dd922414565281c04b1fc59db719c2d748b9 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Tue, 23 Jan 2024 16:25:33 -0300
Subject: [PATCH 41/55] e2e testcafe: updating files

---
 packages/e2e/app/src/handlers.js              | 53 +++++++++++------
 packages/e2e/app/src/pages/Cards/Cards.js     | 48 ++++++++--------
 .../app/src/pages/CustomCards/CustomCards.js  | 57 +++++++++++--------
 packages/e2e/app/src/pages/Dropin/Dropin.js   |  5 +-
 .../e2e/app/src/pages/GiftCards/GiftCards.js  | 24 ++++----
 .../app/src/pages/IssuerLists/IssuerLists.js  |  3 +-
 .../src/pages/OpenInvoices/OpenInvoices.js    |  5 +-
 .../app/src/pages/StoredCards/StoredCards.js  |  5 +-
 packages/e2e/app/src/utils/handlers.js        | 37 ------------
 .../onOrderUpdated/onOrderUpdated.test.js     |  2 +-
 10 files changed, 115 insertions(+), 124 deletions(-)
 delete mode 100644 packages/e2e/app/src/utils/handlers.js

diff --git a/packages/e2e/app/src/handlers.js b/packages/e2e/app/src/handlers.js
index fdea9a217d..26a7b5d317 100644
--- a/packages/e2e/app/src/handlers.js
+++ b/packages/e2e/app/src/handlers.js
@@ -17,26 +17,43 @@ export function handleError(obj) {
     }
 }
 
-export function handleSubmit(state, component) {
-    component.setStatus('loading');
-
-    return makePayment(state.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
+export async function handleSubmit(state, component, actions) {
+    try {
+        const { action, order, resultCode, donationToken } = await makePayment(state.data);
+
+        if (!resultCode) actions.reject();
+
+        actions.resolve({
+            resultCode,
+            action,
+            order,
+            donationToken
         });
+    } catch (error) {
+        console.error('## onSubmit - critical error', error);
+        actions.reject();
+    }
 }
 
-export function handleAdditionalDetails(details, component) {
-    return makeDetailsCall(details.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
+export async function handleAdditionalDetails(state, component, actions) {
+    try {
+        const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data);
+
+        if (!resultCode) actions.reject();
+
+        actions.resolve({
+            resultCode,
+            action,
+            order,
+            donationToken
         });
+    } catch (error) {
+        console.error('## onAdditionalDetails - critical error', error);
+        actions.reject();
+    }
+}
+
+export function handlePaymentCompleted(data, component) {
+    component.remove();
+    alert(data.resultCode);
 }
diff --git a/packages/e2e/app/src/pages/Cards/Cards.js b/packages/e2e/app/src/pages/Cards/Cards.js
index e5216eff92..09feb6fd5e 100644
--- a/packages/e2e/app/src/pages/Cards/Cards.js
+++ b/packages/e2e/app/src/pages/Cards/Cards.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, Card } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 
@@ -15,35 +15,35 @@ const initCheckout = async () => {
         showPayButton: true,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError,
         ...window.mainConfiguration
     });
 
     // Credit card with installments
     window.card = new Card({
-            core: checkout,
-            brands: ['mc', 'visa', 'amex', 'maestro', 'bcmc'],
-            onChange: state => {
-                /**
-                 * Needed now that, for v5, we enhance the securedFields state.errors object with a rootNode prop
-                 *  - Testcafe doesn't like a ClientFunction retrieving an object with a DOM node in it!?
-                 *
-                 *  AND, for some reason, if you place this onChange function in expiryDate.clientScripts.js it doesn't always get read.
-                 *  It'll work when it's part of a small batch but if part of the full test suite it gets ignored - so the tests that rely on
-                 *  window.mappedStateErrors fail
-                 */
-                if (!!Object.keys(state.errors).length) {
-                    // Replace any rootNode values in the objects in state.errors with an empty string
-                    const nuErrors = Object.entries(state.errors).reduce((acc, [fieldType, error]) => {
-                        acc[fieldType] = error ? { ...error, rootNode: '' } : error;
-                        return acc;
-                    }, {});
-                    window.mappedStateErrors = nuErrors;
-                }
-            },
-            ...window.cardConfig
-        })
-        .mount('.card-field');
+        core: checkout,
+        brands: ['mc', 'visa', 'amex', 'maestro', 'bcmc'],
+        onChange: state => {
+            /**
+             * Needed now that, for v5, we enhance the securedFields state.errors object with a rootNode prop
+             *  - Testcafe doesn't like a ClientFunction retrieving an object with a DOM node in it!?
+             *
+             *  AND, for some reason, if you place this onChange function in expiryDate.clientScripts.js it doesn't always get read.
+             *  It'll work when it's part of a small batch but if part of the full test suite it gets ignored - so the tests that rely on
+             *  window.mappedStateErrors fail
+             */
+            if (!!Object.keys(state.errors).length) {
+                // Replace any rootNode values in the objects in state.errors with an empty string
+                const nuErrors = Object.entries(state.errors).reduce((acc, [fieldType, error]) => {
+                    acc[fieldType] = error ? { ...error, rootNode: '' } : error;
+                    return acc;
+                }, {});
+                window.mappedStateErrors = nuErrors;
+            }
+        },
+        ...window.cardConfig
+    }).mount('.card-field');
 };
 
 initCheckout();
diff --git a/packages/e2e/app/src/pages/CustomCards/CustomCards.js b/packages/e2e/app/src/pages/CustomCards/CustomCards.js
index ee021aee4a..a3a26f274f 100644
--- a/packages/e2e/app/src/pages/CustomCards/CustomCards.js
+++ b/packages/e2e/app/src/pages/CustomCards/CustomCards.js
@@ -1,10 +1,12 @@
-import { AdyenCheckout, CustomCard} from '@adyen/adyen-web';
+import { AdyenCheckout, CustomCard } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
 import { handleSubmit, handleAdditionalDetails } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 import './customcards.style.scss';
 import { setFocus, onBrand, onConfigSuccess, onBinLookup, onChange } from './customCards.config';
+import { makePayment } from '@adyen/adyen-web-playwright/app/src/services';
+import { showAuthorised } from '@adyen/adyen-web-playwright/app/src/handlers';
 
 const initCheckout = async () => {
     // window.TextEncoder = null; // Comment in to force use of "compat" version
@@ -21,32 +23,30 @@ const initCheckout = async () => {
     });
 
     window.securedFields = new CustomCard({
-            core: checkout,
-            type: 'card',
-            brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro', 'cartebancaire'],
-            onConfigSuccess,
-            onBrand,
-            onFocus: setFocus,
-            onBinLookup,
-            onChange,
-            ...window.cardConfig
-        })
-        .mount('.secured-fields');
+        core: checkout,
+        type: 'card',
+        brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro', 'cartebancaire'],
+        onConfigSuccess,
+        onBrand,
+        onFocus: setFocus,
+        onBinLookup,
+        onChange,
+        ...window.cardConfig
+    }).mount('.secured-fields');
 
     createPayButton('.secured-fields', window.securedFields, 'securedfields');
 
     window.securedFields2 = new CustomCard({
-            core: checkout,
-            //            type: 'card',// Deliberately exclude to ensure a default value is set
-            brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro', 'cartebancaire'],
-            onConfigSuccess,
-            onBrand,
-            onFocus: setFocus,
-            onBinLookup,
-            onChange,
-            ...window.cardConfig
-        })
-        .mount('.secured-fields-2');
+        core: checkout,
+        //            type: 'card',// Deliberately exclude to ensure a default value is set
+        brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro', 'cartebancaire'],
+        onConfigSuccess,
+        onBrand,
+        onFocus: setFocus,
+        onBinLookup,
+        onChange,
+        ...window.cardConfig
+    }).mount('.secured-fields-2');
 
     createPayButton('.secured-fields-2', window.securedFields2, 'securedfields2');
 
@@ -57,7 +57,7 @@ const initCheckout = async () => {
         payBtn.name = 'pay';
         payBtn.classList.add('adyen-checkout__button', 'js-components-button--one-click', `js-${attribute}`);
 
-        payBtn.addEventListener('click', e => {
+        payBtn.addEventListener('click', async e => {
             e.preventDefault();
 
             if (!component.isValid) return component.showValidation();
@@ -69,7 +69,14 @@ const initCheckout = async () => {
             };
             component.state.data = { paymentMethod };
 
-            handleSubmit(component.state, component);
+            const response = await makePayment(component.state.data);
+            component.setStatus('ready');
+
+            if (response.action) {
+                component.handleAction(response.action, window.actionConfigObject || {});
+            } else if (response.resultCode) {
+                alert(response.resultCode);
+            }
         });
 
         document.querySelector(parent).appendChild(payBtn);
diff --git a/packages/e2e/app/src/pages/Dropin/Dropin.js b/packages/e2e/app/src/pages/Dropin/Dropin.js
index 6550046278..64a201424e 100644
--- a/packages/e2e/app/src/pages/Dropin/Dropin.js
+++ b/packages/e2e/app/src/pages/Dropin/Dropin.js
@@ -2,7 +2,7 @@ import { AdyenCheckout, Dropin } from '@adyen/adyen-web/auto';
 import '@adyen/adyen-web/styles/adyen.css';
 import { getPaymentMethods } from '../../services';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import '../../style.scss';
 
 const initCheckout = async () => {
@@ -17,11 +17,12 @@ const initCheckout = async () => {
         environment: 'test',
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError,
         ...window.mainConfiguration
     });
 
-    window.dropin = new Dropin({core: checkout, ...window.dropinConfig}).mount('#dropin-container');
+    window.dropin = new Dropin({ core: checkout, ...window.dropinConfig }).mount('#dropin-container');
 };
 
 initCheckout();
diff --git a/packages/e2e/app/src/pages/GiftCards/GiftCards.js b/packages/e2e/app/src/pages/GiftCards/GiftCards.js
index baa3f7cc2d..bd261c5c66 100644
--- a/packages/e2e/app/src/pages/GiftCards/GiftCards.js
+++ b/packages/e2e/app/src/pages/GiftCards/GiftCards.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, Giftcard } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import { checkBalance, createOrder } from '../../services';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
@@ -15,22 +15,22 @@ const initCheckout = async () => {
         showPayButton: true,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError,
         ...window.mainConfiguration
     });
 
     window.giftcard = new Giftcard({
-            core: window.checkout,
-            type: 'giftcard',
-            brand: 'valuelink',
-            onBalanceCheck: async (resolve, reject, data) => {
-                resolve(await checkBalance(data));
-            },
-            onOrderRequest: async (resolve, reject) => {
-                resolve(await createOrder({ amount }));
-            }
-        })
-        .mount('.card-field');
+        core: window.checkout,
+        type: 'giftcard',
+        brand: 'valuelink',
+        onBalanceCheck: async (resolve, reject, data) => {
+            resolve(await checkBalance(data));
+        },
+        onOrderRequest: async (resolve, reject) => {
+            resolve(await createOrder({ amount }));
+        }
+    }).mount('.card-field');
 };
 
 initCheckout();
diff --git a/packages/e2e/app/src/pages/IssuerLists/IssuerLists.js b/packages/e2e/app/src/pages/IssuerLists/IssuerLists.js
index 7886d24b1f..c94c3ba257 100644
--- a/packages/e2e/app/src/pages/IssuerLists/IssuerLists.js
+++ b/packages/e2e/app/src/pages/IssuerLists/IssuerLists.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, Ideal } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 import { getPaymentMethods } from '../../services';
@@ -24,6 +24,7 @@ const initCheckout = async () => {
         showPayButton: true,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError
         // ...window.mainConfiguration
     });
diff --git a/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.js b/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.js
index 090528f955..a0a1fe25a4 100644
--- a/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.js
+++ b/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, AfterPay } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 import { getPaymentMethods } from '../../services';
@@ -21,11 +21,12 @@ const initCheckout = async () => {
         showPayButton: true,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError
         // ...window.mainConfiguration
     });
 
-    window.afterpay = new AfterPay({core: checkout}).mount('.afterpay-field');
+    window.afterpay = new AfterPay({ core: checkout }).mount('.afterpay-field');
 };
 
 initCheckout();
diff --git a/packages/e2e/app/src/pages/StoredCards/StoredCards.js b/packages/e2e/app/src/pages/StoredCards/StoredCards.js
index 6698e203ef..bec33e2e4a 100644
--- a/packages/e2e/app/src/pages/StoredCards/StoredCards.js
+++ b/packages/e2e/app/src/pages/StoredCards/StoredCards.js
@@ -1,6 +1,6 @@
 import { AdyenCheckout, Card } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
-import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers';
+import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers';
 import { amount, shopperLocale, countryCode } from '../../services/commonConfig';
 import '../../style.scss';
 
@@ -14,6 +14,7 @@ const initCheckout = async () => {
         showPayButton: true,
         onSubmit: handleSubmit,
         onAdditionalDetails: handleAdditionalDetails,
+        onPaymentCompleted: handlePaymentCompleted,
         onError: handleError,
         ...window.mainConfiguration
     });
@@ -34,7 +35,7 @@ const initCheckout = async () => {
     };
 
     // Credit card with installments
-    window.storedCard = new Card({ core: checkout, ...storedCardData}).mount('.stored-card-field');
+    window.storedCard = new Card({ core: checkout, ...storedCardData }).mount('.stored-card-field');
 };
 
 initCheckout();
diff --git a/packages/e2e/app/src/utils/handlers.js b/packages/e2e/app/src/utils/handlers.js
deleted file mode 100644
index 35b8c29abe..0000000000
--- a/packages/e2e/app/src/utils/handlers.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { makePayment, makeDetailsCall } from '../services';
-
-export function handleResponse(response, component) {
-    if (response.action) {
-        component.handleAction(response.action);
-    } else if (response.resultCode) {
-        alert(response.resultCode);
-    }
-}
-
-export function handleError(obj) {
-    console.error(obj);
-}
-
-export function handleSubmit(state, component) {
-    component.setStatus('loading');
-
-    return makePayment(state.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
-        });
-}
-
-export function handleAdditionalDetails(details, component) {
-    return makeDetailsCall(details.data)
-        .then(response => {
-            component.setStatus('ready');
-            handleResponse(response, component);
-        })
-        .catch(error => {
-            throw Error(error);
-        });
-}
diff --git a/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js b/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
index 02734399e3..9227ff7dd2 100644
--- a/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
+++ b/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
@@ -3,7 +3,7 @@ import { ClientFunction } from 'testcafe';
 import { fillIFrame, getInputSelector } from '../../utils/commonUtils';
 import { GIFTCARD_NUMBER, GIFTCARD_PIN } from '../utils/constants';
 import { GIFTCARDS_SESSIONS_URL } from '../../pages';
-import { mock, noCallbackMock, loggers, MOCK_SESSION_DATA } from './onOrderCreated.mocks';
+import { mock, noCallbackMock, loggers, MOCK_SESSION_DATA } from './onOrderUpdated.mocks';
 
 import { GiftCardSessionPage } from '../../_models/GiftCardComponent.page';
 

From 0c5319f185c21056922e9b5f9bb55acbf856e92e Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Wed, 24 Jan 2024 15:44:00 -0300
Subject: [PATCH 42/55] fixed testcafe tests

---
 .../pages/DropinSessions/DropinSessions.js    |  4 ----
 packages/e2e/app/src/services/index.js        |  4 ++++
 .../e2e/tests/bacsDD/bacs.clientScripts.js    | 10 +++++-----
 packages/e2e/tests/bacsDD/bacs.test.js        |  2 +-
 .../cards/Bancontact/bancontact.visa.test.js  |  2 +-
 .../binLookup.branding.reset.test.js          |  4 ++--
 .../dualBranding/dualBranding.dropin.test.js  |  6 +++---
 .../startWithoutKCP/startWithoutKCP.test.js   | 16 +++++----------
 .../threeDS2/threeDS2.default.size.test.js    |  7 ++-----
 .../threeDS2.handleAction.clientScripts.js    | 20 ++++++++-----------
 .../threeDS2/threeDS2.handleAction.test.js    | 16 +++++++--------
 .../cards/threeDS2/threeDS2.redirect.test.js  |  2 +-
 .../e2e/tests/cards/threeDS2/threeDS2.test.js |  2 +-
 .../dropinSessions/dropinSessions.test.js     |  2 +-
 .../onOrderUpdated/onOrderUpdated.test.js     |  2 +-
 packages/e2e/tests/storedCard/general.test.js |  8 ++++----
 .../Dropin/components/DropinComponent.tsx     |  3 ++-
 17 files changed, 49 insertions(+), 61 deletions(-)

diff --git a/packages/e2e/app/src/pages/DropinSessions/DropinSessions.js b/packages/e2e/app/src/pages/DropinSessions/DropinSessions.js
index 6863bd18ad..5036a60c32 100644
--- a/packages/e2e/app/src/pages/DropinSessions/DropinSessions.js
+++ b/packages/e2e/app/src/pages/DropinSessions/DropinSessions.js
@@ -19,10 +19,6 @@ const initCheckout = async () => {
         clientKey: process.env.__CLIENT_KEY__,
         session,
 
-        // Events
-        beforeSubmit: (data, component, actions) => {
-            actions.resolve(data);
-        },
         onPaymentCompleted: (result, component) => {
             console.info(result, component);
         },
diff --git a/packages/e2e/app/src/services/index.js b/packages/e2e/app/src/services/index.js
index 4beb8268a5..92ccaed05a 100644
--- a/packages/e2e/app/src/services/index.js
+++ b/packages/e2e/app/src/services/index.js
@@ -20,6 +20,10 @@ export const getPaymentMethods = configuration =>
         .catch(console.error);
 
 export const makePayment = (data, config = {}) => {
+    if (data.paymentMethod.storedPaymentMethodId) {
+        config = { recurringProcessingModel: 'CardOnFile', ...config };
+    }
+
     // NOTE: Merging data object. DO NOT do this in production.
     const paymentRequest = { ...paymentsConfig, ...config, ...data };
     return httpPost('payments', paymentRequest)
diff --git a/packages/e2e/tests/bacsDD/bacs.clientScripts.js b/packages/e2e/tests/bacsDD/bacs.clientScripts.js
index f9291baf91..dce14d1e3e 100644
--- a/packages/e2e/tests/bacsDD/bacs.clientScripts.js
+++ b/packages/e2e/tests/bacsDD/bacs.clientScripts.js
@@ -1,12 +1,12 @@
 window.dropinConfig = {
-    showStoredPaymentMethods: false // hide stored PMs
-};
-
-window.mainConfiguration = {
-    removePaymentMethods: ['paywithgoogle', 'applepay'],
+    showStoredPaymentMethods: false,
     paymentMethodsConfiguration: {
         card: {
             _disableClickToPay: true
         }
     }
 };
+
+window.mainConfiguration = {
+    removePaymentMethods: ['paywithgoogle', 'applepay']
+};
diff --git a/packages/e2e/tests/bacsDD/bacs.test.js b/packages/e2e/tests/bacsDD/bacs.test.js
index 573906625b..51ba2ad39d 100644
--- a/packages/e2e/tests/bacsDD/bacs.test.js
+++ b/packages/e2e/tests/bacsDD/bacs.test.js
@@ -15,7 +15,7 @@ const validTicks = Selector('.adyen-checkout-input__inline-validation--valid');
 const voucher = Selector('.adyen-checkout__voucher-result--directdebit_GB');
 const voucherButton = Selector('.adyen-checkout__voucher-result--directdebit_GB .adyen-checkout__button--action');
 
-const TEST_SPEED = 1;
+const TEST_SPEED = 0.8;
 
 fixture`Testing BacsDD in dropin`.page(BASE_URL + '?countryCode=GB').clientScripts('bacs.clientScripts.js');
 
diff --git a/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js b/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js
index 3dafe7d7b6..8cf9fd4861 100644
--- a/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js
+++ b/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js
@@ -68,7 +68,7 @@ test('#3 Enter card number, that we mock to co-branded bcmc/visa ' + 'then compl
     await dropinPage.cc.cardUtils.fillDate(t, TEST_DATE_VALUE);
 
     // Expect comp to now be valid
-    await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true);
+    await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true, { timeout: 3000 });
 });
 
 test(
diff --git a/packages/e2e/tests/cards/dualBranding/binLookup.branding.reset.test.js b/packages/e2e/tests/cards/dualBranding/binLookup.branding.reset.test.js
index 3df8fbe5bf..c38a4d4379 100644
--- a/packages/e2e/tests/cards/dualBranding/binLookup.branding.reset.test.js
+++ b/packages/e2e/tests/cards/dualBranding/binLookup.branding.reset.test.js
@@ -7,7 +7,7 @@ import { BCMC_CARD, UNKNOWN_VISA_CARD, REGULAR_TEST_CARD } from '../utils/consta
 const cvcSpan = Selector('.adyen-checkout__dropin .adyen-checkout__field__cvc');
 
 const brandingIcon = Selector('.adyen-checkout__dropin .adyen-checkout__card__cardNumber__brandIcon');
-const dualBrandingIconHolderActive = Selector('.adyen-checkout__payment-method--card .adyen-checkout__card__dual-branding__buttons--active');
+const dualBrandingIconHolderActive = Selector('.adyen-checkout__payment-method--scheme .adyen-checkout__card__dual-branding__buttons--active');
 
 const getPropFromPMData = ClientFunction(prop => {
     return window.dropin.dropinRef.state.activePaymentMethod.formatData().paymentMethod[prop];
@@ -15,7 +15,7 @@ const getPropFromPMData = ClientFunction(prop => {
 
 const TEST_SPEED = 1;
 
-const iframeSelector = getIframeSelector('.adyen-checkout__payment-method--card iframe');
+const iframeSelector = getIframeSelector('.adyen-checkout__payment-method--scheme iframe');
 
 const cardUtils = cu(iframeSelector);
 
diff --git a/packages/e2e/tests/cards/dualBranding/dualBranding.dropin.test.js b/packages/e2e/tests/cards/dualBranding/dualBranding.dropin.test.js
index 135f0e2e9c..1cb915536f 100644
--- a/packages/e2e/tests/cards/dualBranding/dualBranding.dropin.test.js
+++ b/packages/e2e/tests/cards/dualBranding/dualBranding.dropin.test.js
@@ -4,8 +4,8 @@ import cu from '../utils/cardUtils';
 import { DUAL_BRANDED_CARD, REGULAR_TEST_CARD, DUAL_BRANDED_CARD_EXCLUDED } from '../utils/constants';
 import { BASE_URL } from '../../pages';
 
-const dualBrandingIconHolder = Selector('.adyen-checkout__payment-method--card .adyen-checkout__card__dual-branding__buttons');
-const dualBrandingIconHolderActive = Selector('.adyen-checkout__payment-method--card .adyen-checkout__card__dual-branding__buttons--active');
+const dualBrandingIconHolder = Selector('.adyen-checkout__payment-method--scheme .adyen-checkout__card__dual-branding__buttons');
+const dualBrandingIconHolderActive = Selector('.adyen-checkout__payment-method--scheme .adyen-checkout__card__dual-branding__buttons--active');
 
 const NOT_SELECTED_CLASS = 'adyen-checkout__card__cardNumber__brandIcon--not-selected';
 
@@ -15,7 +15,7 @@ const getPropFromPMData = ClientFunction(prop => {
 
 const TEST_SPEED = 1;
 
-const iframeSelector = getIframeSelector('.adyen-checkout__payment-method--card iframe');
+const iframeSelector = getIframeSelector('.adyen-checkout__payment-method--scheme iframe');
 
 const cardUtils = cu(iframeSelector);
 
diff --git a/packages/e2e/tests/cards/kcp/startWithoutKCP/startWithoutKCP.test.js b/packages/e2e/tests/cards/kcp/startWithoutKCP/startWithoutKCP.test.js
index 3627f6e733..35a5656045 100644
--- a/packages/e2e/tests/cards/kcp/startWithoutKCP/startWithoutKCP.test.js
+++ b/packages/e2e/tests/cards/kcp/startWithoutKCP/startWithoutKCP.test.js
@@ -25,7 +25,7 @@ test(
         'then complete the form & check component becomes valid',
     async t => {
         // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly
-        await t.setTestSpeed(0.9);
+        await t.setTestSpeed(0.5);
 
         // Wait for field to appear in DOM
         await cardPage.numHolder();
@@ -63,7 +63,7 @@ test(
         'then delete card number and check taxNumber and password state are cleared',
     async t => {
         // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly
-        await t.setTestSpeed(0.9);
+        await t.setTestSpeed(0.5);
 
         await cardPage.numHolder();
 
@@ -89,13 +89,7 @@ test(
         // Look for expected properties
         await t.expect(JWETokenArr.length).eql(5); // Expected number of components in the JWE token
 
-        await t
-            .expect(headerObj.alg)
-            .eql(JWE_ALG)
-            .expect(headerObj.enc)
-            .eql(JWE_CONTENT_ALG)
-            .expect(headerObj.version)
-            .eql(JWE_VERSION);
+        await t.expect(headerObj.alg).eql(JWE_ALG).expect(headerObj.enc).eql(JWE_CONTENT_ALG).expect(headerObj.version).eql(JWE_VERSION);
 
         // await t.expect(cardPage.getFromState('data.encryptedPassword')).contains('adyenjs_0_1_');
 
@@ -123,7 +117,7 @@ test(
         'then replace card number with non-korean card and expect component to be valid & to be able to pay',
     async t => {
         // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly
-        await t.setTestSpeed(0.9);
+        await t.setTestSpeed(0.5);
 
         await cardPage.numHolder();
 
@@ -168,7 +162,7 @@ test(
         'expect component not to be valid and for password field to show error',
     async t => {
         // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly
-        await t.setTestSpeed(0.9);
+        await t.setTestSpeed(0.5);
 
         await cardPage.numHolder();
 
diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.default.size.test.js b/packages/e2e/tests/cards/threeDS2/threeDS2.default.size.test.js
index 91d27ae968..0a1d0624c8 100644
--- a/packages/e2e/tests/cards/threeDS2/threeDS2.default.size.test.js
+++ b/packages/e2e/tests/cards/threeDS2/threeDS2.default.size.test.js
@@ -34,7 +34,6 @@ const loggerSubmitThreeDS2 = RequestLogger(
 fixture`Testing default size of the 3DS2 challenge window, & the challenge flows, on the Card component, since all other tests are for Dropin`
     .beforeEach(async t => {
         await t.navigateTo(cardPage.pageUrl);
-        await turnOffSDKMocking();
     })
     .clientScripts('./threeDS2.default.size.clientScripts.js')
     .requestHooks([loggerDetails, loggerSubmitThreeDS2]);
@@ -78,9 +77,7 @@ test('#1 Fill in card number that will trigger full flow (fingerprint & challeng
         .expect(loggerDetails.contains(r => r.response.statusCode === 200))
         // Allow time for the /details call, which we expect to be successful
         .ok({ timeout: 5000 })
-        .wait(1000);
-
-    // console.log(logger.requests[1].response.headers);
+        .wait(3000);
 
     // Check the value of the alert text
     const history = await t.getNativeDialogHistory();
@@ -117,7 +114,7 @@ test('#2 Fill in card number that will trigger challenge-only flow', async t =>
         .expect(loggerDetails.contains(r => r.response.statusCode === 200))
         // Allow time for the ONLY details call, which we expect to be successful
         .ok({ timeout: 5000 })
-        .wait(2000);
+        .wait(3000);
 
     // Check the value of the alert text
     const history = await t.getNativeDialogHistory();
diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.clientScripts.js b/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.clientScripts.js
index 5e1c13c4c5..eb68cff79d 100644
--- a/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.clientScripts.js
+++ b/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.clientScripts.js
@@ -1,5 +1,11 @@
 window.dropinConfig = {
-    showStoredPaymentMethods: false // hide stored PMs so credit card is first on list
+    showStoredPaymentMethods: false, // hide stored PMs so credit card is first on list
+    paymentMethodsConfiguration: {
+        card: {
+            _disableClickToPay: true,
+            challengeWindowSize: '04'
+        }
+    }
 };
 
 /**
@@ -10,15 +16,5 @@ window.dropinConfig = {
  *    at https://pay.google.com/gp/p/js/pay.js:237:404
  */
 window.mainConfiguration = {
-    removePaymentMethods: ['paywithgoogle', 'applepay'],
-    paymentMethodsConfiguration: {
-        threeDS2: {
-            challengeWindowSize: '04'
-        },
-        card: {
-            _disableClickToPay: true
-        }
-    }
+    removePaymentMethods: ['paywithgoogle', 'applepay']
 };
-
-window.actionConfigObject = { challengeWindowSize: '01' };
diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.test.js b/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.test.js
index d46accd2f4..962aa00d4b 100644
--- a/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.test.js
+++ b/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.test.js
@@ -11,7 +11,7 @@ import { turnOffSDKMocking } from '../../_common/cardMocks';
 
 const dropinPage = new DropinPage({
     components: {
-        cc: new CardComponentPage('.adyen-checkout__payment-method--card')
+        cc: new CardComponentPage('.adyen-checkout__payment-method--scheme')
     }
 });
 
@@ -32,7 +32,7 @@ const logger = RequestLogger(
 
 const apiVersion = Number(process.env.API_VERSION.substr(1));
 
-fixture`Testing new (v67) 3DS2 Flow (handleAction config)`
+fixture`Testing new (v67) 3DS2 Flow (custom challengeWindowSize config)`
     .beforeEach(async t => {
         await t.navigateTo(dropinPage.pageUrl);
         await turnOffSDKMocking();
@@ -40,7 +40,7 @@ fixture`Testing new (v67) 3DS2 Flow (handleAction config)`
     .clientScripts('threeDS2.handleAction.clientScripts.js')
     .requestHooks(logger);
 
-test('#1 Fill in card number that will trigger full flow (fingerprint & challenge) with challenge window size set in the call to handleAction', async t => {
+test('#1 Fill in card number that will trigger full flow (fingerprint & challenge) with challenge window size set in the component configuration', async t => {
     logger.clear();
 
     await dropinPage.cc.numSpan();
@@ -71,7 +71,7 @@ test('#1 Fill in card number that will trigger full flow (fingerprint & challeng
     //        console.log('logger.requests[0].response', logger.requests[0].response);
 
     // Check challenge window size is read from config prop set in handleAction call
-    await t.expect(dropinPage.challengeWindowSize01.exists).ok({ timeout: 3000 });
+    await t.expect(dropinPage.challengeWindowSize04.exists).ok({ timeout: 3000 });
 
     // Complete challenge
     await fillChallengeField(t);
@@ -82,7 +82,7 @@ test('#1 Fill in card number that will trigger full flow (fingerprint & challeng
         .wait(2000)
         .expect(logger.contains(r => r.request.url.indexOf('/details') > -1 && r.response.statusCode === 200))
         .ok()
-        .wait(1000);
+        .wait(3000);
 
     // Check request body is in the expected form
     const requestBodyBuffer = logger.requests[1].request.body;
@@ -95,7 +95,7 @@ test('#1 Fill in card number that will trigger full flow (fingerprint & challeng
     await t.expect(history[0].text).eql('Authorised');
 });
 
-test('#2 Fill in card number that will trigger challenge-only flow  with challenge window size set in the call to handleAction', async t => {
+test('#2 Fill in card number that will trigger challenge-only flow  with challenge window size set in component configuration', async t => {
     logger.clear();
 
     await dropinPage.cc.numSpan();
@@ -114,7 +114,7 @@ test('#2 Fill in card number that will trigger challenge-only flow  with challen
     await t.click(dropinPage.cc.payButton);
 
     // Check challenge window size is read from config prop set in handleAction call
-    await t.expect(dropinPage.challengeWindowSize01.exists).ok({ timeout: 3000 });
+    await t.expect(dropinPage.challengeWindowSize04.exists).ok({ timeout: 3000 });
 
     // Complete challenge
     await fillChallengeField(t);
@@ -125,7 +125,7 @@ test('#2 Fill in card number that will trigger challenge-only flow  with challen
         .wait(2000)
         .expect(logger.contains(r => r.request.url.indexOf('/details') > -1 && r.response.statusCode === 200))
         .ok()
-        .wait(2000);
+        .wait(3000);
 
     // Check the value of the alert text
     const history = await t.getNativeDialogHistory();
diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.redirect.test.js b/packages/e2e/tests/cards/threeDS2/threeDS2.redirect.test.js
index e41b40e313..131be397c7 100644
--- a/packages/e2e/tests/cards/threeDS2/threeDS2.redirect.test.js
+++ b/packages/e2e/tests/cards/threeDS2/threeDS2.redirect.test.js
@@ -15,7 +15,7 @@ import CardComponentPage from '../../_models/CardComponent.page';
 
 const dropinPage = new DropinPage({
     components: {
-        cc: new CardComponentPage('.adyen-checkout__payment-method--card')
+        cc: new CardComponentPage('.adyen-checkout__payment-method--scheme')
     }
 });
 
diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.test.js b/packages/e2e/tests/cards/threeDS2/threeDS2.test.js
index 1798be31b0..5921ce473c 100644
--- a/packages/e2e/tests/cards/threeDS2/threeDS2.test.js
+++ b/packages/e2e/tests/cards/threeDS2/threeDS2.test.js
@@ -11,7 +11,7 @@ import { turnOffSDKMocking } from '../../_common/cardMocks';
 
 const dropinPage = new DropinPage({
     components: {
-        cc: new CardComponentPage('.adyen-checkout__payment-method--card')
+        cc: new CardComponentPage('.adyen-checkout__payment-method--scheme')
     }
 });
 
diff --git a/packages/e2e/tests/dropinSessions/dropinSessions.test.js b/packages/e2e/tests/dropinSessions/dropinSessions.test.js
index fc5e554974..8d937757fd 100644
--- a/packages/e2e/tests/dropinSessions/dropinSessions.test.js
+++ b/packages/e2e/tests/dropinSessions/dropinSessions.test.js
@@ -5,7 +5,7 @@ import { getIframeSelector } from '../utils/commonUtils';
 import cu from '../cards/utils/cardUtils';
 import { TEST_CVC_VALUE } from '../cards/utils/constants';
 
-const iframeSelector = getIframeSelector('.adyen-checkout__payment-method--card iframe');
+const iframeSelector = getIframeSelector('.adyen-checkout__payment-method--scheme iframe');
 const cardUtils = cu(iframeSelector);
 const { setupLogger, paymentLogger } = loggers;
 
diff --git a/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js b/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
index 9227ff7dd2..8c66638a8d 100644
--- a/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
+++ b/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.test.js
@@ -10,7 +10,7 @@ import { GiftCardSessionPage } from '../../_models/GiftCardComponent.page';
 const giftCard = new GiftCardSessionPage();
 const { setupLogger, balanceLogger, ordersLogger } = loggers;
 
-const getCallBackData = ClientFunction(() => window.onOrderCreatedTestData);
+const getCallBackData = ClientFunction(() => window.onOrderUpdatedTestData);
 
 // only setup the loggers for the endpoints so we can setup different responses for different scenarios
 fixture`Testing gift cards`.page(GIFTCARDS_SESSIONS_URL).requestHooks([setupLogger, balanceLogger, ordersLogger]);
diff --git a/packages/e2e/tests/storedCard/general.test.js b/packages/e2e/tests/storedCard/general.test.js
index 69e05fae3d..4560e8f50f 100644
--- a/packages/e2e/tests/storedCard/general.test.js
+++ b/packages/e2e/tests/storedCard/general.test.js
@@ -23,11 +23,11 @@ test('#1 Can fill out the cvc fields in the stored card and make a successful pa
     await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0);
 
     // click pay
-    await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(1000);
+    await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000);
 
     // Check the value of the alert text
     const history = await t.getNativeDialogHistory();
-    await t.expect(history[0].text).eql('Authorised');
+    await t.expect(history[0].text).eql('Authorised', { timeout: 5000 });
 });
 
 test('#2 Pressing pay without filling the cvc should generate a translated error ("empty")', async t => {
@@ -57,9 +57,9 @@ test('#3 A storedCard with no expiry date field still can be used for a successf
     await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0);
 
     // click pay
-    await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(1000);
+    await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000);
 
     // Check the value of the alert text
     const history = await t.getNativeDialogHistory();
-    await t.expect(history[0].text).eql('Authorised');
+    await t.expect(history[0].text).eql('Authorised', { timeout: 5000 });
 }).clientScripts('./storedCard.noExpiry.clientScripts.js'); // N.B. the clientScript nullifies the expiryMonth & Year fields in the storedCardData
diff --git a/packages/lib/src/components/Dropin/components/DropinComponent.tsx b/packages/lib/src/components/Dropin/components/DropinComponent.tsx
index 3ce2e28767..977238cd30 100644
--- a/packages/lib/src/components/Dropin/components/DropinComponent.tsx
+++ b/packages/lib/src/components/Dropin/components/DropinComponent.tsx
@@ -119,6 +119,7 @@ export class DropinComponent extends Component<DropinComponentProps, DropinCompo
     render(props, { elements, instantPaymentElements, storedPaymentElements, status, activePaymentMethod, cachedPaymentMethods }) {
         const isLoading = status.type === 'loading';
         const isRedirecting = status.type === 'redirect';
+        const hasPaymentMethodsToBeDisplayed = elements?.length || instantPaymentElements?.length || storedPaymentElements?.length;
 
         switch (status.type) {
             case 'success':
@@ -135,7 +136,7 @@ export class DropinComponent extends Component<DropinComponentProps, DropinCompo
                     <div className={`adyen-checkout__dropin adyen-checkout__dropin--${status.type}`}>
                         {isRedirecting && status.props.component && status.props.component.render()}
                         {isLoading && status.props && status.props.component && status.props.component.render()}
-                        {elements && !!elements.length && (
+                        {hasPaymentMethodsToBeDisplayed && (
                             <PaymentMethodList
                                 isLoading={isLoading || isRedirecting}
                                 isDisablingPaymentMethod={this.state.isDisabling}

From ea50de1f3d23b5d602f9dd9c7d2f203d4482284f Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 25 Jan 2024 07:47:33 -0300
Subject: [PATCH 43/55] fixing sonarcloud lint

---
 .../lib/src/components/ApplePay/ApplePay.tsx  | 28 +++----------------
 packages/lib/src/components/ApplePay/types.ts |  2 +-
 .../src/components/GooglePay/GooglePay.tsx    | 19 +++----------
 .../lib/src/components/GooglePay/utils.ts     | 16 -----------
 ...at-paypal-order-contact-to-adyen-format.ts |  7 ++---
 .../internal/UIElement/UIElement.tsx          | 16 +++++++----
 packages/lib/src/core/core.ts                 | 24 ++++------------
 packages/lib/src/core/types.ts                |  2 --
 packages/lib/src/types/global-types.ts        | 15 ----------
 9 files changed, 28 insertions(+), 101 deletions(-)

diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx
index d949d221a8..b09b4dc701 100644
--- a/packages/lib/src/components/ApplePay/ApplePay.tsx
+++ b/packages/lib/src/components/ApplePay/ApplePay.tsx
@@ -8,12 +8,7 @@ import { httpPost } from '../../core/Services/http';
 import { APPLEPAY_SESSION_ENDPOINT } from './config';
 import { preparePaymentRequest } from './payment-request';
 import { resolveSupportedVersion, mapBrands, formatApplePayContactToAdyenAddressFormat } from './utils';
-import {
-    ApplePayConfiguration,
-    ApplePayElementData,
-    ApplePayPaymentOrderDetails,
-    ApplePaySessionRequest
-} from './types';
+import { ApplePayConfiguration, ApplePayElementData, ApplePayPaymentOrderDetails, ApplePaySessionRequest } from './types';
 import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 import { TxVariants } from '../tx-variants';
 import { PaymentResponseData, RawPaymentResponse } from '../../types/global-types';
@@ -71,13 +66,7 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
     };
 
     private startSession() {
-        const {
-            version,
-            onValidateMerchant,
-            onPaymentMethodSelected,
-            onShippingMethodSelected,
-            onShippingContactSelected
-        } = this.props;
+        const { version, onValidateMerchant, onPaymentMethodSelected, onShippingMethodSelected, onShippingContactSelected } = this.props;
 
         const paymentRequest = preparePaymentRequest({
             companyName: this.props.configuration.merchantName,
@@ -244,12 +233,7 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
      */
     public override async isAvailable(): Promise<void> {
         if (document.location.protocol !== 'https:') {
-            return Promise.reject(
-                new AdyenCheckoutError(
-                    'IMPLEMENTATION_ERROR',
-                    'Trying to start an Apple Pay session from an insecure document'
-                )
-            );
+            return Promise.reject(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Trying to start an Apple Pay session from an insecure document'));
         }
 
         if (!this.props.onValidateMerchant && !this.props.clientKey) {
@@ -257,11 +241,7 @@ class ApplePayElement extends UIElement<ApplePayConfiguration> {
         }
 
         try {
-            if (
-                window.ApplePaySession &&
-                ApplePaySession.canMakePayments() &&
-                ApplePaySession.supportsVersion(this.props.version)
-            ) {
+            if (window.ApplePaySession && ApplePaySession.canMakePayments() && ApplePaySession.supportsVersion(this.props.version)) {
                 return Promise.resolve();
             }
         } catch (error) {
diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts
index b443b4b704..53a84bd645 100644
--- a/packages/lib/src/components/ApplePay/types.ts
+++ b/packages/lib/src/components/ApplePay/types.ts
@@ -220,7 +220,7 @@ export interface ApplePayElementData {
         type: string;
         applePayToken: string;
         billingAddress?: AddressData;
-        shippingAddress?: AddressData;
+        deliveryAddress?: AddressData;
     };
 }
 
diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 66d94a45c5..342e2f5be2 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -32,14 +32,10 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
     }
 
     formatProps(props): GooglePayConfiguration {
-        // const allowedCardNetworks = props.brands?.length ? mapBrands(props.brands) : props.allowedCardNetworks;
         const buttonSizeMode = props.buttonSizeMode ?? (props.isDropin ? 'fill' : 'static');
         const buttonLocale = getGooglePayLocale(props.buttonLocale ?? props.i18n?.locale);
 
-        const callbackIntents: google.payments.api.CallbackIntent[] = [
-            ...props.callbackIntents,
-            'PAYMENT_AUTHORIZATION'
-        ];
+        const callbackIntents: google.payments.api.CallbackIntent[] = [...props.callbackIntents, 'PAYMENT_AUTHORIZATION'];
 
         return {
             ...props,
@@ -86,16 +82,9 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
      *
      * @see https://developers.google.com/pay/api/web/reference/client#onPaymentAuthorized
      **/
-    private onPaymentAuthorized = async (
-        paymentData: google.payments.api.PaymentData
-    ): Promise<google.payments.api.PaymentAuthorizationResult> => {
-        const billingAddress: AddressData = formatGooglePayContactToAdyenAddressFormat(
-            paymentData.paymentMethodData.info.billingAddress
-        );
-        const deliveryAddress: AddressData = formatGooglePayContactToAdyenAddressFormat(
-            paymentData.shippingAddress,
-            true
-        );
+    private onPaymentAuthorized = async (paymentData: google.payments.api.PaymentData): Promise<google.payments.api.PaymentAuthorizationResult> => {
+        const billingAddress: AddressData = formatGooglePayContactToAdyenAddressFormat(paymentData.paymentMethodData.info.billingAddress);
+        const deliveryAddress: AddressData = formatGooglePayContactToAdyenAddressFormat(paymentData.shippingAddress, true);
 
         this.setState({
             authorizedEvent: paymentData,
diff --git a/packages/lib/src/components/GooglePay/utils.ts b/packages/lib/src/components/GooglePay/utils.ts
index 8c767d96dd..20e96bd883 100644
--- a/packages/lib/src/components/GooglePay/utils.ts
+++ b/packages/lib/src/components/GooglePay/utils.ts
@@ -45,22 +45,6 @@ export function formatGooglePayContactToAdyenAddressFormat(
     };
 }
 
-// export function mapBrands(brands) {
-//     const brandMapping = {
-//         mc: 'MASTERCARD',
-//         amex: 'AMEX',
-//         visa: 'VISA',
-//         interac: 'INTERAC',
-//         discover: 'DISCOVER'
-//     };
-//     return brands.reduce((accumulator, item) => {
-//         if (!!brandMapping[item] && !accumulator.includes(brandMapping[item])) {
-//             accumulator.push(brandMapping[item]);
-//         }
-//         return accumulator;
-//     }, []);
-// }
-
 const supportedLocales = [
     'en',
     'ar',
diff --git a/packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts b/packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts
index 753d3a38d7..747ef64e60 100644
--- a/packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts
+++ b/packages/lib/src/components/PayPal/utils/format-paypal-order-contact-to-adyen-format.ts
@@ -3,10 +3,7 @@ import { AddressData } from '../../../types/global-types';
 /**
  * This function formats PayPal contact format to Adyen address format
  */
-export const formatPaypalOrderContatcToAdyenFormat = (
-    paymentContact: any,
-    isDeliveryAddress?: boolean
-): AddressData | null => {
+export const formatPaypalOrderContatcToAdyenFormat = (paymentContact: any, isDeliveryAddress?: boolean): AddressData | null => {
     const getStreet = (addressPart1 = null, addressPart2 = null): string | null => {
         if (addressPart1 && addressPart2) return `${addressPart1}, ${addressPart2}`;
         if (addressPart1) return addressPart1;
@@ -14,7 +11,7 @@ export const formatPaypalOrderContatcToAdyenFormat = (
         return null;
     };
 
-    if (!paymentContact || !paymentContact.address) return null;
+    if (paymentContact?.address === undefined) return null;
 
     const { address, name } = paymentContact;
     const street = getStreet(address.address_line_1, address.address_line_2);
diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx
index b4b366566e..880d7d9e77 100644
--- a/packages/lib/src/components/internal/UIElement/UIElement.tsx
+++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx
@@ -9,14 +9,14 @@ import { Resources } from '../../../core/Context/Resources';
 import { NewableComponent } from '../../../core/core.registry';
 import { ComponentMethodsRef, IUIElement, PayButtonFunctionProps, UIElementProps, UIElementStatus } from './types';
 import {
+    AdditionalDetailsStateData,
+    CheckoutAdvancedFlowResponse,
     Order,
     PaymentAction,
     PaymentData,
     PaymentMethodsResponse,
-    CheckoutAdvancedFlowResponse,
     PaymentResponseData,
-    RawPaymentResponse,
-    AdditionalDetailsStateData
+    RawPaymentResponse
 } from '../../../types/global-types';
 import './UIElement.scss';
 import { CheckoutSessionDetailsResponse, CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
@@ -249,7 +249,7 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
 
     private async submitAdditionalDetailsUsingSessionsFlow(data: any): Promise<CheckoutSessionDetailsResponse> {
         try {
-            return this.core.session.submitDetails(data);
+            return await this.core.session.submitDetails(data);
         } catch (error) {
             this.handleError(error);
             return Promise.reject(error);
@@ -402,10 +402,16 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> exten
         return <PayButton {...props} amount={this.props.amount} secondaryAmount={this.props.secondaryAmount} onClick={this.submit} />;
     };
 
+    /**
+     * Used in the Partial Orders flow.
+     * When the Order is updated, the merchant can request new payment methods based on the new remaining amount
+     *
+     * @private
+     */
     private async handleAdvanceFlowPaymentMethodsUpdate(order: Order) {
         return new Promise<PaymentMethodsResponse>((resolve, reject) => {
             if (!this.props.onPaymentMethodsRequest) {
-                return reject();
+                return reject(new Error('onPaymentMethodsRequest is not implemented'));
             }
 
             this.props.onPaymentMethodsRequest(
diff --git a/packages/lib/src/core/core.ts b/packages/lib/src/core/core.ts
index bb9795c9c7..bcf61453f8 100644
--- a/packages/lib/src/core/core.ts
+++ b/packages/lib/src/core/core.ts
@@ -59,18 +59,12 @@ class Core implements ICore {
         this.setOptions(props);
 
         this.loadingContext = resolveEnvironment(this.options.environment, this.options.environmentUrls?.api);
-        this.cdnContext = resolveCDNEnvironment(
-            this.options.resourceEnvironment || this.options.environment,
-            this.options.environmentUrls?.api
-        );
-        this.session =
-            this.options.session && new Session(this.options.session, this.options.clientKey, this.loadingContext);
+        this.cdnContext = resolveCDNEnvironment(this.options.resourceEnvironment || this.options.environment, this.options.environmentUrls?.api);
+        this.session = this.options.session && new Session(this.options.session, this.options.clientKey, this.loadingContext);
 
         const clientKeyType = this.options.clientKey?.substr(0, 4);
         if ((clientKeyType === 'test' || clientKeyType === 'live') && !this.loadingContext.includes(clientKeyType)) {
-            throw new Error(
-                `Error: you are using a '${clientKeyType}' clientKey against the '${this.options.environment}' environment`
-            );
+            throw new Error(`Error: you are using a '${clientKeyType}' clientKey against the '${this.options.environment}' environment`);
         }
 
         // Expose version number for npm builds
@@ -168,9 +162,7 @@ class Core implements ICore {
                         'a "resultCode": have you passed in the whole response object by mistake?'
                 );
             }
-            throw new Error(
-                'createFromAction::Invalid Action - the passed action object does not have a "type" property'
-            );
+            throw new Error('createFromAction::Invalid Action - the passed action object does not have a "type" property');
         }
 
         if (action.type) {
@@ -260,8 +252,7 @@ class Core implements ICore {
      * @internal
      */
     private handleCreateError(paymentMethod?): never {
-        const paymentMethodName =
-            paymentMethod && paymentMethod.name ? paymentMethod.name : 'The passed payment method';
+        const paymentMethodName = paymentMethod?.name ?? 'The passed payment method';
         const errorMessage = paymentMethod
             ? `${paymentMethodName} is not a valid Checkout Component. What was passed as a txVariant was: ${JSON.stringify(
                   paymentMethod
@@ -272,10 +263,7 @@ class Core implements ICore {
     }
 
     private createPaymentMethodsList(paymentMethodsResponse?: PaymentMethods): void {
-        this.paymentMethodsResponse = new PaymentMethods(
-            this.options.paymentMethodsResponse || paymentMethodsResponse,
-            this.options
-        );
+        this.paymentMethodsResponse = new PaymentMethods(this.options.paymentMethodsResponse || paymentMethodsResponse, this.options);
     }
 
     private createCoreModules(): void {
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index a16553e454..0ee2643d60 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -191,7 +191,6 @@ export interface CoreConfiguration {
         actions: {
             resolve: (response: CheckoutAdvancedFlowResponse) => void;
             reject: () => void;
-            // reject: (error?: onSubmitReject) => void;
         }
     ): void;
 
@@ -208,7 +207,6 @@ export interface CoreConfiguration {
         actions: {
             resolve: (response: CheckoutAdvancedFlowResponse) => void;
             reject: () => void;
-            // reject: (error?: onSubmitReject) => void;
         }
     ): void;
 
diff --git a/packages/lib/src/types/global-types.ts b/packages/lib/src/types/global-types.ts
index 91890daa18..8c3d23b12f 100644
--- a/packages/lib/src/types/global-types.ts
+++ b/packages/lib/src/types/global-types.ts
@@ -1,6 +1,5 @@
 import { ADDRESS_SCHEMA } from '../components/internal/Address/constants';
 import actionTypes from '../core/ProcessResponse/PaymentAction/actionTypes';
-// import { onSubmitReject } from '../core/types';
 
 export type PaymentActionsType = keyof typeof actionTypes;
 
@@ -206,19 +205,6 @@ export interface PaymentAmountExtended extends PaymentAmount {
     currencyDisplay?: string;
 }
 
-export type ShopperDetails = {
-    shopperName?: {
-        firstName?: string;
-        lastName?: string;
-    };
-    shopperEmail?: string;
-    countryCode?: string;
-    telephoneNumber?: string;
-    dateOfBirth?: string;
-    billingAddress?: Partial<AddressData>;
-    shippingAddress?: Partial<AddressData>;
-};
-
 export type AddressField = (typeof ADDRESS_SCHEMA)[number];
 
 export type AddressData = {
@@ -336,7 +322,6 @@ export type SessionsResponse = {
     resultCode: ResultCode;
 };
 
-//TODO double check these values
 export interface PaymentMethodsRequestData {
     order?: Order;
     locale?: string;

From c49af073002696230fbac85d303454eecf0eb68f Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 25 Jan 2024 09:51:48 -0300
Subject: [PATCH 44/55] attempt to delete types.ts from sonarcloud

---
 packages/lib/config/jest.config.cjs | 7 ++++++-
 sonar-project.properties            | 1 +
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/packages/lib/config/jest.config.cjs b/packages/lib/config/jest.config.cjs
index 8aafd69dcf..0aeb342961 100644
--- a/packages/lib/config/jest.config.cjs
+++ b/packages/lib/config/jest.config.cjs
@@ -10,5 +10,10 @@ module.exports = {
     moduleNameMapper: {
         '\\.scss$': '<rootDir>/config/testMocks/styleMock.js'
     },
-    coveragePathIgnorePatterns: ['node_modules/', 'config/', 'scripts/']
+    collectCoverageFrom: [
+        'src/**/*.{ts,tsx}',
+        '!src/**/types.ts',
+        '!src/language/locales/**'
+    ],
+    coveragePathIgnorePatterns: ['node_modules/', 'config/', 'scripts/', 'storybook/', '.storybook/', 'auto/']
 };
diff --git a/sonar-project.properties b/sonar-project.properties
index f884d83d47..6449952b4c 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -4,3 +4,4 @@ sonar.projectKey=Adyen_adyen-web
 sonar.sourceEncoding=UTF-8
 
 sonar.javascript.lcov.reportPaths=./packages/lib/coverage/lcov.info
+sonar.coverage.exclusions=packages/lib/src/**/types.ts

From a2ee22cef015ab67774f0366362251aae9cb82f9 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 25 Jan 2024 10:11:31 -0300
Subject: [PATCH 45/55] moved exclusions to sonarcloud file

---
 sonar-project.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sonar-project.properties b/sonar-project.properties
index 6449952b4c..c5bedf1ce1 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -4,4 +4,4 @@ sonar.projectKey=Adyen_adyen-web
 sonar.sourceEncoding=UTF-8
 
 sonar.javascript.lcov.reportPaths=./packages/lib/coverage/lcov.info
-sonar.coverage.exclusions=packages/lib/src/**/types.ts
+sonar.coverage.exclusions=packages/lib/src/**/types.ts,packages/lib/src/**/*.test.*,packages/lib/storybook/**/*,packages/e2e/**/*,packages/e2e-playwright/**/*,packages/playground/**/*,packages/lib/config/**/*

From d167743e1d9d1c8434b609d798d68fb4322e3982 Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 25 Jan 2024 10:19:10 -0300
Subject: [PATCH 46/55] sonarcloud: adding spec file

---
 sonar-project.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sonar-project.properties b/sonar-project.properties
index c5bedf1ce1..8bff498863 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -4,4 +4,4 @@ sonar.projectKey=Adyen_adyen-web
 sonar.sourceEncoding=UTF-8
 
 sonar.javascript.lcov.reportPaths=./packages/lib/coverage/lcov.info
-sonar.coverage.exclusions=packages/lib/src/**/types.ts,packages/lib/src/**/*.test.*,packages/lib/storybook/**/*,packages/e2e/**/*,packages/e2e-playwright/**/*,packages/playground/**/*,packages/lib/config/**/*
+sonar.coverage.exclusions=packages/lib/src/**/types.ts,packages/lib/src/**/*.test.*,packages/lib/src/**/*.spec.*,packages/lib/storybook/**/*,packages/e2e/**/*,packages/e2e-playwright/**/*,packages/playground/**/*,packages/lib/config/**/*

From 39a5ea888b65e1eae0a716a05efe336d8787358a Mon Sep 17 00:00:00 2001
From: guilhermer <guilherme.ribeiro@adyen.com>
Date: Thu, 25 Jan 2024 11:29:24 -0300
Subject: [PATCH 47/55] feat: using error message from i18n

---
 packages/lib/src/components/GooglePay/GooglePay.tsx | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx
index 342e2f5be2..9de377e834 100644
--- a/packages/lib/src/components/GooglePay/GooglePay.tsx
+++ b/packages/lib/src/components/GooglePay/GooglePay.tsx
@@ -110,18 +110,19 @@ class GooglePay extends UIElement<GooglePayConfiguration> {
                     this.setElementStatus('ready');
 
                     const googlePayError = paymentResponse.error?.googlePayError;
+                    const fallbackMessage = this.props.i18n.get('error.subtitle.payment');
 
                     const error: google.payments.api.PaymentDataError =
                         typeof googlePayError === 'string' || undefined
                             ? {
                                   intent: 'PAYMENT_AUTHORIZATION',
                                   reason: 'OTHER_ERROR',
-                                  message: (googlePayError as string) || 'Payment failed'
+                                  message: (googlePayError as string) || fallbackMessage
                               }
                             : {
                                   intent: googlePayError?.intent || 'PAYMENT_AUTHORIZATION',
                                   reason: googlePayError?.reason || 'OTHER_ERROR',
-                                  message: googlePayError?.message || 'Payment failed'
+                                  message: googlePayError?.message || fallbackMessage
                               };
 
                     resolve({

From b7d9a04af4463a5cd55a16802ce2c6e6540cb98b Mon Sep 17 00:00:00 2001
From: nicholas <nicholas.spong@adyen.com>
Date: Wed, 31 Jan 2024 14:35:52 +0100
Subject: [PATCH 48/55] Fixed typescript errors related to mismatching onError
 function signatures in 3DS2 and Card

---
 packages/lib/src/components/Card/Card.tsx     |  1 -
 packages/lib/src/components/Card/types.ts     |  4 ++--
 .../src/components/CustomCard/CustomCard.tsx  |  1 -
 .../components/ThreeDS2/ThreeDS2Challenge.tsx |  7 ++----
 .../ThreeDS2/ThreeDS2DeviceFingerprint.tsx    | 14 +++--------
 .../Challenge/PrepareChallenge3DS2.tsx        | 23 +++++++++++++++----
 .../PrepareFingerprint3DS2.tsx                |  7 +++---
 .../lib/src/components/ThreeDS2/config.ts     |  3 +++
 packages/lib/src/components/ThreeDS2/types.ts |  6 ++---
 .../lib/src/core/Errors/AdyenCheckoutError.ts | 17 ++++++++++----
 packages/lib/src/core/core.registry.ts        |  1 -
 packages/playground/src/pages/Cards/Cards.js  |  3 ---
 12 files changed, 47 insertions(+), 40 deletions(-)

diff --git a/packages/lib/src/components/Card/Card.tsx b/packages/lib/src/components/Card/Card.tsx
index 787ee03ccb..6538fd4497 100644
--- a/packages/lib/src/components/Card/Card.tsx
+++ b/packages/lib/src/components/Card/Card.tsx
@@ -17,7 +17,6 @@ import UIElement from '../internal/UIElement';
 import PayButton from '../internal/PayButton';
 import { PayButtonProps } from '../internal/PayButton/PayButton';
 
-// @ts-ignore TODO: Check with nick
 export class CardElement extends UIElement<CardConfiguration> {
     public static type = TxVariants.scheme;
 
diff --git a/packages/lib/src/components/Card/types.ts b/packages/lib/src/components/Card/types.ts
index 7dd84e28a3..ba283d3534 100644
--- a/packages/lib/src/components/Card/types.ts
+++ b/packages/lib/src/components/Card/types.ts
@@ -16,8 +16,8 @@ import { InstallmentOptions } from './components/CardInput/components/types';
 import { DisclaimerMsgObject } from '../internal/DisclaimerMessage/DisclaimerMessage';
 import { Placeholders } from './components/CardInput/types';
 import { UIElementProps } from '../internal/UIElement/types';
+import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 
-// @ts-ignore TODO: Check with nick
 export interface CardConfiguration extends UIElementProps {
     /**
      * Only set for a stored card,
@@ -150,7 +150,7 @@ export interface CardConfiguration extends UIElementProps {
     /**
      * Called in case of an invalid Card Number, invalid Expiry Date, or incomplete field. Called again when errors are cleared.
      */
-    onError?: (event: CbObjOnError) => void;
+    onError?: (error: CbObjOnError | AdyenCheckoutError) => void;
 
     /**
      * Called when a field gains or loses focus.
diff --git a/packages/lib/src/components/CustomCard/CustomCard.tsx b/packages/lib/src/components/CustomCard/CustomCard.tsx
index 108a0cf782..69903498eb 100644
--- a/packages/lib/src/components/CustomCard/CustomCard.tsx
+++ b/packages/lib/src/components/CustomCard/CustomCard.tsx
@@ -15,7 +15,6 @@ import { CustomCardConfiguration } from './types';
 // type
 // countryCode
 
-// @ts-ignore TODO: Check with nick
 export class CustomCard extends UIElement<CustomCardConfiguration> {
     public static type = TxVariants.customCard;
 
diff --git a/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx b/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx
index e16b956501..1e3003c8da 100644
--- a/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx
+++ b/packages/lib/src/components/ThreeDS2/ThreeDS2Challenge.tsx
@@ -6,8 +6,8 @@ import { existy } from '../internal/SecuredFields/lib/utilities/commonUtils';
 import { hasOwnProperty } from '../../utils/hasOwnProperty';
 import { TxVariants } from '../tx-variants';
 import { ThreeDS2ChallengeConfiguration } from './types';
+import AdyenCheckoutError, { API_ERROR } from '../../core/Errors/AdyenCheckoutError';
 
-// @ts-ignore TODO: Check with nick
 class ThreeDS2Challenge extends UIElement<ThreeDS2ChallengeConfiguration> {
     public static type = TxVariants.threeDS2Challenge;
 
@@ -31,10 +31,7 @@ class ThreeDS2Challenge extends UIElement<ThreeDS2ChallengeConfiguration> {
              */
             const dataTypeForError = hasOwnProperty(this.props, 'isMDFlow') ? 'paymentData' : 'authorisationToken';
 
-            this.props.onError({
-                errorCode: 'threeds2.challenge',
-                message: `No ${dataTypeForError} received. Challenge cannot proceed`
-            });
+            this.props.onError(new AdyenCheckoutError(API_ERROR, `No ${dataTypeForError} received. 3DS2 Challenge cannot proceed`));
             return null;
         }
 
diff --git a/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx b/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx
index 4503c2ee64..debf8e0034 100644
--- a/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx
+++ b/packages/lib/src/components/ThreeDS2/ThreeDS2DeviceFingerprint.tsx
@@ -5,8 +5,8 @@ import callSubmit3DS2Fingerprint from './callSubmit3DS2Fingerprint';
 import { existy } from '../internal/SecuredFields/lib/utilities/commonUtils';
 import { TxVariants } from '../tx-variants';
 import { ThreeDS2DeviceFingerprintConfiguration } from './types';
+import AdyenCheckoutError, { API_ERROR } from '../../core/Errors/AdyenCheckoutError';
 
-// @ts-ignore TODO: Check with nick
 class ThreeDS2DeviceFingerprint extends UIElement<ThreeDS2DeviceFingerprintConfiguration> {
     public static type = TxVariants.threeDS2Fingerprint;
 
@@ -28,10 +28,7 @@ class ThreeDS2DeviceFingerprint extends UIElement<ThreeDS2DeviceFingerprintConfi
          * In the MDFlow the paymentData is always present (albeit an empty string, which is why we use 'existy' since we should be allowed to proceed with this)
          */
         if (!existy(this.props.paymentData)) {
-            this.props.onError({
-                errorCode: ThreeDS2DeviceFingerprint.defaultProps.dataKey,
-                message: 'No paymentData received. Fingerprinting cannot proceed'
-            });
+            this.props.onError(new AdyenCheckoutError(API_ERROR, `No paymentData received. 3DS2 Fingerprint cannot proceed`));
             return null;
         }
 
@@ -39,12 +36,7 @@ class ThreeDS2DeviceFingerprint extends UIElement<ThreeDS2DeviceFingerprintConfi
          * this.props.isMDFlow indicates a threeds2InMDFlow process. It means the action to create this component came from the threeds2InMDFlow process
          * and upon completion should call the passed onComplete callback (instead of the /submitThreeDS2Fingerprint endpoint for the regular, "native" flow)
          */
-        return (
-            <PrepareFingerprint
-                {...this.props}
-                onComplete={this.props.isMDFlow ? this.onComplete : this.callSubmit3DS2Fingerprint}
-            />
-        );
+        return <PrepareFingerprint {...this.props} onComplete={this.props.isMDFlow ? this.onComplete : this.callSubmit3DS2Fingerprint} />;
     }
 }
 
diff --git a/packages/lib/src/components/ThreeDS2/components/Challenge/PrepareChallenge3DS2.tsx b/packages/lib/src/components/ThreeDS2/components/Challenge/PrepareChallenge3DS2.tsx
index 43258421bf..cc6195787a 100644
--- a/packages/lib/src/components/ThreeDS2/components/Challenge/PrepareChallenge3DS2.tsx
+++ b/packages/lib/src/components/ThreeDS2/components/Challenge/PrepareChallenge3DS2.tsx
@@ -1,6 +1,6 @@
 import { Component, h } from 'preact';
 import DoChallenge3DS2 from './DoChallenge3DS2';
-import { createChallengeResolveData, handleErrorCode, prepareChallengeData, createOldChallengeResolveData } from '../utils';
+import { createChallengeResolveData, prepareChallengeData, createOldChallengeResolveData } from '../utils';
 import { PrepareChallenge3DS2Props, PrepareChallenge3DS2State } from './types';
 import { ChallengeData, ThreeDS2FlowObject } from '../../types';
 import '../../ThreeDS2.scss';
@@ -8,6 +8,8 @@ import Img from '../../../internal/Img';
 import './challenge.scss';
 import { hasOwnProperty } from '../../../../utils/hasOwnProperty';
 import useImage from '../../../../core/Context/useImage';
+import AdyenCheckoutError, { ERROR } from '../../../../core/Errors/AdyenCheckoutError';
+import { THREEDS2_CHALLENGE_ERROR } from '../../config';
 
 class PrepareChallenge3DS2 extends Component<PrepareChallenge3DS2Props, PrepareChallenge3DS2State> {
     public static defaultProps = {
@@ -80,8 +82,15 @@ class PrepareChallenge3DS2 extends Component<PrepareChallenge3DS2Props, PrepareC
                         // Challenge has resulted in an error (no transStatus could be retrieved) - but we still treat this as a valid scenario
                         if (hasOwnProperty(challenge.result, 'errorCode') && challenge.result.errorCode.length) {
                             // Tell the merchant there's been an error
-                            const errorCodeObject = handleErrorCode(challenge.result.errorCode, challenge.result.errorDescription);
-                            this.props.onError(errorCodeObject);
+                            this.props.onError(
+                                new AdyenCheckoutError(
+                                    ERROR,
+                                    `${THREEDS2_CHALLENGE_ERROR}: ${
+                                        challenge.result.errorDescription ? challenge.result.errorDescription : 'no transStatus could be retrieved'
+                                    }`,
+                                    { cause: challenge.result.errorCode }
+                                )
+                            );
                         }
 
                         this.setStatusComplete(challenge.result);
@@ -91,8 +100,12 @@ class PrepareChallenge3DS2 extends Component<PrepareChallenge3DS2Props, PrepareC
                          * Called when challenge times-out (which is still a valid scenario)...
                          */
                         if (hasOwnProperty(challenge, 'errorCode')) {
-                            const errorCodeObject = handleErrorCode(challenge.errorCode);
-                            this.props.onError(errorCodeObject);
+                            this.props.onError(
+                                new AdyenCheckoutError(ERROR, `${THREEDS2_CHALLENGE_ERROR}: '3DS2 challenge timed out'`, {
+                                    cause: challenge.errorCode
+                                })
+                            );
+
                             this.setStatusComplete(challenge.result);
                             return;
                         }
diff --git a/packages/lib/src/components/ThreeDS2/components/DeviceFingerprint/PrepareFingerprint3DS2.tsx b/packages/lib/src/components/ThreeDS2/components/DeviceFingerprint/PrepareFingerprint3DS2.tsx
index 43ff24635a..740765670a 100644
--- a/packages/lib/src/components/ThreeDS2/components/DeviceFingerprint/PrepareFingerprint3DS2.tsx
+++ b/packages/lib/src/components/ThreeDS2/components/DeviceFingerprint/PrepareFingerprint3DS2.tsx
@@ -3,6 +3,8 @@ import DoFingerprint3DS2 from './DoFingerprint3DS2';
 import { createFingerprintResolveData, createOldFingerprintResolveData, handleErrorCode, prepareFingerPrintData } from '../utils';
 import { PrepareFingerprint3DS2Props, PrepareFingerprint3DS2State } from './types';
 import { FingerPrintData, ResultObject } from '../../types';
+import AdyenCheckoutError, { ERROR } from '../../../../core/Errors/AdyenCheckoutError';
+import { THREEDS2_FINGERPRINT_ERROR } from '../../config';
 
 class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, PrepareFingerprint3DS2State> {
     public static type = 'scheme';
@@ -23,10 +25,7 @@ class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, Prep
             this.state = { status: 'error' };
             // TODO - confirm that we should do this, or is it possible to proceed to the challenge anyway?
             //  ...in which case we should console.debug the error object and then call: this.setStatusComplete({ threeDSCompInd: 'N' });
-            this.props.onError({
-                errorCode: this.props.dataKey,
-                message: 'Missing fingerprintToken parameter'
-            });
+            this.props.onError(new AdyenCheckoutError(ERROR, `${THREEDS2_FINGERPRINT_ERROR}: Missing "token" property from 3DS2Fingerprint action`));
         }
     }
 
diff --git a/packages/lib/src/components/ThreeDS2/config.ts b/packages/lib/src/components/ThreeDS2/config.ts
index 1fe676cac6..361756e1eb 100644
--- a/packages/lib/src/components/ThreeDS2/config.ts
+++ b/packages/lib/src/components/ThreeDS2/config.ts
@@ -1,5 +1,8 @@
 import { ThreeDS2FlowObject } from './types';
 
+export const THREEDS2_FINGERPRINT_ERROR = '3DS2Fingerprint_Error';
+export const THREEDS2_CHALLENGE_ERROR = '3DS2Challenge_Error';
+
 export const DEFAULT_CHALLENGE_WINDOW_SIZE = '02';
 
 export const THREEDS_METHOD_TIMEOUT = 10000;
diff --git a/packages/lib/src/components/ThreeDS2/types.ts b/packages/lib/src/components/ThreeDS2/types.ts
index d06279fb04..289f6a36d1 100644
--- a/packages/lib/src/components/ThreeDS2/types.ts
+++ b/packages/lib/src/components/ThreeDS2/types.ts
@@ -1,15 +1,15 @@
 import { ICore } from '../../core/types';
-import { ErrorCodeObject } from './components/utils';
 import UIElement from '../internal/UIElement';
 import { ActionHandledReturnObject } from '../../types/global-types';
 import Language from '../../language';
+import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 
 export interface ThreeDS2DeviceFingerprintConfiguration {
     core: ICore;
     dataKey: string;
     token: string;
     notificationURL: string;
-    onError: (error?: string | ErrorCodeObject) => void;
+    onError: (error: AdyenCheckoutError, element?: UIElement) => void;
     paymentData: string;
     showSpinner: boolean;
     type: string;
@@ -25,7 +25,7 @@ export interface ThreeDS2ChallengeConfiguration {
     token?: string;
     dataKey?: string;
     notificationURL?: string;
-    onError?: (error: string | ErrorCodeObject) => void;
+    onError?: (error: AdyenCheckoutError, element?: UIElement) => void;
     paymentData?: string;
     size?: string;
     challengeWindowSize?: '01' | '02' | '03' | '04' | '05';
diff --git a/packages/lib/src/core/Errors/AdyenCheckoutError.ts b/packages/lib/src/core/Errors/AdyenCheckoutError.ts
index f3a16862bf..4b84f4a7cf 100644
--- a/packages/lib/src/core/Errors/AdyenCheckoutError.ts
+++ b/packages/lib/src/core/Errors/AdyenCheckoutError.ts
@@ -2,19 +2,28 @@ interface CheckoutErrorOptions {
     cause?: any;
 }
 
+export const NETWORK_ERROR = 'NETWORK_ERROR';
+export const CANCEL = 'CANCEL';
+export const IMPLEMENTATION_ERROR = 'IMPLEMENTATION_ERROR';
+export const API_ERROR = 'API_ERROR';
+export const ERROR = 'ERROR';
+
 class AdyenCheckoutError extends Error {
     protected static errorTypes = {
         /** Network error. */
-        NETWORK_ERROR: 'NETWORK_ERROR',
+        NETWORK_ERROR,
 
         /** Shopper canceled the current transaction. */
-        CANCEL: 'CANCEL',
+        CANCEL,
 
         /** Implementation error. The method or parameter are incorrect or are not supported. */
-        IMPLEMENTATION_ERROR: 'IMPLEMENTATION_ERROR',
+        IMPLEMENTATION_ERROR,
+
+        /** API error. The API has not returned the expected data  */
+        API_ERROR,
 
         /** Generic error. */
-        ERROR: 'ERROR'
+        ERROR
     };
 
     public cause: unknown;
diff --git a/packages/lib/src/core/core.registry.ts b/packages/lib/src/core/core.registry.ts
index 35b421b1bb..49481aa11d 100644
--- a/packages/lib/src/core/core.registry.ts
+++ b/packages/lib/src/core/core.registry.ts
@@ -23,7 +23,6 @@ const defaultComponents = {
 };
 
 class Registry implements IRegistry {
-    // @ts-ignore TODO: Check with nick
     public componentsMap: Record<string, NewableComponent> = defaultComponents;
 
     public supportedTxVariants: Set<string> = new Set(Object.values(TxVariants));
diff --git a/packages/playground/src/pages/Cards/Cards.js b/packages/playground/src/pages/Cards/Cards.js
index 40cd4d3504..ab7614bc67 100644
--- a/packages/playground/src/pages/Cards/Cards.js
+++ b/packages/playground/src/pages/Cards/Cards.js
@@ -85,9 +85,6 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
             // holderNameRequired: true,
             // maskSecurityCode: true,
             // enableStoreDetails: true
-            onError: obj => {
-                console.log('### Cards::onError:: obj=', obj);
-            },
             onBinLookup: obj => {
                 console.log('### Cards::onBinLookup:: obj=', obj);
             }

From 1377445c66ed53f8e1bcb0687cc842e6d37ca5f3 Mon Sep 17 00:00:00 2001
From: nicholas <nicholas.spong@adyen.com>
Date: Wed, 31 Jan 2024 15:07:55 +0100
Subject: [PATCH 49/55] Added wait to e2e test to give time for payment to be
 processed

---
 packages/e2e/tests/cards/avs/avs.partial.test.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/e2e/tests/cards/avs/avs.partial.test.js b/packages/e2e/tests/cards/avs/avs.partial.test.js
index ea843c7ff7..88acf7b7bf 100644
--- a/packages/e2e/tests/cards/avs/avs.partial.test.js
+++ b/packages/e2e/tests/cards/avs/avs.partial.test.js
@@ -35,6 +35,8 @@ test('should not validate Postal Code if property data.billingAddress.country is
     await t.typeText(cardPage.postalCodeInput, INVALID_POSTALCODE);
     await t.click(cardPage.payButton);
 
+    await t.wait(3000);
+
     // Check the value of the alert text
     const history = await t.getNativeDialogHistory();
     await t.expect(history[0].text).eql('Authorised');

From edc36eae086de0a9585e52be28a7da0a0856f59d Mon Sep 17 00:00:00 2001
From: nicholas <nicholas.spong@adyen.com>
Date: Thu, 1 Feb 2024 12:08:14 +0100
Subject: [PATCH 50/55] Removed onError type from Card/types

---
 packages/lib/src/components/Card/types.ts | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/packages/lib/src/components/Card/types.ts b/packages/lib/src/components/Card/types.ts
index ba283d3534..e356194dcb 100644
--- a/packages/lib/src/components/Card/types.ts
+++ b/packages/lib/src/components/Card/types.ts
@@ -3,7 +3,6 @@ import {
     CbObjOnBinValue,
     CbObjOnBrand,
     CbObjOnConfigSuccess,
-    CbObjOnError,
     CbObjOnFieldValid,
     CbObjOnFocus,
     CbObjOnLoad,
@@ -16,7 +15,6 @@ import { InstallmentOptions } from './components/CardInput/components/types';
 import { DisclaimerMsgObject } from '../internal/DisclaimerMessage/DisclaimerMessage';
 import { Placeholders } from './components/CardInput/types';
 import { UIElementProps } from '../internal/UIElement/types';
-import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError';
 
 export interface CardConfiguration extends UIElementProps {
     /**
@@ -147,11 +145,6 @@ export interface CardConfiguration extends UIElementProps {
      */
     onBrand?: (event: CbObjOnBrand) => void;
 
-    /**
-     * Called in case of an invalid Card Number, invalid Expiry Date, or incomplete field. Called again when errors are cleared.
-     */
-    onError?: (error: CbObjOnError | AdyenCheckoutError) => void;
-
     /**
      * Called when a field gains or loses focus.
      */

From 8b88b8f201e78e69cb7f3e773beff8f31b9d4a7a Mon Sep 17 00:00:00 2001
From: nicholas <nicholas.spong@adyen.com>
Date: Thu, 1 Feb 2024 12:09:06 +0100
Subject: [PATCH 51/55] Added onValidationError callback for the CustomCard
 component

---
 .../Card/components/CardInput/types.ts        |  6 +++---
 .../CustomCardInput/CustomCardInput.tsx       | 19 +++++++++++++++++--
 .../lib/src/components/CustomCard/types.ts    |  9 ++++++++-
 .../src/pages/CustomCards/CustomCards.js      |  7 +++++--
 .../pages/CustomCards/customCards.config.js   | 12 ------------
 5 files changed, 33 insertions(+), 20 deletions(-)

diff --git a/packages/lib/src/components/Card/components/CardInput/types.ts b/packages/lib/src/components/Card/components/CardInput/types.ts
index 1f8c8a4577..5b066c5dbb 100644
--- a/packages/lib/src/components/Card/components/CardInput/types.ts
+++ b/packages/lib/src/components/Card/components/CardInput/types.ts
@@ -168,9 +168,9 @@ export interface FieldError {
 }
 
 export interface SFError {
-    isValid: boolean;
-    errorMessage: string;
-    errorI18n: string;
+    isValid?: boolean;
+    errorMessage?: string;
+    errorI18n?: string;
     error: string;
     rootNode: HTMLElement;
     detectedBrands?: string[];
diff --git a/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx b/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx
index a21bc44abd..5243b36624 100644
--- a/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx
+++ b/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx
@@ -7,7 +7,8 @@ import { BinLookupResponse, CardBrandsConfiguration } from '../../Card/types';
 import SFExtensions from '../../internal/SecuredFields/binLookup/extensions';
 import { StylesObject } from '../../internal/SecuredFields/lib/types';
 import { Resources } from '../../../core/Context/Resources';
-import { Placeholders } from '../../Card/components/CardInput/types';
+import { Placeholders, SFError } from '../../Card/components/CardInput/types';
+import { ValidationError } from '../types';
 
 interface SecuredFieldsProps {
     autoFocus?: boolean;
@@ -113,13 +114,27 @@ function CustomCardInput(props: SecuredFieldsProps) {
     useEffect(() => {
         const sfStateErrorsObj = sfp.current.mapErrorsToValidationRuleResult();
 
+        const mappedErrors = { ...errors, ...sfStateErrorsObj }; // maps sfErrors
+
         props.onChange({
             data,
             valid,
-            errors: { ...errors, ...sfStateErrorsObj }, // maps sfErrors
+            errors: mappedErrors,
             isValid: isSfpValid,
             selectedBrandValue
         });
+
+        // Create an array of Validation error objects and send to callback
+        if (Object.keys(mappedErrors).length) {
+            const validationErrors: ValidationError[] = Object.entries(mappedErrors).map(([fieldType, error]) => {
+                const valErr: ValidationError = {
+                    fieldType,
+                    ...(error ? (error as SFError) : { error: '', rootNode: this.props.rootNode })
+                };
+                return valErr;
+            });
+            this.props.onValidationError(validationErrors);
+        }
     }, [data, valid, errors, selectedBrandValue]);
 
     /**
diff --git a/packages/lib/src/components/CustomCard/types.ts b/packages/lib/src/components/CustomCard/types.ts
index 4afa259d82..16765450a7 100644
--- a/packages/lib/src/components/CustomCard/types.ts
+++ b/packages/lib/src/components/CustomCard/types.ts
@@ -1,4 +1,5 @@
 import { CardConfiguration } from '../Card/types';
+import { SFError } from '../Card/components/CardInput/types';
 
 export type CustomCardConfiguration = Omit<
     CardConfiguration,
@@ -20,4 +21,10 @@ export type CustomCardConfiguration = Omit<
     | 'installmentOptions'
     | 'showInstallmentAmounts'
     | 'configuration'
->;
+> & {
+    onValidationError?: (validationErrors: ValidationError[]) => void;
+};
+
+export type ValidationError = SFError & {
+    fieldType: string;
+};
diff --git a/packages/playground/src/pages/CustomCards/CustomCards.js b/packages/playground/src/pages/CustomCards/CustomCards.js
index 62eebccef3..b0a633db2f 100644
--- a/packages/playground/src/pages/CustomCards/CustomCards.js
+++ b/packages/playground/src/pages/CustomCards/CustomCards.js
@@ -2,7 +2,7 @@ import { AdyenCheckout, CustomCard } from '@adyen/adyen-web';
 import '@adyen/adyen-web/styles/adyen.css';
 
 import { makePayment, makeDetailsCall } from '../../services';
-import { styles, setFocus, onBrand, onConfigSuccess, onBinLookup, onChange } from './customCards.config';
+import { styles, setFocus, onBrand, onConfigSuccess, onBinLookup, onChange, setCCErrors } from './customCards.config';
 import { styles_si, onConfigSuccess_si, onFieldValid_si, onBrand_si, onError_si, onFocus_si } from './customCards-si.config';
 import { fancyStyles, fancyChangeBrand, fancyErrors, fancyFieldValid, fancyFocus } from './customCards-fancy.config';
 import { materialStyles, materialFocus, handleMaterialError, onMaterialFieldValid } from './customCards-material.config';
@@ -92,7 +92,10 @@ const initCheckout = async () => {
         },
         onFocus: setFocus,
         onBinLookup,
-        onChange
+        onChange,
+        onValidationError: errors => {
+            errors.forEach(setCCErrors);
+        }
         // brandsConfiguration: {
         //     synchrony_plcc: {
         //         icon: 'http://localhost:3000/test_images/smartmoney.png'
diff --git a/packages/playground/src/pages/CustomCards/customCards.config.js b/packages/playground/src/pages/CustomCards/customCards.config.js
index 463ac5e533..5d56999183 100644
--- a/packages/playground/src/pages/CustomCards/customCards.config.js
+++ b/packages/playground/src/pages/CustomCards/customCards.config.js
@@ -258,18 +258,6 @@ export function onBinLookup(pCallbackObj) {
 }
 
 export function onChange(state, component) {
-    // From v5 the onError handler is no longer only for card comp related errors
-    // - so watch state.errors and use it to call the custom card specific 'setErrors' function
-    if (!!Object.keys(state.errors).length) {
-        const errors = Object.entries(state.errors).map(([fieldType, error]) => {
-            return {
-                fieldType,
-                ...(error ? error : { error: '', rootNode: component._node })
-            };
-        });
-        errors.forEach(setCCErrors);
-    }
-
     /**
      * If we're in a dual branding scenario & the number field becomes valid or is valid and become invalid
      * - set the brand logos to the required 'state'

From cbc28f2363c8b44eaff7d20e2662ff3de331e0ea Mon Sep 17 00:00:00 2001
From: nicholas <nicholas.spong@adyen.com>
Date: Mon, 5 Feb 2024 13:19:00 +0100
Subject: [PATCH 52/55] Added optional chaining operator for customCard's
 onValidationError callback

---
 .../components/CustomCard/CustomCardInput/CustomCardInput.tsx   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx b/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx
index 5243b36624..e0fd097152 100644
--- a/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx
+++ b/packages/lib/src/components/CustomCard/CustomCardInput/CustomCardInput.tsx
@@ -133,7 +133,7 @@ function CustomCardInput(props: SecuredFieldsProps) {
                 };
                 return valErr;
             });
-            this.props.onValidationError(validationErrors);
+            this.props.onValidationError?.(validationErrors);
         }
     }, [data, valid, errors, selectedBrandValue]);
 

From 03faf1f2cfc038a3fa2246f44d1c0f49e8ee6c14 Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Mon, 29 Jan 2024 19:24:58 +0100
Subject: [PATCH 53/55] fix giftcard callback types

---
 packages/lib/src/components/Giftcard/types.ts | 26 ++++++++++++++++---
 packages/lib/src/core/types.ts                |  7 +++--
 2 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/packages/lib/src/components/Giftcard/types.ts b/packages/lib/src/components/Giftcard/types.ts
index f9976e5296..e70e1bfaca 100644
--- a/packages/lib/src/components/Giftcard/types.ts
+++ b/packages/lib/src/components/Giftcard/types.ts
@@ -1,6 +1,7 @@
 import { FunctionComponent } from 'preact';
 import { GiftcardFieldsProps } from './components/types';
 import { UIElementProps } from '../internal/UIElement/types';
+import { Order, PaymentData } from '../../types/global-types';
 
 export interface GiftCardElementData {
     paymentMethod: {
@@ -11,16 +12,35 @@ export interface GiftCardElementData {
     };
 }
 
+export type balanceCheckResponseType = {
+    pspReference: string;
+    resultCode: string;
+    balance: {
+        currency: string;
+        value: number;
+    };
+};
+
+export type onBalanceCheckCallbackType = (
+    resolve: (res: balanceCheckResponseType) => void,
+    reject: (error: Error) => void,
+    data: GiftCardElementData
+) => Promise<void>;
+
+export type onOrderRequestCallbackType = (resolve: (order: Order) => void, reject: (error: Error) => void, data: PaymentData) => Promise<void>;
+
 // TODO: Fix these types
 export interface GiftCardConfiguration extends UIElementProps {
     pinRequired?: boolean;
     expiryDateRequired?: boolean;
     brandsConfiguration?: any;
     brand?: string;
-    onOrderUpdated?(data): void;
-    onOrderRequest?(resolve, reject, data): void;
-    onBalanceCheck?(resolve, reject, data): void;
+    onOrderUpdated?: (data) => void;
+    onBalanceCheck?: onBalanceCheckCallbackType;
+    onOrderRequest?: onOrderRequestCallbackType;
+
     onRequiringConfirmation?(): void;
+
     /**
      * @internal
      */
diff --git a/packages/lib/src/core/types.ts b/packages/lib/src/core/types.ts
index 0ee2643d60..6fdb80c4de 100644
--- a/packages/lib/src/core/types.ts
+++ b/packages/lib/src/core/types.ts
@@ -5,7 +5,6 @@ import {
     PaymentAction,
     PaymentMethodsResponse,
     ActionHandledReturnObject,
-    PaymentData,
     CheckoutAdvancedFlowResponse,
     PaymentMethodsRequestData,
     SessionsResponse,
@@ -16,7 +15,7 @@ import { AnalyticsOptions } from './Analytics/types';
 import { RiskModuleOptions } from './RiskModule/RiskModule';
 import UIElement from '../components/internal/UIElement/UIElement';
 import AdyenCheckoutError from './Errors/AdyenCheckoutError';
-import { GiftCardElementData } from '../components/Giftcard/types';
+import { onBalanceCheckCallbackType, onOrderRequestCallbackType } from '../components/Giftcard/types';
 import { SRPanelConfig } from './Errors/types';
 import { NewableComponent } from './core.registry';
 import Session from './CheckoutSession';
@@ -216,9 +215,9 @@ export interface CoreConfiguration {
 
     onError?(error: AdyenCheckoutError, element?: UIElement): void;
 
-    onBalanceCheck?(resolve: () => void, reject: () => void, data: GiftCardElementData): Promise<void>;
+    onBalanceCheck?: onBalanceCheckCallbackType;
 
-    onOrderRequest?(resolve: () => void, reject: () => void, data: PaymentData): Promise<void>;
+    onOrderRequest?: onOrderRequestCallbackType;
 
     onPaymentMethodsRequest?(
         data: PaymentMethodsRequestData,

From b5ed5f95c4c2a77a3a8c51c9cdab3d8e95f0b167 Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Mon, 5 Feb 2024 16:09:28 +0100
Subject: [PATCH 54/55] AmazonPay storybook

---
 .../lib/src/components/AmazonPay/types.ts     |  16 +--
 .../stories/wallets/AmazonPay.stories.tsx     |  20 +++
 .../stories/wallets/AmazonPayExample.tsx      | 132 ++++++++++++++++++
 3 files changed, 159 insertions(+), 9 deletions(-)
 create mode 100644 packages/lib/storybook/stories/wallets/AmazonPay.stories.tsx
 create mode 100644 packages/lib/storybook/stories/wallets/AmazonPayExample.tsx

diff --git a/packages/lib/src/components/AmazonPay/types.ts b/packages/lib/src/components/AmazonPay/types.ts
index 4e912ce462..dab8eac1dc 100644
--- a/packages/lib/src/components/AmazonPay/types.ts
+++ b/packages/lib/src/components/AmazonPay/types.ts
@@ -1,4 +1,3 @@
-import Language from '../../language/Language';
 import { SUPPORTED_LOCALES_EU, SUPPORTED_LOCALES_US } from './config';
 import UIElement from '../internal/UIElement/UIElement';
 import { UIElementProps } from '../internal/UIElement/types';
@@ -50,7 +49,6 @@ export interface AmazonPayConfiguration extends UIElementProps {
     currency?: Currency;
     deliverySpecifications?: DeliverySpecifications;
     environment?: string;
-    i18n: Language;
     loadingContext?: string;
     locale?: string;
     merchantMetadata?: MerchantMetadata;
@@ -60,14 +58,14 @@ export interface AmazonPayConfiguration extends UIElementProps {
     productType?: ProductType;
     recurringMetadata?: RecurringMetadata;
     returnUrl?: string;
-    showChangePaymentDetailsButton: boolean;
-    showOrderButton: boolean;
-    showPayButton: boolean;
-    showSignOutButton: boolean;
+    showChangePaymentDetailsButton?: boolean;
+    showOrderButton?: boolean;
+    showPayButton?: boolean;
+    showSignOutButton?: boolean;
     signature?: string;
-    onClick: (resolve, reject) => Promise<void>;
-    onError: (error, component) => void;
-    onSignOut: (resolve, reject) => Promise<void>;
+    onClick?: (resolve, reject) => Promise<void>;
+    onError?: (error, component) => void;
+    onSignOut?: (resolve, reject) => Promise<void>;
 }
 
 export interface AmazonPayComponentProps extends AmazonPayConfiguration {
diff --git a/packages/lib/storybook/stories/wallets/AmazonPay.stories.tsx b/packages/lib/storybook/stories/wallets/AmazonPay.stories.tsx
new file mode 100644
index 0000000000..85f0a714bc
--- /dev/null
+++ b/packages/lib/storybook/stories/wallets/AmazonPay.stories.tsx
@@ -0,0 +1,20 @@
+import { MetaConfiguration, StoryConfiguration } from '../types';
+import { ApplePayConfiguration } from '../../../src/components/ApplePay/types';
+import { AmazonPayConfiguration } from '../../../src/components/AmazonPay/types';
+import { AmazonPayExample } from './AmazonPayExample';
+
+type AmazonPayStory = StoryConfiguration<AmazonPayConfiguration>;
+
+const meta: MetaConfiguration<ApplePayConfiguration> = {
+    title: 'Wallets/AmazonPay'
+};
+
+export const Default: AmazonPayStory = {
+    render: args => {
+        return <AmazonPayExample contextArgs={args} />;
+    },
+    args: {
+        countryCode: 'GB'
+    }
+};
+export default meta;
diff --git a/packages/lib/storybook/stories/wallets/AmazonPayExample.tsx b/packages/lib/storybook/stories/wallets/AmazonPayExample.tsx
new file mode 100644
index 0000000000..945a4eb39d
--- /dev/null
+++ b/packages/lib/storybook/stories/wallets/AmazonPayExample.tsx
@@ -0,0 +1,132 @@
+import { useEffect, useRef, useState } from 'preact/hooks';
+import { createSessionsCheckout } from '../../helpers/create-sessions-checkout';
+import { createAdvancedFlowCheckout } from '../../helpers/create-advanced-checkout';
+import { PaymentMethodStoryProps } from '../types';
+import AmazonPay from '../../../src/components/AmazonPay';
+import { AmazonPayConfiguration } from '../../../src/components/AmazonPay/types';
+
+interface AmazonPayExampleProps {
+    contextArgs: PaymentMethodStoryProps<AmazonPayConfiguration>;
+}
+
+export const AmazonPayExample = ({ contextArgs }: AmazonPayExampleProps) => {
+    const container = useRef(null);
+    const checkout = useRef(null);
+    const [element, setElement] = useState(null);
+    const [errorMessage, setErrorMessage] = useState(null);
+
+    const createCheckout = async () => {
+        const { useSessions, showPayButton, countryCode, shopperLocale, amount } = contextArgs;
+
+        //URL selection
+        // http://localhost:3020/iframe.html?id=wallets-amazonpay--default&viewMode=story
+        // http://localhost:3020/?path=/story/wallets-amazonpay--default
+        // either has iframe or path
+
+        const urlSearchParams = new URLSearchParams(window.location.search);
+        const amazonCheckoutSessionId = urlSearchParams.get('amazonCheckoutSessionId');
+        const step = urlSearchParams.get('step');
+
+        // TODO move this to args
+        const chargeOptions = {
+            // chargePermissionType: 'Recurring',
+            // recurringMetadata: {
+            //     frequency: {
+            //         unit: 'Month',
+            //         value: '1'
+            //     }
+            // }
+        };
+
+        checkout.current = useSessions
+            ? await createSessionsCheckout({ showPayButton, countryCode, shopperLocale, amount })
+            : await createAdvancedFlowCheckout({
+                  showPayButton,
+                  countryCode,
+                  shopperLocale,
+                  amount
+              });
+
+        if (step === 'review') {
+            const amazonPayElement = new AmazonPay({
+                core: checkout.current,
+                ...chargeOptions,
+                /**
+                 * The merchant will receive the amazonCheckoutSessionId attached in the return URL.
+                 */
+                amazonCheckoutSessionId,
+                cancelUrl: 'http://localhost:3020/iframe.html?id=wallets-amazonpay--default&viewMode=story',
+                returnUrl: 'http://localhost:3020/iframe.html?id=wallets-amazonpay--default&viewMode=story&step=result'
+            });
+            setElement(amazonPayElement);
+        } else if (step === 'result') {
+            const amazonPayElement = new AmazonPay({
+                core: checkout.current,
+                /**
+                 * The merchant will receive the amazonCheckoutSessionId attached in the return URL.
+                 */
+                amazonCheckoutSessionId,
+                showOrderButton: false,
+                onSubmit: (state, component) => {
+                    return makePayment(state.data)
+                        .then(response => {
+                            if (response.action) {
+                                component.handleAction(response.action);
+                            } else if (response?.resultCode && checkPaymentResult(response.resultCode)) {
+                                alert(response.resultCode);
+                            } else {
+                                // Try handling the decline flow
+                                // This will redirect the shopper to select another payment method
+                                component.handleDeclineFlow();
+                            }
+                        })
+                        .catch(error => {
+                            throw Error(error);
+                        });
+                },
+                onError: e => {
+                    if (e.resultCode) {
+                        alert(e.resultCode);
+                    } else {
+                        console.error(e);
+                    }
+                }
+            });
+            setElement(amazonPayElement);
+        } else {
+            const amazonPayElement = new AmazonPay({
+                core: checkout.current,
+
+                productType: 'PayOnly',
+                ...chargeOptions,
+                // Regular checkout:
+                // checkoutMode: 'ProcessOrder'
+
+                // Express Checkout flow:
+                returnUrl: 'http://localhost:3020/?path=/story/wallets-amazonpay--default&step=review'
+            });
+            setElement(amazonPayElement);
+        }
+    };
+
+    useEffect(() => {
+        void createCheckout();
+    }, [contextArgs]);
+
+    useEffect(() => {
+        if (element?.isAvailable) {
+            element
+                .isAvailable()
+                .then(() => {
+                    element.mount(container.current);
+                })
+                .catch(error => {
+                    setErrorMessage(error.toString());
+                });
+        } else if (element) {
+            element.mount(container.current);
+        }
+    }, [element]);
+
+    return <div>{errorMessage ? <div>{errorMessage}</div> : <div ref={container} id="component-root" className="component-wrapper" />}</div>;
+};

From 4d1b1cbf75ec4def43837d2b9df18d8a66535932 Mon Sep 17 00:00:00 2001
From: antoniof <m1aw@users.noreply.github.com>
Date: Mon, 5 Feb 2024 16:49:06 +0100
Subject: [PATCH 55/55] fix type def amazonpay

---
 packages/lib/src/components/AmazonPay/AmazonPay.tsx |  6 +++++-
 packages/lib/src/components/AmazonPay/types.ts      | 12 ++++++++++--
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/packages/lib/src/components/AmazonPay/AmazonPay.tsx b/packages/lib/src/components/AmazonPay/AmazonPay.tsx
index b9398a1671..6c458f8f04 100644
--- a/packages/lib/src/components/AmazonPay/AmazonPay.tsx
+++ b/packages/lib/src/components/AmazonPay/AmazonPay.tsx
@@ -52,7 +52,7 @@ export class AmazonPayElement extends UIElement<AmazonPayConfiguration> {
         return getCheckoutDetails(loadingContext, clientKey, request);
     }
 
-    handleDeclineFlow() {
+    public handleDeclineFlow() {
         const { amazonCheckoutSessionId, configuration = {}, loadingContext, clientKey } = this.props;
         if (!amazonCheckoutSessionId) return console.error('Could handle the decline flow. Missing checkoutSessionId.');
 
@@ -96,6 +96,10 @@ export class AmazonPayElement extends UIElement<AmazonPayConfiguration> {
                     ref={ref => {
                         this.componentRef = ref;
                     }}
+                    showPayButton={this.props.showPayButton}
+                    onClick={this.props.onClick}
+                    onError={this.props.onError}
+                    onSignOut={this.props.onSignOut}
                     {...this.props}
                 />
             </CoreProvider>
diff --git a/packages/lib/src/components/AmazonPay/types.ts b/packages/lib/src/components/AmazonPay/types.ts
index dab8eac1dc..8faf8655d5 100644
--- a/packages/lib/src/components/AmazonPay/types.ts
+++ b/packages/lib/src/components/AmazonPay/types.ts
@@ -1,7 +1,7 @@
 import { SUPPORTED_LOCALES_EU, SUPPORTED_LOCALES_US } from './config';
-import UIElement from '../internal/UIElement/UIElement';
 import { UIElementProps } from '../internal/UIElement/types';
 import { BrowserInfo, PaymentAmount } from '../../types/global-types';
+import AmazonPayElement from './AmazonPay';
 
 declare global {
     interface Window {
@@ -52,7 +52,7 @@ export interface AmazonPayConfiguration extends UIElementProps {
     loadingContext?: string;
     locale?: string;
     merchantMetadata?: MerchantMetadata;
-    onSubmit?: (state: any, element: UIElement) => void;
+    onSubmit?: (state: any, element: AmazonPayElement) => void;
     payButton?: any;
     placement?: Placement;
     productType?: ProductType;
@@ -69,6 +69,14 @@ export interface AmazonPayConfiguration extends UIElementProps {
 }
 
 export interface AmazonPayComponentProps extends AmazonPayConfiguration {
+    showPayButton: boolean;
+    showSignOutButton?: boolean;
+    amazonCheckoutSessionId?: string;
+    showOrderButton?: boolean;
+    showChangePaymentDetailsButton?: boolean;
+    onClick: (resolve, reject) => Promise<void>;
+    onError: (error, component) => void;
+    onSignOut: (resolve, reject) => Promise<void>;
     ref: any;
 }