@paypal/checkout-components 5.0.388 → 5.0.390

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paypal/checkout-components",
3
- "version": "5.0.388",
3
+ "version": "5.0.390",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -14,7 +14,6 @@ import {
14
14
  renderDefaultButton,
15
15
  renderForm,
16
16
  renderStandaloneButton,
17
- getTrackingId,
18
17
  } from "./utils";
19
18
  import type {
20
19
  HostedButtonsComponent,
@@ -50,34 +49,37 @@ export const getHostedButtonsComponent = (): HostedButtonsComponent => {
50
49
  selector,
51
50
  });
52
51
 
53
- const trackingId = getTrackingId(selector);
52
+ const fptiTrackingParams =
53
+ window[
54
+ `__pp_form_fields_${hostedButtonId}`
55
+ ]?.getFptiTrackingParams?.() || {};
54
56
 
55
57
  const createOrder = buildHostedButtonCreateOrder({
56
58
  enableDPoP,
57
59
  hostedButtonId,
58
60
  merchantId,
59
- trackingId,
61
+ fptiTrackingParams,
60
62
  });
61
63
 
62
64
  const onApprove = buildHostedButtonOnApprove({
63
65
  enableDPoP,
64
66
  hostedButtonId,
65
67
  merchantId,
66
- trackingId,
68
+ fptiTrackingParams,
67
69
  });
68
70
 
69
71
  const onShippingAddressChange = buildHostedButtonOnShippingAddressChange({
70
72
  enableDPoP,
71
73
  hostedButtonId,
72
74
  shouldIncludeShippingCallbacks,
73
- trackingId,
75
+ fptiTrackingParams,
74
76
  });
75
77
 
76
78
  const onShippingOptionsChange = buildHostedButtonOnShippingOptionsChange({
77
79
  enableDPoP,
78
80
  hostedButtonId,
79
81
  shouldIncludeShippingCallbacks,
80
- trackingId,
82
+ fptiTrackingParams,
81
83
  });
82
84
 
83
85
  const buttonOptions: HostedButtonOptions = {
@@ -133,7 +133,7 @@ export type GetCallbackProps = {|
133
133
  hostedButtonId: string,
134
134
  merchantId?: string,
135
135
  shouldIncludeShippingCallbacks?: boolean,
136
- trackingId?: string,
136
+ fptiTrackingParams?: { [key: string]: mixed },
137
137
  |};
138
138
 
139
139
  export type HostedButtonsInstance = {|
@@ -225,18 +225,6 @@ export function getElementFromSelector(
225
225
  : selector;
226
226
  }
227
227
 
228
- export function getTrackingId(
229
- HostedButtonSelector: string | HTMLElement
230
- ): string {
231
- if (typeof HostedButtonSelector !== "string") {
232
- return "";
233
- }
234
- const ele = document.querySelector(
235
- `${HostedButtonSelector} input[name="uuid"]`
236
- );
237
- return ele ? ele.getAttribute("value") || "" : "";
238
- }
239
-
240
228
  /**
241
229
  * Attaches form fields (html) to the given selector, and
242
230
  * initializes window.__pp_form_fields (htmlScript).
@@ -269,7 +257,7 @@ export const buildHostedButtonCreateOrder = ({
269
257
  enableDPoP,
270
258
  hostedButtonId,
271
259
  merchantId,
272
- trackingId,
260
+ fptiTrackingParams,
273
261
  }: GetCallbackProps): CreateOrder => {
274
262
  return async (data) => {
275
263
  const userInputs =
@@ -280,7 +268,7 @@ export const buildHostedButtonCreateOrder = ({
280
268
  const url = `${apiUrl}/v1/checkout/links/${hostedButtonId}/create-context`;
281
269
  const method = "POST";
282
270
  const headers = await buildRequestHeaders({ url, method, enableDPoP });
283
-
271
+ const funding_source = data.paymentSource.toUpperCase();
284
272
  const response = await request({
285
273
  url,
286
274
  // $FlowIssue optional properties are not compatible with [key: string]: string
@@ -288,7 +276,7 @@ export const buildHostedButtonCreateOrder = ({
288
276
  method,
289
277
  body: JSON.stringify({
290
278
  entry_point: entryPoint,
291
- funding_source: data.paymentSource.toUpperCase(),
279
+ funding_source,
292
280
  merchant_id: merchantId,
293
281
  ...userInputs,
294
282
  }),
@@ -297,9 +285,10 @@ export const buildHostedButtonCreateOrder = ({
297
285
  const { body } = response;
298
286
  getLogger()
299
287
  .track({
288
+ ...fptiTrackingParams,
300
289
  [FPTI_KEY.CONTEXT_ID]: body.context_id,
301
290
  [FPTI_KEY.EVENT_NAME]: "ncps_create_order",
302
- tracking_id: trackingId,
291
+ funding_type: funding_source,
303
292
  })
304
293
  .flush();
305
294
  return body.context_id || onError(body.details?.[0]?.issue || body.name);
@@ -313,7 +302,7 @@ export const buildHostedButtonOnApprove = ({
313
302
  enableDPoP,
314
303
  hostedButtonId,
315
304
  merchantId,
316
- trackingId,
305
+ fptiTrackingParams,
317
306
  }: GetCallbackProps): OnApprove => {
318
307
  return async (data) => {
319
308
  const url = `${apiUrl}/v1/checkout/links/${hostedButtonId}/pay`;
@@ -333,9 +322,9 @@ export const buildHostedButtonOnApprove = ({
333
322
  }).then((response) => {
334
323
  getLogger()
335
324
  .track({
325
+ ...fptiTrackingParams,
336
326
  [FPTI_KEY.CONTEXT_ID]: data.orderID,
337
327
  [FPTI_KEY.EVENT_NAME]: "ncps_onapprove_order",
338
- tracking_id: trackingId,
339
328
  })
340
329
  .flush();
341
330
 
@@ -359,7 +348,7 @@ export const buildHostedButtonOnShippingAddressChange = ({
359
348
  enableDPoP,
360
349
  hostedButtonId,
361
350
  shouldIncludeShippingCallbacks,
362
- trackingId,
351
+ fptiTrackingParams,
363
352
  }: GetCallbackProps): OnShippingAddressChange | typeof undefined => {
364
353
  if (shouldIncludeShippingCallbacks) {
365
354
  return async (data, actions) => {
@@ -398,9 +387,9 @@ export const buildHostedButtonOnShippingAddressChange = ({
398
387
 
399
388
  getLogger()
400
389
  .track({
390
+ ...fptiTrackingParams,
401
391
  [FPTI_KEY.CONTEXT_ID]: orderID,
402
392
  [FPTI_KEY.EVENT_NAME]: "ncps_shipping_address_change",
403
- tracking_id: trackingId,
404
393
  })
405
394
  .flush();
406
395
  };
@@ -411,7 +400,7 @@ export const buildHostedButtonOnShippingOptionsChange = ({
411
400
  enableDPoP,
412
401
  hostedButtonId,
413
402
  shouldIncludeShippingCallbacks,
414
- trackingId,
403
+ fptiTrackingParams,
415
404
  }: GetCallbackProps): OnShippingOptionsChange | typeof undefined => {
416
405
  if (shouldIncludeShippingCallbacks) {
417
406
  return async (data, actions) => {
@@ -439,9 +428,9 @@ export const buildHostedButtonOnShippingOptionsChange = ({
439
428
 
440
429
  getLogger()
441
430
  .track({
431
+ ...fptiTrackingParams,
442
432
  [FPTI_KEY.CONTEXT_ID]: orderID,
443
433
  [FPTI_KEY.EVENT_NAME]: "ncps_shipping_options_change",
444
- tracking_id: trackingId,
445
434
  })
446
435
  .flush();
447
436
  };
@@ -21,7 +21,6 @@ import {
21
21
  renderStandaloneButton,
22
22
  applyContainerStyles,
23
23
  renderDefaultButton,
24
- getTrackingId,
25
24
  } from "./utils";
26
25
 
27
26
  vi.mock("@krakenjs/belter/src", async () => {
@@ -914,35 +913,6 @@ test("getElementFromSelector", () => {
914
913
  expect(mockQuerySelector).toHaveBeenCalledWith(containerId);
915
914
  });
916
915
 
917
- describe("getTrackingId", () => {
918
- const containerId = "#container-id";
919
-
920
- test("returns uuid value when input element exists and has a value", () => {
921
- const inputElement = document.createElement("input");
922
- inputElement.setAttribute("name", "uuid");
923
- inputElement.setAttribute("value", "test-uuid-123");
924
-
925
- const containerElement = document.createElement("div");
926
- containerElement.appendChild(inputElement);
927
-
928
- vi.spyOn(document, "querySelector").mockImplementationOnce(
929
- () => inputElement
930
- );
931
-
932
- const result = getTrackingId(containerId);
933
-
934
- expect(result).toBe("test-uuid-123");
935
- });
936
-
937
- test("returns empty string when input element doesn't exist", () => {
938
- vi.spyOn(document, "querySelector").mockImplementationOnce(() => null);
939
-
940
- const result = getTrackingId(containerId);
941
-
942
- expect(result).toBe("");
943
- });
944
- });
945
-
946
916
  describe("getButtonPreferences", () => {
947
917
  test("returns all button preferences if all are eligible", () => {
948
918
  const params = {
@@ -34,21 +34,36 @@ const parseSdkConfig = ({ sdkConfig, logger }): SdkConfig => {
34
34
  return sdkConfig;
35
35
  };
36
36
 
37
- const parseMerchantPayload = ({
37
+ export const parseMerchantPayload = ({
38
38
  merchantPayload,
39
39
  }: {|
40
40
  merchantPayload: MerchantPayloadData,
41
41
  |}): requestData => {
42
- const { threeDSRequested, amount, currency, nonce, transactionContext } =
43
- merchantPayload;
42
+ const {
43
+ threeDSRequested,
44
+ verificationMethod,
45
+ amount,
46
+ currency,
47
+ nonce,
48
+ transactionContext,
49
+ } = merchantPayload;
50
+
51
+ let cardVerificationMethod = "SCA_WHEN_REQUIRED";
52
+
53
+ if (verificationMethod) {
54
+ cardVerificationMethod = verificationMethod;
55
+ } else if (threeDSRequested !== undefined) {
56
+ cardVerificationMethod = threeDSRequested
57
+ ? "SCA_ALWAYS"
58
+ : "SCA_WHEN_REQUIRED";
59
+ }
60
+
44
61
  return {
45
62
  intent: "THREE_DS_VERIFICATION",
46
63
  payment_source: {
47
64
  card: {
48
65
  single_use_token: nonce,
49
- verification_method: threeDSRequested
50
- ? "SCA_ALWAYS"
51
- : "SCA_WHEN_REQUIRED",
66
+ verification_method: cardVerificationMethod,
52
67
  },
53
68
  },
54
69
  amount: {
@@ -128,7 +143,7 @@ export class ThreeDomainSecureComponent {
128
143
  // eslint-disable-next-line compat/compat
129
144
  return new Promise((resolve, reject) => {
130
145
  let authenticationState,
131
- liabilityShift = "false";
146
+ liabilityShift;
132
147
  const cancelThreeDS = () => {
133
148
  return ZalgoPromise.try(() => {
134
149
  this.logger.warn("3DS Cancelled");
@@ -137,7 +152,6 @@ export class ThreeDomainSecureComponent {
137
152
  instance.close();
138
153
  resolve({
139
154
  authenticationState: "cancelled",
140
- liabilityShift: "false",
141
155
  nonce: this.fastlaneNonce,
142
156
  });
143
157
  });
@@ -147,12 +161,14 @@ export class ThreeDomainSecureComponent {
147
161
  payerActionUrl: this.authenticationURL,
148
162
  onSuccess: async (res) => {
149
163
  const { reference_id, liability_shift, success } = res;
164
+ // $FlowFixMe incompatible type payload
165
+ this.logger.info("helios_response", JSON.stringify(res));
150
166
  let enrichedNonce;
151
167
  // Helios returns a boolen parameter: "success"
152
168
  // It will be true for all cases where liability is shifted to merchant
153
- // and false for downstream failures and errors
169
+ // and false for downstream failures and errors where liability_shift= NO|UNKNOWN.
154
170
  authenticationState = success ? "succeeded" : "errored";
155
- liabilityShift = liability_shift ? liability_shift : "false";
171
+ liabilityShift = liability_shift;
156
172
 
157
173
  // call BT mutation to update fastlaneNonce with 3ds data
158
174
  // reference_id will be available for all usecases(success/failure)
@@ -6,7 +6,7 @@ import { FPTI_KEY } from "@paypal/sdk-constants/src";
6
6
 
7
7
  import { ValidationError } from "../lib";
8
8
 
9
- import { ThreeDomainSecureComponent } from "./component";
9
+ import { ThreeDomainSecureComponent, parseMerchantPayload } from "./component";
10
10
 
11
11
  const defaultSdkConfig = {
12
12
  authenticationToken: "sdk-client-token",
@@ -237,3 +237,119 @@ describe("three domain secure component - initialization", () => {
237
237
  });
238
238
  });
239
239
  });
240
+
241
+ describe(" three domain secure component - parseMerchantPayload", () => {
242
+ const baseMerchantPayload = {
243
+ amount: "100.00",
244
+ currency: "USD",
245
+ nonce: "test-nonce-123",
246
+ };
247
+
248
+ it("should default to SCA_WHEN_REQUIRED when no 3DS parameters provided", () => {
249
+ const result = parseMerchantPayload({
250
+ merchantPayload: baseMerchantPayload,
251
+ });
252
+
253
+ expect(result).toEqual({
254
+ intent: "THREE_DS_VERIFICATION",
255
+ payment_source: {
256
+ card: {
257
+ single_use_token: "test-nonce-123",
258
+ verification_method: "SCA_WHEN_REQUIRED",
259
+ },
260
+ },
261
+ amount: {
262
+ currency_code: "USD",
263
+ value: "100.00",
264
+ },
265
+ });
266
+ });
267
+
268
+ it("should use verificationMethod when provided", () => {
269
+ const payload = {
270
+ ...baseMerchantPayload,
271
+ verificationMethod: "SCA_ALWAYS",
272
+ };
273
+
274
+ const result = parseMerchantPayload({ merchantPayload: payload });
275
+
276
+ expect(result.payment_source.card.verification_method).toBe("SCA_ALWAYS");
277
+ });
278
+
279
+ it("should prioritize verificationMethod over threeDSRequested", () => {
280
+ const payload = {
281
+ ...baseMerchantPayload,
282
+ threeDSRequested: false,
283
+ verificationMethod: "SCA_ALWAYS",
284
+ };
285
+
286
+ const result = parseMerchantPayload({ merchantPayload: payload });
287
+
288
+ expect(result.payment_source.card.verification_method).toBe("SCA_ALWAYS");
289
+ });
290
+
291
+ it("should use threeDSRequested when verificationMethod not provided", () => {
292
+ const payloadWithTrue = {
293
+ ...baseMerchantPayload,
294
+ threeDSRequested: true,
295
+ };
296
+
297
+ const payloadWithFalse = {
298
+ ...baseMerchantPayload,
299
+ threeDSRequested: false,
300
+ };
301
+
302
+ const resultTrue = parseMerchantPayload({
303
+ merchantPayload: payloadWithTrue,
304
+ });
305
+ const resultFalse = parseMerchantPayload({
306
+ merchantPayload: payloadWithFalse,
307
+ });
308
+
309
+ expect(resultTrue.payment_source.card.verification_method).toBe(
310
+ "SCA_ALWAYS"
311
+ );
312
+ expect(resultFalse.payment_source.card.verification_method).toBe(
313
+ "SCA_WHEN_REQUIRED"
314
+ );
315
+ });
316
+
317
+ it("should include transactionContext when provided", () => {
318
+ const payload = {
319
+ ...baseMerchantPayload,
320
+ transactionContext: {
321
+ soft_descriptor: "PAYPAL *TEST",
322
+ },
323
+ };
324
+
325
+ const result = parseMerchantPayload({ merchantPayload: payload });
326
+
327
+ expect(result).toMatchObject({
328
+ intent: "THREE_DS_VERIFICATION",
329
+ payment_source: {
330
+ card: {
331
+ single_use_token: "test-nonce-123",
332
+ verification_method: "SCA_WHEN_REQUIRED",
333
+ },
334
+ },
335
+ amount: {
336
+ currency_code: "USD",
337
+ value: "100.00",
338
+ },
339
+ soft_descriptor: "PAYPAL *TEST",
340
+ });
341
+ });
342
+
343
+ it("should handle SCA_WHEN_REQUIRED in verificationMethod", () => {
344
+ const payload = {
345
+ ...baseMerchantPayload,
346
+ verificationMethod: "SCA_WHEN_REQUIRED",
347
+ };
348
+
349
+ const result = parseMerchantPayload({ merchantPayload: payload });
350
+
351
+ expect(result.payment_source.card.verification_method).toBe(
352
+ "SCA_WHEN_REQUIRED"
353
+ );
354
+ });
355
+ });
@@ -31,17 +31,18 @@ export function destroy(err?: mixed) {
31
31
  export const ThreeDomainSecureClient: LazyExport<ThreeDomainSecureComponentInterface> =
32
32
  {
33
33
  __get__: () => {
34
+ const accessToken = getSDKToken() || getUserIDToken();
34
35
  const threeDomainSecureInstance = new ThreeDomainSecureComponent({
35
36
  logger: getLogger(),
36
- restClient: new RestClient({ accessToken: getSDKToken() }),
37
+ restClient: new RestClient({ accessToken }),
37
38
  graphQLClient: new GraphQLClient({
38
39
  baseURL:
39
40
  getEnv() === "production" ? BRAINTREE_PROD : BRAINTREE_SANDBOX,
40
- accessToken: getSDKToken(),
41
+ accessToken,
41
42
  }),
42
43
  // $FlowIssue ZalgoPromise vs Promise
43
44
  sdkConfig: {
44
- authenticationToken: getSDKToken() || getUserIDToken(),
45
+ authenticationToken: accessToken,
45
46
  paypalApiDomain: getPayPalAPIDomain(),
46
47
  clientID: getClientID(),
47
48
  },
@@ -8,6 +8,7 @@ export type MerchantPayloadData = {|
8
8
  currency: string,
9
9
  nonce: string,
10
10
  threeDSRequested?: boolean,
11
+ verificationMethod?: "SCA_ALWAYS" | "SCA_WHEN_REQUIRED",
11
12
  transactionContext?: Object,
12
13
  idToken?: string,
13
14
  |};
@@ -73,7 +74,7 @@ export type SdkConfig = {|
73
74
  |};
74
75
 
75
76
  export type ThreeDSResponse = {|
76
- liabilityShift: string,
77
+ liabilityShift?: string,
77
78
  authenticationState: string,
78
79
  nonce?: string,
79
80
  |};