@paypal/checkout-components 5.0.383 → 5.0.385

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.383",
3
+ "version": "5.0.385",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -23,6 +23,7 @@
23
23
  "jest-ssr": "jest test/ssr --env=node --no-cache --collectCoverage --collectCoverageFrom='src/' --coverageDirectory='coverage/jest'",
24
24
  "karma": "cross-env NODE_ENV=test babel-node ./node_modules/.bin/karma start",
25
25
  "lint": "eslint --ext .js --ext .jsx src/ test/ *.js",
26
+ "lint:fix": "eslint --ext .js --ext .jsx src/ test/ *.js --fix",
26
27
  "postversion": "./scripts/postversion.sh",
27
28
  "preversion": "./scripts/preversion.sh",
28
29
  "reinstall": "rimraf flow-typed && rimraf node_modules && npm install && npm run flow-typed",
@@ -122,7 +123,7 @@
122
123
  "@paypal/funding-components": "^1.0.31",
123
124
  "@paypal/sdk-client": "^4.0.199",
124
125
  "@paypal/sdk-constants": "^1.0.156",
125
- "@paypal/sdk-logos": "^2.3.1"
126
+ "@paypal/sdk-logos": "^2.3.2"
126
127
  },
127
128
  "lint-staged": {
128
129
  "**/*": "prettier --write --ignore-unknown"
@@ -4,6 +4,7 @@
4
4
  import {
5
5
  BancontactLogoInlineSVG,
6
6
  BancontactLogoExternalImage,
7
+ BancontactMarkRebrandExternalImage,
7
8
  } from "@paypal/sdk-logos/src";
8
9
  import { Fragment, node } from "@krakenjs/jsx-pragmatic/src";
9
10
 
@@ -47,5 +48,8 @@ export function getBancontactConfig(): FundingSourceConfig {
47
48
 
48
49
  return <BasicLabel {...opts} logo={apmLogo} />;
49
50
  },
51
+
52
+ Mark: () => <BancontactMarkRebrandExternalImage />,
53
+ shouldUseMarkForRebrandOnly: true,
50
54
  };
51
55
  }
@@ -149,6 +149,7 @@ export type FundingSourceConfig = {|
149
149
  instrument: WalletInstrument,
150
150
  userIDToken: ?string,
151
151
  |}) => boolean,
152
+ shouldUseMarkForRebrandOnly?: boolean,
152
153
  |};
153
154
 
154
155
  export function BasicLabel({
@@ -13,6 +13,7 @@ import {
13
13
  LOGO_COLOR,
14
14
  PPRebrandLogoInlineSVG,
15
15
  PPRebrandLogoExternalImage,
16
+ CreditMarkRebrandExternalImage,
16
17
  } from "@paypal/sdk-logos/src";
17
18
 
18
19
  import {
@@ -93,6 +94,9 @@ export function getCreditConfig(): FundingSourceConfig {
93
94
  );
94
95
  },
95
96
 
97
+ Mark: () => <CreditMarkRebrandExternalImage />,
98
+ shouldUseMarkForRebrandOnly: true,
99
+
96
100
  WalletLabel,
97
101
 
98
102
  colors: [
@@ -16,6 +16,7 @@ import type { Wallet, Experiment } from "../types";
16
16
  import { BUTTON_LAYOUT, BUTTON_FLOW } from "../constants";
17
17
 
18
18
  import { getFundingConfig } from "./config";
19
+ import { supportsVenmoPopups, isSupportedNativeVenmoBrowser } from "./util";
19
20
 
20
21
  type IsFundingEligibleOptions = {|
21
22
  layout?: $Values<typeof BUTTON_LAYOUT>,
@@ -35,6 +36,7 @@ type IsFundingEligibleOptions = {|
35
36
  supportedNativeBrowser: boolean,
36
37
  experiment?: Experiment,
37
38
  displayOnly?: $ReadOnlyArray<$Values<typeof DISPLAY_ONLY_VALUES>>,
39
+ userAgent?: string,
38
40
  |};
39
41
 
40
42
  function isFundingVaultable({
@@ -84,6 +86,7 @@ export function isFundingEligible(
84
86
  supportedNativeBrowser,
85
87
  experiment,
86
88
  displayOnly,
89
+ userAgent,
87
90
  }: IsFundingEligibleOptions
88
91
  ): boolean {
89
92
  if (!fundingEligibility[source] || !fundingEligibility[source].eligible) {
@@ -156,10 +159,17 @@ export function isFundingEligible(
156
159
  return false;
157
160
  }
158
161
 
159
- if (fundingConfig.requires) {
162
+ if (fundingConfig.requires && userAgent) {
160
163
  const required = fundingConfig.requires({ experiment, platform });
161
-
162
- if (required.popup === true && supportsPopups === false) {
164
+ const popupSupport =
165
+ source === FUNDING.VENMO
166
+ ? supportsVenmoPopups(experiment, userAgent)
167
+ : supportsPopups;
168
+ const nativeBrowserSupport =
169
+ source === FUNDING.VENMO
170
+ ? isSupportedNativeVenmoBrowser(experiment, userAgent)
171
+ : supportedNativeBrowser;
172
+ if (required.popup === true && popupSupport === false) {
163
173
  return false;
164
174
  }
165
175
 
@@ -167,7 +177,7 @@ export function isFundingEligible(
167
177
  return false;
168
178
  }
169
179
 
170
- if (required.native === true && supportedNativeBrowser === false) {
180
+ if (required.native === true && nativeBrowserSupport === false) {
171
181
  return false;
172
182
  }
173
183
  }
@@ -204,6 +214,7 @@ export function determineEligibleFunding({
204
214
  supportedNativeBrowser,
205
215
  experiment,
206
216
  displayOnly = [],
217
+ userAgent = "",
207
218
  }: {|
208
219
  fundingSource: ?$Values<typeof FUNDING>,
209
220
  remembered: $ReadOnlyArray<$Values<typeof FUNDING>>,
@@ -223,6 +234,7 @@ export function determineEligibleFunding({
223
234
  supportedNativeBrowser: boolean,
224
235
  experiment: Experiment,
225
236
  displayOnly?: $ReadOnlyArray<$Values<typeof DISPLAY_ONLY_VALUES>>,
237
+ userAgent?: string,
226
238
  |}): $ReadOnlyArray<$Values<typeof FUNDING>> {
227
239
  if (fundingSource) {
228
240
  return [fundingSource];
@@ -247,6 +259,7 @@ export function determineEligibleFunding({
247
259
  supportedNativeBrowser,
248
260
  experiment,
249
261
  displayOnly,
262
+ userAgent,
250
263
  })
251
264
  );
252
265
 
@@ -1,10 +1,23 @@
1
1
  /* @flow */
2
- import { COMPONENTS, FUNDING, PLATFORM } from "@paypal/sdk-constants/src";
3
- import { describe, expect } from "vitest";
2
+ import { COMPONENTS, FUNDING } from "@paypal/sdk-constants/src";
3
+ import { describe, expect, vi, beforeEach, afterEach } from "vitest";
4
4
 
5
5
  import { BUTTON_FLOW } from "../constants";
6
6
 
7
7
  import { isFundingEligible, isWalletFundingEligible } from "./funding";
8
+ import { supportsVenmoPopups, isSupportedNativeVenmoBrowser } from "./util";
9
+ import { getFundingConfig } from "./config";
10
+
11
+ // Mock the venmo utility functions
12
+ vi.mock("./util", () => ({
13
+ supportsVenmoPopups: vi.fn(),
14
+ isSupportedNativeVenmoBrowser: vi.fn(),
15
+ }));
16
+
17
+ // Mock getFundingConfig to control funding config behavior
18
+ vi.mock("./config", () => ({
19
+ getFundingConfig: vi.fn(),
20
+ }));
8
21
 
9
22
  const defaultMockFundingOptions = {
10
23
  platform: "desktop",
@@ -55,9 +68,41 @@ const defaultMockFundingOptions = {
55
68
  onShippingChange: null,
56
69
  onShippingAddressChange: null,
57
70
  onShippingOptionsChange: null,
71
+ userAgent:
72
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
58
73
  };
59
74
 
60
75
  describe("Funding eligibility", () => {
76
+ beforeEach(() => {
77
+ // Mock getFundingConfig with basic configs for all funding sources
78
+ vi.mocked(getFundingConfig).mockReturnValue({
79
+ [FUNDING.PAYLATER]: {
80
+ enabled: true,
81
+ automatic: true,
82
+ },
83
+ [FUNDING.CARD]: {
84
+ enabled: true,
85
+ automatic: true,
86
+ },
87
+ [FUNDING.SEPA]: {
88
+ enabled: false,
89
+ automatic: false,
90
+ },
91
+ [FUNDING.OXXO]: {
92
+ enabled: false,
93
+ automatic: false,
94
+ },
95
+ [FUNDING.VENMO]: {
96
+ enabled: true,
97
+ automatic: true,
98
+ },
99
+ [FUNDING.PAYPAL]: {
100
+ enabled: true,
101
+ automatic: true,
102
+ },
103
+ });
104
+ });
105
+
61
106
  describe("Desktop", () => {
62
107
  test("should not be eligible if funding source is missing from fundingEligibility", () => {
63
108
  const fundingEligible = isFundingEligible(
@@ -173,29 +218,268 @@ describe("Funding eligibility", () => {
173
218
  });
174
219
  });
175
220
 
176
- describe("Mobile", () => {
177
- test("should be eligible if window.popupBridge is defined for Venmo and supportsPopups is false", () => {
178
- window.popupBridge = {};
221
+ describe("Venmo-specific funding requirements", () => {
222
+ beforeEach(() => {
223
+ // Reset all mocks before each test
224
+ vi.clearAllMocks();
179
225
 
180
- const fundingEligible = isFundingEligible(FUNDING.VENMO, {
181
- ...defaultMockFundingOptions,
182
- platform: PLATFORM.MOBILE,
183
- supportsPopups: false,
226
+ // Mock getFundingConfig to return configs with venmo requirements
227
+ vi.mocked(getFundingConfig).mockReturnValue({
228
+ [FUNDING.PAYLATER]: {
229
+ enabled: true,
230
+ automatic: true,
231
+ },
232
+ [FUNDING.CARD]: {
233
+ enabled: true,
234
+ automatic: true,
235
+ },
236
+ [FUNDING.SEPA]: {
237
+ enabled: false,
238
+ automatic: false,
239
+ },
240
+ [FUNDING.OXXO]: {
241
+ enabled: false,
242
+ automatic: false,
243
+ },
244
+ [FUNDING.VENMO]: {
245
+ enabled: true,
246
+ automatic: true,
247
+ requires: () => ({
248
+ popup: true,
249
+ native: true,
250
+ }),
251
+ },
252
+ [FUNDING.PAYPAL]: {
253
+ enabled: true,
254
+ automatic: true,
255
+ requires: () => ({
256
+ popup: true,
257
+ native: true,
258
+ }),
259
+ },
184
260
  });
261
+ });
262
+
263
+ afterEach(() => {
264
+ vi.resetAllMocks();
265
+ });
185
266
 
186
- expect(fundingEligible).toBe(true);
267
+ test("should use supportsVenmoPopups for venmo funding source when popup is required", () => {
268
+ vi.mocked(supportsVenmoPopups).mockReturnValue(true);
269
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(true);
187
270
 
188
- window.popupBridge = undefined;
271
+ const options = {
272
+ ...defaultMockFundingOptions,
273
+ fundingSource: FUNDING.VENMO,
274
+ platform: "mobile",
275
+ experiment: { venmoEnableWebOnNonNativeBrowser: true },
276
+ };
277
+
278
+ const result = isFundingEligible(FUNDING.VENMO, options);
279
+
280
+ expect(supportsVenmoPopups).toHaveBeenCalledWith(
281
+ options.experiment,
282
+ options.userAgent
283
+ );
284
+ expect(result).toBe(true);
285
+ });
286
+
287
+ test("should use isSupportedNativeVenmoBrowser for venmo funding source when native is required", () => {
288
+ vi.mocked(supportsVenmoPopups).mockReturnValue(true);
289
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(true);
290
+
291
+ const options = {
292
+ ...defaultMockFundingOptions,
293
+ fundingSource: FUNDING.VENMO,
294
+ platform: "mobile",
295
+ experiment: { venmoEnableWebOnNonNativeBrowser: true },
296
+ };
297
+
298
+ const result = isFundingEligible(FUNDING.VENMO, options);
299
+
300
+ expect(isSupportedNativeVenmoBrowser).toHaveBeenCalledWith(
301
+ options.experiment,
302
+ options.userAgent
303
+ );
304
+ expect(result).toBe(true);
305
+ });
306
+
307
+ test("should return false when venmo popup support is required but supportsVenmoPopups returns false", () => {
308
+ vi.mocked(supportsVenmoPopups).mockReturnValue(false);
309
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(true);
310
+
311
+ const options = {
312
+ ...defaultMockFundingOptions,
313
+ fundingSource: FUNDING.VENMO,
314
+ platform: "mobile",
315
+ experiment: {},
316
+ };
317
+
318
+ const result = isFundingEligible(FUNDING.VENMO, options);
319
+
320
+ expect(supportsVenmoPopups).toHaveBeenCalledWith(
321
+ options.experiment,
322
+ options.userAgent
323
+ );
324
+ expect(result).toBe(false);
189
325
  });
190
326
 
191
- test("should not be eligible if window.popupBridge is undefined for Venmo and supportsPopups is false", () => {
192
- const fundingEligible = isFundingEligible(FUNDING.VENMO, {
327
+ test("should return false when venmo native support is required but isSupportedNativeVenmoBrowser returns false", () => {
328
+ vi.mocked(supportsVenmoPopups).mockReturnValue(true);
329
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(false);
330
+
331
+ const options = {
193
332
  ...defaultMockFundingOptions,
194
- platform: PLATFORM.MOBILE,
195
- supportsPopups: false,
333
+ fundingSource: FUNDING.VENMO,
334
+ platform: "mobile",
335
+ experiment: {},
336
+ };
337
+
338
+ const result = isFundingEligible(FUNDING.VENMO, options);
339
+
340
+ expect(isSupportedNativeVenmoBrowser).toHaveBeenCalledWith(
341
+ options.experiment,
342
+ options.userAgent
343
+ );
344
+ expect(result).toBe(false);
345
+ });
346
+
347
+ test("should use standard supportsPopups for non-venmo funding sources", () => {
348
+ vi.mocked(supportsVenmoPopups).mockReturnValue(false);
349
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(false);
350
+
351
+ // Update the mock to not require popup and native for PayPal to isolate the test
352
+ vi.mocked(getFundingConfig).mockReturnValue({
353
+ [FUNDING.PAYLATER]: {
354
+ enabled: true,
355
+ automatic: true,
356
+ },
357
+ [FUNDING.CARD]: {
358
+ enabled: true,
359
+ automatic: true,
360
+ },
361
+ [FUNDING.SEPA]: {
362
+ enabled: false,
363
+ automatic: false,
364
+ },
365
+ [FUNDING.OXXO]: {
366
+ enabled: false,
367
+ automatic: false,
368
+ },
369
+ [FUNDING.VENMO]: {
370
+ enabled: true,
371
+ automatic: true,
372
+ requires: () => ({
373
+ popup: true,
374
+ native: true,
375
+ }),
376
+ },
377
+ [FUNDING.PAYPAL]: {
378
+ enabled: true,
379
+ automatic: true,
380
+ // No requires function for PayPal to test standard behavior
381
+ },
196
382
  });
197
383
 
198
- expect(fundingEligible).toBe(false);
384
+ const options = {
385
+ ...defaultMockFundingOptions,
386
+ fundingSource: FUNDING.PAYPAL,
387
+ platform: "mobile",
388
+ supportsPopups: true,
389
+ supportedNativeBrowser: true,
390
+ experiment: {},
391
+ fundingEligibility: {
392
+ ...defaultMockFundingOptions.fundingEligibility,
393
+ paypal: {
394
+ eligible: true,
395
+ vaultable: false,
396
+ branded: false,
397
+ },
398
+ },
399
+ };
400
+
401
+ const result = isFundingEligible(FUNDING.PAYPAL, options);
402
+
403
+ // Venmo functions should not be called for non-venmo sources
404
+ expect(supportsVenmoPopups).not.toHaveBeenCalled();
405
+ expect(isSupportedNativeVenmoBrowser).not.toHaveBeenCalled();
406
+ expect(result).toBe(true);
407
+ });
408
+
409
+ test("should handle undefined experiment parameter for venmo", () => {
410
+ vi.mocked(supportsVenmoPopups).mockReturnValue(true);
411
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(true);
412
+
413
+ const options = {
414
+ ...defaultMockFundingOptions,
415
+ fundingSource: FUNDING.VENMO,
416
+ platform: "mobile",
417
+ experiment: undefined,
418
+ };
419
+
420
+ const result = isFundingEligible(FUNDING.VENMO, options);
421
+
422
+ expect(supportsVenmoPopups).toHaveBeenCalledWith(
423
+ undefined,
424
+ options.userAgent
425
+ );
426
+ expect(isSupportedNativeVenmoBrowser).toHaveBeenCalledWith(
427
+ undefined,
428
+ options.userAgent
429
+ );
430
+ expect(result).toBe(true);
431
+ });
432
+
433
+ test("should pass through experiment flags to venmo utility functions", () => {
434
+ vi.mocked(supportsVenmoPopups).mockReturnValue(true);
435
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(true);
436
+
437
+ const experimentFlags = {
438
+ venmoEnableWebOnNonNativeBrowser: true,
439
+ venmoVaultWithoutPurchase: false,
440
+ };
441
+
442
+ const options = {
443
+ ...defaultMockFundingOptions,
444
+ fundingSource: FUNDING.VENMO,
445
+ platform: "mobile",
446
+ experiment: experimentFlags,
447
+ };
448
+
449
+ const result = isFundingEligible(FUNDING.VENMO, options);
450
+
451
+ expect(supportsVenmoPopups).toHaveBeenCalledWith(
452
+ experimentFlags,
453
+ options.userAgent
454
+ );
455
+ expect(isSupportedNativeVenmoBrowser).toHaveBeenCalledWith(
456
+ experimentFlags,
457
+ options.userAgent
458
+ );
459
+ expect(result).toBe(true);
460
+ });
461
+
462
+ test("should respect combination of venmo popup and native requirements", () => {
463
+ // Test case where popup succeeds but native fails
464
+ vi.mocked(supportsVenmoPopups).mockReturnValue(true);
465
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(false);
466
+
467
+ const options = {
468
+ ...defaultMockFundingOptions,
469
+ fundingSource: FUNDING.VENMO,
470
+ platform: "mobile",
471
+ experiment: {},
472
+ };
473
+
474
+ const result = isFundingEligible(FUNDING.VENMO, options);
475
+
476
+ expect(result).toBe(false);
477
+
478
+ // Test case where both succeed
479
+ vi.mocked(isSupportedNativeVenmoBrowser).mockReturnValue(true);
480
+
481
+ const result2 = isFundingEligible(FUNDING.VENMO, options);
482
+ expect(result2).toBe(true);
199
483
  });
200
484
  });
201
485
  });
@@ -10,6 +10,7 @@ import {
10
10
  LOGO_COLOR,
11
11
  PPRebrandLogoInlineSVG,
12
12
  PPRebrandLogoExternalImage,
13
+ PaylaterMarkRebrandExternalImage,
13
14
  } from "@paypal/sdk-logos/src";
14
15
 
15
16
  import { Logo } from "../paypal/template";
@@ -131,6 +132,9 @@ export function getPaylaterConfig(): FundingSourceConfig {
131
132
  );
132
133
  },
133
134
 
135
+ Mark: () => <PaylaterMarkRebrandExternalImage />,
136
+ shouldUseMarkForRebrandOnly: true,
137
+
134
138
  colors: [
135
139
  BUTTON_COLOR.WHITE,
136
140
  BUTTON_COLOR.BLACK,
@@ -0,0 +1,99 @@
1
+ /* @flow */
2
+
3
+ import {
4
+ isWebView,
5
+ isIosWebview,
6
+ isAndroidWebview,
7
+ isFacebookWebView,
8
+ isOperaMini,
9
+ isFirefoxIOS,
10
+ isEdgeIOS,
11
+ isQQBrowser,
12
+ isElectron,
13
+ supportsPopups,
14
+ isTablet,
15
+ isIos,
16
+ isSafari,
17
+ isAndroid,
18
+ isChrome,
19
+ isFirefox,
20
+ } from "@krakenjs/belter/src";
21
+
22
+ import type { Experiment } from "../types";
23
+
24
+ const isMacOsCna = (userAgent: string): boolean => {
25
+ return /Macintosh.*AppleWebKit(?!.*Safari)/i.test(userAgent);
26
+ };
27
+
28
+ const isVenmoSupportedWebView = (userAgent: string): boolean => {
29
+ return (
30
+ isWebView(userAgent) ||
31
+ isIosWebview(userAgent) ||
32
+ isAndroidWebview(userAgent) ||
33
+ isFacebookWebView(userAgent)
34
+ );
35
+ };
36
+
37
+ const venmoUserAgentSupportsPopups = (userAgent: string): boolean => {
38
+ return !(
39
+ isVenmoSupportedWebView(userAgent) ||
40
+ isOperaMini(userAgent) ||
41
+ isFirefoxIOS(userAgent) ||
42
+ isEdgeIOS(userAgent) ||
43
+ isQQBrowser(userAgent) ||
44
+ isMacOsCna(userAgent) ||
45
+ isElectron()
46
+ );
47
+ };
48
+
49
+ export function supportsVenmoPopups(
50
+ experiment?: Experiment,
51
+ userAgent: string
52
+ ): boolean {
53
+ if (isVenmoSupportedWebView(userAgent)) {
54
+ if (typeof window !== "undefined" && window.popupBridge) {
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ if (experiment?.venmoEnableWebOnNonNativeBrowser === true) {
61
+ return venmoUserAgentSupportsPopups(userAgent);
62
+ }
63
+ return supportsPopups();
64
+ }
65
+
66
+ export function isSupportedNativeVenmoBrowser(
67
+ experiment?: Experiment,
68
+ userAgent: string
69
+ ): boolean {
70
+ if (isVenmoSupportedWebView(userAgent)) {
71
+ if (typeof window !== "undefined" && window.popupBridge) {
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ if (isTablet(userAgent)) {
78
+ return false;
79
+ }
80
+
81
+ // Default supported browsers for Venmo
82
+ if (
83
+ (isIos(userAgent) && isSafari(userAgent)) ||
84
+ (isAndroid(userAgent) && isChrome(userAgent))
85
+ ) {
86
+ return true;
87
+ }
88
+
89
+ // Additional browsers enabled by experiment
90
+ if (
91
+ experiment?.venmoEnableWebOnNonNativeBrowser === true &&
92
+ ((isIos(userAgent) && isChrome(userAgent)) ||
93
+ (isAndroid(userAgent) && isFirefox(userAgent)))
94
+ ) {
95
+ return true;
96
+ }
97
+
98
+ return false;
99
+ }