@paypal/checkout-components 5.0.292-alpha.11 → 5.0.293-alpha.11

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.292-alpha.11",
3
+ "version": "5.0.293-alpha.11",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/api/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* @flow */
2
2
 
3
3
  import { getPartnerAttributionID, getSessionID } from "@paypal/sdk-client/src";
4
- import { request } from "@krakenjs/belter/src";
4
+ import { inlineMemoize, request } from "@krakenjs/belter/src";
5
5
  import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
6
6
 
7
7
  import { HEADERS } from "../constants/api";
@@ -52,3 +52,16 @@ export function callRestAPI({
52
52
  return body;
53
53
  });
54
54
  }
55
+
56
+ export function callMemoizedRestAPI({
57
+ accessToken,
58
+ method,
59
+ url,
60
+ data,
61
+ }: RestAPIParams): ZalgoPromise<Object> {
62
+ return inlineMemoize(
63
+ callMemoizedRestAPI,
64
+ () => callRestAPI({ accessToken, method, url, data }),
65
+ [accessToken, method, url, JSON.stringify(data)]
66
+ );
67
+ }
@@ -17,7 +17,7 @@ import { FPTI_KEY } from "@paypal/sdk-constants/src";
17
17
  import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
18
18
  import { stringifyError } from "@krakenjs/belter/src";
19
19
 
20
- import { callRestAPI } from "../api";
20
+ import { callMemoizedRestAPI } from "../api";
21
21
  import {
22
22
  ELIGIBLE_PAYMENT_METHODS,
23
23
  FPTI_TRANSITION,
@@ -134,7 +134,7 @@ export function getShopperInsightsComponent(): ShopperInsightsComponent {
134
134
  const requestPayload =
135
135
  createRecommendedPaymentMethodsRequestPayload(merchantPayload);
136
136
 
137
- return callRestAPI({
137
+ return callMemoizedRestAPI({
138
138
  method: "POST",
139
139
  url: `${getPayPalAPIDomain()}/${ELIGIBLE_PAYMENT_METHODS}`,
140
140
  data: requestPayload,
@@ -2,8 +2,7 @@
2
2
  import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
3
3
  import { getEnv, getBuyerCountry } from "@paypal/sdk-client/src";
4
4
  import { vi, describe, expect } from "vitest";
5
-
6
- import { callRestAPI } from "../api";
5
+ import { request } from "@krakenjs/belter/src";
7
6
 
8
7
  import { getShopperInsightsComponent } from "./component";
9
8
 
@@ -25,23 +24,22 @@ vi.mock("@paypal/sdk-client/src", () => {
25
24
  };
26
25
  });
27
26
 
28
- vi.mock("../api", async () => {
29
- const actual = await vi.importActual("../api");
27
+ vi.mock("@krakenjs/belter/src", async () => {
28
+ const actual = await vi.importActual("@krakenjs/belter/src");
30
29
  return {
31
30
  ...actual,
32
- callRestAPI: vi.fn(() =>
31
+ request: vi.fn(() =>
33
32
  ZalgoPromise.resolve({
34
- eligible_methods: {
35
- paypal: {
36
- can_be_vaulted: true,
37
- eligible_in_paypal_network: true,
38
- recommended: true,
39
- recommended_priority: 1,
40
- },
41
- venmo: {
42
- can_be_vaulted: true,
43
- eligible_in_paypal_network: true,
44
- recommended: false,
33
+ status: 200,
34
+ headers: {},
35
+ body: {
36
+ eligible_methods: {
37
+ paypal: {
38
+ can_be_vaulted: false,
39
+ eligible_in_paypal_network: true,
40
+ recommended: true,
41
+ recommended_priority: 1,
42
+ },
45
43
  },
46
44
  },
47
45
  })
@@ -67,7 +65,7 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
67
65
  },
68
66
  });
69
67
 
70
- expect(callRestAPI).toHaveBeenCalled();
68
+ expect(request).toHaveBeenCalled();
71
69
  expect(recommendedPaymentMethods).toEqual({
72
70
  isPayPalRecommended: true,
73
71
  isVenmoRecommended: false,
@@ -75,12 +73,79 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
75
73
  expect.assertions(2);
76
74
  });
77
75
 
76
+ test("should get recommended payment methods from memoized request for the exact same payload", async () => {
77
+ const shopperInsightsComponent = getShopperInsightsComponent();
78
+ const payload = {
79
+ customer: {
80
+ email: "email-1.0@test.com",
81
+ phone: {
82
+ countryCode: "1",
83
+ nationalNumber: "2345678901",
84
+ },
85
+ },
86
+ };
87
+ const response1 =
88
+ await shopperInsightsComponent.getRecommendedPaymentMethods(payload);
89
+ expect(request).toHaveBeenCalled();
90
+ expect(request).toHaveBeenCalledTimes(1);
91
+ const response2 =
92
+ await shopperInsightsComponent.getRecommendedPaymentMethods(payload);
93
+
94
+ expect(request).toHaveBeenCalled();
95
+ // This should not change as the payload is same
96
+ expect(request).toHaveBeenCalledTimes(1);
97
+ expect(response1).toEqual({
98
+ isPayPalRecommended: true,
99
+ isVenmoRecommended: false,
100
+ });
101
+ expect(response2).toEqual({
102
+ isPayPalRecommended: true,
103
+ isVenmoRecommended: false,
104
+ });
105
+ expect.assertions(6);
106
+ });
107
+
108
+ test("should not get recommended payment methods from memoized request for a different payload", async () => {
109
+ const shopperInsightsComponent = getShopperInsightsComponent();
110
+ const response1 =
111
+ await shopperInsightsComponent.getRecommendedPaymentMethods({
112
+ customer: {
113
+ email: "email-1.1@test.com",
114
+ },
115
+ });
116
+ expect(request).toHaveBeenCalled();
117
+ expect(request).toHaveBeenCalledTimes(1);
118
+ const response2 =
119
+ await shopperInsightsComponent.getRecommendedPaymentMethods({
120
+ customer: {
121
+ email: "email-1.2@test.com",
122
+ },
123
+ });
124
+
125
+ expect(request).toHaveBeenCalled();
126
+ // This must change to 2 as the payload is different
127
+ expect(request).toHaveBeenCalledTimes(2);
128
+ expect(response1).toEqual({
129
+ isPayPalRecommended: true,
130
+ isVenmoRecommended: false,
131
+ });
132
+ expect(response2).toEqual({
133
+ isPayPalRecommended: true,
134
+ isVenmoRecommended: false,
135
+ });
136
+ expect.assertions(6);
137
+ });
138
+
78
139
  test("catch errors from the API", async () => {
79
140
  // $FlowFixMe
80
- callRestAPI.mockImplementationOnce(() =>
81
- ZalgoPromise.reject({
82
- name: "ERROR",
83
- message: "This is an API error",
141
+ request.mockImplementationOnce(() =>
142
+ ZalgoPromise.resolve({
143
+ status: 400,
144
+ headers: {},
145
+ body: {
146
+ name: "ERROR",
147
+ message: "This is an API error",
148
+ },
84
149
  })
85
150
  );
86
151
 
@@ -92,12 +157,16 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
92
157
  email: "email@test.com",
93
158
  phone: {
94
159
  countryCode: "1",
95
- nationalNumber: "2345678901",
160
+ nationalNumber: "2345678905",
96
161
  },
97
162
  },
98
163
  })
99
- ).rejects.toThrow("This is an API error");
100
- expect(callRestAPI).toHaveBeenCalled();
164
+ ).rejects.toThrow(
165
+ new Error(
166
+ `https://api.paypal.com/v2/payments/find-eligible-methods returned status 400\n\n{"name":"ERROR","message":"This is an API error"}`
167
+ )
168
+ );
169
+ expect(request).toHaveBeenCalled();
101
170
  expect.assertions(2);
102
171
  });
103
172
 
@@ -105,22 +174,22 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
105
174
  const shopperInsightsComponent = getShopperInsightsComponent();
106
175
  await shopperInsightsComponent.getRecommendedPaymentMethods({
107
176
  customer: {
108
- email: "email@test.com",
177
+ email: "email10@test.com",
109
178
  phone: {
110
179
  countryCode: "1",
111
- nationalNumber: "2345678901",
180
+ nationalNumber: "2345678906",
112
181
  },
113
182
  },
114
183
  });
115
184
 
116
- expect(callRestAPI).toHaveBeenCalledWith(
185
+ expect(request).toHaveBeenCalledWith(
117
186
  expect.objectContaining({
118
- data: expect.objectContaining({
187
+ json: expect.objectContaining({
119
188
  customer: expect.objectContaining({
120
- email: "email@test.com",
189
+ email: "email10@test.com",
121
190
  phone: expect.objectContaining({
122
191
  country_code: "1",
123
- national_number: "2345678901",
192
+ national_number: "2345678906",
124
193
  }),
125
194
  }),
126
195
  }),
@@ -132,15 +201,15 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
132
201
  const shopperInsightsComponent = getShopperInsightsComponent();
133
202
  await shopperInsightsComponent.getRecommendedPaymentMethods({
134
203
  customer: {
135
- email: "email@test.com",
204
+ email: "email2@test.com",
136
205
  },
137
206
  });
138
207
 
139
- expect(callRestAPI).toHaveBeenCalledWith(
208
+ expect(request).toHaveBeenCalledWith(
140
209
  expect.objectContaining({
141
- data: expect.objectContaining({
210
+ json: expect.objectContaining({
142
211
  customer: expect.objectContaining({
143
- email: "email@test.com",
212
+ email: "email2@test.com",
144
213
  }),
145
214
  }),
146
215
  })
@@ -151,7 +220,7 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
151
220
  const shopperInsightsComponent = getShopperInsightsComponent();
152
221
  await shopperInsightsComponent.getRecommendedPaymentMethods({
153
222
  customer: {
154
- email: "email@test.com",
223
+ email: "email5@test.com",
155
224
  phone: {
156
225
  countryCode: "1",
157
226
  nationalNumber: "2345678901",
@@ -159,9 +228,9 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
159
228
  },
160
229
  });
161
230
 
162
- expect(callRestAPI).toHaveBeenCalledWith(
231
+ expect(request).toHaveBeenCalledWith(
163
232
  expect.objectContaining({
164
- data: expect.objectContaining({
233
+ json: expect.objectContaining({
165
234
  customer: expect.objectContaining({
166
235
  phone: expect.objectContaining({
167
236
  country_code: "1",
@@ -177,13 +246,13 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
177
246
  const shopperInsightsComponent = getShopperInsightsComponent();
178
247
  await shopperInsightsComponent.getRecommendedPaymentMethods({
179
248
  customer: {
180
- email: "email@test.com",
249
+ email: "email6@test.com",
181
250
  },
182
251
  });
183
252
 
184
- expect(callRestAPI).toHaveBeenCalledWith(
253
+ expect(request).toHaveBeenCalledWith(
185
254
  expect.objectContaining({
186
- data: expect.objectContaining({
255
+ json: expect.objectContaining({
187
256
  purchase_units: expect.arrayContaining([
188
257
  expect.objectContaining({
189
258
  amount: expect.objectContaining({
@@ -203,13 +272,13 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
203
272
  const shopperInsightsComponent = getShopperInsightsComponent();
204
273
  await shopperInsightsComponent.getRecommendedPaymentMethods({
205
274
  customer: {
206
- email: "email@test.com",
275
+ email: "email7@test.com",
207
276
  },
208
277
  });
209
278
 
210
- expect(callRestAPI).toHaveBeenCalledWith(
279
+ expect(request).toHaveBeenCalledWith(
211
280
  expect.objectContaining({
212
- data: expect.objectContaining({
281
+ json: expect.objectContaining({
213
282
  customer: expect.objectContaining({
214
283
  country_code: "US",
215
284
  }),
@@ -227,13 +296,13 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
227
296
  const shopperInsightsComponent = getShopperInsightsComponent();
228
297
  await shopperInsightsComponent.getRecommendedPaymentMethods({
229
298
  customer: {
230
- email: "email@test.com",
299
+ email: "email9@test.com",
231
300
  },
232
301
  });
233
302
 
234
- expect(callRestAPI).toHaveBeenCalledWith(
303
+ expect(request).toHaveBeenCalledWith(
235
304
  expect.objectContaining({
236
- data: expect.objectContaining({
305
+ json: expect.objectContaining({
237
306
  customer: expect.objectContaining({
238
307
  country_code: "US",
239
308
  }),
@@ -250,9 +319,9 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
250
319
  },
251
320
  });
252
321
 
253
- expect(callRestAPI).toHaveBeenCalledWith(
322
+ expect(request).toHaveBeenCalledWith(
254
323
  expect.objectContaining({
255
- data: expect.objectContaining({
324
+ json: expect.objectContaining({
256
325
  customer: expect.not.objectContaining({
257
326
  country_code: expect.anything(),
258
327
  }),
@@ -265,13 +334,13 @@ describe("shopper insights component - getRecommendedPaymentMethods()", () => {
265
334
  const shopperInsightsComponent = getShopperInsightsComponent();
266
335
  await shopperInsightsComponent.getRecommendedPaymentMethods({
267
336
  customer: {
268
- email: "email@test.com",
337
+ email: "email9@test.com",
269
338
  },
270
339
  });
271
340
 
272
- expect(callRestAPI).toHaveBeenCalledWith(
341
+ expect(request).toHaveBeenCalledWith(
273
342
  expect.objectContaining({
274
- data: expect.objectContaining({
343
+ json: expect.objectContaining({
275
344
  preferences: expect.objectContaining({
276
345
  include_account_details: true,
277
346
  }),
@@ -1,13 +1,12 @@
1
1
  /* @flow */
2
2
 
3
- import type { LazyProtectedExport } from "../../types";
4
- import { protectedExport } from "../../lib";
3
+ import type { LazyExport } from "../../types";
5
4
 
6
5
  import {
7
6
  getShopperInsightsComponent,
8
7
  type ShopperInsightsComponent,
9
8
  } from "./component";
10
9
 
11
- export const ShopperInsights: LazyProtectedExport<ShopperInsightsComponent> = {
12
- __get__: () => protectedExport(getShopperInsightsComponent()),
10
+ export const ShopperInsights: LazyExport<ShopperInsightsComponent> = {
11
+ __get__: () => getShopperInsightsComponent(),
13
12
  };
@@ -1,6 +1,6 @@
1
1
  /* @flow */
2
2
  import { loadAxo } from "@paypal/connect-loader-component";
3
- import { stringifyError, getCurrentScriptUID } from "@krakenjs/belter/src";
3
+ import { stringifyError } from "@krakenjs/belter/src";
4
4
  import {
5
5
  getClientID,
6
6
  getClientMetadataID,
@@ -10,14 +10,47 @@ import {
10
10
  loadFraudnet,
11
11
  getCSPNonce,
12
12
  getDebug,
13
+ getSessionID,
13
14
  } from "@paypal/sdk-client/src";
14
15
  import { FPTI_KEY } from "@paypal/sdk-constants";
15
16
 
16
17
  import { sendCountMetric } from "./sendCountMetric";
17
18
 
19
+ const MIN_MAJOR_VERSION = 3;
20
+ const MIN_MINOR_VERSION = 97;
21
+ export const MIN_BT_VERSION = `${MIN_MAJOR_VERSION}.${MIN_MINOR_VERSION}.3-connect-alpha.6.1`; // Minimum for supporting AXO
22
+
23
+ export function getSdkVersion(version: string | null): string {
24
+ if (!version) {
25
+ return MIN_BT_VERSION;
26
+ }
27
+ const versionSplit = version.split(".");
28
+ const majorVersion = Number(versionSplit[0]);
29
+ const minorVersion = Number(versionSplit[1]);
30
+
31
+ if (
32
+ majorVersion < MIN_MAJOR_VERSION ||
33
+ (majorVersion === MIN_MAJOR_VERSION && minorVersion < MIN_MINOR_VERSION)
34
+ ) {
35
+ sendCountMetric({
36
+ name: "pp.app.paypal_sdk.connect.init.error.count",
37
+ event: "error",
38
+ dimensions: {
39
+ errorName: "braintree_version_not_supported_error",
40
+ },
41
+ });
42
+
43
+ throw new Error(
44
+ `The braintree version used does not support Connect. Please use version ${MIN_BT_VERSION} or above`
45
+ );
46
+ }
47
+
48
+ return version;
49
+ }
50
+
18
51
  // $FlowFixMe
19
52
  export const getConnectComponent = async (merchantProps = {}) => {
20
- const cmid = getClientMetadataID();
53
+ const cmid = getClientMetadataID() || getSessionID();
21
54
  const clientID = getClientID();
22
55
  const userIdToken = getUserIDToken();
23
56
  const env = getEnv();
@@ -25,7 +58,7 @@ export const getConnectComponent = async (merchantProps = {}) => {
25
58
 
26
59
  const { collect } = loadFraudnet({
27
60
  env,
28
- clientMetadataID: cmid || getCurrentScriptUID(),
61
+ clientMetadataID: cmid,
29
62
  cspNonce,
30
63
  appName: "ppcp-sdk-connect",
31
64
  // queryStringParams = {}, // TODO: what do we need here in this case?
@@ -43,7 +76,7 @@ export const getConnectComponent = async (merchantProps = {}) => {
43
76
  try {
44
77
  loadResult = await loadAxo({
45
78
  platform: "PPCP",
46
- btSdkVersion: "3.97.3-connect-alpha.6.1",
79
+ btSdkVersion: getSdkVersion(window?.braintree?.version ?? null),
47
80
  minified: !debugEnabled,
48
81
  metadata,
49
82
  });
@@ -78,6 +111,7 @@ export const getConnectComponent = async (merchantProps = {}) => {
78
111
  clientID,
79
112
  fraudnet: collect,
80
113
  clientMetadataId: cmid,
114
+ env,
81
115
  },
82
116
  });
83
117
  getLogger()
@@ -8,7 +8,11 @@ import {
8
8
  import { loadAxo } from "@paypal/connect-loader-component";
9
9
  import { describe, expect, test, vi } from "vitest";
10
10
 
11
- import { getConnectComponent } from "./component";
11
+ import {
12
+ getConnectComponent,
13
+ getSdkVersion,
14
+ MIN_BT_VERSION,
15
+ } from "./component";
12
16
  import { sendCountMetric } from "./sendCountMetric";
13
17
 
14
18
  vi.mock("@paypal/sdk-client/src", () => {
@@ -72,6 +76,7 @@ describe("getConnectComponent: returns ConnectComponent", () => {
72
76
  clientMetadataId: "mock-cmid",
73
77
  userIdToken: "mock-uid",
74
78
  fraudnet: expect.any(Function),
79
+ env: "mock-env",
75
80
  },
76
81
  });
77
82
  expect(sendCountMetric).toBeCalledTimes(2);
@@ -107,3 +112,32 @@ describe("getConnectComponent: returns ConnectComponent", () => {
107
112
  });
108
113
  });
109
114
  });
115
+
116
+ describe("getSdkVersion", () => {
117
+ test("returns minimum supported braintree version for AXO if input version is null", () => {
118
+ const version = getSdkVersion(null);
119
+
120
+ expect(version).toEqual(MIN_BT_VERSION);
121
+ });
122
+ test("returns the version passed if it is supported for AXO", () => {
123
+ const result1 = getSdkVersion("3.97.00");
124
+ const result2 = getSdkVersion("3.97.alpha-test");
125
+ const result3 = getSdkVersion("4.34.beta-test");
126
+ const result4 = getSdkVersion("4.34.47");
127
+
128
+ expect(result1).toEqual("3.97.00");
129
+ expect(result2).toEqual("3.97.alpha-test");
130
+ expect(result3).toEqual("4.34.beta-test");
131
+ expect(result4).toEqual("4.34.47");
132
+ });
133
+
134
+ test("throws error if the version passed is not supported for AXO and is not null", () => {
135
+ const result1 = getSdkVersion("3.96.00");
136
+ const result2 = getSdkVersion("2.87.alpha-test");
137
+ const result3 = getSdkVersion("3.34.beta-test");
138
+
139
+ expect(result1).toThrowError();
140
+ expect(result2).toThrowError();
141
+ expect(result3).toThrowError();
142
+ });
143
+ });
@@ -36,7 +36,10 @@ export type CreateOrder = (data: {|
36
36
  paymentSource: string,
37
37
  |}) => ZalgoPromise<string>;
38
38
 
39
- export type OnApprove = (data: {| orderID: string |}) => ZalgoPromise<void>;
39
+ export type OnApprove = (data: {|
40
+ orderID: string,
41
+ paymentSource: string,
42
+ |}) => ZalgoPromise<void>;
40
43
 
41
44
  export type CreateAccessToken = (clientID: string) => ZalgoPromise<string>;
42
45
 
@@ -1,8 +1,10 @@
1
1
  /* @flow */
2
2
 
3
- import { request, noop, memoize } from "@krakenjs/belter/src";
3
+ import { request, memoize, popup } from "@krakenjs/belter/src";
4
4
  import { getSDKHost, getClientID } from "@paypal/sdk-client/src";
5
5
 
6
+ import { DEFAULT_POPUP_SIZE } from "../zoid/checkout";
7
+
6
8
  import type {
7
9
  ButtonVariables,
8
10
  CreateAccessToken,
@@ -106,7 +108,9 @@ export const buildHostedButtonCreateOrder = ({
106
108
  merchant_id: merchantId,
107
109
  ...userInputs,
108
110
  }),
109
- }).then(({ body }) => body.context_id);
111
+ }).then(({ body }) => {
112
+ return body.context_id;
113
+ });
110
114
  });
111
115
  };
112
116
  };
@@ -126,7 +130,15 @@ export const buildHostedButtonOnApprove = ({
126
130
  merchant_id: merchantId,
127
131
  context_id: data.orderID,
128
132
  }),
129
- }).then(noop);
133
+ }).then(() => {
134
+ if (data.paymentSource === "card") {
135
+ const url = `${baseUrl}/ncp/payment/${hostedButtonId}/${data.orderID}`;
136
+ popup(url, {
137
+ width: DEFAULT_POPUP_SIZE.WIDTH,
138
+ height: DEFAULT_POPUP_SIZE.HEIGHT,
139
+ });
140
+ }
141
+ });
130
142
  });
131
143
  };
132
144
  };
@@ -107,7 +107,7 @@ test("buildHostedButtonOnApprove", async () => {
107
107
  body: {},
108
108
  })
109
109
  );
110
- await onApprove({ orderID: "EC-1234567890" });
110
+ await onApprove({ orderID: "EC-1234567890", paymentSource: "paypal" });
111
111
  expect(request).toHaveBeenCalledWith(
112
112
  expect.objectContaining({
113
113
  body: JSON.stringify({
@@ -740,7 +740,12 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
740
740
  clientMetadataID: {
741
741
  type: "string",
742
742
  required: false,
743
- default: getClientMetadataID,
743
+ default: () => {
744
+ const clientMetadataId = getClientMetadataID();
745
+ const sessionID = getSessionID();
746
+
747
+ return clientMetadataId || sessionID;
748
+ },
744
749
  queryParam: true,
745
750
  },
746
751