@payment-kit-js/vanilla 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { o as getOrCreateCheckoutRequestId, r as createCheckoutIFrame } from "./
4
4
  import posthog from "posthog-js";
5
5
 
6
6
  //#region package.json
7
- var version = "0.5.2";
7
+ var version = "0.5.4";
8
8
 
9
9
  //#endregion
10
10
  //#region src/analytics/mock-adapter.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["STATIC_ENVIRONMENT_URLS: Record<Exclude<PaymentKitEnvironment, \"tunnel\">, EnvironmentUrls>","PaymentKit: PaymentKitType","PACKAGE_VERSION","externalFuncsMapByPm: ExternalFuncsMapByPm<PaymentMethods>","submit: PaymentKitReturnType[\"submit\"]"],"sources":["../package.json","../src/analytics/mock-adapter.ts","../src/analytics/posthog-adapter.ts","../src/analytics/service.ts","../src/analytics/checkout-timing.ts","../src/types.ts","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@payment-kit-js/vanilla\",\n \"version\": \"0.5.2\",\n \"main\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.mts\",\n \"module\": \"./dist/index.mjs\",\n \"exports\": {\n \".\": \"./dist/index.mjs\",\n \"./payment-methods/airwallex-apple-pay-adapter\": \"./dist/payment-methods/airwallex-apple-pay-adapter.mjs\",\n \"./payment-methods/airwallex-google-pay-adapter\": \"./dist/payment-methods/airwallex-google-pay-adapter.mjs\",\n \"./payment-methods/apple-pay\": \"./dist/payment-methods/apple-pay.mjs\",\n \"./payment-methods/card\": \"./dist/payment-methods/card.mjs\",\n \"./payment-methods/google-pay\": \"./dist/payment-methods/google-pay.mjs\",\n \"./payment-methods/next-action-handlers\": \"./dist/payment-methods/next-action-handlers.mjs\",\n \"./payment-methods/paypal\": \"./dist/payment-methods/paypal.mjs\",\n \"./payment-methods/stripe-apple-pay-adapter\": \"./dist/payment-methods/stripe-apple-pay-adapter.mjs\",\n \"./payment-methods/stripe-google-pay-adapter\": \"./dist/payment-methods/stripe-google-pay-adapter.mjs\",\n \"./payment-methods/vgs-collect-loader\": \"./dist/payment-methods/vgs-collect-loader.mjs\",\n \"./penpal/connect-card\": \"./dist/penpal/connect-card.mjs\",\n \"./penpal/connect-tunnel-x\": \"./dist/penpal/connect-tunnel-x.mjs\",\n \"./package.json\": \"./package.json\"\n },\n \"license\": \"MIT\",\n \"description\": \"Vanilla package for PaymentKit\",\n \"files\": [\n \"dist\",\n \"dist/cdn\"\n ],\n \"scripts\": {\n \"dev\": \"yarn tsdown --watch ./src\",\n \"build:npm\": \"yarn tsdown\",\n \"build:cdn\": \"node build-cdn.mjs\",\n \"build\": \"yarn build:npm && yarn build:cdn\"\n },\n \"packageManager\": \"yarn@4.10.3\",\n \"dependencies\": {\n \"nanoid\": \"^5.0.7\",\n \"penpal\": \"^7.0.4\",\n \"posthog-js\": \"^1.196.0\",\n \"valibot\": \"^1.1.0\"\n },\n \"devDependencies\": {\n \"@pkg/sdk\": \"workspace:*\",\n \"@pkg/tsconfig\": \"workspace:*\",\n \"@stripe/stripe-js\": \"^8.6.1\",\n \"@types/applepayjs\": \"^14.0.9\",\n \"esbuild\": \"^0.24.2\",\n \"tsdown\": \"^0.15.10\",\n \"typescript\": \"^5.9.3\"\n },\n \"stableVersion\": \"0.5.2\"\n}\n","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","import type { CheckoutTimingTracker } from \"./analytics\";\nimport type { CardErrorCode } from \"./penpal/connect-card\";\nimport type { TunnelXIFrameConnection } from \"./penpal/connect-tunnel-x\";\n\nexport type PaymentKitEnvironment = \"local\" | \"tunnel\" | \"sandbox\" | \"production\";\n\ntype EnvironmentUrls = {\n baseUrl: string;\n apiBaseUrl: string;\n};\n\n// TODO: Cleanup this after launch to only use SDK instead of plain fetch.\nconst STATIC_ENVIRONMENT_URLS: Record<Exclude<PaymentKitEnvironment, \"tunnel\">, EnvironmentUrls> = {\n local: {\n baseUrl: \"http://localhost:9101\",\n apiBaseUrl: \"http://localhost:9000\",\n },\n sandbox: {\n baseUrl: \"https://staging.paymentkit.com/customer\",\n apiBaseUrl: \"https://staging.paymentkit.com\",\n },\n production: {\n baseUrl: \"https://paymentkit.com/customer\",\n apiBaseUrl: \"https://paymentkit.com\",\n },\n};\n\nfunction getTunnelUrls(): EnvironmentUrls {\n if (typeof window !== \"undefined\") {\n const params = new URLSearchParams(window.location.search);\n const apiBaseUrl = params.get(\"api_base_url\");\n if (apiBaseUrl) {\n return {\n baseUrl: window.location.origin,\n apiBaseUrl,\n };\n }\n // For loclx-style tunnels where API hostname is derived from the current hostname\n const hostname = window.location.hostname;\n if (hostname.includes(\"loclx.io\")) {\n const apiHostname = hostname.replace(/cportal|playground/, \"api\");\n return {\n baseUrl: `https://${hostname}`,\n apiBaseUrl: `https://${apiHostname}`,\n };\n }\n }\n // Fallback\n return STATIC_ENVIRONMENT_URLS.local;\n}\n\nexport function getUrlsForEnvironment(environment: string): EnvironmentUrls {\n if (environment === \"tunnel\") {\n return getTunnelUrls();\n }\n const env = environment as Exclude<PaymentKitEnvironment, \"tunnel\">;\n const urls = STATIC_ENVIRONMENT_URLS[env];\n if (!urls) {\n throw new Error(`Invalid environment: ${environment}. Must be one of: local, tunnel, sandbox, production`);\n }\n return urls;\n}\n\ntype FormFieldNames = \"customer_name\" | \"customer_email\" | \"customer_country\" | \"customer_zip_code\";\n\nexport type FormErrorCodes = \"required\" | \"invalid\";\n\nexport type TInternalFuncs = {\n submitPayment: (\n fields: PaymentKitFields,\n options?: unknown,\n ) => Promise<{ data: { [key: string]: unknown }; errors?: never } | { data?: never; errors: PaymentKitErrors }>;\n cleanup?: () => void;\n};\n\nexport type PaymentKit = <T extends readonly PaymentMethod<unknown>[]>(options: {\n environment: string;\n secureToken: string;\n paymentMethods: T;\n /** Card tokenization mode from checkout session */\n cardTokenizationMode?: \"direct\" | \"vgs\";\n /** VGS vault ID from checkout session */\n vgsVaultId?: string;\n /** VGS environment from checkout session */\n vgsEnvironment?: string;\n /** @internal Enable analytics test mode for e2e testing */\n __enableAnalyticsTestMode?: boolean;\n}) => ExternalFuncsMapByPm<T> & {\n submit: PaymentKitSubmitHandler<T>;\n cleanup: () => void;\n};\n\ntype PaymentKitSubmitHandler<T extends readonly PaymentMethod<unknown>[]> = <\n N extends keyof ExternalFuncsMapByPm<T>,\n>(options: {\n fields: Partial<PaymentKitFields>;\n paymentMethod: N;\n options?: unknown;\n onError: (error: PaymentKitErrors) => void;\n onSuccess: (data: { [key: string]: unknown }) => void;\n}) => void;\n\nexport type PaymentKitStates = {\n baseUrl: string;\n apiBaseUrl: string;\n secureToken: string;\n environment: string;\n tunnelXConnection: TunnelXIFrameConnection;\n timingTracker: CheckoutTimingTracker;\n /** Checkout request ID for correlating all API calls and analytics events */\n checkoutRequestId: string;\n /** Card tokenization mode: 'direct' (KMS) or 'vgs' (VGS Collect + proxy) */\n cardTokenizationMode?: \"direct\" | \"vgs\";\n /** VGS vault ID (only when cardTokenizationMode is 'vgs') */\n vgsVaultId?: string;\n /** VGS environment: 'sandbox' or 'live' (only when cardTokenizationMode is 'vgs') */\n vgsEnvironment?: string;\n /** @internal Promise that resolves when session config (incl. VGS mode) is auto-detected */\n _sessionConfigReady?: Promise<void>;\n};\n\nexport type PaymentKitErrors = {\n root?: string;\n card_pan?: CardErrorCode;\n card_exp?: CardErrorCode;\n card_cvc?: CardErrorCode;\n paypal?: string;\n google_pay?: string;\n apple_pay?: string;\n processor_id?: string;\n amount?: string;\n currency?: string;\n country?: string;\n} & { [key in FormFieldNames]?: FormErrorCodes | string };\n\nexport type PaymentKitFields = { [key in FormFieldNames]: string };\n\nexport type PaymentMethod<TExternalFuncs = unknown, TName = string> = (paymentKitStates: PaymentKitStates) => {\n name: TName;\n externalFuncs: TExternalFuncs;\n internalFuncs: TInternalFuncs;\n};\n\nexport type ExternalFuncsMapByPm<T extends readonly PaymentMethod<unknown>[]> = {\n [K in T[number] as ReturnType<K>[\"name\"]]: ReturnType<K>[\"externalFuncs\"];\n};\n","import { version as PACKAGE_VERSION } from \"../package.json\";\nimport { AnalyticsService, CheckoutTimingTracker } from \"./analytics\";\nimport { connectToTunnelXIframe } from \"./penpal/connect-tunnel-x\";\nimport type { ExternalFuncsMapByPm, PaymentKitFields, PaymentKit as PaymentKitType } from \"./types\";\nimport { getUrlsForEnvironment } from \"./types\";\nimport { createCheckoutIFrame } from \"./utils\";\nimport { getOrCreateCheckoutRequestId } from \"./utils/checkout-request-id\";\n\ntype PaymentKitReturnType = ReturnType<PaymentKitType>;\n\nconst createTunnelXConnection = (baseUrl: string, apiBaseUrl: string, token: string) => {\n const iframe = createCheckoutIFrame(\"tunnel-x\", baseUrl, {\n checkout_token: token,\n api_base_url: apiBaseUrl,\n });\n document.body.appendChild(iframe);\n\n const connection = connectToTunnelXIframe(iframe, {});\n\n const unmount = () => {\n connection.destroy();\n document.body.removeChild(iframe);\n };\n\n return { unmount, connection };\n};\n\nconst PaymentKit: PaymentKitType = ({\n environment,\n secureToken,\n paymentMethods,\n cardTokenizationMode,\n vgsVaultId,\n vgsEnvironment,\n __enableAnalyticsTestMode,\n}) => {\n type PaymentMethods = typeof paymentMethods;\n\n // Resolve URLs from environment\n const { baseUrl, apiBaseUrl } = getUrlsForEnvironment(environment);\n\n // Log version for debugging\n console.log(`[PaymentKit] v${PACKAGE_VERSION} initialized (env: ${environment})`);\n\n // Generate checkout request ID for correlating all API calls and analytics events\n // This is idempotent - same ID is reused across multiple PaymentKit instantiations\n const checkoutRequestId = getOrCreateCheckoutRequestId(environment);\n console.log(`[PaymentKit] checkout_request_id: ${checkoutRequestId}`);\n\n // Initialize analytics and identify with secure token for correlation\n AnalyticsService.init(apiBaseUrl, __enableAnalyticsTestMode);\n AnalyticsService.identify(secureToken);\n\n // Create timing tracker for this checkout session\n const timingTracker = new CheckoutTimingTracker(secureToken, checkoutRequestId);\n\n // Track page ready immediately\n timingTracker.trackPageReady();\n\n const { connection: tunnelXConnection, unmount: unmountTunnelX } = createTunnelXConnection(\n baseUrl,\n apiBaseUrl,\n secureToken,\n );\n\n const paymentKitStates = {\n baseUrl,\n apiBaseUrl,\n secureToken,\n environment,\n tunnelXConnection,\n timingTracker,\n checkoutRequestId,\n cardTokenizationMode,\n vgsVaultId,\n vgsEnvironment,\n _sessionConfigReady: undefined as Promise<void> | undefined,\n };\n\n // Auto-detect VGS mode from session API if not explicitly provided.\n // The promise resolves before any card mount or submit needs the mode.\n if (cardTokenizationMode === undefined) {\n const sessionConfigUrl = `${apiBaseUrl}/api/checkout-sessions/token/${secureToken}`;\n console.log(\n `[PaymentKit] cardTokenizationMode not provided — auto-detecting from session API: ${sessionConfigUrl}`,\n );\n paymentKitStates._sessionConfigReady = fetch(sessionConfigUrl)\n .then((resp) => {\n if (!resp.ok) {\n console.warn(`[PaymentKit] Session config fetch failed with status ${resp.status}`);\n return null;\n }\n return resp.json();\n })\n .then((session) => {\n if (session && typeof session === \"object\" && typeof session.card_tokenization_mode === \"string\") {\n paymentKitStates.cardTokenizationMode = session.card_tokenization_mode || \"direct\";\n console.log(\n `[PaymentKit] Session config detected — cardTokenizationMode: ${paymentKitStates.cardTokenizationMode}`,\n );\n\n // Validate VGS config from API before trusting it\n const vaultId = session.vgs_vault_id;\n const vgsEnv = session.vgs_environment;\n if (vaultId && /^tnt[a-z0-9]+$/.test(vaultId) && (vgsEnv === \"sandbox\" || vgsEnv === \"live\")) {\n paymentKitStates.vgsVaultId = vaultId;\n paymentKitStates.vgsEnvironment = vgsEnv;\n console.log(`[PaymentKit] VGS config valid — vaultId: ${vaultId}, environment: ${vgsEnv}`);\n } else if (paymentKitStates.cardTokenizationMode === \"vgs\") {\n console.warn(\n `[PaymentKit] Invalid VGS config from session API (vaultId: ${vaultId}, env: ${vgsEnv}), falling back to direct mode`,\n );\n paymentKitStates.cardTokenizationMode = \"direct\";\n }\n } else {\n console.log(\"[PaymentKit] Session response missing card_tokenization_mode — defaulting to direct mode\");\n paymentKitStates.cardTokenizationMode = \"direct\";\n }\n })\n .catch((err) => {\n console.error(\"[PaymentKit] Session config fetch error — falling back to direct mode:\", err);\n paymentKitStates.cardTokenizationMode = \"direct\";\n });\n } else {\n console.log(`[PaymentKit] cardTokenizationMode explicitly set: ${cardTokenizationMode}`);\n }\n\n const pmInstances = paymentMethods.map((paymentMethod) => paymentMethod(paymentKitStates));\n\n const externalFuncsMapByPm: ExternalFuncsMapByPm<PaymentMethods> = pmInstances.reduce(\n (acc, { name, externalFuncs }) => {\n // @ts-expect-error - typecase this better in future\n acc[name] = externalFuncs;\n return acc;\n },\n {} as ExternalFuncsMapByPm<PaymentMethods>,\n );\n\n const submit: PaymentKitReturnType[\"submit\"] = ({\n paymentMethod: paymentMethodName,\n fields,\n options,\n onSuccess,\n onError,\n }) => {\n const paymentMethod = pmInstances.find(({ name }) => name === paymentMethodName);\n if (!paymentMethod) {\n onError({ root: \"payment_method_not_found\" });\n return;\n }\n paymentMethod.internalFuncs\n .submitPayment(fields, options)\n .then(({ data, errors }) => {\n errors ? onError(errors) : onSuccess(data);\n })\n .catch((e) => {\n console.error(\"PaymentKit:submit:catch\", e);\n\n // Try to extract error message from response\n if (e?.response?.data) {\n onError(e.response.data);\n } else if (e?.message) {\n onError({ root: e.message });\n } else {\n onError({ root: \"unknown_error\" });\n }\n });\n };\n\n const cleanup = () => {\n // Clean up all payment method instances\n for (const pm of pmInstances) {\n if (pm.internalFuncs.cleanup) {\n pm.internalFuncs.cleanup();\n }\n }\n unmountTunnelX();\n };\n\n return {\n submit,\n cleanup,\n ...externalFuncsMapByPm,\n };\n};\n\nexport type { PaymentKitFields };\n\nexport default PaymentKit;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["STATIC_ENVIRONMENT_URLS: Record<Exclude<PaymentKitEnvironment, \"tunnel\">, EnvironmentUrls>","PaymentKit: PaymentKitType","PACKAGE_VERSION","externalFuncsMapByPm: ExternalFuncsMapByPm<PaymentMethods>","submit: PaymentKitReturnType[\"submit\"]"],"sources":["../package.json","../src/analytics/mock-adapter.ts","../src/analytics/posthog-adapter.ts","../src/analytics/service.ts","../src/analytics/checkout-timing.ts","../src/types.ts","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@payment-kit-js/vanilla\",\n \"version\": \"0.5.4\",\n \"main\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.mts\",\n \"module\": \"./dist/index.mjs\",\n \"exports\": {\n \".\": \"./dist/index.mjs\",\n \"./payment-methods/airwallex-apple-pay-adapter\": \"./dist/payment-methods/airwallex-apple-pay-adapter.mjs\",\n \"./payment-methods/airwallex-google-pay-adapter\": \"./dist/payment-methods/airwallex-google-pay-adapter.mjs\",\n \"./payment-methods/apple-pay\": \"./dist/payment-methods/apple-pay.mjs\",\n \"./payment-methods/card\": \"./dist/payment-methods/card.mjs\",\n \"./payment-methods/google-pay\": \"./dist/payment-methods/google-pay.mjs\",\n \"./payment-methods/next-action-handlers\": \"./dist/payment-methods/next-action-handlers.mjs\",\n \"./payment-methods/paypal\": \"./dist/payment-methods/paypal.mjs\",\n \"./payment-methods/stripe-apple-pay-adapter\": \"./dist/payment-methods/stripe-apple-pay-adapter.mjs\",\n \"./payment-methods/stripe-google-pay-adapter\": \"./dist/payment-methods/stripe-google-pay-adapter.mjs\",\n \"./payment-methods/vgs-collect-loader\": \"./dist/payment-methods/vgs-collect-loader.mjs\",\n \"./penpal/connect-card\": \"./dist/penpal/connect-card.mjs\",\n \"./penpal/connect-tunnel-x\": \"./dist/penpal/connect-tunnel-x.mjs\",\n \"./package.json\": \"./package.json\"\n },\n \"license\": \"MIT\",\n \"description\": \"Vanilla package for PaymentKit\",\n \"files\": [\n \"dist\",\n \"dist/cdn\"\n ],\n \"scripts\": {\n \"dev\": \"yarn tsdown --watch ./src\",\n \"build:npm\": \"yarn tsdown\",\n \"build:cdn\": \"node build-cdn.mjs\",\n \"build\": \"yarn build:npm && yarn build:cdn\"\n },\n \"packageManager\": \"yarn@4.10.3\",\n \"dependencies\": {\n \"nanoid\": \"^5.0.7\",\n \"penpal\": \"^7.0.4\",\n \"posthog-js\": \"^1.196.0\",\n \"valibot\": \"^1.1.0\"\n },\n \"devDependencies\": {\n \"@pkg/sdk\": \"workspace:*\",\n \"@pkg/tsconfig\": \"workspace:*\",\n \"@stripe/stripe-js\": \"^8.6.1\",\n \"@types/applepayjs\": \"^14.0.9\",\n \"esbuild\": \"^0.24.2\",\n \"tsdown\": \"^0.15.10\",\n \"typescript\": \"^5.9.3\"\n },\n \"stableVersion\": \"0.5.4\"\n}\n","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","import type { CheckoutTimingTracker } from \"./analytics\";\nimport type { CardErrorCode } from \"./penpal/connect-card\";\nimport type { TunnelXIFrameConnection } from \"./penpal/connect-tunnel-x\";\n\nexport type PaymentKitEnvironment = \"local\" | \"tunnel\" | \"sandbox\" | \"production\";\n\ntype EnvironmentUrls = {\n baseUrl: string;\n apiBaseUrl: string;\n};\n\n// TODO: Cleanup this after launch to only use SDK instead of plain fetch.\nconst STATIC_ENVIRONMENT_URLS: Record<Exclude<PaymentKitEnvironment, \"tunnel\">, EnvironmentUrls> = {\n local: {\n baseUrl: \"http://localhost:9101\",\n apiBaseUrl: \"http://localhost:9000\",\n },\n sandbox: {\n baseUrl: \"https://staging.paymentkit.com/customer\",\n apiBaseUrl: \"https://staging.paymentkit.com\",\n },\n production: {\n baseUrl: \"https://paymentkit.com/customer\",\n apiBaseUrl: \"https://paymentkit.com\",\n },\n};\n\nfunction getTunnelUrls(): EnvironmentUrls {\n if (typeof window !== \"undefined\") {\n const params = new URLSearchParams(window.location.search);\n const apiBaseUrl = params.get(\"api_base_url\");\n if (apiBaseUrl) {\n return {\n baseUrl: window.location.origin,\n apiBaseUrl,\n };\n }\n // For loclx-style tunnels where API hostname is derived from the current hostname\n const hostname = window.location.hostname;\n if (hostname.includes(\"loclx.io\")) {\n const apiHostname = hostname.replace(/cportal|playground/, \"api\");\n return {\n baseUrl: `https://${hostname}`,\n apiBaseUrl: `https://${apiHostname}`,\n };\n }\n }\n // Fallback\n return STATIC_ENVIRONMENT_URLS.local;\n}\n\nexport function getUrlsForEnvironment(environment: string): EnvironmentUrls {\n if (environment === \"tunnel\") {\n return getTunnelUrls();\n }\n const env = environment as Exclude<PaymentKitEnvironment, \"tunnel\">;\n const urls = STATIC_ENVIRONMENT_URLS[env];\n if (!urls) {\n throw new Error(`Invalid environment: ${environment}. Must be one of: local, tunnel, sandbox, production`);\n }\n return urls;\n}\n\ntype FormFieldNames = \"customer_name\" | \"customer_email\" | \"customer_country\" | \"customer_zip_code\";\n\nexport type FormErrorCodes = \"required\" | \"invalid\";\n\nexport type TInternalFuncs = {\n submitPayment: (\n fields: PaymentKitFields,\n options?: unknown,\n ) => Promise<{ data: { [key: string]: unknown }; errors?: never } | { data?: never; errors: PaymentKitErrors }>;\n cleanup?: () => void;\n};\n\nexport type PaymentKit = <T extends readonly PaymentMethod<unknown>[]>(options: {\n environment: string;\n secureToken: string;\n paymentMethods: T;\n /** Card tokenization mode from checkout session */\n cardTokenizationMode?: \"direct\" | \"vgs\";\n /** VGS vault ID from checkout session */\n vgsVaultId?: string;\n /** VGS environment from checkout session */\n vgsEnvironment?: string;\n /** @internal Enable analytics test mode for e2e testing */\n __enableAnalyticsTestMode?: boolean;\n}) => ExternalFuncsMapByPm<T> & {\n submit: PaymentKitSubmitHandler<T>;\n cleanup: () => void;\n};\n\ntype PaymentKitSubmitHandler<T extends readonly PaymentMethod<unknown>[]> = <\n N extends keyof ExternalFuncsMapByPm<T>,\n>(options: {\n fields: Partial<PaymentKitFields>;\n paymentMethod: N;\n options?: unknown;\n onError: (error: PaymentKitErrors) => void;\n onSuccess: (data: { [key: string]: unknown }) => void;\n}) => void;\n\nexport type PaymentKitStates = {\n baseUrl: string;\n apiBaseUrl: string;\n secureToken: string;\n environment: string;\n tunnelXConnection: TunnelXIFrameConnection;\n timingTracker: CheckoutTimingTracker;\n /** Checkout request ID for correlating all API calls and analytics events */\n checkoutRequestId: string;\n /** Card tokenization mode: 'direct' (KMS) or 'vgs' (VGS Collect + proxy) */\n cardTokenizationMode?: \"direct\" | \"vgs\";\n /** VGS vault ID (only when cardTokenizationMode is 'vgs') */\n vgsVaultId?: string;\n /** VGS environment: 'sandbox' or 'live' (only when cardTokenizationMode is 'vgs') */\n vgsEnvironment?: string;\n /** @internal Promise that resolves when session config (incl. VGS mode) is auto-detected */\n _sessionConfigReady?: Promise<void>;\n};\n\nexport type PaymentKitErrors = {\n root?: string;\n card_pan?: CardErrorCode;\n card_exp?: CardErrorCode;\n card_cvc?: CardErrorCode;\n paypal?: string;\n google_pay?: string;\n apple_pay?: string;\n processor_id?: string;\n amount?: string;\n currency?: string;\n country?: string;\n} & { [key in FormFieldNames]?: FormErrorCodes | string };\n\nexport type PaymentKitFields = { [key in FormFieldNames]: string };\n\nexport type PaymentMethod<TExternalFuncs = unknown, TName = string> = (paymentKitStates: PaymentKitStates) => {\n name: TName;\n externalFuncs: TExternalFuncs;\n internalFuncs: TInternalFuncs;\n};\n\nexport type ExternalFuncsMapByPm<T extends readonly PaymentMethod<unknown>[]> = {\n [K in T[number] as ReturnType<K>[\"name\"]]: ReturnType<K>[\"externalFuncs\"];\n};\n","import { version as PACKAGE_VERSION } from \"../package.json\";\nimport { AnalyticsService, CheckoutTimingTracker } from \"./analytics\";\nimport { connectToTunnelXIframe } from \"./penpal/connect-tunnel-x\";\nimport type { ExternalFuncsMapByPm, PaymentKitFields, PaymentKit as PaymentKitType } from \"./types\";\nimport { getUrlsForEnvironment } from \"./types\";\nimport { createCheckoutIFrame } from \"./utils\";\nimport { getOrCreateCheckoutRequestId } from \"./utils/checkout-request-id\";\n\ntype PaymentKitReturnType = ReturnType<PaymentKitType>;\n\nconst createTunnelXConnection = (baseUrl: string, apiBaseUrl: string, token: string) => {\n const iframe = createCheckoutIFrame(\"tunnel-x\", baseUrl, {\n checkout_token: token,\n api_base_url: apiBaseUrl,\n });\n document.body.appendChild(iframe);\n\n const connection = connectToTunnelXIframe(iframe, {});\n\n const unmount = () => {\n connection.destroy();\n document.body.removeChild(iframe);\n };\n\n return { unmount, connection };\n};\n\nconst PaymentKit: PaymentKitType = ({\n environment,\n secureToken,\n paymentMethods,\n cardTokenizationMode,\n vgsVaultId,\n vgsEnvironment,\n __enableAnalyticsTestMode,\n}) => {\n type PaymentMethods = typeof paymentMethods;\n\n // Resolve URLs from environment\n const { baseUrl, apiBaseUrl } = getUrlsForEnvironment(environment);\n\n // Log version for debugging\n console.log(`[PaymentKit] v${PACKAGE_VERSION} initialized (env: ${environment})`);\n\n // Generate checkout request ID for correlating all API calls and analytics events\n // This is idempotent - same ID is reused across multiple PaymentKit instantiations\n const checkoutRequestId = getOrCreateCheckoutRequestId(environment);\n console.log(`[PaymentKit] checkout_request_id: ${checkoutRequestId}`);\n\n // Initialize analytics and identify with secure token for correlation\n AnalyticsService.init(apiBaseUrl, __enableAnalyticsTestMode);\n AnalyticsService.identify(secureToken);\n\n // Create timing tracker for this checkout session\n const timingTracker = new CheckoutTimingTracker(secureToken, checkoutRequestId);\n\n // Track page ready immediately\n timingTracker.trackPageReady();\n\n const { connection: tunnelXConnection, unmount: unmountTunnelX } = createTunnelXConnection(\n baseUrl,\n apiBaseUrl,\n secureToken,\n );\n\n const paymentKitStates = {\n baseUrl,\n apiBaseUrl,\n secureToken,\n environment,\n tunnelXConnection,\n timingTracker,\n checkoutRequestId,\n cardTokenizationMode,\n vgsVaultId,\n vgsEnvironment,\n _sessionConfigReady: undefined as Promise<void> | undefined,\n };\n\n // Auto-detect VGS mode from session API if not explicitly provided.\n // The promise resolves before any card mount or submit needs the mode.\n if (cardTokenizationMode === undefined) {\n const sessionConfigUrl = `${apiBaseUrl}/api/checkout-sessions/token/${secureToken}`;\n console.log(\n `[PaymentKit] cardTokenizationMode not provided — auto-detecting from session API: ${sessionConfigUrl}`,\n );\n paymentKitStates._sessionConfigReady = fetch(sessionConfigUrl)\n .then((resp) => {\n if (!resp.ok) {\n console.warn(`[PaymentKit] Session config fetch failed with status ${resp.status}`);\n return null;\n }\n return resp.json();\n })\n .then((session) => {\n if (session && typeof session === \"object\" && typeof session.card_tokenization_mode === \"string\") {\n paymentKitStates.cardTokenizationMode = session.card_tokenization_mode || \"direct\";\n console.log(\n `[PaymentKit] Session config detected — cardTokenizationMode: ${paymentKitStates.cardTokenizationMode}`,\n );\n\n // Validate VGS config from API before trusting it\n const vaultId = session.vgs_vault_id;\n const vgsEnv = session.vgs_environment;\n if (vaultId && /^tnt[a-z0-9]+$/.test(vaultId) && (vgsEnv === \"sandbox\" || vgsEnv === \"live\")) {\n paymentKitStates.vgsVaultId = vaultId;\n paymentKitStates.vgsEnvironment = vgsEnv;\n console.log(`[PaymentKit] VGS config valid — vaultId: ${vaultId}, environment: ${vgsEnv}`);\n } else if (paymentKitStates.cardTokenizationMode === \"vgs\") {\n console.warn(\n `[PaymentKit] Invalid VGS config from session API (vaultId: ${vaultId}, env: ${vgsEnv}), falling back to direct mode`,\n );\n paymentKitStates.cardTokenizationMode = \"direct\";\n }\n } else {\n console.log(\"[PaymentKit] Session response missing card_tokenization_mode — defaulting to direct mode\");\n paymentKitStates.cardTokenizationMode = \"direct\";\n }\n })\n .catch((err) => {\n console.error(\"[PaymentKit] Session config fetch error — falling back to direct mode:\", err);\n paymentKitStates.cardTokenizationMode = \"direct\";\n });\n } else {\n console.log(`[PaymentKit] cardTokenizationMode explicitly set: ${cardTokenizationMode}`);\n }\n\n const pmInstances = paymentMethods.map((paymentMethod) => paymentMethod(paymentKitStates));\n\n const externalFuncsMapByPm: ExternalFuncsMapByPm<PaymentMethods> = pmInstances.reduce(\n (acc, { name, externalFuncs }) => {\n // @ts-expect-error - typecase this better in future\n acc[name] = externalFuncs;\n return acc;\n },\n {} as ExternalFuncsMapByPm<PaymentMethods>,\n );\n\n const submit: PaymentKitReturnType[\"submit\"] = ({\n paymentMethod: paymentMethodName,\n fields,\n options,\n onSuccess,\n onError,\n }) => {\n const paymentMethod = pmInstances.find(({ name }) => name === paymentMethodName);\n if (!paymentMethod) {\n onError({ root: \"payment_method_not_found\" });\n return;\n }\n paymentMethod.internalFuncs\n .submitPayment(fields, options)\n .then(({ data, errors }) => {\n errors ? onError(errors) : onSuccess(data);\n })\n .catch((e) => {\n console.error(\"PaymentKit:submit:catch\", e);\n\n // Try to extract error message from response\n if (e?.response?.data) {\n onError(e.response.data);\n } else if (e?.message) {\n onError({ root: e.message });\n } else {\n onError({ root: \"unknown_error\" });\n }\n });\n };\n\n const cleanup = () => {\n // Clean up all payment method instances\n for (const pm of pmInstances) {\n if (pm.internalFuncs.cleanup) {\n pm.internalFuncs.cleanup();\n }\n }\n unmountTunnelX();\n };\n\n return {\n submit,\n cleanup,\n ...externalFuncsMapByPm,\n };\n};\n\nexport type { PaymentKitFields };\n\nexport default PaymentKit;\n"],"mappings":";;;;;;cAEa;;;;;;;;;ACKb,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;;;;;;ACrGzD,MAAMA,0BAA6F;CACjG,OAAO;EACL,SAAS;EACT,YAAY;EACb;CACD,SAAS;EACP,SAAS;EACT,YAAY;EACb;CACD,YAAY;EACV,SAAS;EACT,YAAY;EACb;CACF;AAED,SAAS,gBAAiC;AACxC,KAAI,OAAO,WAAW,aAAa;EAEjC,MAAM,aADS,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAChC,IAAI,eAAe;AAC7C,MAAI,WACF,QAAO;GACL,SAAS,OAAO,SAAS;GACzB;GACD;EAGH,MAAM,WAAW,OAAO,SAAS;AACjC,MAAI,SAAS,SAAS,WAAW,EAAE;GACjC,MAAM,cAAc,SAAS,QAAQ,sBAAsB,MAAM;AACjE,UAAO;IACL,SAAS,WAAW;IACpB,YAAY,WAAW;IACxB;;;AAIL,QAAO,wBAAwB;;AAGjC,SAAgB,sBAAsB,aAAsC;AAC1E,KAAI,gBAAgB,SAClB,QAAO,eAAe;CAGxB,MAAM,OAAO,wBADD;AAEZ,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,wBAAwB,YAAY,sDAAsD;AAE5G,QAAO;;;;;AClDT,MAAM,2BAA2B,SAAiB,YAAoB,UAAkB;CACtF,MAAM,SAAS,qBAAqB,YAAY,SAAS;EACvD,gBAAgB;EAChB,cAAc;EACf,CAAC;AACF,UAAS,KAAK,YAAY,OAAO;CAEjC,MAAM,aAAa,uBAAuB,QAAQ,EAAE,CAAC;CAErD,MAAM,gBAAgB;AACpB,aAAW,SAAS;AACpB,WAAS,KAAK,YAAY,OAAO;;AAGnC,QAAO;EAAE;EAAS;EAAY;;AAGhC,MAAMC,cAA8B,EAClC,aACA,aACA,gBACA,sBACA,YACA,gBACA,gCACI;CAIJ,MAAM,EAAE,SAAS,eAAe,sBAAsB,YAAY;AAGlE,SAAQ,IAAI,iBAAiBC,QAAgB,qBAAqB,YAAY,GAAG;CAIjF,MAAM,oBAAoB,6BAA6B,YAAY;AACnE,SAAQ,IAAI,qCAAqC,oBAAoB;AAGrE,kBAAiB,KAAK,YAAY,0BAA0B;AAC5D,kBAAiB,SAAS,YAAY;CAGtC,MAAM,gBAAgB,IAAI,sBAAsB,aAAa,kBAAkB;AAG/E,eAAc,gBAAgB;CAE9B,MAAM,EAAE,YAAY,mBAAmB,SAAS,mBAAmB,wBACjE,SACA,YACA,YACD;CAED,MAAM,mBAAmB;EACvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,qBAAqB;EACtB;AAID,KAAI,yBAAyB,QAAW;EACtC,MAAM,mBAAmB,GAAG,WAAW,+BAA+B;AACtE,UAAQ,IACN,qFAAqF,mBACtF;AACD,mBAAiB,sBAAsB,MAAM,iBAAiB,CAC3D,MAAM,SAAS;AACd,OAAI,CAAC,KAAK,IAAI;AACZ,YAAQ,KAAK,wDAAwD,KAAK,SAAS;AACnF,WAAO;;AAET,UAAO,KAAK,MAAM;IAClB,CACD,MAAM,YAAY;AACjB,OAAI,WAAW,OAAO,YAAY,YAAY,OAAO,QAAQ,2BAA2B,UAAU;AAChG,qBAAiB,uBAAuB,QAAQ,0BAA0B;AAC1E,YAAQ,IACN,gEAAgE,iBAAiB,uBAClF;IAGD,MAAM,UAAU,QAAQ;IACxB,MAAM,SAAS,QAAQ;AACvB,QAAI,WAAW,iBAAiB,KAAK,QAAQ,KAAK,WAAW,aAAa,WAAW,SAAS;AAC5F,sBAAiB,aAAa;AAC9B,sBAAiB,iBAAiB;AAClC,aAAQ,IAAI,4CAA4C,QAAQ,iBAAiB,SAAS;eACjF,iBAAiB,yBAAyB,OAAO;AAC1D,aAAQ,KACN,8DAA8D,QAAQ,SAAS,OAAO,gCACvF;AACD,sBAAiB,uBAAuB;;UAErC;AACL,YAAQ,IAAI,2FAA2F;AACvG,qBAAiB,uBAAuB;;IAE1C,CACD,OAAO,QAAQ;AACd,WAAQ,MAAM,0EAA0E,IAAI;AAC5F,oBAAiB,uBAAuB;IACxC;OAEJ,SAAQ,IAAI,qDAAqD,uBAAuB;CAG1F,MAAM,cAAc,eAAe,KAAK,kBAAkB,cAAc,iBAAiB,CAAC;CAE1F,MAAMC,uBAA6D,YAAY,QAC5E,KAAK,EAAE,MAAM,oBAAoB;AAEhC,MAAI,QAAQ;AACZ,SAAO;IAET,EAAE,CACH;CAED,MAAMC,UAA0C,EAC9C,eAAe,mBACf,QACA,SACA,WACA,cACI;EACJ,MAAM,gBAAgB,YAAY,MAAM,EAAE,WAAW,SAAS,kBAAkB;AAChF,MAAI,CAAC,eAAe;AAClB,WAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC7C;;AAEF,gBAAc,cACX,cAAc,QAAQ,QAAQ,CAC9B,MAAM,EAAE,MAAM,aAAa;AAC1B,YAAS,QAAQ,OAAO,GAAG,UAAU,KAAK;IAC1C,CACD,OAAO,MAAM;AACZ,WAAQ,MAAM,2BAA2B,EAAE;AAG3C,OAAI,GAAG,UAAU,KACf,SAAQ,EAAE,SAAS,KAAK;YACf,GAAG,QACZ,SAAQ,EAAE,MAAM,EAAE,SAAS,CAAC;OAE5B,SAAQ,EAAE,MAAM,iBAAiB,CAAC;IAEpC;;CAGN,MAAM,gBAAgB;AAEpB,OAAK,MAAM,MAAM,YACf,KAAI,GAAG,cAAc,QACnB,IAAG,cAAc,SAAS;AAG9B,kBAAgB;;AAGlB,QAAO;EACL;EACA;EACA,GAAG;EACJ;;AAKH,kBAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"card.d.mts","names":[],"sources":["../../src/payment-methods/card.ts"],"sourcesContent":[],"mappings":";;;;;KAQY,aAAA,GAAgB;KAChB,gBAAA,GAAmB;KA0D1B,oBAAA,GAAuB,QAAQ,kBAAkB;UAC5C;AA5DV,CAAA;AACA,cA6jBM,iBA7jBsB,EAiEsC,aAjEb,CAAA;EA0DhD,aAAA,EAAA,CAAA,IAAA,EAOW,eAPS,EAAA,OAAA,EAOqB,oBAPrB,EAAA,GAAA;IAA6B,KAAA,EAAA,CAAA,cAAA,EAAA,MAAA,EAAA,GAAA;MAAlB,OAAA,EAAA,GAAA,GAAA,IAAA;IAAR,CAAA;EAClB,CAAA;CAAM,EAAA,MAAA,CAAA"}
1
+ {"version":3,"file":"card.d.mts","names":[],"sources":["../../src/payment-methods/card.ts"],"sourcesContent":[],"mappings":";;;;;KAQY,aAAA,GAAgB;KAChB,gBAAA,GAAmB;KA2D1B,oBAAA,GAAuB,QAAQ,kBAAkB;UAC5C;AA7DV,CAAA;AACA,cAokBM,iBApkBsB,EAkEsC,aAlEb,CAAA;EA2DhD,aAAA,EAAA,CAAA,IAAA,EAOW,eAPS,EAAA,OAAA,EAOqB,oBAPrB,EAAA,GAAA;IAA6B,KAAA,EAAA,CAAA,cAAA,EAAA,MAAA,EAAA,GAAA;MAAlB,OAAA,EAAA,GAAA,GAAA,IAAA;IAAR,CAAA;EAClB,CAAA;CAAM,EAAA,MAAA,CAAA"}
@@ -3,7 +3,7 @@ import { t as TunnelXManager } from "../connect-tunnel-x-BhVAej5Q.mjs";
3
3
  import { a as validateFormFields, i as definePaymentMethod, n as collectFraudMetadata, r as createCheckoutIFrame, s as withRequestId, t as $ } from "../utils-B70Y8YcZ.mjs";
4
4
  import { t as handleNextAction } from "../next-action-handlers-CTx8tRt0.mjs";
5
5
  import { t as connectToCardIframe } from "../connect-card-CMZkeppE.mjs";
6
- import { n as initVgsCollect, r as loadVgsCollectScript, t as createVgsCardFields } from "../vgs-collect-loader-D3_xOA-G.mjs";
6
+ import { n as initVgsCollect, r as loadVgsCollectScript, t as createVgsCardFields } from "../vgs-collect-loader-BiwzMF2Y.mjs";
7
7
 
8
8
  //#region src/payment-methods/card.ts
9
9
  /**
@@ -47,6 +47,8 @@ const defCreateElement = (states) => {
47
47
  * Runs synchronously when mode is explicitly provided, or after the
48
48
  * auto-detect fetch resolves.
49
49
  */
50
+ if (!states.vgsMountSelectors) states.vgsMountSelectors = {};
51
+ states.vgsMountSelectors[type] = parentSelector;
50
52
  const doMount = () => {
51
53
  if (cancelled) return;
52
54
  const isVgsMode = states.cardTokenizationMode === "vgs";
@@ -77,10 +79,10 @@ const defCreateElement = (states) => {
77
79
  }
78
80
  states.vgsForm = form;
79
81
  const css = style ? Object.fromEntries(Object.entries(style).map(([k, v]) => [k.replace(/([A-Z])/g, "-$1").toLowerCase(), v])) : void 0;
80
- const expSelector = parentSelector.replace(/card[-_]pan/, (m) => m.replace("pan", "exp"));
81
- const cvcSelector = parentSelector.replace(/card[-_]pan/, (m) => m.replace("pan", "cvc"));
82
- if (expSelector === parentSelector || cvcSelector === parentSelector) {
83
- const msg = `[PaymentKit] Failed to derive exp/cvc selectors from "${parentSelector}". Expected a selector containing "card-pan" or "card_pan".`;
82
+ const expSelector = states.vgsMountSelectors?.card_exp;
83
+ const cvcSelector = states.vgsMountSelectors?.card_cvc;
84
+ if (!expSelector || !cvcSelector) {
85
+ const msg = `[PaymentKit] VGS mode requires all three card fields to be mounted. Missing: ${!expSelector ? "card_exp" : ""}${!expSelector && !cvcSelector ? ", " : ""}${!cvcSelector ? "card_cvc" : ""}`;
84
86
  console.error(msg);
85
87
  onLoaded?.();
86
88
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"card.mjs","names":["cleanupVgs: (() => void) | undefined","cleanupDirect: (() => void) | undefined","cdnTimeoutId: ReturnType<typeof setTimeout>","params: Record<string, string>","submitPayment: TInternalFuncs[\"submitPayment\"]","createCardTimeoutId: ReturnType<typeof setTimeout>","errors: PaymentKitErrors"],"sources":["../../src/payment-methods/card.ts"],"sourcesContent":["import {\n connectToCardIframe,\n type IFrameConnection,\n type CardInputType as PenpalCardInputType,\n type CheckoutResponse as PenpalCheckoutResponse,\n} from \"../penpal/connect-card\";\n\n// Re-export types for public API\nexport type CardInputType = PenpalCardInputType;\nexport type CheckoutResponse = PenpalCheckoutResponse;\n\nimport { TunnelXManager } from \"../penpal/connect-tunnel-x\";\nimport type { PaymentKitErrors, PaymentKitFields, PaymentKitStates, TInternalFuncs } from \"../types\";\nimport {\n $,\n collectFraudMetadata,\n createCheckoutIFrame,\n definePaymentMethod,\n validateFormFields,\n withRequestId,\n} from \"../utils\";\nimport { handleNextAction } from \"./next-action-handlers\";\nimport { createVgsCardFields, initVgsCollect, loadVgsCollectScript } from \"./vgs-collect-loader\";\n\n/**\n * Splits a full name into first name and last name.\n * First word becomes firstName, rest becomes lastName.\n */\nconst splitName = (fullName: string): { firstName: string; lastName?: string } => {\n const trimmed = fullName.trim();\n const spaceIndex = trimmed.indexOf(\" \");\n if (spaceIndex === -1) {\n return { firstName: trimmed };\n }\n return {\n firstName: trimmed.substring(0, spaceIndex),\n lastName: trimmed.substring(spaceIndex + 1).trim() || undefined,\n };\n};\n\n/**\n * Maps PaymentKitFields to the customerInfo structure expected by the API.\n */\nconst mapFieldsToCustomerInfo = (fields: PaymentKitFields) => {\n const { firstName, lastName } = splitName(fields.customer_name);\n\n return {\n email: fields.customer_email || undefined,\n firstName,\n lastName,\n billingAddress:\n fields.customer_country || fields.customer_zip_code\n ? {\n country: fields.customer_country || undefined,\n zipCode: fields.customer_zip_code || undefined,\n }\n : undefined,\n };\n};\n\ntype CardStates = PaymentKitStates & {\n cardSetupIntentId?: string;\n cardInputConnections: Partial<Record<PenpalCardInputType, IFrameConnection>>;\n vgsForm?: VGSCollectForm;\n vgsFocusCallbacks?: Partial<Record<PenpalCardInputType, (isFocused: boolean) => void>>;\n};\n\ntype CreateElementOptions = Partial<Parameters<typeof connectToCardIframe>[1]> & {\n style?: Record<string, string>;\n};\n\nconst defCreateElement = (states: CardStates) => {\n const { baseUrl, apiBaseUrl, cardInputConnections, secureToken, timingTracker } = states;\n\n return (type: PenpalCardInputType, options: CreateElementOptions) => {\n const { style, onLoaded, onFocusChange } = options;\n\n const mountIFrame = (parentSelector: string) => {\n // Mutable cleanup callbacks — set once mode is determined and mount completes\n let cleanupVgs: (() => void) | undefined;\n let cleanupDirect: (() => void) | undefined;\n // Cancellation flag: prevents deferred doMount from executing after unmount.\n // Needed because React strict mode may call unmount before the deferred\n // _sessionConfigReady promise resolves.\n let cancelled = false;\n\n /**\n * Performs the actual DOM mount after the tokenization mode is known.\n * Runs synchronously when mode is explicitly provided, or after the\n * auto-detect fetch resolves.\n */\n const doMount = () => {\n if (cancelled) return;\n const isVgsMode = states.cardTokenizationMode === \"vgs\";\n console.log(\n `[PaymentKit] card.doMount(${type}) — mode: ${states.cardTokenizationMode}, vgsVaultId: ${states.vgsVaultId ?? \"unset\"}, vgsEnvironment: ${states.vgsEnvironment ?? \"unset\"}`,\n );\n\n // VGS mode: initialize VGS Collect form and mount fields on card_pan\n if (isVgsMode && states.vgsVaultId && states.vgsEnvironment) {\n // Store per-field focus callbacks so createVgsCardFields can wire each\n // field to its own callback. All three createElement calls complete\n // before the CDN script loads, so all callbacks are available by then.\n if (onFocusChange) {\n if (!states.vgsFocusCallbacks) states.vgsFocusCallbacks = {};\n states.vgsFocusCallbacks[type] = onFocusChange;\n }\n\n if (type === \"card_pan\") {\n const vaultId = states.vgsVaultId;\n const vgsEnv = states.vgsEnvironment;\n\n console.log(`[PaymentKit] VGS mode active — loading VGS Collect CDN for vault ${vaultId} (${vgsEnv})...`);\n const VGS_CDN_TIMEOUT_MS = 15_000;\n let cdnTimeoutId: ReturnType<typeof setTimeout>;\n Promise.race([\n loadVgsCollectScript(),\n new Promise<never>((_, reject) => {\n cdnTimeoutId = setTimeout(() => {\n console.warn(`[PaymentKit] VGS Collect CDN load timed out after ${VGS_CDN_TIMEOUT_MS / 1000}s`);\n reject(new Error(\"VGS Collect CDN load timed out\"));\n }, VGS_CDN_TIMEOUT_MS);\n }),\n ])\n .finally(() => clearTimeout(cdnTimeoutId))\n .then(() => {\n if (cancelled) return;\n console.log(\"[PaymentKit] VGS Collect CDN loaded — initializing form...\");\n const form = initVgsCollect(vaultId, vgsEnv);\n if (cancelled) {\n form.unmount();\n return;\n }\n states.vgsForm = form;\n\n const css = style\n ? Object.fromEntries(\n Object.entries(style).map(([k, v]) => [k.replace(/([A-Z])/g, \"-$1\").toLowerCase(), v]),\n )\n : undefined;\n\n // Derive exp/cvc selectors from the pan selector.\n // Handles both hyphenated (#pk-card-pan) and underscored (#card_pan) IDs.\n const expSelector = parentSelector.replace(/card[-_]pan/, (m) => m.replace(\"pan\", \"exp\"));\n const cvcSelector = parentSelector.replace(/card[-_]pan/, (m) => m.replace(\"pan\", \"cvc\"));\n if (expSelector === parentSelector || cvcSelector === parentSelector) {\n const msg =\n `[PaymentKit] Failed to derive exp/cvc selectors from \"${parentSelector}\". ` +\n `Expected a selector containing \"card-pan\" or \"card_pan\".`;\n console.error(msg);\n onLoaded?.();\n return;\n }\n console.log(\n `[PaymentKit] Creating VGS card fields — pan: ${parentSelector}, exp: ${expSelector}, cvc: ${cvcSelector}`,\n );\n createVgsCardFields(\n form,\n {\n pan: parentSelector,\n exp: expSelector,\n cvc: cvcSelector,\n },\n css,\n states.vgsFocusCallbacks,\n );\n\n console.log(\"[PaymentKit] VGS card fields created successfully\");\n timingTracker.trackInputReady();\n onLoaded?.();\n })\n .catch((err) => {\n console.error(\"[PaymentKit] Failed to load VGS Collect script. Card input will not be available:\", err);\n // Signal loaded so the form exits loading state rather than hanging forever.\n // Card fields won't render, but submit will fail with a clear error.\n onLoaded?.();\n });\n }\n\n // For non-pan fields in VGS mode, signal loaded immediately since\n // VGS handles all three fields from the card_pan mount.\n if (type !== \"card_pan\") {\n onLoaded?.();\n }\n\n cleanupVgs = () => {\n if (type === \"card_pan\" && states.vgsForm) {\n states.vgsForm.unmount();\n states.vgsForm = undefined;\n }\n };\n return;\n }\n\n // If we got here and mode was \"vgs\", it means VGS config was incomplete\n if (isVgsMode) {\n console.warn(\n `[PaymentKit] VGS mode requested but config incomplete (vgsVaultId: ${states.vgsVaultId ?? \"unset\"}, vgsEnvironment: ${states.vgsEnvironment ?? \"unset\"}) — using direct mode`,\n );\n }\n\n // Direct mode: existing penpal iframe logic\n const parent = $(parentSelector);\n const params: Record<string, string> = {\n checkout_token: secureToken,\n api_base_url: apiBaseUrl,\n };\n\n if (style) {\n params.style = JSON.stringify(style);\n }\n\n const iframe = createCheckoutIFrame(type.replace(\"_\", \"-\"), baseUrl, params);\n\n // Set cleanup before DOM mutation to avoid leaking the iframe if\n // unmount() is called between appendChild and onload.\n cleanupDirect = () => {\n const connection = cardInputConnections[type];\n connection?.destroy();\n parent.removeChild(iframe);\n states.cardInputConnections[type] = undefined;\n };\n\n parent.appendChild(iframe);\n\n iframe.onload = () => {\n if (type === \"card_pan\") {\n timingTracker.trackInputReady();\n }\n\n const connection = connectToCardIframe(iframe, {\n onLoaded: onLoaded || (() => {}),\n onFocusChange: onFocusChange || (() => {}),\n });\n\n states.cardInputConnections[type] = connection;\n };\n };\n\n // If session config needs auto-detection, wait for it; otherwise mount synchronously\n if (states._sessionConfigReady) {\n states._sessionConfigReady.then(doMount);\n } else {\n doMount();\n }\n\n return {\n unmount: () => {\n cancelled = true;\n cleanupVgs?.();\n cleanupDirect?.();\n },\n };\n };\n\n return { mount: mountIFrame };\n };\n};\n\nconst defSubmitPayment = (states: CardStates) => {\n const submitPayment: TInternalFuncs[\"submitPayment\"] = async (fields) => {\n // Ensure session config is loaded (no-op if explicitly provided)\n if (states._sessionConfigReady) await states._sessionConfigReady;\n const isVgsMode = states.cardTokenizationMode === \"vgs\";\n\n const { timingTracker, checkoutRequestId } = states;\n\n // Track submit event\n timingTracker.trackSubmit();\n\n // Use the checkout request ID from states (generated on page load)\n const requestOptions = withRequestId(checkoutRequestId);\n\n const tunnelX = await TunnelXManager.createFromPenpalConnection(states.tunnelXConnection);\n\n // Step 1. Validate form values.\n // Form field validation is shared across both modes.\n // In VGS mode, card field validation is handled by VGS Collect (built-in validators).\n // In direct mode, card field validation is handled by penpal iframes.\n const validateFormResult = await validateFormFields(fields);\n if (!isVgsMode) {\n const validateCardResult = await validateCardFields(states);\n if (!(validateCardResult.isSuccess && validateFormResult.isSuccess)) {\n timingTracker.trackFail(null, \"validation_error\", \"Form validation failed\");\n return { errors: { ...validateCardResult.errors, ...validateFormResult.errors } };\n }\n } else if (!validateFormResult.isSuccess) {\n timingTracker.trackFail(null, \"validation_error\", \"Form validation failed\");\n return { errors: validateFormResult.errors };\n }\n\n // Step 2. Create card setup intent if not present\n if (!states.cardSetupIntentId) {\n const res = await tunnelX.publicEndpoints.createCardSetupIntent({\n checkoutToken: states.secureToken,\n });\n states.cardSetupIntentId = res.cardSetupIntentId;\n }\n\n // Step 3. Submit card values.\n if (isVgsMode) {\n // VGS mode: create card via CMP, then send aliases to backend\n console.log(\"[PaymentKit] Submitting card via VGS CMP...\");\n const vgsSubmitResult = await submitVgsCardFields(states, tunnelX);\n if (!vgsSubmitResult.isSuccess) {\n timingTracker.trackFail(null, \"card_submit_error\", \"VGS card submission failed\");\n return { errors: vgsSubmitResult.errors };\n }\n } else {\n // Direct mode: submit via penpal iframes\n const submitCardResult = await submitCardFields(states);\n if (!submitCardResult.isSuccess) {\n timingTracker.trackFail(null, \"card_submit_error\", \"Card submission failed\");\n return { errors: submitCardResult.errors };\n }\n }\n\n // Step 4. Get card setup intent.\n const cardSetupIntent = await tunnelX.publicEndpoints.getCardSetupIntent({\n cardSetupIntentId: states.cardSetupIntentId,\n checkoutToken: states.secureToken,\n });\n if (!cardSetupIntent.isCardAllSet) {\n const errors = {} as PaymentKitErrors;\n if (!cardSetupIntent.isCardPanSet) errors.card_pan = \"required\";\n if (!cardSetupIntent.isCardExpSet) errors.card_exp = \"required\";\n if (!cardSetupIntent.isCardCvcSet) errors.card_cvc = \"required\";\n timingTracker.trackFail(null, \"card_setup_incomplete\", \"Card details incomplete\");\n return { errors };\n }\n\n console.log(\"Card setup intent is set ✅\", cardSetupIntent);\n console.log(\"Fields\", fields);\n\n // Step 5. Submit card checkout with customer info and fraud metadata\n let currentResult = await tunnelX.publicEndpoints.cardCheckout(\n {\n checkoutToken: states.secureToken,\n publicCardCheckoutRequest: {\n cardSetupIntentId: states.cardSetupIntentId,\n customerInfo: mapFieldsToCustomerInfo(fields),\n fraudMetadata: collectFraudMetadata(),\n },\n },\n requestOptions,\n );\n\n console.log(\"Card checkout result:\", currentResult);\n\n // Step 6. Handle next actions in a loop (supports multiple 3DS challenges)\n // This loop handles the case where one processor fails and we try another\n // that also requires 3DS authentication.\n const MAX_USER_ACTIONS = 5; // Safety limit to prevent infinite loops\n let userActionCount = 0;\n\n while (currentResult.nextAction && userActionCount < MAX_USER_ACTIONS) {\n userActionCount++;\n console.log(`Handling user action ${userActionCount}/${MAX_USER_ACTIONS}...`);\n\n const actionResult = await handleNextAction(currentResult.nextAction);\n\n // Always call verify endpoint so backend can properly conclude the checkout.\n // This is needed even on 3DS failure so the backend can transition from\n // user_actions_requested to card_payment_concluded with proper failure state.\n console.log(\"User action completed, verifying checkout...\");\n const verifyResult = await tunnelX.publicEndpoints.cardCheckoutVerify(\n {\n checkoutToken: states.secureToken,\n },\n requestOptions,\n );\n\n // Check if another action is required (e.g., cascade to next processor with 3DS)\n // This must be checked BEFORE returning error, as cascade may offer a new 3DS challenge\n if (verifyResult.nextAction) {\n if (!actionResult.success) {\n console.log(\"3DS failed but cascade triggered new action, continuing loop...\");\n } else {\n console.log(\"Another user action required, continuing loop...\");\n }\n currentResult = verifyResult;\n continue;\n }\n\n if (!actionResult.success) {\n // 3DS failed and no cascade/retry available - return error\n // Include verifyResult as checkout_response so error_code is available to frontend\n console.log(\"3DS authentication failed, checkout concluded:\", verifyResult);\n timingTracker.trackFail(\n verifyResult.checkoutAttemptId || null,\n verifyResult.errorCode || \"3ds_failed\",\n verifyResult.errorMessageForCustomer || actionResult.error,\n );\n return {\n errors: {\n root: verifyResult.errorMessageForCustomer || actionResult.error,\n checkout_response: verifyResult,\n },\n };\n }\n\n console.log(\"Card checkout verified ✅\", verifyResult);\n\n // No more actions needed, return the result\n timingTracker.trackSuccess(verifyResult.checkoutAttemptId || \"unknown\");\n return { data: verifyResult as { [key: string]: unknown } };\n }\n\n if (userActionCount >= MAX_USER_ACTIONS) {\n console.error(\"Max user actions exceeded\");\n timingTracker.trackFail(null, \"max_actions_exceeded\", \"Too many authentication attempts\");\n return { errors: { root: \"Too many authentication attempts. Please try again.\" } };\n }\n\n // Check if checkout succeeded or failed\n // States: checkout_succeeded (success), payment_failed (failure)\n if (currentResult.state === \"payment_failed\" || currentResult.state === \"checkout_failed\") {\n console.log(\"Card checkout failed:\", currentResult);\n timingTracker.trackFail(\n currentResult.checkoutAttemptId || null,\n currentResult.errorCode || \"payment_failed\",\n currentResult.errorMessageForCustomer || \"Payment failed\",\n );\n // Return the full response as errors so frontend can display error details\n // including error_code, error_message_for_customer, error_message_for_debug\n return {\n errors: {\n root: currentResult.errorMessageForCustomer || \"Payment failed\",\n checkout_response: currentResult,\n },\n };\n }\n\n console.log(\"Card checkout completed ✅\", currentResult);\n timingTracker.trackSuccess(currentResult.checkoutAttemptId || \"unknown\");\n return { data: currentResult as { [key: string]: unknown } };\n };\n return submitPayment;\n};\n\n/**\n * Fetches a VGS CMP access token from the backend.\n */\nconst fetchCollectToken = async (apiBaseUrl: string, secureToken: string): Promise<string> => {\n const resp = await fetch(`${apiBaseUrl}/api/card-setup-intents/${secureToken}/collect-token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!resp.ok) {\n throw new Error(`Failed to get collect token: ${resp.status}`);\n }\n const data = await resp.json();\n return data.access_token;\n};\n\n/**\n * Creates a card via VGS CMP and sends aliases to our backend.\n *\n * Flow:\n * 1. Fetch CMP access token from our backend\n * 2. Call form.createCard() → VGS creates card, returns aliases\n * 3. Send aliases + fraud metadata to our backend via updateCardSetupIntent\n */\nconst submitVgsCardFields = async (\n states: CardStates,\n tunnelX: Awaited<ReturnType<typeof TunnelXManager.createFromPenpalConnection>>,\n): Promise<{ errors: PaymentKitErrors; isSuccess: boolean }> => {\n const { vgsForm, cardSetupIntentId, secureToken, apiBaseUrl } = states;\n const errors = {} as PaymentKitErrors;\n\n if (!vgsForm) {\n errors.card_pan = \"required\";\n return { errors, isSuccess: false };\n }\n\n if (!cardSetupIntentId) {\n errors.root = \"Card setup intent not created\";\n return { errors, isSuccess: false };\n }\n\n try {\n // Step 1: Get CMP access token from our backend\n const accessToken = await fetchCollectToken(apiBaseUrl, secureToken);\n\n // Step 2: Create card via VGS CMP — returns aliases for PAN/CVC, plain text expiry\n const VGS_CREATE_CARD_TIMEOUT_MS = 30_000;\n let createCardTimeoutId: ReturnType<typeof setTimeout>;\n const cardResult = await Promise.race([\n new Promise<VGSCreateCardResponse>((resolve, reject) => {\n vgsForm.createCard(\n {\n auth: accessToken,\n data: { cardholder: {} },\n },\n (_status, cardObject) => resolve(cardObject),\n (error) => reject(error),\n );\n }),\n new Promise<never>((_, reject) => {\n createCardTimeoutId = setTimeout(\n () => reject(new Error(\"VGS createCard timed out\")),\n VGS_CREATE_CARD_TIMEOUT_MS,\n );\n }),\n ]).finally(() => clearTimeout(createCardTimeoutId));\n\n // Step 3: Extract aliases and expiry from CMP response\n const { pan_alias, cvc_alias, exp_month, exp_year } = cardResult.data.attributes;\n const fullYear = exp_year < 100 ? 2000 + exp_year : exp_year;\n const formattedExp = `${String(exp_month).padStart(2, \"0\")}/${fullYear}`;\n\n // Step 4: Send aliases + fraud metadata to our backend\n await tunnelX.publicEndpoints.updateCardSetupIntent({\n checkoutToken: secureToken,\n cardSetupIntentId: cardSetupIntentId,\n updateCardSetupIntentReq: {\n cardPan: pan_alias,\n cardExp: formattedExp,\n cardCvc: cvc_alias,\n fraudMetadata: collectFraudMetadata(),\n },\n });\n\n return { errors: {}, isSuccess: true };\n } catch (err) {\n console.error(\"[PaymentKit] VGS CMP createCard failed:\", err);\n errors.root = \"Card submission failed\";\n return { errors, isSuccess: false };\n }\n};\n\nconst submitCardFields = async (states: CardStates) => {\n const errors = {} as PaymentKitErrors;\n const { cardSetupIntentId, cardInputConnections } = states;\n\n const submitPromises = Object.entries(cardInputConnections).map(async ([_type, connection]) => {\n const type = _type as PenpalCardInputType;\n\n const remote = await connection.promise;\n const result = await remote.onSubmit(cardSetupIntentId || \"\");\n\n if (\"error\" in result) {\n errors[type] = result.error;\n }\n });\n\n await Promise.allSettled(submitPromises);\n\n return {\n errors,\n isSuccess: Object.keys(errors).length === 0,\n };\n};\n\nconst validateCardFields = async (states: CardStates) => {\n const errors: PaymentKitErrors = {};\n const { cardInputConnections } = states;\n\n const validatePromises = Object.entries(cardInputConnections).map(async ([_type, connection]) => {\n const type = _type as PenpalCardInputType;\n\n if (!connection) {\n errors[type] = \"penpal_not_connected\";\n return;\n }\n\n const remote = await connection.promise;\n const errorMsg = await remote.onValidate();\n\n if (errorMsg) {\n errors[type] = errorMsg;\n }\n });\n\n await Promise.allSettled(validatePromises);\n\n return {\n errors,\n isSuccess: Object.keys(errors).length === 0,\n };\n};\n\nconst CardPaymentMethod = definePaymentMethod((paymentKitStates) => {\n // Object.create() creates prototype delegation: reads fall through to the shared\n // paymentKitStates (preserving auto-detected VGS config), writes stay local.\n // This prevents card-specific properties (cardInputConnections, vgsForm, etc.)\n // from leaking onto the shared paymentKitStates object.\n const localStates = Object.create(paymentKitStates) as CardStates;\n localStates.cardInputConnections = {};\n\n return {\n name: \"card\",\n externalFuncs: {\n createElement: defCreateElement(localStates),\n },\n internalFuncs: {\n submitPayment: defSubmitPayment(localStates),\n cleanup: () => {\n // Clean up VGS form if present\n if (localStates.vgsForm) {\n localStates.vgsForm.unmount();\n localStates.vgsForm = undefined;\n }\n // Clean up all card input iframe connections\n for (const connection of Object.values(localStates.cardInputConnections)) {\n connection?.destroy();\n }\n localStates.cardInputConnections = {};\n },\n },\n };\n});\n\nexport default CardPaymentMethod;\n"],"mappings":";;;;;;;;;;;;AA4BA,MAAM,aAAa,aAA+D;CAChF,MAAM,UAAU,SAAS,MAAM;CAC/B,MAAM,aAAa,QAAQ,QAAQ,IAAI;AACvC,KAAI,eAAe,GACjB,QAAO,EAAE,WAAW,SAAS;AAE/B,QAAO;EACL,WAAW,QAAQ,UAAU,GAAG,WAAW;EAC3C,UAAU,QAAQ,UAAU,aAAa,EAAE,CAAC,MAAM,IAAI;EACvD;;;;;AAMH,MAAM,2BAA2B,WAA6B;CAC5D,MAAM,EAAE,WAAW,aAAa,UAAU,OAAO,cAAc;AAE/D,QAAO;EACL,OAAO,OAAO,kBAAkB;EAChC;EACA;EACA,gBACE,OAAO,oBAAoB,OAAO,oBAC9B;GACE,SAAS,OAAO,oBAAoB;GACpC,SAAS,OAAO,qBAAqB;GACtC,GACD;EACP;;AAcH,MAAM,oBAAoB,WAAuB;CAC/C,MAAM,EAAE,SAAS,YAAY,sBAAsB,aAAa,kBAAkB;AAElF,SAAQ,MAA2B,YAAkC;EACnE,MAAM,EAAE,OAAO,UAAU,kBAAkB;EAE3C,MAAM,eAAe,mBAA2B;GAE9C,IAAIA;GACJ,IAAIC;GAIJ,IAAI,YAAY;;;;;;GAOhB,MAAM,gBAAgB;AACpB,QAAI,UAAW;IACf,MAAM,YAAY,OAAO,yBAAyB;AAClD,YAAQ,IACN,6BAA6B,KAAK,YAAY,OAAO,qBAAqB,gBAAgB,OAAO,cAAc,QAAQ,oBAAoB,OAAO,kBAAkB,UACrK;AAGD,QAAI,aAAa,OAAO,cAAc,OAAO,gBAAgB;AAI3D,SAAI,eAAe;AACjB,UAAI,CAAC,OAAO,kBAAmB,QAAO,oBAAoB,EAAE;AAC5D,aAAO,kBAAkB,QAAQ;;AAGnC,SAAI,SAAS,YAAY;MACvB,MAAM,UAAU,OAAO;MACvB,MAAM,SAAS,OAAO;AAEtB,cAAQ,IAAI,oEAAoE,QAAQ,IAAI,OAAO,MAAM;MACzG,MAAM,qBAAqB;MAC3B,IAAIC;AACJ,cAAQ,KAAK,CACX,sBAAsB,EACtB,IAAI,SAAgB,GAAG,WAAW;AAChC,sBAAe,iBAAiB;AAC9B,gBAAQ,KAAK,qDAAqD,qBAAqB,IAAK,GAAG;AAC/F,+BAAO,IAAI,MAAM,iCAAiC,CAAC;UAClD,mBAAmB;QACtB,CACH,CAAC,CACC,cAAc,aAAa,aAAa,CAAC,CACzC,WAAW;AACV,WAAI,UAAW;AACf,eAAQ,IAAI,6DAA6D;OACzE,MAAM,OAAO,eAAe,SAAS,OAAO;AAC5C,WAAI,WAAW;AACb,aAAK,SAAS;AACd;;AAEF,cAAO,UAAU;OAEjB,MAAM,MAAM,QACR,OAAO,YACL,OAAO,QAAQ,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,EAAE,QAAQ,YAAY,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CACvF,GACD;OAIJ,MAAM,cAAc,eAAe,QAAQ,gBAAgB,MAAM,EAAE,QAAQ,OAAO,MAAM,CAAC;OACzF,MAAM,cAAc,eAAe,QAAQ,gBAAgB,MAAM,EAAE,QAAQ,OAAO,MAAM,CAAC;AACzF,WAAI,gBAAgB,kBAAkB,gBAAgB,gBAAgB;QACpE,MAAM,MACJ,yDAAyD,eAAe;AAE1E,gBAAQ,MAAM,IAAI;AAClB,oBAAY;AACZ;;AAEF,eAAQ,IACN,gDAAgD,eAAe,SAAS,YAAY,SAAS,cAC9F;AACD,2BACE,MACA;QACE,KAAK;QACL,KAAK;QACL,KAAK;QACN,EACD,KACA,OAAO,kBACR;AAED,eAAQ,IAAI,oDAAoD;AAChE,qBAAc,iBAAiB;AAC/B,mBAAY;QACZ,CACD,OAAO,QAAQ;AACd,eAAQ,MAAM,qFAAqF,IAAI;AAGvG,mBAAY;QACZ;;AAKN,SAAI,SAAS,WACX,aAAY;AAGd,wBAAmB;AACjB,UAAI,SAAS,cAAc,OAAO,SAAS;AACzC,cAAO,QAAQ,SAAS;AACxB,cAAO,UAAU;;;AAGrB;;AAIF,QAAI,UACF,SAAQ,KACN,sEAAsE,OAAO,cAAc,QAAQ,oBAAoB,OAAO,kBAAkB,QAAQ,uBACzJ;IAIH,MAAM,SAAS,EAAE,eAAe;IAChC,MAAMC,SAAiC;KACrC,gBAAgB;KAChB,cAAc;KACf;AAED,QAAI,MACF,QAAO,QAAQ,KAAK,UAAU,MAAM;IAGtC,MAAM,SAAS,qBAAqB,KAAK,QAAQ,KAAK,IAAI,EAAE,SAAS,OAAO;AAI5E,0BAAsB;AAEpB,KADmB,qBAAqB,OAC5B,SAAS;AACrB,YAAO,YAAY,OAAO;AAC1B,YAAO,qBAAqB,QAAQ;;AAGtC,WAAO,YAAY,OAAO;AAE1B,WAAO,eAAe;AACpB,SAAI,SAAS,WACX,eAAc,iBAAiB;KAGjC,MAAM,aAAa,oBAAoB,QAAQ;MAC7C,UAAU,mBAAmB;MAC7B,eAAe,wBAAwB;MACxC,CAAC;AAEF,YAAO,qBAAqB,QAAQ;;;AAKxC,OAAI,OAAO,oBACT,QAAO,oBAAoB,KAAK,QAAQ;OAExC,UAAS;AAGX,UAAO,EACL,eAAe;AACb,gBAAY;AACZ,kBAAc;AACd,qBAAiB;MAEpB;;AAGH,SAAO,EAAE,OAAO,aAAa;;;AAIjC,MAAM,oBAAoB,WAAuB;CAC/C,MAAMC,gBAAiD,OAAO,WAAW;AAEvE,MAAI,OAAO,oBAAqB,OAAM,OAAO;EAC7C,MAAM,YAAY,OAAO,yBAAyB;EAElD,MAAM,EAAE,eAAe,sBAAsB;AAG7C,gBAAc,aAAa;EAG3B,MAAM,iBAAiB,cAAc,kBAAkB;EAEvD,MAAM,UAAU,MAAM,eAAe,2BAA2B,OAAO,kBAAkB;EAMzF,MAAM,qBAAqB,MAAM,mBAAmB,OAAO;AAC3D,MAAI,CAAC,WAAW;GACd,MAAM,qBAAqB,MAAM,mBAAmB,OAAO;AAC3D,OAAI,EAAE,mBAAmB,aAAa,mBAAmB,YAAY;AACnE,kBAAc,UAAU,MAAM,oBAAoB,yBAAyB;AAC3E,WAAO,EAAE,QAAQ;KAAE,GAAG,mBAAmB;KAAQ,GAAG,mBAAmB;KAAQ,EAAE;;aAE1E,CAAC,mBAAmB,WAAW;AACxC,iBAAc,UAAU,MAAM,oBAAoB,yBAAyB;AAC3E,UAAO,EAAE,QAAQ,mBAAmB,QAAQ;;AAI9C,MAAI,CAAC,OAAO,kBAIV,QAAO,qBAHK,MAAM,QAAQ,gBAAgB,sBAAsB,EAC9D,eAAe,OAAO,aACvB,CAAC,EAC6B;AAIjC,MAAI,WAAW;AAEb,WAAQ,IAAI,8CAA8C;GAC1D,MAAM,kBAAkB,MAAM,oBAAoB,QAAQ,QAAQ;AAClE,OAAI,CAAC,gBAAgB,WAAW;AAC9B,kBAAc,UAAU,MAAM,qBAAqB,6BAA6B;AAChF,WAAO,EAAE,QAAQ,gBAAgB,QAAQ;;SAEtC;GAEL,MAAM,mBAAmB,MAAM,iBAAiB,OAAO;AACvD,OAAI,CAAC,iBAAiB,WAAW;AAC/B,kBAAc,UAAU,MAAM,qBAAqB,yBAAyB;AAC5E,WAAO,EAAE,QAAQ,iBAAiB,QAAQ;;;EAK9C,MAAM,kBAAkB,MAAM,QAAQ,gBAAgB,mBAAmB;GACvE,mBAAmB,OAAO;GAC1B,eAAe,OAAO;GACvB,CAAC;AACF,MAAI,CAAC,gBAAgB,cAAc;GACjC,MAAM,SAAS,EAAE;AACjB,OAAI,CAAC,gBAAgB,aAAc,QAAO,WAAW;AACrD,OAAI,CAAC,gBAAgB,aAAc,QAAO,WAAW;AACrD,OAAI,CAAC,gBAAgB,aAAc,QAAO,WAAW;AACrD,iBAAc,UAAU,MAAM,yBAAyB,0BAA0B;AACjF,UAAO,EAAE,QAAQ;;AAGnB,UAAQ,IAAI,8BAA8B,gBAAgB;AAC1D,UAAQ,IAAI,UAAU,OAAO;EAG7B,IAAI,gBAAgB,MAAM,QAAQ,gBAAgB,aAChD;GACE,eAAe,OAAO;GACtB,2BAA2B;IACzB,mBAAmB,OAAO;IAC1B,cAAc,wBAAwB,OAAO;IAC7C,eAAe,sBAAsB;IACtC;GACF,EACD,eACD;AAED,UAAQ,IAAI,yBAAyB,cAAc;EAKnD,MAAM,mBAAmB;EACzB,IAAI,kBAAkB;AAEtB,SAAO,cAAc,cAAc,kBAAkB,kBAAkB;AACrE;AACA,WAAQ,IAAI,wBAAwB,gBAAgB,GAAG,iBAAiB,KAAK;GAE7E,MAAM,eAAe,MAAM,iBAAiB,cAAc,WAAW;AAKrE,WAAQ,IAAI,+CAA+C;GAC3D,MAAM,eAAe,MAAM,QAAQ,gBAAgB,mBACjD,EACE,eAAe,OAAO,aACvB,EACD,eACD;AAID,OAAI,aAAa,YAAY;AAC3B,QAAI,CAAC,aAAa,QAChB,SAAQ,IAAI,kEAAkE;QAE9E,SAAQ,IAAI,mDAAmD;AAEjE,oBAAgB;AAChB;;AAGF,OAAI,CAAC,aAAa,SAAS;AAGzB,YAAQ,IAAI,kDAAkD,aAAa;AAC3E,kBAAc,UACZ,aAAa,qBAAqB,MAClC,aAAa,aAAa,cAC1B,aAAa,2BAA2B,aAAa,MACtD;AACD,WAAO,EACL,QAAQ;KACN,MAAM,aAAa,2BAA2B,aAAa;KAC3D,mBAAmB;KACpB,EACF;;AAGH,WAAQ,IAAI,4BAA4B,aAAa;AAGrD,iBAAc,aAAa,aAAa,qBAAqB,UAAU;AACvE,UAAO,EAAE,MAAM,cAA4C;;AAG7D,MAAI,mBAAmB,kBAAkB;AACvC,WAAQ,MAAM,4BAA4B;AAC1C,iBAAc,UAAU,MAAM,wBAAwB,mCAAmC;AACzF,UAAO,EAAE,QAAQ,EAAE,MAAM,uDAAuD,EAAE;;AAKpF,MAAI,cAAc,UAAU,oBAAoB,cAAc,UAAU,mBAAmB;AACzF,WAAQ,IAAI,yBAAyB,cAAc;AACnD,iBAAc,UACZ,cAAc,qBAAqB,MACnC,cAAc,aAAa,kBAC3B,cAAc,2BAA2B,iBAC1C;AAGD,UAAO,EACL,QAAQ;IACN,MAAM,cAAc,2BAA2B;IAC/C,mBAAmB;IACpB,EACF;;AAGH,UAAQ,IAAI,6BAA6B,cAAc;AACvD,gBAAc,aAAa,cAAc,qBAAqB,UAAU;AACxE,SAAO,EAAE,MAAM,eAA6C;;AAE9D,QAAO;;;;;AAMT,MAAM,oBAAoB,OAAO,YAAoB,gBAAyC;CAC5F,MAAM,OAAO,MAAM,MAAM,GAAG,WAAW,0BAA0B,YAAY,iBAAiB;EAC5F,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAChD,CAAC;AACF,KAAI,CAAC,KAAK,GACR,OAAM,IAAI,MAAM,gCAAgC,KAAK,SAAS;AAGhE,SADa,MAAM,KAAK,MAAM,EAClB;;;;;;;;;;AAWd,MAAM,sBAAsB,OAC1B,QACA,YAC8D;CAC9D,MAAM,EAAE,SAAS,mBAAmB,aAAa,eAAe;CAChE,MAAM,SAAS,EAAE;AAEjB,KAAI,CAAC,SAAS;AACZ,SAAO,WAAW;AAClB,SAAO;GAAE;GAAQ,WAAW;GAAO;;AAGrC,KAAI,CAAC,mBAAmB;AACtB,SAAO,OAAO;AACd,SAAO;GAAE;GAAQ,WAAW;GAAO;;AAGrC,KAAI;EAEF,MAAM,cAAc,MAAM,kBAAkB,YAAY,YAAY;EAGpE,MAAM,6BAA6B;EACnC,IAAIC;EAqBJ,MAAM,EAAE,WAAW,WAAW,WAAW,cApBtB,MAAM,QAAQ,KAAK,CACpC,IAAI,SAAgC,SAAS,WAAW;AACtD,WAAQ,WACN;IACE,MAAM;IACN,MAAM,EAAE,YAAY,EAAE,EAAE;IACzB,GACA,SAAS,eAAe,QAAQ,WAAW,GAC3C,UAAU,OAAO,MAAM,CACzB;IACD,EACF,IAAI,SAAgB,GAAG,WAAW;AAChC,yBAAsB,iBACd,uBAAO,IAAI,MAAM,2BAA2B,CAAC,EACnD,2BACD;IACD,CACH,CAAC,CAAC,cAAc,aAAa,oBAAoB,CAAC,EAGc,KAAK;EACtE,MAAM,WAAW,WAAW,MAAM,MAAO,WAAW;EACpD,MAAM,eAAe,GAAG,OAAO,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG;AAG9D,QAAM,QAAQ,gBAAgB,sBAAsB;GAClD,eAAe;GACI;GACnB,0BAA0B;IACxB,SAAS;IACT,SAAS;IACT,SAAS;IACT,eAAe,sBAAsB;IACtC;GACF,CAAC;AAEF,SAAO;GAAE,QAAQ,EAAE;GAAE,WAAW;GAAM;UAC/B,KAAK;AACZ,UAAQ,MAAM,2CAA2C,IAAI;AAC7D,SAAO,OAAO;AACd,SAAO;GAAE;GAAQ,WAAW;GAAO;;;AAIvC,MAAM,mBAAmB,OAAO,WAAuB;CACrD,MAAM,SAAS,EAAE;CACjB,MAAM,EAAE,mBAAmB,yBAAyB;CAEpD,MAAM,iBAAiB,OAAO,QAAQ,qBAAqB,CAAC,IAAI,OAAO,CAAC,OAAO,gBAAgB;EAC7F,MAAM,OAAO;EAGb,MAAM,SAAS,OADA,MAAM,WAAW,SACJ,SAAS,qBAAqB,GAAG;AAE7D,MAAI,WAAW,OACb,QAAO,QAAQ,OAAO;GAExB;AAEF,OAAM,QAAQ,WAAW,eAAe;AAExC,QAAO;EACL;EACA,WAAW,OAAO,KAAK,OAAO,CAAC,WAAW;EAC3C;;AAGH,MAAM,qBAAqB,OAAO,WAAuB;CACvD,MAAMC,SAA2B,EAAE;CACnC,MAAM,EAAE,yBAAyB;CAEjC,MAAM,mBAAmB,OAAO,QAAQ,qBAAqB,CAAC,IAAI,OAAO,CAAC,OAAO,gBAAgB;EAC/F,MAAM,OAAO;AAEb,MAAI,CAAC,YAAY;AACf,UAAO,QAAQ;AACf;;EAIF,MAAM,WAAW,OADF,MAAM,WAAW,SACF,YAAY;AAE1C,MAAI,SACF,QAAO,QAAQ;GAEjB;AAEF,OAAM,QAAQ,WAAW,iBAAiB;AAE1C,QAAO;EACL;EACA,WAAW,OAAO,KAAK,OAAO,CAAC,WAAW;EAC3C;;AAGH,MAAM,oBAAoB,qBAAqB,qBAAqB;CAKlE,MAAM,cAAc,OAAO,OAAO,iBAAiB;AACnD,aAAY,uBAAuB,EAAE;AAErC,QAAO;EACL,MAAM;EACN,eAAe,EACb,eAAe,iBAAiB,YAAY,EAC7C;EACD,eAAe;GACb,eAAe,iBAAiB,YAAY;GAC5C,eAAe;AAEb,QAAI,YAAY,SAAS;AACvB,iBAAY,QAAQ,SAAS;AAC7B,iBAAY,UAAU;;AAGxB,SAAK,MAAM,cAAc,OAAO,OAAO,YAAY,qBAAqB,CACtE,aAAY,SAAS;AAEvB,gBAAY,uBAAuB,EAAE;;GAExC;EACF;EACD;AAEF,mBAAe"}
1
+ {"version":3,"file":"card.mjs","names":["cleanupVgs: (() => void) | undefined","cleanupDirect: (() => void) | undefined","cdnTimeoutId: ReturnType<typeof setTimeout>","params: Record<string, string>","submitPayment: TInternalFuncs[\"submitPayment\"]","createCardTimeoutId: ReturnType<typeof setTimeout>","errors: PaymentKitErrors"],"sources":["../../src/payment-methods/card.ts"],"sourcesContent":["import {\n connectToCardIframe,\n type IFrameConnection,\n type CardInputType as PenpalCardInputType,\n type CheckoutResponse as PenpalCheckoutResponse,\n} from \"../penpal/connect-card\";\n\n// Re-export types for public API\nexport type CardInputType = PenpalCardInputType;\nexport type CheckoutResponse = PenpalCheckoutResponse;\n\nimport { TunnelXManager } from \"../penpal/connect-tunnel-x\";\nimport type { PaymentKitErrors, PaymentKitFields, PaymentKitStates, TInternalFuncs } from \"../types\";\nimport {\n $,\n collectFraudMetadata,\n createCheckoutIFrame,\n definePaymentMethod,\n validateFormFields,\n withRequestId,\n} from \"../utils\";\nimport { handleNextAction } from \"./next-action-handlers\";\nimport { createVgsCardFields, initVgsCollect, loadVgsCollectScript } from \"./vgs-collect-loader\";\n\n/**\n * Splits a full name into first name and last name.\n * First word becomes firstName, rest becomes lastName.\n */\nconst splitName = (fullName: string): { firstName: string; lastName?: string } => {\n const trimmed = fullName.trim();\n const spaceIndex = trimmed.indexOf(\" \");\n if (spaceIndex === -1) {\n return { firstName: trimmed };\n }\n return {\n firstName: trimmed.substring(0, spaceIndex),\n lastName: trimmed.substring(spaceIndex + 1).trim() || undefined,\n };\n};\n\n/**\n * Maps PaymentKitFields to the customerInfo structure expected by the API.\n */\nconst mapFieldsToCustomerInfo = (fields: PaymentKitFields) => {\n const { firstName, lastName } = splitName(fields.customer_name);\n\n return {\n email: fields.customer_email || undefined,\n firstName,\n lastName,\n billingAddress:\n fields.customer_country || fields.customer_zip_code\n ? {\n country: fields.customer_country || undefined,\n zipCode: fields.customer_zip_code || undefined,\n }\n : undefined,\n };\n};\n\ntype CardStates = PaymentKitStates & {\n cardSetupIntentId?: string;\n cardInputConnections: Partial<Record<PenpalCardInputType, IFrameConnection>>;\n vgsForm?: VGSCollectForm;\n vgsFocusCallbacks?: Partial<Record<PenpalCardInputType, (isFocused: boolean) => void>>;\n vgsMountSelectors?: Partial<Record<PenpalCardInputType, string>>;\n};\n\ntype CreateElementOptions = Partial<Parameters<typeof connectToCardIframe>[1]> & {\n style?: Record<string, string>;\n};\n\nconst defCreateElement = (states: CardStates) => {\n const { baseUrl, apiBaseUrl, cardInputConnections, secureToken, timingTracker } = states;\n\n return (type: PenpalCardInputType, options: CreateElementOptions) => {\n const { style, onLoaded, onFocusChange } = options;\n\n const mountIFrame = (parentSelector: string) => {\n // Mutable cleanup callbacks — set once mode is determined and mount completes\n let cleanupVgs: (() => void) | undefined;\n let cleanupDirect: (() => void) | undefined;\n // Cancellation flag: prevents deferred doMount from executing after unmount.\n // Needed because React strict mode may call unmount before the deferred\n // _sessionConfigReady promise resolves.\n let cancelled = false;\n\n /**\n * Performs the actual DOM mount after the tokenization mode is known.\n * Runs synchronously when mode is explicitly provided, or after the\n * auto-detect fetch resolves.\n */\n // Store mount selector so VGS init (triggered by card_pan) can read\n // the exp/cvc selectors that were registered by their own createElement calls.\n if (!states.vgsMountSelectors) states.vgsMountSelectors = {};\n states.vgsMountSelectors[type] = parentSelector;\n\n const doMount = () => {\n if (cancelled) return;\n const isVgsMode = states.cardTokenizationMode === \"vgs\";\n console.log(\n `[PaymentKit] card.doMount(${type}) — mode: ${states.cardTokenizationMode}, vgsVaultId: ${states.vgsVaultId ?? \"unset\"}, vgsEnvironment: ${states.vgsEnvironment ?? \"unset\"}`,\n );\n\n // VGS mode: initialize VGS Collect form and mount fields on card_pan\n if (isVgsMode && states.vgsVaultId && states.vgsEnvironment) {\n // Store per-field focus callbacks so createVgsCardFields can wire each\n // field to its own callback. All three createElement calls complete\n // before the CDN script loads, so all callbacks are available by then.\n if (onFocusChange) {\n if (!states.vgsFocusCallbacks) states.vgsFocusCallbacks = {};\n states.vgsFocusCallbacks[type] = onFocusChange;\n }\n\n if (type === \"card_pan\") {\n const vaultId = states.vgsVaultId;\n const vgsEnv = states.vgsEnvironment;\n\n console.log(`[PaymentKit] VGS mode active — loading VGS Collect CDN for vault ${vaultId} (${vgsEnv})...`);\n const VGS_CDN_TIMEOUT_MS = 15_000;\n let cdnTimeoutId: ReturnType<typeof setTimeout>;\n Promise.race([\n loadVgsCollectScript(),\n new Promise<never>((_, reject) => {\n cdnTimeoutId = setTimeout(() => {\n console.warn(`[PaymentKit] VGS Collect CDN load timed out after ${VGS_CDN_TIMEOUT_MS / 1000}s`);\n reject(new Error(\"VGS Collect CDN load timed out\"));\n }, VGS_CDN_TIMEOUT_MS);\n }),\n ])\n .finally(() => clearTimeout(cdnTimeoutId))\n .then(() => {\n if (cancelled) return;\n console.log(\"[PaymentKit] VGS Collect CDN loaded — initializing form...\");\n const form = initVgsCollect(vaultId, vgsEnv);\n if (cancelled) {\n form.unmount();\n return;\n }\n states.vgsForm = form;\n\n const css = style\n ? Object.fromEntries(\n Object.entries(style).map(([k, v]) => [k.replace(/([A-Z])/g, \"-$1\").toLowerCase(), v]),\n )\n : undefined;\n\n // Read exp/cvc selectors from shared state.\n // Each createElement(\"card_exp/card_cvc\").mount(selector) stores its\n // selector before this async callback runs, so they're available here.\n const expSelector = states.vgsMountSelectors?.card_exp;\n const cvcSelector = states.vgsMountSelectors?.card_cvc;\n if (!expSelector || !cvcSelector) {\n const msg =\n `[PaymentKit] VGS mode requires all three card fields to be mounted. ` +\n `Missing: ${!expSelector ? \"card_exp\" : \"\"}${!expSelector && !cvcSelector ? \", \" : \"\"}${!cvcSelector ? \"card_cvc\" : \"\"}`;\n console.error(msg);\n onLoaded?.();\n return;\n }\n console.log(\n `[PaymentKit] Creating VGS card fields — pan: ${parentSelector}, exp: ${expSelector}, cvc: ${cvcSelector}`,\n );\n createVgsCardFields(\n form,\n {\n pan: parentSelector,\n exp: expSelector,\n cvc: cvcSelector,\n },\n css,\n states.vgsFocusCallbacks,\n );\n\n console.log(\"[PaymentKit] VGS card fields created successfully\");\n timingTracker.trackInputReady();\n onLoaded?.();\n })\n .catch((err) => {\n console.error(\"[PaymentKit] Failed to load VGS Collect script. Card input will not be available:\", err);\n // Signal loaded so the form exits loading state rather than hanging forever.\n // Card fields won't render, but submit will fail with a clear error.\n onLoaded?.();\n });\n }\n\n // For non-pan fields in VGS mode, signal loaded immediately since\n // VGS handles all three fields from the card_pan mount.\n if (type !== \"card_pan\") {\n onLoaded?.();\n }\n\n cleanupVgs = () => {\n if (type === \"card_pan\" && states.vgsForm) {\n states.vgsForm.unmount();\n states.vgsForm = undefined;\n }\n };\n return;\n }\n\n // If we got here and mode was \"vgs\", it means VGS config was incomplete\n if (isVgsMode) {\n console.warn(\n `[PaymentKit] VGS mode requested but config incomplete (vgsVaultId: ${states.vgsVaultId ?? \"unset\"}, vgsEnvironment: ${states.vgsEnvironment ?? \"unset\"}) — using direct mode`,\n );\n }\n\n // Direct mode: existing penpal iframe logic\n const parent = $(parentSelector);\n const params: Record<string, string> = {\n checkout_token: secureToken,\n api_base_url: apiBaseUrl,\n };\n\n if (style) {\n params.style = JSON.stringify(style);\n }\n\n const iframe = createCheckoutIFrame(type.replace(\"_\", \"-\"), baseUrl, params);\n\n // Set cleanup before DOM mutation to avoid leaking the iframe if\n // unmount() is called between appendChild and onload.\n cleanupDirect = () => {\n const connection = cardInputConnections[type];\n connection?.destroy();\n parent.removeChild(iframe);\n states.cardInputConnections[type] = undefined;\n };\n\n parent.appendChild(iframe);\n\n iframe.onload = () => {\n if (type === \"card_pan\") {\n timingTracker.trackInputReady();\n }\n\n const connection = connectToCardIframe(iframe, {\n onLoaded: onLoaded || (() => {}),\n onFocusChange: onFocusChange || (() => {}),\n });\n\n states.cardInputConnections[type] = connection;\n };\n };\n\n // If session config needs auto-detection, wait for it; otherwise mount synchronously\n if (states._sessionConfigReady) {\n states._sessionConfigReady.then(doMount);\n } else {\n doMount();\n }\n\n return {\n unmount: () => {\n cancelled = true;\n cleanupVgs?.();\n cleanupDirect?.();\n },\n };\n };\n\n return { mount: mountIFrame };\n };\n};\n\nconst defSubmitPayment = (states: CardStates) => {\n const submitPayment: TInternalFuncs[\"submitPayment\"] = async (fields) => {\n // Ensure session config is loaded (no-op if explicitly provided)\n if (states._sessionConfigReady) await states._sessionConfigReady;\n const isVgsMode = states.cardTokenizationMode === \"vgs\";\n\n const { timingTracker, checkoutRequestId } = states;\n\n // Track submit event\n timingTracker.trackSubmit();\n\n // Use the checkout request ID from states (generated on page load)\n const requestOptions = withRequestId(checkoutRequestId);\n\n const tunnelX = await TunnelXManager.createFromPenpalConnection(states.tunnelXConnection);\n\n // Step 1. Validate form values.\n // Form field validation is shared across both modes.\n // In VGS mode, card field validation is handled by VGS Collect (built-in validators).\n // In direct mode, card field validation is handled by penpal iframes.\n const validateFormResult = await validateFormFields(fields);\n if (!isVgsMode) {\n const validateCardResult = await validateCardFields(states);\n if (!(validateCardResult.isSuccess && validateFormResult.isSuccess)) {\n timingTracker.trackFail(null, \"validation_error\", \"Form validation failed\");\n return { errors: { ...validateCardResult.errors, ...validateFormResult.errors } };\n }\n } else if (!validateFormResult.isSuccess) {\n timingTracker.trackFail(null, \"validation_error\", \"Form validation failed\");\n return { errors: validateFormResult.errors };\n }\n\n // Step 2. Create card setup intent if not present\n if (!states.cardSetupIntentId) {\n const res = await tunnelX.publicEndpoints.createCardSetupIntent({\n checkoutToken: states.secureToken,\n });\n states.cardSetupIntentId = res.cardSetupIntentId;\n }\n\n // Step 3. Submit card values.\n if (isVgsMode) {\n // VGS mode: create card via CMP, then send aliases to backend\n console.log(\"[PaymentKit] Submitting card via VGS CMP...\");\n const vgsSubmitResult = await submitVgsCardFields(states, tunnelX);\n if (!vgsSubmitResult.isSuccess) {\n timingTracker.trackFail(null, \"card_submit_error\", \"VGS card submission failed\");\n return { errors: vgsSubmitResult.errors };\n }\n } else {\n // Direct mode: submit via penpal iframes\n const submitCardResult = await submitCardFields(states);\n if (!submitCardResult.isSuccess) {\n timingTracker.trackFail(null, \"card_submit_error\", \"Card submission failed\");\n return { errors: submitCardResult.errors };\n }\n }\n\n // Step 4. Get card setup intent.\n const cardSetupIntent = await tunnelX.publicEndpoints.getCardSetupIntent({\n cardSetupIntentId: states.cardSetupIntentId,\n checkoutToken: states.secureToken,\n });\n if (!cardSetupIntent.isCardAllSet) {\n const errors = {} as PaymentKitErrors;\n if (!cardSetupIntent.isCardPanSet) errors.card_pan = \"required\";\n if (!cardSetupIntent.isCardExpSet) errors.card_exp = \"required\";\n if (!cardSetupIntent.isCardCvcSet) errors.card_cvc = \"required\";\n timingTracker.trackFail(null, \"card_setup_incomplete\", \"Card details incomplete\");\n return { errors };\n }\n\n console.log(\"Card setup intent is set ✅\", cardSetupIntent);\n console.log(\"Fields\", fields);\n\n // Step 5. Submit card checkout with customer info and fraud metadata\n let currentResult = await tunnelX.publicEndpoints.cardCheckout(\n {\n checkoutToken: states.secureToken,\n publicCardCheckoutRequest: {\n cardSetupIntentId: states.cardSetupIntentId,\n customerInfo: mapFieldsToCustomerInfo(fields),\n fraudMetadata: collectFraudMetadata(),\n },\n },\n requestOptions,\n );\n\n console.log(\"Card checkout result:\", currentResult);\n\n // Step 6. Handle next actions in a loop (supports multiple 3DS challenges)\n // This loop handles the case where one processor fails and we try another\n // that also requires 3DS authentication.\n const MAX_USER_ACTIONS = 5; // Safety limit to prevent infinite loops\n let userActionCount = 0;\n\n while (currentResult.nextAction && userActionCount < MAX_USER_ACTIONS) {\n userActionCount++;\n console.log(`Handling user action ${userActionCount}/${MAX_USER_ACTIONS}...`);\n\n const actionResult = await handleNextAction(currentResult.nextAction);\n\n // Always call verify endpoint so backend can properly conclude the checkout.\n // This is needed even on 3DS failure so the backend can transition from\n // user_actions_requested to card_payment_concluded with proper failure state.\n console.log(\"User action completed, verifying checkout...\");\n const verifyResult = await tunnelX.publicEndpoints.cardCheckoutVerify(\n {\n checkoutToken: states.secureToken,\n },\n requestOptions,\n );\n\n // Check if another action is required (e.g., cascade to next processor with 3DS)\n // This must be checked BEFORE returning error, as cascade may offer a new 3DS challenge\n if (verifyResult.nextAction) {\n if (!actionResult.success) {\n console.log(\"3DS failed but cascade triggered new action, continuing loop...\");\n } else {\n console.log(\"Another user action required, continuing loop...\");\n }\n currentResult = verifyResult;\n continue;\n }\n\n if (!actionResult.success) {\n // 3DS failed and no cascade/retry available - return error\n // Include verifyResult as checkout_response so error_code is available to frontend\n console.log(\"3DS authentication failed, checkout concluded:\", verifyResult);\n timingTracker.trackFail(\n verifyResult.checkoutAttemptId || null,\n verifyResult.errorCode || \"3ds_failed\",\n verifyResult.errorMessageForCustomer || actionResult.error,\n );\n return {\n errors: {\n root: verifyResult.errorMessageForCustomer || actionResult.error,\n checkout_response: verifyResult,\n },\n };\n }\n\n console.log(\"Card checkout verified ✅\", verifyResult);\n\n // No more actions needed, return the result\n timingTracker.trackSuccess(verifyResult.checkoutAttemptId || \"unknown\");\n return { data: verifyResult as { [key: string]: unknown } };\n }\n\n if (userActionCount >= MAX_USER_ACTIONS) {\n console.error(\"Max user actions exceeded\");\n timingTracker.trackFail(null, \"max_actions_exceeded\", \"Too many authentication attempts\");\n return { errors: { root: \"Too many authentication attempts. Please try again.\" } };\n }\n\n // Check if checkout succeeded or failed\n // States: checkout_succeeded (success), payment_failed (failure)\n if (currentResult.state === \"payment_failed\" || currentResult.state === \"checkout_failed\") {\n console.log(\"Card checkout failed:\", currentResult);\n timingTracker.trackFail(\n currentResult.checkoutAttemptId || null,\n currentResult.errorCode || \"payment_failed\",\n currentResult.errorMessageForCustomer || \"Payment failed\",\n );\n // Return the full response as errors so frontend can display error details\n // including error_code, error_message_for_customer, error_message_for_debug\n return {\n errors: {\n root: currentResult.errorMessageForCustomer || \"Payment failed\",\n checkout_response: currentResult,\n },\n };\n }\n\n console.log(\"Card checkout completed ✅\", currentResult);\n timingTracker.trackSuccess(currentResult.checkoutAttemptId || \"unknown\");\n return { data: currentResult as { [key: string]: unknown } };\n };\n return submitPayment;\n};\n\n/**\n * Fetches a VGS CMP access token from the backend.\n */\nconst fetchCollectToken = async (apiBaseUrl: string, secureToken: string): Promise<string> => {\n const resp = await fetch(`${apiBaseUrl}/api/card-setup-intents/${secureToken}/collect-token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!resp.ok) {\n throw new Error(`Failed to get collect token: ${resp.status}`);\n }\n const data = await resp.json();\n return data.access_token;\n};\n\n/**\n * Creates a card via VGS CMP and sends aliases to our backend.\n *\n * Flow:\n * 1. Fetch CMP access token from our backend\n * 2. Call form.createCard() → VGS creates card, returns aliases\n * 3. Send aliases + fraud metadata to our backend via updateCardSetupIntent\n */\nconst submitVgsCardFields = async (\n states: CardStates,\n tunnelX: Awaited<ReturnType<typeof TunnelXManager.createFromPenpalConnection>>,\n): Promise<{ errors: PaymentKitErrors; isSuccess: boolean }> => {\n const { vgsForm, cardSetupIntentId, secureToken, apiBaseUrl } = states;\n const errors = {} as PaymentKitErrors;\n\n if (!vgsForm) {\n errors.card_pan = \"required\";\n return { errors, isSuccess: false };\n }\n\n if (!cardSetupIntentId) {\n errors.root = \"Card setup intent not created\";\n return { errors, isSuccess: false };\n }\n\n try {\n // Step 1: Get CMP access token from our backend\n const accessToken = await fetchCollectToken(apiBaseUrl, secureToken);\n\n // Step 2: Create card via VGS CMP — returns aliases for PAN/CVC, plain text expiry\n const VGS_CREATE_CARD_TIMEOUT_MS = 30_000;\n let createCardTimeoutId: ReturnType<typeof setTimeout>;\n const cardResult = await Promise.race([\n new Promise<VGSCreateCardResponse>((resolve, reject) => {\n vgsForm.createCard(\n {\n auth: accessToken,\n data: { cardholder: {} },\n },\n (_status, cardObject) => resolve(cardObject),\n (error) => reject(error),\n );\n }),\n new Promise<never>((_, reject) => {\n createCardTimeoutId = setTimeout(\n () => reject(new Error(\"VGS createCard timed out\")),\n VGS_CREATE_CARD_TIMEOUT_MS,\n );\n }),\n ]).finally(() => clearTimeout(createCardTimeoutId));\n\n // Step 3: Extract aliases and expiry from CMP response\n const { pan_alias, cvc_alias, exp_month, exp_year } = cardResult.data.attributes;\n const fullYear = exp_year < 100 ? 2000 + exp_year : exp_year;\n const formattedExp = `${String(exp_month).padStart(2, \"0\")}/${fullYear}`;\n\n // Step 4: Send aliases + fraud metadata to our backend\n await tunnelX.publicEndpoints.updateCardSetupIntent({\n checkoutToken: secureToken,\n cardSetupIntentId: cardSetupIntentId,\n updateCardSetupIntentReq: {\n cardPan: pan_alias,\n cardExp: formattedExp,\n cardCvc: cvc_alias,\n fraudMetadata: collectFraudMetadata(),\n },\n });\n\n return { errors: {}, isSuccess: true };\n } catch (err) {\n console.error(\"[PaymentKit] VGS CMP createCard failed:\", err);\n errors.root = \"Card submission failed\";\n return { errors, isSuccess: false };\n }\n};\n\nconst submitCardFields = async (states: CardStates) => {\n const errors = {} as PaymentKitErrors;\n const { cardSetupIntentId, cardInputConnections } = states;\n\n const submitPromises = Object.entries(cardInputConnections).map(async ([_type, connection]) => {\n const type = _type as PenpalCardInputType;\n\n const remote = await connection.promise;\n const result = await remote.onSubmit(cardSetupIntentId || \"\");\n\n if (\"error\" in result) {\n errors[type] = result.error;\n }\n });\n\n await Promise.allSettled(submitPromises);\n\n return {\n errors,\n isSuccess: Object.keys(errors).length === 0,\n };\n};\n\nconst validateCardFields = async (states: CardStates) => {\n const errors: PaymentKitErrors = {};\n const { cardInputConnections } = states;\n\n const validatePromises = Object.entries(cardInputConnections).map(async ([_type, connection]) => {\n const type = _type as PenpalCardInputType;\n\n if (!connection) {\n errors[type] = \"penpal_not_connected\";\n return;\n }\n\n const remote = await connection.promise;\n const errorMsg = await remote.onValidate();\n\n if (errorMsg) {\n errors[type] = errorMsg;\n }\n });\n\n await Promise.allSettled(validatePromises);\n\n return {\n errors,\n isSuccess: Object.keys(errors).length === 0,\n };\n};\n\nconst CardPaymentMethod = definePaymentMethod((paymentKitStates) => {\n // Object.create() creates prototype delegation: reads fall through to the shared\n // paymentKitStates (preserving auto-detected VGS config), writes stay local.\n // This prevents card-specific properties (cardInputConnections, vgsForm, etc.)\n // from leaking onto the shared paymentKitStates object.\n const localStates = Object.create(paymentKitStates) as CardStates;\n localStates.cardInputConnections = {};\n\n return {\n name: \"card\",\n externalFuncs: {\n createElement: defCreateElement(localStates),\n },\n internalFuncs: {\n submitPayment: defSubmitPayment(localStates),\n cleanup: () => {\n // Clean up VGS form if present\n if (localStates.vgsForm) {\n localStates.vgsForm.unmount();\n localStates.vgsForm = undefined;\n }\n // Clean up all card input iframe connections\n for (const connection of Object.values(localStates.cardInputConnections)) {\n connection?.destroy();\n }\n localStates.cardInputConnections = {};\n },\n },\n };\n});\n\nexport default CardPaymentMethod;\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { n as initVgsCollect, r as loadVgsCollectScript, t as createVgsCardFields } from "../vgs-collect-loader-D3_xOA-G.mjs";
1
+ import { n as initVgsCollect, r as loadVgsCollectScript, t as createVgsCardFields } from "../vgs-collect-loader-BiwzMF2Y.mjs";
2
2
 
3
3
  export { createVgsCardFields, initVgsCollect, loadVgsCollectScript };
@@ -72,6 +72,30 @@ function createVgsCardFields(form, mountPoints, css, onFocusChangeCallbacks) {
72
72
  validations: ["required", "validCardSecurityCode"],
73
73
  css: fieldCss
74
74
  });
75
+ for (const selector of [
76
+ mountPoints.pan,
77
+ mountPoints.exp,
78
+ mountPoints.cvc
79
+ ]) {
80
+ const container = document.querySelector(selector);
81
+ if (!container) continue;
82
+ const styleIframe = (iframe) => {
83
+ iframe.style.width = "100%";
84
+ iframe.style.height = "100%";
85
+ };
86
+ const existing = container.querySelector("iframe");
87
+ if (existing) styleIframe(existing);
88
+ else new MutationObserver((_, obs) => {
89
+ const iframe = container.querySelector("iframe");
90
+ if (iframe) {
91
+ styleIframe(iframe);
92
+ obs.disconnect();
93
+ }
94
+ }).observe(container, {
95
+ childList: true,
96
+ subtree: true
97
+ });
98
+ }
75
99
  if (onFocusChangeCallbacks) {
76
100
  const fieldMap = [
77
101
  [panField, "card_pan"],
@@ -90,4 +114,4 @@ function createVgsCardFields(form, mountPoints, css, onFocusChangeCallbacks) {
90
114
 
91
115
  //#endregion
92
116
  export { initVgsCollect as n, loadVgsCollectScript as r, createVgsCardFields as t };
93
- //# sourceMappingURL=vgs-collect-loader-D3_xOA-G.mjs.map
117
+ //# sourceMappingURL=vgs-collect-loader-BiwzMF2Y.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vgs-collect-loader-BiwzMF2Y.mjs","names":["loadPromise: Promise<void> | null","fieldMap: [VGSCollectField, string][]"],"sources":["../src/payment-methods/vgs-collect-loader.ts"],"sourcesContent":["const VGS_COLLECT_CDN = \"https://js.verygoodvault.com/vgs-collect/3.2.1/vgs-collect.js\";\n\nlet loadPromise: Promise<void> | null = null;\n\n/**\n * Loads VGS Collect JS from CDN. Idempotent — subsequent calls return the same promise.\n */\nexport function loadVgsCollectScript(): Promise<void> {\n if (loadPromise) {\n console.log(\"[PaymentKit] VGS Collect CDN load already in progress — reusing promise\");\n return loadPromise;\n }\n\n loadPromise = new Promise<void>((resolve, reject) => {\n if (typeof window !== \"undefined\" && window.VGSCollect) {\n console.log(\"[PaymentKit] VGS Collect JS already available on window\");\n resolve();\n return;\n }\n\n console.log(`[PaymentKit] Loading VGS Collect JS from CDN: ${VGS_COLLECT_CDN}`);\n const script = document.createElement(\"script\");\n script.src = VGS_COLLECT_CDN;\n script.async = true;\n script.onload = () => {\n console.log(\"[PaymentKit] VGS Collect JS loaded successfully from CDN\");\n resolve();\n };\n script.onerror = () => {\n console.error(`[PaymentKit] Failed to load VGS Collect JS from CDN: ${VGS_COLLECT_CDN}`);\n loadPromise = null;\n reject(new Error(\"Failed to load VGS Collect JS\"));\n };\n document.head.appendChild(script);\n });\n\n return loadPromise;\n}\n\n/**\n * Initializes a VGS Collect form instance.\n */\nexport function initVgsCollect(vaultId: string, environment: string): VGSCollectForm {\n if (!window.VGSCollect) {\n throw new Error(\"VGS Collect JS not loaded. Call loadVgsCollectScript() first.\");\n }\n return window.VGSCollect.create(vaultId, environment, () => {});\n}\n\n/**\n * Creates and mounts VGS Collect card fields using CMP-specific field methods.\n *\n * Uses cardNumberField(), cardExpirationDateField(), cardCVCField() which are\n * required for the CMP createCard() flow. After mounting, call form.createCard()\n * to create a card in the VGS vault, which returns aliases for PAN and CVC.\n *\n * @param form - VGS Collect form instance\n * @param mountPoints - CSS selectors for PAN, expiry, and CVC containers\n * @param css - Optional CSS to apply to VGS Collect iframe fields\n */\nexport function createVgsCardFields(\n form: VGSCollectForm,\n mountPoints: { pan: string; exp: string; cvc: string },\n css?: Record<string, string>,\n onFocusChangeCallbacks?: Partial<Record<string, (isFocused: boolean) => void>>,\n) {\n const fieldCss = css ?? {\n height: \"46px\",\n \"font-size\": \"1rem\",\n color: \"#18181b\",\n \"font-family\": \"ui-sans-serif, system-ui, sans-serif\",\n };\n\n const panField = form.cardNumberField(mountPoints.pan, {\n placeholder: \"Card number\",\n validations: [\"required\", \"validCardNumber\"],\n css: fieldCss,\n });\n\n const expField = form.cardExpirationDateField(mountPoints.exp, {\n placeholder: \"MM / YY\",\n validations: [\"required\", \"validCardExpirationDate\"],\n css: fieldCss,\n });\n\n const cvcField = form.cardCVCField(mountPoints.cvc, {\n placeholder: \"123\",\n validations: [\"required\", \"validCardSecurityCode\"],\n css: fieldCss,\n });\n\n // VGS Collect doesn't set width/height on its iframes, so they may not\n // fill the merchant's container. Force them to stretch like direct-mode iframes.\n // Iframes may be inserted asynchronously, so use MutationObserver as a fallback.\n for (const selector of [mountPoints.pan, mountPoints.exp, mountPoints.cvc]) {\n const container = document.querySelector(selector);\n if (!container) continue;\n\n const styleIframe = (iframe: HTMLIFrameElement) => {\n iframe.style.width = \"100%\";\n iframe.style.height = \"100%\";\n };\n\n const existing = container.querySelector(\"iframe\") as HTMLIFrameElement | null;\n if (existing) {\n styleIframe(existing);\n } else {\n const observer = new MutationObserver((_, obs) => {\n const iframe = container.querySelector(\"iframe\") as HTMLIFrameElement | null;\n if (iframe) {\n styleIframe(iframe);\n obs.disconnect();\n }\n });\n observer.observe(container, { childList: true, subtree: true });\n }\n }\n\n if (onFocusChangeCallbacks) {\n const fieldMap: [VGSCollectField, string][] = [\n [panField, \"card_pan\"],\n [expField, \"card_exp\"],\n [cvcField, \"card_cvc\"],\n ];\n for (const [field, key] of fieldMap) {\n const cb = onFocusChangeCallbacks[key];\n if (cb) {\n field.on(\"focus\", () => cb(true));\n field.on(\"blur\", () => cb(false));\n }\n }\n }\n}\n"],"mappings":";AAAA,MAAM,kBAAkB;AAExB,IAAIA,cAAoC;;;;AAKxC,SAAgB,uBAAsC;AACpD,KAAI,aAAa;AACf,UAAQ,IAAI,0EAA0E;AACtF,SAAO;;AAGT,eAAc,IAAI,SAAe,SAAS,WAAW;AACnD,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAQ,IAAI,0DAA0D;AACtE,YAAS;AACT;;AAGF,UAAQ,IAAI,iDAAiD,kBAAkB;EAC/E,MAAM,SAAS,SAAS,cAAc,SAAS;AAC/C,SAAO,MAAM;AACb,SAAO,QAAQ;AACf,SAAO,eAAe;AACpB,WAAQ,IAAI,2DAA2D;AACvE,YAAS;;AAEX,SAAO,gBAAgB;AACrB,WAAQ,MAAM,wDAAwD,kBAAkB;AACxF,iBAAc;AACd,0BAAO,IAAI,MAAM,gCAAgC,CAAC;;AAEpD,WAAS,KAAK,YAAY,OAAO;GACjC;AAEF,QAAO;;;;;AAMT,SAAgB,eAAe,SAAiB,aAAqC;AACnF,KAAI,CAAC,OAAO,WACV,OAAM,IAAI,MAAM,gEAAgE;AAElF,QAAO,OAAO,WAAW,OAAO,SAAS,mBAAmB,GAAG;;;;;;;;;;;;;AAcjE,SAAgB,oBACd,MACA,aACA,KACA,wBACA;CACA,MAAM,WAAW,OAAO;EACtB,QAAQ;EACR,aAAa;EACb,OAAO;EACP,eAAe;EAChB;CAED,MAAM,WAAW,KAAK,gBAAgB,YAAY,KAAK;EACrD,aAAa;EACb,aAAa,CAAC,YAAY,kBAAkB;EAC5C,KAAK;EACN,CAAC;CAEF,MAAM,WAAW,KAAK,wBAAwB,YAAY,KAAK;EAC7D,aAAa;EACb,aAAa,CAAC,YAAY,0BAA0B;EACpD,KAAK;EACN,CAAC;CAEF,MAAM,WAAW,KAAK,aAAa,YAAY,KAAK;EAClD,aAAa;EACb,aAAa,CAAC,YAAY,wBAAwB;EAClD,KAAK;EACN,CAAC;AAKF,MAAK,MAAM,YAAY;EAAC,YAAY;EAAK,YAAY;EAAK,YAAY;EAAI,EAAE;EAC1E,MAAM,YAAY,SAAS,cAAc,SAAS;AAClD,MAAI,CAAC,UAAW;EAEhB,MAAM,eAAe,WAA8B;AACjD,UAAO,MAAM,QAAQ;AACrB,UAAO,MAAM,SAAS;;EAGxB,MAAM,WAAW,UAAU,cAAc,SAAS;AAClD,MAAI,SACF,aAAY,SAAS;MASrB,CAPiB,IAAI,kBAAkB,GAAG,QAAQ;GAChD,MAAM,SAAS,UAAU,cAAc,SAAS;AAChD,OAAI,QAAQ;AACV,gBAAY,OAAO;AACnB,QAAI,YAAY;;IAElB,CACO,QAAQ,WAAW;GAAE,WAAW;GAAM,SAAS;GAAM,CAAC;;AAInE,KAAI,wBAAwB;EAC1B,MAAMC,WAAwC;GAC5C,CAAC,UAAU,WAAW;GACtB,CAAC,UAAU,WAAW;GACtB,CAAC,UAAU,WAAW;GACvB;AACD,OAAK,MAAM,CAAC,OAAO,QAAQ,UAAU;GACnC,MAAM,KAAK,uBAAuB;AAClC,OAAI,IAAI;AACN,UAAM,GAAG,eAAe,GAAG,KAAK,CAAC;AACjC,UAAM,GAAG,cAAc,GAAG,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payment-kit-js/vanilla",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "module": "./dist/index.mjs",
@@ -48,5 +48,5 @@
48
48
  "tsdown": "^0.15.10",
49
49
  "typescript": "^5.9.3"
50
50
  },
51
- "stableVersion": "0.5.2"
51
+ "stableVersion": "0.5.4"
52
52
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"vgs-collect-loader-D3_xOA-G.mjs","names":["loadPromise: Promise<void> | null","fieldMap: [VGSCollectField, string][]"],"sources":["../src/payment-methods/vgs-collect-loader.ts"],"sourcesContent":["const VGS_COLLECT_CDN = \"https://js.verygoodvault.com/vgs-collect/3.2.1/vgs-collect.js\";\n\nlet loadPromise: Promise<void> | null = null;\n\n/**\n * Loads VGS Collect JS from CDN. Idempotent — subsequent calls return the same promise.\n */\nexport function loadVgsCollectScript(): Promise<void> {\n if (loadPromise) {\n console.log(\"[PaymentKit] VGS Collect CDN load already in progress — reusing promise\");\n return loadPromise;\n }\n\n loadPromise = new Promise<void>((resolve, reject) => {\n if (typeof window !== \"undefined\" && window.VGSCollect) {\n console.log(\"[PaymentKit] VGS Collect JS already available on window\");\n resolve();\n return;\n }\n\n console.log(`[PaymentKit] Loading VGS Collect JS from CDN: ${VGS_COLLECT_CDN}`);\n const script = document.createElement(\"script\");\n script.src = VGS_COLLECT_CDN;\n script.async = true;\n script.onload = () => {\n console.log(\"[PaymentKit] VGS Collect JS loaded successfully from CDN\");\n resolve();\n };\n script.onerror = () => {\n console.error(`[PaymentKit] Failed to load VGS Collect JS from CDN: ${VGS_COLLECT_CDN}`);\n loadPromise = null;\n reject(new Error(\"Failed to load VGS Collect JS\"));\n };\n document.head.appendChild(script);\n });\n\n return loadPromise;\n}\n\n/**\n * Initializes a VGS Collect form instance.\n */\nexport function initVgsCollect(vaultId: string, environment: string): VGSCollectForm {\n if (!window.VGSCollect) {\n throw new Error(\"VGS Collect JS not loaded. Call loadVgsCollectScript() first.\");\n }\n return window.VGSCollect.create(vaultId, environment, () => {});\n}\n\n/**\n * Creates and mounts VGS Collect card fields using CMP-specific field methods.\n *\n * Uses cardNumberField(), cardExpirationDateField(), cardCVCField() which are\n * required for the CMP createCard() flow. After mounting, call form.createCard()\n * to create a card in the VGS vault, which returns aliases for PAN and CVC.\n *\n * @param form - VGS Collect form instance\n * @param mountPoints - CSS selectors for PAN, expiry, and CVC containers\n * @param css - Optional CSS to apply to VGS Collect iframe fields\n */\nexport function createVgsCardFields(\n form: VGSCollectForm,\n mountPoints: { pan: string; exp: string; cvc: string },\n css?: Record<string, string>,\n onFocusChangeCallbacks?: Partial<Record<string, (isFocused: boolean) => void>>,\n) {\n const fieldCss = css ?? {\n height: \"46px\",\n \"font-size\": \"1rem\",\n color: \"#18181b\",\n \"font-family\": \"ui-sans-serif, system-ui, sans-serif\",\n };\n\n const panField = form.cardNumberField(mountPoints.pan, {\n placeholder: \"Card number\",\n validations: [\"required\", \"validCardNumber\"],\n css: fieldCss,\n });\n\n const expField = form.cardExpirationDateField(mountPoints.exp, {\n placeholder: \"MM / YY\",\n validations: [\"required\", \"validCardExpirationDate\"],\n css: fieldCss,\n });\n\n const cvcField = form.cardCVCField(mountPoints.cvc, {\n placeholder: \"123\",\n validations: [\"required\", \"validCardSecurityCode\"],\n css: fieldCss,\n });\n\n if (onFocusChangeCallbacks) {\n const fieldMap: [VGSCollectField, string][] = [\n [panField, \"card_pan\"],\n [expField, \"card_exp\"],\n [cvcField, \"card_cvc\"],\n ];\n for (const [field, key] of fieldMap) {\n const cb = onFocusChangeCallbacks[key];\n if (cb) {\n field.on(\"focus\", () => cb(true));\n field.on(\"blur\", () => cb(false));\n }\n }\n }\n}\n"],"mappings":";AAAA,MAAM,kBAAkB;AAExB,IAAIA,cAAoC;;;;AAKxC,SAAgB,uBAAsC;AACpD,KAAI,aAAa;AACf,UAAQ,IAAI,0EAA0E;AACtF,SAAO;;AAGT,eAAc,IAAI,SAAe,SAAS,WAAW;AACnD,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAQ,IAAI,0DAA0D;AACtE,YAAS;AACT;;AAGF,UAAQ,IAAI,iDAAiD,kBAAkB;EAC/E,MAAM,SAAS,SAAS,cAAc,SAAS;AAC/C,SAAO,MAAM;AACb,SAAO,QAAQ;AACf,SAAO,eAAe;AACpB,WAAQ,IAAI,2DAA2D;AACvE,YAAS;;AAEX,SAAO,gBAAgB;AACrB,WAAQ,MAAM,wDAAwD,kBAAkB;AACxF,iBAAc;AACd,0BAAO,IAAI,MAAM,gCAAgC,CAAC;;AAEpD,WAAS,KAAK,YAAY,OAAO;GACjC;AAEF,QAAO;;;;;AAMT,SAAgB,eAAe,SAAiB,aAAqC;AACnF,KAAI,CAAC,OAAO,WACV,OAAM,IAAI,MAAM,gEAAgE;AAElF,QAAO,OAAO,WAAW,OAAO,SAAS,mBAAmB,GAAG;;;;;;;;;;;;;AAcjE,SAAgB,oBACd,MACA,aACA,KACA,wBACA;CACA,MAAM,WAAW,OAAO;EACtB,QAAQ;EACR,aAAa;EACb,OAAO;EACP,eAAe;EAChB;CAED,MAAM,WAAW,KAAK,gBAAgB,YAAY,KAAK;EACrD,aAAa;EACb,aAAa,CAAC,YAAY,kBAAkB;EAC5C,KAAK;EACN,CAAC;CAEF,MAAM,WAAW,KAAK,wBAAwB,YAAY,KAAK;EAC7D,aAAa;EACb,aAAa,CAAC,YAAY,0BAA0B;EACpD,KAAK;EACN,CAAC;CAEF,MAAM,WAAW,KAAK,aAAa,YAAY,KAAK;EAClD,aAAa;EACb,aAAa,CAAC,YAAY,wBAAwB;EAClD,KAAK;EACN,CAAC;AAEF,KAAI,wBAAwB;EAC1B,MAAMC,WAAwC;GAC5C,CAAC,UAAU,WAAW;GACtB,CAAC,UAAU,WAAW;GACtB,CAAC,UAAU,WAAW;GACvB;AACD,OAAK,MAAM,CAAC,OAAO,QAAQ,UAAU;GACnC,MAAM,KAAK,uBAAuB;AAClC,OAAI,IAAI;AACN,UAAM,GAAG,eAAe,GAAG,KAAK,CAAC;AACjC,UAAM,GAAG,cAAc,GAAG,MAAM,CAAC"}