@paypal/checkout-components 5.0.436 → 5.0.437-alpha-4d75517.0

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.436",
3
+ "version": "5.0.437-alpha-4d75517.0",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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",
@@ -239,6 +239,11 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
239
239
  return {
240
240
  iframe: {
241
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()}`,
242
247
  scrolling: "no",
243
248
  title: `${FUNDING_BRAND_LABEL.PAYPAL}${fundingSource}`,
244
249
  role: "presentation",
@@ -262,11 +267,11 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
262
267
  supportsVenmoPopups = supportsVenmoPopupsUtil(
263
268
  buttonExperiments,
264
269
  userAgentSupportsPopups(),
265
- getUserAgent(),
270
+ getUserAgent()
266
271
  ),
267
272
  supportedNativeVenmoBrowser = isSupportedNativeVenmoBrowser(
268
273
  buttonExperiments,
269
- getUserAgent(),
274
+ getUserAgent()
270
275
  ),
271
276
  experiment = buttonExperiments,
272
277
  createBillingAgreement,
@@ -380,7 +385,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
380
385
  ({ props: { buttonSessionID } }) =>
381
386
  ({ close }) => {
382
387
  const overlay = document.getElementsByName(
383
- `paypal-overlay-${buttonSessionID}`,
388
+ `paypal-overlay-${buttonSessionID}`
384
389
  )?.[0];
385
390
 
386
391
  if (overlay) {
@@ -456,7 +461,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
456
461
  () => {
457
462
  window.addEventListener(
458
463
  "visibilitychange",
459
- props.visibilityChangeHandler,
464
+ props.visibilityChangeHandler
460
465
  );
461
466
  },
462
467
  },
@@ -469,7 +474,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
469
474
  () => {
470
475
  window.removeEventListener(
471
476
  "visibilitychange",
472
- props.visibilityChangeHandler,
477
+ props.visibilityChangeHandler
473
478
  );
474
479
  },
475
480
  },
@@ -956,7 +961,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
956
961
  // $FlowFixMe
957
962
  value,
958
963
  layout,
959
- fundingSources,
964
+ fundingSources
960
965
  );
961
966
  },
962
967
  },
@@ -1041,7 +1046,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1041
1046
  const modalInstance = await getModal(
1042
1047
  clientID,
1043
1048
  merchantID,
1044
- buttonSessionID,
1049
+ buttonSessionID
1045
1050
  );
1046
1051
  return modalInstance?.show({
1047
1052
  amount,
@@ -1137,8 +1142,8 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1137
1142
  value: ({ props }) => {
1138
1143
  return Boolean(
1139
1144
  props.onShippingChange ||
1140
- props.onShippingAddressChange ||
1141
- props.onShippingOptionsChange,
1145
+ props.onShippingAddressChange ||
1146
+ props.onShippingOptionsChange
1142
1147
  );
1143
1148
  },
1144
1149
  },
@@ -1372,7 +1377,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1372
1377
  value: ({ props }) => {
1373
1378
  return isSupportedNativeVenmoBrowser(
1374
1379
  props.experiment,
1375
- props.userAgent,
1380
+ props.userAgent
1376
1381
  );
1377
1382
  },
1378
1383
  queryParam: true,
@@ -1393,7 +1398,7 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
1393
1398
  return supportsVenmoPopupsUtil(
1394
1399
  props.experiment,
1395
1400
  userAgentSupportsPopups(),
1396
- props.userAgent,
1401
+ props.userAgent
1397
1402
  );
1398
1403
  },
1399
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,7 +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
  });
60
+
61
+ describe("getButtonsComponent iframe payment permissions policy", () => {
62
+ it("delegates the payment permissions policy to the buttons iframe's actual origin", () => {
63
+ getButtonsComponent();
64
+ const { attributes } = createMock.mock.calls[0][0];
65
+
66
+ expect(attributes({ props: {} }).iframe.allow).toBe(
67
+ "payment https://www.paypal.com"
68
+ );
69
+ });
70
+
71
+ it("keeps the legacy allowpaymentrequest attribute alongside the modern allow attribute", () => {
72
+ getButtonsComponent();
73
+ const { attributes } = createMock.mock.calls[0][0];
74
+
75
+ const iframeAttrs = attributes({ props: {} }).iframe;
76
+ expect(iframeAttrs.allowpaymentrequest).toBe("allowpaymentrequest");
77
+ expect(iframeAttrs.allow).toBe("payment https://www.paypal.com");
78
+ });
79
+
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).
88
+ getButtonsComponent();
89
+ const { attributes } = createMock.mock.calls[0][0];
90
+
91
+ const iframeAttrs = attributes({ props: {} }).iframe;
92
+ expect(iframeAttrs.allow).not.toBe("payment");
93
+ expect(iframeAttrs.allow).toContain("https://www.paypal.com");
94
+ });
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
+ });