• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Adyen / adyen-web / 11934929963

20 Nov 2024 02:01PM UTC coverage: 79.907%. First build
11934929963

Pull #2973

github

web-flow
Merge 78080a9a8 into 4bd35a257
Pull Request #2973: Send error events for redirect errors

3204 of 4575 branches covered (70.03%)

Branch coverage included in aggregate %.

29 of 30 new or added lines in 11 files covered. (96.67%)

8977 of 10669 relevant lines covered (84.14%)

710.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.53
/packages/lib/src/components/internal/UIElement/UIElement.tsx
1
import { h } from 'preact';
108✔
2
import BaseElement from '../BaseElement/BaseElement';
108✔
3
import PayButton from '../PayButton';
108✔
4
import { assertIsDropin, cleanupFinalResult, getRegulatoryDefaults, sanitizeResponse, verifyPaymentDidNotFail } from './utils';
108✔
5
import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError';
108✔
6
import { hasOwnProperty } from '../../../utils/hasOwnProperty';
108✔
7
import { Resources } from '../../../core/Context/Resources';
8
import { ANALYTICS_SUBMIT_STR } from '../../../core/Analytics/constants';
108✔
9

10
import type { AnalyticsInitialEvent, SendAnalyticsObject } from '../../../core/Analytics/types';
11
import type { CoreConfiguration, ICore, AdditionalDetailsData } from '../../../core/types';
12
import type { ComponentMethodsRef, PayButtonFunctionProps, UIElementProps, UIElementStatus } from './types';
13
import type { CheckoutSessionDetailsResponse, CheckoutSessionPaymentResponse } from '../../../core/CheckoutSession/types';
14
import type {
15
    ActionHandledReturnObject,
16
    CheckoutAdvancedFlowResponse,
17
    Order,
18
    PaymentAction,
19
    PaymentAmount,
20
    PaymentData,
21
    PaymentMethodsResponse,
22
    PaymentResponseData,
23
    RawPaymentResponse
24
} from '../../../types/global-types';
25
import type { IDropin } from '../../Dropin/types';
26
import type { NewableComponent } from '../../../core/core.registry';
27
import CancelError from '../../../core/Errors/CancelError';
108✔
28

29
import './UIElement.scss';
108✔
30

31
export abstract class UIElement<P extends UIElementProps = UIElementProps> extends BaseElement<P> {
108✔
32
    protected componentRef: any;
33

34
    protected resources: Resources;
35

36
    public elementRef: UIElement;
37

38
    public static type = undefined;
108✔
39

40
    /**
41
     * Defines all txVariants that the Component supports (in case it support multiple ones besides the 'type' one)
42
     */
43
    public static txVariants: string[] = [];
108✔
44

45
    constructor(checkout: ICore, props?: P) {
46
        super(checkout, props);
780✔
47

48
        this.core.register(this.constructor as NewableComponent);
778✔
49

50
        this.submit = this.submit.bind(this);
778✔
51
        this.setState = this.setState.bind(this);
778✔
52
        this.onComplete = this.onComplete.bind(this);
778✔
53
        this.handleAction = this.handleAction.bind(this);
778✔
54
        this.handleOrder = this.handleOrder.bind(this);
778✔
55
        this.handleAdditionalDetails = this.handleAdditionalDetails.bind(this);
778✔
56
        this.handleResponse = this.handleResponse.bind(this);
778✔
57
        this.setElementStatus = this.setElementStatus.bind(this);
778✔
58
        this.submitAnalytics = this.submitAnalytics.bind(this);
778✔
59
        this.makePaymentsCall = this.makePaymentsCall.bind(this);
778✔
60
        this.makeAdditionalDetailsCall = this.makeAdditionalDetailsCall.bind(this);
778✔
61
        this.submitUsingSessionsFlow = this.submitUsingSessionsFlow.bind(this);
778✔
62

63
        this.elementRef = (props && props.elementRef) || this;
778✔
64
        this.resources = this.props.modules ? this.props.modules.resources : undefined;
778✔
65

66
        this.storeElementRefOnCore(this.props);
778✔
67

68
        this.onEnterKeyPressed = this.onEnterKeyPressed.bind(this);
778✔
69
        this.onActionHandled = this.onActionHandled.bind(this);
778✔
70
    }
71

72
    protected override buildElementProps(componentProps?: P) {
73
        const globalCoreProps = this.core.getCorePropsForComponent();
780✔
74
        const isStoredPaymentMethod = !!componentProps?.isStoredPaymentMethod;
780✔
75
        const paymentMethodsResponseProps = isStoredPaymentMethod
780✔
76
            ? {}
77
            : this.core.paymentMethodsResponse.find(componentProps?.type || this.constructor['type']);
1,360✔
78

79
        const finalProps = {
780✔
80
            showPayButton: true,
81
            ...globalCoreProps,
82
            ...paymentMethodsResponseProps,
83
            ...componentProps
84
        };
85

86
        const isDropin = assertIsDropin(this as unknown as IDropin);
780✔
87

88
        this.props = this.formatProps({
780✔
89
            ...this.constructor['defaultProps'], // component defaults
90
            ...getRegulatoryDefaults(this.core.options.countryCode, isDropin), // regulatory defaults
91
            ...finalProps // the rest (inc. merchant defined config)
92
        });
93
    }
94

95
    protected storeElementRefOnCore(props?: P) {
96
        if (!props?.isDropin) {
716✔
97
            this.core.storeElementReference(this);
608✔
98
        }
99
    }
100

101
    public isAvailable(): Promise<void> {
102
        return Promise.resolve();
98✔
103
    }
104

105
    public setState(newState: object): void {
106
        this.state = { ...this.state, ...newState };
2,506✔
107
        this.onChange();
2,506✔
108
    }
109

110
    public showValidation(): this {
111
        if (this.componentRef && this.componentRef.showValidation) this.componentRef.showValidation();
16✔
112
        return this;
16✔
113
    }
114

115
    public setElementStatus(status: UIElementStatus, props?: any): this {
116
        this.elementRef?.setStatus(status, props);
98✔
117
        return this;
98✔
118
    }
119

120
    public setStatus(status: UIElementStatus, props?): this {
121
        if (this.componentRef?.setStatus) {
116✔
122
            this.componentRef.setStatus(status, props);
64✔
123
        }
124
        return this;
116✔
125
    }
126

127
    protected onChange(): void {
128
        this.props.onChange?.(
2,508✔
129
            {
130
                data: this.data,
131
                isValid: this.isValid,
132
                errors: this.state.errors,
133
                valid: this.state.valid
134
            },
135
            this.elementRef
136
        );
137
    }
138

139
    // Only called once, for UIElements (including Dropin), as they are being mounted
140
    protected setUpAnalytics(setUpAnalyticsObj: AnalyticsInitialEvent) {
141
        const sessionId = this.props.session?.id;
20✔
142

143
        return this.props.modules.analytics.setUp({
20✔
144
            ...setUpAnalyticsObj,
145
            ...(sessionId && { sessionId })
20!
146
        });
147
    }
148

149
    /**
150
     * A function for all UIElements, or BaseElement, to use to create an analytics action for when it's been:
151
     *  - mounted,
152
     *  - a PM has been selected
153
     *  - onSubmit has been called (as a result of the pay button being pressed)
154
     *
155
     *  In some other cases e.g. 3DS2 components, this function is overridden to allow more specific analytics actions to be created
156
     */
157

158
    protected submitAnalytics(analyticsObj: SendAnalyticsObject, uiElementProps?) {
159
        /** Work out what the component's "type" is:
160
         * - first check for a dedicated "analyticsType" (currently only applies to custom-cards)
161
         * - otherwise, distinguish cards from non-cards: cards will use their static type property, everything else will use props.type
162
         */
163
        try {
138✔
164
            this.props.modules.analytics.sendAnalytics(this.getComponent(analyticsObj), analyticsObj, uiElementProps);
138✔
165
        } catch (error) {
166
            console.warn('Failed to submit the analytics event. Error:', error);
36✔
167
        }
168
    }
169

170
    private getComponent({ component }: SendAnalyticsObject): string {
171
        if (component) {
102✔
172
            return component;
4✔
173
        }
174
        if (this.constructor['analyticsType']) {
98!
NEW
175
            return this.constructor['analyticsType'];
×
176
        }
177
        if (this.constructor['type'] === 'scheme' || this.constructor['type'] === 'bcmc') {
98✔
178
            return this.constructor['type'];
30✔
179
        }
180
        return this.props.type;
68✔
181
    }
182

183
    public submit(): void {
184
        if (!this.isValid) {
70✔
185
            this.showValidation();
12✔
186
            return;
12✔
187
        }
188

189
        this.makePaymentsCall()
58✔
190
            .then(sanitizeResponse)
191
            .then(verifyPaymentDidNotFail)
192
            .then(this.handleResponse)
193
            .catch((e: PaymentResponseData | Error) => {
194
                if (e instanceof CancelError) {
8✔
195
                    this.setElementStatus('ready');
2✔
196
                    return;
2✔
197
                }
198
                this.handleFailedResult(e as PaymentResponseData);
6✔
199
            });
200
    }
201

202
    protected makePaymentsCall(): Promise<CheckoutAdvancedFlowResponse | CheckoutSessionPaymentResponse> {
203
        this.setElementStatus('loading');
74✔
204

205
        if (this.props.onSubmit) {
74✔
206
            return this.submitUsingAdvancedFlow();
62✔
207
        }
208

209
        if (this.core.session) {
12✔
210
            const beforeSubmitEvent: Promise<PaymentData> = this.props.beforeSubmit
12✔
211
                ? new Promise((resolve, reject) =>
212
                      this.props.beforeSubmit(this.data, this.elementRef, {
4✔
213
                          resolve,
214
                          reject: () => reject(new CancelError('beforeSubmitRejected'))
2✔
215
                      })
216
                  )
217
                : Promise.resolve(this.data);
218

219
            return beforeSubmitEvent.then(this.submitUsingSessionsFlow);
12✔
220
        }
221

222
        this.handleError(
×
223
            new AdyenCheckoutError(
224
                'IMPLEMENTATION_ERROR',
225
                'It can not perform /payments call. Callback "onSubmit" is missing or Checkout session is not available'
226
            )
227
        );
228
    }
229

230
    private async submitUsingAdvancedFlow(): Promise<CheckoutAdvancedFlowResponse> {
231
        return new Promise<CheckoutAdvancedFlowResponse>((resolve, reject) => {
62✔
232
            // Call analytics endpoint
233
            this.submitAnalytics({ type: ANALYTICS_SUBMIT_STR });
62✔
234

235
            this.props.onSubmit(
62✔
236
                {
237
                    data: this.data,
238
                    isValid: this.isValid
239
                },
240
                this.elementRef,
241
                { resolve, reject }
242
            );
243
        });
244
    }
245

246
    private async submitUsingSessionsFlow(data: PaymentData): Promise<CheckoutSessionPaymentResponse> {
247
        this.submitAnalytics({ type: ANALYTICS_SUBMIT_STR });
8✔
248

249
        try {
8✔
250
            return await this.core.session.submitPayment(data);
8✔
251
        } catch (error: unknown) {
252
            if (error instanceof AdyenCheckoutError) this.handleError(error);
4!
253
            else this.handleError(new AdyenCheckoutError('ERROR', 'Error when making /payments call', { cause: error }));
4✔
254

255
            return Promise.reject(error);
4✔
256
        }
257

258
        // // Uncomment to simulate failed
259
        // return {
260
        //     resultCode: 'Refused',
261
        //     sessionData:
262
        //         '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',
263
        //     sessionResult:
264
        //         'X3XtfGC7!H4sIAAAAAAAA/6tWykxRslJyDjaxNDMyM3E2MXIyNDUys3RU0lHKTS1KzkjMK3FMTs4vzSsBKgtJLS7xhYo6Z6QmZ+eXlgAVFpcklpQWA+WLUtNKi1NTlGoBMEEbz1cAAAA=iMsCaEJ5LcnsqIUtmNxjm8HtfQ8gZW8JewEU3wHz4qg='
265
        // };
266
    }
267

268
    protected onComplete(state): void {
269
        if (this.props.onComplete) this.props.onComplete(state, this.elementRef);
4✔
270
    }
271

272
    protected handleError = (error: AdyenCheckoutError): void => {
778✔
273
        /**
274
         * Set status using elementRef, which:
275
         * - If Drop-in, will set status for Dropin component, and then it will propagate the new status for the active payment method component
276
         * - If Component, it will set its own status
277
         */
278
        this.setElementStatus('ready');
8✔
279

280
        if (this.props.onError) {
8✔
281
            this.props.onError(error, this.elementRef);
4✔
282
        }
283
    };
284

285
    protected handleAdditionalDetails(state: AdditionalDetailsData): void {
286
        this.makeAdditionalDetailsCall(state)
10✔
287
            .then(sanitizeResponse)
288
            .then(verifyPaymentDidNotFail)
289
            .then(this.handleResponse)
290
            .catch(this.handleFailedResult);
291
    }
292

293
    private makeAdditionalDetailsCall(state: AdditionalDetailsData): Promise<CheckoutSessionDetailsResponse | CheckoutAdvancedFlowResponse> {
294
        if (this.props.onAdditionalDetails) {
10✔
295
            return new Promise<CheckoutAdvancedFlowResponse>((resolve, reject) => {
8✔
296
                this.props.onAdditionalDetails(state, this.elementRef, { resolve, reject });
8✔
297
            });
298
        }
299

300
        if (this.core.session) {
2✔
301
            return this.submitAdditionalDetailsUsingSessionsFlow(state.data);
2✔
302
        }
303

304
        this.handleError(
×
305
            new AdyenCheckoutError(
306
                'IMPLEMENTATION_ERROR',
307
                'It can not perform /payments/details call. Callback "onAdditionalDetails" is missing or Checkout session is not available'
308
            )
309
        );
310
    }
311

312
    private async submitAdditionalDetailsUsingSessionsFlow(data: any): Promise<CheckoutSessionDetailsResponse> {
313
        try {
2✔
314
            return await this.core.session.submitDetails(data);
2✔
315
        } catch (error: unknown) {
316
            if (error instanceof AdyenCheckoutError) this.handleError(error);
×
317
            else this.handleError(new AdyenCheckoutError('ERROR', 'Error when making /details call', { cause: error }));
×
318

319
            return Promise.reject(error);
×
320
        }
321
    }
322

323
    public handleAction(action: PaymentAction, props = {}): UIElement | null {
10✔
324
        if (!action || !action.type) {
10✔
325
            if (hasOwnProperty(action, 'action') && hasOwnProperty(action, 'resultCode')) {
4✔
326
                throw new Error(
2✔
327
                    'handleAction::Invalid Action - the passed action object itself has an "action" property and ' +
328
                        'a "resultCode": have you passed in the whole response object by mistake?'
329
                );
330
            }
331
            throw new Error('handleAction::Invalid Action - the passed action object does not have a "type" property');
2✔
332
        }
333

334
        const paymentAction = this.core.createFromAction(action, {
6✔
335
            ...this.elementRef.props,
336
            ...props,
337
            onAdditionalDetails: this.handleAdditionalDetails
338
        });
339

340
        if (paymentAction) {
6✔
341
            this.unmount();
4✔
342
            return paymentAction.mount(this._node);
4✔
343
        }
344

345
        return null;
2✔
346
    }
347

348
    protected onActionHandled(actionHandledObj: ActionHandledReturnObject) {
349
        this.props?.onActionHandled?.({ originalAction: this.props.originalAction, ...actionHandledObj });
4✔
350
    }
351

352
    protected handleOrder = (response: PaymentResponseData): void => {
778✔
353
        const { order } = response;
6✔
354

355
        const updateCorePromise = this.core.session ? this.core.update({ order }) : this.handleAdvanceFlowPaymentMethodsUpdate(order);
6✔
356

357
        void updateCorePromise.then(() => {
6✔
358
            this.props.onOrderUpdated?.({ order });
6✔
359
        });
360
    };
361

362
    /**
363
     * Handles when the payment fails. The payment fails when:
364
     * - adv flow: the merchant rejects the payment due to a critical error
365
     * - adv flow: the merchant resolves the payment with a failed resultCode
366
     * - sessions: a network error occurs when making the payment
367
     * - sessions: the payment fails with a failed resultCode
368
     *
369
     * @param result
370
     */
371
    protected handleFailedResult = (result?: PaymentResponseData): void => {
778✔
372
        if (assertIsDropin(this.elementRef)) {
26!
373
            this.elementRef.displayFinalAnimation('error');
×
374
        }
375

376
        cleanupFinalResult(result);
26✔
377
        this.props.onPaymentFailed?.(result, this.elementRef);
26✔
378
    };
379

380
    protected handleSuccessResult = (result: PaymentResponseData): void => {
778✔
381
        if (assertIsDropin(this.elementRef)) {
20!
382
            this.elementRef.displayFinalAnimation('success');
×
383
        }
384

385
        cleanupFinalResult(result);
20✔
386
        this.props.onPaymentCompleted?.(result, this.elementRef);
20✔
387
    };
388

389
    /**
390
     * Handles a /payments or /payments/details response.
391
     * The component will handle automatically actions, orders, and final results.
392
     *
393
     * @param rawResponse -
394
     */
395
    protected handleResponse(rawResponse: RawPaymentResponse): void {
396
        const response = sanitizeResponse(rawResponse);
28✔
397

398
        if (response.action) {
28✔
399
            this.elementRef.handleAction(response.action);
2✔
400
            return;
2✔
401
        }
402

403
        if (response.order?.remainingAmount?.value > 0) {
26✔
404
            // we don't want to call elementRef here, use the component handler
405
            // we do this way so the logic on handlingOrder is associated with payment method
406
            this.handleOrder(response);
6✔
407
            return;
6✔
408
        }
409

410
        this.handleSuccessResult(response);
20✔
411
    }
412

413
    protected handleKeyPress(e: h.JSX.TargetedKeyboardEvent<HTMLInputElement> | KeyboardEvent) {
414
        if (e.key === 'Enter' || e.code === 'Enter') {
54✔
415
            e.preventDefault(); // Prevent <form> submission if Component is placed inside a form
4✔
416

417
            this.onEnterKeyPressed(document?.activeElement, this);
4✔
418
        }
419
    }
420

421
    /**
422
     * Handle Enter key pressed from a UIElement (called via handleKeyPress)
423
     * @param obj
424
     */
425
    protected onEnterKeyPressed(activeElement: Element, component: UIElement) {
426
        if (this.props.onEnterKeyPressed) {
4✔
427
            this.props.onEnterKeyPressed(activeElement, component);
2✔
428
        } else {
429
            (activeElement as HTMLElement).blur();
2✔
430
            this.submit();
2✔
431
        }
432
    }
433

434
    /**
435
     * Call update on parent instance
436
     * This function exist to make safe access to the protected _parentInstance
437
     * @param options - CoreOptions
438
     */
439
    public updateParent(options: CoreConfiguration = {}): Promise<ICore> {
×
440
        return this.elementRef.core.update(options);
×
441
    }
442

443
    public setComponentRef = (ref: ComponentMethodsRef) => {
778✔
444
        this.componentRef = ref;
68✔
445
    };
446

447
    /**
448
     * Get the current validation status of the element
449
     */
450
    public get isValid(): boolean {
451
        return false;
2✔
452
    }
453

454
    /**
455
     * Get the element icon URL for the current environment
456
     */
457

458
    public get icon(): string {
459
        const type = this.props.paymentMethodType || this.type;
136✔
460
        return this.props.icon ?? this.resources.getImage()(type);
136✔
461
    }
462

463
    /**
464
     * Get the element's displayable name
465
     */
466
    public get displayName(): string {
467
        const paymentMethodFromResponse = this.core.paymentMethodsResponse?.paymentMethods?.find(pm => pm.type === this.type);
384✔
468
        return this.props.name || paymentMethodFromResponse?.name || this.type;
198✔
469
    }
470

471
    /**
472
     * Get the element accessible name, used in the aria-label of the button that controls selected payment method
473
     */
474
    public get accessibleName(): string {
475
        return this.displayName;
×
476
    }
477

478
    /**
479
     * Used to display the second line of a payment method item
480
     */
481
    get additionalInfo(): string {
482
        return null;
160✔
483
    }
484

485
    /**
486
     * Return the type of an element
487
     */
488
    public get type(): string {
489
        return this.props.type || this.constructor['type'];
840✔
490
    }
491

492
    /**
493
     * Get the payButton component for the current element
494
     */
495
    protected payButton = (props: PayButtonFunctionProps) => {
778✔
496
        return <PayButton {...props} amount={this.props.amount} secondaryAmount={this.props.secondaryAmount} onClick={this.submit} />;
3,414✔
497
    };
498

499
    /**
500
     * Used in the Partial Orders flow.
501
     * When the Order is updated, the merchant can request new payment methods based on the new remaining amount
502
     *
503
     * @private
504
     */
505
    protected async handleAdvanceFlowPaymentMethodsUpdate(order: Order | null, amount?: PaymentAmount) {
506
        return new Promise<void | PaymentMethodsResponse>((resolve, reject) => {
4✔
507
            if (!this.props.onPaymentMethodsRequest) {
4✔
508
                return resolve();
2✔
509
            }
510

511
            this.props.onPaymentMethodsRequest(
2✔
512
                {
513
                    ...(order && {
4✔
514
                        order: {
515
                            orderData: order.orderData,
516
                            pspReference: order.pspReference
517
                        }
518
                    }),
519
                    locale: this.core.options.locale
520
                },
521
                { resolve, reject }
522
            );
523
        })
524
            .catch(error => {
525
                this.handleError(
×
526
                    new AdyenCheckoutError(
527
                        'IMPLEMENTATION_ERROR',
528
                        'Something failed during payment methods update or onPaymentMethodsRequest was not implemented',
529
                        {
530
                            cause: error
531
                        }
532
                    )
533
                );
534
            })
535
            .then(paymentMethodsResponse => {
536
                // in the case of the session flow we get order, amount, countryCode and shopperLocale from initialize()
537
                // apply the same logic here for order and amount
538
                // in the future it might be worth moving this logic to be performed by the core on update()
539
                // it would make this more consistent
540
                return this.core.update({
4✔
541
                    ...(paymentMethodsResponse && { paymentMethodsResponse }),
6✔
542
                    order,
543
                    amount: order ? order.remainingAmount : amount
4!
544
                });
545
            });
546
    }
547
}
548

549
export default UIElement;
108✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc