diff --git a/__sdk__.js b/__sdk__.js index e40a140f3..0e50b9a12 100644 --- a/__sdk__.js +++ b/__sdk__.js @@ -95,5 +95,6 @@ module.exports = { }, "three-domain-secure": { entry: "./src/three-domain-secure/interface", + globals, }, }; diff --git a/src/three-domain-secure/component.jsx b/src/three-domain-secure/component.jsx index cfa6688a6..0ddf87739 100644 --- a/src/three-domain-secure/component.jsx +++ b/src/three-domain-secure/component.jsx @@ -15,7 +15,8 @@ import type { responseBody, MerchantPayloadData, SdkConfig, - threeDSResponse, + ThreeDSResponse, + HeliosResponse, TDSProps, Update3DSTokenResponse, } from "./types"; @@ -63,7 +64,7 @@ const parseMerchantPayload = ({ export interface ThreeDomainSecureComponentInterface { isEligible(payload: MerchantPayloadData): Promise; - show(): Promise; + show(): Promise; } export class ThreeDomainSecureComponent { @@ -131,63 +132,83 @@ export class ThreeDomainSecureComponent { } } - async show(): Promise { + async show(): Promise { if (!this.threeDSIframe) { - throw new ValidationError(`Ineligible for three domain secure`); + return Promise.reject( + new ValidationError(`Ineligible for three domain secure`) + ); } - const promise = new ZalgoPromise(); - const cancelThreeDS = () => { - return ZalgoPromise.try(() => { - this.logger.warn("3DS Cancelled"); - }).then(() => { - // eslint-disable-next-line no-use-before-define + // eslint-disable-next-line compat/compat + return new Promise((resolve, reject) => { + let authenticationState, + liabilityShift = "false"; + const cancelThreeDS = () => { + return ZalgoPromise.try(() => { + this.logger.warn("3DS Cancelled"); + }).then(() => { + resolve({ + authenticationState: "cancelled", + liabilityShift: "false", + nonce: this.fastlaneNonce, + }); + // eslint-disable-next-line no-use-before-define + instance.close(); + }); + }; + + const instance = this.threeDSIframe({ + payerActionUrl: this.authenticationURL, + onSuccess: async (res) => { + const { reference_id, liability_shift, success } = res; + let enrichedNonce; + // Helios returns a boolen parameter: "success" + // It will be true for all cases where liability is shifted to merchant + // and false for downstream failures and errors + authenticationState = success ? "success" : "errored"; + liabilityShift = liability_shift ? liability_shift : "false"; + + // call BT mutation to update fastlaneNonce with 3ds data + // reference_id will be available for all usecases(success/failure) + if (reference_id) { + const gqlResponse = await this.updateNonceWith3dsData(reference_id); + const { data, errors } = gqlResponse; + if (data) { + enrichedNonce = + data.updateTokenizedCreditCardWithExternalThreeDSecure + .paymentMethod.id; + } else if (errors) { + this.logger.warn("Errors returned when updating nonce", errors); + } + } + + // Resolve the parent promise with enriched nonce if available + // else, return the original nonce that the merchant sent + resolve({ + authenticationState, + liabilityShift, + nonce: enrichedNonce || this.fastlaneNonce, + }); + }, + onCancel: cancelThreeDS, + onError: (err) => { + instance.close(); + reject( + new Error( + `Error with obtaining 3DS auth response: ${JSON.stringify(err)}` + ) + ); + }, + }); + + // Render the iframe + instance.render("body").catch(() => { instance.close(); }); - }; - // $FlowFixMe - const instance = await this.threeDSIframe({ - payerActionUrl: this.authenticationURL, - onSuccess: async (res) => { - const { reference_id, authentication_status, liability_shift } = res; - let enrichedNonce, response; - - if (reference_id) { - // $FlowFixMe ZalgoPromise not recognized - response = await this.updateNonceWith3dsData(reference_id); - } - // $FlowIssue - const { data, errors } = response; - if (data) { - enrichedNonce = - data?.updateTokenizedCreditCardWithExternalThreeDSecure - .paymentMethod.id; - } else if (errors) { - return promise.resolve({ - authenticationStatus: authentication_status, - liabilityShift: liability_shift, - nonce: enrichedNonce, - }); - } - }, - onCancel: cancelThreeDS, - onError: (err) => { - return ZalgoPromise.reject( - new Error( - `Error with obtaining 3DS auth response, ${JSON.stringify(err)}` - ) - ); - }, }); - - return instance - .render("body") - .then(() => promise) - .finally(instance.close); } - updateNonceWith3dsData( - threeDSRefID: string - ): ZalgoPromise { + updateNonceWith3dsData(threeDSRefID: string): Promise { + // $FlowFixMe Zalgopromise not recognized return this.graphQLClient.request({ headers: { "Braintree-Version": "2023-09-28", diff --git a/src/three-domain-secure/types.js b/src/three-domain-secure/types.js index 4661c39ff..237131fdf 100644 --- a/src/three-domain-secure/types.js +++ b/src/three-domain-secure/types.js @@ -1,5 +1,6 @@ /* @flow */ /* eslint-disable no-restricted-globals, promise/no-native */ +import { ZalgoPromise } from "@krakenjs/zalgo-promise/src"; import { type ZoidComponent } from "@krakenjs/zoid/src"; export type MerchantPayloadData = {| @@ -70,19 +71,26 @@ export type SdkConfig = {| clientID: string, |}; -export type threeDSResponse = {| +export type ThreeDSResponse = {| liabilityShift: string, - authenticationStatus: string, + authenticationState: string, nonce?: string, |}; +export type HeliosResponse = {| + liability_shift?: string, + reference_id?: string, + success: boolean, +|}; + export type TDSResult = {||}; export type TDSProps = {| xcomponent?: string, payerActionUrl: string, - onSuccess: (data: threeDSResponse) => void, + onSuccess: (data: HeliosResponse) => Promise, onError: (mixed) => void, + onCancel: (mixed) => ZalgoPromise, sdkMeta?: string, content?: void | {| windowMessage?: string, @@ -90,7 +98,7 @@ export type TDSProps = {| cancelMessage?: string, interrogativeMessage?: string, |}, - nonce: string, + nonce?: string, |}; export type UrlProps = {| diff --git a/src/three-domain-secure/utils.jsx b/src/three-domain-secure/utils.jsx index c9fb2d4c0..b6f6d5b93 100644 --- a/src/three-domain-secure/utils.jsx +++ b/src/three-domain-secure/utils.jsx @@ -43,7 +43,8 @@ export function getFastlaneThreeDS(): TDSComponent { return (