UNPKG

@payos-inc/payos-js

Version:

PayOS JavaScript SDK for browser-based checkout and wallet onboarding

602 lines (581 loc) 20.7 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["PayOS"] = factory(); else root["PayOS"] = factory(); })(this, () => { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 149: /***/ ((__unused_webpack_module, exports) => { /** * WalletOnboardClient - Instance-based client for PayOS Wallet Onboard * Provides a consistent API similar to CheckoutClient */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.WalletOnboardClient = void 0; class WalletOnboardClient { constructor(config) { this.popup = null; if (config?.baseUrl) { // Wallet uses root path this.defaultUrl = config.baseUrl; } else { this.defaultUrl = 'https://wallet-onboard.payos.ai'; } this.defaultParams = config?.defaultParams; } /** * Open wallet onboard with specified options * Supports popup and redirect modes */ open(options) { const { mode = 'popup' } = options; // Clean up any existing popup this.close(); switch (mode) { case 'popup': return this.openPopup(options); case 'redirect': return this.openRedirect(options); default: throw new Error(`Invalid mode: ${mode}`); } } /** * Open wallet onboard in popup mode */ openPopup(options) { const { token, baseUrl, customParams, environment = 'sandbox', returnUrl, merchantName, walletUserId, onComplete, onError, onCancel } = options; // Use provided baseUrl or default const walletUrl = baseUrl || this.defaultUrl; // Generate state for CSRF protection const state = crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}`; // Build URL with all params const params = new URLSearchParams({ token, env: environment, state, ...this.defaultParams, // Global testing params ...customParams // Per-request params }); if (merchantName) { params.set('merchantName', merchantName); } if (walletUserId) { params.set('walletUserId', walletUserId); } const url = `${walletUrl}?${params.toString()}`; // Open popup window this.popup = window.open(url, 'payos-wallet-onboard', 'width=500,height=700,left=100,top=100'); if (!this.popup || this.popup.closed) { if (returnUrl) { window.location.href = url; // Simple fallback return { close: () => { } }; } onError?.(new Error('Popup blocked')); return { close: () => { } }; } // Listen for messages from popup const messageHandler = (event) => { // Strict origin check const allowedOrigins = [ 'https://payos.app', 'https://wallet-onboard.payos.ai', 'https://staging.wallet-onboard.payos.ai', 'https://purple-grass-0eb0a1b1e.1.azurestaticapps.net', 'http://localhost:3000', 'http://localhost:3001', 'http://localhost:5173' // Vite dev server ]; if (!allowedOrigins.some(origin => event.origin === origin)) return; const data = event.data; // New format if (data?.status) { if (data.state && data.state !== state) return; if (data.status === 'success') { onComplete?.({ walletUserId: data.data?.walletUserId, linkedCardIds: data.data?.linkedCardIds || [] }); } else if (data.status === 'cancel') { onCancel?.(); } else if (data.status === 'error') { onError?.(new Error(data.error || 'Unknown error')); } window.removeEventListener('message', messageHandler); this.popup?.close(); this.popup = null; return; } // Old format (keep existing) const { type, success, walletUserId } = data; if (type === 'PAYOS_LINK_CLOSE') { window.removeEventListener('message', messageHandler); if (success && onComplete) { onComplete({ walletUserId, linkedCardIds: [] }); } else if (!success && onCancel) { onCancel(); } this.popup = null; } }; window.addEventListener('message', messageHandler); // Simple timeout - 5 minutes const timeout = setTimeout(() => { window.removeEventListener('message', messageHandler); this.popup?.close(); this.popup = null; }, 5 * 60 * 1000); // Check if popup was closed const checkClosed = setInterval(() => { if (this.popup && this.popup.closed) { clearInterval(checkClosed); clearTimeout(timeout); window.removeEventListener('message', messageHandler); if (onCancel) { onCancel(); } this.popup = null; } }, 500); return { close: () => { if (this.popup && !this.popup.closed) { this.popup.close(); this.popup = null; } } }; } /** * Open wallet onboard in redirect mode */ openRedirect(options) { const { token, baseUrl, customParams, environment = 'sandbox', merchantName, walletUserId, returnUrl = window.location.href } = options; // Use provided baseUrl or default const walletUrl = baseUrl || this.defaultUrl; // Build URL with all params const params = new URLSearchParams({ token, env: environment, returnUrl, ...this.defaultParams, // Global testing params ...customParams // Per-request params }); if (merchantName) { params.set('merchantName', merchantName); } if (walletUserId) { params.set('walletUserId', walletUserId); } const url = `${walletUrl}?${params.toString()}`; // Redirect to PayOS Link window.location.href = url; return { close: () => { // Can't close in redirect mode } }; } /** * Close the current wallet onboard instance */ close() { if (this.popup && !this.popup.closed) { this.popup.close(); this.popup = null; } } } exports.WalletOnboardClient = WalletOnboardClient; /***/ }), /***/ 156: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { /** * PayOS.js - Client-side JavaScript SDK for PayOS * Combines checkout and wallet onboarding functionality */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.WalletOnboardClient = exports.CheckoutClient = exports.PayOS = void 0; const CheckoutClient_1 = __webpack_require__(566); Object.defineProperty(exports, "CheckoutClient", ({ enumerable: true, get: function () { return CheckoutClient_1.CheckoutClient; } })); const WalletOnboardClient_1 = __webpack_require__(149); Object.defineProperty(exports, "WalletOnboardClient", ({ enumerable: true, get: function () { return WalletOnboardClient_1.WalletOnboardClient; } })); __exportStar(__webpack_require__(613), exports); /** * Main PayOS.js class * Provides checkout and wallet onboarding functionality */ class PayOS { constructor(config) { // Initialize clients with optional config this._checkout = new CheckoutClient_1.CheckoutClient(config); this._walletOnboard = new WalletOnboardClient_1.WalletOnboardClient(config); } /** * Checkout module for payment authentication * Opens hosted checkout UI with a token from your backend * * @example * ```javascript * // Get token from your backend * const { token } = await fetch('/api/create-checkout-token', {...}); * * // Open checkout * payos.checkout.open({ * token: token, * mode: 'iframe', * onComplete: (result) => console.log('Payment complete', result) * }); * ``` */ get checkout() { return this._checkout; } /** * Wallet Onboard module for adding payment methods * Opens hosted wallet onboard UI with a token from your backend * * @example * ```javascript * // Get token from your backend * const { token } = await fetch('/api/create-onboard-token', {...}); * * // Open wallet onboard * payos.walletOnboard.open({ * token: token, * mode: 'iframe', * onComplete: (result) => console.log('Card added', result) * }); * ``` */ get walletOnboard() { return this._walletOnboard; } /** * Static method to check if PayOS.js is loaded */ static isLoaded() { return true; } /** * Version of the SDK */ static get version() { return '1.0.0'; } } exports.PayOS = PayOS; /** * Initialize PayOS Wallet Onboard * * @example * ```javascript * // Simple: init with just token * payos.walletOnboard.init('token'); * * // With options * payos.walletOnboard.init({ token: 'token', mode: 'popup' }); * ``` */ PayOS.walletOnboard = { init: initWalletOnboard }; /** * Initialize PayOS Wallet Onboard using popup or redirect mode * Supports multiple usage patterns: * * 1. Simple: init('token') * 2. With options: init({ token: 'token', ...options }) */ function initWalletOnboard(tokenOrOptions) { const walletClient = new WalletOnboardClient_1.WalletOnboardClient(); let options; // Case 1: init('token') if (typeof tokenOrOptions === "string") { options = { token: tokenOrOptions }; } // Case 2: init({ token: 'token', ...options }) else { options = tokenOrOptions; if (!options.token) { throw new Error("Token is required"); } } // Open wallet onboard with the specified or default mode const instance = walletClient.open({ token: options.token, mode: options.mode || 'popup', environment: options.environment, merchantName: options.merchantName, returnUrl: options.returnUrl, walletUserId: undefined, // This would come from elsewhere if needed onComplete: (data) => { if (options.onSuccess) { options.onSuccess(data.walletUserId); } }, onError: options.onError, onCancel: options.onClose }); return instance; } // Add backwards compatibility for wallet onboard const PayOSWalletOnboardSDK = { init: initWalletOnboard }; // For direct script inclusion, expose as global if (typeof window !== "undefined") { window.PayOS = PayOS; window.PayOSWalletOnboard = PayOSWalletOnboardSDK; window.initPayOS = (token, options) => { const simpleOptions = { token, ...options }; return initWalletOnboard(simpleOptions); }; } // Default export exports["default"] = PayOS; /***/ }), /***/ 566: /***/ ((__unused_webpack_module, exports) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.CheckoutClient = void 0; class CheckoutClient { constructor(config) { this.popup = null; this.messageHandler = null; this.currentState = null; this.popupCheckInterval = null; if (config?.baseUrl) { // Add /checkout path to the base URL this.defaultUrl = `${config.baseUrl}/checkout`; } else { // Default to production wallet-onboard URL for checkout this.defaultUrl = 'https://wallet-onboard.payos.ai/checkout'; } this.defaultParams = config?.defaultParams; } /** * Open PayOS Checkout with a token */ open(options) { const { token, mode = 'popup', environment = 'sandbox', returnUrl, baseUrl, customParams, onReady, onComplete, onError, onCancel } = options; // Validate token if (!token) { const error = new Error('Token is required'); onError?.(error); throw error; } // Determine the base URL to use const checkoutUrl = baseUrl ? `${baseUrl}/checkout` // Add /checkout if custom baseUrl provided : this.defaultUrl; // Generate state for CSRF protection this.currentState = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); // Build checkout URL with token, state, environment, and custom params const params = new URLSearchParams({ token, mode, env: environment, state: this.currentState, ...this.defaultParams, // Global params from config ...customParams // Per-request params }); if (returnUrl) { params.set('returnUrl', returnUrl); } const fullUrl = `${checkoutUrl}?${params.toString()}`; // Open checkout based on mode switch (mode) { case 'redirect': this.openRedirect(fullUrl); break; case 'popup': default: this.openPopup(fullUrl, returnUrl, onReady, onComplete, onError, onCancel); break; } } /** * Close checkout */ close() { // Clean up popup check interval if (this.popupCheckInterval) { clearInterval(this.popupCheckInterval); this.popupCheckInterval = null; } // Clean up popup if (this.popup) { this.popup.close(); this.popup = null; } // Clean up message listener if (this.messageHandler) { window.removeEventListener('message', this.messageHandler); this.messageHandler = null; } } openRedirect(url) { window.location.href = url; } openPopup(url, returnUrl, onReady, onComplete, onError, onCancel) { // Calculate popup position const width = 500; const height = 700; const left = (window.innerWidth - width) / 2 + window.screenLeft; const top = (window.innerHeight - height) / 2 + window.screenTop; // Open popup this.popup = window.open(url, 'payos-checkout', `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`); if (!this.popup || this.popup.closed) { if (returnUrl) { window.location.href = url; // Simple fallback return; } onError?.(new Error('Popup blocked')); return; } // Listen for messages this.setupMessageListener(onReady, onComplete, onError, onCancel); // Check if popup is closed this.popupCheckInterval = setInterval(() => { if (this.popup?.closed) { if (this.popupCheckInterval) { clearInterval(this.popupCheckInterval); this.popupCheckInterval = null; } onCancel?.(); this.close(); } }, 500); } setupMessageListener(onReady, onComplete, onError, onCancel) { this.messageHandler = (event) => { // Strict origin check const allowedOrigins = [ 'https://payos.app', 'https://checkout.payos.ai', 'https://calm-moss-06c4a301e.2.azurestaticapps.net', 'http://localhost:3000', 'http://localhost:3001', 'http://localhost:5173' // Vite dev server ]; if (!allowedOrigins.some(origin => event.origin === origin)) return; const data = event.data; // Handle new format first (simpler, cleaner) if (data?.status) { if (data.state && data.state !== this.currentState) return; if (data.status === 'success') onComplete?.(data.data || {}); else if (data.status === 'cancel') onCancel?.(); else if (data.status === 'error') onError?.(new Error(data.error || 'Unknown error')); this.close(); return; } // Keep old format support (unchanged) if (data?.source !== 'payos-checkout') return; // Handle old message types switch (data.type) { case 'CHECKOUT_READY': onReady?.(); break; case 'CHECKOUT_COMPLETE': onComplete?.(data.payload); this.close(); break; case 'CHECKOUT_ERROR': onError?.(new Error(data.error || 'Checkout error')); this.close(); break; case 'CHECKOUT_CANCELLED': onCancel?.(); this.close(); break; } }; window.addEventListener('message', this.messageHandler); // Simple timeout - 5 minutes setTimeout(() => this.close(), 5 * 60 * 1000); } } exports.CheckoutClient = CheckoutClient; /***/ }), /***/ 613: /***/ ((__unused_webpack_module, exports) => { /** * PayOS.js Type Definitions * Client-side only types for browser SDK */ Object.defineProperty(exports, "__esModule", ({ value: true })); /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module is referenced by other modules so it can't be inlined /******/ var __webpack_exports__ = __webpack_require__(156); /******/ __webpack_exports__ = __webpack_exports__["default"]; /******/ /******/ return __webpack_exports__; /******/ })() ; }); //# sourceMappingURL=payos-js.umd.js.map