@youidian/pay-sdk 1.0.9 → 1.0.11
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 +3 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +3 -2
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +20 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +20 -7
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +17 -5
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +14 -5
- package/dist/server.d.ts +14 -5
- package/dist/server.js +17 -5
- package/dist/server.js.map +1 -1
- package/package.json +43 -51
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(
|
|
62
|
+
throw new Error(
|
|
63
|
+
"Either productCode or both productId and priceId are required"
|
|
64
|
+
);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
let finalUrl = checkoutUrl;
|
|
@@ -143,7 +145,6 @@ var PaymentUI = class {
|
|
|
143
145
|
switch (data.type) {
|
|
144
146
|
case "PAYMENT_SUCCESS":
|
|
145
147
|
options?.onSuccess?.(data.orderId);
|
|
146
|
-
setTimeout(() => this.close(), 2e3);
|
|
147
148
|
break;
|
|
148
149
|
case "PAYMENT_CANCELLED":
|
|
149
150
|
options?.onCancel?.(data.orderId);
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n\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 after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwFO,IAAM,YAAN,MAAgB;AAAA,EAAhB;AACH,wBAAQ,UAAmC;AAC3C,wBAAQ,SAA+B;AACvC,wBAAQ,kBAAyD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjE,YAAY,aAAqC,SAA4B;AACzE,QAAI,OAAO,aAAa,YAAa;AACrC,QAAI,KAAK,MAAO;AAEhB,QAAI;AACJ,QAAI,OAAO,gBAAgB,UAAU;AACjC,oBAAc;AAAA,IAClB,OAAO;AACH,YAAM;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa;AAAA,QACb,UAAU;AAAA,MACd,IAAI;AAGJ,YAAM,QAAQ,oBAAoB,SAAS,QAAQ,OAAO,EAAE;AAE5D,UAAI,aAAa;AAEb,sBAAc,GAAG,IAAI,aAAa,KAAK,SAAS,WAAW,WAAW,mBAAmB,MAAM,CAAC;AAAA,MACpG,WAAW,aAAa,SAAS;AAE7B,sBAAc,GAAG,IAAI,aAAa,KAAK,IAAI,SAAS,IAAI,OAAO,WAAW,mBAAmB,MAAM,CAAC;AAAA,MACxG,OAAO;AACH,cAAM,IAAI,MAAM,+DAA+D;AAAA,MACnF;AAAA,IACJ;AAEA,QAAI,WAAW;AACf,QAAI,SAAS,QAAQ;AACjB,UAAI;AACA,cAAM,MAAM,IAAI,IAAI,WAAW;AAE/B,cAAM,eAAe,IAAI,QAAQ,MAAM;AACvC,YAAI,CAAC,IAAI,SAAS,WAAW,GAAG,YAAY,GAAG,KAAK,IAAI,aAAa,cAAc;AAC/E,cAAI,WAAW,GAAG,YAAY,GAAG,IAAI,QAAQ;AAC7C,qBAAW,IAAI,SAAS;AAAA,QAC5B;AAAA,MACJ,SAAS,IAAI;AAET,cAAM,eAAe,IAAI,QAAQ,MAAM;AACvC,YAAI,CAAC,YAAY,WAAW,GAAG,YAAY,GAAG,KAAK,gBAAgB,cAAc;AAC7E,qBAAW,GAAG,YAAY,GAAG,YAAY,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,WAAW;AAAA,QACrF;AAAA,MACJ;AAAA,IACJ;AAGA,SAAK,QAAQ,SAAS,cAAc,KAAK;AACzC,WAAO,OAAO,KAAK,MAAM,OAAO;AAAA,MAC5B,UAAU;AAAA,MACV,KAAK;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,YAAY;AAAA,IAChB,CAAC;AAGD,UAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,WAAO,OAAO,UAAU,OAAO;AAAA,MAC3B,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,UAAU;AAAA,MACV,UAAU;AAAA,MACV,WAAW;AAAA,IACf,CAAC;AAGD,UAAM,WAAW,SAAS,cAAc,QAAQ;AAChD,aAAS,YAAY;AACrB,WAAO,OAAO,SAAS,OAAO;AAAA,MAC1B,UAAU;AAAA,MACV,OAAO;AAAA,MACP,KAAK;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACZ,CAAC;AACD,aAAS,UAAU,MAAM;AACrB,WAAK,MAAM;AACX,eAAS,WAAW;AAAA,IACxB;AAGA,SAAK,SAAS,SAAS,cAAc,QAAQ;AAC7C,SAAK,OAAO,MAAM;AAClB,WAAO,OAAO,KAAK,OAAO,OAAO;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,IACZ,CAAC;AAED,cAAU,YAAY,QAAQ;AAC9B,cAAU,YAAY,KAAK,MAAM;AACjC,SAAK,MAAM,YAAY,SAAS;AAChC,aAAS,KAAK,YAAY,KAAK,KAAK;AAGpC,SAAK,iBAAiB,CAAC,UAAwB;AAE3C,UAAI,SAAS,iBAAiB,QAAQ,kBAAkB,KAAK;AACzD,YAAI,MAAM,WAAW,QAAQ,eAAe;AACxC;AAAA,QACJ;AAAA,MACJ;AAEA,YAAM,OAAO,MAAM;AAEnB,UAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,MAAM;AACjD;AAAA,MACJ;AAEA,cAAQ,KAAK,MAAM;AAAA,QACf,KAAK;AACD,mBAAS,YAAY,KAAK,OAAO;AAEjC,qBAAW,MAAM,KAAK,MAAM,GAAG,GAAI;AACnC;AAAA,QACJ,KAAK;AACD,mBAAS,WAAW,KAAK,OAAO;AAChC;AAAA,QACJ,KAAK;AACD,cAAI,KAAK,UAAU,WAAW;AAE1B,kBAAM,YAAY,OAAO,cAAc;AACvC,kBAAM,YAAY,KAAK,IAAI,KAAK,QAAQ,SAAS;AACjD,sBAAU,MAAM,SAAS,GAAG,SAAS;AAAA,UACzC;AACA;AAAA,QACJ,KAAK;AACD,eAAK,MAAM;AACX,mBAAS,UAAU;AACnB;AAAA,MACR;AAAA,IACJ;AACA,WAAO,iBAAiB,WAAW,KAAK,cAAc;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ;AACJ,QAAI,OAAO,WAAW,YAAa;AAEnC,QAAI,KAAK,gBAAgB;AACrB,aAAO,oBAAoB,WAAW,KAAK,cAAc;AACzD,WAAK,iBAAiB;AAAA,IAC1B;AACA,QAAI,KAAK,SAAS,KAAK,MAAM,YAAY;AACrC,WAAK,MAAM,WAAW,YAAY,KAAK,KAAK;AAAA,IAChD;AACA,SAAK,QAAQ;AACb,SAAK,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,WAAmB,SAA6C;AAClF,UAAM,WAAW,SAAS,YAAY;AACtC,UAAM,UAAU,SAAS,WAAW;AACpC,UAAM,YAAY,KAAK,IAAI;AAE3B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACpC,YAAM,OAAO,YAAY;AACrB,YAAI;AACA,gBAAM,WAAW,MAAM,MAAM,SAAS;AACtC,cAAI,CAAC,SAAS,IAAI;AACd,kBAAM,IAAI,MAAM,wBAAwB,SAAS,MAAM,EAAE;AAAA,UAC7D;AAEA,gBAAM,SAAsB,MAAM,SAAS,KAAK;AAChD,mBAAS,iBAAiB,MAAM;AAEhC,cAAI,OAAO,WAAW,QAAQ;AAC1B,oBAAQ,MAAM;AACd;AAAA,UACJ;AAEA,cAAI,OAAO,WAAW,eAAe,OAAO,WAAW,UAAU;AAC7D,mBAAO,IAAI,MAAM,SAAS,OAAO,OAAO,YAAY,CAAC,EAAE,CAAC;AACxD;AAAA,UACJ;AAGA,cAAI,KAAK,IAAI,IAAI,YAAY,SAAS;AAClC,mBAAO,IAAI,MAAM,iBAAiB,CAAC;AACnC;AAAA,UACJ;AAGA,qBAAW,MAAM,QAAQ;AAAA,QAC7B,SAAS,OAAO;AAEZ,cAAI,KAAK,IAAI,IAAI,YAAY,SAAS;AAClC,mBAAO,KAAK;AACZ;AAAA,UACJ;AACA,qBAAW,MAAM,QAAQ;AAAA,QAC7B;AAAA,MACJ;AAEA,WAAK;AAAA,IACT,CAAC;AAAA,EACL;AACJ;AAKO,SAAS,kBAA6B;AACzC,SAAO,IAAI,UAAU;AACzB;","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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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(
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Either productCode or both productId and priceId are required"
|
|
41
|
+
);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
let finalUrl = checkoutUrl;
|
|
@@ -120,7 +122,6 @@ var PaymentUI = class {
|
|
|
120
122
|
switch (data.type) {
|
|
121
123
|
case "PAYMENT_SUCCESS":
|
|
122
124
|
options?.onSuccess?.(data.orderId);
|
|
123
|
-
setTimeout(() => this.close(), 2e3);
|
|
124
125
|
break;
|
|
125
126
|
case "PAYMENT_CANCELLED":
|
|
126
127
|
options?.onCancel?.(data.orderId);
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n appId: string;\n userId: string;\n /** Product ID - use with priceId for direct ID-based payment */\n productId?: string;\n /** Price ID - use with productId for direct ID-based payment */\n priceId?: string;\n /** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n productCode?: string;\n\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 after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType =\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(
|
|
75
|
+
throw new Error(
|
|
76
|
+
"Either productCode or both productId and priceId are required"
|
|
77
|
+
);
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
let finalUrl = checkoutUrl;
|
|
@@ -156,7 +158,6 @@ var PaymentUI = class {
|
|
|
156
158
|
switch (data.type) {
|
|
157
159
|
case "PAYMENT_SUCCESS":
|
|
158
160
|
options?.onSuccess?.(data.orderId);
|
|
159
|
-
setTimeout(() => this.close(), 2e3);
|
|
160
161
|
break;
|
|
161
162
|
case "PAYMENT_CANCELLED":
|
|
162
163
|
options?.onCancel?.(data.orderId);
|
|
@@ -313,7 +314,9 @@ var PaymentClient = class {
|
|
|
313
314
|
decrypted += decipher.final("utf8");
|
|
314
315
|
return JSON.parse(decrypted);
|
|
315
316
|
} catch (error) {
|
|
316
|
-
throw new Error(
|
|
317
|
+
throw new Error(
|
|
318
|
+
"Failed to decrypt payment callback: Invalid secret or tampered data."
|
|
319
|
+
);
|
|
317
320
|
}
|
|
318
321
|
}
|
|
319
322
|
/**
|
|
@@ -364,7 +367,8 @@ var PaymentClient = class {
|
|
|
364
367
|
async getOrders(params) {
|
|
365
368
|
const queryParams = new URLSearchParams();
|
|
366
369
|
if (params?.page) queryParams.append("page", params.page.toString());
|
|
367
|
-
if (params?.pageSize)
|
|
370
|
+
if (params?.pageSize)
|
|
371
|
+
queryParams.append("pageSize", params.pageSize.toString());
|
|
368
372
|
if (params?.userId) queryParams.append("userId", params.userId);
|
|
369
373
|
if (params?.status) queryParams.append("status", params.status);
|
|
370
374
|
if (params?.startDate) queryParams.append("startDate", params.startDate);
|
|
@@ -395,7 +399,10 @@ var PaymentClient = class {
|
|
|
395
399
|
* @param amount - Amount to consume
|
|
396
400
|
*/
|
|
397
401
|
async consumeEntitlement(userId, key, amount) {
|
|
398
|
-
return this.request("POST", `/users/${userId}/entitlements/consume`, {
|
|
402
|
+
return this.request("POST", `/users/${userId}/entitlements/consume`, {
|
|
403
|
+
key,
|
|
404
|
+
amount
|
|
405
|
+
});
|
|
399
406
|
}
|
|
400
407
|
/**
|
|
401
408
|
* Add numeric entitlement (e.g. refund)
|
|
@@ -404,7 +411,10 @@ var PaymentClient = class {
|
|
|
404
411
|
* @param amount - Amount to add
|
|
405
412
|
*/
|
|
406
413
|
async addEntitlement(userId, key, amount) {
|
|
407
|
-
return this.request("POST", `/users/${userId}/entitlements/add`, {
|
|
414
|
+
return this.request("POST", `/users/${userId}/entitlements/add`, {
|
|
415
|
+
key,
|
|
416
|
+
amount
|
|
417
|
+
});
|
|
408
418
|
}
|
|
409
419
|
/**
|
|
410
420
|
* Toggle boolean entitlement
|
|
@@ -413,7 +423,10 @@ var PaymentClient = class {
|
|
|
413
423
|
* @param enabled - Whether to enable
|
|
414
424
|
*/
|
|
415
425
|
async toggleEntitlement(userId, key, enabled) {
|
|
416
|
-
return this.request("POST", `/users/${userId}/entitlements/toggle`, {
|
|
426
|
+
return this.request("POST", `/users/${userId}/entitlements/toggle`, {
|
|
427
|
+
key,
|
|
428
|
+
enabled
|
|
429
|
+
});
|
|
417
430
|
}
|
|
418
431
|
/**
|
|
419
432
|
* Generate checkout URL for client-side payment
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK\n * 统一支付集成 SDK\n * \n * @packageDocumentation\n * \n * @example\n * // 服务端使用 - 推荐方式(分离 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 after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n","/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n orderId: string;\n internalId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n description?: string;\n paidAt?: string;\n createdAt: string;\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n [key: string]: {\n value: any;\n name: string;\n description: string;\n };\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n id: string;\n currency: string;\n amount: number;\n displayAmount: string;\n locale: string | null;\n isDefault: boolean;\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n id: string;\n code: string;\n type: string;\n name: string;\n description?: string;\n entitlements: ProductEntitlements;\n prices: ProductPrice[];\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n /** Application ID (Required) */\n appId: string;\n /** Application Secret (Required for server-side operations) */\n appSecret: string;\n\n /**\n * @deprecated Use apiUrl and checkoutUrl instead\n * API Base URL (e.g. https://pay.youidian.com)\n * If apiUrl or checkoutUrl is not provided, this will be used as fallback\n * 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}\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":["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]: {\n\t\tvalue: any\n\t\tname: string\n\t\tdescription: string\n\t}\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(
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Either productCode or both productId and priceId are required"
|
|
41
|
+
);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
let finalUrl = checkoutUrl;
|
|
@@ -120,7 +122,6 @@ var PaymentUI = class {
|
|
|
120
122
|
switch (data.type) {
|
|
121
123
|
case "PAYMENT_SUCCESS":
|
|
122
124
|
options?.onSuccess?.(data.orderId);
|
|
123
|
-
setTimeout(() => this.close(), 2e3);
|
|
124
125
|
break;
|
|
125
126
|
case "PAYMENT_CANCELLED":
|
|
126
127
|
options?.onCancel?.(data.orderId);
|
|
@@ -277,7 +278,9 @@ var PaymentClient = class {
|
|
|
277
278
|
decrypted += decipher.final("utf8");
|
|
278
279
|
return JSON.parse(decrypted);
|
|
279
280
|
} catch (error) {
|
|
280
|
-
throw new Error(
|
|
281
|
+
throw new Error(
|
|
282
|
+
"Failed to decrypt payment callback: Invalid secret or tampered data."
|
|
283
|
+
);
|
|
281
284
|
}
|
|
282
285
|
}
|
|
283
286
|
/**
|
|
@@ -328,7 +331,8 @@ var PaymentClient = class {
|
|
|
328
331
|
async getOrders(params) {
|
|
329
332
|
const queryParams = new URLSearchParams();
|
|
330
333
|
if (params?.page) queryParams.append("page", params.page.toString());
|
|
331
|
-
if (params?.pageSize)
|
|
334
|
+
if (params?.pageSize)
|
|
335
|
+
queryParams.append("pageSize", params.pageSize.toString());
|
|
332
336
|
if (params?.userId) queryParams.append("userId", params.userId);
|
|
333
337
|
if (params?.status) queryParams.append("status", params.status);
|
|
334
338
|
if (params?.startDate) queryParams.append("startDate", params.startDate);
|
|
@@ -359,7 +363,10 @@ var PaymentClient = class {
|
|
|
359
363
|
* @param amount - Amount to consume
|
|
360
364
|
*/
|
|
361
365
|
async consumeEntitlement(userId, key, amount) {
|
|
362
|
-
return this.request("POST", `/users/${userId}/entitlements/consume`, {
|
|
366
|
+
return this.request("POST", `/users/${userId}/entitlements/consume`, {
|
|
367
|
+
key,
|
|
368
|
+
amount
|
|
369
|
+
});
|
|
363
370
|
}
|
|
364
371
|
/**
|
|
365
372
|
* Add numeric entitlement (e.g. refund)
|
|
@@ -368,7 +375,10 @@ var PaymentClient = class {
|
|
|
368
375
|
* @param amount - Amount to add
|
|
369
376
|
*/
|
|
370
377
|
async addEntitlement(userId, key, amount) {
|
|
371
|
-
return this.request("POST", `/users/${userId}/entitlements/add`, {
|
|
378
|
+
return this.request("POST", `/users/${userId}/entitlements/add`, {
|
|
379
|
+
key,
|
|
380
|
+
amount
|
|
381
|
+
});
|
|
372
382
|
}
|
|
373
383
|
/**
|
|
374
384
|
* Toggle boolean entitlement
|
|
@@ -377,7 +387,10 @@ var PaymentClient = class {
|
|
|
377
387
|
* @param enabled - Whether to enable
|
|
378
388
|
*/
|
|
379
389
|
async toggleEntitlement(userId, key, enabled) {
|
|
380
|
-
return this.request("POST", `/users/${userId}/entitlements/toggle`, {
|
|
390
|
+
return this.request("POST", `/users/${userId}/entitlements/toggle`, {
|
|
391
|
+
key,
|
|
392
|
+
enabled
|
|
393
|
+
});
|
|
381
394
|
}
|
|
382
395
|
/**
|
|
383
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 after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n","/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\";\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n orderId: string;\n internalId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n amount: number;\n currency: string;\n description?: string;\n paidAt?: string;\n createdAt: string;\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n [key: string]: {\n value: any;\n name: string;\n description: string;\n };\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n id: string;\n currency: string;\n amount: number;\n displayAmount: string;\n locale: string | null;\n isDefault: boolean;\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n id: string;\n code: string;\n type: string;\n name: string;\n description?: string;\n entitlements: ProductEntitlements;\n prices: ProductPrice[];\n}\n\n/**\n * SDK Client Options\n */\nexport interface PaymentClientOptions {\n /** Application ID (Required) */\n appId: string;\n /** Application Secret (Required for server-side operations) */\n appSecret: string;\n\n /**\n * @deprecated Use apiUrl and checkoutUrl instead\n * API Base URL (e.g. https://pay.youidian.com)\n * If apiUrl or checkoutUrl is not provided, this will be used as fallback\n * 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}\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]: {\n\t\tvalue: any\n\t\tname: string\n\t\tdescription: string\n\t}\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(
|
|
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)
|
|
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`, {
|
|
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`, {
|
|
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`, {
|
|
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
|
package/dist/server.cjs.map
CHANGED
|
@@ -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}\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":["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]: {\n\t\tvalue: any\n\t\tname: string\n\t\tdescription: string\n\t}\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:
|
|
10
|
+
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
11
11
|
paidAt?: string;
|
|
12
12
|
channelTransactionId?: string;
|
|
13
13
|
}
|
|
@@ -17,12 +17,20 @@ interface OrderStatus {
|
|
|
17
17
|
interface OrderDetails {
|
|
18
18
|
orderId: string;
|
|
19
19
|
internalId: string;
|
|
20
|
-
status:
|
|
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
|
|
@@ -95,6 +103,7 @@ interface CreateOrderParams {
|
|
|
95
103
|
metadata?: Record<string, any>;
|
|
96
104
|
merchantOrderId?: string;
|
|
97
105
|
openid?: string;
|
|
106
|
+
locale?: string;
|
|
98
107
|
}
|
|
99
108
|
/**
|
|
100
109
|
* Create Order Response
|
|
@@ -120,7 +129,7 @@ interface PaymentNotification {
|
|
|
120
129
|
interface PaymentCallbackData {
|
|
121
130
|
orderId: string;
|
|
122
131
|
merchantOrderId?: string;
|
|
123
|
-
status:
|
|
132
|
+
status: "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
124
133
|
amount: number;
|
|
125
134
|
currency: string;
|
|
126
135
|
paidAt: string;
|
|
@@ -134,7 +143,7 @@ interface GetOrdersParams {
|
|
|
134
143
|
page?: number;
|
|
135
144
|
pageSize?: number;
|
|
136
145
|
userId?: string;
|
|
137
|
-
status?:
|
|
146
|
+
status?: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
138
147
|
startDate?: string;
|
|
139
148
|
endDate?: string;
|
|
140
149
|
}
|
|
@@ -145,7 +154,7 @@ interface OrderListItem {
|
|
|
145
154
|
orderId: string;
|
|
146
155
|
internalId: string;
|
|
147
156
|
merchantUserId: string;
|
|
148
|
-
status:
|
|
157
|
+
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
149
158
|
amount: number;
|
|
150
159
|
currency: string;
|
|
151
160
|
channel?: string;
|
package/dist/server.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
interface OrderStatus {
|
|
9
9
|
orderId: string;
|
|
10
|
-
status:
|
|
10
|
+
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
11
11
|
paidAt?: string;
|
|
12
12
|
channelTransactionId?: string;
|
|
13
13
|
}
|
|
@@ -17,12 +17,20 @@ interface OrderStatus {
|
|
|
17
17
|
interface OrderDetails {
|
|
18
18
|
orderId: string;
|
|
19
19
|
internalId: string;
|
|
20
|
-
status:
|
|
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
|
|
@@ -95,6 +103,7 @@ interface CreateOrderParams {
|
|
|
95
103
|
metadata?: Record<string, any>;
|
|
96
104
|
merchantOrderId?: string;
|
|
97
105
|
openid?: string;
|
|
106
|
+
locale?: string;
|
|
98
107
|
}
|
|
99
108
|
/**
|
|
100
109
|
* Create Order Response
|
|
@@ -120,7 +129,7 @@ interface PaymentNotification {
|
|
|
120
129
|
interface PaymentCallbackData {
|
|
121
130
|
orderId: string;
|
|
122
131
|
merchantOrderId?: string;
|
|
123
|
-
status:
|
|
132
|
+
status: "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
124
133
|
amount: number;
|
|
125
134
|
currency: string;
|
|
126
135
|
paidAt: string;
|
|
@@ -134,7 +143,7 @@ interface GetOrdersParams {
|
|
|
134
143
|
page?: number;
|
|
135
144
|
pageSize?: number;
|
|
136
145
|
userId?: string;
|
|
137
|
-
status?:
|
|
146
|
+
status?: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
138
147
|
startDate?: string;
|
|
139
148
|
endDate?: string;
|
|
140
149
|
}
|
|
@@ -145,7 +154,7 @@ interface OrderListItem {
|
|
|
145
154
|
orderId: string;
|
|
146
155
|
internalId: string;
|
|
147
156
|
merchantUserId: string;
|
|
148
|
-
status:
|
|
157
|
+
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
149
158
|
amount: number;
|
|
150
159
|
currency: string;
|
|
151
160
|
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(
|
|
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)
|
|
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`, {
|
|
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`, {
|
|
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`, {
|
|
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
|
package/dist/server.js.map
CHANGED
|
@@ -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}\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]: {\n\t\tvalue: any\n\t\tname: string\n\t\tdescription: string\n\t}\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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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.11",
|
|
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
|
+
}
|