@youidian/sdk 3.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 ADDED
@@ -0,0 +1,784 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/hosted-modal.ts
6
+ var SDK_SUPPORTED_LOCALES = [
7
+ "en",
8
+ "zh-CN",
9
+ "zh-Hant",
10
+ "fr",
11
+ "de",
12
+ "ja",
13
+ "es",
14
+ "ko",
15
+ "nl",
16
+ "it",
17
+ "pt"
18
+ ];
19
+ var SDK_DEFAULT_LOCALE = "zh-CN";
20
+ var SDK_LOCALE_ALIASES = {
21
+ en: "en",
22
+ "en-us": "en",
23
+ "en-gb": "en",
24
+ zh: "zh-CN",
25
+ "zh-cn": "zh-CN",
26
+ "zh-tw": "zh-Hant",
27
+ "zh-hk": "zh-Hant",
28
+ "zh-hant": "zh-Hant",
29
+ "zh-hans": "zh-CN",
30
+ fr: "fr",
31
+ de: "de",
32
+ ja: "ja",
33
+ es: "es",
34
+ ko: "ko",
35
+ nl: "nl",
36
+ it: "it",
37
+ pt: "pt"
38
+ };
39
+ function matchSupportedBaseLocale(locale) {
40
+ const base = locale.toLowerCase().split("-")[0];
41
+ return SDK_SUPPORTED_LOCALES.find((item) => item.toLowerCase() === base);
42
+ }
43
+ function normalizeSdkLocale(locale) {
44
+ if (!locale) return;
45
+ const sanitized = locale.trim().replace(/_/g, "-");
46
+ if (!sanitized) return;
47
+ const lower = sanitized.toLowerCase();
48
+ if (SDK_LOCALE_ALIASES[lower]) {
49
+ return SDK_LOCALE_ALIASES[lower];
50
+ }
51
+ let canonical;
52
+ try {
53
+ canonical = Intl.getCanonicalLocales(sanitized)[0];
54
+ } catch {
55
+ canonical = void 0;
56
+ }
57
+ const candidates = [canonical, sanitized].filter(Boolean);
58
+ for (const candidate of candidates) {
59
+ const candidateLower = candidate.toLowerCase();
60
+ if (SDK_LOCALE_ALIASES[candidateLower]) {
61
+ return SDK_LOCALE_ALIASES[candidateLower];
62
+ }
63
+ if (SDK_SUPPORTED_LOCALES.includes(candidate)) {
64
+ return candidate;
65
+ }
66
+ const baseMatch = matchSupportedBaseLocale(candidate);
67
+ if (baseMatch) {
68
+ return baseMatch;
69
+ }
70
+ }
71
+ }
72
+ function getBrowserLocale() {
73
+ if (typeof navigator === "undefined") return;
74
+ for (const candidate of navigator.languages || []) {
75
+ const normalized = normalizeSdkLocale(candidate);
76
+ if (normalized) return normalized;
77
+ }
78
+ return normalizeSdkLocale(navigator.language);
79
+ }
80
+ function getAutoResolvedLocale(locale) {
81
+ return normalizeSdkLocale(locale) || getBrowserLocale() || SDK_DEFAULT_LOCALE;
82
+ }
83
+ function applyLocaleToUrl(urlValue, locale) {
84
+ if (!locale) return urlValue;
85
+ try {
86
+ const url = new URL(urlValue);
87
+ const localePrefix = `/${locale}`;
88
+ if (!url.pathname.startsWith(`${localePrefix}/`) && url.pathname !== localePrefix) {
89
+ url.pathname = `${localePrefix}${url.pathname}`;
90
+ }
91
+ return url.toString();
92
+ } catch (_error) {
93
+ const localePrefix = `/${locale}`;
94
+ if (!urlValue.startsWith(`${localePrefix}/`) && urlValue !== localePrefix) {
95
+ return `${localePrefix}${urlValue.startsWith("/") ? "" : "/"}${urlValue}`;
96
+ }
97
+ return urlValue;
98
+ }
99
+ }
100
+ var HostedFrameModal = class {
101
+ constructor() {
102
+ __publicField(this, "iframe", null);
103
+ __publicField(this, "modal", null);
104
+ __publicField(this, "messageHandler", null);
105
+ }
106
+ openHostedFrame(url, options) {
107
+ if (typeof document === "undefined") return;
108
+ if (this.modal) return;
109
+ this.modal = document.createElement("div");
110
+ Object.assign(this.modal.style, {
111
+ position: "fixed",
112
+ top: "0",
113
+ left: "0",
114
+ width: "100%",
115
+ height: "100%",
116
+ backgroundColor: "rgba(15,23,42,0.52)",
117
+ display: "flex",
118
+ alignItems: "center",
119
+ justifyContent: "center",
120
+ zIndex: "9999",
121
+ transition: "opacity 0.3s ease",
122
+ backdropFilter: "blur(14px)",
123
+ padding: "16px"
124
+ });
125
+ const container = document.createElement("div");
126
+ Object.assign(container.style, {
127
+ width: options.width || "450px",
128
+ height: options.height || "min(600px, 90vh)",
129
+ backgroundColor: "transparent",
130
+ borderRadius: "28px",
131
+ overflow: "visible",
132
+ position: "relative",
133
+ boxShadow: "none",
134
+ maxWidth: "calc(100vw - 32px)",
135
+ maxHeight: "calc(100vh - 32px)",
136
+ transition: "height 180ms ease",
137
+ willChange: "height"
138
+ });
139
+ const closeBtn = document.createElement("button");
140
+ closeBtn.innerHTML = "\xD7";
141
+ Object.assign(closeBtn.style, {
142
+ position: "absolute",
143
+ right: "-14px",
144
+ top: "-14px",
145
+ fontSize: "20px",
146
+ width: "36px",
147
+ height: "36px",
148
+ borderRadius: "9999px",
149
+ border: "1px solid rgba(255,255,255,0.5)",
150
+ background: "rgba(255,255,255,0.9)",
151
+ cursor: "pointer",
152
+ color: "#475569",
153
+ zIndex: "2",
154
+ boxShadow: "0 10px 30px rgba(15,23,42,0.16)"
155
+ });
156
+ closeBtn.onclick = () => {
157
+ this.close();
158
+ options.onCloseButton?.();
159
+ };
160
+ this.iframe = document.createElement("iframe");
161
+ this.iframe.src = url;
162
+ Object.assign(this.iframe.style, {
163
+ width: "100%",
164
+ height: "100%",
165
+ border: "none",
166
+ borderRadius: "28px",
167
+ background: "transparent",
168
+ display: "block"
169
+ });
170
+ container.appendChild(closeBtn);
171
+ container.appendChild(this.iframe);
172
+ this.modal.appendChild(container);
173
+ document.body.appendChild(this.modal);
174
+ this.messageHandler = (event) => {
175
+ if (options.allowedOrigin && options.allowedOrigin !== "*") {
176
+ if (event.origin !== options.allowedOrigin) {
177
+ return;
178
+ }
179
+ }
180
+ const data = event.data;
181
+ if (!data || typeof data !== "object" || !data.type) {
182
+ return;
183
+ }
184
+ options.onMessage(data, container);
185
+ };
186
+ window.addEventListener("message", this.messageHandler);
187
+ }
188
+ close() {
189
+ if (typeof window === "undefined") return;
190
+ if (this.messageHandler) {
191
+ window.removeEventListener("message", this.messageHandler);
192
+ this.messageHandler = null;
193
+ }
194
+ if (this.modal?.parentNode) {
195
+ this.modal.parentNode.removeChild(this.modal);
196
+ }
197
+ this.modal = null;
198
+ this.iframe = null;
199
+ }
200
+ };
201
+
202
+ // src/client.ts
203
+ var PaymentUI = class extends HostedFrameModal {
204
+ /**
205
+ * Opens the payment checkout page in an iframe modal.
206
+ * @param urlOrParams - The checkout page URL or payment parameters
207
+ * @param options - UI options
208
+ */
209
+ openPayment(urlOrParams, options) {
210
+ if (typeof document === "undefined") return;
211
+ if (this.modal) return;
212
+ let checkoutUrl;
213
+ if (typeof urlOrParams === "string") {
214
+ checkoutUrl = urlOrParams;
215
+ } else {
216
+ const {
217
+ appId,
218
+ productId,
219
+ priceId,
220
+ productCode,
221
+ userId,
222
+ checkoutUrl: checkoutUrlParam,
223
+ baseUrl = "https://pay.imgto.link"
224
+ } = urlOrParams;
225
+ const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
226
+ if (productCode) {
227
+ checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
228
+ } else if (productId && priceId) {
229
+ checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
230
+ } else {
231
+ throw new Error(
232
+ "Either productCode or both productId and priceId are required"
233
+ );
234
+ }
235
+ }
236
+ const finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale);
237
+ this.openHostedFrame(finalUrl, {
238
+ allowedOrigin: options?.allowedOrigin,
239
+ onCloseButton: () => options?.onCancel?.(),
240
+ onMessage: (data, container) => {
241
+ switch (data.type) {
242
+ case "PAYMENT_SUCCESS":
243
+ options?.onSuccess?.(data.orderId);
244
+ break;
245
+ case "PAYMENT_CANCELLED":
246
+ options?.onCancel?.(data.orderId);
247
+ break;
248
+ case "PAYMENT_RESIZE":
249
+ if (data.height) {
250
+ const maxHeight = window.innerHeight * 0.9;
251
+ const newHeight = Math.min(data.height, maxHeight);
252
+ container.style.height = `${newHeight}px`;
253
+ }
254
+ break;
255
+ case "PAYMENT_CLOSE":
256
+ this.close();
257
+ options?.onClose?.();
258
+ break;
259
+ }
260
+ }
261
+ });
262
+ }
263
+ /**
264
+ * Poll order status from integrator's API endpoint
265
+ * @param statusUrl - The integrator's API endpoint to check order status
266
+ * @param options - Polling options
267
+ * @returns Promise that resolves when order is paid or rejects on timeout/failure
268
+ */
269
+ async pollOrderStatus(statusUrl, options) {
270
+ const interval = options?.interval || 3e3;
271
+ const timeout = options?.timeout || 3e5;
272
+ const startTime = Date.now();
273
+ return new Promise((resolve, reject) => {
274
+ const poll = async () => {
275
+ try {
276
+ const response = await fetch(statusUrl);
277
+ if (!response.ok) {
278
+ throw new Error(`Status check failed: ${response.status}`);
279
+ }
280
+ const status = await response.json();
281
+ options?.onStatusChange?.(status);
282
+ if (status.status === "PAID") {
283
+ resolve(status);
284
+ return;
285
+ }
286
+ if (status.status === "CANCELLED" || status.status === "FAILED") {
287
+ reject(new Error(`Order ${status.status.toLowerCase()}`));
288
+ return;
289
+ }
290
+ if (Date.now() - startTime > timeout) {
291
+ reject(new Error("Polling timeout"));
292
+ return;
293
+ }
294
+ setTimeout(poll, interval);
295
+ } catch (error) {
296
+ if (Date.now() - startTime > timeout) {
297
+ reject(error);
298
+ return;
299
+ }
300
+ setTimeout(poll, interval);
301
+ }
302
+ };
303
+ poll();
304
+ });
305
+ }
306
+ };
307
+ function createPaymentUI() {
308
+ return new PaymentUI();
309
+ }
310
+
311
+ // src/login.ts
312
+ function getOrigin(value) {
313
+ if (!value) return null;
314
+ try {
315
+ return new URL(value).origin;
316
+ } catch {
317
+ return null;
318
+ }
319
+ }
320
+ function buildLoginUrl(params) {
321
+ const { appId, baseUrl = "https://pay.imgto.link", loginUrl } = params;
322
+ const rawBase = (loginUrl || baseUrl).replace(/\/$/, "");
323
+ const parentOrigin = typeof window !== "undefined" ? window.location.origin : void 0;
324
+ let finalUrl;
325
+ try {
326
+ const url = new URL(rawBase);
327
+ if (!/\/auth\/connect\/[^/]+$/.test(url.pathname)) {
328
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/auth/connect/${appId}`.replace(
329
+ /\/{2,}/g,
330
+ "/"
331
+ );
332
+ }
333
+ finalUrl = url.toString();
334
+ } catch (_error) {
335
+ if (/\/auth\/connect\/[^/]+$/.test(rawBase)) {
336
+ finalUrl = rawBase;
337
+ } else {
338
+ finalUrl = `${rawBase}/auth/connect/${appId}`.replace(/\/{2,}/g, "/");
339
+ }
340
+ }
341
+ finalUrl = applyLocaleToUrl(finalUrl, getAutoResolvedLocale(params.locale));
342
+ try {
343
+ const url = new URL(finalUrl);
344
+ if (params.preferredChannel) {
345
+ url.searchParams.set("preferredChannel", params.preferredChannel);
346
+ }
347
+ if (parentOrigin) {
348
+ url.searchParams.set("origin", parentOrigin);
349
+ }
350
+ return url.toString();
351
+ } catch (_error) {
352
+ const searchParams = new URLSearchParams();
353
+ if (params.preferredChannel) {
354
+ searchParams.set("preferredChannel", params.preferredChannel);
355
+ }
356
+ if (parentOrigin) {
357
+ searchParams.set("origin", parentOrigin);
358
+ }
359
+ const query = searchParams.toString();
360
+ if (!query) {
361
+ return finalUrl;
362
+ }
363
+ const separator = finalUrl.includes("?") ? "&" : "?";
364
+ return `${finalUrl}${separator}${query}`;
365
+ }
366
+ }
367
+ function extractLoginResult(data) {
368
+ return {
369
+ avatar: data.avatar,
370
+ channel: data.channel,
371
+ email: data.email,
372
+ expiresAt: data.expiresAt,
373
+ legacyCasdoorId: data.legacyCasdoorId,
374
+ loginToken: data.loginToken,
375
+ name: data.name,
376
+ userId: data.userId,
377
+ wechatOpenId: data.wechatOpenId,
378
+ wechatUnionId: data.wechatUnionId
379
+ };
380
+ }
381
+ var LOGIN_UI_LOG_PREFIX = "[Youidian LoginUI]";
382
+ function logLoginDebug(message, details) {
383
+ if (typeof console === "undefined") return;
384
+ console.debug(LOGIN_UI_LOG_PREFIX, message, details || {});
385
+ }
386
+ function logLoginInfo(message, details) {
387
+ if (typeof console === "undefined") return;
388
+ console.info(LOGIN_UI_LOG_PREFIX, message, details || {});
389
+ }
390
+ function logLoginWarn(message, details) {
391
+ if (typeof console === "undefined") return;
392
+ console.warn(LOGIN_UI_LOG_PREFIX, message, details || {});
393
+ }
394
+ var LoginUI = class {
395
+ constructor() {
396
+ __publicField(this, "popup", null);
397
+ __publicField(this, "messageHandler", null);
398
+ __publicField(this, "closeMonitor", null);
399
+ __publicField(this, "completed", false);
400
+ }
401
+ cleanup() {
402
+ if (typeof window !== "undefined" && this.messageHandler) {
403
+ window.removeEventListener("message", this.messageHandler);
404
+ }
405
+ if (typeof window !== "undefined" && this.closeMonitor) {
406
+ window.clearInterval(this.closeMonitor);
407
+ }
408
+ this.messageHandler = null;
409
+ this.closeMonitor = null;
410
+ this.popup = null;
411
+ this.completed = false;
412
+ }
413
+ close() {
414
+ logLoginInfo("close() called", {
415
+ hasPopup: Boolean(this.popup),
416
+ popupClosed: this.popup?.closed ?? true
417
+ });
418
+ if (this.popup && !this.popup.closed) {
419
+ this.popup.close();
420
+ }
421
+ this.cleanup();
422
+ }
423
+ openLogin(params, options) {
424
+ if (typeof window === "undefined") return;
425
+ if (this.popup && !this.popup.closed) return;
426
+ const finalUrl = buildLoginUrl(params);
427
+ logLoginInfo("Opening hosted login popup", {
428
+ appId: params.appId,
429
+ loginUrl: finalUrl,
430
+ allowedOrigin: options?.allowedOrigin || null,
431
+ autoClose: options?.autoClose ?? true
432
+ });
433
+ const popupWidth = 520;
434
+ const popupHeight = 720;
435
+ const left = Math.max(
436
+ 0,
437
+ Math.round(window.screenX + (window.outerWidth - popupWidth) / 2)
438
+ );
439
+ const top = Math.max(
440
+ 0,
441
+ Math.round(window.screenY + (window.outerHeight - popupHeight) / 2)
442
+ );
443
+ const features = [
444
+ "popup=yes",
445
+ `width=${popupWidth}`,
446
+ `height=${popupHeight}`,
447
+ `left=${left}`,
448
+ `top=${top}`,
449
+ "resizable=yes",
450
+ "scrollbars=yes"
451
+ ].join(",");
452
+ const popup = window.open(finalUrl, "youidian-login", features);
453
+ if (!popup) {
454
+ logLoginWarn("Popup blocked by the browser");
455
+ options?.onError?.("Login popup was blocked");
456
+ return;
457
+ }
458
+ this.popup = popup;
459
+ this.completed = false;
460
+ const allowedOrigin = options?.allowedOrigin;
461
+ const popupOrigin = getOrigin(finalUrl);
462
+ this.messageHandler = (event) => {
463
+ if (allowedOrigin && allowedOrigin !== "*" && event.origin !== allowedOrigin) {
464
+ logLoginWarn("Ignored message due to allowedOrigin mismatch", {
465
+ allowedOrigin,
466
+ eventOrigin: event.origin
467
+ });
468
+ return;
469
+ }
470
+ if (!allowedOrigin && popupOrigin && event.origin !== popupOrigin) {
471
+ logLoginWarn("Ignored message due to popup origin mismatch", {
472
+ expectedOrigin: popupOrigin,
473
+ eventOrigin: event.origin
474
+ });
475
+ return;
476
+ }
477
+ const data = event.data;
478
+ if (!data || typeof data !== "object" || !data.type) {
479
+ logLoginDebug("Ignored non-login postMessage payload");
480
+ return;
481
+ }
482
+ logLoginInfo("Received login event", {
483
+ type: data.type,
484
+ eventOrigin: event.origin
485
+ });
486
+ switch (data.type) {
487
+ case "LOGIN_SUCCESS":
488
+ this.completed = true;
489
+ logLoginInfo("Login succeeded", {
490
+ channel: data.channel || null,
491
+ userId: data.userId || null
492
+ });
493
+ options?.onSuccess?.(extractLoginResult(data));
494
+ break;
495
+ case "LOGIN_CANCELLED":
496
+ logLoginInfo("Login cancelled");
497
+ options?.onCancel?.();
498
+ break;
499
+ case "LOGIN_RESIZE":
500
+ break;
501
+ case "LOGIN_ERROR":
502
+ logLoginWarn("Login flow reported an error", {
503
+ message: data.message || data.error || null
504
+ });
505
+ options?.onError?.(data.message || data.error, data);
506
+ break;
507
+ case "LOGIN_CLOSE":
508
+ if (options?.autoClose === false) {
509
+ logLoginInfo("Login popup requested close; autoClose disabled, keeping popup open");
510
+ options?.onClose?.();
511
+ break;
512
+ }
513
+ logLoginInfo("Login popup requested close; autoClose enabled");
514
+ this.close();
515
+ options?.onClose?.();
516
+ break;
517
+ }
518
+ };
519
+ window.addEventListener("message", this.messageHandler);
520
+ this.closeMonitor = window.setInterval(() => {
521
+ if (!this.popup || this.popup.closed) {
522
+ const shouldTreatAsCancel = !this.completed;
523
+ logLoginInfo("Detected popup closed", {
524
+ shouldTreatAsCancel
525
+ });
526
+ this.cleanup();
527
+ if (shouldTreatAsCancel) {
528
+ options?.onCancel?.();
529
+ }
530
+ options?.onClose?.();
531
+ }
532
+ }, 500);
533
+ }
534
+ };
535
+ function createLoginUI() {
536
+ return new LoginUI();
537
+ }
538
+
539
+ // src/server.ts
540
+ import crypto from "crypto";
541
+ var PaymentClient = class {
542
+ // 用于生成 checkout URL
543
+ constructor(options) {
544
+ __publicField(this, "appId");
545
+ __publicField(this, "appSecret");
546
+ __publicField(this, "apiUrl");
547
+ // 用于 API 调用
548
+ __publicField(this, "checkoutUrl");
549
+ if (!options.appId) throw new Error("appId is required");
550
+ if (!options.appSecret) throw new Error("appSecret is required");
551
+ this.appId = options.appId;
552
+ this.appSecret = options.appSecret;
553
+ const apiUrl = options.apiUrl || options.baseUrl || "https://pay-api.imgto.link";
554
+ this.apiUrl = apiUrl.replace(/\/$/, "");
555
+ const checkoutUrl = options.checkoutUrl || options.baseUrl || "https://pay.imgto.link";
556
+ this.checkoutUrl = checkoutUrl.replace(/\/$/, "");
557
+ }
558
+ /**
559
+ * Generate SHA256 signature for the request
560
+ * Logic: SHA256(appId + appSecret + timestamp)
561
+ */
562
+ generateSignature(timestamp) {
563
+ const str = `${this.appId}${this.appSecret}${timestamp}`;
564
+ return crypto.createHash("sha256").update(str).digest("hex");
565
+ }
566
+ /**
567
+ * Internal request helper for Gateway API
568
+ */
569
+ async request(method, path, body) {
570
+ const timestamp = Date.now();
571
+ const signature = this.generateSignature(timestamp);
572
+ const url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`;
573
+ const headers = {
574
+ "Content-Type": "application/json",
575
+ "X-Pay-Timestamp": timestamp.toString(),
576
+ "X-Pay-Sign": signature
577
+ };
578
+ const options = {
579
+ method,
580
+ headers,
581
+ body: body ? JSON.stringify(body) : void 0
582
+ };
583
+ const response = await fetch(url, options);
584
+ if (!response.ok) {
585
+ const errorText = await response.text();
586
+ throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);
587
+ }
588
+ const json = await response.json();
589
+ if (json.error) {
590
+ throw new Error(`Payment API Error: ${json.error}`);
591
+ }
592
+ return json.data;
593
+ }
594
+ /**
595
+ * Decrypts the callback notification payload using AES-256-GCM.
596
+ * @param notification - The encrypted notification from payment webhook
597
+ * @returns Decrypted payment callback data
598
+ */
599
+ decryptCallback(notification) {
600
+ try {
601
+ const { iv, encryptedData, authTag } = notification;
602
+ const key = crypto.createHash("sha256").update(this.appSecret).digest();
603
+ const decipher = crypto.createDecipheriv(
604
+ "aes-256-gcm",
605
+ key,
606
+ Buffer.from(iv, "hex")
607
+ );
608
+ decipher.setAuthTag(Buffer.from(authTag, "hex"));
609
+ let decrypted = decipher.update(encryptedData, "hex", "utf8");
610
+ decrypted += decipher.final("utf8");
611
+ return JSON.parse(decrypted);
612
+ } catch (error) {
613
+ throw new Error(
614
+ "Failed to decrypt payment callback: Invalid secret or tampered data."
615
+ );
616
+ }
617
+ }
618
+ /**
619
+ * Fetch products for the configured app.
620
+ */
621
+ async getProducts(options) {
622
+ const params = new URLSearchParams();
623
+ if (options?.locale) params.append("locale", options.locale);
624
+ if (options?.currency) params.append("currency", options.currency);
625
+ const path = params.toString() ? `/products?${params.toString()}` : "/products";
626
+ return this.request("GET", path);
627
+ }
628
+ /**
629
+ * Create a new order
630
+ * @param params - Order creation parameters
631
+ * @returns Order details with payment parameters
632
+ */
633
+ async createOrder(params) {
634
+ return this.request("POST", "/orders", params);
635
+ }
636
+ /**
637
+ * Create a WeChat Mini Program order (channel fixed to WECHAT_MINI)
638
+ * @param params - Mini program order parameters
639
+ * @returns Order details with payment parameters
640
+ */
641
+ async createMiniProgramOrder(params) {
642
+ const { openid, ...rest } = params;
643
+ return this.request("POST", "/orders", {
644
+ ...rest,
645
+ channel: "WECHAT_MINI",
646
+ openid,
647
+ metadata: { openid }
648
+ });
649
+ }
650
+ /**
651
+ * Pay for an existing order
652
+ * @param orderId - The order ID to pay
653
+ * @param params - Payment parameters including channel
654
+ */
655
+ async payOrder(orderId, params) {
656
+ return this.request("POST", `/orders/${orderId}/pay`, params);
657
+ }
658
+ /**
659
+ * Query order status
660
+ * @param orderId - The order ID to query
661
+ */
662
+ async getOrderStatus(orderId) {
663
+ return this.request("GET", `/orders/${orderId}`);
664
+ }
665
+ /**
666
+ * Get order details (full order information)
667
+ * @param orderId - The order ID to query
668
+ */
669
+ async getOrderDetails(orderId) {
670
+ return this.request("GET", `/orders/${orderId}/details`);
671
+ }
672
+ /**
673
+ * Get orders list with pagination
674
+ * @param params - Query parameters (pagination, filters)
675
+ * @returns Orders list and pagination info
676
+ */
677
+ async getOrders(params) {
678
+ const queryParams = new URLSearchParams();
679
+ if (params?.page) queryParams.append("page", params.page.toString());
680
+ if (params?.pageSize)
681
+ queryParams.append("pageSize", params.pageSize.toString());
682
+ if (params?.userId) queryParams.append("userId", params.userId);
683
+ if (params?.status) queryParams.append("status", params.status);
684
+ if (params?.startDate) queryParams.append("startDate", params.startDate);
685
+ if (params?.endDate) queryParams.append("endDate", params.endDate);
686
+ const path = queryParams.toString() ? `/orders?${queryParams.toString()}` : "/orders";
687
+ return this.request("GET", path);
688
+ }
689
+ /**
690
+ * Get user entitlements in the legacy flat shape.
691
+ * @param userId - User ID
692
+ */
693
+ async getEntitlements(userId) {
694
+ return this.request("GET", `/users/${userId}/entitlements`);
695
+ }
696
+ /**
697
+ * Get user entitlements with full details (type, expiry, reset config, source)
698
+ * @param userId - User ID
699
+ */
700
+ async getEntitlementsDetail(userId) {
701
+ return this.request("GET", `/users/${userId}/entitlements/detail`);
702
+ }
703
+ /**
704
+ * Ensure user exists and auto-assign trial product if new user
705
+ * This should be called when user first logs in or registers
706
+ * @param userId - User ID
707
+ */
708
+ async ensureUserWithTrial(userId) {
709
+ return this.request("POST", `/users/${userId}/entitlements/bootstrap`, {});
710
+ }
711
+ /**
712
+ * Get a single entitlement value
713
+ * @param userId - User ID
714
+ * @param key - Entitlement key
715
+ */
716
+ async getEntitlementValue(userId, key) {
717
+ const entitlements = await this.getEntitlements(userId);
718
+ return entitlements[key] ?? null;
719
+ }
720
+ /**
721
+ * Consume numeric entitlement
722
+ * @param userId - User ID
723
+ * @param key - Entitlement key
724
+ * @param amount - Amount to consume
725
+ */
726
+ async consumeEntitlement(userId, key, amount, options) {
727
+ return this.request("POST", `/users/${userId}/entitlements/consume`, {
728
+ key,
729
+ amount,
730
+ ...options
731
+ });
732
+ }
733
+ /**
734
+ * Add numeric entitlement (e.g. refund)
735
+ * @param userId - User ID
736
+ * @param key - Entitlement key
737
+ * @param amount - Amount to add
738
+ */
739
+ async addEntitlement(userId, key, amount) {
740
+ return this.request("POST", `/users/${userId}/entitlements/add`, {
741
+ key,
742
+ amount
743
+ });
744
+ }
745
+ /**
746
+ * Toggle boolean entitlement
747
+ * @param userId - User ID
748
+ * @param key - Entitlement key
749
+ * @param enabled - Whether to enable
750
+ */
751
+ async toggleEntitlement(userId, key, enabled) {
752
+ return this.request("POST", `/users/${userId}/entitlements/toggle`, {
753
+ key,
754
+ enabled
755
+ });
756
+ }
757
+ /**
758
+ * Generate checkout URL for client-side payment
759
+ * @param productId - Product ID
760
+ * @param priceId - Price ID
761
+ * @returns Checkout page URL
762
+ */
763
+ getCheckoutUrl(productId, priceId) {
764
+ return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;
765
+ }
766
+ /**
767
+ * Verify a hosted login token and return the normalized login profile.
768
+ * This request is signed with your app credentials and routed through the worker API.
769
+ */
770
+ async verifyLoginToken(token) {
771
+ if (!token?.trim()) {
772
+ throw new Error("login token is required");
773
+ }
774
+ return this.request("POST", "/login/tokens/verify", { token: token.trim() });
775
+ }
776
+ };
777
+ export {
778
+ LoginUI,
779
+ PaymentClient,
780
+ PaymentUI,
781
+ createLoginUI,
782
+ createPaymentUI
783
+ };
784
+ //# sourceMappingURL=index.js.map