Skip to content

Commit d5efa76

Browse files
authored
Added new component for captcha integration (#109)
* Added new compnent for captcha integration * update component * fix flow errors * update prop * logs added for debugging * added logging * testing common-components * testing common-components * testing common-components * testing common-components * change createOrder query param to token * added new eventlistener for captcha overlay * added 5sec timeout for captcha overlay * added 3sec timeout for captcha overlay * Remove console.log * make timeout its own function and set to 2 sec * 3 second timeout * remove prerender timeout * remove paypal logo on overlay for 3ds and captcha unbranded flows * update tests * add timeout for 3 sec * captcha emits event.rendered
1 parent 9ec76ed commit d5efa76

File tree

7 files changed

+238
-3
lines changed

7 files changed

+238
-3
lines changed

src/captcha/component.jsx

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/* @flow */
2+
/** @jsx node */
3+
/* eslint max-lines: 0 */
4+
5+
import { node, dom } from "@krakenjs/jsx-pragmatic/src";
6+
import { create, type ZoidComponent } from "@krakenjs/zoid/src";
7+
import { inlineMemoize, noop } from "@krakenjs/belter/src";
8+
import { getSDKMeta, getClientID, getCSPNonce } from "@paypal/sdk-client/src";
9+
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
10+
11+
import { Overlay } from "../overlay";
12+
import { getCaptchaUrl } from "../config";
13+
14+
export type CaptchaResult = {||};
15+
16+
export const USER_TYPE = {
17+
BRANDED_GUEST: ("BRANDED_GUEST": "BRANDED_GUEST"), // inline guest flow
18+
UNBRANDED_GUEST: ("UNBRANDED_GUEST": "UNBRANDED_GUEST"), // UCC
19+
MEMBER: ("MEMBER": "MEMBER"),
20+
};
21+
22+
export type CaptchaProps = {|
23+
action: string,
24+
xcomponent: string,
25+
flow: string,
26+
orderID: string,
27+
onSuccess: (CaptchaResult) => void,
28+
onError: (mixed) => void,
29+
sdkMeta: string,
30+
content?: void | {|
31+
windowMessage?: string,
32+
continueMessage?: string,
33+
cancelMessage?: string,
34+
interrogativeMessage?: string,
35+
|},
36+
userType: ?$Values<typeof USER_TYPE>,
37+
nonce: string,
38+
|};
39+
40+
export type CaptchaComponent = ZoidComponent<CaptchaProps>;
41+
42+
export function getCaptchaComponent(): CaptchaComponent {
43+
return inlineMemoize(getCaptchaComponent, () => {
44+
const component = create({
45+
tag: "captcha",
46+
url: getCaptchaUrl,
47+
48+
attributes: {
49+
iframe: {
50+
scrolling: "no",
51+
},
52+
},
53+
54+
containerTemplate: ({
55+
context,
56+
focus,
57+
close,
58+
frame,
59+
prerenderFrame,
60+
doc,
61+
event,
62+
props,
63+
}) => {
64+
return (
65+
<Overlay
66+
context={context}
67+
close={close}
68+
focus={focus}
69+
event={event}
70+
frame={frame}
71+
prerenderFrame={prerenderFrame}
72+
content={props.content}
73+
nonce={props.nonce}
74+
isUnbrandedFlow={true}
75+
/>
76+
).render(dom({ doc }));
77+
},
78+
79+
props: {
80+
action: {
81+
type: "string",
82+
queryParam: true,
83+
value: (data) => (data.props.action ? data.props.action : "verify"),
84+
},
85+
xcomponent: {
86+
type: "string",
87+
queryParam: true,
88+
value: () => "1",
89+
},
90+
flow: {
91+
type: "string",
92+
queryParam: true,
93+
value: () => "rca",
94+
},
95+
createOrder: {
96+
type: "function",
97+
queryParam: "token",
98+
// $FlowFixMe[incompatible-call]
99+
queryValue: ({ value }) => ZalgoPromise.try(value),
100+
required: false,
101+
},
102+
token: {
103+
type: "string",
104+
queryParam: "token",
105+
queryValue: ({ value }) => value,
106+
required: false,
107+
},
108+
clientID: {
109+
type: "string",
110+
value: getClientID,
111+
queryParam: true,
112+
},
113+
onError: {
114+
type: "function",
115+
required: false,
116+
},
117+
onSuccess: {
118+
type: "function",
119+
alias: "onContingencyResult",
120+
decorate: ({ props, value, onError }) => {
121+
return (err, result) => {
122+
const isCardFieldFlow = props?.userType === "UNBRANDED_GUEST";
123+
124+
const hasError = isCardFieldFlow
125+
? Boolean(err)
126+
: // $FlowFixMe[incompatible-use]
127+
Boolean(err) || result?.success === false;
128+
129+
if (hasError) {
130+
if (onError) {
131+
return onError(
132+
err || new Error("CAPTCHA verification failed")
133+
);
134+
}
135+
return;
136+
}
137+
138+
return value(result);
139+
};
140+
},
141+
},
142+
onCancel: {
143+
type: "function",
144+
required: false,
145+
},
146+
sdkMeta: {
147+
type: "string",
148+
queryParam: true,
149+
sendToChild: false,
150+
value: getSDKMeta,
151+
},
152+
content: {
153+
type: "object",
154+
required: false,
155+
},
156+
userType: {
157+
type: "string",
158+
required: false,
159+
},
160+
nonce: {
161+
type: "string",
162+
default: getCSPNonce,
163+
},
164+
integrationType: {
165+
type: "string",
166+
required: false,
167+
queryParam: true,
168+
},
169+
},
170+
});
171+
172+
if (component.isChild()) {
173+
window.xchild = {
174+
props: component.xprops,
175+
close: noop,
176+
};
177+
}
178+
return component;
179+
});
180+
}

src/captcha/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* @flow */
2+
3+
export * from "./component";

src/config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import { getPayPalDomain } from "@paypal/sdk-client/src";
55
const URI = __TEST__
66
? {
77
THREEDOMAINSECURE: `/base/test/integration/windows/helios/index.htm`,
8+
CAPTCHA: `/base/test/integration/windows/helios/index.htm`,
89
}
910
: {
1011
THREEDOMAINSECURE: `/webapps/helios`,
12+
CAPTCHA: `/heliosnext`,
1113
};
1214

1315
export function getThreeDomainSecureUrl(): string {
1416
return `${getPayPalDomain()}${URI.THREEDOMAINSECURE}`;
1517
}
18+
19+
export function getCaptchaUrl(): string {
20+
return `${getPayPalDomain()}${URI.CAPTCHA}`;
21+
}

src/interface.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getThreeDomainSecureComponent,
99
type TDSComponent,
1010
} from "./three-domain-secure";
11+
import { getCaptchaComponent, type CaptchaComponent } from "./captcha";
1112

1213
export type LazyExport<T> = {|
1314
__get__: () => T,
@@ -27,6 +28,10 @@ export const ThreeDomainSecure: LazyProtectedExport<TDSComponent> = {
2728
__get__: () => protectedExport(getThreeDomainSecureComponent()),
2829
};
2930

31+
export const Captcha: LazyProtectedExport<CaptchaComponent> = {
32+
__get__: () => protectedExport(getCaptchaComponent()),
33+
};
34+
3035
export const postRobot: LazyProtectedExport<typeof postRobotModule> = {
3136
__get__: () => protectedExport(postRobotModule),
3237
};

src/overlay/template.jsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export type OverlayProps = {|
4848
hideCloseButton?: boolean,
4949
nonce: string,
5050
fullScreen?: boolean,
51+
isUnbrandedFlow?: boolean, // eslint-disable-line react/no-unused-prop-types
5152
|};
5253

5354
export function Overlay({
@@ -62,6 +63,7 @@ export function Overlay({
6263
hideCloseButton,
6364
nonce,
6465
fullScreen = false,
66+
isUnbrandedFlow = false,
6567
}: OverlayProps): ElementNode {
6668
const uid = `paypal-overlay-${uniqueID()}`;
6769
const overlayIframeName = `__paypal_checkout_sandbox_${uid}__`;
@@ -194,9 +196,12 @@ export function Overlay({
194196
)}
195197
{!fullScreen && (
196198
<div class="paypal-checkout-modal">
197-
<div class="paypal-checkout-logo" dir="ltr">
198-
<PayPalRebrandLogo logoColor={LOGO_COLOR.WHITE} />
199-
</div>
199+
{/* Cannot have any PayPal branding for unbranded flows */}
200+
{isUnbrandedFlow ? null : (
201+
<div class="paypal-checkout-logo" dir="ltr">
202+
<PayPalRebrandLogo logoColor={LOGO_COLOR.WHITE} />
203+
</div>
204+
)}
200205
{content.windowMessage && (
201206
<div class="paypal-checkout-message">
202207
{content.windowMessage}

src/three-domain-secure/component.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export function getThreeDomainSecureComponent(): TDSComponent {
7676
prerenderFrame={prerenderFrame}
7777
content={props.content}
7878
nonce={props.nonce}
79+
isUnbrandedFlow={true}
7980
/>
8081
).render(dom({ doc }));
8182
},

test/integration/tests/overlay/happy.jsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,41 @@ describe(`paypal overlay component happy path`, () => {
166166

167167
focussed = null; // reset
168168
});
169+
170+
it("should show PayPal logo by default (branded flow)", () => {
171+
const domNode = getOverlay().render(dom());
172+
addOverlayToDOM(domNode);
173+
174+
if (!getOverlayContainer(domNode).querySelector(".paypal-checkout-logo")) {
175+
throw new Error(`Expected PayPal logo to be shown in branded flow`);
176+
}
177+
});
178+
179+
it("should hide PayPal logo for unbranded flow", () => {
180+
const getUnbrandedOverlay = () => (
181+
<Overlay
182+
context={context}
183+
content={content}
184+
close={close}
185+
focus={focus}
186+
event={event}
187+
frame={frame}
188+
prerenderFrame={prerenderFrame}
189+
autoResize={autoResize}
190+
hideCloseButton={hideCloseButton}
191+
nonce={nonce}
192+
fullScreen={fullScreen}
193+
isUnbrandedFlow={true}
194+
/>
195+
);
196+
197+
const domNode = getUnbrandedOverlay().render(dom());
198+
addOverlayToDOM(domNode);
199+
200+
if (getOverlayContainer(domNode).querySelector(".paypal-checkout-logo")) {
201+
throw new Error(`Expected PayPal logo to be hidden in unbranded flow`);
202+
}
203+
});
169204
});
170205

171206
describe(`venmo overlay component happy path`, () => {

0 commit comments

Comments
 (0)