@payos-inc/payos-js
Version:
PayOS JavaScript SDK for browser-based checkout and wallet onboarding
602 lines (581 loc) • 20.7 kB
JavaScript
(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