{"version":3,"file":"next-action-handlers-DzhzSvEB.mjs","names":[],"sources":["../src/utils/3ds-iframe-modal.ts","../src/payment-methods/next-action-handlers.ts"],"sourcesContent":["/**\n * 3DS iframe modal for displaying authentication pages.\n *\n * This module provides an iframe modal used by payment processors\n * that need to display a 3DS authentication page.\n */\n\ntype IframeModalResult = { success: true; data: Record<string, unknown> } | { success: false; error: string };\n\ntype ThreeDSCompleteMessage = {\n  type: \"3ds_complete\";\n  processor: string | null;\n  isSuccess: boolean;\n  error?: string;\n};\n\ntype IframeModalMessageHandler = (\n  event: MessageEvent<ThreeDSCompleteMessage>,\n) => { handled: true; result: IframeModalResult } | { handled: false };\n\ninterface IframeModalOptions {\n  /** URL to load in the iframe */\n  url: string;\n  /** Size of the iframe in pixels */\n  size: { width: number; height: number };\n  /** Border radius for the iframe in pixels (default: 16) */\n  radius?: number;\n  /** Handler to process postMessage events from the iframe */\n  onMessage: IframeModalMessageHandler;\n}\n\n/**\n * Shows an iframe modal and waits for a result via postMessage.\n *\n * Features:\n * - Close/cancel button positioned outside the iframe\n * - Responsive design (works on mobile)\n * - Keyboard accessibility (Escape to close, focus trap)\n * - Fade-in/fade-out animations\n * - Iframe load error handling\n *\n * @param options - Modal configuration options\n * @returns Promise that resolves with success/failure result\n *\n * @example\n * ```ts\n * const result = await show3DSIframeModal({\n *   url: \"https://bank.com/3ds-auth\",\n *   size: { width: 605, height: 500 },\n *   radius: 6,\n *   onMessage: (event) => {\n *     if (event.data?.type === \"auth_complete\") {\n *       return {\n *         handled: true,\n *         result: event.data.succeeded\n *           ? { success: true, data: { id: event.data.id } }\n *           : { success: false, error: event.data.error }\n *       };\n *     }\n *     return { handled: false };\n *   },\n * });\n * ```\n */\n\nexport default function show3DSIframeModal(options: IframeModalOptions): Promise<IframeModalResult> {\n  const { url, size, radius = 6, onMessage } = options;\n\n  return new Promise((resolve) => {\n    let resolved = false;\n\n    // Create modal overlay with fade-in animation\n    const overlay = document.createElement(\"div\");\n    overlay.id = \"pk-iframe-modal-overlay\";\n    overlay.setAttribute(\"role\", \"dialog\");\n    overlay.setAttribute(\"aria-modal\", \"true\");\n    overlay.setAttribute(\"aria-label\", \"3D Secure Verification\");\n    overlay.style.cssText = `\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0, 0, 0, 0);\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10000;\n      transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n    `;\n\n    // Create container for iframe and close button\n    const container = document.createElement(\"div\");\n    container.style.cssText = `\n      position: relative;\n      width: ${size.width}px;\n      height: ${size.height}px;\n      max-width: calc(100vw - 32px);\n      max-height: calc(100vh - 64px);\n      overflow: visible;\n      opacity: 0;\n      transform: scale(0.95) translateY(10px);\n      transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n    `;\n\n    // Create close button (positioned inside top-right of container)\n    // Uses !important to prevent merchant CSS from hiding/repositioning the button\n    const closeButton = document.createElement(\"button\");\n    closeButton.id = \"pk-iframe-modal-close\";\n    closeButton.setAttribute(\"aria-label\", \"Cancel verification\");\n    closeButton.setAttribute(\"type\", \"button\");\n    closeButton.style.cssText = `\n      position: absolute !important;\n      top: 8px !important;\n      right: 8px !important;\n      width: 32px !important;\n      height: 32px !important;\n      border: none !important;\n      background: white !important;\n      border-radius: 8px !important;\n      cursor: pointer !important;\n      display: flex !important;\n      align-items: center !important;\n      justify-content: center !important;\n      z-index: 10 !important;\n      transition: background 0.15s ease !important;\n      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n    `;\n    closeButton.innerHTML = `\n      <svg width=\"12\" height=\"12\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <path d=\"M1 1L13 13M1 13L13 1\" stroke=\"#666\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n      </svg>\n    `;\n    closeButton.onmouseenter = (): void => {\n      closeButton.style.setProperty(\"background\", \"#f5f5f5\", \"important\");\n    };\n    closeButton.onmouseleave = (): void => {\n      closeButton.style.setProperty(\"background\", \"white\", \"important\");\n    };\n\n    // Create iframe (hidden initially until loaded)\n    const iframe = document.createElement(\"iframe\");\n    iframe.src = url;\n    iframe.setAttribute(\"title\", \"3D Secure Verification\");\n    iframe.style.cssText = `\n      width: 100%;\n      height: 100%;\n      border: none;\n      opacity: 0;\n      border-radius: ${radius}px;\n      background: white;\n      transition: opacity 0.2s ease;\n      position: relative;\n      z-index: 1;\n    `;\n\n    // Handle iframe load success\n    iframe.onload = (): void => {\n      iframe.style.opacity = \"1\";\n    };\n\n    // Handle iframe load error\n    iframe.onerror = (): void => {\n      if (!resolved) {\n        console.error(\"[IframeModal] Failed to load iframe\");\n        cleanupWithAnimation();\n        resolve({ success: false, error: \"Failed to load verification page. Please try again.\" });\n      }\n    };\n\n    container.appendChild(closeButton);\n    container.appendChild(iframe);\n    overlay.appendChild(container);\n    document.body.appendChild(overlay);\n\n    // Trigger fade-in animation\n    requestAnimationFrame(() => {\n      overlay.style.background = \"rgba(0, 0, 0, 0.5)\";\n      container.style.opacity = \"1\";\n      container.style.transform = \"scale(1) translateY(0)\";\n    });\n\n    // Cleanup with fade-out animation\n    const cleanupWithAnimation = (): void => {\n      if (resolved) return;\n      resolved = true;\n\n      window.removeEventListener(\"message\", handleMessage);\n      window.removeEventListener(\"keydown\", handleKeydown);\n\n      // Use requestAnimationFrame to ensure transition is applied\n      requestAnimationFrame(() => {\n        overlay.style.background = \"rgba(0, 0, 0, 0)\";\n        container.style.opacity = \"0\";\n        container.style.transform = \"scale(0.95) translateY(10px)\";\n\n        setTimeout(() => {\n          overlay.remove();\n        }, 250);\n      });\n    };\n\n    // Handle close button click\n    const handleCancel = (): void => {\n      if (!resolved) {\n        console.log(\"[IframeModal] User cancelled\");\n        cleanupWithAnimation();\n        resolve({ success: false, error: \"Verification cancelled\" });\n      }\n    };\n\n    closeButton.onclick = handleCancel;\n\n    // Handle Escape key\n    const handleKeydown = (event: KeyboardEvent): void => {\n      if (event.key === \"Escape\") {\n        handleCancel();\n      }\n      // Trap focus within modal (Tab key cycles through close button and iframe)\n      if (event.key === \"Tab\") {\n        event.preventDefault();\n        if (document.activeElement === closeButton) {\n          iframe.focus();\n        } else {\n          closeButton.focus();\n        }\n      }\n    };\n\n    window.addEventListener(\"keydown\", handleKeydown);\n\n    // Set initial focus to close button for accessibility\n    closeButton.focus();\n\n    const handleMessage = (event: MessageEvent): void => {\n      const messageResult = onMessage(event);\n      if (messageResult.handled) {\n        cleanupWithAnimation();\n        resolve(messageResult.result);\n      }\n    };\n\n    window.addEventListener(\"message\", handleMessage);\n  });\n}\n","/**\n * Next action handlers for processor-specific user actions (e.g., 3DS authentication).\n *\n * This module abstracts processor-specific logic away from the generic card payment flow.\n * Each handler implements the logic needed for a specific next action type.\n */\n\nimport type { PublicCardCheckoutResponse } from \"@pkg/sdk/models\";\nimport type { Stripe } from \"@stripe/stripe-js\";\nimport show3DSIframeModal from \"../utils/3ds-iframe-modal\";\n\n// Helper to wait for a condition\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n// Check if Stripe.js is loaded via script tag\nconst isStripeJsPresent = (): boolean => \"Stripe\" in window;\n\n/**\n * Get the Stripe instance from the global window object.\n * Expects Stripe.js to be loaded via HTML script tag.\n * Polls for up to 5 seconds waiting for Stripe to be available.\n */\nconst getLoadedStripe = async (publishableKey: string): Promise<Stripe> => {\n  // Poll for Stripe.js to be loaded (up to 5 seconds)\n  for (let i = 0; i < 10; i++) {\n    if (isStripeJsPresent()) {\n      break;\n    }\n    await sleep(500);\n  }\n\n  if (!isStripeJsPresent()) {\n    throw new Error(\n      \"Stripe.js not loaded. Add this script tag to your HTML <head>:\\n\" +\n        '<script src=\"https://js.stripe.com/v3/\"></script>',\n    );\n  }\n\n  // @ts-expect-error Stripe is loaded globally via script tag\n  const stripe: Stripe = new window.Stripe(publishableKey);\n  return stripe;\n};\n\n/**\n * Result of handling a next action.\n */\nexport type NextActionResult = { success: true } | { success: false; error: string };\n\n/**\n * Handle Stripe 3DS authentication.\n */\nconst handleStripe3ds = async (\n  nextAction: Extract<PublicCardCheckoutResponse[\"nextAction\"], { type: \"stripe_3ds\" }>,\n): Promise<NextActionResult> => {\n  const { clientSecret, stripePk } = nextAction;\n\n  const stripe = await getLoadedStripe(stripePk);\n\n  // Show 3DS modal\n  const { error: stripeError } = await stripe.confirmCardPayment(clientSecret);\n\n  if (stripeError) {\n    console.error(\"[3DS:Stripe] Authentication failed:\", stripeError.message);\n    return { success: false, error: stripeError.message || \"3DS authentication failed\" };\n  }\n\n  return { success: true };\n};\n\n/**\n * Handle Airwallex 3DS authentication via iframe.\n *\n * Renders an iframe with the 3DS authentication URL. The iframe will redirect\n * to the 3DS callback page which sends a postMessage back to complete the flow.\n */\nconst handleAirwallex3ds = async (\n  nextAction: Extract<PublicCardCheckoutResponse[\"nextAction\"], { type: \"airwallex_3ds\" }>,\n): Promise<NextActionResult> => {\n  return show3DSIframeModal({\n    url: nextAction.url,\n    size: { width: 605, height: 550 },\n    onMessage: (event) => {\n      if (event.data.type !== \"3ds_complete\") {\n        return { handled: false };\n      }\n\n      const { isSuccess, error } = event.data;\n\n      if (isSuccess) {\n        return { handled: true, result: { success: true, data: {} } };\n      } else {\n        console.error(\"[3DS:Airwallex] Authentication failed:\", error);\n        return {\n          handled: true,\n          result: { success: false, error: error || \"3DS authentication failed\" },\n        };\n      }\n    },\n  });\n};\n\n/**\n * Handle a next action based on its type.\n * Routes to the appropriate processor-specific handler.\n *\n * @param nextAction - The next action from the checkout response\n * @returns Result indicating success or failure with error message\n */\nexport const handleNextAction = async (\n  nextAction: NonNullable<PublicCardCheckoutResponse[\"nextAction\"]>,\n): Promise<NextActionResult> => {\n  switch (nextAction.type) {\n    case \"stripe_3ds\":\n      return handleStripe3ds(nextAction);\n    case \"airwallex_3ds\":\n      return handleAirwallex3ds(nextAction);\n    default:\n      // TypeScript will catch if we miss a case when new action types are added\n      return { success: false, error: `Unknown next action type: ${(nextAction as { type: string }).type}` };\n  }\n};\n"],"mappings":"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"}