@youidian/pay-sdk 1.0.5 → 1.0.6
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/client.cjs +10 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -1
- package/dist/client.d.ts +6 -1
- package/dist/client.js +10 -2
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +48 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +48 -21
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +38 -19
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +58 -6
- package/dist/server.d.ts +58 -6
- package/dist/server.js +38 -19
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/client.cjs
CHANGED
|
@@ -44,8 +44,16 @@ var PaymentUI = class {
|
|
|
44
44
|
if (typeof urlOrParams === "string") {
|
|
45
45
|
checkoutUrl = urlOrParams;
|
|
46
46
|
} else {
|
|
47
|
-
const {
|
|
48
|
-
|
|
47
|
+
const {
|
|
48
|
+
appId,
|
|
49
|
+
productId,
|
|
50
|
+
priceId,
|
|
51
|
+
productCode,
|
|
52
|
+
userId,
|
|
53
|
+
checkoutUrl: checkoutUrlParam,
|
|
54
|
+
baseUrl = "https://pay.imgto.link"
|
|
55
|
+
} = urlOrParams;
|
|
56
|
+
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
49
57
|
if (productCode) {
|
|
50
58
|
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
51
59
|
} else if (productId && priceId) {
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n /** Defaults to https://pay.imgto.link */\n baseUrl?: string;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param urlOrParams - The checkout page URL or payment parameters\n * @param options - UI options\n */\n openPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let checkoutUrl: string;\n if (typeof urlOrParams === 'string') {\n checkoutUrl = urlOrParams;\n } else {\n const { appId, productId, priceId, productCode, userId, baseUrl = \"https://pay.imgto.link\" } = urlOrParams;\n const base = baseUrl.replace(/\\/$/, '');\n\n if (productCode) {\n // Use product code route\n checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;\n } else if (productId && priceId) {\n // Use ID-based route\n checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;\n } else {\n throw new Error('Either productCode or both productId and priceId are required');\n }\n }\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n // Check if path already starts with /locale/ or is exactly /locale\n const localePrefix = `/${options.locale}`;\n if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {\n url.pathname = `${localePrefix}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n const localePrefix = `/${options.locale}`;\n if (!checkoutUrl.startsWith(`${localePrefix}/`) && checkoutUrl !== localePrefix) {\n finalUrl = `${localePrefix}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n\n /**\n * @deprecated Use checkoutUrl instead\n * Defaults to https://pay.imgto.link\n */\n baseUrl?: string;\n\n /** Checkout page URL (e.g. https://pay.youidian.com) */\n checkoutUrl?: string;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param urlOrParams - The checkout page URL or payment parameters\n * @param options - UI options\n */\n openPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let checkoutUrl: string;\n if (typeof urlOrParams === 'string') {\n checkoutUrl = urlOrParams;\n } else {\n const {\n appId,\n productId,\n priceId,\n productCode,\n userId,\n checkoutUrl: checkoutUrlParam,\n baseUrl = \"https://pay.imgto.link\"\n } = urlOrParams;\n\n // 优先使用 checkoutUrl,否则使用 baseUrl\n const base = (checkoutUrlParam || baseUrl).replace(/\\/$/, '');\n\n if (productCode) {\n // Use product code route\n checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;\n } else if (productId && priceId) {\n // Use ID-based route\n checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;\n } else {\n throw new Error('Either productCode or both productId and priceId are required');\n }\n }\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n // Check if path already starts with /locale/ or is exactly /locale\n const localePrefix = `/${options.locale}`;\n if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {\n url.pathname = `${localePrefix}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n const localePrefix = `/${options.locale}`;\n if (!checkoutUrl.startsWith(`${localePrefix}/`) && checkoutUrl !== localePrefix) {\n finalUrl = `${localePrefix}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":"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","names":[]}
|
package/dist/client.d.cts
CHANGED
|
@@ -62,8 +62,13 @@ interface PaymentParams {
|
|
|
62
62
|
priceId?: string;
|
|
63
63
|
/** Product code - use with locale for code-based payment (alternative to productId/priceId) */
|
|
64
64
|
productCode?: string;
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Use checkoutUrl instead
|
|
67
|
+
* Defaults to https://pay.imgto.link
|
|
68
|
+
*/
|
|
66
69
|
baseUrl?: string;
|
|
70
|
+
/** Checkout page URL (e.g. https://pay.youidian.com) */
|
|
71
|
+
checkoutUrl?: string;
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
69
74
|
* Client-side Payment UI Helper
|
package/dist/client.d.ts
CHANGED
|
@@ -62,8 +62,13 @@ interface PaymentParams {
|
|
|
62
62
|
priceId?: string;
|
|
63
63
|
/** Product code - use with locale for code-based payment (alternative to productId/priceId) */
|
|
64
64
|
productCode?: string;
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Use checkoutUrl instead
|
|
67
|
+
* Defaults to https://pay.imgto.link
|
|
68
|
+
*/
|
|
66
69
|
baseUrl?: string;
|
|
70
|
+
/** Checkout page URL (e.g. https://pay.youidian.com) */
|
|
71
|
+
checkoutUrl?: string;
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
69
74
|
* Client-side Payment UI Helper
|
package/dist/client.js
CHANGED
|
@@ -21,8 +21,16 @@ var PaymentUI = class {
|
|
|
21
21
|
if (typeof urlOrParams === "string") {
|
|
22
22
|
checkoutUrl = urlOrParams;
|
|
23
23
|
} else {
|
|
24
|
-
const {
|
|
25
|
-
|
|
24
|
+
const {
|
|
25
|
+
appId,
|
|
26
|
+
productId,
|
|
27
|
+
priceId,
|
|
28
|
+
productCode,
|
|
29
|
+
userId,
|
|
30
|
+
checkoutUrl: checkoutUrlParam,
|
|
31
|
+
baseUrl = "https://pay.imgto.link"
|
|
32
|
+
} = urlOrParams;
|
|
33
|
+
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
26
34
|
if (productCode) {
|
|
27
35
|
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
28
36
|
} else if (productId && priceId) {
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n /** Defaults to https://pay.imgto.link */\n baseUrl?: string;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param urlOrParams - The checkout page URL or payment parameters\n * @param options - UI options\n */\n openPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let checkoutUrl: string;\n if (typeof urlOrParams === 'string') {\n checkoutUrl = urlOrParams;\n } else {\n const { appId, productId, priceId, productCode, userId, baseUrl = \"https://pay.imgto.link\" } = urlOrParams;\n const base = baseUrl.replace(/\\/$/, '');\n\n if (productCode) {\n // Use product code route\n checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;\n } else if (productId && priceId) {\n // Use ID-based route\n checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;\n } else {\n throw new Error('Either productCode or both productId and priceId are required');\n }\n }\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n // Check if path already starts with /locale/ or is exactly /locale\n const localePrefix = `/${options.locale}`;\n if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {\n url.pathname = `${localePrefix}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n const localePrefix = `/${options.locale}`;\n if (!checkoutUrl.startsWith(`${localePrefix}/`) && checkoutUrl !== localePrefix) {\n finalUrl = `${localePrefix}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n\n /**\n * @deprecated Use checkoutUrl instead\n * Defaults to https://pay.imgto.link\n */\n baseUrl?: string;\n\n /** Checkout page URL (e.g. https://pay.youidian.com) */\n checkoutUrl?: string;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param urlOrParams - The checkout page URL or payment parameters\n * @param options - UI options\n */\n openPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let checkoutUrl: string;\n if (typeof urlOrParams === 'string') {\n checkoutUrl = urlOrParams;\n } else {\n const {\n appId,\n productId,\n priceId,\n productCode,\n userId,\n checkoutUrl: checkoutUrlParam,\n baseUrl = \"https://pay.imgto.link\"\n } = urlOrParams;\n\n // 优先使用 checkoutUrl,否则使用 baseUrl\n const base = (checkoutUrlParam || baseUrl).replace(/\\/$/, '');\n\n if (productCode) {\n // Use product code route\n checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;\n } else if (productId && priceId) {\n // Use ID-based route\n checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;\n } else {\n throw new Error('Either productCode or both productId and priceId are required');\n }\n }\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n // Check if path already starts with /locale/ or is exactly /locale\n const localePrefix = `/${options.locale}`;\n if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {\n url.pathname = `${localePrefix}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n const localePrefix = `/${options.locale}`;\n if (!checkoutUrl.startsWith(`${localePrefix}/`) && checkoutUrl !== localePrefix) {\n finalUrl = `${localePrefix}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -57,8 +57,16 @@ var PaymentUI = class {
|
|
|
57
57
|
if (typeof urlOrParams === "string") {
|
|
58
58
|
checkoutUrl = urlOrParams;
|
|
59
59
|
} else {
|
|
60
|
-
const {
|
|
61
|
-
|
|
60
|
+
const {
|
|
61
|
+
appId,
|
|
62
|
+
productId,
|
|
63
|
+
priceId,
|
|
64
|
+
productCode,
|
|
65
|
+
userId,
|
|
66
|
+
checkoutUrl: checkoutUrlParam,
|
|
67
|
+
baseUrl = "https://pay.imgto.link"
|
|
68
|
+
} = urlOrParams;
|
|
69
|
+
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
62
70
|
if (productCode) {
|
|
63
71
|
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
64
72
|
} else if (productId && priceId) {
|
|
@@ -234,16 +242,30 @@ function createPaymentUI() {
|
|
|
234
242
|
// src/server.ts
|
|
235
243
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
236
244
|
var PaymentClient = class {
|
|
245
|
+
// 用于生成 checkout URL
|
|
237
246
|
constructor(options) {
|
|
238
247
|
__publicField(this, "appId");
|
|
239
248
|
__publicField(this, "appSecret");
|
|
240
|
-
__publicField(this, "
|
|
249
|
+
__publicField(this, "apiUrl");
|
|
250
|
+
// 用于 API 调用
|
|
251
|
+
__publicField(this, "checkoutUrl");
|
|
241
252
|
if (!options.appId) throw new Error("appId is required");
|
|
242
253
|
if (!options.appSecret) throw new Error("appSecret is required");
|
|
243
|
-
if (!options.
|
|
254
|
+
if (!options.apiUrl && !options.checkoutUrl && !options.baseUrl) {
|
|
255
|
+
throw new Error("At least one of baseUrl, apiUrl, or checkoutUrl must be provided");
|
|
256
|
+
}
|
|
244
257
|
this.appId = options.appId;
|
|
245
258
|
this.appSecret = options.appSecret;
|
|
246
|
-
|
|
259
|
+
const apiUrl = options.apiUrl || options.baseUrl;
|
|
260
|
+
if (!apiUrl) {
|
|
261
|
+
throw new Error("apiUrl or baseUrl is required for API operations");
|
|
262
|
+
}
|
|
263
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
264
|
+
const checkoutUrl = options.checkoutUrl || options.baseUrl;
|
|
265
|
+
if (!checkoutUrl) {
|
|
266
|
+
throw new Error("checkoutUrl or baseUrl is required for checkout operations");
|
|
267
|
+
}
|
|
268
|
+
this.checkoutUrl = checkoutUrl.replace(/\/$/, "");
|
|
247
269
|
}
|
|
248
270
|
/**
|
|
249
271
|
* Generate SHA256 signature for the request
|
|
@@ -259,7 +281,7 @@ var PaymentClient = class {
|
|
|
259
281
|
async request(method, path, body) {
|
|
260
282
|
const timestamp = Date.now();
|
|
261
283
|
const signature = this.generateSignature(timestamp);
|
|
262
|
-
const url = `${this.
|
|
284
|
+
const url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`;
|
|
263
285
|
const headers = {
|
|
264
286
|
"Content-Type": "application/json",
|
|
265
287
|
"X-Pay-Timestamp": timestamp.toString(),
|
|
@@ -308,22 +330,11 @@ var PaymentClient = class {
|
|
|
308
330
|
*/
|
|
309
331
|
async getProducts(options) {
|
|
310
332
|
const params = new URLSearchParams();
|
|
311
|
-
params.append("appId", this.appId);
|
|
312
333
|
if (options?.locale) params.append("locale", options.locale);
|
|
313
334
|
if (options?.currency) params.append("currency", options.currency);
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
headers: {
|
|
318
|
-
"Content-Type": "application/json",
|
|
319
|
-
"Accept": "application/json"
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
if (!res.ok) {
|
|
323
|
-
throw new Error(`Failed to fetch products: ${res.status} ${res.statusText}`);
|
|
324
|
-
}
|
|
325
|
-
const json = await res.json();
|
|
326
|
-
return json.data?.products || [];
|
|
335
|
+
const path = params.toString() ? `/products?${params.toString()}` : "/products";
|
|
336
|
+
const response = await this.request("GET", path);
|
|
337
|
+
return response.products || [];
|
|
327
338
|
}
|
|
328
339
|
/**
|
|
329
340
|
* Create a new order
|
|
@@ -355,6 +366,22 @@ var PaymentClient = class {
|
|
|
355
366
|
async getOrderDetails(orderId) {
|
|
356
367
|
return this.request("GET", `/orders/${orderId}/details`);
|
|
357
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Get orders list with pagination
|
|
371
|
+
* @param params - Query parameters (pagination, filters)
|
|
372
|
+
* @returns Orders list and pagination info
|
|
373
|
+
*/
|
|
374
|
+
async getOrders(params) {
|
|
375
|
+
const queryParams = new URLSearchParams();
|
|
376
|
+
if (params?.page) queryParams.append("page", params.page.toString());
|
|
377
|
+
if (params?.pageSize) queryParams.append("pageSize", params.pageSize.toString());
|
|
378
|
+
if (params?.userId) queryParams.append("userId", params.userId);
|
|
379
|
+
if (params?.status) queryParams.append("status", params.status);
|
|
380
|
+
if (params?.startDate) queryParams.append("startDate", params.startDate);
|
|
381
|
+
if (params?.endDate) queryParams.append("endDate", params.endDate);
|
|
382
|
+
const path = queryParams.toString() ? `/orders?${queryParams.toString()}` : "/orders";
|
|
383
|
+
return this.request("GET", path);
|
|
384
|
+
}
|
|
358
385
|
/**
|
|
359
386
|
* Get all entitlements for a user
|
|
360
387
|
* @param userId - User ID
|
|
@@ -405,7 +432,7 @@ var PaymentClient = class {
|
|
|
405
432
|
* @returns Checkout page URL
|
|
406
433
|
*/
|
|
407
434
|
getCheckoutUrl(productId, priceId) {
|
|
408
|
-
return `${this.
|
|
435
|
+
return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;
|
|
409
436
|
}
|
|
410
437
|
};
|
|
411
438
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK\n * 统一支付集成 SDK\n * \n * @packageDocumentation\n * \n * @example\n * // 服务端使用\n * import { PaymentClient } from '@youidian/pay-sdk/server';\n * \n * const client = new PaymentClient({\n * appId: 'your-app-id',\n * appSecret: 'your-app-secret',\n * baseUrl: 'https://pay.youidian.com'\n * });\n * \n * // 创建订单\n * const order = await client.createOrder({\n * productId: 'xxx',\n * priceId: 'xxx',\n * userId: 'user-123'\n * });\n * \n * @example\n * // 客户端使用\n * import { PaymentUI } from '@youidian/pay-sdk/client';\n * \n * const ui = new PaymentUI();\n * ui.openPayment('https://pay.youidian.com/checkout/...', {\n * onSuccess: (orderId) => console.log('支付成功', orderId),\n * onCancel: () => console.log('支付取消')\n * });\n */\n\n// Re-export client module (browser-safe)\nexport { PaymentUI, createPaymentUI } from './client';\nexport type {\n PaymentEventType,\n PaymentEventData,\n PaymentUIOptions,\n PollOptions,\n OrderStatus as ClientOrderStatus\n} from './client';\n\n// Re-export server module (Node.js only)\nexport { PaymentClient } from './server';\nexport type {\n PaymentClientOptions,\n CreateOrderParams,\n CreateOrderResponse,\n PaymentNotification,\n PaymentCallbackData,\n Product,\n ProductPrice,\n ProductEntitlements,\n OrderStatus,\n OrderDetails\n} from './server';\n","/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n /** Defaults to https://pay.imgto.link */\n baseUrl?: string;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param urlOrParams - The checkout page URL or payment parameters\n * @param options - UI options\n */\n openPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let checkoutUrl: string;\n if (typeof urlOrParams === 'string') {\n checkoutUrl = urlOrParams;\n } else {\n const { appId, productId, priceId, productCode, userId, baseUrl = \"https://pay.imgto.link\" } = urlOrParams;\n const base = baseUrl.replace(/\\/$/, '');\n\n if (productCode) {\n // Use product code route\n checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;\n } else if (productId && priceId) {\n // Use ID-based route\n checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;\n } else {\n throw new Error('Either productCode or both productId and priceId are required');\n }\n }\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n // Check if path already starts with /locale/ or is exactly /locale\n const localePrefix = `/${options.locale}`;\n if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {\n url.pathname = `${localePrefix}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n const localePrefix = `/${options.locale}`;\n if (!checkoutUrl.startsWith(`${localePrefix}/`) && checkoutUrl !== localePrefix) {\n finalUrl = `${localePrefix}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n","/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n orderId: string;\n internalId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n description?: string;\n paidAt?: string;\n createdAt: string;\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n [key: string]: {\n value: any;\n name: string;\n description: string;\n };\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n id: string;\n currency: string;\n amount: number;\n displayAmount: string;\n locale: string | null;\n isDefault: boolean;\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n id: string;\n code: string;\n type: string;\n name: string;\n description?: string;\n entitlements: ProductEntitlements;\n prices: ProductPrice[];\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n /** Application ID (Required) */\n appId: string;\n /** Application Secret (Required for server-side operations) */\n appSecret: string;\n /** API Base URL (e.g. https://pay.youidian.com) */\n baseUrl: string;\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n productId?: string;\n priceId?: string;\n channel?: string;\n userId: string;\n returnUrl?: string;\n metadata?: Record<string, any>;\n merchantOrderId?: string;\n openid?: string;\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n orderId: string;\n internalId: string;\n amount: number;\n currency: string;\n payParams: any;\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n iv: string;\n encryptedData: string;\n authTag: string;\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n orderId: string;\n merchantOrderId?: string;\n status: 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n paidAt: string;\n channelTransactionId?: string;\n metadata?: Record<string, any>;\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n private appId: string;\n private appSecret: string;\n private baseUrl: string;\n\n constructor(options: PaymentClientOptions) {\n if (!options.appId) throw new Error(\"appId is required\");\n if (!options.appSecret) throw new Error(\"appSecret is required\");\n if (!options.baseUrl) throw new Error(\"baseUrl is required\");\n\n this.appId = options.appId;\n this.appSecret = options.appSecret;\n this.baseUrl = options.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Generate SHA256 signature for the request\n * Logic: SHA256(appId + appSecret + timestamp)\n */\n private generateSignature(timestamp: number): string {\n const str = `${this.appId}${this.appSecret}${timestamp}`;\n return crypto.createHash(\"sha256\").update(str).digest(\"hex\");\n }\n\n /**\n * Internal request helper for Gateway API\n */\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const timestamp = Date.now();\n const signature = this.generateSignature(timestamp);\n\n const url = `${this.baseUrl}/api/v1/gateway/${this.appId}${path}`;\n\n const headers: HeadersInit = {\n \"Content-Type\": \"application/json\",\n \"X-Pay-Timestamp\": timestamp.toString(),\n \"X-Pay-Sign\": signature,\n };\n\n const options: RequestInit = {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n };\n\n const response = await fetch(url, options);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);\n }\n\n const json = await response.json();\n if (json.error) {\n throw new Error(`Payment API Error: ${json.error}`);\n }\n\n return json.data as T;\n }\n\n /**\n * Decrypts the callback notification payload using AES-256-GCM.\n * @param notification - The encrypted notification from payment webhook\n * @returns Decrypted payment callback data\n */\n decryptCallback(notification: PaymentNotification): PaymentCallbackData {\n try {\n const { iv, encryptedData, authTag } = notification;\n const key = crypto.createHash('sha256').update(this.appSecret).digest();\n const decipher = crypto.createDecipheriv(\n 'aes-256-gcm',\n key,\n Buffer.from(iv, 'hex')\n );\n\n decipher.setAuthTag(Buffer.from(authTag, 'hex'));\n\n let decrypted = decipher.update(encryptedData, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n\n return JSON.parse(decrypted);\n } catch (error) {\n throw new Error(\"Failed to decrypt payment callback: Invalid secret or tampered data.\");\n }\n }\n\n /**\n * Fetch products for the configured app.\n */\n async getProducts(options?: {\n locale?: string;\n currency?: string;\n }): Promise<Product[]> {\n const params = new URLSearchParams();\n params.append('appId', this.appId);\n if (options?.locale) params.append('locale', options.locale);\n if (options?.currency) params.append('currency', options.currency);\n\n const url = `${this.baseUrl}/api/v1/pay/products?${params.toString()}`;\n\n const res = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json'\n }\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch products: ${res.status} ${res.statusText}`);\n }\n\n const json = await res.json();\n return json.data?.products || [];\n }\n\n /**\n * Create a new order\n * @param params - Order creation parameters\n * @returns Order details with payment parameters\n */\n async createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n return this.request(\"POST\", \"/orders\", params);\n }\n\n /**\n * Pay for an existing order\n * @param orderId - The order ID to pay\n * @param params - Payment parameters including channel\n */\n async payOrder(orderId: string, params: {\n channel: string;\n returnUrl?: string;\n openid?: string;\n [key: string]: any;\n }): Promise<CreateOrderResponse> {\n return this.request(\"POST\", `/orders/${orderId}/pay`, params);\n }\n\n /**\n * Query order status\n * @param orderId - The order ID to query\n */\n async getOrderStatus(orderId: string): Promise<OrderStatus> {\n return this.request(\"GET\", `/orders/${orderId}`);\n }\n\n /**\n * Get order details (full order information)\n * @param orderId - The order ID to query\n */\n async getOrderDetails(orderId: string): Promise<OrderDetails> {\n return this.request(\"GET\", `/orders/${orderId}/details`);\n }\n\n /**\n * Get all entitlements for a user\n * @param userId - User ID\n */\n async getEntitlements(userId: string): Promise<Record<string, any>> {\n return this.request(\"GET\", `/users/${userId}/entitlements`);\n }\n\n /**\n * Get a single entitlement value\n * @param userId - User ID\n * @param key - Entitlement key\n */\n async getEntitlementValue(userId: string, key: string): Promise<any> {\n const entitlements = await this.getEntitlements(userId);\n return entitlements[key];\n }\n\n /**\n * Consume numeric entitlement\n * @param userId - User ID\n * @param key - Entitlement key\n * @param amount - Amount to consume\n */\n async consumeEntitlement(userId: string, key: string, amount: number): Promise<{ balance: number }> {\n return this.request(\"POST\", `/users/${userId}/entitlements/consume`, { key, amount });\n }\n\n /**\n * Add numeric entitlement (e.g. refund)\n * @param userId - User ID\n * @param key - Entitlement key\n * @param amount - Amount to add\n */\n async addEntitlement(userId: string, key: string, amount: number): Promise<{ balance: number }> {\n return this.request(\"POST\", `/users/${userId}/entitlements/add`, { key, amount });\n }\n\n /**\n * Toggle boolean entitlement\n * @param userId - User ID\n * @param key - Entitlement key\n * @param enabled - Whether to enable\n */\n async toggleEntitlement(userId: string, key: string, enabled: boolean): Promise<{ isEnabled: boolean }> {\n // Toggle endpoint expects POST with enabled flag\n // However, looking at list_dir, we have toggle/route.ts\n // I should verify its contract, but assuming standard toggle pattern:\n return this.request(\"POST\", `/users/${userId}/entitlements/toggle`, { key, enabled });\n }\n\n /**\n * Generate checkout URL for client-side payment\n * @param productId - Product ID\n * @param priceId - Price ID\n * @returns Checkout page URL\n */\n getCheckoutUrl(productId: string, priceId: string): string {\n return `${this.baseUrl}/checkout/${this.appId}/${productId}/${priceId}`;\n }\n}\n"],"mappings":"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","names":["crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK\n * 统一支付集成 SDK\n * \n * @packageDocumentation\n * \n * @example\n * // 服务端使用 - 推荐方式(分离 API 和 Checkout URL)\n * import { PaymentClient } from '@youidian/pay-sdk/server';\n * \n * const client = new PaymentClient({\n * appId: 'your-app-id',\n * appSecret: 'your-app-secret',\n * apiUrl: 'https://api.youidian.com', // API 服务器地址\n * checkoutUrl: 'https://pay.youidian.com' // Checkout 页面地址\n * });\n * \n * @example\n * // 服务端使用 - 向后兼容方式(使用单一 baseUrl)\n * import { PaymentClient } from '@youidian/pay-sdk/server';\n * \n * const client = new PaymentClient({\n * appId: 'your-app-id',\n * appSecret: 'your-app-secret',\n * baseUrl: 'https://pay.youidian.com' // 同时用于 API 和 Checkout\n * });\n * \n * @example\n * // 客户端使用\n * import { PaymentUI } from '@youidian/pay-sdk/client';\n * \n * const ui = new PaymentUI();\n * ui.openPayment('https://pay.youidian.com/checkout/...', {\n * onSuccess: (orderId) => console.log('支付成功', orderId),\n * onCancel: () => console.log('支付取消')\n * });\n */\n\n// Re-export client module (browser-safe)\nexport { PaymentUI, createPaymentUI } from './client';\nexport type {\n PaymentEventType,\n PaymentEventData,\n PaymentUIOptions,\n PollOptions,\n OrderStatus as ClientOrderStatus\n} from './client';\n\n// Re-export server module (Node.js only)\nexport { PaymentClient } from './server';\nexport type {\n PaymentClientOptions,\n CreateOrderParams,\n CreateOrderResponse,\n PaymentNotification,\n PaymentCallbackData,\n Product,\n ProductPrice,\n ProductEntitlements,\n OrderStatus,\n OrderDetails\n} from './server';\n","/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n\n /**\n * @deprecated Use checkoutUrl instead\n * Defaults to https://pay.imgto.link\n */\n baseUrl?: string;\n\n /** Checkout page URL (e.g. https://pay.youidian.com) */\n checkoutUrl?: string;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param urlOrParams - The checkout page URL or payment parameters\n * @param options - UI options\n */\n openPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let checkoutUrl: string;\n if (typeof urlOrParams === 'string') {\n checkoutUrl = urlOrParams;\n } else {\n const {\n appId,\n productId,\n priceId,\n productCode,\n userId,\n checkoutUrl: checkoutUrlParam,\n baseUrl = \"https://pay.imgto.link\"\n } = urlOrParams;\n\n // 优先使用 checkoutUrl,否则使用 baseUrl\n const base = (checkoutUrlParam || baseUrl).replace(/\\/$/, '');\n\n if (productCode) {\n // Use product code route\n checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;\n } else if (productId && priceId) {\n // Use ID-based route\n checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;\n } else {\n throw new Error('Either productCode or both productId and priceId are required');\n }\n }\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n // Check if path already starts with /locale/ or is exactly /locale\n const localePrefix = `/${options.locale}`;\n if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {\n url.pathname = `${localePrefix}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n const localePrefix = `/${options.locale}`;\n if (!checkoutUrl.startsWith(`${localePrefix}/`) && checkoutUrl !== localePrefix) {\n finalUrl = `${localePrefix}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n","/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n orderId: string;\n internalId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n description?: string;\n paidAt?: string;\n createdAt: string;\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n [key: string]: {\n value: any;\n name: string;\n description: string;\n };\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n id: string;\n currency: string;\n amount: number;\n displayAmount: string;\n locale: string | null;\n isDefault: boolean;\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n id: string;\n code: string;\n type: string;\n name: string;\n description?: string;\n entitlements: ProductEntitlements;\n prices: ProductPrice[];\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n /** Application ID (Required) */\n appId: string;\n /** Application Secret (Required for server-side operations) */\n appSecret: string;\n\n /**\n * @deprecated Use apiUrl and checkoutUrl instead\n * API Base URL (e.g. https://pay.youidian.com)\n * If apiUrl or checkoutUrl is not provided, this will be used as fallback\n */\n baseUrl?: string;\n\n /** API server URL for backend requests (e.g. https://api.youidian.com) */\n apiUrl?: string;\n\n /** Checkout page URL for client-side payment (e.g. https://pay.youidian.com) */\n checkoutUrl?: string;\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n productId?: string;\n priceId?: string;\n channel?: string;\n userId: string;\n returnUrl?: string;\n metadata?: Record<string, any>;\n merchantOrderId?: string;\n openid?: string;\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n orderId: string;\n internalId: string;\n amount: number;\n currency: string;\n payParams: any;\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n iv: string;\n encryptedData: string;\n authTag: string;\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n orderId: string;\n merchantOrderId?: string;\n status: 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n paidAt: string;\n channelTransactionId?: string;\n metadata?: Record<string, any>;\n}\n\n/**\n * Get Orders Parameters\n */\nexport interface GetOrdersParams {\n page?: number;\n pageSize?: number;\n userId?: string;\n status?: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n startDate?: string;\n endDate?: string;\n}\n\n/**\n * Order List Item\n */\nexport interface OrderListItem {\n orderId: string;\n internalId: string;\n merchantUserId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n channel?: string;\n paidAt?: string;\n createdAt: string;\n}\n\n/**\n * Get Orders Response\n */\nexport interface GetOrdersResponse {\n orders: OrderListItem[];\n pagination: {\n total: number;\n page: number;\n pageSize: number;\n totalPages: number;\n };\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n private readonly appId: string;\n private readonly appSecret: string;\n private readonly apiUrl: string; // 用于 API 调用\n private readonly checkoutUrl: string; // 用于生成 checkout URL\n\n constructor(options: PaymentClientOptions) {\n if (!options.appId) throw new Error(\"appId is required\");\n if (!options.appSecret) throw new Error(\"appSecret is required\");\n\n // 向后兼容:如果未提供 apiUrl 或 checkoutUrl,则使用 baseUrl\n if (!options.apiUrl && !options.checkoutUrl && !options.baseUrl) {\n throw new Error(\"At least one of baseUrl, apiUrl, or checkoutUrl must be provided\");\n }\n\n this.appId = options.appId;\n this.appSecret = options.appSecret;\n\n // apiUrl: 优先使用 apiUrl,否则使用 baseUrl\n const apiUrl = options.apiUrl || options.baseUrl;\n if (!apiUrl) {\n throw new Error(\"apiUrl or baseUrl is required for API operations\");\n }\n this.apiUrl = apiUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // checkoutUrl: 优先使用 checkoutUrl,否则使用 baseUrl\n const checkoutUrl = options.checkoutUrl || options.baseUrl;\n if (!checkoutUrl) {\n throw new Error(\"checkoutUrl or baseUrl is required for checkout operations\");\n }\n this.checkoutUrl = checkoutUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Generate SHA256 signature for the request\n * Logic: SHA256(appId + appSecret + timestamp)\n */\n private generateSignature(timestamp: number): string {\n const str = `${this.appId}${this.appSecret}${timestamp}`;\n return crypto.createHash(\"sha256\").update(str).digest(\"hex\");\n }\n\n /**\n * Internal request helper for Gateway API\n */\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const timestamp = Date.now();\n const signature = this.generateSignature(timestamp);\n\n const url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`;\n\n const headers: HeadersInit = {\n \"Content-Type\": \"application/json\",\n \"X-Pay-Timestamp\": timestamp.toString(),\n \"X-Pay-Sign\": signature,\n };\n\n const options: RequestInit = {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n };\n\n const response = await fetch(url, options);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);\n }\n\n const json = await response.json();\n if (json.error) {\n throw new Error(`Payment API Error: ${json.error}`);\n }\n\n return json.data as T;\n }\n\n /**\n * Decrypts the callback notification payload using AES-256-GCM.\n * @param notification - The encrypted notification from payment webhook\n * @returns Decrypted payment callback data\n */\n decryptCallback(notification: PaymentNotification): PaymentCallbackData {\n try {\n const { iv, encryptedData, authTag } = notification;\n const key = crypto.createHash('sha256').update(this.appSecret).digest();\n const decipher = crypto.createDecipheriv(\n 'aes-256-gcm',\n key,\n Buffer.from(iv, 'hex')\n );\n\n decipher.setAuthTag(Buffer.from(authTag, 'hex'));\n\n let decrypted = decipher.update(encryptedData, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n\n return JSON.parse(decrypted);\n } catch (error) {\n throw new Error(\"Failed to decrypt payment callback: Invalid secret or tampered data.\");\n }\n }\n\n /**\n * Fetch products for the configured app.\n */\n async getProducts(options?: {\n locale?: string;\n currency?: string;\n }): Promise<Product[]> {\n const params = new URLSearchParams();\n if (options?.locale) params.append('locale', options.locale);\n if (options?.currency) params.append('currency', options.currency);\n\n const path = params.toString() ? `/products?${params.toString()}` : '/products';\n const response = await this.request<{ products: Product[] }>(\"GET\", path);\n return response.products || [];\n }\n\n /**\n * Create a new order\n * @param params - Order creation parameters\n * @returns Order details with payment parameters\n */\n async createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n return this.request(\"POST\", \"/orders\", params);\n }\n\n /**\n * Pay for an existing order\n * @param orderId - The order ID to pay\n * @param params - Payment parameters including channel\n */\n async payOrder(orderId: string, params: {\n channel: string;\n returnUrl?: string;\n openid?: string;\n [key: string]: any;\n }): Promise<CreateOrderResponse> {\n return this.request(\"POST\", `/orders/${orderId}/pay`, params);\n }\n\n /**\n * Query order status\n * @param orderId - The order ID to query\n */\n async getOrderStatus(orderId: string): Promise<OrderStatus> {\n return this.request(\"GET\", `/orders/${orderId}`);\n }\n\n /**\n * Get order details (full order information)\n * @param orderId - The order ID to query\n */\n async getOrderDetails(orderId: string): Promise<OrderDetails> {\n return this.request(\"GET\", `/orders/${orderId}/details`);\n }\n\n /**\n * Get orders list with pagination\n * @param params - Query parameters (pagination, filters)\n * @returns Orders list and pagination info\n */\n async getOrders(params?: GetOrdersParams): Promise<GetOrdersResponse> {\n const queryParams = new URLSearchParams();\n if (params?.page) queryParams.append('page', params.page.toString());\n if (params?.pageSize) queryParams.append('pageSize', params.pageSize.toString());\n if (params?.userId) queryParams.append('userId', params.userId);\n if (params?.status) queryParams.append('status', params.status);\n if (params?.startDate) queryParams.append('startDate', params.startDate);\n if (params?.endDate) queryParams.append('endDate', params.endDate);\n\n const path = queryParams.toString() ? `/orders?${queryParams.toString()}` : '/orders';\n return this.request<GetOrdersResponse>(\"GET\", path);\n }\n\n /**\n * Get all entitlements for a user\n * @param userId - User ID\n */\n async getEntitlements(userId: string): Promise<Record<string, any>> {\n return this.request(\"GET\", `/users/${userId}/entitlements`);\n }\n\n /**\n * Get a single entitlement value\n * @param userId - User ID\n * @param key - Entitlement key\n */\n async getEntitlementValue(userId: string, key: string): Promise<any> {\n const entitlements = await this.getEntitlements(userId);\n return entitlements[key];\n }\n\n /**\n * Consume numeric entitlement\n * @param userId - User ID\n * @param key - Entitlement key\n * @param amount - Amount to consume\n */\n async consumeEntitlement(userId: string, key: string, amount: number): Promise<{ balance: number }> {\n return this.request(\"POST\", `/users/${userId}/entitlements/consume`, { key, amount });\n }\n\n /**\n * Add numeric entitlement (e.g. refund)\n * @param userId - User ID\n * @param key - Entitlement key\n * @param amount - Amount to add\n */\n async addEntitlement(userId: string, key: string, amount: number): Promise<{ balance: number }> {\n return this.request(\"POST\", `/users/${userId}/entitlements/add`, { key, amount });\n }\n\n /**\n * Toggle boolean entitlement\n * @param userId - User ID\n * @param key - Entitlement key\n * @param enabled - Whether to enable\n */\n async toggleEntitlement(userId: string, key: string, enabled: boolean): Promise<{ isEnabled: boolean }> {\n // Toggle endpoint expects POST with enabled flag\n // However, looking at list_dir, we have toggle/route.ts\n // I should verify its contract, but assuming standard toggle pattern:\n return this.request(\"POST\", `/users/${userId}/entitlements/toggle`, { key, enabled });\n }\n\n /**\n * Generate checkout URL for client-side payment\n * @param productId - Product ID\n * @param priceId - Price ID\n * @returns Checkout page URL\n */\n getCheckoutUrl(productId: string, priceId: string): string {\n return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACqFO,IAAM,YAAN,MAAgB;AAAA,EAAhB;AACH,wBAAQ,UAAmC;AAC3C,wBAAQ,SAA+B;AACvC,wBAAQ,kBAAyD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjE,YAAY,aAAqC,SAA4B;AACzE,QAAI,OAAO,aAAa,YAAa;AACrC,QAAI,KAAK,MAAO;AAEhB,QAAI;AACJ,QAAI,OAAO,gBAAgB,UAAU;AACjC,oBAAc;AAAA,IAClB,OAAO;AACH,YAAM;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa;AAAA,QACb,UAAU;AAAA,MACd,IAAI;AAGJ,YAAM,QAAQ,oBAAoB,SAAS,QAAQ,OAAO,EAAE;AAE5D,UAAI,aAAa;AAEb,sBAAc,GAAG,IAAI,aAAa,KAAK,SAAS,WAAW,WAAW,mBAAmB,MAAM,CAAC;AAAA,MACpG,WAAW,aAAa,SAAS;AAE7B,sBAAc,GAAG,IAAI,aAAa,KAAK,IAAI,SAAS,IAAI,OAAO,WAAW,mBAAmB,MAAM,CAAC;AAAA,MACxG,OAAO;AACH,cAAM,IAAI,MAAM,+DAA+D;AAAA,MACnF;AAAA,IACJ;AAEA,QAAI,WAAW;AACf,QAAI,SAAS,QAAQ;AACjB,UAAI;AACA,cAAM,MAAM,IAAI,IAAI,WAAW;AAE/B,cAAM,eAAe,IAAI,QAAQ,MAAM;AACvC,YAAI,CAAC,IAAI,SAAS,WAAW,GAAG,YAAY,GAAG,KAAK,IAAI,aAAa,cAAc;AAC/E,cAAI,WAAW,GAAG,YAAY,GAAG,IAAI,QAAQ;AAC7C,qBAAW,IAAI,SAAS;AAAA,QAC5B;AAAA,MACJ,SAAS,IAAI;AAET,cAAM,eAAe,IAAI,QAAQ,MAAM;AACvC,YAAI,CAAC,YAAY,WAAW,GAAG,YAAY,GAAG,KAAK,gBAAgB,cAAc;AAC7E,qBAAW,GAAG,YAAY,GAAG,YAAY,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,WAAW;AAAA,QACrF;AAAA,MACJ;AAAA,IACJ;AAGA,SAAK,QAAQ,SAAS,cAAc,KAAK;AACzC,WAAO,OAAO,KAAK,MAAM,OAAO;AAAA,MAC5B,UAAU;AAAA,MACV,KAAK;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,YAAY;AAAA,IAChB,CAAC;AAGD,UAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,WAAO,OAAO,UAAU,OAAO;AAAA,MAC3B,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,UAAU;AAAA,MACV,UAAU;AAAA,MACV,WAAW;AAAA,IACf,CAAC;AAGD,UAAM,WAAW,SAAS,cAAc,QAAQ;AAChD,aAAS,YAAY;AACrB,WAAO,OAAO,SAAS,OAAO;AAAA,MAC1B,UAAU;AAAA,MACV,OAAO;AAAA,MACP,KAAK;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACZ,CAAC;AACD,aAAS,UAAU,MAAM;AACrB,WAAK,MAAM;AACX,eAAS,WAAW;AAAA,IACxB;AAGA,SAAK,SAAS,SAAS,cAAc,QAAQ;AAC7C,SAAK,OAAO,MAAM;AAClB,WAAO,OAAO,KAAK,OAAO,OAAO;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,IACZ,CAAC;AAED,cAAU,YAAY,QAAQ;AAC9B,cAAU,YAAY,KAAK,MAAM;AACjC,SAAK,MAAM,YAAY,SAAS;AAChC,aAAS,KAAK,YAAY,KAAK,KAAK;AAGpC,SAAK,iBAAiB,CAAC,UAAwB;AAE3C,UAAI,SAAS,iBAAiB,QAAQ,kBAAkB,KAAK;AACzD,YAAI,MAAM,WAAW,QAAQ,eAAe;AACxC;AAAA,QACJ;AAAA,MACJ;AAEA,YAAM,OAAO,MAAM;AAEnB,UAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,MAAM;AACjD;AAAA,MACJ;AAEA,cAAQ,KAAK,MAAM;AAAA,QACf,KAAK;AACD,mBAAS,YAAY,KAAK,OAAO;AAEjC,qBAAW,MAAM,KAAK,MAAM,GAAG,GAAI;AACnC;AAAA,QACJ,KAAK;AACD,mBAAS,WAAW,KAAK,OAAO;AAChC;AAAA,QACJ,KAAK;AACD,cAAI,KAAK,UAAU,WAAW;AAE1B,kBAAM,YAAY,OAAO,cAAc;AACvC,kBAAM,YAAY,KAAK,IAAI,KAAK,QAAQ,SAAS;AACjD,sBAAU,MAAM,SAAS,GAAG,SAAS;AAAA,UACzC;AACA;AAAA,QACJ,KAAK;AACD,eAAK,MAAM;AACX,mBAAS,UAAU;AACnB;AAAA,MACR;AAAA,IACJ;AACA,WAAO,iBAAiB,WAAW,KAAK,cAAc;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ;AACJ,QAAI,OAAO,WAAW,YAAa;AAEnC,QAAI,KAAK,gBAAgB;AACrB,aAAO,oBAAoB,WAAW,KAAK,cAAc;AACzD,WAAK,iBAAiB;AAAA,IAC1B;AACA,QAAI,KAAK,SAAS,KAAK,MAAM,YAAY;AACrC,WAAK,MAAM,WAAW,YAAY,KAAK,KAAK;AAAA,IAChD;AACA,SAAK,QAAQ;AACb,SAAK,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,WAAmB,SAA6C;AAClF,UAAM,WAAW,SAAS,YAAY;AACtC,UAAM,UAAU,SAAS,WAAW;AACpC,UAAM,YAAY,KAAK,IAAI;AAE3B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACpC,YAAM,OAAO,YAAY;AACrB,YAAI;AACA,gBAAM,WAAW,MAAM,MAAM,SAAS;AACtC,cAAI,CAAC,SAAS,IAAI;AACd,kBAAM,IAAI,MAAM,wBAAwB,SAAS,MAAM,EAAE;AAAA,UAC7D;AAEA,gBAAM,SAAsB,MAAM,SAAS,KAAK;AAChD,mBAAS,iBAAiB,MAAM;AAEhC,cAAI,OAAO,WAAW,QAAQ;AAC1B,oBAAQ,MAAM;AACd;AAAA,UACJ;AAEA,cAAI,OAAO,WAAW,eAAe,OAAO,WAAW,UAAU;AAC7D,mBAAO,IAAI,MAAM,SAAS,OAAO,OAAO,YAAY,CAAC,EAAE,CAAC;AACxD;AAAA,UACJ;AAGA,cAAI,KAAK,IAAI,IAAI,YAAY,SAAS;AAClC,mBAAO,IAAI,MAAM,iBAAiB,CAAC;AACnC;AAAA,UACJ;AAGA,qBAAW,MAAM,QAAQ;AAAA,QAC7B,SAAS,OAAO;AAEZ,cAAI,KAAK,IAAI,IAAI,YAAY,SAAS;AAClC,mBAAO,KAAK;AACZ;AAAA,UACJ;AACA,qBAAW,MAAM,QAAQ;AAAA,QAC7B;AAAA,MACJ;AAEA,WAAK;AAAA,IACT,CAAC;AAAA,EACL;AACJ;AAKO,SAAS,kBAA6B;AACzC,SAAO,IAAI,UAAU;AACzB;;;AChUA,oBAAmB;AAiLZ,IAAM,gBAAN,MAAoB;AAAA;AAAA,EAMvB,YAAY,SAA+B;AAL3C,wBAAiB;AACjB,wBAAiB;AACjB,wBAAiB;AACjB;AAAA,wBAAiB;AAGb,QAAI,CAAC,QAAQ,MAAO,OAAM,IAAI,MAAM,mBAAmB;AACvD,QAAI,CAAC,QAAQ,UAAW,OAAM,IAAI,MAAM,uBAAuB;AAG/D,QAAI,CAAC,QAAQ,UAAU,CAAC,QAAQ,eAAe,CAAC,QAAQ,SAAS;AAC7D,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACtF;AAEA,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ;AAGzB,UAAM,SAAS,QAAQ,UAAU,QAAQ;AACzC,QAAI,CAAC,QAAQ;AACT,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACtE;AACA,SAAK,SAAS,OAAO,QAAQ,OAAO,EAAE;AAGtC,UAAM,cAAc,QAAQ,eAAe,QAAQ;AACnD,QAAI,CAAC,aAAa;AACd,YAAM,IAAI,MAAM,4DAA4D;AAAA,IAChF;AACA,SAAK,cAAc,YAAY,QAAQ,OAAO,EAAE;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,WAA2B;AACjD,UAAM,MAAM,GAAG,KAAK,KAAK,GAAG,KAAK,SAAS,GAAG,SAAS;AACtD,WAAO,cAAAA,QAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,QAAW,QAAgB,MAAc,MAAwB;AAC3E,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,YAAY,KAAK,kBAAkB,SAAS;AAElD,UAAM,MAAM,GAAG,KAAK,MAAM,mBAAmB,KAAK,KAAK,GAAG,IAAI;AAE9D,UAAM,UAAuB;AAAA,MACzB,gBAAgB;AAAA,MAChB,mBAAmB,UAAU,SAAS;AAAA,MACtC,cAAc;AAAA,IAClB;AAEA,UAAM,UAAuB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACxC;AAEA,UAAM,WAAW,MAAM,MAAM,KAAK,OAAO;AAEzC,QAAI,CAAC,SAAS,IAAI;AACd,YAAM,YAAY,MAAM,SAAS,KAAK;AACtC,YAAM,IAAI,MAAM,sBAAsB,SAAS,MAAM,MAAM,SAAS,EAAE;AAAA,IAC1E;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,KAAK,OAAO;AACZ,YAAM,IAAI,MAAM,sBAAsB,KAAK,KAAK,EAAE;AAAA,IACtD;AAEA,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,cAAwD;AACpE,QAAI;AACA,YAAM,EAAE,IAAI,eAAe,QAAQ,IAAI;AACvC,YAAM,MAAM,cAAAA,QAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,SAAS,EAAE,OAAO;AACtE,YAAM,WAAW,cAAAA,QAAO;AAAA,QACpB;AAAA,QACA;AAAA,QACA,OAAO,KAAK,IAAI,KAAK;AAAA,MACzB;AAEA,eAAS,WAAW,OAAO,KAAK,SAAS,KAAK,CAAC;AAE/C,UAAI,YAAY,SAAS,OAAO,eAAe,OAAO,MAAM;AAC5D,mBAAa,SAAS,MAAM,MAAM;AAElC,aAAO,KAAK,MAAM,SAAS;AAAA,IAC/B,SAAS,OAAO;AACZ,YAAM,IAAI,MAAM,sEAAsE;AAAA,IAC1F;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,SAGK;AACnB,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,SAAS,OAAQ,QAAO,OAAO,UAAU,QAAQ,MAAM;AAC3D,QAAI,SAAS,SAAU,QAAO,OAAO,YAAY,QAAQ,QAAQ;AAEjE,UAAM,OAAO,OAAO,SAAS,IAAI,aAAa,OAAO,SAAS,CAAC,KAAK;AACpE,UAAM,WAAW,MAAM,KAAK,QAAiC,OAAO,IAAI;AACxE,WAAO,SAAS,YAAY,CAAC;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,QAAyD;AACvE,WAAO,KAAK,QAAQ,QAAQ,WAAW,MAAM;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAS,SAAiB,QAKC;AAC7B,WAAO,KAAK,QAAQ,QAAQ,WAAW,OAAO,QAAQ,MAAM;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAe,SAAuC;AACxD,WAAO,KAAK,QAAQ,OAAO,WAAW,OAAO,EAAE;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAgB,SAAwC;AAC1D,WAAO,KAAK,QAAQ,OAAO,WAAW,OAAO,UAAU;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,QAAsD;AAClE,UAAM,cAAc,IAAI,gBAAgB;AACxC,QAAI,QAAQ,KAAM,aAAY,OAAO,QAAQ,OAAO,KAAK,SAAS,CAAC;AACnE,QAAI,QAAQ,SAAU,aAAY,OAAO,YAAY,OAAO,SAAS,SAAS,CAAC;AAC/E,QAAI,QAAQ,OAAQ,aAAY,OAAO,UAAU,OAAO,MAAM;AAC9D,QAAI,QAAQ,OAAQ,aAAY,OAAO,UAAU,OAAO,MAAM;AAC9D,QAAI,QAAQ,UAAW,aAAY,OAAO,aAAa,OAAO,SAAS;AACvE,QAAI,QAAQ,QAAS,aAAY,OAAO,WAAW,OAAO,OAAO;AAEjE,UAAM,OAAO,YAAY,SAAS,IAAI,WAAW,YAAY,SAAS,CAAC,KAAK;AAC5E,WAAO,KAAK,QAA2B,OAAO,IAAI;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAgB,QAA8C;AAChE,WAAO,KAAK,QAAQ,OAAO,UAAU,MAAM,eAAe;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBAAoB,QAAgB,KAA2B;AACjE,UAAM,eAAe,MAAM,KAAK,gBAAgB,MAAM;AACtD,WAAO,aAAa,GAAG;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBAAmB,QAAgB,KAAa,QAA8C;AAChG,WAAO,KAAK,QAAQ,QAAQ,UAAU,MAAM,yBAAyB,EAAE,KAAK,OAAO,CAAC;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAe,QAAgB,KAAa,QAA8C;AAC5F,WAAO,KAAK,QAAQ,QAAQ,UAAU,MAAM,qBAAqB,EAAE,KAAK,OAAO,CAAC;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,kBAAkB,QAAgB,KAAa,SAAmD;AAIpG,WAAO,KAAK,QAAQ,QAAQ,UAAU,MAAM,wBAAwB,EAAE,KAAK,QAAQ,CAAC;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,WAAmB,SAAyB;AACvD,WAAO,GAAG,KAAK,WAAW,aAAa,KAAK,KAAK,IAAI,SAAS,IAAI,OAAO;AAAA,EAC7E;AACJ;","names":["crypto"]}
|
package/dist/index.js
CHANGED
|
@@ -21,8 +21,16 @@ var PaymentUI = class {
|
|
|
21
21
|
if (typeof urlOrParams === "string") {
|
|
22
22
|
checkoutUrl = urlOrParams;
|
|
23
23
|
} else {
|
|
24
|
-
const {
|
|
25
|
-
|
|
24
|
+
const {
|
|
25
|
+
appId,
|
|
26
|
+
productId,
|
|
27
|
+
priceId,
|
|
28
|
+
productCode,
|
|
29
|
+
userId,
|
|
30
|
+
checkoutUrl: checkoutUrlParam,
|
|
31
|
+
baseUrl = "https://pay.imgto.link"
|
|
32
|
+
} = urlOrParams;
|
|
33
|
+
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
26
34
|
if (productCode) {
|
|
27
35
|
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
28
36
|
} else if (productId && priceId) {
|
|
@@ -198,16 +206,30 @@ function createPaymentUI() {
|
|
|
198
206
|
// src/server.ts
|
|
199
207
|
import crypto from "crypto";
|
|
200
208
|
var PaymentClient = class {
|
|
209
|
+
// 用于生成 checkout URL
|
|
201
210
|
constructor(options) {
|
|
202
211
|
__publicField(this, "appId");
|
|
203
212
|
__publicField(this, "appSecret");
|
|
204
|
-
__publicField(this, "
|
|
213
|
+
__publicField(this, "apiUrl");
|
|
214
|
+
// 用于 API 调用
|
|
215
|
+
__publicField(this, "checkoutUrl");
|
|
205
216
|
if (!options.appId) throw new Error("appId is required");
|
|
206
217
|
if (!options.appSecret) throw new Error("appSecret is required");
|
|
207
|
-
if (!options.
|
|
218
|
+
if (!options.apiUrl && !options.checkoutUrl && !options.baseUrl) {
|
|
219
|
+
throw new Error("At least one of baseUrl, apiUrl, or checkoutUrl must be provided");
|
|
220
|
+
}
|
|
208
221
|
this.appId = options.appId;
|
|
209
222
|
this.appSecret = options.appSecret;
|
|
210
|
-
|
|
223
|
+
const apiUrl = options.apiUrl || options.baseUrl;
|
|
224
|
+
if (!apiUrl) {
|
|
225
|
+
throw new Error("apiUrl or baseUrl is required for API operations");
|
|
226
|
+
}
|
|
227
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
228
|
+
const checkoutUrl = options.checkoutUrl || options.baseUrl;
|
|
229
|
+
if (!checkoutUrl) {
|
|
230
|
+
throw new Error("checkoutUrl or baseUrl is required for checkout operations");
|
|
231
|
+
}
|
|
232
|
+
this.checkoutUrl = checkoutUrl.replace(/\/$/, "");
|
|
211
233
|
}
|
|
212
234
|
/**
|
|
213
235
|
* Generate SHA256 signature for the request
|
|
@@ -223,7 +245,7 @@ var PaymentClient = class {
|
|
|
223
245
|
async request(method, path, body) {
|
|
224
246
|
const timestamp = Date.now();
|
|
225
247
|
const signature = this.generateSignature(timestamp);
|
|
226
|
-
const url = `${this.
|
|
248
|
+
const url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`;
|
|
227
249
|
const headers = {
|
|
228
250
|
"Content-Type": "application/json",
|
|
229
251
|
"X-Pay-Timestamp": timestamp.toString(),
|
|
@@ -272,22 +294,11 @@ var PaymentClient = class {
|
|
|
272
294
|
*/
|
|
273
295
|
async getProducts(options) {
|
|
274
296
|
const params = new URLSearchParams();
|
|
275
|
-
params.append("appId", this.appId);
|
|
276
297
|
if (options?.locale) params.append("locale", options.locale);
|
|
277
298
|
if (options?.currency) params.append("currency", options.currency);
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
headers: {
|
|
282
|
-
"Content-Type": "application/json",
|
|
283
|
-
"Accept": "application/json"
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
if (!res.ok) {
|
|
287
|
-
throw new Error(`Failed to fetch products: ${res.status} ${res.statusText}`);
|
|
288
|
-
}
|
|
289
|
-
const json = await res.json();
|
|
290
|
-
return json.data?.products || [];
|
|
299
|
+
const path = params.toString() ? `/products?${params.toString()}` : "/products";
|
|
300
|
+
const response = await this.request("GET", path);
|
|
301
|
+
return response.products || [];
|
|
291
302
|
}
|
|
292
303
|
/**
|
|
293
304
|
* Create a new order
|
|
@@ -319,6 +330,22 @@ var PaymentClient = class {
|
|
|
319
330
|
async getOrderDetails(orderId) {
|
|
320
331
|
return this.request("GET", `/orders/${orderId}/details`);
|
|
321
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Get orders list with pagination
|
|
335
|
+
* @param params - Query parameters (pagination, filters)
|
|
336
|
+
* @returns Orders list and pagination info
|
|
337
|
+
*/
|
|
338
|
+
async getOrders(params) {
|
|
339
|
+
const queryParams = new URLSearchParams();
|
|
340
|
+
if (params?.page) queryParams.append("page", params.page.toString());
|
|
341
|
+
if (params?.pageSize) queryParams.append("pageSize", params.pageSize.toString());
|
|
342
|
+
if (params?.userId) queryParams.append("userId", params.userId);
|
|
343
|
+
if (params?.status) queryParams.append("status", params.status);
|
|
344
|
+
if (params?.startDate) queryParams.append("startDate", params.startDate);
|
|
345
|
+
if (params?.endDate) queryParams.append("endDate", params.endDate);
|
|
346
|
+
const path = queryParams.toString() ? `/orders?${queryParams.toString()}` : "/orders";
|
|
347
|
+
return this.request("GET", path);
|
|
348
|
+
}
|
|
322
349
|
/**
|
|
323
350
|
* Get all entitlements for a user
|
|
324
351
|
* @param userId - User ID
|
|
@@ -369,7 +396,7 @@ var PaymentClient = class {
|
|
|
369
396
|
* @returns Checkout page URL
|
|
370
397
|
*/
|
|
371
398
|
getCheckoutUrl(productId, priceId) {
|
|
372
|
-
return `${this.
|
|
399
|
+
return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;
|
|
373
400
|
}
|
|
374
401
|
};
|
|
375
402
|
export {
|