UNPKG

@payos-inc/payos-js

Version:

PayOS JavaScript SDK for browser-based checkout and wallet onboarding

473 lines (470 loc) 12.8 kB
// src/checkout/CheckoutClient.ts var CheckoutClient = class { constructor(config) { this.popup = null; this.messageHandler = null; this.currentState = null; this.popupCheckInterval = null; if (config?.baseUrl) { this.defaultUrl = `${config.baseUrl}/checkout`; } else { 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; if (!token) { const error = new Error("Token is required"); onError?.(error); throw error; } const checkoutUrl = baseUrl ? `${baseUrl}/checkout` : this.defaultUrl; this.currentState = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 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()}`; switch (mode) { case "redirect": this.openRedirect(fullUrl); break; case "popup": default: this.openPopup(fullUrl, returnUrl, onReady, onComplete, onError, onCancel); break; } } /** * Close checkout */ close() { if (this.popupCheckInterval) { clearInterval(this.popupCheckInterval); this.popupCheckInterval = null; } if (this.popup) { this.popup.close(); this.popup = null; } if (this.messageHandler) { window.removeEventListener("message", this.messageHandler); this.messageHandler = null; } } openRedirect(url) { window.location.href = url; } openPopup(url, returnUrl, onReady, onComplete, onError, onCancel) { const width = 500; const height = 700; const left = (window.innerWidth - width) / 2 + window.screenLeft; const top = (window.innerHeight - height) / 2 + window.screenTop; 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; return; } onError?.(new Error("Popup blocked")); return; } this.setupMessageListener(onReady, onComplete, onError, onCancel); 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) => { 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; 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; } if (data?.source !== "payos-checkout") return; 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); setTimeout(() => this.close(), 5 * 60 * 1e3); } }; // src/wallet-onboard/WalletOnboardClient.ts var WalletOnboardClient = class { constructor(config) { this.popup = null; if (config?.baseUrl) { 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; 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; const walletUrl = baseUrl || this.defaultUrl; const state = crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}`; 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()}`; 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; return { close: () => { } }; } onError?.(new Error("Popup blocked")); return { close: () => { } }; } const messageHandler = (event) => { 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; 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; } const { type, success, walletUserId: walletUserId2 } = data; if (type === "PAYOS_LINK_CLOSE") { window.removeEventListener("message", messageHandler); if (success && onComplete) { onComplete({ walletUserId: walletUserId2, linkedCardIds: [] }); } else if (!success && onCancel) { onCancel(); } this.popup = null; } }; window.addEventListener("message", messageHandler); const timeout = setTimeout(() => { window.removeEventListener("message", messageHandler); this.popup?.close(); this.popup = null; }, 5 * 60 * 1e3); 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; const walletUrl = baseUrl || this.defaultUrl; 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()}`; window.location.href = url; return { close: () => { } }; } /** * Close the current wallet onboard instance */ close() { if (this.popup && !this.popup.closed) { this.popup.close(); this.popup = null; } } }; // src/index.ts var PayOS = class { constructor(config) { this._checkout = new CheckoutClient(config); this._walletOnboard = new 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"; } }; /** * 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 }; function initWalletOnboard(tokenOrOptions) { const walletClient = new WalletOnboardClient(); let options; if (typeof tokenOrOptions === "string") { options = { token: tokenOrOptions }; } else { options = tokenOrOptions; if (!options.token) { throw new Error("Token is required"); } } const instance = walletClient.open({ token: options.token, mode: options.mode || "popup", environment: options.environment, merchantName: options.merchantName, returnUrl: options.returnUrl, walletUserId: void 0, // This would come from elsewhere if needed onComplete: (data) => { if (options.onSuccess) { options.onSuccess(data.walletUserId); } }, onError: options.onError, onCancel: options.onClose }); return instance; } var PayOSWalletOnboardSDK = { init: initWalletOnboard }; if (typeof window !== "undefined") { window.PayOS = PayOS; window.PayOSWalletOnboard = PayOSWalletOnboardSDK; window.initPayOS = (token, options) => { const simpleOptions = { token, ...options }; return initWalletOnboard(simpleOptions); }; } var src_default = PayOS; export { CheckoutClient, PayOS, WalletOnboardClient, src_default as default };