@youidian/pay-sdk 1.1.2 → 2.0.0

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/index.js CHANGED
@@ -2,61 +2,33 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
- // src/client.ts
6
- var PaymentUI = class {
5
+ // src/hosted-modal.ts
6
+ function applyLocaleToUrl(urlValue, locale) {
7
+ if (!locale) return urlValue;
8
+ try {
9
+ const url = new URL(urlValue);
10
+ const localePrefix = `/${locale}`;
11
+ if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {
12
+ url.pathname = `${localePrefix}${url.pathname}`;
13
+ }
14
+ return url.toString();
15
+ } catch (_error) {
16
+ const localePrefix = `/${locale}`;
17
+ if (!urlValue.startsWith(`${localePrefix}/`) && urlValue !== localePrefix) {
18
+ return `${localePrefix}${urlValue.startsWith("/") ? "" : "/"}${urlValue}`;
19
+ }
20
+ return urlValue;
21
+ }
22
+ }
23
+ var HostedFrameModal = class {
7
24
  constructor() {
8
25
  __publicField(this, "iframe", null);
9
26
  __publicField(this, "modal", null);
10
27
  __publicField(this, "messageHandler", null);
11
28
  }
12
- /**
13
- * Opens the payment checkout page in an iframe modal.
14
- * @param urlOrParams - The checkout page URL or payment parameters
15
- * @param options - UI options
16
- */
17
- openPayment(urlOrParams, options) {
29
+ openHostedFrame(url, options) {
18
30
  if (typeof document === "undefined") return;
19
31
  if (this.modal) return;
20
- let checkoutUrl;
21
- if (typeof urlOrParams === "string") {
22
- checkoutUrl = urlOrParams;
23
- } else {
24
- const {
25
- appId,
26
- productId,
27
- priceId,
28
- productCode,
29
- userId,
30
- checkoutUrl: checkoutUrlParam,
31
- baseUrl = "https://pay.imgto.link"
32
- } = urlOrParams;
33
- const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
34
- if (productCode) {
35
- checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
36
- } else if (productId && priceId) {
37
- checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
38
- } else {
39
- throw new Error(
40
- "Either productCode or both productId and priceId are required"
41
- );
42
- }
43
- }
44
- let finalUrl = checkoutUrl;
45
- if (options?.locale) {
46
- try {
47
- const url = new URL(checkoutUrl);
48
- const localePrefix = `/${options.locale}`;
49
- if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {
50
- url.pathname = `${localePrefix}${url.pathname}`;
51
- finalUrl = url.toString();
52
- }
53
- } catch (_e) {
54
- const localePrefix = `/${options.locale}`;
55
- if (!checkoutUrl.startsWith(`${localePrefix}/`) && checkoutUrl !== localePrefix) {
56
- finalUrl = `${localePrefix}${checkoutUrl.startsWith("/") ? "" : "/"}${checkoutUrl}`;
57
- }
58
- }
59
- }
60
32
  this.modal = document.createElement("div");
61
33
  Object.assign(this.modal.style, {
62
34
  position: "fixed",
@@ -64,53 +36,66 @@ var PaymentUI = class {
64
36
  left: "0",
65
37
  width: "100%",
66
38
  height: "100%",
67
- backgroundColor: "rgba(0,0,0,0.5)",
39
+ backgroundColor: "rgba(15,23,42,0.52)",
68
40
  display: "flex",
69
41
  alignItems: "center",
70
42
  justifyContent: "center",
71
43
  zIndex: "9999",
72
- transition: "opacity 0.3s ease"
44
+ transition: "opacity 0.3s ease",
45
+ backdropFilter: "blur(14px)",
46
+ padding: "16px"
73
47
  });
74
48
  const container = document.createElement("div");
75
49
  Object.assign(container.style, {
76
- width: "450px",
77
- height: "min(600px, 90vh)",
78
- backgroundColor: "#fff",
79
- borderRadius: "20px",
80
- overflow: "hidden",
50
+ width: options.width || "450px",
51
+ height: options.height || "min(600px, 90vh)",
52
+ backgroundColor: "transparent",
53
+ borderRadius: "28px",
54
+ overflow: "visible",
81
55
  position: "relative",
82
- boxShadow: "0 25px 50px -12px rgba(0,0,0,0.25)"
56
+ boxShadow: "none",
57
+ maxWidth: "calc(100vw - 32px)",
58
+ maxHeight: "calc(100vh - 32px)",
59
+ transition: "height 180ms ease",
60
+ willChange: "height"
83
61
  });
84
62
  const closeBtn = document.createElement("button");
85
63
  closeBtn.innerHTML = "\xD7";
86
64
  Object.assign(closeBtn.style, {
87
65
  position: "absolute",
88
- right: "15px",
89
- top: "10px",
90
- fontSize: "24px",
91
- border: "none",
92
- background: "none",
66
+ right: "-14px",
67
+ top: "-14px",
68
+ fontSize: "20px",
69
+ width: "36px",
70
+ height: "36px",
71
+ borderRadius: "9999px",
72
+ border: "1px solid rgba(255,255,255,0.5)",
73
+ background: "rgba(255,255,255,0.9)",
93
74
  cursor: "pointer",
94
- color: "#999",
95
- zIndex: "1"
75
+ color: "#475569",
76
+ zIndex: "2",
77
+ boxShadow: "0 10px 30px rgba(15,23,42,0.16)"
96
78
  });
97
79
  closeBtn.onclick = () => {
98
80
  this.close();
99
- options?.onCancel?.();
81
+ options.onCloseButton?.();
100
82
  };
101
83
  this.iframe = document.createElement("iframe");
102
- this.iframe.src = finalUrl;
84
+ this.iframe.src = url;
103
85
  Object.assign(this.iframe.style, {
104
86
  width: "100%",
105
87
  height: "100%",
106
- border: "none"
88
+ border: "none",
89
+ borderRadius: "28px",
90
+ background: "transparent",
91
+ display: "block"
107
92
  });
108
93
  container.appendChild(closeBtn);
109
94
  container.appendChild(this.iframe);
110
95
  this.modal.appendChild(container);
111
96
  document.body.appendChild(this.modal);
112
97
  this.messageHandler = (event) => {
113
- if (options?.allowedOrigin && options.allowedOrigin !== "*") {
98
+ if (options.allowedOrigin && options.allowedOrigin !== "*") {
114
99
  if (event.origin !== options.allowedOrigin) {
115
100
  return;
116
101
  }
@@ -119,43 +104,85 @@ var PaymentUI = class {
119
104
  if (!data || typeof data !== "object" || !data.type) {
120
105
  return;
121
106
  }
122
- switch (data.type) {
123
- case "PAYMENT_SUCCESS":
124
- options?.onSuccess?.(data.orderId);
125
- break;
126
- case "PAYMENT_CANCELLED":
127
- options?.onCancel?.(data.orderId);
128
- break;
129
- case "PAYMENT_RESIZE":
130
- if (data.height && container) {
131
- const maxHeight = window.innerHeight * 0.9;
132
- const newHeight = Math.min(data.height, maxHeight);
133
- container.style.height = `${newHeight}px`;
134
- }
135
- break;
136
- case "PAYMENT_CLOSE":
137
- this.close();
138
- options?.onClose?.();
139
- break;
140
- }
107
+ options.onMessage(data, container);
141
108
  };
142
109
  window.addEventListener("message", this.messageHandler);
143
110
  }
144
- /**
145
- * Close the payment modal
146
- */
147
111
  close() {
148
112
  if (typeof window === "undefined") return;
149
113
  if (this.messageHandler) {
150
114
  window.removeEventListener("message", this.messageHandler);
151
115
  this.messageHandler = null;
152
116
  }
153
- if (this.modal && this.modal.parentNode) {
117
+ if (this.modal?.parentNode) {
154
118
  this.modal.parentNode.removeChild(this.modal);
155
119
  }
156
120
  this.modal = null;
157
121
  this.iframe = null;
158
122
  }
123
+ };
124
+
125
+ // src/client.ts
126
+ var PaymentUI = class extends HostedFrameModal {
127
+ /**
128
+ * Opens the payment checkout page in an iframe modal.
129
+ * @param urlOrParams - The checkout page URL or payment parameters
130
+ * @param options - UI options
131
+ */
132
+ openPayment(urlOrParams, options) {
133
+ if (typeof document === "undefined") return;
134
+ if (this.modal) return;
135
+ let checkoutUrl;
136
+ if (typeof urlOrParams === "string") {
137
+ checkoutUrl = urlOrParams;
138
+ } else {
139
+ const {
140
+ appId,
141
+ productId,
142
+ priceId,
143
+ productCode,
144
+ userId,
145
+ checkoutUrl: checkoutUrlParam,
146
+ baseUrl = "https://pay.imgto.link"
147
+ } = urlOrParams;
148
+ const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
149
+ if (productCode) {
150
+ checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
151
+ } else if (productId && priceId) {
152
+ checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
153
+ } else {
154
+ throw new Error(
155
+ "Either productCode or both productId and priceId are required"
156
+ );
157
+ }
158
+ }
159
+ const finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale);
160
+ this.openHostedFrame(finalUrl, {
161
+ allowedOrigin: options?.allowedOrigin,
162
+ onCloseButton: () => options?.onCancel?.(),
163
+ onMessage: (data, container) => {
164
+ switch (data.type) {
165
+ case "PAYMENT_SUCCESS":
166
+ options?.onSuccess?.(data.orderId);
167
+ break;
168
+ case "PAYMENT_CANCELLED":
169
+ options?.onCancel?.(data.orderId);
170
+ break;
171
+ case "PAYMENT_RESIZE":
172
+ if (data.height) {
173
+ const maxHeight = window.innerHeight * 0.9;
174
+ const newHeight = Math.min(data.height, maxHeight);
175
+ container.style.height = `${newHeight}px`;
176
+ }
177
+ break;
178
+ case "PAYMENT_CLOSE":
179
+ this.close();
180
+ options?.onClose?.();
181
+ break;
182
+ }
183
+ }
184
+ });
185
+ }
159
186
  /**
160
187
  * Poll order status from integrator's API endpoint
161
188
  * @param statusUrl - The integrator's API endpoint to check order status
@@ -415,6 +442,16 @@ var PaymentClient = class {
415
442
  getCheckoutUrl(productId, priceId) {
416
443
  return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;
417
444
  }
445
+ /**
446
+ * Verify a hosted login token and return the normalized login profile.
447
+ * This request is signed with your app credentials and routed through the worker API.
448
+ */
449
+ async verifyLoginToken(token) {
450
+ if (!token?.trim()) {
451
+ throw new Error("login token is required");
452
+ }
453
+ return this.request("POST", "/login/tokens/verify", { token: token.trim() });
454
+ }
418
455
  };
419
456
  export {
420
457
  PaymentClient,
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 =\n\t| \"PAYMENT_SUCCESS\"\n\t| \"PAYMENT_CANCELLED\"\n\t| \"PAYMENT_CLOSE\"\n\t| \"PAYMENT_RESIZE\"\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n\ttype: PaymentEventType\n\torderId?: string\n\theight?: number\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n\tlocale?: string\n\tonSuccess?: (orderId?: string) => void\n\tonCancel?: (orderId?: string) => void\n\tonClose?: () => void\n\t/** Origin to validate postMessage (defaults to '*', set for security) */\n\tallowedOrigin?: string\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n\t/** Polling interval in ms (default: 3000) */\n\tinterval?: number\n\t/** Timeout in ms (default: 300000 = 5 minutes) */\n\ttimeout?: number\n\t/** Callback on each status check */\n\tonStatusChange?: (status: OrderStatus) => void\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\n/**\n * Params for opening payment directly without manual URL construction\n */\nexport interface PaymentParams {\n\tappId: string\n\tuserId: string\n\t/** Product ID - use with priceId for direct ID-based payment */\n\tproductId?: string\n\t/** Price ID - use with productId for direct ID-based payment */\n\tpriceId?: string\n\t/** Product code - use with locale for code-based payment (alternative to productId/priceId) */\n\tproductCode?: string\n\n\t/**\n\t * @deprecated Use checkoutUrl instead\n\t * Defaults to https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * Checkout page URL (e.g. https://pay.youidian.com)\n\t * Defaults to https://pay.imgto.link\n\t */\n\tcheckoutUrl?: string\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n\tprivate iframe: HTMLIFrameElement | null = null\n\tprivate modal: HTMLDivElement | null = null\n\tprivate messageHandler: ((event: MessageEvent) => void) | null = null\n\n\t/**\n\t * Opens the payment checkout page in an iframe modal.\n\t * @param urlOrParams - The checkout page URL or payment parameters\n\t * @param options - UI options\n\t */\n\topenPayment(urlOrParams: string | PaymentParams, options?: PaymentUIOptions) {\n\t\tif (typeof document === \"undefined\") return // Server-side guard\n\t\tif (this.modal) return // Prevent multiple modals\n\n\t\tlet checkoutUrl: string\n\t\tif (typeof urlOrParams === \"string\") {\n\t\t\tcheckoutUrl = urlOrParams\n\t\t} else {\n\t\t\tconst {\n\t\t\t\tappId,\n\t\t\t\tproductId,\n\t\t\t\tpriceId,\n\t\t\t\tproductCode,\n\t\t\t\tuserId,\n\t\t\t\tcheckoutUrl: checkoutUrlParam,\n\t\t\t\tbaseUrl = \"https://pay.imgto.link\",\n\t\t\t} = urlOrParams\n\n\t\t\t// 优先使用 checkoutUrl,其次 baseUrl,默认 https://pay.imgto.link\n\t\t\tconst base = (checkoutUrlParam || baseUrl).replace(/\\/$/, \"\")\n\n\t\t\tif (productCode) {\n\t\t\t\t// Use product code route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else if (productId && priceId) {\n\t\t\t\t// Use ID-based route\n\t\t\t\tcheckoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Either productCode or both productId and priceId are required\",\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tlet finalUrl = checkoutUrl\n\t\tif (options?.locale) {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(checkoutUrl)\n\t\t\t\t// Check if path already starts with /locale/ or is exactly /locale\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!url.pathname.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\turl.pathname !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\turl.pathname = `${localePrefix}${url.pathname}`\n\t\t\t\t\tfinalUrl = url.toString()\n\t\t\t\t}\n\t\t\t} catch (_e) {\n\t\t\t\t// Fallback if URL is relative or invalid\n\t\t\t\tconst localePrefix = `/${options.locale}`\n\t\t\t\tif (\n\t\t\t\t\t!checkoutUrl.startsWith(`${localePrefix}/`) &&\n\t\t\t\t\tcheckoutUrl !== localePrefix\n\t\t\t\t) {\n\t\t\t\t\tfinalUrl = `${localePrefix}${checkoutUrl.startsWith(\"/\") ? \"\" : \"/\"}${checkoutUrl}`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Create Modal Overlay\n\t\tthis.modal = document.createElement(\"div\")\n\t\tObject.assign(this.modal.style, {\n\t\t\tposition: \"fixed\",\n\t\t\ttop: \"0\",\n\t\t\tleft: \"0\",\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tbackgroundColor: \"rgba(0,0,0,0.5)\",\n\t\t\tdisplay: \"flex\",\n\t\t\talignItems: \"center\",\n\t\t\tjustifyContent: \"center\",\n\t\t\tzIndex: \"9999\",\n\t\t\ttransition: \"opacity 0.3s ease\",\n\t\t})\n\n\t\t// Create Container\n\t\tconst container = document.createElement(\"div\")\n\t\tObject.assign(container.style, {\n\t\t\twidth: \"450px\",\n\t\t\theight: \"min(600px, 90vh)\",\n\t\t\tbackgroundColor: \"#fff\",\n\t\t\tborderRadius: \"20px\",\n\t\t\toverflow: \"hidden\",\n\t\t\tposition: \"relative\",\n\t\t\tboxShadow: \"0 25px 50px -12px rgba(0,0,0,0.25)\",\n\t\t})\n\n\t\t// Create Close Button\n\t\tconst closeBtn = document.createElement(\"button\")\n\t\tcloseBtn.innerHTML = \"×\"\n\t\tObject.assign(closeBtn.style, {\n\t\t\tposition: \"absolute\",\n\t\t\tright: \"15px\",\n\t\t\ttop: \"10px\",\n\t\t\tfontSize: \"24px\",\n\t\t\tborder: \"none\",\n\t\t\tbackground: \"none\",\n\t\t\tcursor: \"pointer\",\n\t\t\tcolor: \"#999\",\n\t\t\tzIndex: \"1\",\n\t\t})\n\t\tcloseBtn.onclick = () => {\n\t\t\tthis.close()\n\t\t\toptions?.onCancel?.()\n\t\t}\n\n\t\t// Create Iframe\n\t\tthis.iframe = document.createElement(\"iframe\")\n\t\tthis.iframe.src = finalUrl\n\t\tObject.assign(this.iframe.style, {\n\t\t\twidth: \"100%\",\n\t\t\theight: \"100%\",\n\t\t\tborder: \"none\",\n\t\t})\n\n\t\tcontainer.appendChild(closeBtn)\n\t\tcontainer.appendChild(this.iframe)\n\t\tthis.modal.appendChild(container)\n\t\tdocument.body.appendChild(this.modal)\n\n\t\t// Listen for messages from checkout page\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\t// Validate origin if specified\n\t\t\tif (options?.allowedOrigin && options.allowedOrigin !== \"*\") {\n\t\t\t\tif (event.origin !== options.allowedOrigin) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data = event.data as PaymentEventData\n\n\t\t\tif (!data || typeof data !== \"object\" || !data.type) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch (data.type) {\n\t\t\t\tcase \"PAYMENT_SUCCESS\":\n\t\t\t\t\toptions?.onSuccess?.(data.orderId)\n\t\t\t\t\t// Auto-close removed, waiting for explicit PAYMENT_CLOSE\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CANCELLED\":\n\t\t\t\t\toptions?.onCancel?.(data.orderId)\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_RESIZE\":\n\t\t\t\t\tif (data.height && container) {\n\t\t\t\t\t\t// Limit max height to 90% of viewport\n\t\t\t\t\t\tconst maxHeight = window.innerHeight * 0.9\n\t\t\t\t\t\tconst newHeight = Math.min(data.height, maxHeight)\n\t\t\t\t\t\tcontainer.style.height = `${newHeight}px`\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"PAYMENT_CLOSE\":\n\t\t\t\t\tthis.close()\n\t\t\t\t\toptions?.onClose?.()\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twindow.addEventListener(\"message\", this.messageHandler)\n\t}\n\n\t/**\n\t * Close the payment modal\n\t */\n\tclose() {\n\t\tif (typeof window === \"undefined\") return\n\n\t\tif (this.messageHandler) {\n\t\t\twindow.removeEventListener(\"message\", this.messageHandler)\n\t\t\tthis.messageHandler = null\n\t\t}\n\t\tif (this.modal && this.modal.parentNode) {\n\t\t\tthis.modal.parentNode.removeChild(this.modal)\n\t\t}\n\t\tthis.modal = null\n\t\tthis.iframe = null\n\t}\n\n\t/**\n\t * Poll order status from integrator's API endpoint\n\t * @param statusUrl - The integrator's API endpoint to check order status\n\t * @param options - Polling options\n\t * @returns Promise that resolves when order is paid or rejects on timeout/failure\n\t */\n\tasync pollOrderStatus(\n\t\tstatusUrl: string,\n\t\toptions?: PollOptions,\n\t): Promise<OrderStatus> {\n\t\tconst interval = options?.interval || 3000\n\t\tconst timeout = options?.timeout || 300000\n\t\tconst startTime = Date.now()\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst poll = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(statusUrl)\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Status check failed: ${response.status}`)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst status: OrderStatus = await response.json()\n\t\t\t\t\toptions?.onStatusChange?.(status)\n\n\t\t\t\t\tif (status.status === \"PAID\") {\n\t\t\t\t\t\tresolve(status)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif (status.status === \"CANCELLED\" || status.status === \"FAILED\") {\n\t\t\t\t\t\treject(new Error(`Order ${status.status.toLowerCase()}`))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(new Error(\"Polling timeout\"))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Continue polling\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// On network error, continue polling unless timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(error)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpoll()\n\t\t})\n\t}\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n\treturn new PaymentUI()\n}\n","/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * WeChat JSAPI Payment Parameters (for wx.requestPayment)\n */\nexport interface WechatJsapiPayParams {\n\tappId: string\n\ttimeStamp: string\n\tnonceStr: string\n\tpackage: string\n\tsignType: \"RSA\"\n\tpaySign: string\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 WeChat Mini Program Order Parameters\n */\nexport type CreateMiniProgramOrderParams = {\n\tuserId: string\n\topenid: string\n\tmerchantOrderId?: string\n\tpriceId: 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 * Create a WeChat Mini Program order (channel fixed to WECHAT_MINI)\n\t * @param params - Mini program order parameters\n\t * @returns Order details with payment parameters\n\t */\n\tasync createMiniProgramOrder(\n\t\tparams: CreateMiniProgramOrderParams,\n\t): Promise<CreateOrderResponse> {\n\t\tconst { openid, ...rest } = params\n\t\treturn this.request(\"POST\", \"/orders\", {\n\t\t\t...rest,\n\t\t\tchannel: \"WECHAT_MINI\",\n\t\t\topenid,\n\t\t\tmetadata: { openid },\n\t\t} satisfies CreateOrderParams)\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":[]}
1
+ {"version":3,"sources":["../src/hosted-modal.ts","../src/client.ts","../src/server.ts"],"sourcesContent":["export interface HostedFrameMessage {\n\ttype: string\n\theight?: number\n}\n\ntype HostedFrameOptions<TData extends HostedFrameMessage> = {\n\tallowedOrigin?: string\n\theight?: string\n\tonCloseButton?: () => void\n\tonMessage: (data: TData, container: HTMLDivElement) => void\n\twidth?: string\n}\n\nexport function applyLocaleToUrl(urlValue: string, locale?: string): string {\n\tif (!locale) return urlValue\n\n\ttry {\n\t\tconst url = new URL(urlValue)\n\t\tconst localePrefix = `/${locale}`\n\t\tif (\n\t\t\t!url.pathname.startsWith(`${localePrefix}/`) &&\n\t\t\turl.pathname !== localePrefix\n\t\t) {\n\t\t\turl.pathname = `${localePrefix}${url.pathname}`\n\t\t}\n\t\treturn url.toString()\n\t} catch (_error) {\n\t\tconst localePrefix = `/${locale}`\n\t\tif (!urlValue.startsWith(`${localePrefix}/`) && urlValue !== localePrefix) {\n\t\t\treturn `${localePrefix}${urlValue.startsWith(\"/\") ? \"\" : \"/\"}${urlValue}`\n\t\t}\n\t\treturn urlValue\n\t}\n}\n\nexport class HostedFrameModal<TData extends HostedFrameMessage> {\n\tprotected iframe: HTMLIFrameElement | null = null\n\tprotected modal: HTMLDivElement | null = null\n\tprotected messageHandler: ((event: MessageEvent) => void) | null = null\n\n\tprotected openHostedFrame(\n\t\turl: string,\n\t\toptions: HostedFrameOptions<TData>,\n\t): void {\n\t\tif (typeof document === \"undefined\") return\n\t\tif (this.modal) return\n\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(15,23,42,0.52)\",\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\tbackdropFilter: \"blur(14px)\",\n\t\t\tpadding: \"16px\",\n\t\t})\n\n\t\tconst container = document.createElement(\"div\")\n\t\tObject.assign(container.style, {\n\t\t\twidth: options.width || \"450px\",\n\t\t\theight: options.height || \"min(600px, 90vh)\",\n\t\t\tbackgroundColor: \"transparent\",\n\t\t\tborderRadius: \"28px\",\n\t\t\toverflow: \"visible\",\n\t\t\tposition: \"relative\",\n\t\t\tboxShadow: \"none\",\n\t\t\tmaxWidth: \"calc(100vw - 32px)\",\n\t\t\tmaxHeight: \"calc(100vh - 32px)\",\n\t\t\ttransition: \"height 180ms ease\",\n\t\t\twillChange: \"height\",\n\t\t})\n\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: \"-14px\",\n\t\t\ttop: \"-14px\",\n\t\t\tfontSize: \"20px\",\n\t\t\twidth: \"36px\",\n\t\t\theight: \"36px\",\n\t\t\tborderRadius: \"9999px\",\n\t\t\tborder: \"1px solid rgba(255,255,255,0.5)\",\n\t\t\tbackground: \"rgba(255,255,255,0.9)\",\n\t\t\tcursor: \"pointer\",\n\t\t\tcolor: \"#475569\",\n\t\t\tzIndex: \"2\",\n\t\t\tboxShadow: \"0 10px 30px rgba(15,23,42,0.16)\",\n\t\t})\n\t\tcloseBtn.onclick = () => {\n\t\t\tthis.close()\n\t\t\toptions.onCloseButton?.()\n\t\t}\n\n\t\tthis.iframe = document.createElement(\"iframe\")\n\t\tthis.iframe.src = url\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\tborderRadius: \"28px\",\n\t\t\tbackground: \"transparent\",\n\t\t\tdisplay: \"block\",\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\tthis.messageHandler = (event: MessageEvent) => {\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 TData\n\t\t\tif (!data || typeof data !== \"object\" || !data.type) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\toptions.onMessage(data, container)\n\t\t}\n\n\t\twindow.addEventListener(\"message\", this.messageHandler)\n\t}\n\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\n\t\tif (this.modal?.parentNode) {\n\t\t\tthis.modal.parentNode.removeChild(this.modal)\n\t\t}\n\n\t\tthis.modal = null\n\t\tthis.iframe = null\n\t}\n}\n","/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\nimport {\n\tapplyLocaleToUrl,\n\tHostedFrameModal,\n} from \"./hosted-modal\"\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 extends HostedFrameModal<PaymentEventData> {\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\tconst finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale)\n\n\t\tthis.openHostedFrame(finalUrl, {\n\t\t\tallowedOrigin: options?.allowedOrigin,\n\t\t\tonCloseButton: () => options?.onCancel?.(),\n\t\t\tonMessage: (data, container) => {\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase \"PAYMENT_SUCCESS\":\n\t\t\t\t\t\toptions?.onSuccess?.(data.orderId)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase \"PAYMENT_CANCELLED\":\n\t\t\t\t\t\toptions?.onCancel?.(data.orderId)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase \"PAYMENT_RESIZE\":\n\t\t\t\t\t\tif (data.height) {\n\t\t\t\t\t\t\tconst maxHeight = window.innerHeight * 0.9\n\t\t\t\t\t\t\tconst newHeight = Math.min(data.height, maxHeight)\n\t\t\t\t\t\t\tcontainer.style.height = `${newHeight}px`\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase \"PAYMENT_CLOSE\":\n\t\t\t\t\t\tthis.close()\n\t\t\t\t\t\toptions?.onClose?.()\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t/**\n\t * Poll order status from integrator's API endpoint\n\t * @param statusUrl - The integrator's API endpoint to check order status\n\t * @param options - Polling options\n\t * @returns Promise that resolves when order is paid or rejects on timeout/failure\n\t */\n\tasync pollOrderStatus(\n\t\tstatusUrl: string,\n\t\toptions?: PollOptions,\n\t): Promise<OrderStatus> {\n\t\tconst interval = options?.interval || 3000\n\t\tconst timeout = options?.timeout || 300000\n\t\tconst startTime = Date.now()\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst poll = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(statusUrl)\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Status check failed: ${response.status}`)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst status: OrderStatus = await response.json()\n\t\t\t\t\toptions?.onStatusChange?.(status)\n\n\t\t\t\t\tif (status.status === \"PAID\") {\n\t\t\t\t\t\tresolve(status)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif (status.status === \"CANCELLED\" || status.status === \"FAILED\") {\n\t\t\t\t\t\treject(new Error(`Order ${status.status.toLowerCase()}`))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(new Error(\"Polling timeout\"))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Continue polling\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// On network error, continue polling unless timeout\n\t\t\t\t\tif (Date.now() - startTime > timeout) {\n\t\t\t\t\t\treject(error)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tsetTimeout(poll, interval)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpoll()\n\t\t})\n\t}\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n\treturn new PaymentUI()\n}\n","/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * WeChat JSAPI Payment Parameters (for wx.requestPayment)\n */\nexport interface WechatJsapiPayParams {\n\tappId: string\n\ttimeStamp: string\n\tnonceStr: string\n\tpackage: string\n\tsignType: \"RSA\"\n\tpaySign: string\n}\n\n/**\n * Verified hosted login token payload.\n */\nexport interface VerifiedLoginToken {\n\tappId: string\n\tuserId: string\n\tlegacyCasdoorId?: string | null\n\tchannel: string\n\temail?: string | null\n\tname?: string | null\n\tavatar?: string | null\n\twechatOpenId?: string | null\n\twechatUnionId?: string | null\n\texpiresAt: string\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 WeChat Mini Program Order Parameters\n */\nexport type CreateMiniProgramOrderParams = {\n\tuserId: string\n\topenid: string\n\tmerchantOrderId?: string\n\tpriceId: 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 * Create a WeChat Mini Program order (channel fixed to WECHAT_MINI)\n\t * @param params - Mini program order parameters\n\t * @returns Order details with payment parameters\n\t */\n\tasync createMiniProgramOrder(\n\t\tparams: CreateMiniProgramOrderParams,\n\t): Promise<CreateOrderResponse> {\n\t\tconst { openid, ...rest } = params\n\t\treturn this.request(\"POST\", \"/orders\", {\n\t\t\t...rest,\n\t\t\tchannel: \"WECHAT_MINI\",\n\t\t\topenid,\n\t\t\tmetadata: { openid },\n\t\t} satisfies CreateOrderParams)\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\t/**\n\t * Verify a hosted login token and return the normalized login profile.\n\t * This request is signed with your app credentials and routed through the worker API.\n\t */\n\tasync verifyLoginToken(token: string): Promise<VerifiedLoginToken> {\n\t\tif (!token?.trim()) {\n\t\t\tthrow new Error(\"login token is required\")\n\t\t}\n\t\treturn this.request(\"POST\", \"/login/tokens/verify\", { token: token.trim() })\n\t}\n}\n"],"mappings":"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","names":[]}