Home Reference Source

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;
}