@paypal/checkout-components 5.0.294 → 5.0.295-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 +4 -0
- package/package.json +1 -1
- package/src/hosted-buttons/index.js +58 -0
- package/src/hosted-buttons/index.test.js +83 -0
- package/src/hosted-buttons/types.js +57 -0
- package/src/hosted-buttons/utils.js +170 -0
- package/src/hosted-buttons/utils.test.js +157 -0
- package/src/interface/hosted-buttons.js +29 -0
- package/src/ui/buttons/props.js +1 -0
- package/src/zoid/buttons/component.jsx +13 -2
- package/src/zoid/buttons/prerender.jsx +7 -1
- package/src/zoid/checkout/component.jsx +6 -0
package/__sdk__.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { getButtonsComponent } from "../zoid/buttons";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildHostedButtonCreateOrder,
|
|
7
|
+
buildHostedButtonOnApprove,
|
|
8
|
+
getHostedButtonDetails,
|
|
9
|
+
renderForm,
|
|
10
|
+
getMerchantID,
|
|
11
|
+
} from "./utils";
|
|
12
|
+
import type {
|
|
13
|
+
HostedButtonsComponent,
|
|
14
|
+
HostedButtonsComponentProps,
|
|
15
|
+
HostedButtonsInstance,
|
|
16
|
+
} from "./types";
|
|
17
|
+
|
|
18
|
+
export const getHostedButtonsComponent = (): HostedButtonsComponent => {
|
|
19
|
+
function HostedButtons({
|
|
20
|
+
hostedButtonId,
|
|
21
|
+
}: HostedButtonsComponentProps): HostedButtonsInstance {
|
|
22
|
+
const Buttons = getButtonsComponent();
|
|
23
|
+
const render = (selector) => {
|
|
24
|
+
const merchantId = getMerchantID();
|
|
25
|
+
|
|
26
|
+
getHostedButtonDetails({ hostedButtonId }).then(
|
|
27
|
+
({ html, htmlScript, style }) => {
|
|
28
|
+
const { onInit, onClick } = renderForm({
|
|
29
|
+
hostedButtonId,
|
|
30
|
+
html,
|
|
31
|
+
htmlScript,
|
|
32
|
+
selector,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// $FlowFixMe
|
|
36
|
+
Buttons({
|
|
37
|
+
hostedButtonId,
|
|
38
|
+
style,
|
|
39
|
+
onInit,
|
|
40
|
+
onClick,
|
|
41
|
+
createOrder: buildHostedButtonCreateOrder({
|
|
42
|
+
hostedButtonId,
|
|
43
|
+
merchantId,
|
|
44
|
+
}),
|
|
45
|
+
onApprove: buildHostedButtonOnApprove({
|
|
46
|
+
hostedButtonId,
|
|
47
|
+
merchantId,
|
|
48
|
+
}),
|
|
49
|
+
}).render(selector);
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
render,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return HostedButtons;
|
|
58
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
})
|
|
80
|
+
);
|
|
81
|
+
expect.assertions(1);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
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: {|
|
|
40
|
+
orderID: string,
|
|
41
|
+
paymentSource: string,
|
|
42
|
+
|}) => ZalgoPromise<mixed>;
|
|
43
|
+
|
|
44
|
+
export type CreateAccessToken = (clientID: string) => ZalgoPromise<string>;
|
|
45
|
+
|
|
46
|
+
export type HostedButtonsComponent =
|
|
47
|
+
(HostedButtonsComponentProps) => HostedButtonsInstance;
|
|
48
|
+
|
|
49
|
+
export type RenderForm = ({|
|
|
50
|
+
hostedButtonId: string,
|
|
51
|
+
html: string,
|
|
52
|
+
htmlScript: string,
|
|
53
|
+
selector: string | HTMLElement,
|
|
54
|
+
|}) => {|
|
|
55
|
+
onInit: (data: mixed, actions: mixed) => void,
|
|
56
|
+
onClick: (data: mixed, actions: mixed) => void,
|
|
57
|
+
|};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { request, memoize, popup, supportsPopups } from "@krakenjs/belter/src";
|
|
4
|
+
import {
|
|
5
|
+
getSDKHost,
|
|
6
|
+
getClientID,
|
|
7
|
+
getMerchantID as getSDKMerchantID,
|
|
8
|
+
} from "@paypal/sdk-client/src";
|
|
9
|
+
import { FUNDING } from "@paypal/sdk-constants/src";
|
|
10
|
+
|
|
11
|
+
import { DEFAULT_POPUP_SIZE } from "../zoid/checkout";
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ButtonVariables,
|
|
15
|
+
CreateAccessToken,
|
|
16
|
+
CreateOrder,
|
|
17
|
+
GetCallbackProps,
|
|
18
|
+
HostedButtonDetailsParams,
|
|
19
|
+
OnApprove,
|
|
20
|
+
RenderForm,
|
|
21
|
+
} from "./types";
|
|
22
|
+
|
|
23
|
+
const entryPoint = "SDK";
|
|
24
|
+
const baseUrl = `https://${getSDKHost()}`;
|
|
25
|
+
const apiUrl = baseUrl.replace("www", "api");
|
|
26
|
+
|
|
27
|
+
const getHeaders = (accessToken?: string) => ({
|
|
28
|
+
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"PayPal-Entry-Point": entryPoint,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const getMerchantID = (): string | void => {
|
|
34
|
+
// The SDK supports mutiple merchant IDs, but hosted buttons only
|
|
35
|
+
// have one merchant id as a query parameter to the SDK script.
|
|
36
|
+
// https://github.com/paypal/paypal-sdk-client/blob/c58e35f8f7adbab76523eb25b9c10543449d2d29/src/script.js#L144
|
|
37
|
+
const merchantIds = getSDKMerchantID();
|
|
38
|
+
if (merchantIds.length > 1) {
|
|
39
|
+
throw new Error("Multiple merchant-ids are not supported.");
|
|
40
|
+
}
|
|
41
|
+
return merchantIds[0];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const createAccessToken: CreateAccessToken = memoize<CreateAccessToken>(
|
|
45
|
+
(clientId) => {
|
|
46
|
+
return request({
|
|
47
|
+
url: `${apiUrl}/v1/oauth2/token`,
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: "grant_type=client_credentials",
|
|
50
|
+
headers: {
|
|
51
|
+
Authorization: `Basic ${btoa(clientId)}`,
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
},
|
|
54
|
+
}).then((response) => response.body.access_token);
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const getButtonVariable = (variables: ButtonVariables, key: string): string =>
|
|
59
|
+
variables?.find((variable) => variable.name === key)?.value ?? "";
|
|
60
|
+
|
|
61
|
+
export const getHostedButtonDetails: HostedButtonDetailsParams = ({
|
|
62
|
+
hostedButtonId,
|
|
63
|
+
}) => {
|
|
64
|
+
return request({
|
|
65
|
+
url: `${baseUrl}/ncp/api/form-fields/${hostedButtonId}`,
|
|
66
|
+
headers: getHeaders(),
|
|
67
|
+
}).then(({ body }) => {
|
|
68
|
+
const variables = body.button_details.link_variables;
|
|
69
|
+
return {
|
|
70
|
+
style: {
|
|
71
|
+
layout: getButtonVariable(variables, "layout"),
|
|
72
|
+
shape: getButtonVariable(variables, "shape"),
|
|
73
|
+
color: getButtonVariable(variables, "color"),
|
|
74
|
+
label: getButtonVariable(variables, "button_text"),
|
|
75
|
+
},
|
|
76
|
+
html: body.html,
|
|
77
|
+
htmlScript: body.html_script,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Attaches form fields (html) to the given selector, and
|
|
84
|
+
* initializes window.__pp_form_fields (htmlScript).
|
|
85
|
+
*/
|
|
86
|
+
export const renderForm: RenderForm = ({
|
|
87
|
+
hostedButtonId,
|
|
88
|
+
html,
|
|
89
|
+
htmlScript,
|
|
90
|
+
selector,
|
|
91
|
+
}) => {
|
|
92
|
+
const elm =
|
|
93
|
+
typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
94
|
+
if (elm) {
|
|
95
|
+
elm.innerHTML = html + htmlScript;
|
|
96
|
+
const newScriptEl = document.createElement("script");
|
|
97
|
+
const oldScriptEl = elm.querySelector("script");
|
|
98
|
+
newScriptEl.innerHTML = oldScriptEl?.innerHTML ?? "";
|
|
99
|
+
oldScriptEl?.parentNode?.replaceChild(newScriptEl, oldScriptEl);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
// disable the button, listen for input changes,
|
|
103
|
+
// and enable the button when the form is valid
|
|
104
|
+
// using actions.disable() and actions.enable()
|
|
105
|
+
onInit: window[`__pp_form_fields_${hostedButtonId}`]?.onInit,
|
|
106
|
+
// render form errors, if present
|
|
107
|
+
onClick: window[`__pp_form_fields_${hostedButtonId}`]?.onClick,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const buildHostedButtonCreateOrder = ({
|
|
112
|
+
hostedButtonId,
|
|
113
|
+
merchantId,
|
|
114
|
+
}: GetCallbackProps): CreateOrder => {
|
|
115
|
+
return (data) => {
|
|
116
|
+
const userInputs =
|
|
117
|
+
window[`__pp_form_fields_${hostedButtonId}`]?.getUserInputs?.() || {};
|
|
118
|
+
return createAccessToken(getClientID()).then((accessToken) => {
|
|
119
|
+
return request({
|
|
120
|
+
url: `${apiUrl}/v1/checkout/links/${hostedButtonId}/create-context`,
|
|
121
|
+
headers: getHeaders(accessToken),
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
entry_point: entryPoint,
|
|
125
|
+
funding_source: data.paymentSource.toUpperCase(),
|
|
126
|
+
merchant_id: merchantId,
|
|
127
|
+
...userInputs,
|
|
128
|
+
}),
|
|
129
|
+
}).then(({ body }) => {
|
|
130
|
+
return body.context_id;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const buildHostedButtonOnApprove = ({
|
|
137
|
+
hostedButtonId,
|
|
138
|
+
merchantId,
|
|
139
|
+
}: GetCallbackProps): OnApprove => {
|
|
140
|
+
return (data) => {
|
|
141
|
+
return createAccessToken(getClientID()).then((accessToken) => {
|
|
142
|
+
return request({
|
|
143
|
+
url: `${apiUrl}/v1/checkout/links/${hostedButtonId}/pay`,
|
|
144
|
+
headers: getHeaders(accessToken),
|
|
145
|
+
method: "POST",
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
entry_point: entryPoint,
|
|
148
|
+
merchant_id: merchantId,
|
|
149
|
+
context_id: data.orderID,
|
|
150
|
+
}),
|
|
151
|
+
}).then((response) => {
|
|
152
|
+
// The "Debit or Credit Card" button does not open a popup
|
|
153
|
+
// so we need to open a new popup for buyers who complete
|
|
154
|
+
// a checkout via "Debit or Credit Card".
|
|
155
|
+
if (data.paymentSource === FUNDING.CARD) {
|
|
156
|
+
const url = `${baseUrl}/ncp/payment/${hostedButtonId}/${data.orderID}`;
|
|
157
|
+
if (supportsPopups()) {
|
|
158
|
+
popup(url, {
|
|
159
|
+
width: DEFAULT_POPUP_SIZE.WIDTH,
|
|
160
|
+
height: DEFAULT_POPUP_SIZE.HEIGHT,
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
window.location = url;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return response;
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { test, expect, vi } from "vitest";
|
|
4
|
+
import { request, popup, supportsPopups } from "@krakenjs/belter/src";
|
|
5
|
+
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
buildHostedButtonCreateOrder,
|
|
9
|
+
buildHostedButtonOnApprove,
|
|
10
|
+
getHostedButtonDetails,
|
|
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
|
+
popup: vi.fn(),
|
|
18
|
+
supportsPopups: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
vi.mock("@paypal/sdk-client/src", async () => {
|
|
23
|
+
return {
|
|
24
|
+
...(await vi.importActual("@paypal/sdk-client/src")),
|
|
25
|
+
getSDKHost: () => "example.com",
|
|
26
|
+
getClientID: () => "client_id_123",
|
|
27
|
+
getMerchantID: () => ["merchant_id_123"],
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const hostedButtonId = "B1234567890";
|
|
32
|
+
const merchantId = "M1234567890";
|
|
33
|
+
const orderID = "EC-1234567890";
|
|
34
|
+
|
|
35
|
+
const getHostedButtonDetailsResponse = {
|
|
36
|
+
body: {
|
|
37
|
+
button_details: {
|
|
38
|
+
link_variables: [
|
|
39
|
+
{
|
|
40
|
+
name: "business",
|
|
41
|
+
value: merchantId,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "shape",
|
|
45
|
+
value: "rect",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "layout",
|
|
49
|
+
value: "vertical",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "color",
|
|
53
|
+
value: "gold",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "button_text",
|
|
57
|
+
value: "paypal",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
test("getHostedButtonDetails", async () => {
|
|
65
|
+
// $FlowIssue
|
|
66
|
+
request.mockImplementationOnce(() =>
|
|
67
|
+
ZalgoPromise.resolve(getHostedButtonDetailsResponse)
|
|
68
|
+
);
|
|
69
|
+
await getHostedButtonDetails({
|
|
70
|
+
hostedButtonId,
|
|
71
|
+
}).then(({ style }) => {
|
|
72
|
+
expect(style).toEqual({
|
|
73
|
+
layout: "vertical",
|
|
74
|
+
shape: "rect",
|
|
75
|
+
color: "gold",
|
|
76
|
+
label: "paypal",
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
expect.assertions(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("buildHostedButtonCreateOrder", async () => {
|
|
83
|
+
const createOrder = buildHostedButtonCreateOrder({
|
|
84
|
+
hostedButtonId,
|
|
85
|
+
merchantId,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// $FlowIssue
|
|
89
|
+
request.mockImplementation(() =>
|
|
90
|
+
ZalgoPromise.resolve({
|
|
91
|
+
body: {
|
|
92
|
+
link_id: hostedButtonId,
|
|
93
|
+
merchant_id: merchantId,
|
|
94
|
+
context_id: orderID,
|
|
95
|
+
status: "CREATED",
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
const createdOrderID = await createOrder({ paymentSource: "paypal" });
|
|
100
|
+
expect(createdOrderID).toBe(orderID);
|
|
101
|
+
expect.assertions(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("buildHostedButtonOnApprove", () => {
|
|
105
|
+
test("makes a request to the Hosted Buttons API", async () => {
|
|
106
|
+
const onApprove = buildHostedButtonOnApprove({
|
|
107
|
+
hostedButtonId,
|
|
108
|
+
merchantId,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// $FlowIssue
|
|
112
|
+
request.mockImplementation(() =>
|
|
113
|
+
ZalgoPromise.resolve({
|
|
114
|
+
body: {},
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
await onApprove({ orderID, paymentSource: "paypal" });
|
|
118
|
+
expect(request).toHaveBeenCalledWith(
|
|
119
|
+
expect.objectContaining({
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
entry_point: "SDK",
|
|
122
|
+
merchant_id: merchantId,
|
|
123
|
+
context_id: orderID,
|
|
124
|
+
}),
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
expect.assertions(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("provides its own popup for inline guest", async () => {
|
|
131
|
+
const onApprove = buildHostedButtonOnApprove({
|
|
132
|
+
hostedButtonId,
|
|
133
|
+
merchantId,
|
|
134
|
+
});
|
|
135
|
+
// $FlowIssue
|
|
136
|
+
request.mockImplementation(() =>
|
|
137
|
+
ZalgoPromise.resolve({
|
|
138
|
+
body: {},
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// $FlowIssue
|
|
143
|
+
supportsPopups.mockImplementation(() => true);
|
|
144
|
+
await onApprove({ orderID, paymentSource: "card" });
|
|
145
|
+
expect(popup).toHaveBeenCalled();
|
|
146
|
+
|
|
147
|
+
// but redirects if popups are not supported
|
|
148
|
+
// $FlowIssue
|
|
149
|
+
supportsPopups.mockImplementation(() => false);
|
|
150
|
+
await onApprove({ orderID, paymentSource: "card" });
|
|
151
|
+
expect(window.location).toMatch(
|
|
152
|
+
`/ncp/payment/${hostedButtonId}/${orderID}`
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect.assertions(2);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -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
|
+
}
|
package/src/ui/buttons/props.js
CHANGED
|
@@ -521,6 +521,7 @@ export type ButtonProps = {|
|
|
|
521
521
|
renderedButtons: $ReadOnlyArray<$Values<typeof FUNDING>>,
|
|
522
522
|
createVaultSetupToken: CreateVaultSetupToken,
|
|
523
523
|
displayOnly?: $ReadOnlyArray<$Values<typeof DISPLAY_ONLY_VALUES>>,
|
|
524
|
+
hostedButtonId?: string,
|
|
524
525
|
|};
|
|
525
526
|
|
|
526
527
|
// eslint-disable-next-line flowtype/require-exact-type
|
|
@@ -158,8 +158,13 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
|
|
|
158
158
|
<PrerenderedButtons
|
|
159
159
|
nonce={props.nonce}
|
|
160
160
|
props={props}
|
|
161
|
-
onRenderCheckout={({ win, fundingSource, card }) => {
|
|
162
|
-
state.prerenderDetails = {
|
|
161
|
+
onRenderCheckout={({ win, fundingSource, card, hostedButtonId }) => {
|
|
162
|
+
state.prerenderDetails = {
|
|
163
|
+
win,
|
|
164
|
+
fundingSource,
|
|
165
|
+
card,
|
|
166
|
+
hostedButtonId,
|
|
167
|
+
};
|
|
163
168
|
}}
|
|
164
169
|
/>
|
|
165
170
|
).render(dom({ doc }));
|
|
@@ -849,6 +854,12 @@ export const getButtonsComponent: () => ButtonsComponent = memoize(() => {
|
|
|
849
854
|
value: getExperimentation,
|
|
850
855
|
},
|
|
851
856
|
|
|
857
|
+
hostedButtonId: {
|
|
858
|
+
type: "string",
|
|
859
|
+
required: false,
|
|
860
|
+
queryParam: true,
|
|
861
|
+
},
|
|
862
|
+
|
|
852
863
|
displayOnly: {
|
|
853
864
|
type: "array",
|
|
854
865
|
queryParam: true,
|
|
@@ -27,6 +27,7 @@ type PrerenderedButtonsProps = {|
|
|
|
27
27
|
win?: CrossDomainWindowType,
|
|
28
28
|
fundingSource: $Values<typeof FUNDING>,
|
|
29
29
|
card: ?$Values<typeof CARD>,
|
|
30
|
+
hostedButtonId?: string,
|
|
30
31
|
|}) => void,
|
|
31
32
|
|};
|
|
32
33
|
|
|
@@ -77,7 +78,12 @@ export function PrerenderedButtons({
|
|
|
77
78
|
|
|
78
79
|
writeElementToWindow(win, spinner);
|
|
79
80
|
|
|
80
|
-
onRenderCheckout({
|
|
81
|
+
onRenderCheckout({
|
|
82
|
+
win,
|
|
83
|
+
fundingSource,
|
|
84
|
+
card,
|
|
85
|
+
hostedButtonId: props.hostedButtonId,
|
|
86
|
+
});
|
|
81
87
|
} else {
|
|
82
88
|
onRenderCheckout({ fundingSource, card });
|
|
83
89
|
}
|
|
@@ -317,6 +317,12 @@ export function getCheckoutComponent(): CheckoutComponent {
|
|
|
317
317
|
queryParam: true,
|
|
318
318
|
required: false,
|
|
319
319
|
},
|
|
320
|
+
|
|
321
|
+
hostedButtonId: {
|
|
322
|
+
type: "string",
|
|
323
|
+
required: false,
|
|
324
|
+
queryParam: true,
|
|
325
|
+
},
|
|
320
326
|
},
|
|
321
327
|
|
|
322
328
|
dimensions: ({ props }) => {
|