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 { 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 { public static type = TxVariants.googlepay; @@ -110,7 +110,7 @@ class GooglePay extends UIElement { 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

this.makePaymentsCall() .then(sanitizeResponse) - .then(this.verifyPaymentDidNotFail) + .then(verifyPaymentDidNotFail) .then(this.handleResponse) .catch(this.handleFailedResult); } @@ -164,7 +164,7 @@ export abstract class UIElement

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

// }; } - protected verifyPaymentDidNotFail(response: PaymentResponseData): Promise { - 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

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

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

}; 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 { + 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); }