@paypal/checkout-components 5.0.344 → 5.0.345-alpha-bb62853.0

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.344",
3
+ "version": "5.0.345-alpha-bb62853.0",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -2,7 +2,8 @@
2
2
 
3
3
  import { isSameDomain } from "@krakenjs/cross-domain-utils/src";
4
4
  import { supportsPopups } from "@krakenjs/belter/src";
5
- import { isPayPalDomain } from "@paypal/sdk-client/src";
5
+ import { getEnv, isPayPalDomain } from "@paypal/sdk-client/src";
6
+ import { ENV } from "@paypal/sdk-constants/src";
6
7
 
7
8
  export function allowIframe(): boolean {
8
9
  if (!isPayPalDomain()) {
@@ -28,3 +29,13 @@ export function allowIframe(): boolean {
28
29
  export const protectedExport = (unprotectedExport) =>
29
30
  isPayPalDomain() ? unprotectedExport : undefined;
30
31
  /* eslint-enable no-confusing-arrow */
32
+
33
+ // $FlowIssue
34
+ export const localOrStageExport = (unprotectedExport) => {
35
+ const env = getEnv();
36
+ if (env === ENV.LOCAL || env === ENV.STAGE) {
37
+ return unprotectedExport;
38
+ } else {
39
+ return undefined;
40
+ }
41
+ };
@@ -1,17 +1,25 @@
1
1
  /* @flow */
2
+ /* eslint-disable eslint-comments/disable-enable-pair */
3
+ /* eslint-disable no-restricted-globals, promise/no-native */
2
4
  import { type LoggerType } from "@krakenjs/beaver-logger/src";
5
+ import { type ZoidComponent } from "@krakenjs/zoid/src";
3
6
  import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
4
- import { create, type ZoidComponent } from "@krakenjs/zoid/src";
5
7
  import { FPTI_KEY } from "@paypal/sdk-constants/src";
6
8
 
7
9
  import { ValidationError } from "../lib";
8
10
 
9
- type SdkConfig = {|
10
- sdkToken: ?string,
11
- |};
11
+ import {
12
+ requestData,
13
+ responseBody,
14
+ Request,
15
+ MerchantPayloadData,
16
+ SdkConfig,
17
+ threeDSResponse,
18
+ } from "./types";
19
+ import { getThreeDomainSecureComponent } from "./utils";
12
20
 
13
21
  const parseSdkConfig = ({ sdkConfig, logger }): SdkConfig => {
14
- if (!sdkConfig.sdkToken) {
22
+ if (!sdkConfig.authenticationToken) {
15
23
  throw new ValidationError(
16
24
  `script data attribute sdk-client-token is required but was not passed`
17
25
  );
@@ -23,32 +31,127 @@ const parseSdkConfig = ({ sdkConfig, logger }): SdkConfig => {
23
31
 
24
32
  return sdkConfig;
25
33
  };
34
+
35
+ const parseMerchantPayload = ({
36
+ merchantPayload,
37
+ }: {|
38
+ merchantPayload: MerchantPayloadData,
39
+ |}): requestData => {
40
+ const { threeDSRequested, amount, currency, nonce, transactionContext } =
41
+ merchantPayload;
42
+
43
+ return {
44
+ intent: "THREE_DS_VERIFICATION",
45
+ payment_source: {
46
+ card: {
47
+ single_use_token: nonce,
48
+ verification_method: threeDSRequested
49
+ ? "SCA_ALWAYS"
50
+ : "SCA_WHEN_REQUIRED",
51
+ },
52
+ },
53
+ amount: {
54
+ currency_code: currency,
55
+ value: amount,
56
+ },
57
+ ...transactionContext,
58
+ };
59
+ };
60
+
26
61
  export interface ThreeDomainSecureComponentInterface {
27
- isEligible(): ZalgoPromise<boolean>;
28
- show(): ZoidComponent<void>;
62
+ isEligible(): Promise<boolean>;
63
+ show(): Promise<threeDSResponse>;
29
64
  }
30
65
  export class ThreeDomainSecureComponent {
31
66
  logger: LoggerType;
67
+ request: Request;
32
68
  sdkConfig: SdkConfig;
33
-
69
+ authenticationURL: string;
70
+ threeDSIframe: ZoidComponent;
34
71
  constructor({
35
72
  logger,
73
+ request,
36
74
  sdkConfig,
37
75
  }: {|
38
76
  logger: LoggerType,
77
+ request: Request,
39
78
  sdkConfig: SdkConfig,
40
79
  |}) {
41
80
  this.logger = logger;
81
+ this.request = request;
42
82
  this.sdkConfig = parseSdkConfig({ sdkConfig, logger });
43
83
  }
44
84
 
45
- isEligible(): ZalgoPromise<boolean> {
46
- return new ZalgoPromise((resolve) => {
47
- resolve(false);
48
- });
85
+ async isEligible(merchantPayload: MerchantPayloadData): Promise<boolean> {
86
+ const data = parseMerchantPayload({ merchantPayload });
87
+ try {
88
+ // $FlowFixMe
89
+ const { status, links } = await this.request<requestData, responseBody>({
90
+ method: "POST",
91
+ url: `https://te-test-qa.qa.paypal.com:12326/v2/payments/payment`,
92
+ data,
93
+ accessToken: this.sdkConfig.authenticationToken,
94
+ });
95
+
96
+ let responseStatus = false;
97
+
98
+ if (status === "PAYER_ACTION_REQUIRED") {
99
+ this.authenticationURL = links.find(
100
+ (link) => link.rel === "payer-action"
101
+ ).href;
102
+ responseStatus = true;
103
+ this.threeDSIframe = getThreeDomainSecureComponent(
104
+ this.authenticationURL
105
+ );
106
+ }
107
+ return responseStatus;
108
+ } catch (error) {
109
+ this.logger.warn(error);
110
+ throw error;
111
+ }
49
112
  }
50
113
 
51
- show() {
52
- create({ tag: "", url: "" });
114
+ show(): Promise<threeDSResponse> {
115
+ if (!this.threeDSIframe) {
116
+ throw new ValidationError(`Ineligible for three domain secure`);
117
+ return;
118
+ }
119
+ const promise = new ZalgoPromise();
120
+ const cancelThreeDS = () => {
121
+ return ZalgoPromise.try(() => {
122
+ // eslint-disable-next-line no-console
123
+ console.log("cancelled");
124
+ }).then(() => {
125
+ // eslint-disable-next-line no-use-before-define
126
+ instance.close();
127
+ });
128
+ };
129
+
130
+ const instance = this.threeDSIframe({
131
+ onSuccess: (data) => {
132
+ // const {threeDSRefID, authentication_status, liability_shift } = data;
133
+ // let enrichedNonce;
134
+ // if(threeDSRefID) {
135
+ // enrichedNonce = await updateNonceWith3dsData(threeDSRefID, this.fastlaneNonce)
136
+ // }
137
+
138
+ return promise.resolve(data);
139
+ },
140
+ onClose: cancelThreeDS,
141
+ onError: (err) => {
142
+ return promise.reject(
143
+ new Error(
144
+ `Error with obtaining 3DS contingency, ${JSON.stringify(err)}`
145
+ )
146
+ );
147
+ },
148
+ });
149
+ const TARGET_ELEMENT = {
150
+ BODY: "body",
151
+ };
152
+ return instance
153
+ .renderTo(window.parent, TARGET_ELEMENT.BODY)
154
+ .then(() => promise)
155
+ .finally(instance.close);
53
156
  }
54
157
  }
@@ -1,14 +1,32 @@
1
1
  /* @flow */
2
+ /* eslint-disable eslint-comments/disable-enable-pair */
3
+ /* eslint-disable no-restricted-globals, promise/no-native, compat/compat */
2
4
  import { describe, expect, vi } from "vitest";
3
5
 
4
6
  import { ThreeDomainSecureComponent } from "./component";
5
7
 
6
8
  const defaultSdkConfig = {
7
- sdkToken: "sdk-client-token",
9
+ authenticationToken: "sdk-client-token",
10
+ };
11
+
12
+ const defaultEligibilityResponse = {
13
+ status: "PAYER_ACTION_REQUIRED",
14
+ links: [{ href: "https://testurl.com", rel: "payer-action" }],
15
+ };
16
+
17
+ const defaultMerchantPayload = {
18
+ amount: "1.00",
19
+ currency: "USD",
20
+ nonce: "test-nonce",
21
+ };
22
+
23
+ const mockEligibilityRequest = (body = defaultEligibilityResponse) => {
24
+ return vi.fn().mockResolvedValue(body);
8
25
  };
9
26
 
10
27
  const createThreeDomainSecureComponent = ({
11
28
  sdkConfig = defaultSdkConfig,
29
+ request = mockEligibilityRequest(),
12
30
  logger = {
13
31
  info: vi.fn().mockReturnThis(),
14
32
  warn: vi.fn().mockReturnThis(),
@@ -18,8 +36,11 @@ const createThreeDomainSecureComponent = ({
18
36
  },
19
37
  } = {}) =>
20
38
  new ThreeDomainSecureComponent({
39
+ // $FlowFixMe
21
40
  sdkConfig,
22
41
  // $FlowIssue
42
+ request,
43
+ // $FlowIssue
23
44
  logger,
24
45
  });
25
46
 
@@ -28,17 +49,87 @@ afterEach(() => {
28
49
  });
29
50
 
30
51
  describe("three domain secure component - isEligible method", () => {
31
- test("should return false", async () => {
32
- const threeDomainSecuretClient = createThreeDomainSecureComponent();
33
- const eligibility = await threeDomainSecuretClient.isEligible();
52
+ test("should return true if payer action required", async () => {
53
+ const threeDomainSecureClient = createThreeDomainSecureComponent();
54
+ const eligibility = await threeDomainSecureClient.isEligible(
55
+ defaultMerchantPayload
56
+ );
57
+ expect(eligibility).toEqual(true);
58
+ });
59
+
60
+ test("should return false if payer action is not returned", async () => {
61
+ const threeDomainSecureClient = createThreeDomainSecureComponent({
62
+ request: () =>
63
+ Promise.resolve({ ...defaultEligibilityResponse, status: "SUCCESS" }),
64
+ });
65
+ const eligibility = await threeDomainSecureClient.isEligible(
66
+ defaultMerchantPayload
67
+ );
34
68
  expect(eligibility).toEqual(false);
35
69
  });
70
+
71
+ test("should assign correct URL to authenticationURL", async () => {
72
+ const threeDomainSecureClient = createThreeDomainSecureComponent({
73
+ request: () =>
74
+ Promise.resolve({
75
+ ...defaultEligibilityResponse,
76
+ links: [
77
+ { href: "https://not-payer-action.com", rel: "not-payer-action" },
78
+ ...defaultEligibilityResponse.links,
79
+ ],
80
+ }),
81
+ });
82
+ await threeDomainSecureClient.isEligible(defaultMerchantPayload);
83
+ expect(threeDomainSecureClient.authenticationURL).toEqual(
84
+ "https://testurl.com"
85
+ );
86
+ });
87
+
88
+ test("create payload with correctly parameters", async () => {
89
+ const mockedRequest = mockEligibilityRequest();
90
+ const threeDomainSecureClient = createThreeDomainSecureComponent({
91
+ request: mockedRequest,
92
+ });
93
+
94
+ await threeDomainSecureClient.isEligible(defaultMerchantPayload);
95
+
96
+ expect(mockedRequest).toHaveBeenCalledWith(
97
+ expect.objectContaining({
98
+ data: expect.objectContaining({
99
+ intent: "THREE_DS_VERIFICATION",
100
+ payment_source: expect.objectContaining({
101
+ card: expect.objectContaining({
102
+ single_use_token: defaultMerchantPayload.nonce,
103
+ verification_method: "SCA_WHEN_REQUIRED",
104
+ }),
105
+ }),
106
+ amount: expect.objectContaining({
107
+ currency_code: defaultMerchantPayload.currency,
108
+ value: defaultMerchantPayload.amount,
109
+ }),
110
+ }),
111
+ })
112
+ );
113
+ });
114
+
115
+ test("catch errors from the API", async () => {
116
+ const mockRequest = vi.fn().mockRejectedValue(new Error("Error with API"));
117
+ const threeDomainSecureClient = createThreeDomainSecureComponent({
118
+ request: mockRequest,
119
+ });
120
+
121
+ expect.assertions(2);
122
+ await expect(() =>
123
+ threeDomainSecureClient.isEligible(defaultMerchantPayload)
124
+ ).rejects.toThrow(new Error("Error with API"));
125
+ expect(mockRequest).toHaveBeenCalled();
126
+ });
36
127
  });
37
128
 
38
129
  describe("three domain descure component - show method", () => {
39
- test.skip("should return a zoid component", () => {
40
- const threeDomainSecuretClient = createThreeDomainSecureComponent();
41
- threeDomainSecuretClient.show();
130
+ test.todo("should return a zoid component", () => {
131
+ const threeDomainSecureClient = createThreeDomainSecureComponent();
132
+ threeDomainSecureClient.show();
42
133
  // create test for zoid component
43
134
  });
44
135
  });
@@ -49,7 +140,7 @@ describe("three domain secure component - initialization", () => {
49
140
  createThreeDomainSecureComponent({
50
141
  sdkConfig: {
51
142
  ...defaultSdkConfig,
52
- sdkToken: "",
143
+ authenticationToken: "",
53
144
  },
54
145
  })
55
146
  ).toThrowError(
@@ -1,8 +1,12 @@
1
1
  /* @flow */
2
- import { getLogger, getSDKToken } from "@paypal/sdk-client/src";
2
+ import {
3
+ getLogger,
4
+ getPayPalAPIDomain,
5
+ getUserIDToken,
6
+ } from "@paypal/sdk-client/src";
3
7
 
8
+ import { callRestAPI, localOrStageExport } from "../lib";
4
9
  import type { LazyExport } from "../types";
5
- import { protectedExport } from "../lib";
6
10
 
7
11
  import {
8
12
  ThreeDomainSecureComponent,
@@ -14,12 +18,15 @@ export const ThreeDomainSecureClient: LazyExport<ThreeDomainSecureComponentInter
14
18
  __get__: () => {
15
19
  const threeDomainSecureInstance = new ThreeDomainSecureComponent({
16
20
  logger: getLogger(),
21
+ // $FlowIssue ZalgoPromise vs Promise
22
+ request: callRestAPI,
17
23
  sdkConfig: {
18
- sdkToken: getSDKToken(),
24
+ authenticationToken: getUserIDToken(),
25
+ paypalApiDomain: getPayPalAPIDomain(),
19
26
  },
20
27
  });
21
- return protectedExport({
22
- isEligible: () => threeDomainSecureInstance.isEligible(),
28
+ return localOrStageExport({
29
+ isEligible: (payload) => threeDomainSecureInstance.isEligible(payload),
23
30
  show: () => threeDomainSecureInstance.show(),
24
31
  });
25
32
  },
@@ -0,0 +1,77 @@
1
+ /* @flow */
2
+ /* eslint-disable no-restricted-globals, promise/no-native */
3
+ export type MerchantPayloadData = {|
4
+ amount: string,
5
+ currency: string,
6
+ nonce: string,
7
+ threeDSRequested?: boolean,
8
+ transactionContext?: Object,
9
+ |};
10
+
11
+ // eslint-disable-next-line no-undef
12
+ export type Request = <TRequestData, TResponse>({|
13
+ method?: string,
14
+ url: string,
15
+ // eslint-disable-next-line no-undef
16
+ data: TRequestData,
17
+ accessToken: ?string,
18
+ // eslint-disable-next-line no-undef
19
+ |}) => Promise<TResponse>;
20
+
21
+ export type requestData = {|
22
+ intent: "THREE_DS_VERIFICATION",
23
+ payment_source: {|
24
+ card: {|
25
+ single_use_token: string,
26
+ verification_method: string,
27
+ |},
28
+ |},
29
+ amount: {|
30
+ currency_code: string,
31
+ value: string,
32
+ |},
33
+ transaction_context?: {|
34
+ soft_descriptor?: string,
35
+ |},
36
+ |};
37
+
38
+ export type responseBody = {|
39
+ payment_id: string,
40
+ status: string,
41
+ intent: string,
42
+ payment_source: {|
43
+ card: {|
44
+ last_digits: string,
45
+ type: string,
46
+ name: string,
47
+ expiry: string,
48
+ |},
49
+ |},
50
+ amount: {|
51
+ currency_code: string,
52
+ value: string,
53
+ |},
54
+ transaction_context: {|
55
+ soft_descriptor: string,
56
+ |},
57
+ links: $ReadOnlyArray<{|
58
+ href: string,
59
+ rel: string,
60
+ method: string,
61
+ |}>,
62
+ |};
63
+
64
+ export type SdkConfig = {|
65
+ authenticationToken: ?string,
66
+ paypalApiDomain: string,
67
+ |};
68
+
69
+ export type threeDSResponse = {|
70
+ liabilityShift: string,
71
+ authenticationStatus: string,
72
+ nonce?: string,
73
+ |};
74
+
75
+ export type TDSResult = {||};
76
+
77
+ /* eslint-enable no-restricted-globals, promise/no-native */
@@ -0,0 +1,108 @@
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 { getCSPNonce } from "@paypal/sdk-client/src";
9
+
10
+ import { Overlay } from "../ui/overlay";
11
+
12
+ import { threeDSResponse } from "./types";
13
+
14
+ export type TDSProps = {|
15
+ action: string,
16
+ xcomponent: string,
17
+ flow: string,
18
+ orderID: string,
19
+ onSuccess: (threeDSResponse) => void,
20
+ onError: (mixed) => void,
21
+ sdkMeta: string,
22
+ content?: void | {|
23
+ windowMessage?: string,
24
+ continueMessage?: string,
25
+ cancelMessage?: string,
26
+ interrogativeMessage?: string,
27
+ |},
28
+ nonce: string,
29
+ |};
30
+
31
+ export type TDSComponent = ZoidComponent<TDSProps>;
32
+
33
+ export function getThreeDomainSecureComponent(payerActionUrll): TDSComponent {
34
+ return inlineMemoize(getThreeDomainSecureComponent, () => {
35
+ const component = create({
36
+ tag: "three-domain-secure-client",
37
+ url: payerActionUrll,
38
+
39
+ attributes: {
40
+ iframe: {
41
+ scrolling: "no",
42
+ },
43
+ },
44
+
45
+ containerTemplate: ({
46
+ context,
47
+ focus,
48
+ close,
49
+ frame,
50
+ prerenderFrame,
51
+ doc,
52
+ event,
53
+ props,
54
+ }) => {
55
+ return (
56
+ <Overlay
57
+ context={context}
58
+ close={close}
59
+ focus={focus}
60
+ event={event}
61
+ frame={frame}
62
+ prerenderFrame={prerenderFrame}
63
+ content={props.content}
64
+ nonce={props.nonce}
65
+ />
66
+ ).render(dom({ doc }));
67
+ },
68
+
69
+ props: {
70
+ xcomponent: {
71
+ type: "string",
72
+ queryParam: true,
73
+ value: () => "1",
74
+ },
75
+ onSuccess: {
76
+ type: "function",
77
+ alias: "onContingencyResult",
78
+ decorate: ({ value, onError }) => {
79
+ return (err, result) => {
80
+ if (err) {
81
+ return onError(err);
82
+ }
83
+
84
+ return value(result);
85
+ };
86
+ },
87
+ },
88
+ content: {
89
+ type: "object",
90
+ required: false,
91
+ },
92
+ nonce: {
93
+ type: "string",
94
+ default: getCSPNonce,
95
+ },
96
+ },
97
+ });
98
+
99
+ if (component.isChild()) {
100
+ window.xchild = {
101
+ props: component.xprops,
102
+ close: noop,
103
+ };
104
+ }
105
+
106
+ return component;
107
+ });
108
+ }
@@ -0,0 +1,3 @@
1
+ /* @flow */
2
+
3
+ export * from "./overlay";
@@ -0,0 +1,228 @@
1
+ /* @flow */
2
+ /** @jsx node */
3
+ /* eslint max-lines: off, react/jsx-max-depth: off */
4
+
5
+ import {
6
+ isIos,
7
+ isIpadOs,
8
+ isFirefox,
9
+ animate,
10
+ noop,
11
+ destroyElement,
12
+ uniqueID,
13
+ supportsPopups,
14
+ type EventEmitterType,
15
+ toCSS,
16
+ } from "@krakenjs/belter/src";
17
+ import { EVENT, CONTEXT } from "@krakenjs/zoid/src";
18
+ import { node, type ElementNode } from "@krakenjs/jsx-pragmatic/src";
19
+ import { LOGO_COLOR, PPLogo, PayPalLogo } from "@paypal/sdk-logos/src";
20
+ import { type ZalgoPromise } from "@krakenjs/zalgo-promise/src";
21
+
22
+ import { getContainerStyle, getSandboxStyle, CLASS } from "./style";
23
+
24
+ export type OverlayProps = {|
25
+ context: $Values<typeof CONTEXT>,
26
+ close: () => ZalgoPromise<void>,
27
+ focus: () => ZalgoPromise<void>,
28
+ event: EventEmitterType,
29
+ frame: ?HTMLElement,
30
+ prerenderFrame: ?HTMLElement,
31
+ content?: void | {|
32
+ windowMessage?: string,
33
+ continueMessage?: string,
34
+ cancelMessage?: string,
35
+ interrogativeMessage?: string,
36
+ |},
37
+ autoResize?: boolean,
38
+ hideCloseButton?: boolean,
39
+ nonce: string,
40
+ fullScreen?: boolean,
41
+ |};
42
+ export function Overlay({
43
+ context,
44
+ close,
45
+ focus,
46
+ event,
47
+ frame,
48
+ prerenderFrame,
49
+ content = {},
50
+ autoResize,
51
+ hideCloseButton,
52
+ nonce,
53
+ fullScreen = false,
54
+ }: OverlayProps): ElementNode {
55
+ const uid = `paypal-overlay-${uniqueID()}`;
56
+ const overlayIframeName = `__paypal_checkout_sandbox_${uid}__`;
57
+
58
+ function closeCheckout(e) {
59
+ e.preventDefault();
60
+ e.stopPropagation();
61
+ close();
62
+ }
63
+
64
+ function displayFocusWarning() {
65
+ const overlayIframe: ?HTMLIFrameElement =
66
+ // $FlowFixMe
67
+ document.getElementsByName(overlayIframeName)?.[0];
68
+ const iframeDocument = overlayIframe?.contentWindow.document;
69
+ const warningElement = iframeDocument?.getElementsByClassName(
70
+ "paypal-checkout-focus-warning"
71
+ )?.[0];
72
+
73
+ if (!warningElement) {
74
+ return;
75
+ }
76
+ warningElement.innerText = `Still can't see it? Select "Window" in your toolbar to find "Log in to your PayPal account"`;
77
+ }
78
+
79
+ function focusCheckout(e) {
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+
83
+ if (!supportsPopups()) {
84
+ return;
85
+ }
86
+
87
+ if (isIos() || isIpadOs()) {
88
+ // Note: alerts block the event loop until they are closed.
89
+ // eslint-disable-next-line no-alert
90
+ window.alert("Please switch tabs to reactivate the PayPal window");
91
+ } else if (isFirefox()) {
92
+ displayFocusWarning();
93
+ }
94
+ focus();
95
+ }
96
+
97
+ const setupAnimations = (name) => {
98
+ return (el) => {
99
+ const showContainer = () => animate(el, `show-${name}`, noop);
100
+ const hideContainer = () => animate(el, `hide-${name}`, noop);
101
+ event.on(EVENT.DISPLAY, showContainer);
102
+ event.on(EVENT.CLOSE, hideContainer);
103
+ };
104
+ };
105
+
106
+ const setupAutoResize = (el) => {
107
+ event.on(EVENT.RESIZE, ({ width: newWidth, height: newHeight }) => {
108
+ if (typeof newWidth === "number") {
109
+ el.style.width = toCSS(newWidth);
110
+ }
111
+
112
+ if (typeof newHeight === "number") {
113
+ el.style.height = toCSS(newHeight);
114
+ }
115
+ });
116
+ };
117
+
118
+ const outletOnRender = (el) => {
119
+ setupAnimations("component")(el);
120
+ if (autoResize) {
121
+ setupAutoResize(el);
122
+ }
123
+ };
124
+
125
+ let outlet;
126
+
127
+ if (frame && prerenderFrame) {
128
+ frame.classList.add(CLASS.COMPONENT_FRAME);
129
+ prerenderFrame.classList.add(CLASS.PRERENDER_FRAME);
130
+
131
+ prerenderFrame.classList.add(CLASS.VISIBLE);
132
+ frame.classList.add(CLASS.INVISIBLE);
133
+
134
+ event.on(EVENT.RENDERED, () => {
135
+ prerenderFrame.classList.remove(CLASS.VISIBLE);
136
+ prerenderFrame.classList.add(CLASS.INVISIBLE);
137
+
138
+ frame.classList.remove(CLASS.INVISIBLE);
139
+ frame.classList.add(CLASS.VISIBLE);
140
+
141
+ setTimeout(() => {
142
+ destroyElement(prerenderFrame);
143
+ }, 1);
144
+ });
145
+
146
+ outlet = (
147
+ <div class={CLASS.OUTLET} onRender={outletOnRender}>
148
+ <node el={frame} />
149
+ <node el={prerenderFrame} />
150
+ </div>
151
+ );
152
+ }
153
+
154
+ return (
155
+ <div
156
+ id={uid}
157
+ onRender={setupAnimations("container")}
158
+ class="paypal-checkout-sandbox"
159
+ >
160
+ <style nonce={nonce}>{getSandboxStyle({ uid })}</style>
161
+ <iframe
162
+ title="PayPal Checkout Overlay"
163
+ name={overlayIframeName}
164
+ scrolling="no"
165
+ class={`paypal-checkout-sandbox-iframe${fullScreen ? "-full" : ""}`}
166
+ >
167
+ <html>
168
+ <body>
169
+ <div
170
+ dir="auto"
171
+ id={uid}
172
+ onClick={focusCheckout}
173
+ class={`paypal-overlay-context-${context} paypal-checkout-overlay`}
174
+ >
175
+ {!hideCloseButton && (
176
+ <a
177
+ href="#"
178
+ class="paypal-checkout-close"
179
+ onClick={closeCheckout}
180
+ aria-label="close"
181
+ role="button"
182
+ />
183
+ )}
184
+ {!fullScreen && (
185
+ <div class="paypal-checkout-modal">
186
+ <div class="paypal-checkout-logo" dir="ltr">
187
+ <PPLogo logoColor={LOGO_COLOR.WHITE} />
188
+ <PayPalLogo logoColor={LOGO_COLOR.WHITE} />
189
+ </div>
190
+ {content.windowMessage && (
191
+ <div class="paypal-checkout-message">
192
+ {content.windowMessage}
193
+ </div>
194
+ )}
195
+ <div class="paypal-checkout-focus-warning" />
196
+ {content.continueMessage && (
197
+ <div class="paypal-checkout-continue">
198
+ {/* This handler should be guarded with e.stopPropagation.
199
+ This will stop the event from bubbling up to the overlay click handler
200
+ and causing unexpected behavior. */}
201
+ <a onClick={focusCheckout} href="#">
202
+ {content.continueMessage}
203
+ </a>
204
+ </div>
205
+ )}
206
+ <div class="paypal-checkout-loader">
207
+ <div class="paypal-spinner" />
208
+ </div>
209
+ </div>
210
+ )}
211
+ <div
212
+ class={
213
+ fullScreen
214
+ ? "paypal-checkout-iframe-container-full"
215
+ : "paypal-checkout-iframe-container"
216
+ }
217
+ >
218
+ {outlet}
219
+ </div>
220
+
221
+ <style nonce={nonce}>{getContainerStyle({ uid })}</style>
222
+ </div>
223
+ </body>
224
+ </html>
225
+ </iframe>
226
+ </div>
227
+ );
228
+ }
@@ -0,0 +1,380 @@
1
+ /* @flow */
2
+
3
+ import { CONTEXT } from "@krakenjs/zoid/src";
4
+
5
+ export const CLASS = {
6
+ OUTLET: "outlet",
7
+ VISIBLE: "visible",
8
+ INVISIBLE: "invisible",
9
+ COMPONENT_FRAME: "component-frame",
10
+ PRERENDER_FRAME: "prerender-frame",
11
+ };
12
+
13
+ export function getSandboxStyle({ uid }: {| uid: string |}): string {
14
+ return `
15
+ #${uid}.paypal-checkout-sandbox {
16
+ display: block;
17
+ position: fixed;
18
+ top: 0;
19
+ left: 0;
20
+
21
+ width: 100%;
22
+ height: 100%;
23
+ width: 100vw;
24
+ height: 100vh;
25
+ max-width: 100%;
26
+ max-height: 100%;
27
+ min-width: 100%;
28
+ min-height: 100%;
29
+
30
+ z-index: 2147483647;
31
+
32
+ animation-duration: 0.3s;
33
+ animation-iteration-count: 1;
34
+ animation-fill-mode: forwards !important;
35
+ opacity: 0;
36
+ }
37
+
38
+ #${uid}.paypal-checkout-sandbox .paypal-checkout-sandbox-iframe {
39
+ display: block;
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ width: 100%;
44
+ height: 100%;
45
+ }
46
+
47
+ #${uid}.paypal-checkout-sandbox .paypal-checkout-sandbox-iframe-full {
48
+ border: 0;
49
+ height: 100%;
50
+ width: 100vw;
51
+ }
52
+
53
+ @keyframes show-container {
54
+ from {
55
+ opacity: 0;
56
+ }
57
+
58
+ to {
59
+ opacity: 1;
60
+ }
61
+ }
62
+
63
+ @keyframes hide-container {
64
+ from {
65
+ opacity: 1;
66
+ }
67
+
68
+ 50% {
69
+ opacity: 1;
70
+ }
71
+
72
+ to {
73
+ opacity: 0;
74
+ }
75
+ }
76
+ `;
77
+ }
78
+
79
+ export function getContainerStyle({ uid }: {| uid: string |}): string {
80
+ return `
81
+ #${uid} {
82
+ position: absolute;
83
+ z-index: 2147483647;
84
+ top: 0;
85
+ left: 0;
86
+ width: 100%;
87
+ height: 100%;
88
+
89
+ transform: translate3d(0, 0, 0);
90
+
91
+ background-color: black;
92
+ background-color: rgba(0, 0, 0, 0.8);
93
+ background: radial-gradient(50% 50%, ellipse closest-corner, rgba(0,0,0,0.6) 1%, rgba(0,0,0,0.8) 100%);
94
+
95
+ color: #fff;
96
+ }
97
+
98
+ #${uid} a {
99
+ color: #fff;
100
+ }
101
+
102
+ #${uid} .paypal-checkout-close:before,
103
+ #${uid} .paypal-checkout-close:after {
104
+ background-color: #fff;
105
+ }
106
+
107
+ #${uid}.paypal-overlay-context-${CONTEXT.POPUP} {
108
+ cursor: pointer;
109
+ }
110
+
111
+ #${uid} a {
112
+ text-decoration: none;
113
+ }
114
+
115
+ #${uid} .paypal-checkout-modal {
116
+ font-family: "HelveticaNeue", "HelveticaNeue-Light", "Helvetica Neue Light", helvetica, arial, sans-serif;
117
+ font-size: 14px;
118
+ text-align: center;
119
+
120
+ box-sizing: border-box;
121
+ max-width: 350px;
122
+ top: 50%;
123
+ left: 50%;
124
+ position: absolute;
125
+ transform: translateX(-50%) translateY(-50%);
126
+ cursor: pointer;
127
+ text-align: center;
128
+ }
129
+
130
+ #${uid}.paypal-overlay-loading .paypal-checkout-message, #${uid}.paypal-overlay-loading .paypal-checkout-continue {
131
+ display: none;
132
+ }
133
+
134
+ .paypal-checkout-loader {
135
+ display: none;
136
+ }
137
+
138
+ #${uid}.paypal-overlay-loading .paypal-checkout-loader {
139
+ display: block;
140
+ }
141
+
142
+ #${uid} .paypal-checkout-modal .paypal-checkout-logo {
143
+ cursor: pointer;
144
+ margin-bottom: 30px;
145
+ display: inline-block;
146
+ }
147
+
148
+ #${uid} .paypal-checkout-modal .paypal-checkout-logo img {
149
+ height: 36px;
150
+ }
151
+
152
+ #${uid} .paypal-checkout-modal .paypal-checkout-logo img.paypal-checkout-logo-pp {
153
+ margin-right: 10px;
154
+ }
155
+
156
+ #${uid} .paypal-checkout-modal .paypal-checkout-message {
157
+ font-size: 15px;
158
+ line-height: 1.5;
159
+ padding: 10px 0;
160
+ }
161
+
162
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-message, #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-continue {
163
+ display: none;
164
+ }
165
+
166
+ #${uid} .paypal-checkout-modal .paypal-checkout-continue {
167
+ font-size: 15px;
168
+ line-height: 1.35;
169
+ padding: 10px 0;
170
+ font-weight: bold;
171
+ }
172
+
173
+ #${uid} .paypal-checkout-modal .paypal-checkout-continue a {
174
+ border-bottom: 1px solid white;
175
+ }
176
+
177
+ #${uid} .paypal-checkout-close {
178
+ position: absolute;
179
+ right: 16px;
180
+ top: 16px;
181
+ width: 16px;
182
+ height: 16px;
183
+ opacity: 0.6;
184
+ }
185
+
186
+ #${uid}.paypal-overlay-loading .paypal-checkout-close {
187
+ display: none;
188
+ }
189
+
190
+ #${uid} .paypal-checkout-close:hover {
191
+ opacity: 1;
192
+ }
193
+
194
+ #${uid} .paypal-checkout-close:before, .paypal-checkout-close:after {
195
+ position: absolute;
196
+ left: 8px;
197
+ content: ' ';
198
+ height: 16px;
199
+ width: 2px;
200
+ }
201
+
202
+ #${uid} .paypal-checkout-close:before {
203
+ transform: rotate(45deg);
204
+ }
205
+
206
+ #${uid} .paypal-checkout-close:after {
207
+ transform: rotate(-45deg);
208
+ }
209
+
210
+ #${uid} .paypal-checkout-focus-warning {
211
+ font-size: 14px;
212
+ line-height: 1.35;
213
+ padding: 10px 0;
214
+ }
215
+
216
+ #${uid} .paypal-checkout-iframe-container {
217
+ display: none;
218
+ }
219
+
220
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container,
221
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container > .${CLASS.OUTLET},
222
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container > .${CLASS.OUTLET} > iframe {
223
+ max-height: 95vh;
224
+ max-width: 95vw;
225
+ }
226
+
227
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container-full,
228
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container-full > .${CLASS.OUTLET},
229
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container-full > .${CLASS.OUTLET} > iframe {
230
+ height: 100vh;
231
+ max-width: 100vw;
232
+ width: 100vw;
233
+ }
234
+
235
+ @media screen and (max-width: 470px) {
236
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container,
237
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container > .${CLASS.OUTLET},
238
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container > .${CLASS.OUTLET} > iframe {
239
+ max-height: 85vh;
240
+ }
241
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container-full,
242
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container-full > .${CLASS.OUTLET},
243
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container-full > .${CLASS.OUTLET} > iframe {
244
+ height: 100vh;
245
+ }
246
+ }
247
+
248
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container {
249
+
250
+ display: block;
251
+
252
+ position: absolute;
253
+
254
+ top: 50%;
255
+ left: 50%;
256
+
257
+ min-width: 450px;
258
+
259
+ transform: translate(-50%, -50%);
260
+ transform: translate3d(-50%, -50%, 0);
261
+
262
+ border-radius: 10px;
263
+ overflow: hidden;
264
+ }
265
+
266
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} {
267
+
268
+ position: relative;
269
+
270
+ transition: all 0.3s ease;
271
+ animation-duration: 0.3s;
272
+ animation-fill-mode: forwards !important;
273
+
274
+ min-width: 450px;
275
+ max-width: 450px;
276
+ width: 450px;
277
+ height: 535px;
278
+
279
+ background-color: white;
280
+
281
+ overflow: auto;
282
+
283
+ opacity: 0;
284
+ transform: scale3d(.3, .3, .3);
285
+
286
+ -webkit-overflow-scrolling: touch;
287
+ }
288
+
289
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} > iframe {
290
+ position: absolute;
291
+ top: 0;
292
+ left: 0;
293
+ transition: opacity .4s ease-in-out;
294
+ }
295
+
296
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} > iframe.${CLASS.COMPONENT_FRAME} {
297
+ z-index: 100;
298
+ }
299
+
300
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} > iframe.${CLASS.PRERENDER_FRAME} {
301
+ z-index: 200;
302
+ }
303
+
304
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} > iframe.${CLASS.VISIBLE} {
305
+ opacity: 1;
306
+ z-index: 200;
307
+ }
308
+
309
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} > iframe.${CLASS.INVISIBLE} {
310
+ opacity: 0;
311
+ z-index: 100;
312
+ }
313
+
314
+ @media screen and (max-width: 470px) {
315
+
316
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .paypal-checkout-iframe-container,
317
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} {
318
+ min-width: 100%;
319
+ min-width: calc(100% - 20px);
320
+
321
+ max-width: 100%;
322
+ max-width: calc(100% - 20px);
323
+ }
324
+ }
325
+
326
+ #${uid}.paypal-overlay-context-${CONTEXT.IFRAME} .${CLASS.OUTLET} iframe {
327
+ width: 1px;
328
+ min-width: 100%;
329
+ height: 100%;
330
+ }
331
+
332
+ @keyframes show-component {
333
+ from {
334
+ opacity: 0;
335
+ transform: scale3d(.3, .3, .3);
336
+ }
337
+
338
+ to {
339
+ opacity: 1;
340
+ transform: scale3d(1, 1, 1);
341
+ }
342
+ }
343
+
344
+ @keyframes hide-component {
345
+ from {
346
+ opacity: 1;
347
+ transform: scale3d(1, 1, 1);
348
+ }
349
+
350
+ to {
351
+ opacity: 0;
352
+ transform: scale3d(.3, .3, .3);
353
+ }
354
+ }
355
+
356
+ .paypal-spinner {
357
+ height: 30px;
358
+ width: 30px;
359
+ display: inline-block;
360
+ box-sizing: content-box;
361
+ opacity: 1;
362
+ filter: alpha(opacity=100);
363
+ animation: rotation .7s infinite linear;
364
+ border-left: 8px solid rgba(0, 0, 0, .2);
365
+ border-right: 8px solid rgba(0, 0, 0, .2);
366
+ border-bottom: 8px solid rgba(0, 0, 0, .2);
367
+ border-top: 8px solid #fff;
368
+ border-radius: 100%
369
+ }
370
+
371
+ @keyframes rotation {
372
+ from {
373
+ transform: rotate(0deg)
374
+ }
375
+ to {
376
+ transform: rotate(359deg)
377
+ }
378
+ }
379
+ `;
380
+ }