Skip to content

Commit bee5716

Browse files
nityaspNitya
andauthored
Enhacing PayPal and PayLater hooks to handle redirect and appswitch presentation modes (#739)
* Enhacing PayPal and PayLater hooks to handle redirect and appswitch presentation modes * cleaning console logs * changeset added * Adding null check on newSession * Adding try/catch around resume function and updating testcases * Applying the resume flow check only for redirect an appswitch --------- Co-authored-by: Nitya <[email protected]>
1 parent 6a745d7 commit bee5716

File tree

8 files changed

+110
-26
lines changed

8 files changed

+110
-26
lines changed

.changeset/deep-pumas-sip.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@paypal/react-paypal-js": patch
3+
"@paypal/paypal-js": patch
4+
---
5+
6+
Enhancing the Paypal and PayLater hooks to handle redirect and direct app switch presentation modes.

packages/paypal-js/types/v6/components/base-component.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export type PresentationModeOptionsForModal = {
5555
export type PresentationModeOptionsForRedirect = {
5656
presentationMode: "redirect";
5757
autoRedirect?: { enabled: boolean };
58-
fullPageOverlay?: undefined;
58+
fullPageOverlay?: { enabled: boolean };
5959
};
6060

6161
export type PresentationModeOptionsForPaymentHandler = {
@@ -70,6 +70,12 @@ export type PresentationModeOptionsForAuto = {
7070
autoRedirect?: undefined;
7171
};
7272

73+
export type PresentationModeOptionsForDirectAppSwitch = {
74+
presentationMode: "direct-app-switch";
75+
fullPageOverlay?: { enabled: boolean };
76+
autoRedirect?: { enabled: boolean };
77+
};
78+
7379
export type CreateOrderPromise = Promise<{ orderId: string }>;
7480

7581
export type CreateOrderCallback = () => CreateOrderPromise;

packages/paypal-js/types/v6/components/paypal-payments.d.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
PresentationModeOptionsForAuto,
88
PresentationModeOptionsForPaymentHandler,
99
PresentationModeOptionsForRedirect,
10+
PresentationModeOptionsForDirectAppSwitch,
1011
} from "./base-component";
1112

1213
export type PayLaterCountryCodes =
@@ -99,13 +100,16 @@ export type PayPalPresentationModeOptions =
99100
| PresentationModeOptionsForPopup
100101
| PresentationModeOptionsForModal
101102
| PresentationModeOptionsForPaymentHandler
102-
| PresentationModeOptionsForRedirect;
103+
| PresentationModeOptionsForRedirect
104+
| PresentationModeOptionsForDirectAppSwitch;
103105

104106
export type OneTimePaymentSession = BasePaymentSession & {
105107
start: (
106108
presentationModeOptions: PayPalPresentationModeOptions,
107109
paymentSessionPromise?: Promise<{ orderId: string }>,
108-
) => Promise<void>;
110+
) => Promise<void | { redirectURL?: string }>;
111+
hasReturned?: () => boolean;
112+
resume?: () => Promise<void>;
109113
};
110114

111115
export type SavePaymentSession = Omit<BasePaymentSession, "start"> & {

packages/react-paypal-js/src/v6/hooks/usePayLaterOneTimePaymentSession.test.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,14 @@ describe("usePayLaterOneTimePaymentSession", () => {
122122
});
123123

124124
expect(mockStart).toHaveBeenCalledTimes(1);
125-
expect(mockStart).toHaveBeenCalledWith({
126-
presentationMode: mockPresentationMode,
127-
});
125+
expect(mockStart).toHaveBeenCalledWith(
126+
{
127+
presentationMode: mockPresentationMode,
128+
fullPageOverlay: undefined,
129+
autoRedirect: undefined,
130+
},
131+
undefined,
132+
);
128133
});
129134

130135
test("should call the createOrder callback, if it was provided, on start inside the click handler", async () => {
@@ -172,7 +177,11 @@ describe("usePayLaterOneTimePaymentSession", () => {
172177

173178
expect(mockStart).toHaveBeenCalledTimes(1);
174179
expect(mockStart).toHaveBeenCalledWith(
175-
{ presentationMode: mockPresentationMode },
180+
{
181+
presentationMode: mockPresentationMode,
182+
fullPageOverlay: undefined,
183+
autoRedirect: undefined,
184+
},
176185
mockOrderIdPromise,
177186
);
178187
});

packages/react-paypal-js/src/v6/hooks/usePayLaterOneTimePaymentSession.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,31 @@ export function usePayLaterOneTimePaymentSession({
6161
});
6262
sessionRef.current = newSession;
6363

64+
// check for resume flow in redirect-based presentation modes
65+
const isRedirectMode =
66+
presentationMode === "redirect" ||
67+
presentationMode === "direct-app-switch";
68+
69+
if (isRedirectMode) {
70+
const handleReturnFromPayPal = async () => {
71+
try {
72+
if (!newSession) {
73+
return;
74+
}
75+
const isResumeFlow = newSession.hasReturned?.();
76+
if (isResumeFlow) {
77+
await newSession.resume?.();
78+
}
79+
} catch (err) {
80+
setError(err as Error);
81+
}
82+
};
83+
84+
handleReturnFromPayPal();
85+
}
86+
6487
return handleDestroy;
65-
}, [sdkInstance, orderId, proxyCallbacks, handleDestroy]);
88+
}, [sdkInstance, orderId, proxyCallbacks, handleDestroy, presentationMode]);
6689

6790
const handleCancel = useCallback(() => {
6891
sessionRef.current?.cancel();
@@ -84,11 +107,11 @@ export function usePayLaterOneTimePaymentSession({
84107
autoRedirect,
85108
} as PayPalPresentationModeOptions;
86109

87-
if (createOrder) {
88-
await sessionRef.current.start(startOptions, createOrder());
89-
} else {
90-
await sessionRef.current.start(startOptions);
91-
}
110+
const result = await sessionRef.current.start(
111+
startOptions,
112+
createOrder?.(),
113+
);
114+
return result;
92115
}, [
93116
createOrder,
94117
presentationMode,

packages/react-paypal-js/src/v6/hooks/usePayPalOneTimePaymentSession.test.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,14 @@ describe("usePayPalOneTimePaymentSession", () => {
290290
await result.current.handleClick();
291291
});
292292

293-
expect(mockPayPalSession.start).toHaveBeenCalledWith({
294-
presentationMode: "popup",
295-
});
293+
expect(mockPayPalSession.start).toHaveBeenCalledWith(
294+
{
295+
presentationMode: "popup",
296+
fullPageOverlay: undefined,
297+
autoRedirect: undefined,
298+
},
299+
undefined,
300+
);
296301
});
297302

298303
test("should call the createOrder callback on start inside the click handler", async () => {
@@ -321,6 +326,8 @@ describe("usePayPalOneTimePaymentSession", () => {
321326
expect(mockPayPalSession.start).toHaveBeenCalledWith(
322327
{
323328
presentationMode: "popup",
329+
fullPageOverlay: undefined,
330+
autoRedirect: undefined,
324331
},
325332
expect.any(Promise),
326333
);
@@ -370,9 +377,14 @@ describe("usePayPalOneTimePaymentSession", () => {
370377
await result.current.handleClick();
371378
});
372379

373-
expect(mockPayPalSession.start).toHaveBeenCalledWith({
374-
presentationMode: mode,
375-
});
380+
expect(mockPayPalSession.start).toHaveBeenCalledWith(
381+
{
382+
presentationMode: mode,
383+
fullPageOverlay: undefined,
384+
autoRedirect: undefined,
385+
},
386+
undefined,
387+
);
376388

377389
jest.clearAllMocks();
378390
mockPayPalSession = createMockPayPalSession();

packages/react-paypal-js/src/v6/hooks/usePayPalOneTimePaymentSession.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,39 @@ export function usePayPalOneTimePaymentSession({
5757
return;
5858
}
5959

60+
// Create session (can be created without orderId for resume detection)
6061
const newSession = sdkInstance.createPayPalOneTimePaymentSession({
6162
orderId,
6263
...proxyCallbacks,
6364
});
6465

6566
sessionRef.current = newSession;
6667

68+
// Only check for resume flow in redirect-based presentation modes
69+
const shouldCheckResume =
70+
presentationMode === "redirect" ||
71+
presentationMode === "direct-app-switch";
72+
73+
if (shouldCheckResume) {
74+
const handleReturnFromPayPal = async () => {
75+
try {
76+
if (!newSession) {
77+
return;
78+
}
79+
const isResumeFlow = newSession.hasReturned?.();
80+
if (isResumeFlow) {
81+
await newSession.resume?.();
82+
}
83+
} catch (err) {
84+
setError(err as Error);
85+
}
86+
};
87+
88+
handleReturnFromPayPal();
89+
}
90+
6791
return handleDestroy;
68-
}, [sdkInstance, orderId, proxyCallbacks, handleDestroy]);
92+
}, [sdkInstance, orderId, proxyCallbacks, handleDestroy, presentationMode]);
6993

7094
const handleClick = useCallback(async () => {
7195
if (!isMountedRef.current) {
@@ -83,11 +107,11 @@ export function usePayPalOneTimePaymentSession({
83107
autoRedirect,
84108
} as PayPalPresentationModeOptions;
85109

86-
if (createOrder) {
87-
await sessionRef.current.start(startOptions, createOrder());
88-
} else {
89-
await sessionRef.current.start(startOptions);
90-
}
110+
const result = await sessionRef.current.start(
111+
startOptions,
112+
createOrder?.(),
113+
);
114+
return result;
91115
}, [
92116
isMountedRef,
93117
presentationMode,

packages/react-paypal-js/src/v6/types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export type * from "@paypal/paypal-js/sdk-v6";
44

55
export interface BasePaymentSessionReturn {
66
error: Error | null;
7-
handleClick: () => Promise<void>;
7+
handleClick: () => Promise<{ redirectURL?: string } | void>;
88
handleCancel: () => void;
99
handleDestroy: () => void;
1010
}

0 commit comments

Comments
 (0)