@paypal/checkout-components
Advanced tools
Comparing version 5.0.347-alpha-0f42cd5.0 to 5.0.347-alpha-1d87dc3.0
@@ -98,4 +98,3 @@ /* @flow */ | ||
entry: "./src/three-domain-secure/interface", | ||
globals, | ||
}, | ||
}; |
@@ -26,2 +26,3 @@ /* eslint import/no-commonjs: off, flowtype/require-valid-file-annotation: off, flowtype/require-return-type: off */ | ||
__BUTTONS__: "/smart/buttons", | ||
__PIXEL__: "/smart/pixel", | ||
__MENU__: "/smart/menu", | ||
@@ -28,0 +29,0 @@ __QRCODE__: "/smart/qrcode", |
{ | ||
"name": "@paypal/checkout-components", | ||
"version": "5.0.347-alpha-0f42cd5.0", | ||
"version": "5.0.347-alpha-1d87dc3.0", | ||
"description": "PayPal Checkout components, for integrating checkout products.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -20,1 +20,7 @@ /* @flow */ | ||
export const DEFAULT = ("default": "default"); | ||
export const APP_SWITCH_RETURN_HASH = { | ||
ONAPPROVE: ("onApprove": "onApprove"), | ||
ONCANCEL: ("onCancel": "onCancel"), | ||
ONERROR: ("onError": "onError"), | ||
}; |
@@ -11,2 +11,3 @@ /* @flow */ | ||
__BUTTONS__: string, | ||
__PIXEL__: string, | ||
__CHECKOUT__: string, | ||
@@ -13,0 +14,0 @@ __CARD_FIELDS__: string, |
@@ -36,2 +36,3 @@ /* @flow */ | ||
} from "../zoid/modal/component"; | ||
import { getPixelComponent, type PixelComponent } from "../zoid/pixel"; | ||
@@ -42,2 +43,6 @@ export const Buttons: LazyExport<ButtonsComponent> = { | ||
export const ResumePixel: LazyExport<PixelComponent> = { | ||
__get__: () => getPixelComponent(), | ||
}; | ||
export const Checkout: LazyProtectedExport<CheckoutComponent> = { | ||
@@ -98,2 +103,3 @@ __get__: () => protectedExport(getCheckoutComponent()), | ||
getCheckoutComponent(); | ||
getPixelComponent(); | ||
} | ||
@@ -100,0 +106,0 @@ |
@@ -8,2 +8,3 @@ /* @flow */ | ||
export * from "./session"; | ||
export * from "./appSwitchResume"; | ||
export * from "./perceived-latency-instrumentation"; |
@@ -42,4 +42,1 @@ /* @flow */ | ||
}; | ||
// TODO: Remove after testing | ||
// $FlowIssue | ||
export const payPayDomainRegEx = /\.paypal\.(com|cn)(:\d+)?$/; // eslint-disable-line security/detect-unsafe-regex |
@@ -5,19 +5,74 @@ /* @flow */ | ||
import { type LoggerType } from "@krakenjs/beaver-logger/src"; | ||
import { type ZoidComponent } from "@krakenjs/zoid/src"; | ||
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src"; | ||
import { create, type ZoidComponent } from "@krakenjs/zoid/src"; | ||
import { FPTI_KEY } from "@paypal/sdk-constants/src"; | ||
import { ValidationError } from "../lib"; | ||
import { PAYMENT_3DS_VERIFICATION } from "../constants/api"; | ||
import type { | ||
requestData, | ||
responseBody, | ||
MerchantPayloadData, | ||
SdkConfig, | ||
threeDSResponse, | ||
TDSProps, | ||
} from "./types"; | ||
import { getFastlaneThreeDS } from "./utils"; | ||
import type { GraphQLClient, RestClient } from "./api"; | ||
type MerchantPayloadData = {| | ||
amount: string, | ||
currency: string, | ||
nonce: string, | ||
threeDSRequested?: boolean, | ||
transactionContext?: Object, | ||
|}; | ||
// eslint-disable-next-line no-undef | ||
type Request = <TRequestData, TResponse>({| | ||
method?: string, | ||
url: string, | ||
// eslint-disable-next-line no-undef | ||
data: TRequestData, | ||
accessToken: ?string, | ||
// eslint-disable-next-line no-undef | ||
|}) => Promise<TResponse>; | ||
type requestData = {| | ||
intent: "THREE_DS_VERIFICATION", | ||
payment_source: {| | ||
card: {| | ||
single_use_token: string, | ||
verification_method: string, | ||
|}, | ||
|}, | ||
amount: {| | ||
currency_code: string, | ||
value: string, | ||
|}, | ||
transaction_context?: {| | ||
soft_descriptor?: string, | ||
|}, | ||
|}; | ||
type responseBody = {| | ||
payment_id: string, | ||
status: string, | ||
intent: string, | ||
payment_source: {| | ||
card: {| | ||
last_digits: string, | ||
type: string, | ||
name: string, | ||
expiry: string, | ||
|}, | ||
|}, | ||
amount: {| | ||
currency_code: string, | ||
value: string, | ||
|}, | ||
transaction_context: {| | ||
soft_descriptor: string, | ||
|}, | ||
links: $ReadOnlyArray<{| | ||
href: string, | ||
rel: string, | ||
method: string, | ||
|}>, | ||
|}; | ||
type SdkConfig = {| | ||
authenticationToken: ?string, | ||
paypalApiDomain: string, | ||
|}; | ||
const parseSdkConfig = ({ sdkConfig, logger }): SdkConfig => { | ||
@@ -44,2 +99,3 @@ if (!sdkConfig.authenticationToken) { | ||
merchantPayload; | ||
return { | ||
@@ -64,65 +120,38 @@ intent: "THREE_DS_VERIFICATION", | ||
export interface ThreeDomainSecureComponentInterface { | ||
isEligible(payload: MerchantPayloadData): Promise<boolean>; | ||
show(): ZalgoPromise<threeDSResponse>; | ||
isEligible(): Promise<boolean>; | ||
show(): ZoidComponent<void>; | ||
} | ||
type Update3DSTokenResponse = {| | ||
updateTokenizedCreditCardWithExternalThreeDSecure: {| | ||
paymentMethod: {| | ||
id: string, | ||
|}, | ||
|}, | ||
|}; | ||
export class ThreeDomainSecureComponent { | ||
fastlaneNonce: string; | ||
logger: LoggerType; | ||
restClient: RestClient; | ||
graphQLClient: GraphQLClient; | ||
request: Request; | ||
sdkConfig: SdkConfig; | ||
authenticationURL: string; | ||
threeDSIframe: ZoidComponent<TDSProps>; | ||
constructor({ | ||
logger, | ||
restClient, | ||
graphQLClient, | ||
request, | ||
sdkConfig, | ||
}: {| | ||
logger: LoggerType, | ||
restClient: RestClient, | ||
graphQLClient: GraphQLClient, | ||
request: Request, | ||
sdkConfig: SdkConfig, | ||
|}) { | ||
this.logger = logger; | ||
this.restClient = restClient; | ||
this.graphQLClient = graphQLClient; | ||
this.request = request; | ||
this.sdkConfig = parseSdkConfig({ sdkConfig, logger }); | ||
if (this.sdkConfig.authenticationToken) { | ||
this.restClient.setAccessToken(this.sdkConfig.authenticationToken); | ||
} | ||
} | ||
async isEligible(merchantPayload: MerchantPayloadData): Promise<boolean> { | ||
// eslint-disable-next-line no-console | ||
console.log("Entered IsEligible"); | ||
const data = parseMerchantPayload({ merchantPayload }); | ||
const idToken = merchantPayload.idToken; | ||
this.fastlaneNonce = merchantPayload.nonce; | ||
try { | ||
// $FlowFixMe | ||
const { status, links } = await this.restClient.request< | ||
requestData, | ||
responseBody | ||
>({ | ||
const { status, links } = await this.request<requestData, responseBody>({ | ||
method: "POST", | ||
baseURL: `${this.sdkConfig.paypalApiDomain}/v2/payments/payment`, | ||
url: `${this.sdkConfig.paypalApiDomain}/${PAYMENT_3DS_VERIFICATION}`, | ||
data, | ||
accessToken: idToken, // this.sdkConfig.authenticationToken, | ||
accessToken: this.sdkConfig.authenticationToken, | ||
}); | ||
let responseStatus = false; | ||
if (status === "PAYER_ACTION_REQUIRED") { | ||
@@ -133,3 +162,2 @@ this.authenticationURL = links.find( | ||
responseStatus = true; | ||
this.threeDSIframe = getFastlaneThreeDS(this.authenticationURL); | ||
} | ||
@@ -143,80 +171,5 @@ return responseStatus; | ||
show(): ZalgoPromise<threeDSResponse> { | ||
if (!this.threeDSIframe) { | ||
return ZalgoPromise.reject( | ||
new ValidationError(`Ineligible for three domain secure`) | ||
); | ||
} | ||
const promise = new ZalgoPromise(); | ||
const cancelThreeDS = () => { | ||
return ZalgoPromise.try(() => { | ||
// eslint-disable-next-line no-console | ||
console.log("cancelled"); | ||
}).then(() => { | ||
// eslint-disable-next-line no-use-before-define | ||
instance.close(); | ||
}); | ||
}; | ||
// $FlowFixMe | ||
const instance = this.threeDSIframe({ | ||
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 ZalgoPromise.reject( | ||
new Error( | ||
`Error with obtaining 3DS contingency, ${JSON.stringify(err)}` | ||
) | ||
); | ||
}, | ||
}); | ||
// const TARGET_ELEMENT = { | ||
// BODY: "body", | ||
// }; | ||
return instance | ||
.render("body") | ||
.then(() => promise) | ||
.finally(instance.close); | ||
show() { | ||
create({ tag: "", url: "" }); | ||
} | ||
updateNonceWith3dsData( | ||
threeDSRefID: string | ||
): ZalgoPromise<Update3DSTokenResponse> { | ||
return this.graphQLClient.request({ | ||
headers: { | ||
"Braintree-Version": "2023-09-28", | ||
}, | ||
data: { | ||
query: ` | ||
mutation Update3DSToken($input: UpdateTokenizedCreditCardWithExternalThreeDSecureInput!) { | ||
updateTokenizedCreditCardWithExternalThreeDSecure(input: $input) { | ||
paymentMethod { | ||
id | ||
} | ||
} | ||
} | ||
`, | ||
variables: { | ||
input: { | ||
paymentMethodId: this.fastlaneNonce, | ||
externalThreeDSecureMetadata: { | ||
externalAuthenticationId: threeDSRefID, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}); | ||
} | ||
} |
@@ -11,8 +11,3 @@ /* @flow */ | ||
}; | ||
vi.mock("./utils", async () => { | ||
return { | ||
...(await vi.importActual("./utils")), | ||
getThreeDomainSecureComponent: vi.fn(), | ||
}; | ||
}); | ||
const defaultEligibilityResponse = { | ||
@@ -35,4 +30,3 @@ status: "PAYER_ACTION_REQUIRED", | ||
sdkConfig = defaultSdkConfig, | ||
restClient = mockEligibilityRequest(), | ||
graphQLClient = vi.fn(), | ||
request = mockEligibilityRequest(), | ||
logger = { | ||
@@ -50,6 +44,4 @@ info: vi.fn().mockReturnThis(), | ||
// $FlowIssue | ||
restClient, | ||
request, | ||
// $FlowIssue | ||
graphQLClient, | ||
// $FlowIssue | ||
logger, | ||
@@ -62,3 +54,3 @@ }); | ||
describe.skip("three domain secure component - isEligible method", () => { | ||
describe("three domain secure component - isEligible method", () => { | ||
test("should return true if payer action required", async () => { | ||
@@ -74,3 +66,3 @@ const threeDomainSecureClient = createThreeDomainSecureComponent(); | ||
const threeDomainSecureClient = createThreeDomainSecureComponent({ | ||
restClient: () => | ||
request: () => | ||
Promise.resolve({ ...defaultEligibilityResponse, status: "SUCCESS" }), | ||
@@ -86,3 +78,3 @@ }); | ||
const threeDomainSecureClient = createThreeDomainSecureComponent({ | ||
restClient: () => | ||
request: () => | ||
Promise.resolve({ | ||
@@ -105,3 +97,3 @@ ...defaultEligibilityResponse, | ||
const threeDomainSecureClient = createThreeDomainSecureComponent({ | ||
restClient: mockedRequest, | ||
request: mockedRequest, | ||
}); | ||
@@ -130,6 +122,6 @@ | ||
test.skip("catch errors from the API", async () => { | ||
test("catch errors from the API", async () => { | ||
const mockRequest = vi.fn().mockRejectedValue(new Error("Error with API")); | ||
const threeDomainSecureClient = createThreeDomainSecureComponent({ | ||
restClient: mockRequest, | ||
request: mockRequest, | ||
}); | ||
@@ -136,0 +128,0 @@ |
/* @flow */ | ||
import { | ||
getEnv, | ||
getLogger, | ||
getPayPalAPIDomain, | ||
getSDKToken, | ||
getUserIDToken, | ||
} from "@paypal/sdk-client/src"; | ||
import { callRestAPI, devEnvOnlyExport } from "../lib"; | ||
import type { LazyExport } from "../types"; | ||
@@ -15,7 +15,3 @@ | ||
} 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> = | ||
@@ -26,19 +22,14 @@ { | ||
logger: getLogger(), | ||
restClient: new RestClient(), | ||
graphQLClient: new GraphQLClient({ | ||
baseURL: | ||
getEnv() === "production" ? BRAINTREE_PROD : BRAINTREE_SANDBOX, | ||
accessToken: getSDKToken(), | ||
}), | ||
// $FlowIssue ZalgoPromise vs Promise | ||
request: callRestAPI, | ||
sdkConfig: { | ||
authenticationToken: getSDKToken(), | ||
authenticationToken: getUserIDToken(), | ||
paypalApiDomain: getPayPalAPIDomain(), | ||
}, | ||
}); | ||
return { | ||
return devEnvOnlyExport({ | ||
isEligible: (payload) => threeDomainSecureInstance.isEligible(payload), | ||
show: () => threeDomainSecureInstance.show(), | ||
}; | ||
}); | ||
}, | ||
}; |
@@ -499,3 +499,17 @@ /* eslint-disable eslint-comments/disable-enable-pair */ | ||
export type ButtonExtensions = {| | ||
hasReturned: () => boolean, | ||
resume: () => void, | ||
|}; | ||
export type ButtonProps = {| | ||
// app switch properties | ||
appSwitchWhenAvailable: string, | ||
listenForHashChanges: () => void, | ||
removeListenerForHashChanges: () => void, | ||
// Not passed to child iframe | ||
// change any to HashChangeEvent when we move to typescript | ||
// eslint-disable-next-line flowtype/no-weak-types | ||
hashChangeHandler: (event: any) => void, | ||
fundingSource?: ?$Values<typeof FUNDING>, | ||
@@ -502,0 +516,0 @@ intent: $Values<typeof INTENT>, |
@@ -53,2 +53,3 @@ /* @flow */ | ||
import { create, EVENT, type ZoidComponent } from "@krakenjs/zoid/src"; | ||
import { send as postRobotSend } from "@krakenjs/post-robot/src"; | ||
import { | ||
@@ -77,2 +78,4 @@ uniqueID, | ||
prepareInstrumentationPayload, | ||
isAppSwitchResumeFlow, | ||
getAppSwitchResumeParams, | ||
} from "../../lib"; | ||
@@ -83,4 +86,6 @@ import { | ||
type ButtonProps, | ||
type ButtonExtensions, | ||
} from "../../ui/buttons/props"; | ||
import { isFundingEligible } from "../../funding"; | ||
import { getPixelComponent } from "../pixel"; | ||
import { CLASS } from "../../constants"; | ||
@@ -101,6 +106,12 @@ | ||
export type ButtonsComponent = ZoidComponent<ButtonProps>; | ||
export type ButtonsComponent = ZoidComponent< | ||
ButtonProps, | ||
void, | ||
void, | ||
ButtonExtensions | ||
>; | ||
export const getButtonsComponent: () => ButtonsComponent = memoize(() => { | ||
const queriedEligibleFunding = []; | ||
return create({ | ||
@@ -111,2 +122,37 @@ tag: "paypal-buttons", | ||
domain: getPayPalDomainRegex(), | ||
getExtensions: (parent) => { | ||
return { | ||
hasReturned: () => { | ||
return isAppSwitchResumeFlow(); | ||
}, | ||
resume: () => { | ||
const resumeFlowParams = getAppSwitchResumeParams(); | ||
if (!resumeFlowParams) { | ||
throw new Error("Resume Flow is not supported."); | ||
} | ||
getLogger().metricCounter({ | ||
namespace: "resume_flow.init.count", | ||
event: "info", | ||
dimensions: { | ||
orderID: Boolean(resumeFlowParams.orderID), | ||
vaultSessionID: Boolean(resumeFlowParams.vaultSetupToken), | ||
billingToken: Boolean(resumeFlowParams.billingToken), | ||
payerID: Boolean(resumeFlowParams.payerID), | ||
}, | ||
}); | ||
const resumeComponent = getPixelComponent(); | ||
const parentProps = parent.getProps(); | ||
resumeComponent({ | ||
onApprove: parentProps.onApprove, | ||
// $FlowIgnore[incompatible-call] | ||
onError: parentProps.onError, | ||
// $FlowIgnore[prop-missing] onCancel is incorrectly declared as oncancel in button props | ||
onCancel: parentProps.onCancel, | ||
onClick: parentProps.onClick, | ||
onComplete: parentProps.onComplete, | ||
resumeFlowParams, | ||
}).render("body"); | ||
}, | ||
}; | ||
}, | ||
@@ -254,2 +300,59 @@ autoResize: { | ||
props: { | ||
// App Switch Properties | ||
appSwitchWhenAvailable: { | ||
// this value is a string for now while we test the app switch | ||
// feature. Before we give this to a real merchant, we should | ||
// change this to a boolean - Shane 11 Dec 2024 | ||
type: "string", | ||
required: false, | ||
}, | ||
hashChangeHandler: { | ||
type: "function", | ||
sendToChild: false, | ||
queryParam: false, | ||
required: false, | ||
value: () => (event) => { | ||
const iframes = document.querySelectorAll("iframe"); | ||
// I don't understand why but trying to make iframes which is a NodeList | ||
// into an Iterable (so we could do a for..of loop or .forEach) is not | ||
// working. It ends up iterating over itself so instead of looping over the contents | ||
// of the NodeList you loop over the NodeList itself which is extremely unexpected | ||
// for..in works though :shrug: - Shane 11 Dec 2024 | ||
for (let i = 0; i < iframes.length; i++) { | ||
if (iframes[i].name.includes("zoid__paypal_buttons")) { | ||
postRobotSend( | ||
iframes[i].contentWindow, | ||
"paypal-hashchange", | ||
{ | ||
url: event.newURL, | ||
}, | ||
{ domain: getPayPalDomain() } | ||
); | ||
} | ||
} | ||
}, | ||
}, | ||
listenForHashChanges: { | ||
type: "function", | ||
queryParam: false, | ||
value: | ||
({ props }) => | ||
() => { | ||
window.addEventListener("hashchange", props.hashChangeHandler); | ||
}, | ||
}, | ||
removeListenerForHashChanges: { | ||
type: "function", | ||
queryParam: false, | ||
value: | ||
({ props }) => | ||
() => { | ||
window.removeEventListener("hashchange", props.hashChangeHandler); | ||
}, | ||
}, | ||
// allowBillingPayments prop is used by Honey Extension to render the one-click button | ||
@@ -256,0 +359,0 @@ // with payment methods & to use the payment methods instead of the Billing Agreement |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
1247085
204
17920