"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/core/index.ts
var core_exports = {};
__export(core_exports, {
  CloverElementType: () => CloverElementType,
  ElementRegistry: () => ElementRegistry,
  FIELD_KEY_TO_CLOVER_TYPE: () => FIELD_KEY_TO_CLOVER_TYPE,
  PaymentFieldsMachine: () => PaymentFieldsMachine,
  ThreeDsMachine: () => ThreeDsMachine,
  ValidationMachine: () => ValidationMachine,
  __SDK_VERSION__: () => __SDK_VERSION__,
  compileTheme: () => compileTheme,
  createRateLimiter: () => createRateLimiter,
  getBrowserInfo: () => getBrowserInfo,
  loadClover: () => loadClover,
  loadClover3DS: () => loadClover3DS,
  parseInterim: () => parseInterim,
  postFinalize: () => postFinalize,
  tokenize: () => tokenize
});
module.exports = __toCommonJS(core_exports);

// src/core/browser-info.ts
function getBrowserInfo(util) {
  if (!util?.getBrowserInfo) {
    return void 0;
  }
  try {
    return util.getBrowserInfo();
  } catch (error) {
    console.warn("[wcp-payment-fields] getBrowserInfo failed", error);
    return void 0;
  }
}

// src/core/clover-loader.ts
var POLL_INTERVAL_MS = 50;
var DEFAULT_TIMEOUT_MS = 1e4;
var inFlight = /* @__PURE__ */ new Map();
function waitForGlobal({ url, globalName, timeoutMs = DEFAULT_TIMEOUT_MS }) {
  const w = window;
  if (w[globalName] !== void 0) {
    return Promise.resolve();
  }
  const cached = inFlight.get(url);
  if (cached) {
    return cached;
  }
  const promise = new Promise((resolve, reject) => {
    const existingTag = document.querySelector(`script[src="${url}"]`);
    if (!existingTag) {
      const script = document.createElement("script");
      script.src = url;
      script.async = true;
      script.onerror = () => {
        inFlight.delete(url);
        reject(new Error(`Failed to load script: ${url}`));
      };
      document.head.appendChild(script);
    }
    const start = Date.now();
    const poll = () => {
      if (w[globalName] !== void 0) {
        resolve();
        return;
      }
      if (Date.now() - start >= timeoutMs) {
        inFlight.delete(url);
        reject(new Error(`Timed out waiting for window.${globalName} from ${url}`));
        return;
      }
      setTimeout(poll, POLL_INTERVAL_MS);
    };
    poll();
  });
  inFlight.set(url, promise);
  return promise;
}
async function loadClover(url) {
  await waitForGlobal({ url, globalName: "Clover" });
  const Clover = window.Clover;
  if (!Clover) {
    throw new Error("Clover SDK loaded but window.Clover is missing.");
  }
  return Clover;
}
async function loadClover3DS(url) {
  await waitForGlobal({ url, globalName: "Clover3DS" });
  const Clover3DS = window.Clover3DS;
  if (!Clover3DS) {
    throw new Error("Clover3DS SDK loaded but window.Clover3DS is missing.");
  }
  return Clover3DS;
}

// src/core/types.ts
var CloverElementType = {
  CARD: "CARD",
  NUMBER: "CARD_NUMBER",
  DATE: "CARD_DATE",
  CVV: "CARD_CVV",
  POSTAL_CODE: "CARD_POSTAL_CODE",
  STREET_ADDRESS: "CARD_STREET_ADDRESS",
  NAME: "CARD_NAME",
  EMAIL_ADDRESS: "CARD_EMAIL_ADDRESS",
  PHONE_NUMBER: "CARD_PHONE_NUMBER",
  PAYMENT_REQUEST_BUTTON: "PAYMENT_REQUEST_BUTTON"
};
var FIELD_KEY_TO_CLOVER_TYPE = {
  cardNumber: CloverElementType.NUMBER,
  cardDate: CloverElementType.DATE,
  cardCvv: CloverElementType.CVV,
  cardPostalCode: CloverElementType.POSTAL_CODE,
  cardName: CloverElementType.NAME,
  cardEmail: CloverElementType.EMAIL_ADDRESS,
  paymentRequestButton: CloverElementType.PAYMENT_REQUEST_BUTTON
};

// src/core/element-registry.ts
var ElementRegistry = class {
  constructor(options) {
    this.elements = /* @__PURE__ */ new Map();
    this.destroyed = false;
    this.options = options;
  }
  /**
   * Create a Clover element for each key. Skips keys already created.
   * GPay button creation can throw on devices without PaymentRequest support —
   * caught + logged + skipped, never breaks the rest of the form.
   */
  create(keys) {
    this.assertAlive();
    const cloverElements = this.options.clover.elements();
    for (const key of keys) {
      if (this.elements.has(key)) {
        continue;
      }
      const type = FIELD_KEY_TO_CLOVER_TYPE[key];
      const data = key === "paymentRequestButton" && this.options.paymentRequestData ? this.options.paymentRequestData : this.options.styles;
      try {
        const el = cloverElements.create(type, data);
        this.elements.set(key, el);
      } catch (error) {
        console.warn(`[wcp-payment-fields] failed to create element "${key}"`, error);
      }
    }
    return this;
  }
  /**
   * Mount each created element at its target CSS selector. Skips fields whose
   * target is missing or whose host div doesn't exist in the DOM yet.
   */
  mount(targets) {
    this.assertAlive();
    this.mountIfTargeted("cardNumber", targets.cardNumber);
    this.mountIfTargeted("cardDate", targets.cardDate);
    this.mountIfTargeted("cardCvv", targets.cardCvv);
    this.mountIfTargeted("cardPostalCode", targets.cardPostalCode);
    this.mountIfTargeted("cardName", targets.cardName);
    this.mountIfTargeted("cardEmail", targets.cardEmail);
    this.mountIfTargeted("paymentRequestButton", targets.paymentRequestButton);
    return this;
  }
  get(key) {
    return this.elements.get(key);
  }
  entries() {
    return this.elements.entries();
  }
  destroy() {
    this.destroyed = true;
    this.elements.clear();
  }
  mountIfTargeted(key, selector) {
    if (!selector) {
      return;
    }
    const el = this.elements.get(key);
    if (!el) {
      return;
    }
    const host = document.querySelector(selector);
    if (!host) {
      return;
    }
    try {
      el.mount(selector);
    } catch (error) {
      console.warn(`[wcp-payment-fields] failed to mount "${key}" at "${selector}"`, error);
      return;
    }
    suppressIframeScrollbars(host);
  }
  assertAlive() {
    if (this.destroyed) {
      throw new Error("ElementRegistry has been destroyed.");
    }
  }
};
function suppressIframeScrollbars(host) {
  const apply = (frame) => {
    frame.setAttribute("scrolling", "no");
    frame.style.overflow = "hidden";
  };
  for (const frame of host.querySelectorAll("iframe")) {
    apply(frame);
  }
  const observer = new MutationObserver((mutations) => {
    for (const m of mutations) {
      for (const node of m.addedNodes) {
        if (node instanceof HTMLIFrameElement) {
          apply(node);
        }
      }
    }
  });
  observer.observe(host, { childList: true, subtree: true });
}

// src/core/theme.ts
var PLACEHOLDER_PSEUDOS = [
  "::placeholder",
  "::-webkit-input-placeholder",
  "::-moz-placeholder",
  ":-ms-input-placeholder",
  ":-moz-placeholder"
];
var LEFT_ALIGNED_FIELDS = ["card-number", "card-name", "card-email-address"];
var CENTER_ALIGNED_FIELDS = ["card-date", "card-cvv", "card-postal-code"];
function compileTheme(theme) {
  const styles = {};
  const inputHeight = theme?.inputHeight ?? "46px";
  styles.input = stripUndefined({
    padding: "0",
    margin: "0",
    border: "none",
    height: inputHeight,
    width: "100%",
    lineHeight: inputHeight,
    fontFamily: theme?.fontFamily,
    fontSize: theme?.fontSize,
    color: theme?.textColor
  });
  for (const field of LEFT_ALIGNED_FIELDS) {
    styles[`${field} input`] = {
      textAlign: "left",
      textIndent: "14px"
    };
  }
  for (const field of CENTER_ALIGNED_FIELDS) {
    styles[`${field} input`] = {
      textAlign: "center"
    };
  }
  if (theme?.placeholderColor) {
    for (const pseudo of PLACEHOLDER_PSEUDOS) {
      for (const field of [...LEFT_ALIGNED_FIELDS, ...CENTER_ALIGNED_FIELDS]) {
        styles[`${field} input${pseudo}`] = { color: theme.placeholderColor };
      }
    }
  }
  if (theme?.overrides) {
    Object.assign(styles, theme.overrides);
  }
  return styles;
}
function stripUndefined(obj) {
  const out = {};
  for (const [k, v] of Object.entries(obj)) {
    if (v !== void 0) {
      out[k] = v;
    }
  }
  return out;
}

// src/core/threeds-finalize-client.ts
async function postFinalize(request) {
  const headers = {
    "Content-Type": "application/json",
    Accept: "application/json"
  };
  if (request.headers) {
    Object.assign(headers, await request.headers());
  }
  let response;
  try {
    response = await fetch(request.url, {
      method: "POST",
      headers,
      body: JSON.stringify(request.body)
    });
  } catch (error) {
    throw new Error(`Network error during 3DS finalize: ${error.message}`);
  }
  let data = null;
  try {
    data = await response.json();
  } catch {
    data = null;
  }
  return { ok: response.ok, data };
}
function parseInterim(raw) {
  if (!raw.charge_id) {
    return null;
  }
  return {
    state: raw.state,
    chargeId: raw.charge_id,
    fields: {
      acsUrl: raw.threeds.acs_url,
      acsTransactionId: raw.threeds.acs_transaction_id,
      methodUrl: raw.threeds.method_url,
      methodNotificationUrl: raw.threeds.method_notification_url,
      threeDsServerTransactionId: raw.threeds.threeds_server_transaction_id,
      protocolVersion: raw.threeds.threeds_protocol_version ?? raw.threeds.message_version
    }
  };
}

// src/core/threeds-machine.ts
var DEFAULT_METHOD_TIMEOUT_MS = 30 * 1e3;
var DEFAULT_CHALLENGE_TIMEOUT_MS = 5 * 60 * 1e3;
var ThreeDsMachine = class {
  constructor(options) {
    this.cancelled = false;
    this.options = options;
  }
  cancel() {
    this.cancelled = true;
  }
  /**
   * Run a single 3DS interim payload to a terminal result. For escalation
   * (method completes → Clover wants challenge, or vice versa), the result
   * is returned with `kind: 'escalation'`; the caller (PaymentFieldsMachine)
   * re-enters the loop with the next interim.
   */
  async run(interim) {
    if (this.cancelled) {
      return { kind: "failure", message: "Cancelled." };
    }
    if (interim.state === "method_required") {
      try {
        const status = await this.runMethod(interim);
        if (this.cancelled) {
          return { kind: "failure", message: "Cancelled." };
        }
        return this.finalize(interim, status);
      } catch (error) {
        return { kind: "failure", message: error.message };
      }
    }
    if (interim.state === "challenge_required") {
      const cleanup = this.styleChallengeContainer();
      try {
        const status = await this.runChallenge(interim);
        if (this.cancelled) {
          return { kind: "failure", message: "Cancelled." };
        }
        return await this.finalize(interim, status);
      } catch (error) {
        return { kind: "failure", message: error.message };
      } finally {
        cleanup();
      }
    }
    return {
      kind: "failure",
      message: `Unsupported 3DS state: ${interim.state}`
    };
  }
  // ── flow handlers ──
  runMethod(interim) {
    const f = interim.fields;
    if (!f.threeDsServerTransactionId || !f.methodUrl || !f.methodNotificationUrl) {
      return Promise.reject(new Error("3DS method prerequisites missing."));
    }
    return this.awaitExecutePatch(
      this.options.methodTimeoutMs ?? DEFAULT_METHOD_TIMEOUT_MS,
      "method flow timed out",
      () => this.options.util.perform3DSFingerPrinting({
        threeDSServerTransID: f.threeDsServerTransactionId,
        threeDSMethodUrl: f.methodUrl,
        methodNotificationUrl: f.methodNotificationUrl
      })
    );
  }
  runChallenge(interim) {
    const f = interim.fields;
    if (!f.acsUrl || !f.acsTransactionId || !f.threeDsServerTransactionId || !f.protocolVersion) {
      return Promise.reject(new Error("3DS challenge prerequisites missing."));
    }
    return this.awaitExecutePatch(
      this.options.challengeTimeoutMs ?? DEFAULT_CHALLENGE_TIMEOUT_MS,
      "challenge flow timed out",
      () => this.options.util.perform3DSChallenge({
        messageVersion: f.protocolVersion,
        acsTransID: f.acsTransactionId,
        acsUrl: f.acsUrl,
        threeDSServerTransID: f.threeDsServerTransactionId
      })
    );
  }
  awaitExecutePatch(timeoutMs, timeoutMessage, kickoff) {
    return new Promise((resolve, reject) => {
      let settled = false;
      const finalize = (status) => {
        if (settled) return;
        settled = true;
        window.clearTimeout(timeoutId);
        document.removeEventListener("executePatch", handler);
        resolve(status);
      };
      const handler = (ev) => {
        const detail = ev.detail;
        finalize(detail?._3DSStatus ?? "");
      };
      const timeoutId = window.setTimeout(() => {
        if (settled) return;
        settled = true;
        document.removeEventListener("executePatch", handler);
        reject(new Error(timeoutMessage));
      }, timeoutMs);
      document.addEventListener("executePatch", handler);
      try {
        kickoff();
      } catch (error) {
        settled = true;
        window.clearTimeout(timeoutId);
        document.removeEventListener("executePatch", handler);
        reject(error);
      }
    });
  }
  // ── finalize ──
  async finalize(interim, flowStatus) {
    const body = {
      charge_id: interim.chargeId,
      flow_status: flowStatus,
      ...this.options.extraBody ?? {}
    };
    let envelope;
    try {
      envelope = await postFinalize({
        url: this.options.finalizeUrl,
        headers: this.options.headers,
        body
      });
    } catch (error) {
      return { kind: "failure", message: error.message };
    }
    const data = envelope.data;
    if (!data) {
      return { kind: "failure", message: "Verification response unreadable." };
    }
    if (data.weeconnectpay_threeds_interim) {
      const next = parseInterim(data.weeconnectpay_threeds_interim);
      if (next) {
        return { kind: "escalation", next };
      }
    }
    if (envelope.ok && data.success === true) {
      return { kind: "success", redirect: data.redirect, data };
    }
    return {
      kind: "failure",
      message: data.message ?? "Payment verification failed."
    };
  }
  // ── challenge container styling ──
  //
  // Clover injects `#threedsContainer` into the DOM when `perform3DSChallenge`
  // runs. We style it (overlay + centered card) via a MutationObserver so we
  // don't depend on Clover's render timing.
  styleChallengeContainer() {
    const container = document.getElementById("threedsContainer");
    if (!container) {
      return () => void 0;
    }
    container.style.cssText = "position:fixed;inset:0;z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(0,0,0,0.6);padding:1rem;";
    const styleIframe = () => {
      const iframe = container.querySelector("iframe");
      if (!iframe) {
        return false;
      }
      iframe.style.background = "white";
      iframe.style.borderRadius = "0.75rem";
      iframe.style.boxShadow = "0 25px 50px -12px rgba(0,0,0,0.4)";
      iframe.style.maxWidth = "100%";
      iframe.style.display = "block";
      return true;
    };
    let observer = null;
    if (!styleIframe()) {
      observer = new MutationObserver(() => {
        if (styleIframe()) {
          observer?.disconnect();
          observer = null;
        }
      });
      observer.observe(container, { childList: true });
    }
    return () => {
      observer?.disconnect();
      container.style.cssText = "";
    };
  }
};

// src/core/tokenize.ts
function createRateLimiter(options = {}) {
  const windowMs = options.windowMs ?? 5e3;
  const maxCalls = options.maxCalls ?? 2;
  const timestamps = [];
  return {
    check() {
      const now = Date.now();
      const cutoff = now - windowMs;
      while (timestamps.length > 0 && timestamps[0] < cutoff) {
        timestamps.shift();
      }
      if (timestamps.length >= maxCalls) {
        return false;
      }
      timestamps.push(now);
      return true;
    }
  };
}
async function tokenize(clover, options = {}) {
  if (options.rateLimiter && !options.rateLimiter.check()) {
    throw new Error("Rate limit exceeded for tokenization. Wait a moment and try again.");
  }
  return clover.createToken();
}

// src/core/validation-machine.ts
var VALIDATING_FIELDS = [
  "cardNumber",
  "cardDate",
  "cardCvv",
  "cardPostalCode",
  "cardName",
  "cardEmail"
];
var REQUIRES_NON_EMPTY_OVERRIDE = /* @__PURE__ */ new Set(["cardName", "cardEmail"]);
var CLOVER_KEY_TO_FIELD_KEY = {
  CARD_NUMBER: "cardNumber",
  CARD_DATE: "cardDate",
  CARD_CVV: "cardCvv",
  CARD_POSTAL_CODE: "cardPostalCode",
  CARD_NAME: "cardName",
  CARD_EMAIL_ADDRESS: "cardEmail"
};
function emptyFieldState() {
  return {
    touched: false,
    error: void 0,
    hasBlurred: false,
    hadChangeEver: false,
    displayedError: void 0
  };
}
function toPublic(s) {
  return {
    touched: s.touched,
    error: s.error,
    hasBlurred: s.hasBlurred,
    hadChangeEver: s.hadChangeEver,
    displayedError: s.displayedError
  };
}
function isMeaningfulError(err) {
  return err !== void 0 && err !== "";
}
var ValidationMachine = class {
  constructor(requiredFields) {
    this.state = /* @__PURE__ */ new Map();
    this.required = /* @__PURE__ */ new Set();
    this.listeners = /* @__PURE__ */ new Set();
    for (const f of requiredFields) {
      if (VALIDATING_FIELDS.includes(f)) {
        this.required.add(f);
      }
    }
  }
  /** Wire up the validation machine to a set of Clover elements. */
  attach(elements) {
    for (const [key, el] of elements.entries()) {
      if (!VALIDATING_FIELDS.includes(key)) {
        continue;
      }
      el.addEventListener("change", (event) => this.onCloverEvent(event, "change", key));
      el.addEventListener("blur", (event) => this.onCloverEvent(event, "blur", key));
    }
  }
  /**
   * Overlay tokenize-time errors onto per-field `displayedError`. Used when
   * `clover.createToken()` returns errors at submit time — we route them
   * back to the firing fields instead of joining them into a generic banner.
   *
   * For each field that errored we also set `hasBlurred = true`, so the
   * existing live-error-clearing rule kicks in: as soon as the user edits the
   * field, the next `change` event recomputes `displayedError` from Clover's
   * current state and clears the submit error.
   *
   * The Clover-side empty-vs-valid ambiguity for `CARD_NAME` / `CARD_EMAIL_ADDRESS`
   * means createToken may or may not emit errors for those fields when empty;
   * we surface whatever Clover reports and leave the rest as-is.
   */
  setSubmitErrors(errors) {
    let changed = false;
    for (const [cloverKey, fieldKey] of Object.entries(CLOVER_KEY_TO_FIELD_KEY)) {
      const message = errors[cloverKey];
      if (!message) {
        continue;
      }
      const prev = this.state.get(fieldKey) ?? emptyFieldState();
      const next = {
        ...prev,
        hasBlurred: true,
        displayedError: message
      };
      if (prev.hasBlurred !== next.hasBlurred || prev.displayedError !== next.displayedError) {
        this.state.set(fieldKey, next);
        changed = true;
      }
    }
    if (changed) {
      this.emit();
    }
  }
  subscribe(listener) {
    this.listeners.add(listener);
    return () => {
      this.listeners.delete(listener);
    };
  }
  getSnapshot() {
    const fields = {};
    for (const [key, val] of this.state.entries()) {
      fields[key] = toPublic(val);
    }
    return { fields, canSubmit: this.computeCanSubmit() };
  }
  onCloverEvent(event, eventType, ownerKey) {
    const ev = event;
    let changed = false;
    for (const [cloverKey, fieldKey] of Object.entries(CLOVER_KEY_TO_FIELD_KEY)) {
      const cloverState = ev[cloverKey];
      if (!cloverState) {
        continue;
      }
      const prev = this.state.get(fieldKey) ?? emptyFieldState();
      const next = {
        ...prev,
        touched: cloverState.touched,
        error: isMeaningfulError(cloverState.error) ? cloverState.error : void 0
      };
      const isOwnEvent = fieldKey === ownerKey;
      if (isOwnEvent && eventType === "change") {
        next.hadChangeEver = true;
      }
      if (isOwnEvent && eventType === "blur") {
        next.hasBlurred = true;
      }
      if (eventType === "blur" || next.hasBlurred) {
        next.displayedError = computeDisplayedError(fieldKey, next, this.required);
      } else {
        next.displayedError = void 0;
      }
      if (prev.touched !== next.touched || prev.error !== next.error || prev.hasBlurred !== next.hasBlurred || prev.hadChangeEver !== next.hadChangeEver || prev.displayedError !== next.displayedError) {
        this.state.set(fieldKey, next);
        changed = true;
      }
    }
    if (changed) {
      this.emit();
    }
  }
  computeCanSubmit() {
    for (const field of this.required) {
      const s = this.state.get(field);
      if (!s || !s.touched || s.error) {
        return false;
      }
      if (REQUIRES_NON_EMPTY_OVERRIDE.has(field) && !s.hadChangeEver) {
        return false;
      }
    }
    return true;
  }
  emit() {
    const snapshot = this.getSnapshot();
    for (const listener of this.listeners) {
      listener(snapshot);
    }
  }
};
function computeDisplayedError(fieldKey, state, required) {
  if (state.error) {
    return state.error;
  }
  if (state.hasBlurred && !state.hadChangeEver && required.has(fieldKey) && REQUIRES_NON_EMPTY_OVERRIDE.has(fieldKey)) {
    return "Required";
  }
  return void 0;
}

// src/core/payment-fields-machine.ts
var GPAY_CANCEL_DEBOUNCE_MS = 1e3;
var cloverByPakms = /* @__PURE__ */ new Map();
var clover3DSByMerchant = /* @__PURE__ */ new Map();
var PaymentFieldsMachine = class {
  constructor(config) {
    this.listeners = /* @__PURE__ */ new Set();
    this.rateLimiter = createRateLimiter();
    this.snapshot = {
      state: "idle",
      validation: { fields: {}, canSubmit: false }
    };
    this.mounted = false;
    this.destroyed = false;
    this.clover = null;
    this.threeDsUtil = null;
    this.registry = null;
    this.validation = null;
    this.capturedGpayToken = null;
    this.config = config;
  }
  // ── public API ──
  /** Subscribe to snapshot updates. The listener is called immediately with the current snapshot. */
  subscribe(listener) {
    this.listeners.add(listener);
    listener(this.snapshot);
    return () => {
      this.listeners.delete(listener);
    };
  }
  getSnapshot() {
    return this.snapshot;
  }
  /**
   * Load the Clover SDK(s), create the elements, mount them to the host's DOM
   * targets, and wire validation + GPay event handlers. Idempotent — calling
   * again is a no-op.
   */
  async mount(targets) {
    console.log("[wcp-pf] mount() called", { targets, config: { pakmsKey: this.config.pakmsKey?.slice(0, 8) + "\u2026", merchantId: this.config.merchantId, locale: this.config.locale, cartTotal: this.config.cartTotal, currency: this.config.currency, cloverSdkUrl: this.config.cloverSdkUrl, features: this.config.features } });
    if (this.destroyed) {
      throw new Error("Machine has been destroyed.");
    }
    if (this.mounted) {
      console.log("[wcp-pf] already mounted, skipping");
      return;
    }
    try {
      const cachedClover = cloverByPakms.get(this.config.pakmsKey);
      if (cachedClover) {
        console.log("[wcp-pf] Clover already instantiated for this pakms \u2014 reusing");
        this.clover = cachedClover;
      } else {
        console.log("[wcp-pf] loading Clover SDK from", this.config.cloverSdkUrl);
        const CloverCtor = await loadClover(this.config.cloverSdkUrl);
        console.log("[wcp-pf] Clover loaded, instantiating with pakms", this.config.pakmsKey?.slice(0, 8) + "\u2026");
        this.clover = new CloverCtor(this.config.pakmsKey);
        this.clover.options = {
          locale: this.config.locale,
          merchantId: this.config.merchantId
        };
        console.log("[wcp-pf] Clover instantiated", { hasElements: typeof this.clover.elements });
        cloverByPakms.set(this.config.pakmsKey, this.clover);
      }
    } catch (error) {
      console.error("[wcp-pf] Clover load/instantiate FAILED", error);
      this.emitError("clover_load_failed", "Failed to load Clover SDK.", error);
      throw error;
    }
    if (this.config.features?.threeDSecure && this.config.clover3DSSdkUrl) {
      const cached3DS = clover3DSByMerchant.get(this.config.merchantId);
      if (cached3DS) {
        this.threeDsUtil = cached3DS;
      } else {
        try {
          const Clover3DSCtor = await loadClover3DS(this.config.clover3DSSdkUrl);
          this.threeDsUtil = new Clover3DSCtor({ merchantUuid: this.config.merchantId });
          clover3DSByMerchant.set(this.config.merchantId, this.threeDsUtil);
        } catch (error) {
          this.emitError("clover_3ds_load_failed", "3DS SDK failed to load.", error);
        }
      }
    }
    const wantsGpay = this.config.features?.googlePay === true;
    const cardholderVisible = this.shouldShowCardholderFields();
    const keys = ["cardNumber", "cardDate", "cardCvv", "cardPostalCode"];
    if (cardholderVisible) {
      keys.push("cardName", "cardEmail");
    }
    if (wantsGpay) {
      keys.push("paymentRequestButton");
    }
    const styles = compileTheme(this.config.theme);
    const paymentRequestData = wantsGpay ? this.buildPaymentRequestData() : void 0;
    console.log("[wcp-pf] creating elements for keys", keys, "with targets", targets);
    this.registry = new ElementRegistry({
      clover: this.clover,
      styles,
      paymentRequestData
    });
    this.registry.create(keys);
    console.log("[wcp-pf] elements created, checking targets exist in DOM:", keys.map((k) => {
      const sel = targets[k];
      return { key: k, selector: sel, found: sel ? !!document.querySelector(sel) : "no-selector" };
    }));
    this.registry.mount(targets);
    console.log("[wcp-pf] registry.mount() returned");
    const elements = /* @__PURE__ */ new Map();
    for (const [k, el] of this.registry.entries()) {
      elements.set(k, el);
    }
    this.validation = new ValidationMachine(keys.filter((k) => k !== "paymentRequestButton"));
    this.validation.attach(elements);
    this.validation.subscribe((vs) => {
      this.update({ validation: vs });
    });
    const gpayEl = this.registry.get("paymentRequestButton");
    if (gpayEl) {
      this.wireGpayEvents(gpayEl);
    }
    this.mounted = true;
  }
  /**
   * Return a tokenization result the host can POST to the API.
   *
   * Two paths:
   * - **GPay**: if we're in `gpay_ready`, return the already-captured token immediately (no second prompt).
   * - **Manual card**: call `clover.createToken()`, rate-limited.
   *
   * Throws on rate-limit, invalid card, missing token, or unmounted state.
   */
  async tokenize() {
    if (!this.mounted) {
      const error = { code: "not_mounted", message: "mount() must complete before tokenize()." };
      this.update({ state: "error", error });
      throw new Error(error.message);
    }
    if (this.snapshot.state === "gpay_ready" && this.capturedGpayToken) {
      const result2 = this.capturedGpayToken;
      this.update({ state: "submitting", result: result2 });
      this.config.onTokenized?.(result2);
      return result2;
    }
    if (!this.clover) {
      const error = { code: "not_mounted", message: "Clover not initialized." };
      this.update({ state: "error", error });
      throw new Error(error.message);
    }
    this.update({ state: "tokenizing" });
    let raw;
    try {
      raw = await tokenize(this.clover, { rateLimiter: this.rateLimiter });
    } catch (error) {
      const message = error.message;
      const code = message.toLowerCase().includes("rate") ? "rate_limited" : "tokenization_failed";
      const err = { code, message, cause: error };
      this.update({ state: "error", error: err });
      this.config.onError?.(err);
      throw error;
    }
    if (raw.errors && hasErrors(raw.errors)) {
      this.validation?.setSubmitErrors(raw.errors);
      const err = {
        code: "tokenization_failed",
        message: firstError(raw.errors) ?? "Card details invalid."
      };
      this.update({ state: "error", error: err });
      this.config.onError?.(err);
      throw new Error(err.message);
    }
    if (!raw.token || !raw.card) {
      const err = {
        code: "tokenization_failed",
        message: "Tokenization returned no token."
      };
      this.update({ state: "error", error: err });
      this.config.onError?.(err);
      throw new Error(err.message);
    }
    const result = {
      source: "card",
      token: raw.token,
      card: {
        brand: raw.card.brand,
        last4: raw.card.last4,
        expMonth: raw.card.exp_month,
        expYear: raw.card.exp_year,
        first6: raw.card.first6,
        addressZip: raw.card.address_zip
      },
      browserInfo: this.config.features?.threeDSecure ? getBrowserInfo(this.threeDsUtil) : void 0
    };
    this.update({ state: "submitting", result });
    this.config.onTokenized?.(result);
    return result;
  }
  /**
   * Drive the 3DS flow given an interim payload from the host's API response.
   * Handles method/challenge dispatch, the `executePatch` callback, the
   * finalize POST, and escalation (method ↔ challenge) internally.
   *
   * @param options.extraBody Host-specific fields merged into the finalize POST body.
   *   E.g., WC passes `{ order_id, order_key, nonce }` so the WP REST endpoint
   *   can find and update the order. Keeps WC-specific concerns out of the SDK.
   */
  async runThreeDs(interim, options = {}) {
    if (!this.threeDsUtil) {
      const err = {
        code: "threeds_prerequisites_missing",
        message: "3DS SDK not loaded."
      };
      this.update({ state: "error", error: err });
      return { kind: "failure", message: err.message };
    }
    const finalizeUrl = this.config.endpoints?.threeDsFinalize;
    if (!finalizeUrl) {
      const err = {
        code: "threeds_finalize_failed",
        message: "No 3DS finalize URL configured."
      };
      this.update({ state: "error", error: err });
      return { kind: "failure", message: err.message };
    }
    this.config.onThreeDsRequired?.(interim);
    const machine = new ThreeDsMachine({
      util: this.threeDsUtil,
      finalizeUrl,
      headers: this.config.endpoints?.headers,
      extraBody: options.extraBody
    });
    let current = interim;
    while (true) {
      this.update({
        state: current.state === "method_required" ? "threeds_method" : "threeds_challenge",
        threeDs: current
      });
      const result = await machine.run(current);
      if (result.kind === "escalation") {
        current = result.next;
        continue;
      }
      if (result.kind === "success") {
        this.update({ state: "done" });
        return result;
      }
      const err = { code: "threeds_finalize_failed", message: result.message };
      this.update({ state: "error", error: err });
      this.config.onError?.(err);
      return result;
    }
  }
  /**
   * Restore the form to idle, discarding any captured GPay token. Used by the
   * "Use card details instead" link on the GPay-ready state.
   */
  resetToIdle() {
    this.capturedGpayToken = null;
    this.update({
      state: "idle",
      result: void 0,
      threeDs: void 0,
      error: void 0
    });
  }
  destroy() {
    this.destroyed = true;
    this.registry?.destroy();
    this.registry = null;
    this.validation = null;
    this.threeDsUtil = null;
    this.clover = null;
    this.capturedGpayToken = null;
    this.listeners.clear();
  }
  // ── internals ──
  shouldShowCardholderFields() {
    const setting = this.config.features?.cardholderFields ?? "auto";
    if (setting === "always") return true;
    if (setting === "hidden") return false;
    return this.config.features?.threeDSecure === true;
  }
  buildPaymentRequestData() {
    return {
      paymentReqData: {
        total: {
          label: "Online purchase via Clover",
          currency: this.config.currency,
          amount: this.config.cartTotal
        },
        options: {
          button: { buttonType: "short" }
        }
      }
    };
  }
  wireGpayEvents(el) {
    let methodFiredForCurrentFlow = false;
    el.addEventListener("paymentMethodStart", () => {
      methodFiredForCurrentFlow = false;
      this.capturedGpayToken = null;
      this.update({ state: "gpay_opening" });
    });
    el.addEventListener("paymentMethod", (ev) => {
      methodFiredForCurrentFlow = true;
      const evAny = ev;
      const billing = evAny.customer?.billingInfo;
      const brand = billing?.cardNetwork;
      const last4 = billing?.cardDetails;
      if (!evAny.token || !brand || !last4) {
        const err = {
          code: "tokenization_failed",
          message: "Google Pay token missing required fields."
        };
        this.update({ state: "error", error: err });
        this.config.onError?.(err);
        return;
      }
      const result = {
        source: "google_pay",
        token: evAny.token,
        card: {
          brand,
          last4,
          // GPay doesn't expose expMonth/expYear/first6 in the event payload.
          addressZip: billing?.billingAddress?.postalCode
        },
        browserInfo: this.config.features?.threeDSecure ? getBrowserInfo(this.threeDsUtil) : void 0
      };
      this.capturedGpayToken = result;
      this.update({ state: "gpay_ready", result });
    });
    el.addEventListener("paymentMethodEnd", () => {
      window.setTimeout(() => {
        if (methodFiredForCurrentFlow) {
          return;
        }
        if (this.snapshot.state !== "gpay_opening") {
          return;
        }
        this.update({ state: "gpay_cancelled" });
        window.setTimeout(() => {
          if (this.snapshot.state === "gpay_cancelled") {
            this.update({ state: "idle" });
          }
        }, 100);
      }, GPAY_CANCEL_DEBOUNCE_MS);
    });
  }
  update(partial) {
    this.snapshot = { ...this.snapshot, ...partial };
    for (const listener of this.listeners) {
      listener(this.snapshot);
    }
  }
  emitError(code, message, cause) {
    const error = { code, message, cause };
    this.update({ state: "error", error });
    this.config.onError?.(error);
  }
};
function hasErrors(errors) {
  return Object.values(errors).some((v) => !!v);
}
function firstError(errors) {
  return Object.values(errors).find((v) => !!v);
}

// src/core/index.ts
var __SDK_VERSION__ = "0.1.0";
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  CloverElementType,
  ElementRegistry,
  FIELD_KEY_TO_CLOVER_TYPE,
  PaymentFieldsMachine,
  ThreeDsMachine,
  ValidationMachine,
  __SDK_VERSION__,
  compileTheme,
  createRateLimiter,
  getBrowserInfo,
  loadClover,
  loadClover3DS,
  parseInterim,
  postFinalize,
  tokenize
});
//# sourceMappingURL=index.cjs.map