@paypal/checkout-components 5.0.295-alpha.2 → 5.0.295

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.
@@ -1,7 +1,80 @@
1
1
  /* @flow */
2
- import { type MerchantPayloadData } from "../../constants/api";
2
+
3
+ import { sendCountMetric } from "@paypal/sdk-client/src";
4
+
5
+ import {
6
+ SHOPPER_INSIGHTS_METRIC_NAME,
7
+ type MerchantPayloadData,
8
+ } from "../../constants/api";
3
9
  import { ValidationError } from "../../lib";
4
10
 
11
+ type MerchantConfigParams = {|
12
+ sdkToken: ?string,
13
+ pageType: ?string,
14
+ userIDToken: ?string,
15
+ clientToken: ?string,
16
+ |};
17
+
18
+ export function validateMerchantConfig({
19
+ sdkToken,
20
+ pageType,
21
+ userIDToken,
22
+ clientToken,
23
+ }: MerchantConfigParams) {
24
+ if (!sdkToken) {
25
+ sendCountMetric({
26
+ name: SHOPPER_INSIGHTS_METRIC_NAME,
27
+ event: "error",
28
+ dimensions: {
29
+ errorType: "merchant_configuration_validation_error",
30
+ validationDetails: "sdk_token_not_present",
31
+ },
32
+ });
33
+
34
+ throw new ValidationError(
35
+ `script data attribute sdk-client-token is required but was not passed`
36
+ );
37
+ }
38
+
39
+ if (!pageType) {
40
+ sendCountMetric({
41
+ name: SHOPPER_INSIGHTS_METRIC_NAME,
42
+ event: "error",
43
+ dimensions: {
44
+ errorType: "merchant_configuration_validation_error",
45
+ validationDetails: "page_type_not_present",
46
+ },
47
+ });
48
+
49
+ throw new ValidationError(
50
+ `script data attribute page-type is required but was not passed`
51
+ );
52
+ }
53
+
54
+ if (userIDToken) {
55
+ sendCountMetric({
56
+ name: SHOPPER_INSIGHTS_METRIC_NAME,
57
+ event: "error",
58
+ dimensions: {
59
+ errorType: "merchant_configuration_validation_error",
60
+ validationDetails: "sdk_token_and_id_token_present",
61
+ },
62
+ });
63
+
64
+ throw new ValidationError(
65
+ `use script data attribute sdk-client-token instead of user-id-token`
66
+ );
67
+ }
68
+
69
+ // Client token has widely adopted integrations in the SDK that we do not want
70
+ // to support anymore. For now, we will be only enforcing a warning. We should
71
+ // expand on this warning with upgrade guides when we have them.
72
+ if (clientToken) {
73
+ // eslint-disable-next-line no-console
74
+ console.warn(`script data attribute client-token is not recommended`);
75
+ }
76
+ }
77
+
5
78
  export const hasEmail = (merchantPayload: MerchantPayloadData): boolean =>
6
79
  Boolean(merchantPayload?.email);
7
80
 
@@ -1,7 +1,7 @@
1
1
  /* @flow */
2
2
  import { vi, describe, expect } from "vitest";
3
3
 
4
- import { validateMerchantPayload } from "./validation";
4
+ import { validateMerchantConfig, validateMerchantPayload } from "./validation";
5
5
 
6
6
  vi.mock("@paypal/sdk-client/src", () => {
7
7
  return {
@@ -9,6 +9,47 @@ vi.mock("@paypal/sdk-client/src", () => {
9
9
  };
10
10
  });
11
11
 
12
+ describe("shopper insights merchant SDK config validation", () => {
13
+ test("should throw if sdk token is not passed", () => {
14
+ expect(() =>
15
+ validateMerchantConfig({
16
+ sdkToken: "",
17
+ pageType: "",
18
+ userIDToken: "",
19
+ clientToken: "",
20
+ })
21
+ ).toThrowError(
22
+ "script data attribute sdk-client-token is required but was not passed"
23
+ );
24
+ });
25
+
26
+ test("should throw if page type is not passed", () => {
27
+ expect(() =>
28
+ validateMerchantConfig({
29
+ sdkToken: "sdk-token",
30
+ pageType: "",
31
+ userIDToken: "",
32
+ clientToken: "",
33
+ })
34
+ ).toThrowError(
35
+ "script data attribute page-type is required but was not passed"
36
+ );
37
+ });
38
+
39
+ test("should throw if ID token is passed", () => {
40
+ expect(() =>
41
+ validateMerchantConfig({
42
+ sdkToken: "sdk-token",
43
+ pageType: "product-listing",
44
+ userIDToken: "id-token",
45
+ clientToken: "",
46
+ })
47
+ ).toThrowError(
48
+ "use script data attribute sdk-client-token instead of user-id-token"
49
+ );
50
+ });
51
+ });
52
+
12
53
  describe("shopper insights merchant payload validation", () => {
13
54
  test("should have successful validation if email is only passed", () => {
14
55
  expect(() =>
@@ -1,52 +0,0 @@
1
- /* @flow */
2
- /* eslint-disable eslint-comments/disable-enable-pair */
3
- /* eslint-disable no-restricted-globals, promise/no-native */
4
- import { load as loadFingerprintJS } from "@fingerprintjs/fingerprintjs-pro";
5
-
6
- const __FINGERPRINT_JS_API_KEY__ = "Eh4QKkI51U0rVUUPeQT8";
7
-
8
- type FingerprintResults = {|
9
- requestId: string,
10
- |};
11
-
12
- type Fingerprinter = {|
13
- get: () => Promise<FingerprintResults>,
14
- |};
15
-
16
- export class Fingerprint {
17
- fingerprinter: ?Fingerprinter;
18
- results: ?FingerprintResults;
19
-
20
- async load(): Promise<void> {
21
- if (!this.fingerprinter) {
22
- this.fingerprinter = await loadFingerprintJS({
23
- apiKey: __FINGERPRINT_JS_API_KEY__,
24
- });
25
- }
26
- }
27
-
28
- async collect(): Promise<FingerprintResults> {
29
- if (this.results && this.results.requestId) {
30
- return this.results;
31
- }
32
-
33
- if (!this.fingerprinter) {
34
- await this.load();
35
- }
36
-
37
- if (!this.fingerprinter) {
38
- throw new Error("fingerprint library failed to load");
39
- }
40
-
41
- this.results = await this.fingerprinter.get();
42
-
43
- return this.results;
44
- }
45
-
46
- get(): Promise<FingerprintResults> {
47
- return this.collect();
48
- }
49
- }
50
-
51
- // $FlowIssue flow is bad with classes
52
- export const fingerprint = new Fingerprint();
@@ -1,304 +0,0 @@
1
- /* @flow */
2
- /* eslint-disable eslint-comments/disable-enable-pair */
3
- /* eslint-disable no-restricted-globals, promise/no-native */
4
-
5
- import { type LoggerType } from "@krakenjs/beaver-logger/src";
6
- import { stringifyError } from "@krakenjs/belter/src";
7
- import { sendCountMetric } from "@paypal/sdk-client/src";
8
- import { FPTI_KEY } from "@paypal/sdk-constants/src";
9
-
10
- import {
11
- ELIGIBLE_PAYMENT_METHODS,
12
- FPTI_TRANSITION,
13
- SHOPPER_INSIGHTS_METRIC_NAME,
14
- } from "../../constants/api";
15
- import { ValidationError } from "../../lib";
16
-
17
- import {
18
- validateMerchantPayload,
19
- hasEmail,
20
- hasPhoneNumber,
21
- } from "./validation";
22
- import { Fingerprint } from "./fingerprint";
23
-
24
- export type MerchantPayloadData = {|
25
- email?: string,
26
- phone?: {|
27
- countryCode?: string,
28
- nationalNumber?: string,
29
- |},
30
- |};
31
-
32
- type RecommendedPaymentMethods = {|
33
- isPayPalRecommended: boolean,
34
- isVenmoRecommended: boolean,
35
- |};
36
-
37
- type RecommendedPaymentMethodsRequestData = {|
38
- customer: {|
39
- country_code?: string,
40
- email?: string,
41
- phone?: {|
42
- country_code: string,
43
- national_number: string,
44
- |},
45
- |},
46
- purchase_units: $ReadOnlyArray<{|
47
- amount: {|
48
- currency_code: string,
49
- |},
50
- |}>,
51
- preferences: {|
52
- include_account_details: boolean,
53
- |},
54
- |};
55
-
56
- type RecommendedPaymentMethodsResponse = {|
57
- body: {|
58
- eligible_methods: {
59
- [paymentMethod: "paypal" | "venmo"]: {|
60
- can_be_vaulted: boolean,
61
- eligible_in_paypal_network?: boolean,
62
- recommended?: boolean,
63
- recommended_priority?: number,
64
- |},
65
- },
66
- |},
67
- |};
68
-
69
- type SdkConfig = {|
70
- sdkToken: ?string,
71
- pageType: ?string,
72
- userIDToken: ?string,
73
- clientToken: ?string,
74
- paypalApiDomain: string,
75
- environment: ?string,
76
- buyerCountry: string,
77
- currency: string,
78
- |};
79
-
80
- // eslint's flow integration is very out of date
81
- // it doesn't recognize the generics here as used
82
- // eslint-disable-next-line no-undef
83
- type Request = <TRequestData, TResponse>({|
84
- method?: string,
85
- url: string,
86
- // eslint-disable-next-line no-undef
87
- data: TRequestData,
88
- accessToken: ?string,
89
- // eslint-disable-next-line no-undef
90
- |}) => Promise<TResponse>;
91
-
92
- type Storage = {|
93
- // eslint's flow integration is very out of date
94
- // it doesn't recognize the generics here as used
95
- // eslint-disable-next-line no-undef
96
- get: <TValue>(key: string) => ?TValue,
97
- // eslint-disable-next-line flowtype/no-weak-types
98
- set: (key: string, value: any) => void,
99
- |};
100
-
101
- export const createRecommendedPaymentMethodsRequestPayload = ({
102
- merchantPayload,
103
- sdkConfig,
104
- }: {|
105
- merchantPayload: MerchantPayloadData,
106
- sdkConfig: SdkConfig,
107
- |}): RecommendedPaymentMethodsRequestData => ({
108
- customer: {
109
- ...(sdkConfig.environment !== "production" && {
110
- country_code: sdkConfig.buyerCountry,
111
- }),
112
- // $FlowIssue
113
- ...(hasEmail(merchantPayload) && {
114
- email: merchantPayload?.email,
115
- }),
116
- ...(hasPhoneNumber(merchantPayload) && {
117
- phone: {
118
- country_code: merchantPayload?.phone?.countryCode,
119
- national_number: merchantPayload?.phone?.nationalNumber,
120
- },
121
- }),
122
- },
123
- purchase_units: [
124
- {
125
- amount: {
126
- currency_code: sdkConfig.currency,
127
- },
128
- },
129
- ],
130
- // getRecommendedPaymentMethods maps to include_account_details in the API
131
- preferences: {
132
- include_account_details: true,
133
- },
134
- });
135
-
136
- export interface ShopperInsightsInterface {
137
- getRecommendedPaymentMethods: (
138
- payload: MerchantPayloadData
139
- ) => Promise<RecommendedPaymentMethods>;
140
- identify: () => Promise<{||}>;
141
- }
142
-
143
- export class ShopperSession {
144
- fingerprint: Fingerprint;
145
- logger: LoggerType;
146
- request: Request;
147
- requestId: string = "";
148
- sdkConfig: SdkConfig;
149
- sessionState: Storage;
150
-
151
- constructor({
152
- fingerprint,
153
- logger,
154
- request,
155
- sdkConfig,
156
- sessionState,
157
- }: {|
158
- fingerprint: Fingerprint,
159
- logger: LoggerType,
160
- request: Request,
161
- sdkConfig: SdkConfig,
162
- sessionState: Storage,
163
- |}) {
164
- this.fingerprint = fingerprint;
165
- this.logger = logger;
166
- this.request = request;
167
- this.sdkConfig = sdkConfig;
168
- this.sessionState = sessionState;
169
- }
170
-
171
- validateSdkConfig() {
172
- if (!this.sdkConfig.sdkToken) {
173
- sendCountMetric({
174
- name: SHOPPER_INSIGHTS_METRIC_NAME,
175
- event: "error",
176
- dimensions: {
177
- errorType: "merchant_configuration_validation_error",
178
- validationDetails: "sdk_token_not_present",
179
- },
180
- });
181
-
182
- throw new ValidationError(
183
- `script data attribute sdk-client-token is required but was not passed`
184
- );
185
- }
186
-
187
- if (!this.sdkConfig.pageType) {
188
- sendCountMetric({
189
- name: SHOPPER_INSIGHTS_METRIC_NAME,
190
- event: "error",
191
- dimensions: {
192
- errorType: "merchant_configuration_validation_error",
193
- validationDetails: "page_type_not_present",
194
- },
195
- });
196
-
197
- throw new ValidationError(
198
- `script data attribute page-type is required but was not passed`
199
- );
200
- }
201
-
202
- if (this.sdkConfig.userIDToken) {
203
- sendCountMetric({
204
- name: SHOPPER_INSIGHTS_METRIC_NAME,
205
- event: "error",
206
- dimensions: {
207
- errorType: "merchant_configuration_validation_error",
208
- validationDetails: "sdk_token_and_id_token_present",
209
- },
210
- });
211
-
212
- throw new ValidationError(
213
- `use script data attribute sdk-client-token instead of user-id-token`
214
- );
215
- }
216
-
217
- // Client token has widely adopted integrations in the SDK that we do not want
218
- // to support anymore. For now, we will be only enforcing a warning. We should
219
- // expand on this warning with upgrade guides when we have them.
220
- if (this.sdkConfig.clientToken) {
221
- // eslint-disable-next-line no-console
222
- console.warn(`script data attribute client-token is not recommended`);
223
- }
224
- }
225
-
226
- async getRecommendedPaymentMethods(
227
- merchantPayload: MerchantPayloadData
228
- ): Promise<RecommendedPaymentMethods> {
229
- validateMerchantPayload(merchantPayload);
230
-
231
- const startTime = Date.now();
232
- try {
233
- const { body } = await this.request<
234
- RecommendedPaymentMethodsRequestData,
235
- RecommendedPaymentMethodsResponse
236
- >({
237
- method: "POST",
238
- url: `${this.sdkConfig.paypalApiDomain}/${ELIGIBLE_PAYMENT_METHODS}`,
239
- data: createRecommendedPaymentMethodsRequestPayload({
240
- merchantPayload,
241
- sdkConfig: this.sdkConfig,
242
- }),
243
- accessToken: this.sdkConfig.sdkToken,
244
- });
245
-
246
- this.sessionState.set("shopperInsights", {
247
- getRecommendedPaymentMethodsUsed: true,
248
- });
249
-
250
- const { paypal, venmo } = body?.eligible_methods;
251
-
252
- const isPayPalRecommended =
253
- (paypal?.eligible_in_paypal_network && paypal?.recommended) || false;
254
- const isVenmoRecommended =
255
- (venmo?.eligible_in_paypal_network && venmo?.recommended) || false;
256
-
257
- this.logger.track({
258
- [FPTI_KEY.TRANSITION]: FPTI_TRANSITION.SHOPPER_INSIGHTS_API_SUCCESS,
259
- [FPTI_KEY.EVENT_NAME]: FPTI_TRANSITION.SHOPPER_INSIGHTS_API_SUCCESS,
260
- [FPTI_KEY.RESPONSE_DURATION]: (Date.now() - startTime).toString(),
261
- });
262
-
263
- sendCountMetric({
264
- name: SHOPPER_INSIGHTS_METRIC_NAME,
265
- event: "success",
266
- dimensions: {
267
- isPayPalRecommended: String(isPayPalRecommended),
268
- isVenmoRecommended: String(isVenmoRecommended),
269
- },
270
- });
271
-
272
- return { isPayPalRecommended, isVenmoRecommended };
273
- } catch (error) {
274
- sendCountMetric({
275
- name: SHOPPER_INSIGHTS_METRIC_NAME,
276
- event: "error",
277
- dimensions: {
278
- errorType: "api_error",
279
- },
280
- });
281
-
282
- this.logger.track({
283
- [FPTI_KEY.TRANSITION]: FPTI_TRANSITION.SHOPPER_INSIGHTS_API_ERROR,
284
- [FPTI_KEY.EVENT_NAME]: FPTI_TRANSITION.SHOPPER_INSIGHTS_API_ERROR,
285
- [FPTI_KEY.RESPONSE_DURATION]: (Date.now() - startTime).toString(),
286
- });
287
-
288
- this.logger.error("shopper_insights_api_error", {
289
- err: stringifyError(error),
290
- });
291
-
292
- throw error;
293
- }
294
- }
295
-
296
- async identify(): Promise<{||}> {
297
- const { requestId } = await this.fingerprint.get();
298
-
299
- this.requestId = requestId;
300
-
301
- // $FlowIssue
302
- return {};
303
- }
304
- }