@paypal/checkout-components 5.0.295-alpha.201 → 5.0.295-alpha.221

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.295-alpha.201",
3
+ "version": "5.0.295-alpha.221",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,6 +5,7 @@ import { getButtonsComponent } from "../zoid/buttons";
5
5
  import {
6
6
  buildHostedButtonCreateOrder,
7
7
  buildHostedButtonOnApprove,
8
+ buildOpenPopup,
8
9
  getHostedButtonDetails,
9
10
  renderForm,
10
11
  getMerchantID,
@@ -24,7 +25,7 @@ export const getHostedButtonsComponent = (): HostedButtonsComponent => {
24
25
  const merchantId = getMerchantID();
25
26
 
26
27
  getHostedButtonDetails({ hostedButtonId }).then(
27
- ({ html, htmlScript, style }) => {
28
+ ({ html, htmlScript, style, popupFallback }) => {
28
29
  const { onInit, onClick } = renderForm({
29
30
  hostedButtonId,
30
31
  html,
@@ -32,6 +33,8 @@ export const getHostedButtonsComponent = (): HostedButtonsComponent => {
32
33
  selector,
33
34
  });
34
35
 
36
+ const openPopup = buildOpenPopup({ selector, popupFallback });
37
+
35
38
  // $FlowFixMe
36
39
  Buttons({
37
40
  hostedButtonId,
@@ -45,6 +48,7 @@ export const getHostedButtonsComponent = (): HostedButtonsComponent => {
45
48
  onApprove: buildHostedButtonOnApprove({
46
49
  hostedButtonId,
47
50
  merchantId,
51
+ openPopup,
48
52
  }),
49
53
  }).render(selector);
50
54
  }
@@ -9,6 +9,7 @@ export type HostedButtonsComponentProps = {|
9
9
  export type GetCallbackProps = {|
10
10
  hostedButtonId: string,
11
11
  merchantId?: string,
12
+ openPopup?: (url: string) => void,
12
13
  |};
13
14
 
14
15
  export type HostedButtonsInstance = {|
@@ -25,6 +26,7 @@ export type HostedButtonDetailsParams =
25
26
  color: string,
26
27
  label: string,
27
28
  |},
29
+ popupFallback: string,
28
30
  |}>;
29
31
 
30
32
  export type ButtonVariables = $ReadOnlyArray<{|
@@ -34,7 +36,7 @@ export type ButtonVariables = $ReadOnlyArray<{|
34
36
 
35
37
  export type CreateOrder = (data: {|
36
38
  paymentSource: string,
37
- |}) => ZalgoPromise<string>;
39
+ |}) => ZalgoPromise<string | void>;
38
40
 
39
41
  export type OnApprove = (data: {|
40
42
  orderID: string,
@@ -55,3 +57,8 @@ export type RenderForm = ({|
55
57
  onInit: (data: mixed, actions: mixed) => void,
56
58
  onClick: (data: mixed, actions: mixed) => void,
57
59
  |};
60
+
61
+ export type BuildOpenPopup = ({|
62
+ popupFallback: string,
63
+ selector: string | HTMLElement,
64
+ |}) => (url: string) => void;
@@ -1,6 +1,6 @@
1
1
  /* @flow */
2
2
 
3
- import { request, memoize, popup, supportsPopups } from "@krakenjs/belter/src";
3
+ import { request, memoize, popup } from "@krakenjs/belter/src";
4
4
  import {
5
5
  getSDKHost,
6
6
  getClientID,
@@ -11,6 +11,7 @@ import { FUNDING } from "@paypal/sdk-constants/src";
11
11
  import { DEFAULT_POPUP_SIZE } from "../zoid/checkout";
12
12
 
13
13
  import type {
14
+ BuildOpenPopup,
14
15
  ButtonVariables,
15
16
  CreateAccessToken,
16
17
  CreateOrder,
@@ -23,6 +24,7 @@ import type {
23
24
  const entryPoint = "SDK";
24
25
  const baseUrl = `https://${getSDKHost()}`;
25
26
  const apiUrl = baseUrl.replace("www", "api");
27
+ export const popupFallbackClassName = "paypal-popup-fallback";
26
28
 
27
29
  const getHeaders = (accessToken?: string) => ({
28
30
  ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
@@ -75,6 +77,7 @@ export const getHostedButtonDetails: HostedButtonDetailsParams = ({
75
77
  },
76
78
  html: body.html,
77
79
  htmlScript: body.html_script,
80
+ popupFallback: body.popup_fallback,
78
81
  };
79
82
  });
80
83
  };
@@ -115,6 +118,7 @@ export const buildHostedButtonCreateOrder = ({
115
118
  return (data) => {
116
119
  const userInputs =
117
120
  window[`__pp_form_fields_${hostedButtonId}`]?.getUserInputs?.() || {};
121
+ const onError = window[`__pp_form_fields_${hostedButtonId}`]?.onError;
118
122
  return createAccessToken(getClientID()).then((accessToken) => {
119
123
  return request({
120
124
  url: `${apiUrl}/v1/checkout/links/${hostedButtonId}/create-context`,
@@ -126,19 +130,37 @@ export const buildHostedButtonCreateOrder = ({
126
130
  merchant_id: merchantId,
127
131
  ...userInputs,
128
132
  }),
129
- }).then(({ body }) => {
130
- if (!body.context_id) {
131
- window[`__pp_form_fields_${hostedButtonId}`]?.onError?.(body.name);
132
- }
133
- return body.context_id;
134
- });
133
+ })
134
+ .then(({ body }) => body.context_id || onError(body.name))
135
+ .catch(() => onError("REQUEST_FAILED"));
135
136
  });
136
137
  };
137
138
  };
138
139
 
140
+ export const buildOpenPopup: BuildOpenPopup = ({ popupFallback, selector }) => {
141
+ return (url) => {
142
+ try {
143
+ popup(url, {
144
+ width: DEFAULT_POPUP_SIZE.WIDTH,
145
+ height: DEFAULT_POPUP_SIZE.HEIGHT,
146
+ });
147
+ } catch (e) {
148
+ const div = document.createElement("div");
149
+ div.classList.add(popupFallbackClassName);
150
+ div.innerHTML = popupFallback.replace("#", url);
151
+ const container =
152
+ typeof selector === "string"
153
+ ? document.querySelector(selector)
154
+ : selector;
155
+ container?.appendChild(div);
156
+ }
157
+ };
158
+ };
159
+
139
160
  export const buildHostedButtonOnApprove = ({
140
161
  hostedButtonId,
141
162
  merchantId,
163
+ openPopup,
142
164
  }: GetCallbackProps): OnApprove => {
143
165
  return (data) => {
144
166
  return createAccessToken(getClientID()).then((accessToken) => {
@@ -152,19 +174,14 @@ export const buildHostedButtonOnApprove = ({
152
174
  context_id: data.orderID,
153
175
  }),
154
176
  }).then((response) => {
177
+ // remove the popup fallback message, if present
178
+ document.querySelector(`.${popupFallbackClassName}`)?.remove();
155
179
  // The "Debit or Credit Card" button does not open a popup
156
180
  // so we need to open a new popup for buyers who complete
157
181
  // a checkout via "Debit or Credit Card".
158
182
  if (data.paymentSource === FUNDING.CARD) {
159
183
  const url = `${baseUrl}/ncp/payment/${hostedButtonId}/${data.orderID}`;
160
- if (supportsPopups()) {
161
- popup(url, {
162
- width: DEFAULT_POPUP_SIZE.WIDTH,
163
- height: DEFAULT_POPUP_SIZE.HEIGHT,
164
- });
165
- } else {
166
- window.location = url;
167
- }
184
+ openPopup?.(url);
168
185
  }
169
186
  return response;
170
187
  });
@@ -1,13 +1,15 @@
1
1
  /* @flow */
2
2
 
3
3
  import { test, expect, vi } from "vitest";
4
- import { request, popup, supportsPopups } from "@krakenjs/belter/src";
4
+ import { request, popup } from "@krakenjs/belter/src";
5
5
  import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
6
6
 
7
7
  import {
8
8
  buildHostedButtonCreateOrder,
9
9
  buildHostedButtonOnApprove,
10
+ buildOpenPopup,
10
11
  getHostedButtonDetails,
12
+ popupFallbackClassName,
11
13
  } from "./utils";
12
14
 
13
15
  vi.mock("@krakenjs/belter/src", async () => {
@@ -152,10 +154,20 @@ describe("buildHostedButtonOnApprove", () => {
152
154
  expect.assertions(1);
153
155
  });
154
156
 
155
- test("provides its own popup for inline guest", async () => {
157
+ describe("inline guest", () => {
158
+ const url = "https://example.com/ncp/payment/B1234567890/EC-1234567890";
159
+ const selector = "buttons-container";
160
+ // $FlowIssue
161
+ document.body.innerHTML = `<div id="${selector}"></div>`; // eslint-disable-line compat/compat
162
+ const popupFallback = `<a href="#">See payment details</a>`;
163
+ const openPopup = buildOpenPopup({
164
+ popupFallback,
165
+ selector: `#${selector}`,
166
+ });
156
167
  const onApprove = buildHostedButtonOnApprove({
157
168
  hostedButtonId,
158
169
  merchantId,
170
+ openPopup,
159
171
  });
160
172
  // $FlowIssue
161
173
  request.mockImplementation(() =>
@@ -164,19 +176,55 @@ describe("buildHostedButtonOnApprove", () => {
164
176
  })
165
177
  );
166
178
 
167
- // $FlowIssue
168
- supportsPopups.mockImplementation(() => true);
169
- await onApprove({ orderID, paymentSource: "card" });
170
- expect(popup).toHaveBeenCalled();
179
+ test("provides its own popup", async () => {
180
+ await onApprove({ orderID, paymentSource: "card" });
181
+ expect(popup).toHaveBeenCalledWith(url, expect.anything());
182
+ expect.assertions(1);
183
+ });
171
184
 
172
- // but redirects if popups are not supported
173
- // $FlowIssue
174
- supportsPopups.mockImplementation(() => false);
175
- await onApprove({ orderID, paymentSource: "card" });
176
- expect(window.location).toMatch(
177
- `/ncp/payment/${hostedButtonId}/${orderID}`
178
- );
185
+ test("appends a link if the popup is blocked", async () => {
186
+ // $FlowIssue
187
+ popup.mockImplementationOnce(() => {
188
+ throw new Error("popup_blocked");
189
+ });
190
+ await onApprove({ orderID, paymentSource: "card" });
191
+ const link = document.querySelector(`.${popupFallbackClassName} a`);
192
+ expect(link?.getAttribute("href")).toBe(url);
193
+ expect.assertions(1);
194
+ });
179
195
 
180
- expect.assertions(2);
196
+ test("does not append a second link if the popup is blocked a second time", async () => {
197
+ // still present from the previous popup open failure
198
+ expect(
199
+ document.querySelectorAll(`.${popupFallbackClassName}`).length
200
+ ).toBe(1);
201
+ // $FlowIssue
202
+ popup.mockImplementationOnce(() => {
203
+ throw new Error("popup_blocked");
204
+ });
205
+ await onApprove({ orderID, paymentSource: "card" });
206
+ expect(
207
+ document.querySelectorAll(`.${popupFallbackClassName}`).length
208
+ ).toBe(1);
209
+ expect.assertions(2);
210
+ });
211
+
212
+ test("removes the fallback message if a different payment source is used", async () => {
213
+ // still present from the previous popup open failure
214
+ expect(
215
+ document.querySelectorAll(`.${popupFallbackClassName}`).length
216
+ ).toBe(1);
217
+ // $FlowIssue
218
+ popup.mockImplementationOnce(() => {
219
+ throw new Error("popup_blocked");
220
+ });
221
+
222
+ // note new payment source
223
+ await onApprove({ orderID, paymentSource: "paypal" });
224
+ expect(
225
+ document.querySelectorAll(`.${popupFallbackClassName}`).length
226
+ ).toBe(0);
227
+ expect.assertions(2);
228
+ });
181
229
  });
182
230
  });