src/script.js
/* @flow */
import {
getCurrentScriptUID,
ATTRIBUTES,
parseQuery,
getBrowserLocales,
base64decode,
values,
getCurrentScript,
memoize,
stringifyError,
getScript,
} from "@krakenjs/belter/src";
import {
COUNTRY,
SDK_SETTINGS,
SDK_QUERY_KEYS,
INTENT,
COMMIT,
VAULT,
CURRENCY,
FUNDING,
CARD,
COUNTRY_LANGS,
DEFAULT_INTENT,
DEFAULT_CURRENCY,
DEFAULT_VAULT,
QUERY_BOOL,
LANG,
type LocaleType,
DEFAULT_SALE_COMMIT,
DEFAULT_NONSALE_COMMIT,
PAGE_TYPES,
} from "@paypal/sdk-constants/src";
import { getPath, getDefaultNamespace, getSDKHost } from "./global";
import { CLIENT_ID_ALIAS } from "./config";
import { getComputedLocales } from "./utils";
type GetSDKScript = () => HTMLScriptElement;
export const getSDKScript: GetSDKScript = memoize(() => {
if (__TEST__) {
const script = getScript({
host: getSDKHost(),
path: getPath(),
reverse: true,
});
if (!script) {
throw new Error(`Can not find SDK test script`);
}
return script;
}
try {
return getCurrentScript();
} catch (err) {
throw new Error(
`PayPal Payments SDK script not found on page! Expected to find <script src="https://${getSDKHost()}${getPath()}">\n\n${stringifyError(
err
)}`
);
}
});
type GetSDKAttributes = () => { [string]: string };
export const getSDKAttributes: GetSDKAttributes = memoize(() => {
const sdkScript = getSDKScript();
const result = {};
for (const attr of sdkScript.attributes) {
if (attr.name.indexOf("data-") === 0) {
result[attr.name] = attr.value;
}
}
result[ATTRIBUTES.UID] = getCurrentScriptUID();
return result;
});
export function getSDKAttribute<T: string | void>(
name: $Values<typeof SDK_SETTINGS>,
def: T
): T {
// $FlowFixMe
return getSDKAttributes()[name] || def;
}
export function getSDKQueryParams(): { [string]: string } {
const script = getSDKScript();
return parseQuery(script.src.split("?")[1] || "");
}
type GetSDKQueryParam =
// eslint-disable-next-line no-undef
(<T: string>($Values<typeof SDK_QUERY_KEYS>) => T | void) &
// eslint-disable-next-line no-undef
(<T: string>($Values<typeof SDK_QUERY_KEYS>, T) => T);
export const getSDKQueryParam: GetSDKQueryParam = <T>(name: string, def: T) => {
// $FlowFixMe
return getSDKQueryParams()[name] || def;
};
export function getScriptUrl(): string {
const src = getSDKScript().getAttribute("src");
if (!src) {
throw new Error(`Can not find src for sdk script`);
}
return src;
}
export function getSDKQueryParamBool<T: boolean>(
name: $Values<typeof SDK_QUERY_KEYS>,
def?: T
): T {
return (
// $FlowFixMe
getSDKQueryParam(name, def ? def.toString() : QUERY_BOOL.FALSE) ===
QUERY_BOOL.TRUE
);
}
export function getClientID(): string {
const clientID = getSDKQueryParam(SDK_QUERY_KEYS.CLIENT_ID);
if (!clientID) {
throw new Error(
`Expected ${SDK_QUERY_KEYS.CLIENT_ID} parameter in sdk url`
);
}
if (CLIENT_ID_ALIAS[clientID]) {
return CLIENT_ID_ALIAS[clientID];
}
return clientID;
}
export function getMerchantID(): $ReadOnlyArray<string> {
const merchantIDString = getSDKQueryParam(SDK_QUERY_KEYS.MERCHANT_ID);
if (merchantIDString === "*") {
// get multiple merchant ids or emails from data-merchant-id
const merchantIDAttribute = getSDKAttribute(SDK_SETTINGS.MERCHANT_ID);
if (!merchantIDAttribute) {
throw new Error(
`Must pass ${SDK_SETTINGS.MERCHANT_ID} when ${SDK_QUERY_KEYS.MERCHANT_ID}=* passed in url`
);
}
const merchantID = merchantIDAttribute.split(",");
if (merchantID.length <= 1) {
throw new Error(
`Must pass multiple merchant ids to ${SDK_SETTINGS.MERCHANT_ID}. If passing a single id, pass ${SDK_QUERY_KEYS.MERCHANT_ID}=XYZ in url`
);
}
// check duplicates
const hasDuplicate = merchantID.some(
(val, i) => merchantID && merchantID.indexOf(val) !== i
);
if (hasDuplicate) {
throw new Error(
`Duplicates ${SDK_SETTINGS.MERCHANT_ID}. Must pass unique merchant ids to ${SDK_SETTINGS.MERCHANT_ID}.`
);
}
return merchantID;
}
if (merchantIDString) {
return merchantIDString.split(",");
}
return [];
}
export function getIntent(): $Values<typeof INTENT> {
return getSDKQueryParam(SDK_QUERY_KEYS.INTENT, DEFAULT_INTENT);
}
export function getCommit(): $Values<typeof COMMIT> {
return getSDKQueryParamBool(
SDK_QUERY_KEYS.COMMIT,
getIntent() === INTENT.CAPTURE
? DEFAULT_SALE_COMMIT
: DEFAULT_NONSALE_COMMIT
);
}
export function getVault(): $Values<typeof VAULT> {
return getSDKQueryParamBool(SDK_QUERY_KEYS.VAULT, DEFAULT_VAULT);
}
export function getCurrency(): $Values<typeof CURRENCY> {
return getSDKQueryParam(SDK_QUERY_KEYS.CURRENCY, DEFAULT_CURRENCY);
}
export function getEnableFunding(): $ReadOnlyArray<?$Values<typeof FUNDING>> {
const funding = getSDKQueryParam(SDK_QUERY_KEYS.ENABLE_FUNDING);
if (funding) {
return funding.split(",");
}
return [];
}
export function getDisableFunding(): $ReadOnlyArray<?$Values<typeof FUNDING>> {
const funding = getSDKQueryParam(SDK_QUERY_KEYS.DISABLE_FUNDING);
if (funding) {
return funding.split(",");
}
return [];
}
export function getDisableCard(): $ReadOnlyArray<?$Values<typeof CARD>> {
const funding = getSDKQueryParam(SDK_QUERY_KEYS.DISABLE_CARD);
if (funding) {
return funding.split(",");
}
return [];
}
export function getBuyerCountry(): ?$Values<typeof COUNTRY> {
return getSDKQueryParam(SDK_QUERY_KEYS.BUYER_COUNTRY);
}
export function getNamespace(): string {
return getSDKAttribute(SDK_SETTINGS.NAMESPACE) || getDefaultNamespace();
}
export function getClientToken(): ?string {
return getSDKAttribute(SDK_SETTINGS.CLIENT_TOKEN);
}
export function getAmount(): ?string {
const amount = getSDKAttribute(SDK_SETTINGS.AMOUNT);
if (amount && !amount.match(/^\d+\.\d\d$/)) {
throw new Error(`Invalid amount: ${amount}`);
}
return amount;
}
export function getUserIDToken(): ?string {
const idToken = getSDKAttribute(SDK_SETTINGS.USER_ID_TOKEN);
if (idToken) {
return idToken;
}
const clientToken = getClientToken();
if (clientToken) {
const token = JSON.parse(base64decode(clientToken)).paypal.idToken;
return token;
}
}
export function getClientAccessToken(): ?string {
const clientToken = getClientToken();
if (clientToken) {
return JSON.parse(base64decode(clientToken)).paypal.accessToken;
}
}
export function getPartnerAttributionID(): ?string {
return getSDKAttribute(SDK_SETTINGS.PARTNER_ATTRIBUTION_ID);
}
export function getMerchantRequestedPopupsDisabled(): boolean {
return getSDKAttribute(SDK_SETTINGS.POPUPS_DISABLED) === "true";
}
export function getPageType(): ?string {
const pageType = getSDKAttribute(SDK_SETTINGS.PAGE_TYPE, "");
const validPageType =
values(PAGE_TYPES).indexOf(pageType.toLowerCase()) !== -1;
if (!validPageType && pageType.length) {
throw new Error(`Invalid page type, '${pageType}'`);
}
return pageType.toLowerCase();
}
export function getLocale(): LocaleType {
const locale = getSDKQueryParam(SDK_QUERY_KEYS.LOCALE);
if (locale) {
return getComputedLocales(locale);
}
for (let { country, lang } of getBrowserLocales()) {
country = country && COUNTRY[country];
lang = lang && LANG[lang.toUpperCase()];
if (
country &&
lang &&
COUNTRY_LANGS[country] &&
COUNTRY_LANGS[country].indexOf(lang) !== -1
) {
return { country, lang };
} else if (lang) {
// We infer country from language if there is only one possible country match
const possibleCountries = Object.keys(COUNTRY_LANGS).filter((c) =>
COUNTRY_LANGS[c].some((l) => l === lang)
);
if (possibleCountries.length === 1) {
const possibleCountry = possibleCountries[0];
return { country: possibleCountry, lang };
}
}
}
for (const { country } of getBrowserLocales()) {
if (COUNTRY_LANGS.hasOwnProperty(country)) {
// $FlowFixMe
return { country, lang: COUNTRY_LANGS[country][0] };
}
}
return {
lang: LANG.EN,
country: COUNTRY.US,
};
}
export function getCSPNonce(): string {
return getSDKAttribute(SDK_SETTINGS.CSP_NONCE) || "";
}
export function getEnableThreeDomainSecure(): boolean {
return getSDKAttributes().hasOwnProperty(SDK_SETTINGS.ENABLE_3DS);
}
export function getSDKIntegrationSource(): ?string {
return getSDKAttribute(SDK_SETTINGS.SDK_INTEGRATION_SOURCE);
}
export function getUserExperienceFlow(): ?string {
return getSDKAttribute(SDK_SETTINGS.USER_EXPERIENCE_FLOW);
}
// whether in zoid window
export function isChildWindow(): boolean {
return Boolean(window.xprops);
}
// istanbul ignore next
export function getUserAccessToken(): ?string {
// pass
}
// istanbul ignore next
export function getUserAuthCode(): ?string {
// pass
}
// Remove
// istanbul ignore next
export function getCountry(): $Values<typeof COUNTRY> {
return getLocale().country;
}
// Remove
// istanbul ignore next
export function getLang(): $Values<typeof LANG> {
return getLocale().lang;
}