@paypal/checkout-components 5.0.291 → 5.0.292-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/__sdk__.js +7 -0
- package/dist/button.js +1 -1
- package/dist/test/button.js +1 -1
- package/package.json +1 -1
- package/src/api/api.js +54 -0
- package/src/api/shopper-insights/component.jsx +197 -0
- package/src/api/shopper-insights/component.test.js +282 -0
- package/src/api/shopper-insights/interface.js +13 -0
- package/src/api/shopper-insights/validation.js +137 -0
- package/src/api/shopper-insights/validation.test.js +155 -0
- package/src/constants/api.js +29 -0
- package/src/hosted-buttons/index.js +71 -0
- package/src/hosted-buttons/index.test.js +84 -0
- package/src/hosted-buttons/types.js +50 -0
- package/src/hosted-buttons/utils.js +132 -0
- package/src/hosted-buttons/utils.test.js +121 -0
- package/src/interface/hosted-buttons.js +29 -0
- package/src/zoid/buttons/component.jsx +15 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import { vi, describe, expect } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { validateMerchantConfig, validateMerchantPayload } from "./validation";
|
|
5
|
+
|
|
6
|
+
vi.mock("@paypal/sdk-client/src", () => {
|
|
7
|
+
return {
|
|
8
|
+
sendCountMetric: vi.fn(),
|
|
9
|
+
};
|
|
10
|
+
});
|
|
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
|
+
|
|
53
|
+
describe("shopper insights merchant payload validation", () => {
|
|
54
|
+
test("should have successful validation if email is only passed", () => {
|
|
55
|
+
expect(() =>
|
|
56
|
+
validateMerchantPayload({
|
|
57
|
+
customer: {
|
|
58
|
+
email: "email@test.com",
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
).not.toThrowError();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("should have successful validation if phone is only passed", () => {
|
|
65
|
+
expect(() =>
|
|
66
|
+
validateMerchantPayload({
|
|
67
|
+
customer: {
|
|
68
|
+
phone: {
|
|
69
|
+
countryCode: "1",
|
|
70
|
+
nationalNumber: "2345678901",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
).not.toThrowError();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("should have successful validation if email and phone is passed", () => {
|
|
78
|
+
expect(() =>
|
|
79
|
+
validateMerchantPayload({
|
|
80
|
+
customer: {
|
|
81
|
+
email: "email@test.com",
|
|
82
|
+
phone: {
|
|
83
|
+
countryCode: "1",
|
|
84
|
+
nationalNumber: "2345678901",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
).not.toThrowError();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should throw if email or phone is not passed", () => {
|
|
92
|
+
expect(() =>
|
|
93
|
+
validateMerchantPayload({
|
|
94
|
+
customer: {},
|
|
95
|
+
})
|
|
96
|
+
).toThrowError(
|
|
97
|
+
"Expected shopper information to include an email or phone number"
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("should throw if countryCode or nationalNumber in phone is not passed or is empty", () => {
|
|
102
|
+
expect(() =>
|
|
103
|
+
validateMerchantPayload({
|
|
104
|
+
customer: {
|
|
105
|
+
phone: {
|
|
106
|
+
nationalNumber: "",
|
|
107
|
+
countryCode: "",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
).toThrowError(
|
|
112
|
+
"Expected shopper information to include an email or phone number"
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(() =>
|
|
116
|
+
validateMerchantPayload(
|
|
117
|
+
// $FlowFixMe
|
|
118
|
+
{ customer: { phone: {} } }
|
|
119
|
+
)
|
|
120
|
+
).toThrowError(
|
|
121
|
+
"Expected shopper information to include an email or phone number"
|
|
122
|
+
);
|
|
123
|
+
expect.assertions(2);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should throw if phone is in an invalid format", () => {
|
|
127
|
+
expect(() =>
|
|
128
|
+
validateMerchantPayload({
|
|
129
|
+
customer: { phone: { countryCode: "1", nationalNumber: "2.354" } },
|
|
130
|
+
})
|
|
131
|
+
).toThrowError(
|
|
132
|
+
"Expected shopper information to a valid phone number format"
|
|
133
|
+
);
|
|
134
|
+
expect(() =>
|
|
135
|
+
validateMerchantPayload({
|
|
136
|
+
customer: { phone: { countryCode: "1", nationalNumber: "2-354" } },
|
|
137
|
+
})
|
|
138
|
+
).toThrowError(
|
|
139
|
+
"Expected shopper information to a valid phone number format"
|
|
140
|
+
);
|
|
141
|
+
expect.assertions(2);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("should throw if email is in an invalid format", () => {
|
|
145
|
+
expect(() =>
|
|
146
|
+
validateMerchantPayload({
|
|
147
|
+
customer: {
|
|
148
|
+
email: "123",
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
).toThrowError(
|
|
152
|
+
"Expected shopper information to include a valid email format"
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
export const HEADERS = {
|
|
4
|
+
AUTHORIZATION: "authorization",
|
|
5
|
+
CONTENT_TYPE: "content-type",
|
|
6
|
+
PARTNER_ATTRIBUTION_ID: "paypal-partner-attribution-id",
|
|
7
|
+
CLIENT_METADATA_ID: "paypal-client-metadata-id",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const ELIGIBLE_PAYMENT_METHODS = "v2/payments/find-eligible-methods";
|
|
11
|
+
|
|
12
|
+
export const FPTI_TRANSITION = {
|
|
13
|
+
SHOPPER_INSIGHTS_API_INIT: "sdk_shopper_insights_recommended_init",
|
|
14
|
+
SHOPPER_INSIGHTS_API_SUCCESS: "sdk_shopper_insights_recommended_success",
|
|
15
|
+
SHOPPER_INSIGHTS_API_ERROR: "sdk_shopper_insights_recommended_error",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SHOPPER_INSIGHTS_METRIC_NAME =
|
|
19
|
+
"pp.app.paypal_sdk.api.shopper_insights.count";
|
|
20
|
+
|
|
21
|
+
export type MerchantPayloadData = {|
|
|
22
|
+
customer: {|
|
|
23
|
+
email?: string,
|
|
24
|
+
phone?: {|
|
|
25
|
+
countryCode?: string,
|
|
26
|
+
nationalNumber?: string,
|
|
27
|
+
|},
|
|
28
|
+
|},
|
|
29
|
+
|};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { getMerchantID } from "@paypal/sdk-client/src";
|
|
4
|
+
|
|
5
|
+
import { getButtonsComponent } from "../zoid/buttons";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
buildHostedButtonCreateOrder,
|
|
9
|
+
buildHostedButtonOnApprove,
|
|
10
|
+
getHostedButtonDetails,
|
|
11
|
+
renderForm,
|
|
12
|
+
} from "./utils";
|
|
13
|
+
import type {
|
|
14
|
+
HostedButtonsComponent,
|
|
15
|
+
HostedButtonsComponentProps,
|
|
16
|
+
HostedButtonsInstance,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
export const getHostedButtonsComponent = (): HostedButtonsComponent => {
|
|
20
|
+
function HostedButtons({
|
|
21
|
+
hostedButtonId,
|
|
22
|
+
}: HostedButtonsComponentProps): HostedButtonsInstance {
|
|
23
|
+
const Buttons = getButtonsComponent();
|
|
24
|
+
const render = (selector) => {
|
|
25
|
+
// The SDK supports mutiple merchant IDs, but hosted buttons only
|
|
26
|
+
// have one merchant id as a query parameter to the SDK script.
|
|
27
|
+
// https://github.com/paypal/paypal-sdk-client/blob/c58e35f8f7adbab76523eb25b9c10543449d2d29/src/script.js#L144
|
|
28
|
+
const merchantId = getMerchantID()[0];
|
|
29
|
+
|
|
30
|
+
getHostedButtonDetails({ hostedButtonId }).then(
|
|
31
|
+
({ html, htmlScript, style }) => {
|
|
32
|
+
renderForm({ html, htmlScript, selector });
|
|
33
|
+
|
|
34
|
+
// $FlowFixMe
|
|
35
|
+
Buttons({
|
|
36
|
+
style,
|
|
37
|
+
hostedButtonId,
|
|
38
|
+
onInit(data, actions) {
|
|
39
|
+
// disable the button, listen for input changes,
|
|
40
|
+
// and enable the button when the form is valid
|
|
41
|
+
// using actions.disable() and actions.enable()
|
|
42
|
+
window[`__pp_form_fields_${hostedButtonId}`]?.onInit?.(
|
|
43
|
+
data,
|
|
44
|
+
actions
|
|
45
|
+
);
|
|
46
|
+
},
|
|
47
|
+
onClick(data, actions) {
|
|
48
|
+
// render form errors, if present
|
|
49
|
+
window[`__pp_form_fields_${hostedButtonId}`]?.onClick?.(
|
|
50
|
+
data,
|
|
51
|
+
actions
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
createOrder: buildHostedButtonCreateOrder({
|
|
55
|
+
hostedButtonId,
|
|
56
|
+
merchantId,
|
|
57
|
+
}),
|
|
58
|
+
onApprove: buildHostedButtonOnApprove({
|
|
59
|
+
hostedButtonId,
|
|
60
|
+
merchantId,
|
|
61
|
+
}),
|
|
62
|
+
}).render(selector);
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
render,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return HostedButtons;
|
|
71
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, vi } from "vitest";
|
|
4
|
+
import { request } from "@krakenjs/belter/src";
|
|
5
|
+
import { ZalgoPromise } from "@krakenjs/zalgo-promise";
|
|
6
|
+
|
|
7
|
+
import { getButtonsComponent } from "../zoid/buttons";
|
|
8
|
+
|
|
9
|
+
import { getHostedButtonsComponent } from ".";
|
|
10
|
+
|
|
11
|
+
vi.mock("@krakenjs/belter/src", async () => {
|
|
12
|
+
return {
|
|
13
|
+
...(await vi.importActual("@krakenjs/belter/src")),
|
|
14
|
+
request: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
vi.mock("@paypal/sdk-client/src", async () => {
|
|
19
|
+
return {
|
|
20
|
+
...(await vi.importActual("@paypal/sdk-client/src")),
|
|
21
|
+
getSDKHost: () => "example.com",
|
|
22
|
+
getClientID: () => "client_id_123",
|
|
23
|
+
getMerchantID: () => ["merchant_id_123"],
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
vi.mock("../zoid/buttons", async () => {
|
|
28
|
+
return {
|
|
29
|
+
...(await vi.importActual("../zoid/buttons")),
|
|
30
|
+
getButtonsComponent: vi.fn(),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const getHostedButtonDetailsResponse = {
|
|
35
|
+
body: {
|
|
36
|
+
button_details: {
|
|
37
|
+
link_variables: [
|
|
38
|
+
{
|
|
39
|
+
name: "shape",
|
|
40
|
+
value: "rect",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "layout",
|
|
44
|
+
value: "vertical",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "color",
|
|
48
|
+
value: "gold",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "button_text",
|
|
52
|
+
value: "paypal",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "button_type",
|
|
56
|
+
value: "FIXED_PRICE",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe("HostedButtons", () => {
|
|
64
|
+
test("paypal.Buttons calls getHostedButtonDetails and invokes v5 of the SDK", () => {
|
|
65
|
+
const Buttons = vi.fn(() => ({ render: vi.fn() }));
|
|
66
|
+
// $FlowIssue
|
|
67
|
+
getButtonsComponent.mockImplementationOnce(() => Buttons);
|
|
68
|
+
const HostedButtons = getHostedButtonsComponent();
|
|
69
|
+
// $FlowIssue
|
|
70
|
+
request.mockImplementationOnce(() =>
|
|
71
|
+
ZalgoPromise.resolve(getHostedButtonDetailsResponse)
|
|
72
|
+
);
|
|
73
|
+
HostedButtons({
|
|
74
|
+
hostedButtonId: "B1234567890",
|
|
75
|
+
}).render("#example");
|
|
76
|
+
expect(Buttons).toHaveBeenCalledWith(
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
hostedButtonId: "B1234567890",
|
|
79
|
+
hostedButtonType: "NO_CODE_FIXED_PRICE",
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
expect.assertions(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
|
|
4
|
+
|
|
5
|
+
export type HostedButtonsComponentProps = {|
|
|
6
|
+
hostedButtonId: string,
|
|
7
|
+
|};
|
|
8
|
+
|
|
9
|
+
export type GetCallbackProps = {|
|
|
10
|
+
hostedButtonId: string,
|
|
11
|
+
merchantId: string,
|
|
12
|
+
|};
|
|
13
|
+
|
|
14
|
+
export type HostedButtonsInstance = {|
|
|
15
|
+
render: (string | HTMLElement) => void,
|
|
16
|
+
|};
|
|
17
|
+
|
|
18
|
+
export type HostedButtonDetailsParams =
|
|
19
|
+
(HostedButtonsComponentProps) => ZalgoPromise<{|
|
|
20
|
+
html: string,
|
|
21
|
+
htmlScript: string,
|
|
22
|
+
style: {|
|
|
23
|
+
layout: string,
|
|
24
|
+
shape: string,
|
|
25
|
+
color: string,
|
|
26
|
+
label: string,
|
|
27
|
+
|},
|
|
28
|
+
|}>;
|
|
29
|
+
|
|
30
|
+
export type ButtonVariables = $ReadOnlyArray<{|
|
|
31
|
+
name: string,
|
|
32
|
+
value: string,
|
|
33
|
+
|}>;
|
|
34
|
+
|
|
35
|
+
export type CreateOrder = (data: {|
|
|
36
|
+
paymentSource: string,
|
|
37
|
+
|}) => ZalgoPromise<string>;
|
|
38
|
+
|
|
39
|
+
export type OnApprove = (data: {| orderID: string |}) => ZalgoPromise<void>;
|
|
40
|
+
|
|
41
|
+
export type CreateAccessToken = (clientID: string) => ZalgoPromise<string>;
|
|
42
|
+
|
|
43
|
+
export type HostedButtonsComponent =
|
|
44
|
+
(HostedButtonsComponentProps) => HostedButtonsInstance;
|
|
45
|
+
|
|
46
|
+
export type RenderForm = {|
|
|
47
|
+
html: string,
|
|
48
|
+
htmlScript: string,
|
|
49
|
+
selector: string | HTMLElement,
|
|
50
|
+
|};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { request, noop, memoize } from "@krakenjs/belter/src";
|
|
4
|
+
import { getSDKHost, getClientID } from "@paypal/sdk-client/src";
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ButtonVariables,
|
|
8
|
+
CreateAccessToken,
|
|
9
|
+
CreateOrder,
|
|
10
|
+
GetCallbackProps,
|
|
11
|
+
HostedButtonDetailsParams,
|
|
12
|
+
OnApprove,
|
|
13
|
+
RenderForm,
|
|
14
|
+
} from "./types";
|
|
15
|
+
|
|
16
|
+
const entryPoint = "SDK";
|
|
17
|
+
const baseUrl = `https://${getSDKHost()}`;
|
|
18
|
+
const apiUrl = baseUrl.replace("www", "api");
|
|
19
|
+
|
|
20
|
+
const getHeaders = (accessToken?: string) => ({
|
|
21
|
+
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
"PayPal-Entry-Point": entryPoint,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const createAccessToken: CreateAccessToken = memoize<CreateAccessToken>(
|
|
27
|
+
(clientId) => {
|
|
28
|
+
return request({
|
|
29
|
+
url: `${apiUrl}/v1/oauth2/token`,
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: "grant_type=client_credentials",
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Basic ${btoa(clientId)}`,
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
},
|
|
36
|
+
}).then((response) => response.body.access_token);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const getButtonVariable = (variables: ButtonVariables, key: string): string =>
|
|
41
|
+
variables?.find((variable) => variable.name === key)?.value ?? "";
|
|
42
|
+
|
|
43
|
+
const getFundingSource = (paymentSource) => {
|
|
44
|
+
if (paymentSource === "credit") {
|
|
45
|
+
return `CARD`;
|
|
46
|
+
}
|
|
47
|
+
return paymentSource.toUpperCase();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const getHostedButtonDetails: HostedButtonDetailsParams = ({
|
|
51
|
+
hostedButtonId,
|
|
52
|
+
}) => {
|
|
53
|
+
return request({
|
|
54
|
+
url: `${baseUrl}/ncp/api/form-fields/${hostedButtonId}`,
|
|
55
|
+
headers: getHeaders(),
|
|
56
|
+
}).then(({ body }) => {
|
|
57
|
+
const variables = body.button_details.link_variables;
|
|
58
|
+
return {
|
|
59
|
+
style: {
|
|
60
|
+
layout: getButtonVariable(variables, "layout"),
|
|
61
|
+
shape: getButtonVariable(variables, "shape"),
|
|
62
|
+
color: getButtonVariable(variables, "color"),
|
|
63
|
+
label: getButtonVariable(variables, "button_text"),
|
|
64
|
+
},
|
|
65
|
+
html: body.html,
|
|
66
|
+
htmlScript: body.html_script,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Attaches form fields (html) to the given selector, and
|
|
73
|
+
* initializes window.__pp_form_fields (htmlScript).
|
|
74
|
+
*/
|
|
75
|
+
export const renderForm = ({
|
|
76
|
+
html,
|
|
77
|
+
htmlScript,
|
|
78
|
+
selector,
|
|
79
|
+
}: RenderForm): void => {
|
|
80
|
+
const elm =
|
|
81
|
+
typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
82
|
+
if (elm) {
|
|
83
|
+
elm.innerHTML = html + htmlScript;
|
|
84
|
+
const newScriptEl = document.createElement("script");
|
|
85
|
+
const oldScriptEl = elm.querySelector("script");
|
|
86
|
+
newScriptEl.innerHTML = oldScriptEl?.innerHTML ?? "";
|
|
87
|
+
oldScriptEl?.parentNode?.replaceChild(newScriptEl, oldScriptEl);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const buildHostedButtonCreateOrder = ({
|
|
92
|
+
hostedButtonId,
|
|
93
|
+
merchantId,
|
|
94
|
+
}: GetCallbackProps): CreateOrder => {
|
|
95
|
+
return (data) => {
|
|
96
|
+
const userInputs =
|
|
97
|
+
window[`__pp_form_fields_${hostedButtonId}`]?.getUserInputs?.() || {};
|
|
98
|
+
return createAccessToken(getClientID()).then((accessToken) => {
|
|
99
|
+
return request({
|
|
100
|
+
url: `${apiUrl}/v1/checkout/links/${hostedButtonId}/create-context`,
|
|
101
|
+
headers: getHeaders(accessToken),
|
|
102
|
+
method: "POST",
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
entry_point: entryPoint,
|
|
105
|
+
funding_source: getFundingSource(data.paymentSource),
|
|
106
|
+
merchant_id: merchantId,
|
|
107
|
+
...userInputs,
|
|
108
|
+
}),
|
|
109
|
+
}).then(({ body }) => body.context_id);
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const buildHostedButtonOnApprove = ({
|
|
115
|
+
hostedButtonId,
|
|
116
|
+
merchantId,
|
|
117
|
+
}: GetCallbackProps): OnApprove => {
|
|
118
|
+
return (data) => {
|
|
119
|
+
return createAccessToken(getClientID()).then((accessToken) => {
|
|
120
|
+
return request({
|
|
121
|
+
url: `${apiUrl}/v1/checkout/links/${hostedButtonId}/pay`,
|
|
122
|
+
headers: getHeaders(accessToken),
|
|
123
|
+
method: "POST",
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
entry_point: entryPoint,
|
|
126
|
+
merchant_id: merchantId,
|
|
127
|
+
context_id: data.orderID,
|
|
128
|
+
}),
|
|
129
|
+
}).then(noop);
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { test, expect, vi } from "vitest";
|
|
4
|
+
import { request } from "@krakenjs/belter/src";
|
|
5
|
+
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getHostedButtonDetails,
|
|
9
|
+
buildHostedButtonCreateOrder,
|
|
10
|
+
buildHostedButtonOnApprove,
|
|
11
|
+
} from "./utils";
|
|
12
|
+
|
|
13
|
+
vi.mock("@krakenjs/belter/src", async () => {
|
|
14
|
+
return {
|
|
15
|
+
...(await vi.importActual("@krakenjs/belter/src")),
|
|
16
|
+
request: vi.fn(),
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
vi.mock("@paypal/sdk-client/src", async () => {
|
|
21
|
+
return {
|
|
22
|
+
...(await vi.importActual("@paypal/sdk-client/src")),
|
|
23
|
+
getSDKHost: () => "example.com",
|
|
24
|
+
getClientID: () => "client_id_123",
|
|
25
|
+
getMerchantID: () => ["merchant_id_123"],
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const getHostedButtonDetailsResponse = {
|
|
30
|
+
body: {
|
|
31
|
+
button_details: {
|
|
32
|
+
link_variables: [
|
|
33
|
+
{
|
|
34
|
+
name: "business",
|
|
35
|
+
value: "M1234567890",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "shape",
|
|
39
|
+
value: "rect",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "layout",
|
|
43
|
+
value: "vertical",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "color",
|
|
47
|
+
value: "gold",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "button_text",
|
|
51
|
+
value: "paypal",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
test("getHostedButtonDetails", async () => {
|
|
59
|
+
// $FlowIssue
|
|
60
|
+
request.mockImplementationOnce(() =>
|
|
61
|
+
ZalgoPromise.resolve(getHostedButtonDetailsResponse)
|
|
62
|
+
);
|
|
63
|
+
await getHostedButtonDetails({
|
|
64
|
+
hostedButtonId: "B1234567890",
|
|
65
|
+
}).then(({ style }) => {
|
|
66
|
+
expect(style).toEqual({
|
|
67
|
+
layout: "vertical",
|
|
68
|
+
shape: "rect",
|
|
69
|
+
color: "gold",
|
|
70
|
+
label: "paypal",
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
expect.assertions(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("buildHostedButtonCreateOrder", async () => {
|
|
77
|
+
const createOrder = buildHostedButtonCreateOrder({
|
|
78
|
+
hostedButtonId: "B1234567890",
|
|
79
|
+
merchantId: "M1234567890",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// $FlowIssue
|
|
83
|
+
request.mockImplementation(() =>
|
|
84
|
+
ZalgoPromise.resolve({
|
|
85
|
+
body: {
|
|
86
|
+
link_id: "B1234567890",
|
|
87
|
+
merchant_id: "M1234567890",
|
|
88
|
+
context_id: "EC-1234567890",
|
|
89
|
+
status: "CREATED",
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
const orderID = await createOrder({ paymentSource: "paypal" });
|
|
94
|
+
expect(orderID).toBe("EC-1234567890");
|
|
95
|
+
expect.assertions(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("buildHostedButtonOnApprove", async () => {
|
|
99
|
+
const onApprove = buildHostedButtonOnApprove({
|
|
100
|
+
hostedButtonId: "B1234567890",
|
|
101
|
+
merchantId: "M1234567890",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// $FlowIssue
|
|
105
|
+
request.mockImplementation(() =>
|
|
106
|
+
ZalgoPromise.resolve({
|
|
107
|
+
body: {},
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
await onApprove({ orderID: "EC-1234567890" });
|
|
111
|
+
expect(request).toHaveBeenCalledWith(
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
entry_point: "SDK",
|
|
115
|
+
merchant_id: "M1234567890",
|
|
116
|
+
context_id: "EC-1234567890",
|
|
117
|
+
}),
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
expect.assertions(1);
|
|
121
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { getHostedButtonsComponent } from "../hosted-buttons";
|
|
4
|
+
import type { HostedButtonsComponent } from "../hosted-buttons/types";
|
|
5
|
+
import { getButtonsComponent } from "../zoid/buttons";
|
|
6
|
+
import {
|
|
7
|
+
getCardFormComponent,
|
|
8
|
+
type CardFormComponent,
|
|
9
|
+
} from "../zoid/card-form";
|
|
10
|
+
import { getCheckoutComponent, type CheckoutComponent } from "../zoid/checkout";
|
|
11
|
+
import type { LazyExport, LazyProtectedExport } from "../types";
|
|
12
|
+
import { protectedExport } from "../lib";
|
|
13
|
+
|
|
14
|
+
export const HostedButtons: LazyExport<HostedButtonsComponent> = {
|
|
15
|
+
__get__: () => getHostedButtonsComponent(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Checkout: LazyProtectedExport<CheckoutComponent> = {
|
|
19
|
+
__get__: () => protectedExport(getCheckoutComponent()),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const CardForm: LazyProtectedExport<CardFormComponent> = {
|
|
23
|
+
__get__: () => protectedExport(getCardFormComponent()),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function setup() {
|
|
27
|
+
getButtonsComponent();
|
|
28
|
+
getCheckoutComponent();
|
|
29
|
+
}
|
|
@@ -719,6 +719,16 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
|
|
|
719
719
|
},
|
|
720
720
|
},
|
|
721
721
|
|
|
722
|
+
referrerDomain: {
|
|
723
|
+
type: "string",
|
|
724
|
+
required: false,
|
|
725
|
+
value: () => {
|
|
726
|
+
if (window.document.referrer) {
|
|
727
|
+
return new URL(window.document.referrer).host || undefined;
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
|
|
722
732
|
userIDToken: {
|
|
723
733
|
type: "string",
|
|
724
734
|
default: getUserIDToken,
|
|
@@ -834,6 +844,11 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
|
|
|
834
844
|
value: getExperimentation,
|
|
835
845
|
},
|
|
836
846
|
|
|
847
|
+
hostedButtonId: {
|
|
848
|
+
type: "string",
|
|
849
|
+
required: false,
|
|
850
|
+
},
|
|
851
|
+
|
|
837
852
|
displayOnly: {
|
|
838
853
|
type: "array",
|
|
839
854
|
queryParam: true,
|