@paypal/checkout-components 5.0.405-alpha-c03e09a.0 → 5.0.405
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 +0 -1
- package/src/funding/content.jsx +0 -9
- package/src/funding/paypal/template.jsx +1 -17
- package/src/lib/appSwitchResume.js +78 -23
- package/src/lib/appSwithResume.test.js +332 -3
- package/src/types.js +0 -1
- package/src/ui/buttons/button.jsx +1 -12
- package/src/ui/buttons/props.js +0 -58
- package/src/ui/buttons/props.test.js +1 -97
- package/src/zoid/qr-code/component.test.js +46 -0
- package/src/zoid/qr-code/container.jsx +2 -2
- package/src/zoid/qr-code/container.test.jsx +187 -0
- package/src/zoid/qr-code/prerender.jsx +5 -5
- package/src/zoid/qr-code/prerender.test.jsx +108 -0
package/package.json
CHANGED
package/src/funding/common.jsx
CHANGED
package/src/funding/content.jsx
CHANGED
|
@@ -23,7 +23,6 @@ export type ContentMap = {
|
|
|
23
23
|
Donate?: ({|
|
|
24
24
|
logo: ChildType,
|
|
25
25
|
|}) => ChildType /** Not available in `tr` language **/,
|
|
26
|
-
PayNowOrLater?: ({| logo: ChildType |}) => ChildType,
|
|
27
26
|
|},
|
|
28
27
|
};
|
|
29
28
|
|
|
@@ -380,14 +379,6 @@ export const componentContent: ContentMap = {
|
|
|
380
379
|
</Text>
|
|
381
380
|
</Fragment>
|
|
382
381
|
),
|
|
383
|
-
PayNowOrLater: ({ logo }) => (
|
|
384
|
-
<Fragment>
|
|
385
|
-
{logo}
|
|
386
|
-
<Text animate optional>
|
|
387
|
-
Pay Now or Later
|
|
388
|
-
</Text>
|
|
389
|
-
</Fragment>
|
|
390
|
-
),
|
|
391
382
|
},
|
|
392
383
|
es: {
|
|
393
384
|
Checkout: ({ logo }) => (
|
|
@@ -230,25 +230,9 @@ 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
|
-
|
|
249
233
|
return (
|
|
250
234
|
<Fragment>
|
|
251
|
-
{
|
|
235
|
+
<BasicLabel {...opts} />
|
|
252
236
|
<ButtonPersonalization {...opts} />
|
|
253
237
|
</Fragment>
|
|
254
238
|
);
|
|
@@ -16,13 +16,88 @@ export type AppSwitchResumeParams = {|
|
|
|
16
16
|
checkoutState: "onApprove" | "onCancel" | "onError",
|
|
17
17
|
|};
|
|
18
18
|
|
|
19
|
+
// When the merchant's return_url contains a hash fragment (e.g. /checkout/#payment),
|
|
20
|
+
// PayPal params (token, PayerID) end up inside the hash as /checkout/#payment?token=...&PayerID=...
|
|
21
|
+
// This helper splits the hash into its name and query-string parts,
|
|
22
|
+
// checking for a known app switch action before &, then falling back to ? and &.
|
|
23
|
+
function parseHashFragment(): {| hash: string, queryString: string |} {
|
|
24
|
+
const hashString =
|
|
25
|
+
window.location.hash && String(window.location.hash).slice(1);
|
|
26
|
+
if (!hashString) {
|
|
27
|
+
return { hash: "", queryString: "" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ampersandIndex = hashString.indexOf("&");
|
|
31
|
+
|
|
32
|
+
// If & exists and the segment before it is a known app switch action
|
|
33
|
+
// (e.g. #onApprove&token=...&hash?param=val), split on &.
|
|
34
|
+
// This handles native app returns where the action is &-delimited
|
|
35
|
+
// and the merchant's original hash contained a ?.
|
|
36
|
+
if (ampersandIndex !== -1) {
|
|
37
|
+
const possibleAction = hashString.slice(0, ampersandIndex);
|
|
38
|
+
if (
|
|
39
|
+
[
|
|
40
|
+
APP_SWITCH_RETURN_HASH.ONAPPROVE,
|
|
41
|
+
APP_SWITCH_RETURN_HASH.ONCANCEL,
|
|
42
|
+
APP_SWITCH_RETURN_HASH.ONERROR,
|
|
43
|
+
].includes(possibleAction)
|
|
44
|
+
) {
|
|
45
|
+
return {
|
|
46
|
+
hash: possibleAction,
|
|
47
|
+
queryString: hashString.slice(ampersandIndex + 1),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check for ? delimiter (e.g. #payment?token=...)
|
|
53
|
+
const questionMarkIndex = hashString.indexOf("?");
|
|
54
|
+
if (questionMarkIndex !== -1) {
|
|
55
|
+
return {
|
|
56
|
+
hash: hashString.slice(0, questionMarkIndex),
|
|
57
|
+
queryString: hashString.slice(questionMarkIndex + 1),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Fallback to & delimiter (e.g. #payment&token=...)
|
|
62
|
+
if (ampersandIndex !== -1) {
|
|
63
|
+
return {
|
|
64
|
+
hash: hashString.slice(0, ampersandIndex),
|
|
65
|
+
queryString: hashString.slice(ampersandIndex + 1),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { hash: hashString, queryString: "" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getParamsFromHashFragment(): { [string]: string } {
|
|
73
|
+
const { queryString } = parseHashFragment();
|
|
74
|
+
if (!queryString) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
// eslint-disable-next-line compat/compat
|
|
78
|
+
return Object.fromEntries(new URLSearchParams(queryString));
|
|
79
|
+
}
|
|
80
|
+
|
|
19
81
|
// The Web fallback flow uses different set of query params then appswitch flow.
|
|
20
82
|
function getAppSwitchParamsWebFallback(): AppSwitchResumeParams | null {
|
|
21
83
|
try {
|
|
22
|
-
const
|
|
84
|
+
const searchParams = Object.fromEntries(
|
|
23
85
|
// eslint-disable-next-line compat/compat
|
|
24
86
|
new URLSearchParams(window.location.search)
|
|
25
87
|
);
|
|
88
|
+
|
|
89
|
+
// If no PayPal params found in query string, check if they are embedded
|
|
90
|
+
// inside the hash fragment. This happens when the merchant's return_url
|
|
91
|
+
// contains a hash (e.g. /checkout/#payment) and PayPal params were appended
|
|
92
|
+
// after the fragment: /checkout/#payment?token=...&PayerID=...
|
|
93
|
+
const params =
|
|
94
|
+
searchParams.token ||
|
|
95
|
+
searchParams.vaultSetupToken ||
|
|
96
|
+
searchParams.approval_token_id ||
|
|
97
|
+
searchParams.approval_session_id
|
|
98
|
+
? searchParams
|
|
99
|
+
: { ...getParamsFromHashFragment(), ...searchParams };
|
|
100
|
+
|
|
26
101
|
const {
|
|
27
102
|
button_session_id: buttonSessionID,
|
|
28
103
|
fundingSource,
|
|
@@ -57,31 +132,11 @@ function getAppSwitchParamsWebFallback(): AppSwitchResumeParams | null {
|
|
|
57
132
|
}
|
|
58
133
|
|
|
59
134
|
export function getAppSwitchResumeParams(): AppSwitchResumeParams | null {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
if (!hashString) {
|
|
135
|
+
const { hash, queryString } = parseHashFragment();
|
|
136
|
+
if (!hash) {
|
|
63
137
|
return getAppSwitchParamsWebFallback();
|
|
64
138
|
}
|
|
65
139
|
|
|
66
|
-
let hash = "";
|
|
67
|
-
let queryString = "";
|
|
68
|
-
|
|
69
|
-
// first check for ? as the hash/query separator
|
|
70
|
-
const questionMarkIndex = hashString.indexOf("?");
|
|
71
|
-
|
|
72
|
-
if (questionMarkIndex !== -1) {
|
|
73
|
-
[hash, queryString] = hashString.split("?");
|
|
74
|
-
} else {
|
|
75
|
-
const ampersandIndex = hashString.indexOf("&");
|
|
76
|
-
|
|
77
|
-
if (ampersandIndex !== -1) {
|
|
78
|
-
hash = hashString.slice(0, ampersandIndex);
|
|
79
|
-
queryString = hashString.slice(ampersandIndex + 1);
|
|
80
|
-
} else {
|
|
81
|
-
hash = hashString;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
140
|
const isPostApprovalAction = [
|
|
86
141
|
APP_SWITCH_RETURN_HASH.ONAPPROVE,
|
|
87
142
|
APP_SWITCH_RETURN_HASH.ONCANCEL,
|
|
@@ -70,16 +70,24 @@ describe("app switch resume flow", () => {
|
|
|
70
70
|
expect(isAppSwitchResumeFlow()).toEqual(false);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
test("should
|
|
73
|
+
test("should extract resume params from hash with non-action prefix via web fallback", () => {
|
|
74
|
+
// When hash is not a known action (e.g. #Unknown) but contains PayPal params,
|
|
75
|
+
// the web fallback should extract them from the hash fragment.
|
|
74
76
|
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
75
77
|
hash: `#Unknown?button_session_id=${buttonSessionID}&token=${orderID}&fundingSource=${fundingSource}`,
|
|
78
|
+
search: "",
|
|
76
79
|
});
|
|
77
80
|
|
|
78
81
|
const params = getAppSwitchResumeParams();
|
|
79
82
|
|
|
80
83
|
expect.assertions(2);
|
|
81
|
-
expect(params).toEqual(
|
|
82
|
-
|
|
84
|
+
expect(params).toEqual({
|
|
85
|
+
buttonSessionID,
|
|
86
|
+
checkoutState: "onCancel",
|
|
87
|
+
fundingSource,
|
|
88
|
+
orderID,
|
|
89
|
+
});
|
|
90
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
83
91
|
});
|
|
84
92
|
|
|
85
93
|
test("should test fetching multiple resume params when parameters are correctly passed", () => {
|
|
@@ -140,4 +148,325 @@ describe("app switch resume flow", () => {
|
|
|
140
148
|
});
|
|
141
149
|
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
142
150
|
});
|
|
151
|
+
|
|
152
|
+
test("should extract resume params when merchant return_url has hash fragment with ? delimiter", () => {
|
|
153
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
154
|
+
hash: `#payment?button_session_id=${buttonSessionID}&token=${orderID}&fundingSource=${fundingSource}&PayerID=PP-payer-122`,
|
|
155
|
+
search: "",
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const params = getAppSwitchResumeParams();
|
|
159
|
+
|
|
160
|
+
expect(params).toEqual({
|
|
161
|
+
buttonSessionID,
|
|
162
|
+
checkoutState: "onApprove",
|
|
163
|
+
fundingSource,
|
|
164
|
+
orderID,
|
|
165
|
+
payerID: "PP-payer-122",
|
|
166
|
+
});
|
|
167
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("should extract resume params when merchant return_url has hash fragment without PayerID (cancel)", () => {
|
|
171
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
172
|
+
hash: `#payment?button_session_id=${buttonSessionID}&token=${orderID}&fundingSource=${fundingSource}`,
|
|
173
|
+
search: "",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const params = getAppSwitchResumeParams();
|
|
177
|
+
|
|
178
|
+
expect(params).toEqual({
|
|
179
|
+
buttonSessionID,
|
|
180
|
+
checkoutState: "onCancel",
|
|
181
|
+
fundingSource,
|
|
182
|
+
orderID,
|
|
183
|
+
});
|
|
184
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("should extract resume params when hash uses & delimiter instead of ?", () => {
|
|
188
|
+
// Real-world case: URL like /ppcp-js-sdk?clientSideDelay=0#payment&token=...&PayerID=...
|
|
189
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
190
|
+
hash: `#payment&token=${orderID}&PayerID=PP-payer-122&button_session_id=${buttonSessionID}`,
|
|
191
|
+
search: "?clientSideDelay=0&serverSideDelay=0",
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const params = getAppSwitchResumeParams();
|
|
195
|
+
|
|
196
|
+
expect(params).toEqual({
|
|
197
|
+
buttonSessionID,
|
|
198
|
+
checkoutState: "onApprove",
|
|
199
|
+
orderID,
|
|
200
|
+
payerID: "PP-payer-122",
|
|
201
|
+
});
|
|
202
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("should extract vault resume params from hash fragment", () => {
|
|
206
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
207
|
+
hash: `#/step3?button_session_id=${buttonSessionID}&token=${orderID}&approval_token_id=VA-3`,
|
|
208
|
+
search: "",
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const params = getAppSwitchResumeParams();
|
|
212
|
+
|
|
213
|
+
expect(params).toEqual({
|
|
214
|
+
buttonSessionID,
|
|
215
|
+
checkoutState: "onCancel",
|
|
216
|
+
orderID,
|
|
217
|
+
vaultSetupToken: "VA-3",
|
|
218
|
+
});
|
|
219
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("should prefer search params over hash params when both exist", () => {
|
|
223
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
224
|
+
hash: `#payment?token=WRONG-TOKEN`,
|
|
225
|
+
search: `?button_session_id=${buttonSessionID}&token=${orderID}&PayerID=PP-123`,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const params = getAppSwitchResumeParams();
|
|
229
|
+
|
|
230
|
+
expect(params).toEqual({
|
|
231
|
+
buttonSessionID,
|
|
232
|
+
checkoutState: "onApprove",
|
|
233
|
+
orderID,
|
|
234
|
+
payerID: "PP-123",
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("should return null when hash has merchant fragment but no PayPal params", () => {
|
|
239
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
240
|
+
hash: "#payment",
|
|
241
|
+
search: "",
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const params = getAppSwitchResumeParams();
|
|
245
|
+
|
|
246
|
+
expect(params).toEqual(null);
|
|
247
|
+
expect(isAppSwitchResumeFlow()).toEqual(false);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("should return null when hash has merchant fragment with unrelated params", () => {
|
|
251
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
252
|
+
hash: "#/checkout?step=review&cart=abc123",
|
|
253
|
+
search: "",
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const params = getAppSwitchResumeParams();
|
|
257
|
+
|
|
258
|
+
expect(params).toEqual(null);
|
|
259
|
+
expect(isAppSwitchResumeFlow()).toEqual(false);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("should handle vaultSetupToken in search params", () => {
|
|
263
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
264
|
+
hash: "",
|
|
265
|
+
search: `?button_session_id=${buttonSessionID}&vaultSetupToken=VA-123&PayerID=PP-456`,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const params = getAppSwitchResumeParams();
|
|
269
|
+
|
|
270
|
+
expect(params).toEqual({
|
|
271
|
+
buttonSessionID,
|
|
272
|
+
checkoutState: "onApprove",
|
|
273
|
+
payerID: "PP-456",
|
|
274
|
+
vaultSetupToken: "VA-123",
|
|
275
|
+
});
|
|
276
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("should handle approval_token_id in search params", () => {
|
|
280
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
281
|
+
hash: "",
|
|
282
|
+
search: `?button_session_id=${buttonSessionID}&approval_token_id=AT-789`,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const params = getAppSwitchResumeParams();
|
|
286
|
+
|
|
287
|
+
expect(params).toEqual({
|
|
288
|
+
buttonSessionID,
|
|
289
|
+
checkoutState: "onCancel",
|
|
290
|
+
vaultSetupToken: "AT-789",
|
|
291
|
+
});
|
|
292
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("should handle approval_session_id in search params", () => {
|
|
296
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
297
|
+
hash: "",
|
|
298
|
+
search: `?button_session_id=${buttonSessionID}&approval_session_id=AS-999`,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const params = getAppSwitchResumeParams();
|
|
302
|
+
|
|
303
|
+
expect(params).toEqual({
|
|
304
|
+
buttonSessionID,
|
|
305
|
+
checkoutState: "onCancel",
|
|
306
|
+
vaultSetupToken: "AS-999",
|
|
307
|
+
});
|
|
308
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe("hash URL variations", () => {
|
|
312
|
+
// Tests for all supported hash URL formats that the web fallback must handle.
|
|
313
|
+
// PayPal appends params to the URL after app switch; the exact position depends
|
|
314
|
+
// on the merchant's return_url structure.
|
|
315
|
+
|
|
316
|
+
test("#hash - plain hash with no params returns null", () => {
|
|
317
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
318
|
+
hash: "#hash",
|
|
319
|
+
search: "",
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const params = getAppSwitchResumeParams();
|
|
323
|
+
|
|
324
|
+
expect(params).toEqual(null);
|
|
325
|
+
expect(isAppSwitchResumeFlow()).toEqual(false);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("?query=param#hash - PayPal params in search, merchant hash fragment", () => {
|
|
329
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
330
|
+
hash: "#hash",
|
|
331
|
+
search: `?button_session_id=${buttonSessionID}&token=${orderID}&PayerID=PP-payer-122`,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const params = getAppSwitchResumeParams();
|
|
335
|
+
|
|
336
|
+
expect(params).toEqual({
|
|
337
|
+
buttonSessionID,
|
|
338
|
+
checkoutState: "onApprove",
|
|
339
|
+
orderID,
|
|
340
|
+
payerID: "PP-payer-122",
|
|
341
|
+
});
|
|
342
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("#hash?query=param - PayPal params embedded in hash after ?", () => {
|
|
346
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
347
|
+
hash: `#hash?button_session_id=${buttonSessionID}&token=${orderID}&PayerID=PP-payer-122`,
|
|
348
|
+
search: "",
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const params = getAppSwitchResumeParams();
|
|
352
|
+
|
|
353
|
+
expect(params).toEqual({
|
|
354
|
+
buttonSessionID,
|
|
355
|
+
checkoutState: "onApprove",
|
|
356
|
+
orderID,
|
|
357
|
+
payerID: "PP-payer-122",
|
|
358
|
+
});
|
|
359
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("/#/checkout/completed - SPA-style hash path with no params returns null", () => {
|
|
363
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
364
|
+
hash: "#/checkout/completed",
|
|
365
|
+
search: "",
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const params = getAppSwitchResumeParams();
|
|
369
|
+
|
|
370
|
+
expect(params).toEqual(null);
|
|
371
|
+
expect(isAppSwitchResumeFlow()).toEqual(false);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("/#/checkout/completed?query=param - PayPal params after SPA-style hash path", () => {
|
|
375
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
376
|
+
hash: `#/checkout/completed?button_session_id=${buttonSessionID}&token=${orderID}&PayerID=PP-payer-122`,
|
|
377
|
+
search: "",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const params = getAppSwitchResumeParams();
|
|
381
|
+
|
|
382
|
+
expect(params).toEqual({
|
|
383
|
+
buttonSessionID,
|
|
384
|
+
checkoutState: "onApprove",
|
|
385
|
+
orderID,
|
|
386
|
+
payerID: "PP-payer-122",
|
|
387
|
+
});
|
|
388
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("#onApprove&token=...&hash?param=value - native app return with & delimiter and merchant hash containing ?", () => {
|
|
392
|
+
// Native app constructs return URL with & delimiter between action and params,
|
|
393
|
+
// and the merchant's original hash fragment contained a ? (e.g. #hash?param1=value1).
|
|
394
|
+
// The ? must not cause a mis-split; & appears first so it takes priority.
|
|
395
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
396
|
+
hash: `#onApprove&token=${orderID}&PayerID=PP-payer-122&button_session_id=${buttonSessionID}&switch_initiated_time=1772041777662&hash?param1=value1`,
|
|
397
|
+
search: "",
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const params = getAppSwitchResumeParams();
|
|
401
|
+
|
|
402
|
+
expect(params).toEqual({
|
|
403
|
+
buttonSessionID,
|
|
404
|
+
checkoutState: "onApprove",
|
|
405
|
+
orderID,
|
|
406
|
+
payerID: "PP-payer-122",
|
|
407
|
+
});
|
|
408
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("#onCancel&token=...&hash?param=value - native app cancel with & delimiter and merchant hash containing ?", () => {
|
|
412
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
413
|
+
hash: `#onCancel&token=${orderID}&button_session_id=${buttonSessionID}&hash?param1=value1`,
|
|
414
|
+
search: "",
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const params = getAppSwitchResumeParams();
|
|
418
|
+
|
|
419
|
+
expect(params).toEqual({
|
|
420
|
+
buttonSessionID,
|
|
421
|
+
checkoutState: "onCancel",
|
|
422
|
+
orderID,
|
|
423
|
+
});
|
|
424
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("#onApprove?token=...&hash?param=value - native app return with ? delimiter and merchant hash containing ?", () => {
|
|
428
|
+
// Native app uses ? delimiter, and merchant hash also contains ?.
|
|
429
|
+
// The first ? splits action from params; the second ? is just part of param values.
|
|
430
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
431
|
+
hash: `#onApprove?token=${orderID}&PayerID=PP-payer-122&button_session_id=${buttonSessionID}&hash?param1=value1`,
|
|
432
|
+
search: "",
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const params = getAppSwitchResumeParams();
|
|
436
|
+
|
|
437
|
+
expect(params).toEqual({
|
|
438
|
+
buttonSessionID,
|
|
439
|
+
checkoutState: "onApprove",
|
|
440
|
+
orderID,
|
|
441
|
+
payerID: "PP-payer-122",
|
|
442
|
+
});
|
|
443
|
+
expect(isAppSwitchResumeFlow()).toEqual(true);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("should return null when web fallback throws error", () => {
|
|
448
|
+
// Mock location.search as a getter that throws an error
|
|
449
|
+
// eslint-disable-next-line compat/compat
|
|
450
|
+
const originalURLSearchParams = window.URLSearchParams;
|
|
451
|
+
// eslint-disable-next-line compat/compat
|
|
452
|
+
window.URLSearchParams = class {
|
|
453
|
+
constructor() {
|
|
454
|
+
throw new Error("Invalid URL");
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
459
|
+
hash: "",
|
|
460
|
+
search: "?invalid",
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const params = getAppSwitchResumeParams();
|
|
464
|
+
|
|
465
|
+
expect(params).toEqual(null);
|
|
466
|
+
expect(isAppSwitchResumeFlow()).toEqual(false);
|
|
467
|
+
|
|
468
|
+
// Restore original URLSearchParams
|
|
469
|
+
// eslint-disable-next-line compat/compat
|
|
470
|
+
window.URLSearchParams = originalURLSearchParams;
|
|
471
|
+
});
|
|
143
472
|
});
|
package/src/types.js
CHANGED
|
@@ -109,13 +109,7 @@ export function Button({
|
|
|
109
109
|
const colors = fundingConfig.colors;
|
|
110
110
|
const secondaryColors = fundingConfig.secondaryColors || {};
|
|
111
111
|
|
|
112
|
-
let {
|
|
113
|
-
color,
|
|
114
|
-
period,
|
|
115
|
-
label,
|
|
116
|
-
shouldApplyRebrandedStyles,
|
|
117
|
-
shouldApplyPayNowOrLaterLabel,
|
|
118
|
-
} = style;
|
|
112
|
+
let { color, period, label, shouldApplyRebrandedStyles } = style;
|
|
119
113
|
|
|
120
114
|
// if no color option is passed in via style props
|
|
121
115
|
if (color === "" || typeof color === "undefined") {
|
|
@@ -195,10 +189,6 @@ export function Button({
|
|
|
195
189
|
})
|
|
196
190
|
: fundingConfig.labelText || fundingSource;
|
|
197
191
|
|
|
198
|
-
if (shouldApplyPayNowOrLaterLabel) {
|
|
199
|
-
labelText = "PayPal Pay Now or Later";
|
|
200
|
-
}
|
|
201
|
-
|
|
202
192
|
if (!showPayLabel && instrument?.vendor && instrument.label) {
|
|
203
193
|
labelText = instrument.secondaryInstruments
|
|
204
194
|
? `${instrument.secondaryInstruments[0].type} & ${instrument.vendor} ${instrument.label}`
|
|
@@ -244,7 +234,6 @@ export function Button({
|
|
|
244
234
|
tagline={tagline}
|
|
245
235
|
content={content}
|
|
246
236
|
experiment={experiment}
|
|
247
|
-
shouldApplyPayNowOrLaterLabel={shouldApplyPayNowOrLaterLabel}
|
|
248
237
|
/>
|
|
249
238
|
);
|
|
250
239
|
|
package/src/ui/buttons/props.js
CHANGED
|
@@ -48,7 +48,6 @@ import {
|
|
|
48
48
|
MESSAGE_ALIGN,
|
|
49
49
|
} from "../../constants";
|
|
50
50
|
import { getFundingConfig, isFundingEligible } from "../../funding";
|
|
51
|
-
import { componentContent } from "../../funding/content";
|
|
52
51
|
import type { StateGetSet } from "../../lib/session";
|
|
53
52
|
|
|
54
53
|
import { BUTTON_SIZE_STYLE } from "./config";
|
|
@@ -332,8 +331,6 @@ export type ButtonStyle = {|
|
|
|
332
331
|
borderRadius?: number,
|
|
333
332
|
shouldApplyRebrandedStyles: boolean,
|
|
334
333
|
isButtonColorABTestMerchant: boolean,
|
|
335
|
-
isPayNowOrLaterLabelEligible: boolean,
|
|
336
|
-
shouldApplyPayNowOrLaterLabel: boolean,
|
|
337
334
|
|};
|
|
338
335
|
|
|
339
336
|
export type ButtonStyleInputs = {|
|
|
@@ -973,56 +970,6 @@ export function getButtonColor({
|
|
|
973
970
|
}
|
|
974
971
|
}
|
|
975
972
|
|
|
976
|
-
export function getCobrandedBNPLLabelFlags(props: ?ButtonPropsInputs): {|
|
|
977
|
-
isPayNowOrLaterLabelEligible: boolean,
|
|
978
|
-
shouldApplyPayNowOrLaterLabel: boolean,
|
|
979
|
-
|} {
|
|
980
|
-
const label = props?.style?.label;
|
|
981
|
-
const lang = props?.locale?.lang;
|
|
982
|
-
const isPurchaseFlow = props?.flow === BUTTON_FLOW.PURCHASE;
|
|
983
|
-
const isEnLang = Boolean(lang && componentContent[lang]?.PayNowOrLater);
|
|
984
|
-
const isCobrandedEligibleFundingSource =
|
|
985
|
-
props?.fundingSource === FUNDING.PAYPAL ||
|
|
986
|
-
props?.fundingSource === undefined;
|
|
987
|
-
const isPaylaterEligible =
|
|
988
|
-
props?.fundingEligibility?.paylater?.eligible || false;
|
|
989
|
-
const isLabelEligible = label === undefined || label === BUTTON_LABEL.PAYPAL;
|
|
990
|
-
|
|
991
|
-
const isPaylaterCobrandedLabelEnabled =
|
|
992
|
-
props?.experiment?.isPaylaterCobrandedLabelEnabled || false;
|
|
993
|
-
|
|
994
|
-
// add logs to help debug any issues for alpha branch, remove later
|
|
995
|
-
// eslint-disable-next-line no-console
|
|
996
|
-
console.log("getCobrandedBNPLLabelFlags:", {
|
|
997
|
-
isPaylaterCobrandedLabelEnabled,
|
|
998
|
-
isCobrandedEligibleFundingSource,
|
|
999
|
-
isPaylaterEligible,
|
|
1000
|
-
isLabelEligible,
|
|
1001
|
-
isEnLang,
|
|
1002
|
-
isPurchaseFlow,
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
const isPayNowOrLaterLabelEligible = Boolean(
|
|
1006
|
-
isPaylaterCobrandedLabelEnabled &&
|
|
1007
|
-
isCobrandedEligibleFundingSource &&
|
|
1008
|
-
isPaylaterEligible &&
|
|
1009
|
-
isLabelEligible &&
|
|
1010
|
-
isEnLang &&
|
|
1011
|
-
isPurchaseFlow
|
|
1012
|
-
);
|
|
1013
|
-
|
|
1014
|
-
// All eligible sessions are treatment for now; future: add randomization here
|
|
1015
|
-
const shouldApplyPayNowOrLaterLabel = isPayNowOrLaterLabelEligible;
|
|
1016
|
-
|
|
1017
|
-
// eslint-disable-next-line no-console
|
|
1018
|
-
console.log("getCobrandedBNPLLabelFlags result:", {
|
|
1019
|
-
isPayNowOrLaterLabelEligible,
|
|
1020
|
-
shouldApplyPayNowOrLaterLabel,
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
return { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel };
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
973
|
const getDefaultButtonPropsInput = (): ButtonPropsInputs => {
|
|
1027
974
|
return {};
|
|
1028
975
|
};
|
|
@@ -1172,9 +1119,6 @@ export function normalizeButtonStyle(
|
|
|
1172
1119
|
}
|
|
1173
1120
|
}
|
|
1174
1121
|
|
|
1175
|
-
const { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel } =
|
|
1176
|
-
getCobrandedBNPLLabelFlags(props);
|
|
1177
|
-
|
|
1178
1122
|
return {
|
|
1179
1123
|
label,
|
|
1180
1124
|
layout,
|
|
@@ -1189,8 +1133,6 @@ export function normalizeButtonStyle(
|
|
|
1189
1133
|
borderRadius,
|
|
1190
1134
|
shouldApplyRebrandedStyles,
|
|
1191
1135
|
isButtonColorABTestMerchant,
|
|
1192
|
-
isPayNowOrLaterLabelEligible,
|
|
1193
|
-
shouldApplyPayNowOrLaterLabel,
|
|
1194
1136
|
};
|
|
1195
1137
|
}
|
|
1196
1138
|
|