"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/react/index.ts
var react_exports = {};
__export(react_exports, {
  PAYMENT_FIELDS_CSS: () => PAYMENT_FIELDS_CSS,
  PaymentFields: () => PaymentFields,
  usePaymentFields: () => usePaymentFields
});
module.exports = __toCommonJS(react_exports);

// src/react/use-payment-fields.ts
var import_react = require("react");

// 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/react/use-payment-fields.ts
function usePaymentFields(input) {
  const machineRef = (0, import_react.useRef)(null);
  const ownsRef = (0, import_react.useRef)(false);
  if (machineRef.current === null) {
    if (input instanceof PaymentFieldsMachine) {
      machineRef.current = input;
      ownsRef.current = false;
    } else {
      machineRef.current = new PaymentFieldsMachine(input);
      ownsRef.current = true;
    }
  }
  const machine = machineRef.current;
  const subscribe = (0, import_react.useCallback)(
    (onChange) => machine.subscribe(() => onChange()),
    [machine]
  );
  const getSnapshot = (0, import_react.useCallback)(() => machine.getSnapshot(), [machine]);
  const snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
  (0, import_react.useEffect)(() => {
    return () => {
      if (ownsRef.current) {
        machine.destroy();
      }
      machineRef.current = null;
    };
  }, []);
  const mount = (0, import_react.useCallback)((targets) => machine.mount(targets), [machine]);
  const tokenize2 = (0, import_react.useCallback)(() => machine.tokenize(), [machine]);
  const runThreeDs = (0, import_react.useCallback)(
    (interim, options) => machine.runThreeDs(interim, options),
    [machine]
  );
  const resetToIdle = (0, import_react.useCallback)(() => machine.resetToIdle(), [machine]);
  return (0, import_react.useMemo)(
    () => ({
      state: snapshot.state,
      validation: snapshot.validation,
      result: snapshot.result,
      error: snapshot.error,
      threeDs: snapshot.threeDs,
      snapshot,
      mount,
      tokenize: tokenize2,
      runThreeDs,
      resetToIdle,
      machine
    }),
    [snapshot, mount, tokenize2, runThreeDs, resetToIdle, machine]
  );
}

// src/react/PaymentFields.tsx
var import_react2 = require("react");

// src/react/styles.ts
var PAYMENT_FIELDS_CSS = `
.wcp-pf {
  --wcp-green: #59B210;
  --wcp-clover-green: #228800;
  --wcp-warm: #c4621a;
  --wcp-error: #d64545;
  --wcp-focus: #4a7c6a;
  --wcp-border: #d6d8db;
  --wcp-border-soft: #e5e7eb;
  --wcp-bg: #ffffff;
  --wcp-text: #1a1d21;
  --wcp-text-muted: #6b7280;
  --wcp-text-soft: #8a9199;
  font-family: 'DM Sans', system-ui, -apple-system, sans-serif;
  color: var(--wcp-text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  max-width: 480px;
  margin: 0 auto;
}

.wcp-pf, .wcp-pf * { box-sizing: border-box; }

/* \u2500\u2500 Status strip (top of widget when state !== idle) \u2500\u2500 */
.wcp-pf__strip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  margin-bottom: 16px;
  animation: wcp-pf-strip-in 240ms cubic-bezier(.2,.7,.2,1);
}
.wcp-pf__strip svg { flex: none; }
.wcp-pf__strip--info {
  background: #eef4f1;
  color: var(--wcp-focus);
  border: 1px solid rgba(74,124,106,0.18);
}
.wcp-pf__strip--warning {
  background: #fdf5ed;
  color: var(--wcp-warm);
  border: 1px solid rgba(196,98,26,0.22);
}
.wcp-pf__strip--success {
  background: #f1f8e8;
  color: var(--wcp-clover-green);
  border: 1px solid rgba(34,136,0,0.22);
}
.wcp-pf__strip--error {
  background: #fbeded;
  color: var(--wcp-error);
  border: 1px solid rgba(214,69,69,0.22);
}
.wcp-pf__spinner {
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: wcp-pf-spin 700ms linear infinite;
  flex: none;
  opacity: 0.7;
}

/* \u2500\u2500 Form stack \u2500\u2500
   - Idle: visible.
   - In-progress (tokenizing/submitting/3DS): dimmed so user still sees what they entered.
   - GPay ready: collapsed (max-height + opacity + margin \u2192 0) so the GPay-ready
     card stands alone. 540px is a safe ceiling \u2014 accommodates cardholder name +
     email + the 4 card fields + per-field error text.
*/
.wcp-pf__form {
  max-height: 540px;
  overflow: hidden;
  transition:
    max-height 320ms cubic-bezier(.4,0,.2,1),
    opacity 220ms ease,
    margin 280ms ease,
    filter 240ms ease;
}
.wcp-pf__form--dimmed {
  opacity: 0.42;
  filter: saturate(0.7);
  pointer-events: none;
}
.wcp-pf__form--collapsed {
  max-height: 0;
  opacity: 0;
  margin-top: 0;
  margin-bottom: 0;
  pointer-events: none;
}

/* \u2500\u2500 Field rows \u2500\u2500 */
.wcp-pf__row { margin-bottom: 10px; }
.wcp-pf__row:last-child { margin-bottom: 0; }
.wcp-pf__grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 10px;
  align-items: start;
}
.wcp-pf__grid-3 {
  display: grid;
  grid-template-columns: 1.45fr 1fr 1.1fr;
  gap: 10px;
  align-items: start;
}
/* Vertical wrapper: field (46px) + optional error text underneath. */
.wcp-pf__field-slot {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* The host div for Clover's iframe.
 *
 * Layout strategy: the wrapper has a fixed visual height (46px) for border
 * + focus ring + theme resistance, BUT it uses flex centering so the iframe
 * is positioned by Clover's own auto-resize, not stretched to fill the wrapper.
 *
 * Clover's SDK does this internally (see sdk.js updateFrameStyles): the
 * iframe document measures its rendered content and posts {style, width,
 * height} back to the parent, which Clover then applies to the iframe element
 * inline. If we force height:100% with !important on the iframe, we override
 * Clover's measurement and force the viewport to 46px even when the iframe
 * document needs slightly more (e.g., sub-pixel rounding, focus ring inside
 * the iframe, masked-input wrappers Clover adds around the input element).
 * That is what produces the per-field scrollbars.
 *
 * Instead: let Clover size the iframe to its natural content height (the
 * input is height:46px via theme.ts), center it within our 46px wrapper
 * via flex, and clip any sub-pixel overshoot with overflow:hidden.
 */
.wcp-pf__field {
  width: 100%;
  height: 46px;
  padding: 0;
  background: #fff;
  border: 1.5px solid var(--wcp-border);
  border-radius: 8px;
  overflow: hidden;
  transition: border-color 160ms ease, box-shadow 160ms ease;
  cursor: text;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: stretch;
}
.wcp-pf__field:hover { border-color: #b9bcc1; }
/* :focus-within bubbles focus across iframe boundaries in modern browsers,
   which is how we get a focus ring on the wrapper when the user clicks into
   the Clover-rendered input inside the cross-origin iframe. */
.wcp-pf__field:focus-within,
.wcp-pf__field--focused {
  border-color: var(--wcp-focus);
  box-shadow: 0 0 0 3px rgba(74,124,106,0.08);
}
.wcp-pf__field--error {
  border-color: var(--wcp-error);
  box-shadow: 0 0 0 3px rgba(214,69,69,0.08);
  animation: wcp-pf-shake 360ms cubic-bezier(.36,.07,.19,.97);
}
/* Touched + no error \u2192 positive visual confirmation. The error state's
   counterpart so the user can see "yes, this is accepted" without needing
   to read the form. Border + ring use Clover's accent green. */
.wcp-pf__field--valid {
  border-color: var(--wcp-clover-green);
  box-shadow: 0 0 0 3px rgba(34,136,0,0.08);
}
.wcp-pf__field-check {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--wcp-clover-green);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  animation: wcp-pf-pop 240ms cubic-bezier(.2,.7,.2,1);
}
.wcp-pf__field-check svg { display: block; }
/* No !important on height \u2014 let Clover's updateFrameStyles set the iframe
   height to the measured content size. Width is !important because Clover
   sets width:100% inline at creation and we don't want themes overriding.
   scrolling="no" is set programmatically in element-registry.ts after
   mount \u2014 this is the only fully-reliable way to suppress the cross-origin
   iframe overlay scrollbar (CSS overflow on iframes is browser-spotty). */
.wcp-pf__field iframe {
  width: 100% !important;
  border: 0 !important;
  display: block !important;
  background: transparent !important;
  flex: 0 0 auto;
  overflow: hidden;
}
.wcp-pf__error-text {
  font-size: 12px;
  color: var(--wcp-error);
  margin-top: 6px;
  display: flex;
  align-items: center;
  gap: 5px;
}
.wcp-pf__error-text svg { flex: none; }

/* \u2500\u2500 GPay block (Clover iframe lives in .wcp-pf__gpay-mount) \u2500\u2500
 * The "Or pay instantly" label provides its own horizontal rule via the
 * ::before / ::after pseudo-elements below. The block itself must NOT add
 * a second border-top \u2014 two stacked rules read as an extra empty line.
 */
.wcp-pf__gpay-block {
  margin-top: 18px;
}
.wcp-pf__gpay-label {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--wcp-text-soft);
  font-size: 12px;
  margin-bottom: 12px;
}
.wcp-pf__gpay-label::before, .wcp-pf__gpay-label::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--wcp-border-soft);
}
/* The host div for Clover's PAYMENT_REQUEST_BUTTON iframe. Constrain it the
   same way the legacy PaymentFields.php template did (inline style at line 167:
   width:100%; max-width:200px; margin:0 auto; height:50px). Without this,
   the iframe expands to its container's full width. */
.wcp-pf__gpay-mount {
  width: 100%;
  max-width: 240px;
  height: 48px;
  margin: 0 auto;
  overflow: hidden;
}
.wcp-pf__gpay-mount iframe {
  width: 100% !important;
  height: 100% !important;
  min-height: 40px !important;
  min-width: 90px !important;
  border: 0 !important;
  display: block !important;
}

/* \u2500\u2500 GPay-ready card (Variation 1: refined receipt-card) \u2500\u2500
   Single unified card; amount is the dominant element. Replaces the previous
   "ready strip + details card + greyed fields" triplet. Card fields collapse
   out via .wcp-pf__form--collapsed so this card stands alone.
*/
.wcp-pf__gpay-card {
  background: #ffffff;
  border: 1px solid var(--wcp-border-soft);
  border-radius: 12px;
  padding: 22px 22px 18px;
  margin-bottom: 10px;
  box-shadow: 0 1px 2px rgba(20, 20, 40, 0.04);
  animation: wcp-pf-rise 360ms cubic-bezier(.2,.7,.2,1);
}
.wcp-pf__gpay-card-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 18px;
}
.wcp-pf__gpay-card-check {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--wcp-clover-green);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  box-shadow: 0 0 0 4px #ecfdf5;
}
.wcp-pf__gpay-card-check svg { width: 14px; height: 14px; }
.wcp-pf__gpay-card-head-text { flex: 1; min-width: 0; }
.wcp-pf__gpay-card-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--wcp-text);
  line-height: 1.3;
}
.wcp-pf__gpay-card-sub {
  font-size: 12.5px;
  color: var(--wcp-text-muted);
  margin-top: 2px;
  line-height: 1.4;
}
.wcp-pf__gpay-card-detail {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding-bottom: 14px;
  border-bottom: 1px dashed var(--wcp-border-soft);
  margin-bottom: 14px;
}
.wcp-pf__gpay-card-amount {
  font-size: 32px;
  font-weight: 600;
  color: var(--wcp-text);
  letter-spacing: -0.02em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.wcp-pf__gpay-card-info {
  font-size: 11px;
  color: var(--wcp-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: right;
  line-height: 1.5;
}
.wcp-pf__gpay-card-info-row { display: block; }
.wcp-pf__gpay-card-info b {
  font-weight: 700;
  color: var(--wcp-text);
}
.wcp-pf__gpay-card-cta {
  font-size: 13px;
  color: var(--wcp-text-muted);
  margin: 0;
  line-height: 1.5;
}
.wcp-pf__gpay-card-cta b {
  color: var(--wcp-text);
  font-weight: 600;
}
.wcp-pf__gpay-card-switch {
  display: inline-block;
  margin-top: 8px;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--wcp-text-muted);
  background: transparent;
  border: none;
  text-decoration: underline;
  text-decoration-color: var(--wcp-text-soft);
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 140ms ease;
}
.wcp-pf__gpay-card-switch:hover { color: var(--wcp-text); }
.wcp-pf__gpay-card-switch:disabled {
  cursor: default;
  opacity: 0.55;
}

/* \u2500\u2500 Trust footer (mirrors WP-classic CSS at lines 140-167) \u2500\u2500 */
.wcp-pf__trust {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px solid var(--wcp-border-soft);
  display: flex;
  align-items: center;
  justify-content: center;
}
.wcp-pf__trust-lock {
  padding-right: 10px;
  flex: none;
  display: flex;
}
.wcp-pf__trust-lock img { display: block; width: 20px; height: 20px; }
.wcp-pf__trust-display {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: space-around;
  min-width: 180px;
  gap: 4px 0;
}
.wcp-pf__trust-text {
  white-space: nowrap;
  padding-right: 5px;
  font-size: 12.5px;
  color: #4a4a4a;
  font-weight: 500;
}
.wcp-pf__trust-logos {
  vertical-align: middle;
  margin: 0;
  display: block;
  height: auto;
  max-width: 100%;
}

/* \u2500\u2500 Animations \u2500\u2500 */
@keyframes wcp-pf-spin { to { transform: rotate(360deg); } }
@keyframes wcp-pf-strip-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}
@keyframes wcp-pf-rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
@keyframes wcp-pf-shake {
  10%, 90% { transform: translate3d(-1px, 0, 0); }
  20%, 80% { transform: translate3d(2px, 0, 0); }
  30%, 50%, 70% { transform: translate3d(-3px, 0, 0); }
  40%, 60% { transform: translate3d(3px, 0, 0); }
}
@keyframes wcp-pf-pop {
  from { opacity: 0; transform: translateY(-50%) scale(0.6); }
  to   { opacity: 1; transform: translateY(-50%) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .wcp-pf__strip, .wcp-pf__gpay-ready, .wcp-pf__field { animation: none; }
  .wcp-pf__spinner { animation-duration: 1.4s; }
}
`;

// src/react/PaymentFields.tsx
var import_jsx_runtime = require("react/jsx-runtime");
function PaymentFields(props) {
  const input = props.machine ?? props.config;
  if (!input) {
    throw new Error("PaymentFields requires either a `config` or a `machine` prop.");
  }
  const api = usePaymentFields(input);
  useCloverFooterDecoy();
  const reactId = (0, import_react2.useId)();
  const idPrefix = (0, import_react2.useMemo)(() => `wcp-pf-${reactId.replace(/[^a-zA-Z0-9_-]/g, "")}`, [reactId]);
  const targets = (0, import_react2.useMemo)(
    () => ({
      cardNumber: `#${idPrefix}-card-number`,
      cardDate: `#${idPrefix}-card-date`,
      cardCvv: `#${idPrefix}-card-cvv`,
      cardPostalCode: `#${idPrefix}-card-postal-code`,
      cardName: `#${idPrefix}-card-name`,
      cardEmail: `#${idPrefix}-card-email`,
      paymentRequestButton: `#${idPrefix}-payment-request-button`
    }),
    [idPrefix]
  );
  const mountStarted = (0, import_react2.useRef)(false);
  (0, import_react2.useEffect)(() => {
    if (mountStarted.current) {
      return;
    }
    mountStarted.current = true;
    api.mount(targets).catch((err) => {
      console.warn("[wcp-payment-fields] mount failed", err);
    });
  }, [api, targets]);
  if (props.children) {
    return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: props.children(api) });
  }
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
    DefaultLayout,
    {
      api,
      idPrefix,
      lockIconSrc: props.lockIconSrc,
      trustLogosSrc: props.trustLogosSrc,
      trustText: props.trustText ?? "Payment secured by",
      googlePayEnabled: featureFlag(props, "googlePay"),
      cardholderVisible: cardholderVisibility(props)
    }
  );
}
function useCloverFooterDecoy() {
  (0, import_react2.useEffect)(() => {
    if (typeof document === "undefined" || document.querySelector(".clover-footer")) {
      return;
    }
    const decoy = document.createElement("div");
    decoy.className = "clover-footer";
    document.body.appendChild(decoy);
    return () => {
      decoy.remove();
    };
  }, []);
}
function DefaultLayout(props) {
  const { api, idPrefix, googlePayEnabled, cardholderVisible } = props;
  const isGpayReady = api.state === "gpay_ready";
  const isFormCollapsed = isGpayReady;
  const isFormDimmed = !isGpayReady && isInProgress(api.state);
  const [isPending, startTransition] = (0, import_react2.useTransition)();
  const onUseCard = () => {
    startTransition(() => api.resetToIdle());
  };
  const machineConfig = api.machine.config;
  const formattedAmount = formatAmount(
    machineConfig.cartTotal,
    machineConfig.currency,
    machineConfig.locale
  );
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf", children: [
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: PAYMENT_FIELDS_CSS }),
    !isGpayReady && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusStrip, { state: api.state, error: api.error }, api.state),
    isGpayReady && api.result && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
      GpayReadyCard,
      {
        brand: api.result.card.brand,
        last4: api.result.card.last4,
        amount: formattedAmount,
        onUseCard,
        isPending
      }
    ),
    /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `wcp-pf__form${isFormCollapsed ? " wcp-pf__form--collapsed" : ""}${isFormDimmed ? " wcp-pf__form--dimmed" : ""}`, children: [
      cardholderVisible && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__grid-2", children: [
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldSlot, { api, fieldKey: "cardName", mountId: `${idPrefix}-card-name` }),
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldSlot, { api, fieldKey: "cardEmail", mountId: `${idPrefix}-card-email` })
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wcp-pf__row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldSlot, { api, fieldKey: "cardNumber", mountId: `${idPrefix}-card-number` }) }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__grid-3", children: [
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldSlot, { api, fieldKey: "cardDate", mountId: `${idPrefix}-card-date` }),
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldSlot, { api, fieldKey: "cardCvv", mountId: `${idPrefix}-card-cvv` }),
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldSlot, { api, fieldKey: "cardPostalCode", mountId: `${idPrefix}-card-postal-code` })
      ] })
    ] }),
    googlePayEnabled && !isGpayReady && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__gpay-block", children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wcp-pf__gpay-label", children: "Or pay instantly" }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wcp-pf__gpay-mount", id: `${idPrefix}-payment-request-button` })
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
      TrustFooter,
      {
        lockIconSrc: props.lockIconSrc,
        trustLogosSrc: props.trustLogosSrc,
        trustText: props.trustText
      }
    )
  ] });
}
function StatusStrip({ state, error }) {
  const message = stateMessage(state, error);
  if (!message) {
    return null;
  }
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
    "div",
    {
      className: `wcp-pf__strip wcp-pf__strip--${message.tone}`,
      role: message.tone === "error" ? "alert" : "status",
      "aria-live": message.tone === "error" ? void 0 : "polite",
      children: [
        message.icon,
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: message.text })
      ]
    }
  );
}
function GpayReadyCard(props) {
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__gpay-card", role: "status", children: [
    /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__gpay-card-head", children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "wcp-pf__gpay-card-check", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkmark, {}) }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__gpay-card-head-text", children: [
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wcp-pf__gpay-card-title", children: "Google Pay is ready." }),
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wcp-pf__gpay-card-sub", children: "Your payment is queued and runs when you submit." })
      ] })
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__gpay-card-detail", children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wcp-pf__gpay-card-amount", children: props.amount }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__gpay-card-info", children: [
        /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "wcp-pf__gpay-card-info-row", children: [
          "via ",
          /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Google Pay" })
        ] }),
        /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "wcp-pf__gpay-card-info-row", children: [
          props.brand,
          " \u2022\u2022\u2022\u2022 ",
          /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: props.last4 })
        ] })
      ] })
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "wcp-pf__gpay-card-cta", children: [
      "Click ",
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "Place Order" }),
      " below to confirm."
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
      "button",
      {
        type: "button",
        className: "wcp-pf__gpay-card-switch",
        onClick: props.onUseCard,
        disabled: props.isPending,
        children: "Pay with card instead"
      }
    )
  ] });
}
function formatAmount(cents, currency, locale) {
  try {
    return new Intl.NumberFormat(locale, {
      style: "currency",
      currency
    }).format(cents / 100);
  } catch {
    return `${(cents / 100).toFixed(2)} ${currency}`;
  }
}
function TrustFooter(props) {
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__trust", children: [
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wcp-pf__trust-lock", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: props.lockIconSrc, alt: "", width: 20, height: 20 }) }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__trust-display", children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "wcp-pf__trust-text", children: props.trustText }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
        "img",
        {
          className: "wcp-pf__trust-logos",
          src: props.trustLogosSrc,
          alt: "Secured by Clover & WeeConnectPay",
          width: 180,
          height: 40
        }
      )
    ] })
  ] });
}
function cardholderVisibility(props) {
  const features = props.config?.features ?? props.machine?.config?.features;
  const setting = features?.cardholderFields ?? "auto";
  if (setting === "always") return true;
  if (setting === "hidden") return false;
  return features?.threeDSecure === true;
}
function featureFlag(props, flag) {
  const features = props.config?.features ?? props.machine?.config?.features;
  return features?.[flag] === true;
}
function FieldSlot(props) {
  const field = props.api.validation.fields[props.fieldKey];
  const displayedError = field?.displayedError;
  const requiresChangeForValid = props.fieldKey === "cardName" || props.fieldKey === "cardEmail";
  const valid = !!field?.touched && !field?.error && (!requiresChangeForValid || !!field?.hadChangeEver);
  const fieldClass = displayedError ? "wcp-pf__field wcp-pf__field--error" : valid ? "wcp-pf__field wcp-pf__field--valid" : "wcp-pf__field";
  const showCheck = valid && props.fieldKey !== "cardNumber";
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__field-slot", children: [
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: fieldClass, id: props.mountId, children: showCheck && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "wcp-pf__field-check", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkmark, {}) }) }),
    displayedError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wcp-pf__error-text", role: "alert", children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorIcon, {}),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: displayedError })
    ] })
  ] });
}
function isInProgress(state) {
  return state === "gpay_opening" || state === "tokenizing" || state === "submitting" || state === "threeds_method" || state === "threeds_challenge" || state === "finalizing";
}
function stateMessage(state, error) {
  switch (state) {
    case "idle":
    case "gpay_cancelled":
      return null;
    case "gpay_opening":
      return { text: "Opening Google Pay\u2026", tone: "info", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) };
    case "gpay_ready":
      return {
        text: "Google Pay ready \u2014 place your order to confirm.",
        tone: "success",
        icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkmark, {})
      };
    case "tokenizing":
      return { text: "Securing card details\u2026", tone: "info", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) };
    case "submitting":
      return { text: "Sending payment\u2026", tone: "info", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) };
    case "threeds_method":
      return { text: "Preparing bank verification\u2026", tone: "info", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) };
    case "threeds_challenge":
      return { text: "Complete verification with your bank \u2192", tone: "warning", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shield, {}) };
    case "finalizing":
      return { text: "Confirming payment\u2026", tone: "info", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) };
    case "done":
      return { text: "Payment confirmed.", tone: "success", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkmark, {}) };
    case "error":
      return {
        text: error?.message ?? "Something went wrong. Please try again.",
        tone: "error",
        icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorIcon, {})
      };
  }
}
function Spinner() {
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "wcp-pf__spinner", "aria-hidden": "true" });
}
function Checkmark() {
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
}
function Shield() {
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) });
}
function ErrorIcon() {
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
  ] });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  PAYMENT_FIELDS_CSS,
  PaymentFields,
  usePaymentFields
});
//# sourceMappingURL=index.cjs.map