@youidian/pay-sdk 1.0.10 → 1.0.12

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 CHANGED
@@ -59,7 +59,9 @@ var PaymentUI = class {
59
59
  } else if (productId && priceId) {
60
60
  checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
61
61
  } else {
62
- throw new Error("Either productCode or both productId and priceId are required");
62
+ throw new Error(
63
+ "Either productCode or both productId and priceId are required"
64
+ );
63
65
  }
64
66
  }
65
67
  let finalUrl = checkoutUrl;
@@ -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\n /**\n * @deprecated Use checkoutUrl instead\n * Defaults to https://pay.imgto.link\n */\n baseUrl?: string;\n\n /**\n * Checkout page URL (e.g. https://pay.youidian.com)\n * Defaults to https://pay.imgto.link\n */\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,默认 https://pay.imgto.link\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 removed, waiting for explicit PAYMENT_CLOSE\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 =\n\t| \"PAYMENT_SUCCESS\"\n\t| \"PAYMENT_CANCELLED\"\n\t| \"PAYMENT_CLOSE\"\n\t| \"PAYMENT_RESIZE\"\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n\ttype: PaymentEventType\n\torderId?: string\n\theight?: number\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n\tlocale?: string\n\tonSuccess?: (orderId?: string) => void\n\tonCancel?: (orderId?: string) => void\n\tonClose?: () => void\n\t/** Origin to validate postMessage (defaults to '*', set for security) */\n\tallowedOrigin?: string\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n\t/** Polling interval in ms (default: 3000) */\n\tinterval?: number\n\t/** Timeout in ms (default: 300000 = 5 minutes) */\n\ttimeout?: number\n\t/** Callback on each status check */\n\tonStatusChange?: (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\tappId: string\n\tuserId: string\n\t/** Product ID - use with priceId for direct ID-based payment */\n\tproductId?: string\n\t/** Price ID - use with productId for direct ID-based payment */\n\tpriceId?: string\n\t/** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n\tproductCode?: string\n\n\t/**\n\t * @deprecated Use checkoutUrl instead\n\t * Defaults to https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * Checkout page URL (e.g. https://pay.youidian.com)\n\t * Defaults to https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n\tprivate iframe: HTMLIFrameElement | null = null\n\tprivate modal: HTMLDivElement | null = null\n\tprivate messageHandler: ((event: MessageEvent) => void) | null = null\n\n\t/**\n\t * Opens the payment checkout page in an iframe modal.\n\t * @param urlOrParams - The checkout page URL or payment parameters\n\t * @param options - UI options\n\t */\n\topenPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n\t\tif (typeof document === \"undefined\") return // Server-side guard\n\t\tif (this.modal) return // Prevent multiple modals\n\n\t\tlet checkoutUrl: string\n\t\tif (typeof urlOrParams === \"string\") {\n\t\t\tcheckoutUrl = urlOrParams\n\t\t} else {\n\t\t\tconst {\n\t\t\t\tappId,\n\t\t\t\tproductId,\n\t\t\t\tpriceId,\n\t\t\t\tproductCode,\n\t\t\t\tuserId,\n\t\t\t\tcheckoutUrl: checkoutUrlParam,\n\t\t\t\tbaseUrl = \"https://pay.imgto.link\",\n\t\t\t} = urlOrParams\n\n\t\t\t// 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\t\tconst base = (checkoutUrlParam || baseUrl).replace(/\\/$/, \"\")\n\n\t\t\tif (productCode) {\n\t\t\t\t// Use product code route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else if (productId && priceId) {\n\t\t\t\t// Use ID-based route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Either productCode or both productId and priceId are required\",\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tlet finalUrl = checkoutUrl\n\t\tif (options?.locale) {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(checkoutUrl)\n\t\t\t\t// Check if path already starts with /locale/ or is exactly /locale\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!url.pathname.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\turl.pathname !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\turl.pathname = `${localePrefix}${url.pathname}`\n\t\t\t\t\tfinalUrl = url.toString()\n\t\t\t\t}\n\t\t\t} catch (_e) {\n\t\t\t\t// Fallback if URL is relative or invalid\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!checkoutUrl.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\tcheckoutUrl !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\tfinalUrl = `${localePrefix}${checkoutUrl.startsWith(\"/\") ? \"\" : \"/\"}${checkoutUrl}`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Create Modal Overlay\n\t\tthis.modal = document.createElement(\"div\")\n\t\tObject.assign(this.modal.style, {\n\t\t\tposition: \"fixed\",\n\t\t\ttop: \"0\",\n\t\t\tleft: \"0\",\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tbackgroundColor: \"rgba(0,0,0,0.5)\",\n\t\t\tdisplay: \"flex\",\n\t\t\talignItems: \"center\",\n\t\t\tjustifyContent: \"center\",\n\t\t\tzIndex: \"9999\",\n\t\t\ttransition: \"opacity 0.3s ease\",\n\t\t})\n\n\t\t// Create Container\n\t\tconst container = document.createElement(\"div\")\n\t\tObject.assign(container.style, {\n\t\t\twidth: \"450px\",\n\t\t\theight: \"min(600px, 90vh)\",\n\t\t\tbackgroundColor: \"#fff\",\n\t\t\tborderRadius: \"20px\",\n\t\t\toverflow: \"hidden\",\n\t\t\tposition: \"relative\",\n\t\t\tboxShadow: \"0 25px 50px -12px rgba(0,0,0,0.25)\",\n\t\t})\n\n\t\t// Create Close Button\n\t\tconst closeBtn = document.createElement(\"button\")\n\t\tcloseBtn.innerHTML = \"×\"\n\t\tObject.assign(closeBtn.style, {\n\t\t\tposition: \"absolute\",\n\t\t\tright: \"15px\",\n\t\t\ttop: \"10px\",\n\t\t\tfontSize: \"24px\",\n\t\t\tborder: \"none\",\n\t\t\tbackground: \"none\",\n\t\t\tcursor: \"pointer\",\n\t\t\tcolor: \"#999\",\n\t\t\tzIndex: \"1\",\n\t\t})\n\t\tcloseBtn.onclick = () => {\n\t\t\tthis.close()\n\t\t\toptions?.onCancel?.()\n\t\t}\n\n\t\t// Create Iframe\n\t\tthis.iframe = document.createElement(\"iframe\")\n\t\tthis.iframe.src = finalUrl\n\t\tObject.assign(this.iframe.style, {\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tborder: \"none\",\n\t\t})\n\n\t\tcontainer.appendChild(closeBtn)\n\t\tcontainer.appendChild(this.iframe)\n\t\tthis.modal.appendChild(container)\n\t\tdocument.body.appendChild(this.modal)\n\n\t\t// Listen for messages from checkout page\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\t// Validate origin if specified\n\t\t\tif (options?.allowedOrigin && options.allowedOrigin !== \"*\") {\n\t\t\t\tif (event.origin !== options.allowedOrigin) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data = event.data as PaymentEventData\n\n\t\t\tif (!data || typeof data !== \"object\" || !data.type) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch (data.type) {\n\t\t\t\tcase \"PAYMENT_SUCCESS\":\n\t\t\t\t\toptions?.onSuccess?.(data.orderId)\n\t\t\t\t\t// Auto-close removed, waiting for explicit PAYMENT_CLOSE\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CANCELLED\":\n\t\t\t\t\toptions?.onCancel?.(data.orderId)\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_RESIZE\":\n\t\t\t\t\tif (data.height && container) {\n\t\t\t\t\t\t// Limit max height to 90% of viewport\n\t\t\t\t\t\tconst maxHeight = window.innerHeight * 0.9\n\t\t\t\t\t\tconst newHeight = Math.min(data.height, maxHeight)\n\t\t\t\t\t\tcontainer.style.height = `${newHeight}px`\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CLOSE\":\n\t\t\t\t\tthis.close()\n\t\t\t\t\toptions?.onClose?.()\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twindow.addEventListener(\"message\", this.messageHandler)\n\t}\n\n\t/**\n\t * Close the payment modal\n\t */\n\tclose() {\n\t\tif (typeof window === \"undefined\") return\n\n\t\tif (this.messageHandler) {\n\t\t\twindow.removeEventListener(\"message\", this.messageHandler)\n\t\t\tthis.messageHandler = null\n\t\t}\n\t\tif (this.modal && this.modal.parentNode) {\n\t\t\tthis.modal.parentNode.removeChild(this.modal)\n\t\t}\n\t\tthis.modal = null\n\t\tthis.iframe = null\n\t}\n\n\t/**\n\t * Poll order status from integrator's API endpoint\n\t * @param statusUrl - The integrator's API endpoint to check order status\n\t * @param options - Polling options\n\t * @returns Promise that resolves when order is paid or rejects on timeout/failure\n\t */\n\tasync pollOrderStatus(\n\t\tstatusUrl: string,\n\t\toptions?: PollOptions,\n\t): Promise<OrderStatus> {\n\t\tconst interval = options?.interval || 3000\n\t\tconst timeout = options?.timeout || 300000\n\t\tconst startTime = Date.now()\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst poll = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(statusUrl)\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Status check failed: ${response.status}`)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst status: OrderStatus = await response.json()\n\t\t\t\t\toptions?.onStatusChange?.(status)\n\n\t\t\t\t\tif (status.status === \"PAID\") {\n\t\t\t\t\t\tresolve(status)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif (status.status === \"CANCELLED\" || status.status === \"FAILED\") {\n\t\t\t\t\t\treject(new Error(`Order ${status.status.toLowerCase()}`))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(new Error(\"Polling timeout\"))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Continue polling\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// On network error, continue polling unless timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(error)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpoll()\n\t\t})\n\t}\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n\treturn new PaymentUI()\n}\n"],"mappings":"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","names":[]}
package/dist/client.d.cts CHANGED
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Payment event types from checkout pages
8
8
  */
9
- type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';
9
+ type PaymentEventType = "PAYMENT_SUCCESS" | "PAYMENT_CANCELLED" | "PAYMENT_CLOSE" | "PAYMENT_RESIZE";
10
10
  /**
11
11
  * Payment event data from postMessage
12
12
  */
@@ -20,7 +20,7 @@ interface PaymentEventData {
20
20
  */
21
21
  interface OrderStatus {
22
22
  orderId: string;
23
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
23
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
24
24
  paidAt?: string;
25
25
  channelTransactionId?: string;
26
26
  }
package/dist/client.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Payment event types from checkout pages
8
8
  */
9
- type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';
9
+ type PaymentEventType = "PAYMENT_SUCCESS" | "PAYMENT_CANCELLED" | "PAYMENT_CLOSE" | "PAYMENT_RESIZE";
10
10
  /**
11
11
  * Payment event data from postMessage
12
12
  */
@@ -20,7 +20,7 @@ interface PaymentEventData {
20
20
  */
21
21
  interface OrderStatus {
22
22
  orderId: string;
23
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
23
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
24
24
  paidAt?: string;
25
25
  channelTransactionId?: string;
26
26
  }
package/dist/client.js CHANGED
@@ -36,7 +36,9 @@ var PaymentUI = class {
36
36
  } else if (productId && priceId) {
37
37
  checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
38
38
  } else {
39
- throw new Error("Either productCode or both productId and priceId are required");
39
+ throw new Error(
40
+ "Either productCode or both productId and priceId are required"
41
+ );
40
42
  }
41
43
  }
42
44
  let finalUrl = checkoutUrl;
@@ -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\n /**\n * @deprecated Use checkoutUrl instead\n * Defaults to https://pay.imgto.link\n */\n baseUrl?: string;\n\n /**\n * Checkout page URL (e.g. https://pay.youidian.com)\n * Defaults to https://pay.imgto.link\n */\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,默认 https://pay.imgto.link\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 removed, waiting for explicit PAYMENT_CLOSE\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 =\n\t| \"PAYMENT_SUCCESS\"\n\t| \"PAYMENT_CANCELLED\"\n\t| \"PAYMENT_CLOSE\"\n\t| \"PAYMENT_RESIZE\"\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n\ttype: PaymentEventType\n\torderId?: string\n\theight?: number\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n\tlocale?: string\n\tonSuccess?: (orderId?: string) => void\n\tonCancel?: (orderId?: string) => void\n\tonClose?: () => void\n\t/** Origin to validate postMessage (defaults to '*', set for security) */\n\tallowedOrigin?: string\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n\t/** Polling interval in ms (default: 3000) */\n\tinterval?: number\n\t/** Timeout in ms (default: 300000 = 5 minutes) */\n\ttimeout?: number\n\t/** Callback on each status check */\n\tonStatusChange?: (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\tappId: string\n\tuserId: string\n\t/** Product ID - use with priceId for direct ID-based payment */\n\tproductId?: string\n\t/** Price ID - use with productId for direct ID-based payment */\n\tpriceId?: string\n\t/** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n\tproductCode?: string\n\n\t/**\n\t * @deprecated Use checkoutUrl instead\n\t * Defaults to https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * Checkout page URL (e.g. https://pay.youidian.com)\n\t * Defaults to https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n\tprivate iframe: HTMLIFrameElement | null = null\n\tprivate modal: HTMLDivElement | null = null\n\tprivate messageHandler: ((event: MessageEvent) => void) | null = null\n\n\t/**\n\t * Opens the payment checkout page in an iframe modal.\n\t * @param urlOrParams - The checkout page URL or payment parameters\n\t * @param options - UI options\n\t */\n\topenPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n\t\tif (typeof document === \"undefined\") return // Server-side guard\n\t\tif (this.modal) return // Prevent multiple modals\n\n\t\tlet checkoutUrl: string\n\t\tif (typeof urlOrParams === \"string\") {\n\t\t\tcheckoutUrl = urlOrParams\n\t\t} else {\n\t\t\tconst {\n\t\t\t\tappId,\n\t\t\t\tproductId,\n\t\t\t\tpriceId,\n\t\t\t\tproductCode,\n\t\t\t\tuserId,\n\t\t\t\tcheckoutUrl: checkoutUrlParam,\n\t\t\t\tbaseUrl = \"https://pay.imgto.link\",\n\t\t\t} = urlOrParams\n\n\t\t\t// 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\t\tconst base = (checkoutUrlParam || baseUrl).replace(/\\/$/, \"\")\n\n\t\t\tif (productCode) {\n\t\t\t\t// Use product code route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else if (productId && priceId) {\n\t\t\t\t// Use ID-based route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Either productCode or both productId and priceId are required\",\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tlet finalUrl = checkoutUrl\n\t\tif (options?.locale) {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(checkoutUrl)\n\t\t\t\t// Check if path already starts with /locale/ or is exactly /locale\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!url.pathname.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\turl.pathname !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\turl.pathname = `${localePrefix}${url.pathname}`\n\t\t\t\t\tfinalUrl = url.toString()\n\t\t\t\t}\n\t\t\t} catch (_e) {\n\t\t\t\t// Fallback if URL is relative or invalid\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!checkoutUrl.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\tcheckoutUrl !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\tfinalUrl = `${localePrefix}${checkoutUrl.startsWith(\"/\") ? \"\" : \"/\"}${checkoutUrl}`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Create Modal Overlay\n\t\tthis.modal = document.createElement(\"div\")\n\t\tObject.assign(this.modal.style, {\n\t\t\tposition: \"fixed\",\n\t\t\ttop: \"0\",\n\t\t\tleft: \"0\",\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tbackgroundColor: \"rgba(0,0,0,0.5)\",\n\t\t\tdisplay: \"flex\",\n\t\t\talignItems: \"center\",\n\t\t\tjustifyContent: \"center\",\n\t\t\tzIndex: \"9999\",\n\t\t\ttransition: \"opacity 0.3s ease\",\n\t\t})\n\n\t\t// Create Container\n\t\tconst container = document.createElement(\"div\")\n\t\tObject.assign(container.style, {\n\t\t\twidth: \"450px\",\n\t\t\theight: \"min(600px, 90vh)\",\n\t\t\tbackgroundColor: \"#fff\",\n\t\t\tborderRadius: \"20px\",\n\t\t\toverflow: \"hidden\",\n\t\t\tposition: \"relative\",\n\t\t\tboxShadow: \"0 25px 50px -12px rgba(0,0,0,0.25)\",\n\t\t})\n\n\t\t// Create Close Button\n\t\tconst closeBtn = document.createElement(\"button\")\n\t\tcloseBtn.innerHTML = \"×\"\n\t\tObject.assign(closeBtn.style, {\n\t\t\tposition: \"absolute\",\n\t\t\tright: \"15px\",\n\t\t\ttop: \"10px\",\n\t\t\tfontSize: \"24px\",\n\t\t\tborder: \"none\",\n\t\t\tbackground: \"none\",\n\t\t\tcursor: \"pointer\",\n\t\t\tcolor: \"#999\",\n\t\t\tzIndex: \"1\",\n\t\t})\n\t\tcloseBtn.onclick = () => {\n\t\t\tthis.close()\n\t\t\toptions?.onCancel?.()\n\t\t}\n\n\t\t// Create Iframe\n\t\tthis.iframe = document.createElement(\"iframe\")\n\t\tthis.iframe.src = finalUrl\n\t\tObject.assign(this.iframe.style, {\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tborder: \"none\",\n\t\t})\n\n\t\tcontainer.appendChild(closeBtn)\n\t\tcontainer.appendChild(this.iframe)\n\t\tthis.modal.appendChild(container)\n\t\tdocument.body.appendChild(this.modal)\n\n\t\t// Listen for messages from checkout page\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\t// Validate origin if specified\n\t\t\tif (options?.allowedOrigin && options.allowedOrigin !== \"*\") {\n\t\t\t\tif (event.origin !== options.allowedOrigin) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data = event.data as PaymentEventData\n\n\t\t\tif (!data || typeof data !== \"object\" || !data.type) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch (data.type) {\n\t\t\t\tcase \"PAYMENT_SUCCESS\":\n\t\t\t\t\toptions?.onSuccess?.(data.orderId)\n\t\t\t\t\t// Auto-close removed, waiting for explicit PAYMENT_CLOSE\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CANCELLED\":\n\t\t\t\t\toptions?.onCancel?.(data.orderId)\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_RESIZE\":\n\t\t\t\t\tif (data.height && container) {\n\t\t\t\t\t\t// Limit max height to 90% of viewport\n\t\t\t\t\t\tconst maxHeight = window.innerHeight * 0.9\n\t\t\t\t\t\tconst newHeight = Math.min(data.height, maxHeight)\n\t\t\t\t\t\tcontainer.style.height = `${newHeight}px`\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CLOSE\":\n\t\t\t\t\tthis.close()\n\t\t\t\t\toptions?.onClose?.()\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twindow.addEventListener(\"message\", this.messageHandler)\n\t}\n\n\t/**\n\t * Close the payment modal\n\t */\n\tclose() {\n\t\tif (typeof window === \"undefined\") return\n\n\t\tif (this.messageHandler) {\n\t\t\twindow.removeEventListener(\"message\", this.messageHandler)\n\t\t\tthis.messageHandler = null\n\t\t}\n\t\tif (this.modal && this.modal.parentNode) {\n\t\t\tthis.modal.parentNode.removeChild(this.modal)\n\t\t}\n\t\tthis.modal = null\n\t\tthis.iframe = null\n\t}\n\n\t/**\n\t * Poll order status from integrator's API endpoint\n\t * @param statusUrl - The integrator's API endpoint to check order status\n\t * @param options - Polling options\n\t * @returns Promise that resolves when order is paid or rejects on timeout/failure\n\t */\n\tasync pollOrderStatus(\n\t\tstatusUrl: string,\n\t\toptions?: PollOptions,\n\t): Promise<OrderStatus> {\n\t\tconst interval = options?.interval || 3000\n\t\tconst timeout = options?.timeout || 300000\n\t\tconst startTime = Date.now()\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst poll = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(statusUrl)\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Status check failed: ${response.status}`)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst status: OrderStatus = await response.json()\n\t\t\t\t\toptions?.onStatusChange?.(status)\n\n\t\t\t\t\tif (status.status === \"PAID\") {\n\t\t\t\t\t\tresolve(status)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif (status.status === \"CANCELLED\" || status.status === \"FAILED\") {\n\t\t\t\t\t\treject(new Error(`Order ${status.status.toLowerCase()}`))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(new Error(\"Polling timeout\"))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Continue polling\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// On network error, continue polling unless timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(error)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpoll()\n\t\t})\n\t}\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n\treturn new PaymentUI()\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -72,7 +72,9 @@ var PaymentUI = class {
72
72
  } else if (productId && priceId) {
73
73
  checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
74
74
  } else {
75
- throw new Error("Either productCode or both productId and priceId are required");
75
+ throw new Error(
76
+ "Either productCode or both productId and priceId are required"
77
+ );
76
78
  }
77
79
  }
78
80
  let finalUrl = checkoutUrl;
@@ -312,7 +314,9 @@ var PaymentClient = class {
312
314
  decrypted += decipher.final("utf8");
313
315
  return JSON.parse(decrypted);
314
316
  } catch (error) {
315
- throw new Error("Failed to decrypt payment callback: Invalid secret or tampered data.");
317
+ throw new Error(
318
+ "Failed to decrypt payment callback: Invalid secret or tampered data."
319
+ );
316
320
  }
317
321
  }
318
322
  /**
@@ -363,7 +367,8 @@ var PaymentClient = class {
363
367
  async getOrders(params) {
364
368
  const queryParams = new URLSearchParams();
365
369
  if (params?.page) queryParams.append("page", params.page.toString());
366
- if (params?.pageSize) queryParams.append("pageSize", params.pageSize.toString());
370
+ if (params?.pageSize)
371
+ queryParams.append("pageSize", params.pageSize.toString());
367
372
  if (params?.userId) queryParams.append("userId", params.userId);
368
373
  if (params?.status) queryParams.append("status", params.status);
369
374
  if (params?.startDate) queryParams.append("startDate", params.startDate);
@@ -394,7 +399,10 @@ var PaymentClient = class {
394
399
  * @param amount - Amount to consume
395
400
  */
396
401
  async consumeEntitlement(userId, key, amount) {
397
- return this.request("POST", `/users/${userId}/entitlements/consume`, { key, amount });
402
+ return this.request("POST", `/users/${userId}/entitlements/consume`, {
403
+ key,
404
+ amount
405
+ });
398
406
  }
399
407
  /**
400
408
  * Add numeric entitlement (e.g. refund)
@@ -403,7 +411,10 @@ var PaymentClient = class {
403
411
  * @param amount - Amount to add
404
412
  */
405
413
  async addEntitlement(userId, key, amount) {
406
- return this.request("POST", `/users/${userId}/entitlements/add`, { key, amount });
414
+ return this.request("POST", `/users/${userId}/entitlements/add`, {
415
+ key,
416
+ amount
417
+ });
407
418
  }
408
419
  /**
409
420
  * Toggle boolean entitlement
@@ -412,7 +423,10 @@ var PaymentClient = class {
412
423
  * @param enabled - Whether to enable
413
424
  */
414
425
  async toggleEntitlement(userId, key, enabled) {
415
- return this.request("POST", `/users/${userId}/entitlements/toggle`, { key, enabled });
426
+ return this.request("POST", `/users/${userId}/entitlements/toggle`, {
427
+ key,
428
+ enabled
429
+ });
416
430
  }
417
431
  /**
418
432
  * Generate checkout URL for client-side payment
@@ -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 * // 服务端使用 - 推荐方式(分离 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 /**\n * Checkout page URL (e.g. https://pay.youidian.com)\n * Defaults to https://pay.imgto.link\n */\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,默认 https://pay.imgto.link\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 removed, waiting for explicit PAYMENT_CLOSE\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 * Default: https://pay.imgto.link\n */\n baseUrl?: string;\n\n /**\n * API server URL for backend requests (e.g. https://api.youidian.com)\n * Default: https://pay-api.imgto.link\n */\n apiUrl?: string;\n\n /**\n * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n * Default: https://pay.imgto.link\n */\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 locale?: 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 this.appId = options.appId;\n this.appSecret = options.appSecret;\n\n // apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n const apiUrl = options.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\";\n this.apiUrl = apiUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n const checkoutUrl = options.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\";\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 return this.request(\"GET\", path);\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;;;ACwFO,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;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;;;AClUA,oBAAmB;AAyLZ,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;AAE/D,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ;AAGzB,UAAM,SAAS,QAAQ,UAAU,QAAQ,WAAW;AACpD,SAAK,SAAS,OAAO,QAAQ,OAAO,EAAE;AAGtC,UAAM,cAAc,QAAQ,eAAe,QAAQ,WAAW;AAC9D,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,WAAO,KAAK,QAAQ,OAAO,IAAI;AAAA,EACnC;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"]}
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\nexport type {\n\tOrderStatus as ClientOrderStatus,\n\tPaymentEventData,\n\tPaymentEventType,\n\tPaymentUIOptions,\n\tPollOptions,\n} from \"./client\"\n// Re-export client module (browser-safe)\nexport { createPaymentUI, PaymentUI } from \"./client\"\nexport type {\n\tCreateOrderParams,\n\tCreateOrderResponse,\n\tOrderDetails,\n\tOrderStatus,\n\tPaymentCallbackData,\n\tPaymentClientOptions,\n\tPaymentNotification,\n\tProduct,\n\tProductEntitlements,\n\tProductPrice,\n} from \"./server\"\n// Re-export server module (Node.js only)\nexport { PaymentClient } 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 =\n\t| \"PAYMENT_SUCCESS\"\n\t| \"PAYMENT_CANCELLED\"\n\t| \"PAYMENT_CLOSE\"\n\t| \"PAYMENT_RESIZE\"\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n\ttype: PaymentEventType\n\torderId?: string\n\theight?: number\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n\tlocale?: string\n\tonSuccess?: (orderId?: string) => void\n\tonCancel?: (orderId?: string) => void\n\tonClose?: () => void\n\t/** Origin to validate postMessage (defaults to '*', set for security) */\n\tallowedOrigin?: string\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n\t/** Polling interval in ms (default: 3000) */\n\tinterval?: number\n\t/** Timeout in ms (default: 300000 = 5 minutes) */\n\ttimeout?: number\n\t/** Callback on each status check */\n\tonStatusChange?: (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\tappId: string\n\tuserId: string\n\t/** Product ID - use with priceId for direct ID-based payment */\n\tproductId?: string\n\t/** Price ID - use with productId for direct ID-based payment */\n\tpriceId?: string\n\t/** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n\tproductCode?: string\n\n\t/**\n\t * @deprecated Use checkoutUrl instead\n\t * Defaults to https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * Checkout page URL (e.g. https://pay.youidian.com)\n\t * Defaults to https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n\tprivate iframe: HTMLIFrameElement | null = null\n\tprivate modal: HTMLDivElement | null = null\n\tprivate messageHandler: ((event: MessageEvent) => void) | null = null\n\n\t/**\n\t * Opens the payment checkout page in an iframe modal.\n\t * @param urlOrParams - The checkout page URL or payment parameters\n\t * @param options - UI options\n\t */\n\topenPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n\t\tif (typeof document === \"undefined\") return // Server-side guard\n\t\tif (this.modal) return // Prevent multiple modals\n\n\t\tlet checkoutUrl: string\n\t\tif (typeof urlOrParams === \"string\") {\n\t\t\tcheckoutUrl = urlOrParams\n\t\t} else {\n\t\t\tconst {\n\t\t\t\tappId,\n\t\t\t\tproductId,\n\t\t\t\tpriceId,\n\t\t\t\tproductCode,\n\t\t\t\tuserId,\n\t\t\t\tcheckoutUrl: checkoutUrlParam,\n\t\t\t\tbaseUrl = \"https://pay.imgto.link\",\n\t\t\t} = urlOrParams\n\n\t\t\t// 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\t\tconst base = (checkoutUrlParam || baseUrl).replace(/\\/$/, \"\")\n\n\t\t\tif (productCode) {\n\t\t\t\t// Use product code route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else if (productId && priceId) {\n\t\t\t\t// Use ID-based route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Either productCode or both productId and priceId are required\",\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tlet finalUrl = checkoutUrl\n\t\tif (options?.locale) {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(checkoutUrl)\n\t\t\t\t// Check if path already starts with /locale/ or is exactly /locale\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!url.pathname.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\turl.pathname !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\turl.pathname = `${localePrefix}${url.pathname}`\n\t\t\t\t\tfinalUrl = url.toString()\n\t\t\t\t}\n\t\t\t} catch (_e) {\n\t\t\t\t// Fallback if URL is relative or invalid\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!checkoutUrl.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\tcheckoutUrl !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\tfinalUrl = `${localePrefix}${checkoutUrl.startsWith(\"/\") ? \"\" : \"/\"}${checkoutUrl}`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Create Modal Overlay\n\t\tthis.modal = document.createElement(\"div\")\n\t\tObject.assign(this.modal.style, {\n\t\t\tposition: \"fixed\",\n\t\t\ttop: \"0\",\n\t\t\tleft: \"0\",\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tbackgroundColor: \"rgba(0,0,0,0.5)\",\n\t\t\tdisplay: \"flex\",\n\t\t\talignItems: \"center\",\n\t\t\tjustifyContent: \"center\",\n\t\t\tzIndex: \"9999\",\n\t\t\ttransition: \"opacity 0.3s ease\",\n\t\t})\n\n\t\t// Create Container\n\t\tconst container = document.createElement(\"div\")\n\t\tObject.assign(container.style, {\n\t\t\twidth: \"450px\",\n\t\t\theight: \"min(600px, 90vh)\",\n\t\t\tbackgroundColor: \"#fff\",\n\t\t\tborderRadius: \"20px\",\n\t\t\toverflow: \"hidden\",\n\t\t\tposition: \"relative\",\n\t\t\tboxShadow: \"0 25px 50px -12px rgba(0,0,0,0.25)\",\n\t\t})\n\n\t\t// Create Close Button\n\t\tconst closeBtn = document.createElement(\"button\")\n\t\tcloseBtn.innerHTML = \"×\"\n\t\tObject.assign(closeBtn.style, {\n\t\t\tposition: \"absolute\",\n\t\t\tright: \"15px\",\n\t\t\ttop: \"10px\",\n\t\t\tfontSize: \"24px\",\n\t\t\tborder: \"none\",\n\t\t\tbackground: \"none\",\n\t\t\tcursor: \"pointer\",\n\t\t\tcolor: \"#999\",\n\t\t\tzIndex: \"1\",\n\t\t})\n\t\tcloseBtn.onclick = () => {\n\t\t\tthis.close()\n\t\t\toptions?.onCancel?.()\n\t\t}\n\n\t\t// Create Iframe\n\t\tthis.iframe = document.createElement(\"iframe\")\n\t\tthis.iframe.src = finalUrl\n\t\tObject.assign(this.iframe.style, {\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tborder: \"none\",\n\t\t})\n\n\t\tcontainer.appendChild(closeBtn)\n\t\tcontainer.appendChild(this.iframe)\n\t\tthis.modal.appendChild(container)\n\t\tdocument.body.appendChild(this.modal)\n\n\t\t// Listen for messages from checkout page\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\t// Validate origin if specified\n\t\t\tif (options?.allowedOrigin && options.allowedOrigin !== \"*\") {\n\t\t\t\tif (event.origin !== options.allowedOrigin) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data = event.data as PaymentEventData\n\n\t\t\tif (!data || typeof data !== \"object\" || !data.type) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch (data.type) {\n\t\t\t\tcase \"PAYMENT_SUCCESS\":\n\t\t\t\t\toptions?.onSuccess?.(data.orderId)\n\t\t\t\t\t// Auto-close removed, waiting for explicit PAYMENT_CLOSE\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CANCELLED\":\n\t\t\t\t\toptions?.onCancel?.(data.orderId)\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_RESIZE\":\n\t\t\t\t\tif (data.height && container) {\n\t\t\t\t\t\t// Limit max height to 90% of viewport\n\t\t\t\t\t\tconst maxHeight = window.innerHeight * 0.9\n\t\t\t\t\t\tconst newHeight = Math.min(data.height, maxHeight)\n\t\t\t\t\t\tcontainer.style.height = `${newHeight}px`\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CLOSE\":\n\t\t\t\t\tthis.close()\n\t\t\t\t\toptions?.onClose?.()\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twindow.addEventListener(\"message\", this.messageHandler)\n\t}\n\n\t/**\n\t * Close the payment modal\n\t */\n\tclose() {\n\t\tif (typeof window === \"undefined\") return\n\n\t\tif (this.messageHandler) {\n\t\t\twindow.removeEventListener(\"message\", this.messageHandler)\n\t\t\tthis.messageHandler = null\n\t\t}\n\t\tif (this.modal && this.modal.parentNode) {\n\t\t\tthis.modal.parentNode.removeChild(this.modal)\n\t\t}\n\t\tthis.modal = null\n\t\tthis.iframe = null\n\t}\n\n\t/**\n\t * Poll order status from integrator's API endpoint\n\t * @param statusUrl - The integrator's API endpoint to check order status\n\t * @param options - Polling options\n\t * @returns Promise that resolves when order is paid or rejects on timeout/failure\n\t */\n\tasync pollOrderStatus(\n\t\tstatusUrl: string,\n\t\toptions?: PollOptions,\n\t): Promise<OrderStatus> {\n\t\tconst interval = options?.interval || 3000\n\t\tconst timeout = options?.timeout || 300000\n\t\tconst startTime = Date.now()\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst poll = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(statusUrl)\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Status check failed: ${response.status}`)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst status: OrderStatus = await response.json()\n\t\t\t\t\toptions?.onStatusChange?.(status)\n\n\t\t\t\t\tif (status.status === \"PAID\") {\n\t\t\t\t\t\tresolve(status)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif (status.status === \"CANCELLED\" || status.status === \"FAILED\") {\n\t\t\t\t\t\treject(new Error(`Order ${status.status.toLowerCase()}`))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(new Error(\"Polling timeout\"))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Continue polling\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// On network error, continue polling unless timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(error)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpoll()\n\t\t})\n\t}\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n\treturn 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\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n\t/** Application ID (Required) */\n\tappId: string\n\t/** Application Secret (Required for server-side operations) */\n\tappSecret: string\n\n\t/**\n\t * @deprecated Use apiUrl and checkoutUrl instead\n\t * API Base URL (e.g. https://pay.youidian.com)\n\t * If apiUrl or checkoutUrl is not provided, this will be used as fallback\n\t * Default: https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * API server URL for backend requests (e.g. https://api.youidian.com)\n\t * Default: https://pay-api.imgto.link\n\t */\n\tapiUrl?: string\n\n\t/**\n\t * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n\t * Default: https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n\tproductId?: string\n\tpriceId?: string\n\tchannel?: string\n\tuserId: string\n\treturnUrl?: string\n\tmetadata?: Record<string, any>\n\tmerchantOrderId?: string\n\topenid?: string\n\tlocale?: string\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n\torderId: string\n\tinternalId: string\n\tamount: number\n\tcurrency: string\n\tpayParams: any\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n\tiv: string\n\tencryptedData: string\n\tauthTag: string\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n\torderId: string\n\tmerchantOrderId?: string\n\tstatus: \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tpaidAt: string\n\tchannelTransactionId?: string\n\tmetadata?: Record<string, any>\n}\n\n/**\n * Get Orders Parameters\n */\nexport interface GetOrdersParams {\n\tpage?: number\n\tpageSize?: number\n\tuserId?: string\n\tstatus?: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tstartDate?: string\n\tendDate?: string\n}\n\n/**\n * Order List Item\n */\nexport interface OrderListItem {\n\torderId: string\n\tinternalId: string\n\tmerchantUserId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tchannel?: string\n\tpaidAt?: string\n\tcreatedAt: string\n}\n\n/**\n * Get Orders Response\n */\nexport interface GetOrdersResponse {\n\torders: OrderListItem[]\n\tpagination: {\n\t\ttotal: number\n\t\tpage: number\n\t\tpageSize: number\n\t\ttotalPages: number\n\t}\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n\tprivate readonly appId: string\n\tprivate readonly appSecret: string\n\tprivate readonly apiUrl: string // 用于 API 调用\n\tprivate readonly checkoutUrl: string // 用于生成 checkout URL\n\n\tconstructor(options: PaymentClientOptions) {\n\t\tif (!options.appId) throw new Error(\"appId is required\")\n\t\tif (!options.appSecret) throw new Error(\"appSecret is required\")\n\n\t\tthis.appId = options.appId\n\t\tthis.appSecret = options.appSecret\n\n\t\t// apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n\t\tconst apiUrl =\n\t\t\toptions.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\"\n\t\tthis.apiUrl = apiUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\n\t\t// checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\tconst checkoutUrl =\n\t\t\toptions.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\"\n\t\tthis.checkoutUrl = checkoutUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\t}\n\n\t/**\n\t * Generate SHA256 signature for the request\n\t * Logic: SHA256(appId + appSecret + timestamp)\n\t */\n\tprivate generateSignature(timestamp: number): string {\n\t\tconst str = `${this.appId}${this.appSecret}${timestamp}`\n\t\treturn crypto.createHash(\"sha256\").update(str).digest(\"hex\")\n\t}\n\n\t/**\n\t * Internal request helper for Gateway API\n\t */\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: any,\n\t): Promise<T> {\n\t\tconst timestamp = Date.now()\n\t\tconst signature = this.generateSignature(timestamp)\n\n\t\tconst url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`\n\n\t\tconst headers: HeadersInit = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Pay-Timestamp\": timestamp.toString(),\n\t\t\t\"X-Pay-Sign\": signature,\n\t\t}\n\n\t\tconst options: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t}\n\n\t\tconst response = await fetch(url, options)\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text()\n\t\t\tthrow new Error(`Payment SDK Error (${response.status}): ${errorText}`)\n\t\t}\n\n\t\tconst json = await response.json()\n\t\tif (json.error) {\n\t\t\tthrow new Error(`Payment API Error: ${json.error}`)\n\t\t}\n\n\t\treturn json.data as T\n\t}\n\n\t/**\n\t * Decrypts the callback notification payload using AES-256-GCM.\n\t * @param notification - The encrypted notification from payment webhook\n\t * @returns Decrypted payment callback data\n\t */\n\tdecryptCallback(notification: PaymentNotification): PaymentCallbackData {\n\t\ttry {\n\t\t\tconst { iv, encryptedData, authTag } = notification\n\t\t\tconst key = crypto.createHash(\"sha256\").update(this.appSecret).digest()\n\t\t\tconst decipher = crypto.createDecipheriv(\n\t\t\t\t\"aes-256-gcm\",\n\t\t\t\tkey,\n\t\t\t\tBuffer.from(iv, \"hex\"),\n\t\t\t)\n\n\t\t\tdecipher.setAuthTag(Buffer.from(authTag, \"hex\"))\n\n\t\t\tlet decrypted = decipher.update(encryptedData, \"hex\", \"utf8\")\n\t\t\tdecrypted += decipher.final(\"utf8\")\n\n\t\t\treturn JSON.parse(decrypted)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Failed to decrypt payment callback: Invalid secret or tampered data.\",\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Fetch products for the configured app.\n\t */\n\tasync getProducts(options?: {\n\t\tlocale?: string\n\t\tcurrency?: string\n\t}): Promise<Product[]> {\n\t\tconst params = new URLSearchParams()\n\t\tif (options?.locale) params.append(\"locale\", options.locale)\n\t\tif (options?.currency) params.append(\"currency\", options.currency)\n\n\t\tconst path = params.toString()\n\t\t\t? `/products?${params.toString()}`\n\t\t\t: \"/products\"\n\t\treturn this.request(\"GET\", path)\n\t}\n\n\t/**\n\t * Create a new order\n\t * @param params - Order creation parameters\n\t * @returns Order details with payment parameters\n\t */\n\tasync createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", \"/orders\", params)\n\t}\n\n\t/**\n\t * Pay for an existing order\n\t * @param orderId - The order ID to pay\n\t * @param params - Payment parameters including channel\n\t */\n\tasync payOrder(\n\t\torderId: string,\n\t\tparams: {\n\t\t\tchannel: string\n\t\t\treturnUrl?: string\n\t\t\topenid?: string\n\t\t\t[key: string]: any\n\t\t},\n\t): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", `/orders/${orderId}/pay`, params)\n\t}\n\n\t/**\n\t * Query order status\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderStatus(orderId: string): Promise<OrderStatus> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}`)\n\t}\n\n\t/**\n\t * Get order details (full order information)\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderDetails(orderId: string): Promise<OrderDetails> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}/details`)\n\t}\n\n\t/**\n\t * Get orders list with pagination\n\t * @param params - Query parameters (pagination, filters)\n\t * @returns Orders list and pagination info\n\t */\n\tasync getOrders(params?: GetOrdersParams): Promise<GetOrdersResponse> {\n\t\tconst queryParams = new URLSearchParams()\n\t\tif (params?.page) queryParams.append(\"page\", params.page.toString())\n\t\tif (params?.pageSize)\n\t\t\tqueryParams.append(\"pageSize\", params.pageSize.toString())\n\t\tif (params?.userId) queryParams.append(\"userId\", params.userId)\n\t\tif (params?.status) queryParams.append(\"status\", params.status)\n\t\tif (params?.startDate) queryParams.append(\"startDate\", params.startDate)\n\t\tif (params?.endDate) queryParams.append(\"endDate\", params.endDate)\n\n\t\tconst path = queryParams.toString()\n\t\t\t? `/orders?${queryParams.toString()}`\n\t\t\t: \"/orders\"\n\t\treturn this.request<GetOrdersResponse>(\"GET\", path)\n\t}\n\n\t/**\n\t * Get all entitlements for a user\n\t * @param userId - User ID\n\t */\n\tasync getEntitlements(userId: string): Promise<Record<string, any>> {\n\t\treturn this.request(\"GET\", `/users/${userId}/entitlements`)\n\t}\n\n\t/**\n\t * Get a single entitlement value\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t */\n\tasync getEntitlementValue(userId: string, key: string): Promise<any> {\n\t\tconst entitlements = await this.getEntitlements(userId)\n\t\treturn entitlements[key]\n\t}\n\n\t/**\n\t * Consume numeric entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to consume\n\t */\n\tasync consumeEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/consume`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Add numeric entitlement (e.g. refund)\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to add\n\t */\n\tasync addEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/add`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Toggle boolean entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param enabled - Whether to enable\n\t */\n\tasync toggleEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tenabled: boolean,\n\t): Promise<{ isEnabled: boolean }> {\n\t\t// Toggle endpoint expects POST with enabled flag\n\t\t// However, looking at list_dir, we have toggle/route.ts\n\t\t// I should verify its contract, but assuming standard toggle pattern:\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/toggle`, {\n\t\t\tkey,\n\t\t\tenabled,\n\t\t})\n\t}\n\n\t/**\n\t * Generate checkout URL for client-side payment\n\t * @param productId - Product ID\n\t * @param priceId - Price ID\n\t * @returns Checkout page URL\n\t */\n\tgetCheckoutUrl(productId: string, priceId: string): string {\n\t\treturn `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`\n\t}\n}\n"],"mappings":"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","names":["crypto"]}
package/dist/index.js CHANGED
@@ -36,7 +36,9 @@ var PaymentUI = class {
36
36
  } else if (productId && priceId) {
37
37
  checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
38
38
  } else {
39
- throw new Error("Either productCode or both productId and priceId are required");
39
+ throw new Error(
40
+ "Either productCode or both productId and priceId are required"
41
+ );
40
42
  }
41
43
  }
42
44
  let finalUrl = checkoutUrl;
@@ -276,7 +278,9 @@ var PaymentClient = class {
276
278
  decrypted += decipher.final("utf8");
277
279
  return JSON.parse(decrypted);
278
280
  } catch (error) {
279
- throw new Error("Failed to decrypt payment callback: Invalid secret or tampered data.");
281
+ throw new Error(
282
+ "Failed to decrypt payment callback: Invalid secret or tampered data."
283
+ );
280
284
  }
281
285
  }
282
286
  /**
@@ -327,7 +331,8 @@ var PaymentClient = class {
327
331
  async getOrders(params) {
328
332
  const queryParams = new URLSearchParams();
329
333
  if (params?.page) queryParams.append("page", params.page.toString());
330
- if (params?.pageSize) queryParams.append("pageSize", params.pageSize.toString());
334
+ if (params?.pageSize)
335
+ queryParams.append("pageSize", params.pageSize.toString());
331
336
  if (params?.userId) queryParams.append("userId", params.userId);
332
337
  if (params?.status) queryParams.append("status", params.status);
333
338
  if (params?.startDate) queryParams.append("startDate", params.startDate);
@@ -358,7 +363,10 @@ var PaymentClient = class {
358
363
  * @param amount - Amount to consume
359
364
  */
360
365
  async consumeEntitlement(userId, key, amount) {
361
- return this.request("POST", `/users/${userId}/entitlements/consume`, { key, amount });
366
+ return this.request("POST", `/users/${userId}/entitlements/consume`, {
367
+ key,
368
+ amount
369
+ });
362
370
  }
363
371
  /**
364
372
  * Add numeric entitlement (e.g. refund)
@@ -367,7 +375,10 @@ var PaymentClient = class {
367
375
  * @param amount - Amount to add
368
376
  */
369
377
  async addEntitlement(userId, key, amount) {
370
- return this.request("POST", `/users/${userId}/entitlements/add`, { key, amount });
378
+ return this.request("POST", `/users/${userId}/entitlements/add`, {
379
+ key,
380
+ amount
381
+ });
371
382
  }
372
383
  /**
373
384
  * Toggle boolean entitlement
@@ -376,7 +387,10 @@ var PaymentClient = class {
376
387
  * @param enabled - Whether to enable
377
388
  */
378
389
  async toggleEntitlement(userId, key, enabled) {
379
- return this.request("POST", `/users/${userId}/entitlements/toggle`, { key, enabled });
390
+ return this.request("POST", `/users/${userId}/entitlements/toggle`, {
391
+ key,
392
+ enabled
393
+ });
380
394
  }
381
395
  /**
382
396
  * Generate checkout URL for client-side payment
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/server.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 /**\n * Checkout page URL (e.g. https://pay.youidian.com)\n * Defaults to https://pay.imgto.link\n */\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,默认 https://pay.imgto.link\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 removed, waiting for explicit PAYMENT_CLOSE\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 * Default: https://pay.imgto.link\n */\n baseUrl?: string;\n\n /**\n * API server URL for backend requests (e.g. https://api.youidian.com)\n * Default: https://pay-api.imgto.link\n */\n apiUrl?: string;\n\n /**\n * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n * Default: https://pay.imgto.link\n */\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 locale?: 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 this.appId = options.appId;\n this.appSecret = options.appSecret;\n\n // apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n const apiUrl = options.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\";\n this.apiUrl = apiUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n const checkoutUrl = options.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\";\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 return this.request(\"GET\", path);\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":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/server.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 =\n\t| \"PAYMENT_SUCCESS\"\n\t| \"PAYMENT_CANCELLED\"\n\t| \"PAYMENT_CLOSE\"\n\t| \"PAYMENT_RESIZE\"\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n\ttype: PaymentEventType\n\torderId?: string\n\theight?: number\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n\tlocale?: string\n\tonSuccess?: (orderId?: string) => void\n\tonCancel?: (orderId?: string) => void\n\tonClose?: () => void\n\t/** Origin to validate postMessage (defaults to '*', set for security) */\n\tallowedOrigin?: string\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n\t/** Polling interval in ms (default: 3000) */\n\tinterval?: number\n\t/** Timeout in ms (default: 300000 = 5 minutes) */\n\ttimeout?: number\n\t/** Callback on each status check */\n\tonStatusChange?: (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\tappId: string\n\tuserId: string\n\t/** Product ID - use with priceId for direct ID-based payment */\n\tproductId?: string\n\t/** Price ID - use with productId for direct ID-based payment */\n\tpriceId?: string\n\t/** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n\tproductCode?: string\n\n\t/**\n\t * @deprecated Use checkoutUrl instead\n\t * Defaults to https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * Checkout page URL (e.g. https://pay.youidian.com)\n\t * Defaults to https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n\tprivate iframe: HTMLIFrameElement | null = null\n\tprivate modal: HTMLDivElement | null = null\n\tprivate messageHandler: ((event: MessageEvent) => void) | null = null\n\n\t/**\n\t * Opens the payment checkout page in an iframe modal.\n\t * @param urlOrParams - The checkout page URL or payment parameters\n\t * @param options - UI options\n\t */\n\topenPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n\t\tif (typeof document === \"undefined\") return // Server-side guard\n\t\tif (this.modal) return // Prevent multiple modals\n\n\t\tlet checkoutUrl: string\n\t\tif (typeof urlOrParams === \"string\") {\n\t\t\tcheckoutUrl = urlOrParams\n\t\t} else {\n\t\t\tconst {\n\t\t\t\tappId,\n\t\t\t\tproductId,\n\t\t\t\tpriceId,\n\t\t\t\tproductCode,\n\t\t\t\tuserId,\n\t\t\t\tcheckoutUrl: checkoutUrlParam,\n\t\t\t\tbaseUrl = \"https://pay.imgto.link\",\n\t\t\t} = urlOrParams\n\n\t\t\t// 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\t\tconst base = (checkoutUrlParam || baseUrl).replace(/\\/$/, \"\")\n\n\t\t\tif (productCode) {\n\t\t\t\t// Use product code route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else if (productId && priceId) {\n\t\t\t\t// Use ID-based route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Either productCode or both productId and priceId are required\",\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tlet finalUrl = checkoutUrl\n\t\tif (options?.locale) {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(checkoutUrl)\n\t\t\t\t// Check if path already starts with /locale/ or is exactly /locale\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!url.pathname.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\turl.pathname !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\turl.pathname = `${localePrefix}${url.pathname}`\n\t\t\t\t\tfinalUrl = url.toString()\n\t\t\t\t}\n\t\t\t} catch (_e) {\n\t\t\t\t// Fallback if URL is relative or invalid\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!checkoutUrl.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\tcheckoutUrl !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\tfinalUrl = `${localePrefix}${checkoutUrl.startsWith(\"/\") ? \"\" : \"/\"}${checkoutUrl}`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Create Modal Overlay\n\t\tthis.modal = document.createElement(\"div\")\n\t\tObject.assign(this.modal.style, {\n\t\t\tposition: \"fixed\",\n\t\t\ttop: \"0\",\n\t\t\tleft: \"0\",\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tbackgroundColor: \"rgba(0,0,0,0.5)\",\n\t\t\tdisplay: \"flex\",\n\t\t\talignItems: \"center\",\n\t\t\tjustifyContent: \"center\",\n\t\t\tzIndex: \"9999\",\n\t\t\ttransition: \"opacity 0.3s ease\",\n\t\t})\n\n\t\t// Create Container\n\t\tconst container = document.createElement(\"div\")\n\t\tObject.assign(container.style, {\n\t\t\twidth: \"450px\",\n\t\t\theight: \"min(600px, 90vh)\",\n\t\t\tbackgroundColor: \"#fff\",\n\t\t\tborderRadius: \"20px\",\n\t\t\toverflow: \"hidden\",\n\t\t\tposition: \"relative\",\n\t\t\tboxShadow: \"0 25px 50px -12px rgba(0,0,0,0.25)\",\n\t\t})\n\n\t\t// Create Close Button\n\t\tconst closeBtn = document.createElement(\"button\")\n\t\tcloseBtn.innerHTML = \"×\"\n\t\tObject.assign(closeBtn.style, {\n\t\t\tposition: \"absolute\",\n\t\t\tright: \"15px\",\n\t\t\ttop: \"10px\",\n\t\t\tfontSize: \"24px\",\n\t\t\tborder: \"none\",\n\t\t\tbackground: \"none\",\n\t\t\tcursor: \"pointer\",\n\t\t\tcolor: \"#999\",\n\t\t\tzIndex: \"1\",\n\t\t})\n\t\tcloseBtn.onclick = () => {\n\t\t\tthis.close()\n\t\t\toptions?.onCancel?.()\n\t\t}\n\n\t\t// Create Iframe\n\t\tthis.iframe = document.createElement(\"iframe\")\n\t\tthis.iframe.src = finalUrl\n\t\tObject.assign(this.iframe.style, {\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tborder: \"none\",\n\t\t})\n\n\t\tcontainer.appendChild(closeBtn)\n\t\tcontainer.appendChild(this.iframe)\n\t\tthis.modal.appendChild(container)\n\t\tdocument.body.appendChild(this.modal)\n\n\t\t// Listen for messages from checkout page\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\t// Validate origin if specified\n\t\t\tif (options?.allowedOrigin && options.allowedOrigin !== \"*\") {\n\t\t\t\tif (event.origin !== options.allowedOrigin) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data = event.data as PaymentEventData\n\n\t\t\tif (!data || typeof data !== \"object\" || !data.type) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch (data.type) {\n\t\t\t\tcase \"PAYMENT_SUCCESS\":\n\t\t\t\t\toptions?.onSuccess?.(data.orderId)\n\t\t\t\t\t// Auto-close removed, waiting for explicit PAYMENT_CLOSE\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CANCELLED\":\n\t\t\t\t\toptions?.onCancel?.(data.orderId)\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_RESIZE\":\n\t\t\t\t\tif (data.height && container) {\n\t\t\t\t\t\t// Limit max height to 90% of viewport\n\t\t\t\t\t\tconst maxHeight = window.innerHeight * 0.9\n\t\t\t\t\t\tconst newHeight = Math.min(data.height, maxHeight)\n\t\t\t\t\t\tcontainer.style.height = `${newHeight}px`\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CLOSE\":\n\t\t\t\t\tthis.close()\n\t\t\t\t\toptions?.onClose?.()\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twindow.addEventListener(\"message\", this.messageHandler)\n\t}\n\n\t/**\n\t * Close the payment modal\n\t */\n\tclose() {\n\t\tif (typeof window === \"undefined\") return\n\n\t\tif (this.messageHandler) {\n\t\t\twindow.removeEventListener(\"message\", this.messageHandler)\n\t\t\tthis.messageHandler = null\n\t\t}\n\t\tif (this.modal && this.modal.parentNode) {\n\t\t\tthis.modal.parentNode.removeChild(this.modal)\n\t\t}\n\t\tthis.modal = null\n\t\tthis.iframe = null\n\t}\n\n\t/**\n\t * Poll order status from integrator's API endpoint\n\t * @param statusUrl - The integrator's API endpoint to check order status\n\t * @param options - Polling options\n\t * @returns Promise that resolves when order is paid or rejects on timeout/failure\n\t */\n\tasync pollOrderStatus(\n\t\tstatusUrl: string,\n\t\toptions?: PollOptions,\n\t): Promise<OrderStatus> {\n\t\tconst interval = options?.interval || 3000\n\t\tconst timeout = options?.timeout || 300000\n\t\tconst startTime = Date.now()\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst poll = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(statusUrl)\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Status check failed: ${response.status}`)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst status: OrderStatus = await response.json()\n\t\t\t\t\toptions?.onStatusChange?.(status)\n\n\t\t\t\t\tif (status.status === \"PAID\") {\n\t\t\t\t\t\tresolve(status)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif (status.status === \"CANCELLED\" || status.status === \"FAILED\") {\n\t\t\t\t\t\treject(new Error(`Order ${status.status.toLowerCase()}`))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(new Error(\"Polling timeout\"))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Continue polling\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// On network error, continue polling unless timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(error)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpoll()\n\t\t})\n\t}\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n\treturn 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\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n\t/** Application ID (Required) */\n\tappId: string\n\t/** Application Secret (Required for server-side operations) */\n\tappSecret: string\n\n\t/**\n\t * @deprecated Use apiUrl and checkoutUrl instead\n\t * API Base URL (e.g. https://pay.youidian.com)\n\t * If apiUrl or checkoutUrl is not provided, this will be used as fallback\n\t * Default: https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * API server URL for backend requests (e.g. https://api.youidian.com)\n\t * Default: https://pay-api.imgto.link\n\t */\n\tapiUrl?: string\n\n\t/**\n\t * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n\t * Default: https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n\tproductId?: string\n\tpriceId?: string\n\tchannel?: string\n\tuserId: string\n\treturnUrl?: string\n\tmetadata?: Record<string, any>\n\tmerchantOrderId?: string\n\topenid?: string\n\tlocale?: string\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n\torderId: string\n\tinternalId: string\n\tamount: number\n\tcurrency: string\n\tpayParams: any\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n\tiv: string\n\tencryptedData: string\n\tauthTag: string\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n\torderId: string\n\tmerchantOrderId?: string\n\tstatus: \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tpaidAt: string\n\tchannelTransactionId?: string\n\tmetadata?: Record<string, any>\n}\n\n/**\n * Get Orders Parameters\n */\nexport interface GetOrdersParams {\n\tpage?: number\n\tpageSize?: number\n\tuserId?: string\n\tstatus?: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tstartDate?: string\n\tendDate?: string\n}\n\n/**\n * Order List Item\n */\nexport interface OrderListItem {\n\torderId: string\n\tinternalId: string\n\tmerchantUserId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tchannel?: string\n\tpaidAt?: string\n\tcreatedAt: string\n}\n\n/**\n * Get Orders Response\n */\nexport interface GetOrdersResponse {\n\torders: OrderListItem[]\n\tpagination: {\n\t\ttotal: number\n\t\tpage: number\n\t\tpageSize: number\n\t\ttotalPages: number\n\t}\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n\tprivate readonly appId: string\n\tprivate readonly appSecret: string\n\tprivate readonly apiUrl: string // 用于 API 调用\n\tprivate readonly checkoutUrl: string // 用于生成 checkout URL\n\n\tconstructor(options: PaymentClientOptions) {\n\t\tif (!options.appId) throw new Error(\"appId is required\")\n\t\tif (!options.appSecret) throw new Error(\"appSecret is required\")\n\n\t\tthis.appId = options.appId\n\t\tthis.appSecret = options.appSecret\n\n\t\t// apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n\t\tconst apiUrl =\n\t\t\toptions.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\"\n\t\tthis.apiUrl = apiUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\n\t\t// checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\tconst checkoutUrl =\n\t\t\toptions.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\"\n\t\tthis.checkoutUrl = checkoutUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\t}\n\n\t/**\n\t * Generate SHA256 signature for the request\n\t * Logic: SHA256(appId + appSecret + timestamp)\n\t */\n\tprivate generateSignature(timestamp: number): string {\n\t\tconst str = `${this.appId}${this.appSecret}${timestamp}`\n\t\treturn crypto.createHash(\"sha256\").update(str).digest(\"hex\")\n\t}\n\n\t/**\n\t * Internal request helper for Gateway API\n\t */\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: any,\n\t): Promise<T> {\n\t\tconst timestamp = Date.now()\n\t\tconst signature = this.generateSignature(timestamp)\n\n\t\tconst url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`\n\n\t\tconst headers: HeadersInit = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Pay-Timestamp\": timestamp.toString(),\n\t\t\t\"X-Pay-Sign\": signature,\n\t\t}\n\n\t\tconst options: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t}\n\n\t\tconst response = await fetch(url, options)\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text()\n\t\t\tthrow new Error(`Payment SDK Error (${response.status}): ${errorText}`)\n\t\t}\n\n\t\tconst json = await response.json()\n\t\tif (json.error) {\n\t\t\tthrow new Error(`Payment API Error: ${json.error}`)\n\t\t}\n\n\t\treturn json.data as T\n\t}\n\n\t/**\n\t * Decrypts the callback notification payload using AES-256-GCM.\n\t * @param notification - The encrypted notification from payment webhook\n\t * @returns Decrypted payment callback data\n\t */\n\tdecryptCallback(notification: PaymentNotification): PaymentCallbackData {\n\t\ttry {\n\t\t\tconst { iv, encryptedData, authTag } = notification\n\t\t\tconst key = crypto.createHash(\"sha256\").update(this.appSecret).digest()\n\t\t\tconst decipher = crypto.createDecipheriv(\n\t\t\t\t\"aes-256-gcm\",\n\t\t\t\tkey,\n\t\t\t\tBuffer.from(iv, \"hex\"),\n\t\t\t)\n\n\t\t\tdecipher.setAuthTag(Buffer.from(authTag, \"hex\"))\n\n\t\t\tlet decrypted = decipher.update(encryptedData, \"hex\", \"utf8\")\n\t\t\tdecrypted += decipher.final(\"utf8\")\n\n\t\t\treturn JSON.parse(decrypted)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Failed to decrypt payment callback: Invalid secret or tampered data.\",\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Fetch products for the configured app.\n\t */\n\tasync getProducts(options?: {\n\t\tlocale?: string\n\t\tcurrency?: string\n\t}): Promise<Product[]> {\n\t\tconst params = new URLSearchParams()\n\t\tif (options?.locale) params.append(\"locale\", options.locale)\n\t\tif (options?.currency) params.append(\"currency\", options.currency)\n\n\t\tconst path = params.toString()\n\t\t\t? `/products?${params.toString()}`\n\t\t\t: \"/products\"\n\t\treturn this.request(\"GET\", path)\n\t}\n\n\t/**\n\t * Create a new order\n\t * @param params - Order creation parameters\n\t * @returns Order details with payment parameters\n\t */\n\tasync createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", \"/orders\", params)\n\t}\n\n\t/**\n\t * Pay for an existing order\n\t * @param orderId - The order ID to pay\n\t * @param params - Payment parameters including channel\n\t */\n\tasync payOrder(\n\t\torderId: string,\n\t\tparams: {\n\t\t\tchannel: string\n\t\t\treturnUrl?: string\n\t\t\topenid?: string\n\t\t\t[key: string]: any\n\t\t},\n\t): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", `/orders/${orderId}/pay`, params)\n\t}\n\n\t/**\n\t * Query order status\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderStatus(orderId: string): Promise<OrderStatus> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}`)\n\t}\n\n\t/**\n\t * Get order details (full order information)\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderDetails(orderId: string): Promise<OrderDetails> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}/details`)\n\t}\n\n\t/**\n\t * Get orders list with pagination\n\t * @param params - Query parameters (pagination, filters)\n\t * @returns Orders list and pagination info\n\t */\n\tasync getOrders(params?: GetOrdersParams): Promise<GetOrdersResponse> {\n\t\tconst queryParams = new URLSearchParams()\n\t\tif (params?.page) queryParams.append(\"page\", params.page.toString())\n\t\tif (params?.pageSize)\n\t\t\tqueryParams.append(\"pageSize\", params.pageSize.toString())\n\t\tif (params?.userId) queryParams.append(\"userId\", params.userId)\n\t\tif (params?.status) queryParams.append(\"status\", params.status)\n\t\tif (params?.startDate) queryParams.append(\"startDate\", params.startDate)\n\t\tif (params?.endDate) queryParams.append(\"endDate\", params.endDate)\n\n\t\tconst path = queryParams.toString()\n\t\t\t? `/orders?${queryParams.toString()}`\n\t\t\t: \"/orders\"\n\t\treturn this.request<GetOrdersResponse>(\"GET\", path)\n\t}\n\n\t/**\n\t * Get all entitlements for a user\n\t * @param userId - User ID\n\t */\n\tasync getEntitlements(userId: string): Promise<Record<string, any>> {\n\t\treturn this.request(\"GET\", `/users/${userId}/entitlements`)\n\t}\n\n\t/**\n\t * Get a single entitlement value\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t */\n\tasync getEntitlementValue(userId: string, key: string): Promise<any> {\n\t\tconst entitlements = await this.getEntitlements(userId)\n\t\treturn entitlements[key]\n\t}\n\n\t/**\n\t * Consume numeric entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to consume\n\t */\n\tasync consumeEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/consume`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Add numeric entitlement (e.g. refund)\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to add\n\t */\n\tasync addEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/add`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Toggle boolean entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param enabled - Whether to enable\n\t */\n\tasync toggleEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tenabled: boolean,\n\t): Promise<{ isEnabled: boolean }> {\n\t\t// Toggle endpoint expects POST with enabled flag\n\t\t// However, looking at list_dir, we have toggle/route.ts\n\t\t// I should verify its contract, but assuming standard toggle pattern:\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/toggle`, {\n\t\t\tkey,\n\t\t\tenabled,\n\t\t})\n\t}\n\n\t/**\n\t * Generate checkout URL for client-side payment\n\t * @param productId - Product ID\n\t * @param priceId - Price ID\n\t * @returns Checkout page URL\n\t */\n\tgetCheckoutUrl(productId: string, priceId: string): string {\n\t\treturn `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`\n\t}\n}\n"],"mappings":"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","names":[]}
package/dist/server.cjs CHANGED
@@ -108,7 +108,9 @@ var PaymentClient = class {
108
108
  decrypted += decipher.final("utf8");
109
109
  return JSON.parse(decrypted);
110
110
  } catch (error) {
111
- throw new Error("Failed to decrypt payment callback: Invalid secret or tampered data.");
111
+ throw new Error(
112
+ "Failed to decrypt payment callback: Invalid secret or tampered data."
113
+ );
112
114
  }
113
115
  }
114
116
  /**
@@ -159,7 +161,8 @@ var PaymentClient = class {
159
161
  async getOrders(params) {
160
162
  const queryParams = new URLSearchParams();
161
163
  if (params?.page) queryParams.append("page", params.page.toString());
162
- if (params?.pageSize) queryParams.append("pageSize", params.pageSize.toString());
164
+ if (params?.pageSize)
165
+ queryParams.append("pageSize", params.pageSize.toString());
163
166
  if (params?.userId) queryParams.append("userId", params.userId);
164
167
  if (params?.status) queryParams.append("status", params.status);
165
168
  if (params?.startDate) queryParams.append("startDate", params.startDate);
@@ -190,7 +193,10 @@ var PaymentClient = class {
190
193
  * @param amount - Amount to consume
191
194
  */
192
195
  async consumeEntitlement(userId, key, amount) {
193
- return this.request("POST", `/users/${userId}/entitlements/consume`, { key, amount });
196
+ return this.request("POST", `/users/${userId}/entitlements/consume`, {
197
+ key,
198
+ amount
199
+ });
194
200
  }
195
201
  /**
196
202
  * Add numeric entitlement (e.g. refund)
@@ -199,7 +205,10 @@ var PaymentClient = class {
199
205
  * @param amount - Amount to add
200
206
  */
201
207
  async addEntitlement(userId, key, amount) {
202
- return this.request("POST", `/users/${userId}/entitlements/add`, { key, amount });
208
+ return this.request("POST", `/users/${userId}/entitlements/add`, {
209
+ key,
210
+ amount
211
+ });
203
212
  }
204
213
  /**
205
214
  * Toggle boolean entitlement
@@ -208,7 +217,10 @@ var PaymentClient = class {
208
217
  * @param enabled - Whether to enable
209
218
  */
210
219
  async toggleEntitlement(userId, key, enabled) {
211
- return this.request("POST", `/users/${userId}/entitlements/toggle`, { key, enabled });
220
+ return this.request("POST", `/users/${userId}/entitlements/toggle`, {
221
+ key,
222
+ enabled
223
+ });
212
224
  }
213
225
  /**
214
226
  * Generate checkout URL for client-side payment
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\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 * Default: https://pay.imgto.link\n */\n baseUrl?: string;\n\n /**\n * API server URL for backend requests (e.g. https://api.youidian.com)\n * Default: https://pay-api.imgto.link\n */\n apiUrl?: string;\n\n /**\n * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n * Default: https://pay.imgto.link\n */\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 locale?: 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 this.appId = options.appId;\n this.appSecret = options.appSecret;\n\n // apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n const apiUrl = options.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\";\n this.apiUrl = apiUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n const checkoutUrl = options.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\";\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 return this.request(\"GET\", path);\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;AAKA,oBAAmB;AAyLZ,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;AAE/D,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ;AAGzB,UAAM,SAAS,QAAQ,UAAU,QAAQ,WAAW;AACpD,SAAK,SAAS,OAAO,QAAQ,OAAO,EAAE;AAGtC,UAAM,cAAc,QAAQ,eAAe,QAAQ,WAAW;AAC9D,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,WAAO,KAAK,QAAQ,OAAO,IAAI;AAAA,EACnC;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"]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n\t/** Application ID (Required) */\n\tappId: string\n\t/** Application Secret (Required for server-side operations) */\n\tappSecret: string\n\n\t/**\n\t * @deprecated Use apiUrl and checkoutUrl instead\n\t * API Base URL (e.g. https://pay.youidian.com)\n\t * If apiUrl or checkoutUrl is not provided, this will be used as fallback\n\t * Default: https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * API server URL for backend requests (e.g. https://api.youidian.com)\n\t * Default: https://pay-api.imgto.link\n\t */\n\tapiUrl?: string\n\n\t/**\n\t * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n\t * Default: https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n\tproductId?: string\n\tpriceId?: string\n\tchannel?: string\n\tuserId: string\n\treturnUrl?: string\n\tmetadata?: Record<string, any>\n\tmerchantOrderId?: string\n\topenid?: string\n\tlocale?: string\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n\torderId: string\n\tinternalId: string\n\tamount: number\n\tcurrency: string\n\tpayParams: any\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n\tiv: string\n\tencryptedData: string\n\tauthTag: string\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n\torderId: string\n\tmerchantOrderId?: string\n\tstatus: \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tpaidAt: string\n\tchannelTransactionId?: string\n\tmetadata?: Record<string, any>\n}\n\n/**\n * Get Orders Parameters\n */\nexport interface GetOrdersParams {\n\tpage?: number\n\tpageSize?: number\n\tuserId?: string\n\tstatus?: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tstartDate?: string\n\tendDate?: string\n}\n\n/**\n * Order List Item\n */\nexport interface OrderListItem {\n\torderId: string\n\tinternalId: string\n\tmerchantUserId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tchannel?: string\n\tpaidAt?: string\n\tcreatedAt: string\n}\n\n/**\n * Get Orders Response\n */\nexport interface GetOrdersResponse {\n\torders: OrderListItem[]\n\tpagination: {\n\t\ttotal: number\n\t\tpage: number\n\t\tpageSize: number\n\t\ttotalPages: number\n\t}\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n\tprivate readonly appId: string\n\tprivate readonly appSecret: string\n\tprivate readonly apiUrl: string // 用于 API 调用\n\tprivate readonly checkoutUrl: string // 用于生成 checkout URL\n\n\tconstructor(options: PaymentClientOptions) {\n\t\tif (!options.appId) throw new Error(\"appId is required\")\n\t\tif (!options.appSecret) throw new Error(\"appSecret is required\")\n\n\t\tthis.appId = options.appId\n\t\tthis.appSecret = options.appSecret\n\n\t\t// apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n\t\tconst apiUrl =\n\t\t\toptions.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\"\n\t\tthis.apiUrl = apiUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\n\t\t// checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\tconst checkoutUrl =\n\t\t\toptions.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\"\n\t\tthis.checkoutUrl = checkoutUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\t}\n\n\t/**\n\t * Generate SHA256 signature for the request\n\t * Logic: SHA256(appId + appSecret + timestamp)\n\t */\n\tprivate generateSignature(timestamp: number): string {\n\t\tconst str = `${this.appId}${this.appSecret}${timestamp}`\n\t\treturn crypto.createHash(\"sha256\").update(str).digest(\"hex\")\n\t}\n\n\t/**\n\t * Internal request helper for Gateway API\n\t */\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: any,\n\t): Promise<T> {\n\t\tconst timestamp = Date.now()\n\t\tconst signature = this.generateSignature(timestamp)\n\n\t\tconst url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`\n\n\t\tconst headers: HeadersInit = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Pay-Timestamp\": timestamp.toString(),\n\t\t\t\"X-Pay-Sign\": signature,\n\t\t}\n\n\t\tconst options: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t}\n\n\t\tconst response = await fetch(url, options)\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text()\n\t\t\tthrow new Error(`Payment SDK Error (${response.status}): ${errorText}`)\n\t\t}\n\n\t\tconst json = await response.json()\n\t\tif (json.error) {\n\t\t\tthrow new Error(`Payment API Error: ${json.error}`)\n\t\t}\n\n\t\treturn json.data as T\n\t}\n\n\t/**\n\t * Decrypts the callback notification payload using AES-256-GCM.\n\t * @param notification - The encrypted notification from payment webhook\n\t * @returns Decrypted payment callback data\n\t */\n\tdecryptCallback(notification: PaymentNotification): PaymentCallbackData {\n\t\ttry {\n\t\t\tconst { iv, encryptedData, authTag } = notification\n\t\t\tconst key = crypto.createHash(\"sha256\").update(this.appSecret).digest()\n\t\t\tconst decipher = crypto.createDecipheriv(\n\t\t\t\t\"aes-256-gcm\",\n\t\t\t\tkey,\n\t\t\t\tBuffer.from(iv, \"hex\"),\n\t\t\t)\n\n\t\t\tdecipher.setAuthTag(Buffer.from(authTag, \"hex\"))\n\n\t\t\tlet decrypted = decipher.update(encryptedData, \"hex\", \"utf8\")\n\t\t\tdecrypted += decipher.final(\"utf8\")\n\n\t\t\treturn JSON.parse(decrypted)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Failed to decrypt payment callback: Invalid secret or tampered data.\",\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Fetch products for the configured app.\n\t */\n\tasync getProducts(options?: {\n\t\tlocale?: string\n\t\tcurrency?: string\n\t}): Promise<Product[]> {\n\t\tconst params = new URLSearchParams()\n\t\tif (options?.locale) params.append(\"locale\", options.locale)\n\t\tif (options?.currency) params.append(\"currency\", options.currency)\n\n\t\tconst path = params.toString()\n\t\t\t? `/products?${params.toString()}`\n\t\t\t: \"/products\"\n\t\treturn this.request(\"GET\", path)\n\t}\n\n\t/**\n\t * Create a new order\n\t * @param params - Order creation parameters\n\t * @returns Order details with payment parameters\n\t */\n\tasync createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", \"/orders\", params)\n\t}\n\n\t/**\n\t * Pay for an existing order\n\t * @param orderId - The order ID to pay\n\t * @param params - Payment parameters including channel\n\t */\n\tasync payOrder(\n\t\torderId: string,\n\t\tparams: {\n\t\t\tchannel: string\n\t\t\treturnUrl?: string\n\t\t\topenid?: string\n\t\t\t[key: string]: any\n\t\t},\n\t): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", `/orders/${orderId}/pay`, params)\n\t}\n\n\t/**\n\t * Query order status\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderStatus(orderId: string): Promise<OrderStatus> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}`)\n\t}\n\n\t/**\n\t * Get order details (full order information)\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderDetails(orderId: string): Promise<OrderDetails> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}/details`)\n\t}\n\n\t/**\n\t * Get orders list with pagination\n\t * @param params - Query parameters (pagination, filters)\n\t * @returns Orders list and pagination info\n\t */\n\tasync getOrders(params?: GetOrdersParams): Promise<GetOrdersResponse> {\n\t\tconst queryParams = new URLSearchParams()\n\t\tif (params?.page) queryParams.append(\"page\", params.page.toString())\n\t\tif (params?.pageSize)\n\t\t\tqueryParams.append(\"pageSize\", params.pageSize.toString())\n\t\tif (params?.userId) queryParams.append(\"userId\", params.userId)\n\t\tif (params?.status) queryParams.append(\"status\", params.status)\n\t\tif (params?.startDate) queryParams.append(\"startDate\", params.startDate)\n\t\tif (params?.endDate) queryParams.append(\"endDate\", params.endDate)\n\n\t\tconst path = queryParams.toString()\n\t\t\t? `/orders?${queryParams.toString()}`\n\t\t\t: \"/orders\"\n\t\treturn this.request<GetOrdersResponse>(\"GET\", path)\n\t}\n\n\t/**\n\t * Get all entitlements for a user\n\t * @param userId - User ID\n\t */\n\tasync getEntitlements(userId: string): Promise<Record<string, any>> {\n\t\treturn this.request(\"GET\", `/users/${userId}/entitlements`)\n\t}\n\n\t/**\n\t * Get a single entitlement value\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t */\n\tasync getEntitlementValue(userId: string, key: string): Promise<any> {\n\t\tconst entitlements = await this.getEntitlements(userId)\n\t\treturn entitlements[key]\n\t}\n\n\t/**\n\t * Consume numeric entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to consume\n\t */\n\tasync consumeEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/consume`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Add numeric entitlement (e.g. refund)\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to add\n\t */\n\tasync addEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/add`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Toggle boolean entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param enabled - Whether to enable\n\t */\n\tasync toggleEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tenabled: boolean,\n\t): Promise<{ isEnabled: boolean }> {\n\t\t// Toggle endpoint expects POST with enabled flag\n\t\t// However, looking at list_dir, we have toggle/route.ts\n\t\t// I should verify its contract, but assuming standard toggle pattern:\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/toggle`, {\n\t\t\tkey,\n\t\t\tenabled,\n\t\t})\n\t}\n\n\t/**\n\t * Generate checkout URL for client-side payment\n\t * @param productId - Product ID\n\t * @param priceId - Price ID\n\t * @returns Checkout page URL\n\t */\n\tgetCheckoutUrl(productId: string, priceId: string): string {\n\t\treturn `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`\n\t}\n}\n"],"mappings":"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","names":["crypto"]}
package/dist/server.d.cts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  interface OrderStatus {
9
9
  orderId: string;
10
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
10
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
11
11
  paidAt?: string;
12
12
  channelTransactionId?: string;
13
13
  }
@@ -17,22 +17,26 @@ interface OrderStatus {
17
17
  interface OrderDetails {
18
18
  orderId: string;
19
19
  internalId: string;
20
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
20
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
21
21
  amount: number;
22
22
  currency: string;
23
23
  description?: string;
24
24
  paidAt?: string;
25
25
  createdAt: string;
26
+ channel?: string;
27
+ product?: {
28
+ code: string;
29
+ type: string;
30
+ name: string;
31
+ description?: string;
32
+ entitlements: ProductEntitlements;
33
+ };
26
34
  }
27
35
  /**
28
36
  * Product Entitlements
29
37
  */
30
38
  interface ProductEntitlements {
31
- [key: string]: {
32
- value: any;
33
- name: string;
34
- description: string;
35
- };
39
+ [key: string]: any;
36
40
  }
37
41
  /**
38
42
  * Product Price
@@ -121,7 +125,7 @@ interface PaymentNotification {
121
125
  interface PaymentCallbackData {
122
126
  orderId: string;
123
127
  merchantOrderId?: string;
124
- status: 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
128
+ status: "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
125
129
  amount: number;
126
130
  currency: string;
127
131
  paidAt: string;
@@ -135,7 +139,7 @@ interface GetOrdersParams {
135
139
  page?: number;
136
140
  pageSize?: number;
137
141
  userId?: string;
138
- status?: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
142
+ status?: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
139
143
  startDate?: string;
140
144
  endDate?: string;
141
145
  }
@@ -146,7 +150,7 @@ interface OrderListItem {
146
150
  orderId: string;
147
151
  internalId: string;
148
152
  merchantUserId: string;
149
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
153
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
150
154
  amount: number;
151
155
  currency: string;
152
156
  channel?: string;
package/dist/server.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  interface OrderStatus {
9
9
  orderId: string;
10
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
10
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
11
11
  paidAt?: string;
12
12
  channelTransactionId?: string;
13
13
  }
@@ -17,22 +17,26 @@ interface OrderStatus {
17
17
  interface OrderDetails {
18
18
  orderId: string;
19
19
  internalId: string;
20
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
20
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
21
21
  amount: number;
22
22
  currency: string;
23
23
  description?: string;
24
24
  paidAt?: string;
25
25
  createdAt: string;
26
+ channel?: string;
27
+ product?: {
28
+ code: string;
29
+ type: string;
30
+ name: string;
31
+ description?: string;
32
+ entitlements: ProductEntitlements;
33
+ };
26
34
  }
27
35
  /**
28
36
  * Product Entitlements
29
37
  */
30
38
  interface ProductEntitlements {
31
- [key: string]: {
32
- value: any;
33
- name: string;
34
- description: string;
35
- };
39
+ [key: string]: any;
36
40
  }
37
41
  /**
38
42
  * Product Price
@@ -121,7 +125,7 @@ interface PaymentNotification {
121
125
  interface PaymentCallbackData {
122
126
  orderId: string;
123
127
  merchantOrderId?: string;
124
- status: 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
128
+ status: "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
125
129
  amount: number;
126
130
  currency: string;
127
131
  paidAt: string;
@@ -135,7 +139,7 @@ interface GetOrdersParams {
135
139
  page?: number;
136
140
  pageSize?: number;
137
141
  userId?: string;
138
- status?: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
142
+ status?: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
139
143
  startDate?: string;
140
144
  endDate?: string;
141
145
  }
@@ -146,7 +150,7 @@ interface OrderListItem {
146
150
  orderId: string;
147
151
  internalId: string;
148
152
  merchantUserId: string;
149
- status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
153
+ status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
150
154
  amount: number;
151
155
  currency: string;
152
156
  channel?: string;
package/dist/server.js CHANGED
@@ -76,7 +76,9 @@ var PaymentClient = class {
76
76
  decrypted += decipher.final("utf8");
77
77
  return JSON.parse(decrypted);
78
78
  } catch (error) {
79
- throw new Error("Failed to decrypt payment callback: Invalid secret or tampered data.");
79
+ throw new Error(
80
+ "Failed to decrypt payment callback: Invalid secret or tampered data."
81
+ );
80
82
  }
81
83
  }
82
84
  /**
@@ -127,7 +129,8 @@ var PaymentClient = class {
127
129
  async getOrders(params) {
128
130
  const queryParams = new URLSearchParams();
129
131
  if (params?.page) queryParams.append("page", params.page.toString());
130
- if (params?.pageSize) queryParams.append("pageSize", params.pageSize.toString());
132
+ if (params?.pageSize)
133
+ queryParams.append("pageSize", params.pageSize.toString());
131
134
  if (params?.userId) queryParams.append("userId", params.userId);
132
135
  if (params?.status) queryParams.append("status", params.status);
133
136
  if (params?.startDate) queryParams.append("startDate", params.startDate);
@@ -158,7 +161,10 @@ var PaymentClient = class {
158
161
  * @param amount - Amount to consume
159
162
  */
160
163
  async consumeEntitlement(userId, key, amount) {
161
- return this.request("POST", `/users/${userId}/entitlements/consume`, { key, amount });
164
+ return this.request("POST", `/users/${userId}/entitlements/consume`, {
165
+ key,
166
+ amount
167
+ });
162
168
  }
163
169
  /**
164
170
  * Add numeric entitlement (e.g. refund)
@@ -167,7 +173,10 @@ var PaymentClient = class {
167
173
  * @param amount - Amount to add
168
174
  */
169
175
  async addEntitlement(userId, key, amount) {
170
- return this.request("POST", `/users/${userId}/entitlements/add`, { key, amount });
176
+ return this.request("POST", `/users/${userId}/entitlements/add`, {
177
+ key,
178
+ amount
179
+ });
171
180
  }
172
181
  /**
173
182
  * Toggle boolean entitlement
@@ -176,7 +185,10 @@ var PaymentClient = class {
176
185
  * @param enabled - Whether to enable
177
186
  */
178
187
  async toggleEntitlement(userId, key, enabled) {
179
- return this.request("POST", `/users/${userId}/entitlements/toggle`, { key, enabled });
188
+ return this.request("POST", `/users/${userId}/entitlements/toggle`, {
189
+ key,
190
+ enabled
191
+ });
180
192
  }
181
193
  /**
182
194
  * Generate checkout URL for client-side payment
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\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 * Default: https://pay.imgto.link\n */\n baseUrl?: string;\n\n /**\n * API server URL for backend requests (e.g. https://api.youidian.com)\n * Default: https://pay-api.imgto.link\n */\n apiUrl?: string;\n\n /**\n * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n * Default: https://pay.imgto.link\n */\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 locale?: 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 this.appId = options.appId;\n this.appSecret = options.appSecret;\n\n // apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n const apiUrl = options.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\";\n this.apiUrl = apiUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n const checkoutUrl = options.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\";\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 return this.request(\"GET\", path);\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":"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","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n\t/** Application ID (Required) */\n\tappId: string\n\t/** Application Secret (Required for server-side operations) */\n\tappSecret: string\n\n\t/**\n\t * @deprecated Use apiUrl and checkoutUrl instead\n\t * API Base URL (e.g. https://pay.youidian.com)\n\t * If apiUrl or checkoutUrl is not provided, this will be used as fallback\n\t * Default: https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * API server URL for backend requests (e.g. https://api.youidian.com)\n\t * Default: https://pay-api.imgto.link\n\t */\n\tapiUrl?: string\n\n\t/**\n\t * Checkout page URL for client-side payment (e.g. https://pay.youidian.com)\n\t * Default: https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Create Order Parameters\n */\nexport interface CreateOrderParams {\n\tproductId?: string\n\tpriceId?: string\n\tchannel?: string\n\tuserId: string\n\treturnUrl?: string\n\tmetadata?: Record<string, any>\n\tmerchantOrderId?: string\n\topenid?: string\n\tlocale?: string\n}\n\n/**\n * Create Order Response\n */\nexport interface CreateOrderResponse {\n\torderId: string\n\tinternalId: string\n\tamount: number\n\tcurrency: string\n\tpayParams: any\n}\n\n/**\n * Payment Callback Notification\n */\nexport interface PaymentNotification {\n\tiv: string\n\tencryptedData: string\n\tauthTag: string\n}\n\n/**\n * Decrypted Payment Callback Data\n */\nexport interface PaymentCallbackData {\n\torderId: string\n\tmerchantOrderId?: string\n\tstatus: \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tpaidAt: string\n\tchannelTransactionId?: string\n\tmetadata?: Record<string, any>\n}\n\n/**\n * Get Orders Parameters\n */\nexport interface GetOrdersParams {\n\tpage?: number\n\tpageSize?: number\n\tuserId?: string\n\tstatus?: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tstartDate?: string\n\tendDate?: string\n}\n\n/**\n * Order List Item\n */\nexport interface OrderListItem {\n\torderId: string\n\tinternalId: string\n\tmerchantUserId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tchannel?: string\n\tpaidAt?: string\n\tcreatedAt: string\n}\n\n/**\n * Get Orders Response\n */\nexport interface GetOrdersResponse {\n\torders: OrderListItem[]\n\tpagination: {\n\t\ttotal: number\n\t\tpage: number\n\t\tpageSize: number\n\t\ttotalPages: number\n\t}\n}\n\n/**\n * Server-side Payment Client\n * 服务端支付客户端,用于创建订单、查询状态、解密回调\n */\nexport class PaymentClient {\n\tprivate readonly appId: string\n\tprivate readonly appSecret: string\n\tprivate readonly apiUrl: string // 用于 API 调用\n\tprivate readonly checkoutUrl: string // 用于生成 checkout URL\n\n\tconstructor(options: PaymentClientOptions) {\n\t\tif (!options.appId) throw new Error(\"appId is required\")\n\t\tif (!options.appSecret) throw new Error(\"appSecret is required\")\n\n\t\tthis.appId = options.appId\n\t\tthis.appSecret = options.appSecret\n\n\t\t// apiUrl: 优先使用 apiUrl,其次 baseUrl,默认 https://pay-api.imgto.link\n\t\tconst apiUrl =\n\t\t\toptions.apiUrl || options.baseUrl || \"https://pay-api.imgto.link\"\n\t\tthis.apiUrl = apiUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\n\t\t// checkoutUrl: 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\tconst checkoutUrl =\n\t\t\toptions.checkoutUrl || options.baseUrl || \"https://pay.imgto.link\"\n\t\tthis.checkoutUrl = checkoutUrl.replace(/\\/$/, \"\") // Remove trailing slash\n\t}\n\n\t/**\n\t * Generate SHA256 signature for the request\n\t * Logic: SHA256(appId + appSecret + timestamp)\n\t */\n\tprivate generateSignature(timestamp: number): string {\n\t\tconst str = `${this.appId}${this.appSecret}${timestamp}`\n\t\treturn crypto.createHash(\"sha256\").update(str).digest(\"hex\")\n\t}\n\n\t/**\n\t * Internal request helper for Gateway API\n\t */\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: any,\n\t): Promise<T> {\n\t\tconst timestamp = Date.now()\n\t\tconst signature = this.generateSignature(timestamp)\n\n\t\tconst url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`\n\n\t\tconst headers: HeadersInit = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-Pay-Timestamp\": timestamp.toString(),\n\t\t\t\"X-Pay-Sign\": signature,\n\t\t}\n\n\t\tconst options: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t}\n\n\t\tconst response = await fetch(url, options)\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text()\n\t\t\tthrow new Error(`Payment SDK Error (${response.status}): ${errorText}`)\n\t\t}\n\n\t\tconst json = await response.json()\n\t\tif (json.error) {\n\t\t\tthrow new Error(`Payment API Error: ${json.error}`)\n\t\t}\n\n\t\treturn json.data as T\n\t}\n\n\t/**\n\t * Decrypts the callback notification payload using AES-256-GCM.\n\t * @param notification - The encrypted notification from payment webhook\n\t * @returns Decrypted payment callback data\n\t */\n\tdecryptCallback(notification: PaymentNotification): PaymentCallbackData {\n\t\ttry {\n\t\t\tconst { iv, encryptedData, authTag } = notification\n\t\t\tconst key = crypto.createHash(\"sha256\").update(this.appSecret).digest()\n\t\t\tconst decipher = crypto.createDecipheriv(\n\t\t\t\t\"aes-256-gcm\",\n\t\t\t\tkey,\n\t\t\t\tBuffer.from(iv, \"hex\"),\n\t\t\t)\n\n\t\t\tdecipher.setAuthTag(Buffer.from(authTag, \"hex\"))\n\n\t\t\tlet decrypted = decipher.update(encryptedData, \"hex\", \"utf8\")\n\t\t\tdecrypted += decipher.final(\"utf8\")\n\n\t\t\treturn JSON.parse(decrypted)\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Failed to decrypt payment callback: Invalid secret or tampered data.\",\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Fetch products for the configured app.\n\t */\n\tasync getProducts(options?: {\n\t\tlocale?: string\n\t\tcurrency?: string\n\t}): Promise<Product[]> {\n\t\tconst params = new URLSearchParams()\n\t\tif (options?.locale) params.append(\"locale\", options.locale)\n\t\tif (options?.currency) params.append(\"currency\", options.currency)\n\n\t\tconst path = params.toString()\n\t\t\t? `/products?${params.toString()}`\n\t\t\t: \"/products\"\n\t\treturn this.request(\"GET\", path)\n\t}\n\n\t/**\n\t * Create a new order\n\t * @param params - Order creation parameters\n\t * @returns Order details with payment parameters\n\t */\n\tasync createOrder(params: CreateOrderParams): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", \"/orders\", params)\n\t}\n\n\t/**\n\t * Pay for an existing order\n\t * @param orderId - The order ID to pay\n\t * @param params - Payment parameters including channel\n\t */\n\tasync payOrder(\n\t\torderId: string,\n\t\tparams: {\n\t\t\tchannel: string\n\t\t\treturnUrl?: string\n\t\t\topenid?: string\n\t\t\t[key: string]: any\n\t\t},\n\t): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", `/orders/${orderId}/pay`, params)\n\t}\n\n\t/**\n\t * Query order status\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderStatus(orderId: string): Promise<OrderStatus> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}`)\n\t}\n\n\t/**\n\t * Get order details (full order information)\n\t * @param orderId - The order ID to query\n\t */\n\tasync getOrderDetails(orderId: string): Promise<OrderDetails> {\n\t\treturn this.request(\"GET\", `/orders/${orderId}/details`)\n\t}\n\n\t/**\n\t * Get orders list with pagination\n\t * @param params - Query parameters (pagination, filters)\n\t * @returns Orders list and pagination info\n\t */\n\tasync getOrders(params?: GetOrdersParams): Promise<GetOrdersResponse> {\n\t\tconst queryParams = new URLSearchParams()\n\t\tif (params?.page) queryParams.append(\"page\", params.page.toString())\n\t\tif (params?.pageSize)\n\t\t\tqueryParams.append(\"pageSize\", params.pageSize.toString())\n\t\tif (params?.userId) queryParams.append(\"userId\", params.userId)\n\t\tif (params?.status) queryParams.append(\"status\", params.status)\n\t\tif (params?.startDate) queryParams.append(\"startDate\", params.startDate)\n\t\tif (params?.endDate) queryParams.append(\"endDate\", params.endDate)\n\n\t\tconst path = queryParams.toString()\n\t\t\t? `/orders?${queryParams.toString()}`\n\t\t\t: \"/orders\"\n\t\treturn this.request<GetOrdersResponse>(\"GET\", path)\n\t}\n\n\t/**\n\t * Get all entitlements for a user\n\t * @param userId - User ID\n\t */\n\tasync getEntitlements(userId: string): Promise<Record<string, any>> {\n\t\treturn this.request(\"GET\", `/users/${userId}/entitlements`)\n\t}\n\n\t/**\n\t * Get a single entitlement value\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t */\n\tasync getEntitlementValue(userId: string, key: string): Promise<any> {\n\t\tconst entitlements = await this.getEntitlements(userId)\n\t\treturn entitlements[key]\n\t}\n\n\t/**\n\t * Consume numeric entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to consume\n\t */\n\tasync consumeEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/consume`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Add numeric entitlement (e.g. refund)\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param amount - Amount to add\n\t */\n\tasync addEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tamount: number,\n\t): Promise<{ balance: number }> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/add`, {\n\t\t\tkey,\n\t\t\tamount,\n\t\t})\n\t}\n\n\t/**\n\t * Toggle boolean entitlement\n\t * @param userId - User ID\n\t * @param key - Entitlement key\n\t * @param enabled - Whether to enable\n\t */\n\tasync toggleEntitlement(\n\t\tuserId: string,\n\t\tkey: string,\n\t\tenabled: boolean,\n\t): Promise<{ isEnabled: boolean }> {\n\t\t// Toggle endpoint expects POST with enabled flag\n\t\t// However, looking at list_dir, we have toggle/route.ts\n\t\t// I should verify its contract, but assuming standard toggle pattern:\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/toggle`, {\n\t\t\tkey,\n\t\t\tenabled,\n\t\t})\n\t}\n\n\t/**\n\t * Generate checkout URL for client-side payment\n\t * @param productId - Product ID\n\t * @param priceId - Price ID\n\t * @returns Checkout page URL\n\t */\n\tgetCheckoutUrl(productId: string, priceId: string): string {\n\t\treturn `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`\n\t}\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,52 +1,44 @@
1
1
  {
2
- "name": "@youidian/pay-sdk",
3
- "version": "1.0.10",
4
- "description": "Youidian Payment SDK - 统一支付集成 SDK",
5
- "author": "Youidian",
6
- "license": "MIT",
7
- "type": "module",
8
- "main": "./dist/index.cjs",
9
- "module": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js",
15
- "require": "./dist/index.cjs"
16
- },
17
- "./client": {
18
- "types": "./dist/client.d.ts",
19
- "import": "./dist/client.js",
20
- "require": "./dist/client.cjs"
21
- },
22
- "./server": {
23
- "types": "./dist/server.d.ts",
24
- "import": "./dist/server.js",
25
- "require": "./dist/server.cjs"
26
- }
27
- },
28
- "files": [
29
- "dist"
30
- ],
31
- "scripts": {
32
- "build": "tsup",
33
- "prepublishOnly": "npm run build"
34
- },
35
- "dependencies": {},
36
- "devDependencies": {
37
- "@repo/typescript-config": "*",
38
- "tsup": "^8.0.0",
39
- "typescript": "^5.9.3"
40
- },
41
- "keywords": [
42
- "payment",
43
- "sdk",
44
- "paypal",
45
- "wechat-pay",
46
- "alipay"
47
- ],
48
- "repository": {
49
- "type": "git",
50
- "url": "https://github.com/youidian/youidian-pay"
51
- }
52
- }
2
+ "name": "@youidian/pay-sdk",
3
+ "version": "1.0.12",
4
+ "description": "Youidian Payment SDK - 统一支付集成 SDK",
5
+ "author": "Youidian",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ },
17
+ "./client": {
18
+ "types": "./dist/client.d.ts",
19
+ "import": "./dist/client.js",
20
+ "require": "./dist/client.cjs"
21
+ },
22
+ "./server": {
23
+ "types": "./dist/server.d.ts",
24
+ "import": "./dist/server.js",
25
+ "require": "./dist/server.cjs"
26
+ }
27
+ },
28
+ "files": ["dist"],
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "dependencies": {},
34
+ "devDependencies": {
35
+ "@repo/typescript-config": "*",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.9.3"
38
+ },
39
+ "keywords": ["payment", "sdk", "paypal", "wechat-pay", "alipay"],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/youidian/youidian-pay"
43
+ }
44
+ }