{"version":3,"file":"analytics-Blvs2DW7.mjs","names":[],"sources":["../src/analytics/mock-adapter.ts","../src/analytics/posthog-adapter.ts","../src/analytics/service.ts","../src/analytics/checkout-timing.ts"],"sourcesContent":["import type { AnalyticsAdapter, CapturedEvent } from \"./types\";\n\n/**\n * Mock analytics adapter for testing.\n *\n * Records all captured events for assertion in tests.\n */\nexport class MockAnalyticsAdapter implements AnalyticsAdapter {\n  private events: CapturedEvent[] = [];\n  private distinctId: string | null = null;\n\n  identify(distinctId: string): void {\n    this.distinctId = distinctId;\n  }\n\n  capture(event: string, properties?: Record<string, unknown>): void {\n    this.events.push({\n      event,\n      distinct_id: this.distinctId,\n      properties,\n      timestamp: Date.now(),\n    });\n  }\n\n  isInitialized(): boolean {\n    return true;\n  }\n\n  // Test helper methods\n\n  /**\n   * Get all captured events.\n   */\n  getEvents(): CapturedEvent[] {\n    return [...this.events];\n  }\n\n  /**\n   * Get events by name.\n   */\n  getEventsByName(eventName: string): CapturedEvent[] {\n    return this.events.filter((e) => e.event === eventName);\n  }\n\n  /**\n   * Clear all captured events.\n   */\n  clear(): void {\n    this.events = [];\n  }\n}\n","import posthog from \"posthog-js\";\nimport type { AnalyticsAdapter } from \"./types\";\n\n// PostHog API key (PaymentKit internal)\nconst POSTHOG_API_KEY = \"phc_u9YDYUeUxo3CYn16XGrrwpVb8ycXgcjz5DarvIKRAAG\";\n\n/**\n * PostHog implementation of the analytics adapter.\n *\n * Singleton stored in window to ensure only one instance exists across\n * all script loads. Configures PostHog to use the reverse proxy at\n * /ingest to avoid ad blockers.\n */\nexport class PostHogAdapter implements AnalyticsAdapter {\n  private initialized = false;\n\n  /**\n   * Get or create the PostHog adapter singleton (stored in window).\n   *\n   * @param apiBaseUrl - The API base URL for the reverse proxy\n   * @throws Error if called with a different apiBaseUrl than the first call\n   */\n  static getInstance(apiBaseUrl: string): PostHogAdapter {\n    if (typeof window === \"undefined\") {\n      throw new Error(\"[PaymentKit Analytics] PostHog requires a browser environment\");\n    }\n\n    const existing = window.__paymentKitPostHog__;\n    if (existing) {\n      if (existing.apiBaseUrl !== apiBaseUrl) {\n        throw new Error(\n          `[PaymentKit Analytics] PostHog already initialized with apiBaseUrl \"${existing.apiBaseUrl}\", ` +\n            `cannot re-initialize with different apiBaseUrl \"${apiBaseUrl}\"`,\n        );\n      }\n      return existing.instance;\n    }\n\n    const instance = new PostHogAdapter(apiBaseUrl);\n    window.__paymentKitPostHog__ = { instance, apiBaseUrl };\n    return instance;\n  }\n\n  private constructor(apiBaseUrl: string) {\n    this.initPostHog(apiBaseUrl);\n  }\n\n  private initPostHog(apiBaseUrl: string): void {\n    try {\n      posthog.init(POSTHOG_API_KEY, {\n        // Use reverse proxy to avoid ad blockers\n        api_host: `${apiBaseUrl}/ingest`,\n        // PostHog UI host (for toolbar, if ever needed)\n        ui_host: \"https://us.posthog.com\",\n        // Memory persistence - no cookies in embedded checkout iframe\n        persistence: \"memory\",\n        // Disable session recording - not needed for metrics\n        disable_session_recording: true,\n        // Manual events only - no autocapture\n        autocapture: false,\n        // Manual pageview control\n        capture_pageview: false,\n        // Disable batching - send events immediately to avoid data loss on page close\n        request_batching: false,\n        // Disable bot detection - allows events from automated tests and debugging environments\n        // PostHog detects Playwright/headless browsers as bots by default\n        opt_out_useragent_filter: true,\n        // Callback when PostHog is ready\n        loaded: () => {\n          this.initialized = true;\n        },\n      });\n    } catch (e) {\n      console.warn(\"[PaymentKit Analytics] Failed to initialize PostHog:\", e);\n    }\n  }\n\n  identify(distinctId: string): void {\n    try {\n      posthog.identify(distinctId);\n    } catch (e) {\n      // Silent fail - never break checkout\n      console.warn(\"[PaymentKit Analytics] Failed to identify:\", e);\n    }\n  }\n\n  capture(event: string, properties?: Record<string, unknown>): void {\n    try {\n      posthog.capture(event, properties);\n    } catch (e) {\n      // Silent fail - never break checkout\n      console.warn(\"[PaymentKit Analytics] Failed to capture event:\", e);\n    }\n  }\n\n  isInitialized(): boolean {\n    return this.initialized;\n  }\n}\n","import { MockAnalyticsAdapter } from \"./mock-adapter\";\nimport { PostHogAdapter } from \"./posthog-adapter\";\nimport type { AnalyticsAdapter } from \"./types\";\n\n/**\n * Analytics service singleton.\n *\n * Provides a global interface for capturing analytics events.\n * Supports multiple adapters that receive all events (composite pattern).\n *\n * Usage:\n *   // Initialize (typically done once at SDK init)\n *   AnalyticsService.init(\"http://localhost:9000\");\n *\n *   // Capture events - broadcasts to all registered adapters\n *   AnalyticsService.capture(\"checkout_page_ready\", { session_id: \"...\" });\n *\n *   // For testing - add a mock adapter\n *   import { MockAnalyticsAdapter } from \"./mock-adapter\";\n *   const mock = new MockAnalyticsAdapter();\n *   AnalyticsService.addAdapter(mock);\n *\n *   // Or use the built-in test mock (exposed globally as window.__paymentKitAnalytics__)\n *   AnalyticsService.enableTestMode();\n *   // Events can be read via window.__paymentKitAnalytics__.getEvents()\n */\nclass AnalyticsServiceClass {\n  private adapters: AnalyticsAdapter[] = [];\n  private testMock: MockAnalyticsAdapter | null = null;\n\n  /**\n   * Initialize the analytics service with PostHog.\n   *\n   * @param apiBaseUrl - The API base URL for the reverse proxy\n   * @param enableTestMode - Enable test mode for capturing events in tests\n   */\n  init(apiBaseUrl: string, enableTestMode?: boolean): void {\n    try {\n      // Add PostHog adapter (singleton - safe to call multiple times with same URL)\n      this.addAdapter(PostHogAdapter.getInstance(apiBaseUrl));\n\n      // Enable test mode if explicitly requested\n      if (enableTestMode) {\n        this.enableTestMode();\n      }\n    } catch (e) {\n      // Silent fail - analytics should never break checkout\n      console.warn(\"[PaymentKit Analytics] Init failed:\", e);\n    }\n  }\n\n  /**\n   * Enable test mode by adding a mock adapter and exposing it globally.\n   * The mock adapter is accessible via window.__paymentKitAnalytics__\n   */\n  enableTestMode(): void {\n    if (this.testMock) return; // Already enabled\n\n    this.testMock = new MockAnalyticsAdapter();\n    this.addAdapter(this.testMock);\n\n    // Expose globally for Playwright/test access\n    if (typeof window !== \"undefined\") {\n      window.__paymentKitAnalytics__ = this.testMock;\n    }\n  }\n\n  /**\n   * Get the test mock adapter (if test mode is enabled).\n   */\n  getTestMock(): MockAnalyticsAdapter | null {\n    return this.testMock;\n  }\n\n  /**\n   * Add an analytics adapter.\n   * Events will be broadcast to all registered adapters.\n   * Duplicate adapters (same instance) are ignored.\n   *\n   * @param adapter - The adapter to add\n   */\n  addAdapter(adapter: AnalyticsAdapter): void {\n    if (!this.adapters.includes(adapter)) {\n      this.adapters.push(adapter);\n    }\n  }\n\n  /**\n   * Clear all adapters.\n   */\n  clearAdapters(): void {\n    this.adapters = [];\n    this.testMock = null;\n    if (typeof window !== \"undefined\") {\n      delete window.__paymentKitAnalytics__;\n    }\n  }\n\n  /**\n   * Set the distinct_id for all adapters.\n   * This is used to correlate frontend events with backend events.\n   * Should be called with the checkout session secure token.\n   */\n  identify(distinctId: string): void {\n    for (const adapter of this.adapters) {\n      try {\n        adapter.identify(distinctId);\n      } catch {\n        // Silent fail - never break checkout\n      }\n    }\n  }\n\n  /**\n   * Capture an analytics event.\n   * Broadcasts to all registered adapters. Silently fails on errors.\n   */\n  capture(event: string, properties?: Record<string, unknown>): void {\n    for (const adapter of this.adapters) {\n      try {\n        adapter.capture(event, properties);\n      } catch {\n        // Silent fail - never break checkout\n      }\n    }\n  }\n\n  /**\n   * Check if any adapter is initialized.\n   */\n  isInitialized(): boolean {\n    return this.adapters.some((adapter) => adapter.isInitialized());\n  }\n}\n\n/**\n * Global analytics service instance.\n */\nexport const AnalyticsService = new AnalyticsServiceClass();\n","import { AnalyticsService } from \"./service\";\n\n/**\n * Tracks timing metrics throughout the checkout flow.\n *\n * Create one instance per checkout session and call the track methods\n * at appropriate points in the checkout flow.\n *\n * Usage:\n *   const requestId = getOrCreateCheckoutRequestId(environment);\n *   const tracker = new CheckoutTimingTracker(secureToken, requestId);\n *   tracker.trackPageReady();\n *   // ... iframe loads ...\n *   tracker.trackInputReady();\n *   // ... user submits ...\n *   tracker.trackSubmit();\n *   // ... API response ...\n *   tracker.trackSuccess(attemptId);\n */\nexport class CheckoutTimingTracker {\n  // Class-level guard to prevent duplicate page_ready events across PaymentKit instances\n  private static pageReadyTracked = false;\n\n  private startTime: number;\n  private checkoutSessionId: string;\n  private requestId: string;\n  private inputReadyTracked = false;\n\n  constructor(checkoutSessionId: string, requestId: string) {\n    this.startTime = performance.now();\n    this.checkoutSessionId = checkoutSessionId;\n    this.requestId = requestId;\n  }\n\n  /**\n   * Track when SDK is initialized and ready.\n   * Call immediately after PaymentKit() initialization.\n   * Only tracks once per page load (subsequent calls are no-ops).\n   */\n  trackPageReady(): void {\n    if (CheckoutTimingTracker.pageReadyTracked) return;\n    CheckoutTimingTracker.pageReadyTracked = true;\n\n    AnalyticsService.capture(\"checkout_page_ready\", {\n      checkout_session_id: this.checkoutSessionId,\n      request_id: this.requestId,\n      elapsed_ms: this.getElapsedMs(),\n    });\n  }\n\n  /**\n   * Track when card input iframe is loaded and ready for input.\n   * Should only be called once (for first iframe, typically card_pan).\n   * Subsequent calls are no-ops.\n   */\n  trackInputReady(): void {\n    if (this.inputReadyTracked) return;\n    this.inputReadyTracked = true;\n\n    AnalyticsService.capture(\"checkout_input_ready\", {\n      checkout_session_id: this.checkoutSessionId,\n      request_id: this.requestId,\n      elapsed_ms: this.getElapsedMs(),\n    });\n  }\n\n  /**\n   * Track when user clicks submit button.\n   */\n  trackSubmit(): void {\n    AnalyticsService.capture(\"checkout_submit\", {\n      checkout_session_id: this.checkoutSessionId,\n      request_id: this.requestId,\n      elapsed_ms: this.getElapsedMs(),\n    });\n  }\n\n  /**\n   * Track successful checkout.\n   *\n   * @param checkoutAttemptId - The checkout attempt ID\n   */\n  trackSuccess(checkoutAttemptId: string): void {\n    AnalyticsService.capture(\"checkout_success\", {\n      checkout_session_id: this.checkoutSessionId,\n      checkout_attempt_id: checkoutAttemptId,\n      request_id: this.requestId,\n      total_elapsed_ms: this.getElapsedMs(),\n    });\n  }\n\n  /**\n   * Track failed checkout.\n   *\n   * @param checkoutAttemptId - The checkout attempt ID (may be null if failed early)\n   * @param errorCode - The error code from the checkout response\n   * @param errorMessage - The customer-facing error message\n   */\n  trackFail(checkoutAttemptId: string | null, errorCode: string | null, errorMessage: string | null): void {\n    AnalyticsService.capture(\"checkout_fail\", {\n      checkout_session_id: this.checkoutSessionId,\n      checkout_attempt_id: checkoutAttemptId,\n      request_id: this.requestId,\n      error_code: errorCode,\n      error_message: errorMessage,\n      total_elapsed_ms: this.getElapsedMs(),\n    });\n  }\n\n  /**\n   * Get elapsed time in milliseconds since tracker creation.\n   */\n  private getElapsedMs(): number {\n    return Math.round(performance.now() - this.startTime);\n  }\n}\n"],"mappings":";;;;;;;;AAOA,IAAa,uBAAb,MAA8D;CAC5D,AAAQ,SAA0B,EAAE;CACpC,AAAQ,aAA4B;CAEpC,SAAS,YAA0B;AACjC,OAAK,aAAa;;CAGpB,QAAQ,OAAe,YAA4C;AACjE,OAAK,OAAO,KAAK;GACf;GACA,aAAa,KAAK;GAClB;GACA,WAAW,KAAK,KAAK;GACtB,CAAC;;CAGJ,gBAAyB;AACvB,SAAO;;;;;CAQT,YAA6B;AAC3B,SAAO,CAAC,GAAG,KAAK,OAAO;;;;;CAMzB,gBAAgB,WAAoC;AAClD,SAAO,KAAK,OAAO,QAAQ,MAAM,EAAE,UAAU,UAAU;;;;;CAMzD,QAAc;AACZ,OAAK,SAAS,EAAE;;;;;;AC5CpB,MAAM,kBAAkB;;;;;;;;AASxB,IAAa,iBAAb,MAAa,eAA2C;CACtD,AAAQ,cAAc;;;;;;;CAQtB,OAAO,YAAY,YAAoC;AACrD,MAAI,OAAO,WAAW,YACpB,OAAM,IAAI,MAAM,gEAAgE;EAGlF,MAAM,WAAW,OAAO;AACxB,MAAI,UAAU;AACZ,OAAI,SAAS,eAAe,WAC1B,OAAM,IAAI,MACR,uEAAuE,SAAS,WAAW,qDACtC,WAAW,GACjE;AAEH,UAAO,SAAS;;EAGlB,MAAM,WAAW,IAAI,eAAe,WAAW;AAC/C,SAAO,wBAAwB;GAAE;GAAU;GAAY;AACvD,SAAO;;CAGT,AAAQ,YAAY,YAAoB;AACtC,OAAK,YAAY,WAAW;;CAG9B,AAAQ,YAAY,YAA0B;AAC5C,MAAI;AACF,WAAQ,KAAK,iBAAiB;IAE5B,UAAU,GAAG,WAAW;IAExB,SAAS;IAET,aAAa;IAEb,2BAA2B;IAE3B,aAAa;IAEb,kBAAkB;IAElB,kBAAkB;IAGlB,0BAA0B;IAE1B,cAAc;AACZ,UAAK,cAAc;;IAEtB,CAAC;WACK,GAAG;AACV,WAAQ,KAAK,wDAAwD,EAAE;;;CAI3E,SAAS,YAA0B;AACjC,MAAI;AACF,WAAQ,SAAS,WAAW;WACrB,GAAG;AAEV,WAAQ,KAAK,8CAA8C,EAAE;;;CAIjE,QAAQ,OAAe,YAA4C;AACjE,MAAI;AACF,WAAQ,QAAQ,OAAO,WAAW;WAC3B,GAAG;AAEV,WAAQ,KAAK,mDAAmD,EAAE;;;CAItE,gBAAyB;AACvB,SAAO,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACtEhB,IAAM,wBAAN,MAA4B;CAC1B,AAAQ,WAA+B,EAAE;CACzC,AAAQ,WAAwC;;;;;;;CAQhD,KAAK,YAAoB,gBAAgC;AACvD,MAAI;AAEF,QAAK,WAAW,eAAe,YAAY,WAAW,CAAC;AAGvD,OAAI,eACF,MAAK,gBAAgB;WAEhB,GAAG;AAEV,WAAQ,KAAK,uCAAuC,EAAE;;;;;;;CAQ1D,iBAAuB;AACrB,MAAI,KAAK,SAAU;AAEnB,OAAK,WAAW,IAAI,sBAAsB;AAC1C,OAAK,WAAW,KAAK,SAAS;AAG9B,MAAI,OAAO,WAAW,YACpB,QAAO,0BAA0B,KAAK;;;;;CAO1C,cAA2C;AACzC,SAAO,KAAK;;;;;;;;;CAUd,WAAW,SAAiC;AAC1C,MAAI,CAAC,KAAK,SAAS,SAAS,QAAQ,CAClC,MAAK,SAAS,KAAK,QAAQ;;;;;CAO/B,gBAAsB;AACpB,OAAK,WAAW,EAAE;AAClB,OAAK,WAAW;AAChB,MAAI,OAAO,WAAW,YACpB,QAAO,OAAO;;;;;;;CASlB,SAAS,YAA0B;AACjC,OAAK,MAAM,WAAW,KAAK,SACzB,KAAI;AACF,WAAQ,SAAS,WAAW;UACtB;;;;;;CAUZ,QAAQ,OAAe,YAA4C;AACjE,OAAK,MAAM,WAAW,KAAK,SACzB,KAAI;AACF,WAAQ,QAAQ,OAAO,WAAW;UAC5B;;;;;CASZ,gBAAyB;AACvB,SAAO,KAAK,SAAS,MAAM,YAAY,QAAQ,eAAe,CAAC;;;;;;AAOnE,MAAa,mBAAmB,IAAI,uBAAuB;;;;;;;;;;;;;;;;;;;;;ACvH3D,IAAa,wBAAb,MAAa,sBAAsB;CAEjC,OAAe,mBAAmB;CAElC,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ,oBAAoB;CAE5B,YAAY,mBAA2B,WAAmB;AACxD,OAAK,YAAY,YAAY,KAAK;AAClC,OAAK,oBAAoB;AACzB,OAAK,YAAY;;;;;;;CAQnB,iBAAuB;AACrB,MAAI,sBAAsB,iBAAkB;AAC5C,wBAAsB,mBAAmB;AAEzC,mBAAiB,QAAQ,uBAAuB;GAC9C,qBAAqB,KAAK;GAC1B,YAAY,KAAK;GACjB,YAAY,KAAK,cAAc;GAChC,CAAC;;;;;;;CAQJ,kBAAwB;AACtB,MAAI,KAAK,kBAAmB;AAC5B,OAAK,oBAAoB;AAEzB,mBAAiB,QAAQ,wBAAwB;GAC/C,qBAAqB,KAAK;GAC1B,YAAY,KAAK;GACjB,YAAY,KAAK,cAAc;GAChC,CAAC;;;;;CAMJ,cAAoB;AAClB,mBAAiB,QAAQ,mBAAmB;GAC1C,qBAAqB,KAAK;GAC1B,YAAY,KAAK;GACjB,YAAY,KAAK,cAAc;GAChC,CAAC;;;;;;;CAQJ,aAAa,mBAAiC;AAC5C,mBAAiB,QAAQ,oBAAoB;GAC3C,qBAAqB,KAAK;GAC1B,qBAAqB;GACrB,YAAY,KAAK;GACjB,kBAAkB,KAAK,cAAc;GACtC,CAAC;;;;;;;;;CAUJ,UAAU,mBAAkC,WAA0B,cAAmC;AACvG,mBAAiB,QAAQ,iBAAiB;GACxC,qBAAqB,KAAK;GAC1B,qBAAqB;GACrB,YAAY,KAAK;GACjB,YAAY;GACZ,eAAe;GACf,kBAAkB,KAAK,cAAc;GACtC,CAAC;;;;;CAMJ,AAAQ,eAAuB;AAC7B,SAAO,KAAK,MAAM,YAAY,KAAK,GAAG,KAAK,UAAU"}