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