@paypal/checkout-components 5.0.405 → 5.0.406
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/dist/button.js +1 -1
- package/dist/test/button.js +1 -1
- package/package.json +1 -1
- package/src/funding/common.jsx +1 -0
- package/src/funding/content.jsx +9 -0
- package/src/funding/paypal/template.jsx +17 -1
- package/src/types.js +2 -0
- package/src/ui/buttons/button.jsx +12 -1
- package/src/ui/buttons/props.bnpl.test.js +297 -0
- package/src/ui/buttons/props.js +121 -0
- package/src/zoid/buttons/component.jsx +1 -0
package/package.json
CHANGED
package/src/funding/common.jsx
CHANGED
package/src/funding/content.jsx
CHANGED
|
@@ -23,6 +23,7 @@ export type ContentMap = {
|
|
|
23
23
|
Donate?: ({|
|
|
24
24
|
logo: ChildType,
|
|
25
25
|
|}) => ChildType /** Not available in `tr` language **/,
|
|
26
|
+
PayNowOrLater?: ({| logo: ChildType |}) => ChildType,
|
|
26
27
|
|},
|
|
27
28
|
};
|
|
28
29
|
|
|
@@ -379,6 +380,14 @@ export const componentContent: ContentMap = {
|
|
|
379
380
|
</Text>
|
|
380
381
|
</Fragment>
|
|
381
382
|
),
|
|
383
|
+
PayNowOrLater: ({ logo }) => (
|
|
384
|
+
<Fragment>
|
|
385
|
+
{logo}
|
|
386
|
+
<Text animate optional>
|
|
387
|
+
Pay Now or Later
|
|
388
|
+
</Text>
|
|
389
|
+
</Fragment>
|
|
390
|
+
),
|
|
382
391
|
},
|
|
383
392
|
es: {
|
|
384
393
|
Checkout: ({ logo }) => (
|
|
@@ -230,9 +230,25 @@ function ButtonPersonalization(opts: LabelOptions): ?ChildType {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
export function Label(opts: LabelOptions): ChildType {
|
|
233
|
+
const {
|
|
234
|
+
logo,
|
|
235
|
+
locale: { lang },
|
|
236
|
+
shouldApplyPayNowOrLaterLabel,
|
|
237
|
+
} = opts;
|
|
238
|
+
|
|
239
|
+
let buttonLabel = <BasicLabel {...opts} />;
|
|
240
|
+
|
|
241
|
+
if (!__WEB__) {
|
|
242
|
+
const { PayNowOrLater } = componentContent[lang];
|
|
243
|
+
|
|
244
|
+
if (shouldApplyPayNowOrLaterLabel && PayNowOrLater) {
|
|
245
|
+
buttonLabel = <PayNowOrLater logo={logo} />;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
233
249
|
return (
|
|
234
250
|
<Fragment>
|
|
235
|
-
|
|
251
|
+
{buttonLabel}
|
|
236
252
|
<ButtonPersonalization {...opts} />
|
|
237
253
|
</Fragment>
|
|
238
254
|
);
|
package/src/types.js
CHANGED
|
@@ -68,6 +68,8 @@ export type Experiment = {|
|
|
|
68
68
|
venmoEnableWebOnNonNativeBrowser?: boolean,
|
|
69
69
|
spbEagerOrderCreation?: boolean,
|
|
70
70
|
paypalCreditButtonCreateVaultSetupTokenExists?: boolean,
|
|
71
|
+
isPaylaterCobrandedLabelEnabled?: boolean,
|
|
72
|
+
isPaylaterCobrandedLabelRandomizationEnabled?: boolean,
|
|
71
73
|
|};
|
|
72
74
|
|
|
73
75
|
export type Requires = {|
|
|
@@ -109,7 +109,13 @@ export function Button({
|
|
|
109
109
|
const colors = fundingConfig.colors;
|
|
110
110
|
const secondaryColors = fundingConfig.secondaryColors || {};
|
|
111
111
|
|
|
112
|
-
let {
|
|
112
|
+
let {
|
|
113
|
+
color,
|
|
114
|
+
period,
|
|
115
|
+
label,
|
|
116
|
+
shouldApplyRebrandedStyles,
|
|
117
|
+
shouldApplyPayNowOrLaterLabel,
|
|
118
|
+
} = style;
|
|
113
119
|
|
|
114
120
|
// if no color option is passed in via style props
|
|
115
121
|
if (color === "" || typeof color === "undefined") {
|
|
@@ -189,6 +195,10 @@ export function Button({
|
|
|
189
195
|
})
|
|
190
196
|
: fundingConfig.labelText || fundingSource;
|
|
191
197
|
|
|
198
|
+
if (shouldApplyPayNowOrLaterLabel) {
|
|
199
|
+
labelText = "PayPal Pay Now or Later";
|
|
200
|
+
}
|
|
201
|
+
|
|
192
202
|
if (!showPayLabel && instrument?.vendor && instrument.label) {
|
|
193
203
|
labelText = instrument.secondaryInstruments
|
|
194
204
|
? `${instrument.secondaryInstruments[0].type} & ${instrument.vendor} ${instrument.label}`
|
|
@@ -234,6 +244,7 @@ export function Button({
|
|
|
234
244
|
tagline={tagline}
|
|
235
245
|
content={content}
|
|
236
246
|
experiment={experiment}
|
|
247
|
+
shouldApplyPayNowOrLaterLabel={shouldApplyPayNowOrLaterLabel}
|
|
237
248
|
/>
|
|
238
249
|
);
|
|
239
250
|
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import { describe, expect, it, beforeEach, vi } from "vitest";
|
|
3
|
+
import { FUNDING } from "@paypal/sdk-constants";
|
|
4
|
+
|
|
5
|
+
import { BUTTON_FLOW } from "../../constants";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getCobrandedBNPLLabelFlags,
|
|
9
|
+
getBNPLLabelABTestFromStorage,
|
|
10
|
+
determineRandomBNPLLabel,
|
|
11
|
+
getBNPLLabelForABTest,
|
|
12
|
+
} from "./props";
|
|
13
|
+
|
|
14
|
+
describe("getBNPLLabelABTestFromStorage", () => {
|
|
15
|
+
it("should return null when storage state has no bnplLabelABTest value", () => {
|
|
16
|
+
const storageState = {
|
|
17
|
+
get: vi.fn().mockReturnValue(null),
|
|
18
|
+
set: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const result = getBNPLLabelABTestFromStorage(storageState);
|
|
22
|
+
|
|
23
|
+
expect(result).toBeNull();
|
|
24
|
+
expect(storageState.get).toHaveBeenCalledWith("bnplLabelABTest");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should return null when storage state has bnplLabelABTest but no value property", () => {
|
|
28
|
+
const storageState = {
|
|
29
|
+
get: vi.fn().mockReturnValue({ someOtherProperty: "test" }),
|
|
30
|
+
set: vi.fn(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const result = getBNPLLabelABTestFromStorage(storageState);
|
|
34
|
+
|
|
35
|
+
expect(result).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return value when storage state has bnplLabelABTest with value property", () => {
|
|
39
|
+
const mockStoredValue = {
|
|
40
|
+
shouldApplyPayNowOrLaterLabel: true,
|
|
41
|
+
sessionID: "test-session",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const storageState = {
|
|
45
|
+
get: vi.fn().mockReturnValue({ value: mockStoredValue }),
|
|
46
|
+
set: vi.fn(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const result = getBNPLLabelABTestFromStorage(storageState);
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual(mockStoredValue);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("determineRandomBNPLLabel", () => {
|
|
56
|
+
let mathRandomSpy;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
mathRandomSpy = vi.spyOn(Math, "random");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
mathRandomSpy.mockRestore();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should return true when random value is less than 0.5", () => {
|
|
67
|
+
mathRandomSpy.mockReturnValue(0.3);
|
|
68
|
+
expect(determineRandomBNPLLabel()).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should return false when random value is greater than or equal to 0.5", () => {
|
|
72
|
+
mathRandomSpy.mockReturnValue(0.7);
|
|
73
|
+
expect(determineRandomBNPLLabel()).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should return false when random value is exactly 0.5", () => {
|
|
77
|
+
mathRandomSpy.mockReturnValue(0.5);
|
|
78
|
+
expect(determineRandomBNPLLabel()).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("getBNPLLabelForABTest", () => {
|
|
83
|
+
it("should return cached value when sessionID matches", () => {
|
|
84
|
+
const mockSessionID = "test-session-123";
|
|
85
|
+
const storageState = {
|
|
86
|
+
get: vi.fn().mockReturnValue({
|
|
87
|
+
value: {
|
|
88
|
+
shouldApplyPayNowOrLaterLabel: true,
|
|
89
|
+
sessionID: mockSessionID,
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
set: vi.fn(),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const result = getBNPLLabelForABTest({
|
|
96
|
+
storageState,
|
|
97
|
+
sessionID: mockSessionID,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(result).toBe(true);
|
|
101
|
+
expect(storageState.set).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should randomize and store when sessionID does not match", () => {
|
|
105
|
+
const storageState = {
|
|
106
|
+
get: vi.fn().mockReturnValue({
|
|
107
|
+
value: {
|
|
108
|
+
shouldApplyPayNowOrLaterLabel: true,
|
|
109
|
+
sessionID: "old-session",
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
set: vi.fn(),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const result = getBNPLLabelForABTest({
|
|
116
|
+
storageState,
|
|
117
|
+
sessionID: "new-session",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(typeof result).toBe("boolean");
|
|
121
|
+
expect(storageState.set).toHaveBeenCalledWith("bnplLabelABTest", {
|
|
122
|
+
shouldApplyPayNowOrLaterLabel: result,
|
|
123
|
+
sessionID: "new-session",
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should randomize and store when storage is empty", () => {
|
|
128
|
+
const storageState = {
|
|
129
|
+
get: vi.fn().mockReturnValue(null),
|
|
130
|
+
set: vi.fn(),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const result = getBNPLLabelForABTest({
|
|
134
|
+
storageState,
|
|
135
|
+
sessionID: "fresh-session",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(typeof result).toBe("boolean");
|
|
139
|
+
expect(storageState.set).toHaveBeenCalledWith("bnplLabelABTest", {
|
|
140
|
+
shouldApplyPayNowOrLaterLabel: result,
|
|
141
|
+
sessionID: "fresh-session",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("getCobrandedBNPLLabelFlags", () => {
|
|
147
|
+
const mockStorageState = {
|
|
148
|
+
get: vi.fn().mockReturnValue(null),
|
|
149
|
+
set: vi.fn(),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
mockStorageState.get.mockReturnValue(null);
|
|
154
|
+
mockStorageState.set.mockClear();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// $FlowFixMe - test object intentionally omits non-relevant ButtonPropsInputs fields
|
|
158
|
+
const eligibleProps = {
|
|
159
|
+
fundingSource: FUNDING.PAYPAL,
|
|
160
|
+
fundingEligibility: {
|
|
161
|
+
paylater: { eligible: true },
|
|
162
|
+
},
|
|
163
|
+
experiment: { isPaylaterCobrandedLabelEnabled: true },
|
|
164
|
+
locale: { lang: "en", country: "US" },
|
|
165
|
+
style: {},
|
|
166
|
+
flow: BUTTON_FLOW.PURCHASE,
|
|
167
|
+
storageState: mockStorageState,
|
|
168
|
+
sessionID: "test-session-id",
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
it("should return eligible true and use randomization when all conditions are met", () => {
|
|
172
|
+
const { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel } =
|
|
173
|
+
// $FlowFixMe
|
|
174
|
+
getCobrandedBNPLLabelFlags(eligibleProps);
|
|
175
|
+
|
|
176
|
+
expect(isPayNowOrLaterLabelEligible).toBe(true);
|
|
177
|
+
expect(typeof shouldApplyPayNowOrLaterLabel).toBe("boolean");
|
|
178
|
+
expect(mockStorageState.set).toHaveBeenCalledWith(
|
|
179
|
+
"bnplLabelABTest",
|
|
180
|
+
expect.objectContaining({
|
|
181
|
+
shouldApplyPayNowOrLaterLabel,
|
|
182
|
+
sessionID: "test-session-id",
|
|
183
|
+
})
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should return false when experiment flag is disabled", () => {
|
|
188
|
+
const { isPayNowOrLaterLabelEligible } =
|
|
189
|
+
// $FlowFixMe
|
|
190
|
+
getCobrandedBNPLLabelFlags({
|
|
191
|
+
...eligibleProps,
|
|
192
|
+
experiment: { isPaylaterCobrandedLabelEnabled: false },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should return false when paylater is not eligible", () => {
|
|
199
|
+
const { isPayNowOrLaterLabelEligible } =
|
|
200
|
+
// $FlowFixMe
|
|
201
|
+
getCobrandedBNPLLabelFlags({
|
|
202
|
+
...eligibleProps,
|
|
203
|
+
fundingEligibility: { paylater: { eligible: false } },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should return false when fundingSource is not PAYPAL or undefined", () => {
|
|
210
|
+
const { isPayNowOrLaterLabelEligible } =
|
|
211
|
+
// $FlowFixMe
|
|
212
|
+
getCobrandedBNPLLabelFlags({
|
|
213
|
+
...eligibleProps,
|
|
214
|
+
fundingSource: FUNDING.VENMO,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("should return false when a non-paypal label is set", () => {
|
|
221
|
+
const { isPayNowOrLaterLabelEligible } =
|
|
222
|
+
// $FlowFixMe
|
|
223
|
+
getCobrandedBNPLLabelFlags({
|
|
224
|
+
...eligibleProps,
|
|
225
|
+
style: { label: "checkout" },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should return true when label is explicitly set to paypal", () => {
|
|
232
|
+
const { isPayNowOrLaterLabelEligible } =
|
|
233
|
+
// $FlowFixMe
|
|
234
|
+
getCobrandedBNPLLabelFlags({
|
|
235
|
+
...eligibleProps,
|
|
236
|
+
style: { label: "paypal" },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
expect(isPayNowOrLaterLabelEligible).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("should return false when locale does not have PayNowOrLater content", () => {
|
|
243
|
+
const { isPayNowOrLaterLabelEligible } =
|
|
244
|
+
// $FlowFixMe
|
|
245
|
+
getCobrandedBNPLLabelFlags({
|
|
246
|
+
...eligibleProps,
|
|
247
|
+
locale: { lang: "fr", country: "FR" },
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should return false when props is null", () => {
|
|
254
|
+
const { isPayNowOrLaterLabelEligible } = getCobrandedBNPLLabelFlags(null);
|
|
255
|
+
|
|
256
|
+
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should return shouldApplyPayNowOrLaterLabel true when randomization is disabled", () => {
|
|
260
|
+
const { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel } =
|
|
261
|
+
// $FlowFixMe
|
|
262
|
+
getCobrandedBNPLLabelFlags({
|
|
263
|
+
...eligibleProps,
|
|
264
|
+
experiment: {
|
|
265
|
+
isPaylaterCobrandedLabelEnabled: true,
|
|
266
|
+
isPaylaterCobrandedLabelRandomizationEnabled: false,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(isPayNowOrLaterLabelEligible).toBe(true);
|
|
271
|
+
expect(shouldApplyPayNowOrLaterLabel).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should return shouldApplyPayNowOrLaterLabel true when eligible but no storageState", () => {
|
|
275
|
+
const { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel } =
|
|
276
|
+
// $FlowFixMe
|
|
277
|
+
getCobrandedBNPLLabelFlags({
|
|
278
|
+
...eligibleProps,
|
|
279
|
+
storageState: undefined,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(isPayNowOrLaterLabelEligible).toBe(true);
|
|
283
|
+
expect(shouldApplyPayNowOrLaterLabel).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should return shouldApplyPayNowOrLaterLabel false when not eligible regardless of randomization", () => {
|
|
287
|
+
const { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel } =
|
|
288
|
+
// $FlowFixMe
|
|
289
|
+
getCobrandedBNPLLabelFlags({
|
|
290
|
+
...eligibleProps,
|
|
291
|
+
fundingSource: FUNDING.VENMO,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
295
|
+
expect(shouldApplyPayNowOrLaterLabel).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
});
|
package/src/ui/buttons/props.js
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
MESSAGE_ALIGN,
|
|
49
49
|
} from "../../constants";
|
|
50
50
|
import { getFundingConfig, isFundingEligible } from "../../funding";
|
|
51
|
+
import { componentContent } from "../../funding/content";
|
|
51
52
|
import type { StateGetSet } from "../../lib/session";
|
|
52
53
|
|
|
53
54
|
import { BUTTON_SIZE_STYLE } from "./config";
|
|
@@ -331,6 +332,8 @@ export type ButtonStyle = {|
|
|
|
331
332
|
borderRadius?: number,
|
|
332
333
|
shouldApplyRebrandedStyles: boolean,
|
|
333
334
|
isButtonColorABTestMerchant: boolean,
|
|
335
|
+
isPayNowOrLaterLabelEligible: boolean,
|
|
336
|
+
shouldApplyPayNowOrLaterLabel: boolean,
|
|
334
337
|
|};
|
|
335
338
|
|
|
336
339
|
export type ButtonStyleInputs = {|
|
|
@@ -344,6 +347,7 @@ export type ButtonStyleInputs = {|
|
|
|
344
347
|
disableMaxWidth?: boolean | void,
|
|
345
348
|
disableMaxHeight?: boolean | void,
|
|
346
349
|
borderRadius?: number | void,
|
|
350
|
+
shouldApplyPayNowOrLaterLabel?: boolean | void,
|
|
347
351
|
|};
|
|
348
352
|
|
|
349
353
|
type PersonalizationComponentProps = {|
|
|
@@ -529,6 +533,11 @@ type ColorABTestStorage = {|
|
|
|
529
533
|
sessionID: string,
|
|
530
534
|
|};
|
|
531
535
|
|
|
536
|
+
type BNPLLabelABTestStorage = {|
|
|
537
|
+
shouldApplyPayNowOrLaterLabel: boolean,
|
|
538
|
+
sessionID: string,
|
|
539
|
+
|};
|
|
540
|
+
|
|
532
541
|
type GetButtonColorArgs = {|
|
|
533
542
|
experiment: Experiment,
|
|
534
543
|
fundingSource: ?$Values<typeof FUNDING>,
|
|
@@ -695,6 +704,7 @@ export type ButtonPropsInputs = {
|
|
|
695
704
|
messageMarkup?: string | void,
|
|
696
705
|
renderedButtons: $ReadOnlyArray<$Values<typeof FUNDING>>,
|
|
697
706
|
buttonColor: ButtonColor,
|
|
707
|
+
storageState?: StateGetSet,
|
|
698
708
|
userAgent: string,
|
|
699
709
|
};
|
|
700
710
|
|
|
@@ -728,6 +738,48 @@ export function getColorABTestFromStorage(
|
|
|
728
738
|
return null;
|
|
729
739
|
}
|
|
730
740
|
|
|
741
|
+
export function getBNPLLabelABTestFromStorage(
|
|
742
|
+
storageState: StateGetSet
|
|
743
|
+
): ?BNPLLabelABTestStorage {
|
|
744
|
+
const sessionState = storageState.get("bnplLabelABTest");
|
|
745
|
+
|
|
746
|
+
if (sessionState && sessionState.value) {
|
|
747
|
+
return sessionState.value;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export function determineRandomBNPLLabel(): boolean {
|
|
754
|
+
return Math.random() < 0.5;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function getBNPLLabelForABTest({
|
|
758
|
+
storageState,
|
|
759
|
+
sessionID,
|
|
760
|
+
}: {|
|
|
761
|
+
storageState: StateGetSet,
|
|
762
|
+
sessionID: ?string,
|
|
763
|
+
|}): boolean {
|
|
764
|
+
const bnplLabelFromStorage = getBNPLLabelABTestFromStorage(storageState);
|
|
765
|
+
|
|
766
|
+
if (bnplLabelFromStorage) {
|
|
767
|
+
const { sessionID: storedSessionID, shouldApplyPayNowOrLaterLabel } =
|
|
768
|
+
bnplLabelFromStorage;
|
|
769
|
+
|
|
770
|
+
if (storedSessionID && sessionID === storedSessionID) {
|
|
771
|
+
return shouldApplyPayNowOrLaterLabel;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const shouldApplyPayNowOrLaterLabel = determineRandomBNPLLabel();
|
|
776
|
+
storageState.set("bnplLabelABTest", {
|
|
777
|
+
shouldApplyPayNowOrLaterLabel,
|
|
778
|
+
sessionID,
|
|
779
|
+
});
|
|
780
|
+
return shouldApplyPayNowOrLaterLabel;
|
|
781
|
+
}
|
|
782
|
+
|
|
731
783
|
export function determineRandomButtonColor({
|
|
732
784
|
buttonColorInput,
|
|
733
785
|
}: {|
|
|
@@ -970,6 +1022,70 @@ export function getButtonColor({
|
|
|
970
1022
|
}
|
|
971
1023
|
}
|
|
972
1024
|
|
|
1025
|
+
export function getCobrandedBNPLLabelFlags(props: ?ButtonPropsInputs): {|
|
|
1026
|
+
isPayNowOrLaterLabelEligible: boolean,
|
|
1027
|
+
shouldApplyPayNowOrLaterLabel: boolean,
|
|
1028
|
+
|} {
|
|
1029
|
+
const label = props?.style?.label;
|
|
1030
|
+
const lang = props?.locale?.lang;
|
|
1031
|
+
const isPurchaseFlow = props?.flow === BUTTON_FLOW.PURCHASE;
|
|
1032
|
+
const isEnLang = Boolean(lang && componentContent[lang]?.PayNowOrLater);
|
|
1033
|
+
const isCobrandedEligibleFundingSource =
|
|
1034
|
+
props?.fundingSource === FUNDING.PAYPAL ||
|
|
1035
|
+
props?.fundingSource === undefined;
|
|
1036
|
+
const isPaylaterEligible =
|
|
1037
|
+
props?.fundingEligibility?.paylater?.eligible || false;
|
|
1038
|
+
const isLabelEligible = label === undefined || label === BUTTON_LABEL.PAYPAL;
|
|
1039
|
+
|
|
1040
|
+
const isPaylaterCobrandedLabelEnabled =
|
|
1041
|
+
props?.experiment?.isPaylaterCobrandedLabelEnabled || false;
|
|
1042
|
+
|
|
1043
|
+
const isPayNowOrLaterLabelEligible = Boolean(
|
|
1044
|
+
isPaylaterCobrandedLabelEnabled &&
|
|
1045
|
+
isCobrandedEligibleFundingSource &&
|
|
1046
|
+
isPaylaterEligible &&
|
|
1047
|
+
isLabelEligible &&
|
|
1048
|
+
isEnLang &&
|
|
1049
|
+
isPurchaseFlow
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
const isPaylaterCobrandedLabelRandomizationEnabled =
|
|
1053
|
+
props?.experiment?.isPaylaterCobrandedLabelRandomizationEnabled ?? true;
|
|
1054
|
+
const hasStorageState = Boolean(props?.storageState);
|
|
1055
|
+
const hasSessionID = Boolean(props?.sessionID);
|
|
1056
|
+
const shouldRunABTestRandomization =
|
|
1057
|
+
isPaylaterCobrandedLabelRandomizationEnabled &&
|
|
1058
|
+
hasStorageState &&
|
|
1059
|
+
hasSessionID;
|
|
1060
|
+
|
|
1061
|
+
// SSR path: the client already computed values
|
|
1062
|
+
const precomputedLabel = props?.style?.shouldApplyPayNowOrLaterLabel;
|
|
1063
|
+
|
|
1064
|
+
if (precomputedLabel === true || precomputedLabel === false) {
|
|
1065
|
+
return {
|
|
1066
|
+
isPayNowOrLaterLabelEligible,
|
|
1067
|
+
shouldApplyPayNowOrLaterLabel: precomputedLabel,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Client path: compute shouldApplyPayNowOrLaterLabel from scratch
|
|
1072
|
+
let shouldApplyPayNowOrLaterLabel = false;
|
|
1073
|
+
|
|
1074
|
+
if (isPayNowOrLaterLabelEligible) {
|
|
1075
|
+
if (shouldRunABTestRandomization && props && props.storageState) {
|
|
1076
|
+
shouldApplyPayNowOrLaterLabel = getBNPLLabelForABTest({
|
|
1077
|
+
storageState: props.storageState,
|
|
1078
|
+
sessionID: props.sessionID,
|
|
1079
|
+
});
|
|
1080
|
+
} else {
|
|
1081
|
+
// Randomization disabled or storageState unavailable → 100% treatment
|
|
1082
|
+
shouldApplyPayNowOrLaterLabel = true;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
return { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel };
|
|
1087
|
+
}
|
|
1088
|
+
|
|
973
1089
|
const getDefaultButtonPropsInput = (): ButtonPropsInputs => {
|
|
974
1090
|
return {};
|
|
975
1091
|
};
|
|
@@ -1119,6 +1235,9 @@ export function normalizeButtonStyle(
|
|
|
1119
1235
|
}
|
|
1120
1236
|
}
|
|
1121
1237
|
|
|
1238
|
+
const { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel } =
|
|
1239
|
+
getCobrandedBNPLLabelFlags(props);
|
|
1240
|
+
|
|
1122
1241
|
return {
|
|
1123
1242
|
label,
|
|
1124
1243
|
layout,
|
|
@@ -1133,6 +1252,8 @@ export function normalizeButtonStyle(
|
|
|
1133
1252
|
borderRadius,
|
|
1134
1253
|
shouldApplyRebrandedStyles,
|
|
1135
1254
|
isButtonColorABTestMerchant,
|
|
1255
|
+
isPayNowOrLaterLabelEligible,
|
|
1256
|
+
shouldApplyPayNowOrLaterLabel,
|
|
1136
1257
|
};
|
|
1137
1258
|
}
|
|
1138
1259
|
|