@paypal/checkout-components 5.0.437-alpha-ca07432.0 → 5.0.437

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.437-alpha-ca07432.0",
3
+ "version": "5.0.437",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/types.js CHANGED
@@ -71,7 +71,6 @@ export type Experiment = {|
71
71
  isPaylaterCobrandedLabelEnabled?: boolean,
72
72
  isPaylaterCobrandedLabelRandomizationEnabled?: boolean,
73
73
  isBfcacheEnabled?: boolean,
74
- isWebMCPEnabled?: boolean,
75
74
  |};
76
75
 
77
76
  export type Requires = {|
@@ -116,7 +116,7 @@ export type ButtonsComponent = ZoidComponent<
116
116
  ButtonProps,
117
117
  void,
118
118
  void,
119
- ButtonExtensions,
119
+ ButtonExtensions
120
120
  >;
121
121
 
122
122
  // $FlowIssue
@@ -195,7 +195,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
195
195
  try {
196
196
  const cplPhases = prepareInstrumentationPayload(
197
197
  buttonSessionID,
198
- "buttons",
198
+ "buttons"
199
199
  );
200
200
  const cplLatencyMetrics = {
201
201
  [FPTI_KEY.STATE]: "CPL_LATENCY_METRICS",
@@ -236,17 +236,18 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
236
236
  fundingSource = `-${props.fundingSource}`;
237
237
  }
238
238
 
239
- const baseIframeAttributes = {
240
- allowpaymentrequest: "allowpaymentrequest",
241
- scrolling: "no",
242
- title: `${FUNDING_BRAND_LABEL.PAYPAL}${fundingSource}`,
243
- role: "presentation",
244
- };
245
-
246
239
  return {
247
- iframe: props.experiment?.isWebMCPEnabled
248
- ? { ...baseIframeAttributes, allow: "tools" }
249
- : baseIframeAttributes,
240
+ iframe: {
241
+ allowpaymentrequest: "allowpaymentrequest",
242
+ // Named origin, not bare "payment": zoid never sets this iframe's `src`
243
+ // attribute (it navigates the frame via window.location/form-POST after
244
+ // creation instead), so the 'src' keyword that bare `allow="payment"`
245
+ // resolves to can never match the actual checkout origin.
246
+ allow: `payment ${getPayPalDomain()}`,
247
+ scrolling: "no",
248
+ title: `${FUNDING_BRAND_LABEL.PAYPAL}${fundingSource}`,
249
+ role: "presentation",
250
+ },
250
251
  };
251
252
  },
252
253
 
@@ -266,11 +267,11 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
266
267
  supportsVenmoPopups = supportsVenmoPopupsUtil(
267
268
  buttonExperiments,
268
269
  userAgentSupportsPopups(),
269
- getUserAgent(),
270
+ getUserAgent()
270
271
  ),
271
272
  supportedNativeVenmoBrowser = isSupportedNativeVenmoBrowser(
272
273
  buttonExperiments,
273
- getUserAgent(),
274
+ getUserAgent()
274
275
  ),
275
276
  experiment = buttonExperiments,
276
277
  createBillingAgreement,
@@ -384,7 +385,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
384
385
  ({ props: { buttonSessionID } }) =>
385
386
  ({ close }) => {
386
387
  const overlay = document.getElementsByName(
387
- `paypal-overlay-${buttonSessionID}`,
388
+ `paypal-overlay-${buttonSessionID}`
388
389
  )?.[0];
389
390
 
390
391
  if (overlay) {
@@ -460,7 +461,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
460
461
  () => {
461
462
  window.addEventListener(
462
463
  "visibilitychange",
463
- props.visibilityChangeHandler,
464
+ props.visibilityChangeHandler
464
465
  );
465
466
  },
466
467
  },
@@ -473,7 +474,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
473
474
  () => {
474
475
  window.removeEventListener(
475
476
  "visibilitychange",
476
- props.visibilityChangeHandler,
477
+ props.visibilityChangeHandler
477
478
  );
478
479
  },
479
480
  },
@@ -960,7 +961,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
960
961
  // $FlowFixMe
961
962
  value,
962
963
  layout,
963
- fundingSources,
964
+ fundingSources
964
965
  );
965
966
  },
966
967
  },
@@ -1045,7 +1046,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1045
1046
  const modalInstance = await getModal(
1046
1047
  clientID,
1047
1048
  merchantID,
1048
- buttonSessionID,
1049
+ buttonSessionID
1049
1050
  );
1050
1051
  return modalInstance?.show({
1051
1052
  amount,
@@ -1141,8 +1142,8 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1141
1142
  value: ({ props }) => {
1142
1143
  return Boolean(
1143
1144
  props.onShippingChange ||
1144
- props.onShippingAddressChange ||
1145
- props.onShippingOptionsChange,
1145
+ props.onShippingAddressChange ||
1146
+ props.onShippingOptionsChange
1146
1147
  );
1147
1148
  },
1148
1149
  },
@@ -1376,7 +1377,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1376
1377
  value: ({ props }) => {
1377
1378
  return isSupportedNativeVenmoBrowser(
1378
1379
  props.experiment,
1379
- props.userAgent,
1380
+ props.userAgent
1380
1381
  );
1381
1382
  },
1382
1383
  queryParam: true,
@@ -1397,7 +1398,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1397
1398
  return supportsVenmoPopupsUtil(
1398
1399
  props.experiment,
1399
1400
  userAgentSupportsPopups(),
1400
- props.userAgent,
1401
+ props.userAgent
1401
1402
  );
1402
1403
  },
1403
1404
  queryParam: true,
@@ -29,6 +29,7 @@ vi.mock("@paypal/sdk-client/src", async (importOriginal) => ({
29
29
  error: vi.fn(),
30
30
  })),
31
31
  getPayPalDomainRegex: vi.fn(() => /paypal\.com/),
32
+ getPayPalDomain: vi.fn(() => "https://www.paypal.com"),
32
33
  getEnv: vi.fn(() => "test"),
33
34
  }));
34
35
 
@@ -43,7 +44,7 @@ describe("getButtonsComponent iframe title", () => {
43
44
  const { attributes } = createMock.mock.calls[0][0];
44
45
 
45
46
  expect(attributes({ props: {} }).iframe.title).toBe(
46
- FUNDING_BRAND_LABEL.PAYPAL,
47
+ FUNDING_BRAND_LABEL.PAYPAL
47
48
  );
48
49
  });
49
50
 
@@ -52,36 +53,43 @@ describe("getButtonsComponent iframe title", () => {
52
53
  const { attributes } = createMock.mock.calls[0][0];
53
54
 
54
55
  expect(attributes({ props: { fundingSource: "venmo" } }).iframe.title).toBe(
55
- `${FUNDING_BRAND_LABEL.PAYPAL}-venmo`,
56
+ `${FUNDING_BRAND_LABEL.PAYPAL}-venmo`
56
57
  );
57
58
  });
58
59
  });
59
60
 
60
- describe("getButtonsComponent iframe allow attribute", () => {
61
- it("does not set allow when the WebMCP first-render experiment flag is absent", () => {
61
+ describe("getButtonsComponent iframe payment permissions policy", () => {
62
+ it("delegates the payment permissions policy to the buttons iframe's actual origin", () => {
62
63
  getButtonsComponent();
63
64
  const { attributes } = createMock.mock.calls[0][0];
64
65
 
65
- expect(attributes({ props: {} }).iframe.allow).toBeUndefined();
66
+ expect(attributes({ props: {} }).iframe.allow).toBe(
67
+ "payment https://www.paypal.com"
68
+ );
66
69
  });
67
70
 
68
- it("does not set allow when the WebMCP first-render experiment flag is disabled", () => {
71
+ it("keeps the legacy allowpaymentrequest attribute alongside the modern allow attribute", () => {
69
72
  getButtonsComponent();
70
73
  const { attributes } = createMock.mock.calls[0][0];
71
74
 
72
- expect(
73
- attributes({ props: { experiment: { isWebMCPEnabled: false } } }).iframe
74
- .allow,
75
- ).toBeUndefined();
75
+ const iframeAttrs = attributes({ props: {} }).iframe;
76
+ expect(iframeAttrs.allowpaymentrequest).toBe("allowpaymentrequest");
77
+ expect(iframeAttrs.allow).toBe("payment https://www.paypal.com");
76
78
  });
77
79
 
78
- it("sets allow to 'tools' when the WebMCP first-render experiment flag is enabled", () => {
80
+ it("names the origin explicitly rather than relying on the 'src' keyword", () => {
81
+ // zoid's openFrame() never sets this iframe's `src` attribute — it creates
82
+ // the iframe bare, then navigates it later via proxyWin.setLocation(), which
83
+ // uses window.location/form-POST, not the `src` attribute. Permissions
84
+ // Policy's 'src' keyword resolves by reading that attribute, so a bare
85
+ // `allow="payment"` (== `payment 'src'`) can never grant the feature here.
86
+ // Regression test for the retest failure after the original 'payment'-only
87
+ // fix (DTINAPPXO-5124).
79
88
  getButtonsComponent();
80
89
  const { attributes } = createMock.mock.calls[0][0];
81
90
 
82
- expect(
83
- attributes({ props: { experiment: { isWebMCPEnabled: true } } }).iframe
84
- .allow,
85
- ).toBe("tools");
91
+ const iframeAttrs = attributes({ props: {} }).iframe;
92
+ expect(iframeAttrs.allow).not.toBe("payment");
93
+ expect(iframeAttrs.allow).toContain("https://www.paypal.com");
86
94
  });
87
95
  });
@@ -68,13 +68,13 @@ function spyOnWindowNameAssignment(): void {
68
68
  new Error(
69
69
  `window.name was overwritten: ${String(value).slice(
70
70
  0,
71
- WINDOW_NAME_LOG_TRUNCATION_LENGTH,
72
- )}`,
73
- ),
71
+ WINDOW_NAME_LOG_TRUNCATION_LENGTH
72
+ )}`
73
+ )
74
74
  ),
75
75
  originalValue: String(currentWindowName).slice(
76
76
  0,
77
- WINDOW_NAME_LOG_TRUNCATION_LENGTH,
77
+ WINDOW_NAME_LOG_TRUNCATION_LENGTH
78
78
  ),
79
79
  });
80
80
  currentWindowName = value;
@@ -97,6 +97,13 @@ export function getCheckoutComponent(): CheckoutComponent {
97
97
  attributes: {
98
98
  iframe: {
99
99
  scrolling: "yes",
100
+ // Delegates the Permissions Policy 'payment' feature so PaymentRequest
101
+ // works when this iframe falls back to CONTEXT.IFRAME (supportsPopups() false).
102
+ // zoid never sets this iframe's `src` attribute (it navigates the frame
103
+ // via window.location/form-POST after creation instead), so the 'src'
104
+ // keyword that bare `allow="payment"` resolves to can never match the
105
+ // actual checkout origin. Name the origin explicitly instead.
106
+ allow: `payment ${getPayPalDomain()}`,
100
107
  },
101
108
  },
102
109
 
@@ -343,8 +350,8 @@ export function getCheckoutComponent(): CheckoutComponent {
343
350
  value: ({ props }) => {
344
351
  return Boolean(
345
352
  props.onShippingChange ||
346
- props.onShippingAddressChange ||
347
- props.onShippingOptionsChange,
353
+ props.onShippingAddressChange ||
354
+ props.onShippingOptionsChange
348
355
  );
349
356
  },
350
357
  },
@@ -0,0 +1,84 @@
1
+ /* @flow */
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { supportsPopups } from "@krakenjs/belter/src";
5
+
6
+ import { getCheckoutComponent } from "./component";
7
+
8
+ // Capture the config object component.jsx passes to zoid's create() so we can
9
+ // assert on the real (un-exported) attributes.iframe config, mirroring the
10
+ // pattern used in ../buttons/component.test.js.
11
+ const { createMock } = vi.hoisted(() => ({
12
+ createMock: vi.fn((options) => ({ ...options, isChild: () => false })),
13
+ }));
14
+
15
+ vi.mock("@krakenjs/zoid/src", () => ({
16
+ create: createMock,
17
+ CONTEXT: { POPUP: "popup", IFRAME: "iframe" },
18
+ EVENT: { FOCUS: "focus" },
19
+ }));
20
+
21
+ vi.mock("@krakenjs/belter/src", async (importOriginal) => ({
22
+ ...(await importOriginal()),
23
+ supportsPopups: vi.fn(() => true),
24
+ }));
25
+
26
+ vi.mock("@paypal/sdk-client/src", async (importOriginal) => ({
27
+ ...(await importOriginal()),
28
+ getLogger: vi.fn(() => ({
29
+ metricCounter: vi.fn(),
30
+ track: vi.fn(),
31
+ info: vi.fn(),
32
+ error: vi.fn(),
33
+ })),
34
+ getPayPalDomainRegex: vi.fn(() => /paypal\.com/),
35
+ getPayPalDomain: vi.fn(() => "https://www.paypal.com"),
36
+ }));
37
+
38
+ describe("getCheckoutComponent iframe payment permissions policy", () => {
39
+ it("delegates the payment permissions policy to the checkout iframe's actual origin", () => {
40
+ getCheckoutComponent();
41
+ const { attributes } = createMock.mock.calls[0][0];
42
+
43
+ expect(attributes.iframe.allow).toBe("payment https://www.paypal.com");
44
+ });
45
+
46
+ it("keeps the existing scrolling attribute alongside the new allow attribute", () => {
47
+ getCheckoutComponent();
48
+ const { attributes } = createMock.mock.calls[0][0];
49
+
50
+ expect(attributes.iframe.scrolling).toBe("yes");
51
+ expect(attributes.iframe.allow).toBe("payment https://www.paypal.com");
52
+ });
53
+
54
+ it("still delegates the payment permissions policy when the component falls back to CONTEXT.IFRAME", async () => {
55
+ // getCheckoutComponent() is inline-memoized, so the earlier tests' calls
56
+ // never re-invoke create(). Reset the module registry to get a fresh,
57
+ // un-memoized component that actually re-evaluates supportsPopups().
58
+ vi.mocked(supportsPopups).mockReturnValueOnce(false);
59
+ vi.resetModules();
60
+ const { getCheckoutComponent: getFreshCheckoutComponent } = await import(
61
+ "./component"
62
+ );
63
+
64
+ getFreshCheckoutComponent();
65
+
66
+ const { attributes } = createMock.mock.calls.at(-1)[0];
67
+ expect(attributes.iframe.allow).toBe("payment https://www.paypal.com");
68
+ });
69
+
70
+ it("names the origin explicitly rather than relying on the 'src' keyword", () => {
71
+ // zoid's openFrame() never sets this iframe's `src` attribute — it creates
72
+ // the iframe bare, then navigates it later via proxyWin.setLocation(), which
73
+ // uses window.location/form-POST, not the `src` attribute. Permissions
74
+ // Policy's 'src' keyword resolves by reading that attribute, so a bare
75
+ // `allow="payment"` (== `payment 'src'`) can never grant the feature here.
76
+ // Regression test for the retest failure after the original 'payment'-only
77
+ // fix (DTINAPPXO-5124).
78
+ getCheckoutComponent();
79
+ const { attributes } = createMock.mock.calls[0][0];
80
+
81
+ expect(attributes.iframe.allow).not.toBe("payment");
82
+ expect(attributes.iframe.allow).toContain("https://www.paypal.com");
83
+ });
84
+ });