Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
imbrian authored and siddy2181 committed Nov 18, 2024
1 parent 5cf3f99 commit 58581a5
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 28 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
__HOST__: true,
__PATH__: true,
__COMPONENTS__: true,
$Shape: true,
},

rules: {
Expand Down
109 changes: 109 additions & 0 deletions src/three-domain-secure/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* @flow */
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
import { request } from "@krakenjs/belter/src";
import { getSessionID, getPartnerAttributionID } from "@paypal/sdk-client/src";

import { callRestAPI } from "../lib";
import { HEADERS } from "../constants/api";

type HTTPRequestOptions = {|
// eslint-disable-next-line flowtype/no-weak-types
data: any,
baseURL?: string,
accessToken?: string,
method?: string, // TODO do we have an available type for this in Flow?
|};

interface HTTPClientType {
accessToken: ?string;
baseURL: ?string;
}

type HTTPClientOptions = {|
accessToken: ?string,
baseURL: ?string,
|};

class HTTPClient implements HTTPClientType {
accessToken: ?string;
baseURL: ?string;

constructor(options?: $Shape<HTTPClientOptions> = {}) {
this.accessToken = options.accessToken;
this.baseURL = options.baseURL;
}

setAccessToken(token: string) {
this.accessToken = token;
}
}

export class RestClient extends HTTPClient {
request({ baseURL, ...rest }: HTTPRequestOptions): ZalgoPromise<{ ... }> {
return callRestAPI({
url: baseURL ?? this.baseURL ?? "",
accessToken: this.accessToken,
...rest,
});
}
}

const GRAPHQL_URI = "/graphql";

type GQLQuery = {|
query: string,
variables: { ... },
|};

export function callGraphQLAPI({
accessToken,
baseURL,
data: query,
}: {|
accessToken: ?string,
baseURL: string,
data: GQLQuery,
// eslint-disable-next-line flowtype/no-weak-types
|}): ZalgoPromise<any> {
if (!accessToken) {
throw new Error(
`No access token passed to GraphQL request ${baseURL}${GRAPHQL_URI}`
);
}

const requestHeaders = {
[HEADERS.AUTHORIZATION]: `Bearer ${accessToken}`,
[HEADERS.CONTENT_TYPE]: "application/json",
[HEADERS.PARTNER_ATTRIBUTION_ID]: getPartnerAttributionID() ?? "",
[HEADERS.CLIENT_METADATA_ID]: getSessionID(),
};

return request({
method: "post",
url: `${baseURL}${GRAPHQL_URI}`,
headers: requestHeaders,
json: query,
}).then(({ status, body }) => {
// TODO handle body.errors
if (status !== 200) {
throw new Error(`${baseURL}${GRAPHQL_URI} returned status ${status}`);
}

return body.data;
});
}

export class GraphQLClient extends HTTPClient {
request({
baseURL,
data,
accessToken,
}: // eslint-disable-next-line flowtype/no-weak-types
$Shape<HTTPRequestOptions> & {| data: GQLQuery |} & any): ZalgoPromise<any> {
return callGraphQLAPI({
accessToken: accessToken ?? this.accessToken,
data,
baseURL: baseURL ?? this.baseURL ?? "",
});
}
}
108 changes: 82 additions & 26 deletions src/three-domain-secure/component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-restricted-globals, promise/no-native */
import { type LoggerType } from "@krakenjs/beaver-logger/src";
import { type ZoidComponent } from "@krakenjs/zoid/src";
import { type ZoidComponentInstance } from "@krakenjs/zoid/src";
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
import { FPTI_KEY } from "@paypal/sdk-constants/src";

Expand All @@ -11,12 +11,13 @@ import { ValidationError } from "../lib";
import type {
requestData,
responseBody,
Request,
MerchantPayloadData,
SdkConfig,
threeDSResponse,
TDSProps,
} from "./types";
import { getThreeDomainSecureComponent, type TDSProps } from "./utils";
import { getFastlaneThreeDS } from "./utils";
import type { GraphQLClient, RestClient } from "./api";

const parseSdkConfig = ({ sdkConfig, logger }): SdkConfig => {
if (!sdkConfig.authenticationToken) {
Expand Down Expand Up @@ -61,24 +62,43 @@ export interface ThreeDomainSecureComponentInterface {
isEligible(payload: MerchantPayloadData): Promise<boolean>;
show(): ZalgoPromise<threeDSResponse>;
}

type Update3DSTokenResponse = {|
updateTokenizedCreditCardWithExternalThreeDSecure: {|
paymentMethod: {|
id: string,
|},
|},
|};

export class ThreeDomainSecureComponent {
fastlaneNonce: string;
logger: LoggerType;
request: Request;
restClient: RestClient;
graphQLClient: GraphQLClient;
sdkConfig: SdkConfig;
authenticationURL: string;
threeDSIframe: ZoidComponent<TDSProps>;
threeDSIframe: (params: { ... }) => ZoidComponentInstance<TDSProps>;

constructor({
logger,
request,
restClient,
graphQLClient,
sdkConfig,
}: {|
logger: LoggerType,
request: Request,
restClient: RestClient,
graphQLClient: GraphQLClient,
sdkConfig: SdkConfig,
|}) {
this.logger = logger;
this.request = request;
this.restClient = restClient;
this.graphQLClient = graphQLClient;
this.sdkConfig = parseSdkConfig({ sdkConfig, logger });

if (this.sdkConfig.authenticationToken) {
this.restClient.setAccessToken(this.sdkConfig.authenticationToken);
}
}

async isEligible(merchantPayload: MerchantPayloadData): Promise<boolean> {
Expand All @@ -87,9 +107,14 @@ export class ThreeDomainSecureComponent {

const data = parseMerchantPayload({ merchantPayload });
const idToken = merchantPayload.idToken;
this.fastlaneNonce = merchantPayload.nonce;

try {
// $FlowFixMe
const { status, links } = await this.request<requestData, responseBody>({
const { status, links } = await this.restClient.request<
requestData,
responseBody
>({
method: "POST",
url: `${this.sdkConfig.paypalApiDomain}/v2/payments/payment`,
data,
Expand All @@ -102,9 +127,7 @@ export class ThreeDomainSecureComponent {
(link) => link.rel === "payer-action"
).href;
responseStatus = true;
this.threeDSIframe = getThreeDomainSecureComponent(
this.authenticationURL
);
this.threeDSIframe = getFastlaneThreeDS(this.authenticationURL);
}
return responseStatus;
} catch (error) {
Expand All @@ -115,7 +138,9 @@ export class ThreeDomainSecureComponent {

show(): ZalgoPromise<threeDSResponse> {
if (!this.threeDSIframe) {
throw new ValidationError(`Ineligible for three domain secure`);
return ZalgoPromise.reject(
new ValidationError(`Ineligible for three domain secure`)
);
}
const promise = new ZalgoPromise();
const cancelThreeDS = () => {
Expand All @@ -129,30 +154,61 @@ export class ThreeDomainSecureComponent {
};
// $FlowFixMe
const instance = this.threeDSIframe({
onSuccess: (data) => {
// const {threeDSRefID, authentication_status, liability_shift } = data;
// let enrichedNonce;
// if(threeDSRefID) {
// enrichedNonce = await updateNonceWith3dsData(threeDSRefID, this.fastlaneNonce)
// }

return promise.resolve(data);
onSuccess: async (data) => {
// const { threeDSRefID, authentication_status, liability_shift } = data;
const { threeDSRefID } = data;
// eslint-disable-next-line no-console
console.log("threeDSRefID", threeDSRefID);
let enrichedNonce;

if (threeDSRefID) {
enrichedNonce = await this.updateNonceWith3dsData(threeDSRefID);
}
// eslint-disable-next-line no-console
console.log("Received enriched nonce", enrichedNonce);
return promise.resolve({ ...data, nonce: enrichedNonce });
},
onClose: cancelThreeDS,
onError: (err) => {
return promise.reject(
return ZalgoPromise.reject(
new Error(
`Error with obtaining 3DS contingency, ${JSON.stringify(err)}`
)
);
},
});
const TARGET_ELEMENT = {
BODY: "body",
};
// const TARGET_ELEMENT = {
// BODY: "body",
// };
return instance
.renderTo(window.parent, TARGET_ELEMENT.BODY)
.render("body")
.then(() => promise)
.finally(instance.close);
}

updateNonceWith3dsData(
threeDSRefID: string
): ZalgoPromise<Update3DSTokenResponse> {
return this.graphQLClient.request({
data: {
query: `
mutation Update3DSToken($input: UpdateTokenizedCreditCardWithExternalThreeDSecureInput!) {
updateTokenizedCreditCardWithExternalThreeDSecure(input: $input) {
paymentMethod {
id
}
}
}
`,
variables: {
input: {
paymentMethodId: this.fastlaneNonce,
externalThreeDSecureMetadata: {
externalAuthenticationId: threeDSRefID,
},
},
},
},
});
}
}
11 changes: 9 additions & 2 deletions src/three-domain-secure/interface.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
/* @flow */
import {
getEnv,
getLogger,
getPayPalAPIDomain,
getSDKToken,
} from "@paypal/sdk-client/src";

import { callRestAPI } from "../lib";
import type { LazyExport } from "../types";

import {
ThreeDomainSecureComponent,
type ThreeDomainSecureComponentInterface,
} from "./component";
import { GraphQLClient, RestClient } from "./api";

const BRAINTREE_PROD = "https://payments.braintree-api.com";
const BRAINTREE_SANDBOX = "https://payments.sandbox.braintree-api.com";

export const ThreeDomainSecureClient: LazyExport<ThreeDomainSecureComponentInterface> =
{
__get__: () => {
const threeDomainSecureInstance = new ThreeDomainSecureComponent({
logger: getLogger(),
restClient: new RestClient(),
graphQLClient: new GraphQLClient(),
// $FlowIssue ZalgoPromise vs Promise
request: callRestAPI,
sdkConfig: {
authenticationToken: getSDKToken(),
paypalApiDomain: getPayPalAPIDomain(),
braintreeApiDomain:
getEnv() === "production" ? BRAINTREE_PROD : BRAINTREE_SANDBOX,
},
});
return {
Expand Down
22 changes: 22 additions & 0 deletions src/three-domain-secure/types.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* @flow */
/* eslint-disable no-restricted-globals, promise/no-native */
import { type ZoidComponent } from "@krakenjs/zoid/src";

export type MerchantPayloadData = {|
amount: string,
currency: string,
Expand Down Expand Up @@ -64,6 +66,7 @@ export type responseBody = {|

export type SdkConfig = {|
authenticationToken: ?string,
braintreeApiDomain: string,
paypalApiDomain: string,
|};

Expand All @@ -75,4 +78,23 @@ export type threeDSResponse = {|

export type TDSResult = {||};

export type TDSProps = {|
action: string,
xcomponent: string,
flow: string,
orderID: string,
onSuccess: (threeDSResponse) => void,
onError: (mixed) => void,
sdkMeta: string,
content?: void | {|
windowMessage?: string,
continueMessage?: string,
cancelMessage?: string,
interrogativeMessage?: string,
|},
nonce: string,
|};

export type TDSComponent = ZoidComponent<TDSProps>;

/* eslint-enable no-restricted-globals, promise/no-native */

0 comments on commit 58581a5

Please sign in to comment.