@youidian/pay-sdk 1.1.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs +118 -89
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +4 -8
- package/dist/client.d.ts +4 -8
- package/dist/client.js +117 -90
- package/dist/client.js.map +1 -1
- package/dist/hosted-modal-BZmYmXTU.d.cts +20 -0
- package/dist/hosted-modal-BZmYmXTU.d.ts +20 -0
- package/dist/index.cjs +127 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +127 -90
- package/dist/index.js.map +1 -1
- package/dist/login.cjs +304 -0
- package/dist/login.cjs.map +1 -0
- package/dist/login.d.cts +60 -0
- package/dist/login.d.ts +60 -0
- package/dist/login.js +278 -0
- package/dist/login.js.map +1 -0
- package/dist/server.cjs +10 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +21 -1
- package/dist/server.d.ts +21 -1
- package/dist/server.js +10 -0
- package/dist/server.js.map +1 -1
- package/package.json +7 -2
package/dist/login.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
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
|
+
|
|
101
|
+
// src/login.ts
|
|
102
|
+
function getOrigin(value) {
|
|
103
|
+
if (!value) return null;
|
|
104
|
+
try {
|
|
105
|
+
return new URL(value).origin;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function buildLoginUrl(params) {
|
|
111
|
+
const { appId, baseUrl = "https://pay.imgto.link", loginUrl } = params;
|
|
112
|
+
const rawBase = (loginUrl || baseUrl).replace(/\/$/, "");
|
|
113
|
+
const parentOrigin = typeof window !== "undefined" ? window.location.origin : void 0;
|
|
114
|
+
let finalUrl;
|
|
115
|
+
try {
|
|
116
|
+
const url = new URL(rawBase);
|
|
117
|
+
if (!/\/auth\/connect\/[^/]+$/.test(url.pathname)) {
|
|
118
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}/auth/connect/${appId}`.replace(
|
|
119
|
+
/\/{2,}/g,
|
|
120
|
+
"/"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
finalUrl = url.toString();
|
|
124
|
+
} catch (_error) {
|
|
125
|
+
if (/\/auth\/connect\/[^/]+$/.test(rawBase)) {
|
|
126
|
+
finalUrl = rawBase;
|
|
127
|
+
} else {
|
|
128
|
+
finalUrl = `${rawBase}/auth/connect/${appId}`.replace(/\/{2,}/g, "/");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
finalUrl = applyLocaleToUrl(finalUrl, getAutoResolvedLocale(params.locale));
|
|
132
|
+
try {
|
|
133
|
+
const url = new URL(finalUrl);
|
|
134
|
+
if (params.preferredChannel) {
|
|
135
|
+
url.searchParams.set("preferredChannel", params.preferredChannel);
|
|
136
|
+
}
|
|
137
|
+
if (parentOrigin) {
|
|
138
|
+
url.searchParams.set("origin", parentOrigin);
|
|
139
|
+
}
|
|
140
|
+
return url.toString();
|
|
141
|
+
} catch (_error) {
|
|
142
|
+
const searchParams = new URLSearchParams();
|
|
143
|
+
if (params.preferredChannel) {
|
|
144
|
+
searchParams.set("preferredChannel", params.preferredChannel);
|
|
145
|
+
}
|
|
146
|
+
if (parentOrigin) {
|
|
147
|
+
searchParams.set("origin", parentOrigin);
|
|
148
|
+
}
|
|
149
|
+
const query = searchParams.toString();
|
|
150
|
+
if (!query) {
|
|
151
|
+
return finalUrl;
|
|
152
|
+
}
|
|
153
|
+
const separator = finalUrl.includes("?") ? "&" : "?";
|
|
154
|
+
return `${finalUrl}${separator}${query}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function extractLoginResult(data) {
|
|
158
|
+
return {
|
|
159
|
+
avatar: data.avatar,
|
|
160
|
+
channel: data.channel,
|
|
161
|
+
email: data.email,
|
|
162
|
+
expiresAt: data.expiresAt,
|
|
163
|
+
legacyCasdoorId: data.legacyCasdoorId,
|
|
164
|
+
loginToken: data.loginToken,
|
|
165
|
+
name: data.name,
|
|
166
|
+
userId: data.userId,
|
|
167
|
+
wechatOpenId: data.wechatOpenId,
|
|
168
|
+
wechatUnionId: data.wechatUnionId
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
var LoginUI = class {
|
|
172
|
+
constructor() {
|
|
173
|
+
__publicField(this, "popup", null);
|
|
174
|
+
__publicField(this, "messageHandler", null);
|
|
175
|
+
__publicField(this, "closeMonitor", null);
|
|
176
|
+
__publicField(this, "completed", false);
|
|
177
|
+
}
|
|
178
|
+
cleanup() {
|
|
179
|
+
if (typeof window !== "undefined" && this.messageHandler) {
|
|
180
|
+
window.removeEventListener("message", this.messageHandler);
|
|
181
|
+
}
|
|
182
|
+
if (typeof window !== "undefined" && this.closeMonitor) {
|
|
183
|
+
window.clearInterval(this.closeMonitor);
|
|
184
|
+
}
|
|
185
|
+
this.messageHandler = null;
|
|
186
|
+
this.closeMonitor = null;
|
|
187
|
+
this.popup = null;
|
|
188
|
+
this.completed = false;
|
|
189
|
+
}
|
|
190
|
+
close() {
|
|
191
|
+
if (this.popup && !this.popup.closed) {
|
|
192
|
+
this.popup.close();
|
|
193
|
+
}
|
|
194
|
+
this.cleanup();
|
|
195
|
+
}
|
|
196
|
+
openLogin(params, options) {
|
|
197
|
+
if (typeof window === "undefined") return;
|
|
198
|
+
if (this.popup && !this.popup.closed) return;
|
|
199
|
+
const finalUrl = buildLoginUrl(params);
|
|
200
|
+
const popupWidth = 520;
|
|
201
|
+
const popupHeight = 720;
|
|
202
|
+
const left = Math.max(
|
|
203
|
+
0,
|
|
204
|
+
Math.round(window.screenX + (window.outerWidth - popupWidth) / 2)
|
|
205
|
+
);
|
|
206
|
+
const top = Math.max(
|
|
207
|
+
0,
|
|
208
|
+
Math.round(window.screenY + (window.outerHeight - popupHeight) / 2)
|
|
209
|
+
);
|
|
210
|
+
const features = [
|
|
211
|
+
"popup=yes",
|
|
212
|
+
`width=${popupWidth}`,
|
|
213
|
+
`height=${popupHeight}`,
|
|
214
|
+
`left=${left}`,
|
|
215
|
+
`top=${top}`,
|
|
216
|
+
"resizable=yes",
|
|
217
|
+
"scrollbars=yes"
|
|
218
|
+
].join(",");
|
|
219
|
+
const popup = window.open(finalUrl, "youidian-login", features);
|
|
220
|
+
if (!popup) {
|
|
221
|
+
options?.onError?.("Login popup was blocked");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this.popup = popup;
|
|
225
|
+
this.completed = false;
|
|
226
|
+
const allowedOrigin = options?.allowedOrigin;
|
|
227
|
+
const popupOrigin = getOrigin(finalUrl);
|
|
228
|
+
this.messageHandler = (event) => {
|
|
229
|
+
if (allowedOrigin && allowedOrigin !== "*" && event.origin !== allowedOrigin) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (!allowedOrigin && popupOrigin && event.origin !== popupOrigin) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const data = event.data;
|
|
236
|
+
if (!data || typeof data !== "object" || !data.type) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
switch (data.type) {
|
|
240
|
+
case "LOGIN_SUCCESS":
|
|
241
|
+
this.completed = true;
|
|
242
|
+
options?.onSuccess?.(extractLoginResult(data));
|
|
243
|
+
break;
|
|
244
|
+
case "LOGIN_CANCELLED":
|
|
245
|
+
options?.onCancel?.(extractLoginResult(data));
|
|
246
|
+
break;
|
|
247
|
+
case "LOGIN_RESIZE":
|
|
248
|
+
break;
|
|
249
|
+
case "LOGIN_ERROR":
|
|
250
|
+
options?.onError?.(data.message || data.error, data);
|
|
251
|
+
break;
|
|
252
|
+
case "LOGIN_CLOSE":
|
|
253
|
+
this.close();
|
|
254
|
+
options?.onClose?.();
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
window.addEventListener("message", this.messageHandler);
|
|
259
|
+
this.closeMonitor = window.setInterval(() => {
|
|
260
|
+
if (!this.popup || this.popup.closed) {
|
|
261
|
+
const shouldTreatAsCancel = !this.completed;
|
|
262
|
+
this.cleanup();
|
|
263
|
+
if (shouldTreatAsCancel) {
|
|
264
|
+
options?.onCancel?.();
|
|
265
|
+
}
|
|
266
|
+
options?.onClose?.();
|
|
267
|
+
}
|
|
268
|
+
}, 500);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
function createLoginUI() {
|
|
272
|
+
return new LoginUI();
|
|
273
|
+
}
|
|
274
|
+
export {
|
|
275
|
+
LoginUI,
|
|
276
|
+
createLoginUI
|
|
277
|
+
};
|
|
278
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hosted-modal.ts","../src/login.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\nconst SDK_SUPPORTED_LOCALES = [\n\t\"en\",\n\t\"zh-CN\",\n\t\"zh-Hant\",\n\t\"fr\",\n\t\"de\",\n\t\"ja\",\n\t\"es\",\n\t\"ko\",\n\t\"nl\",\n\t\"it\",\n\t\"pt\",\n] as const\n\nconst SDK_DEFAULT_LOCALE = \"zh-CN\"\n\nconst SDK_LOCALE_ALIASES: Record<string, string> = {\n\ten: \"en\",\n\t\"en-us\": \"en\",\n\t\"en-gb\": \"en\",\n\tzh: \"zh-CN\",\n\t\"zh-cn\": \"zh-CN\",\n\t\"zh-tw\": \"zh-Hant\",\n\t\"zh-hk\": \"zh-Hant\",\n\t\"zh-hant\": \"zh-Hant\",\n\t\"zh-hans\": \"zh-CN\",\n\tfr: \"fr\",\n\tde: \"de\",\n\tja: \"ja\",\n\tes: \"es\",\n\tko: \"ko\",\n\tnl: \"nl\",\n\tit: \"it\",\n\tpt: \"pt\",\n}\n\nfunction matchSupportedBaseLocale(locale: string): string | undefined {\n\tconst base = locale.toLowerCase().split(\"-\")[0]\n\treturn SDK_SUPPORTED_LOCALES.find((item) => item.toLowerCase() === base)\n}\n\nexport function normalizeSdkLocale(locale?: string): string | undefined {\n\tif (!locale) return\n\n\tconst sanitized = locale.trim().replace(/_/g, \"-\")\n\tif (!sanitized) return\n\n\tconst lower = sanitized.toLowerCase()\n\tif (SDK_LOCALE_ALIASES[lower]) {\n\t\treturn SDK_LOCALE_ALIASES[lower]\n\t}\n\n\tlet canonical: string | undefined\n\ttry {\n\t\tcanonical = Intl.getCanonicalLocales(sanitized)[0]\n\t} catch {\n\t\tcanonical = undefined\n\t}\n\n\tconst candidates = [canonical, sanitized].filter(Boolean) as string[]\n\n\tfor (const candidate of candidates) {\n\t\tconst candidateLower = candidate.toLowerCase()\n\n\t\tif (SDK_LOCALE_ALIASES[candidateLower]) {\n\t\t\treturn SDK_LOCALE_ALIASES[candidateLower]\n\t\t}\n\t\tif ((SDK_SUPPORTED_LOCALES as readonly string[]).includes(candidate)) {\n\t\t\treturn candidate\n\t\t}\n\n\t\tconst baseMatch = matchSupportedBaseLocale(candidate)\n\t\tif (baseMatch) {\n\t\t\treturn baseMatch\n\t\t}\n\t}\n}\n\nexport function getBrowserLocale(): string | undefined {\n\tif (typeof navigator === \"undefined\") return\n\n\tfor (const candidate of navigator.languages || []) {\n\t\tconst normalized = normalizeSdkLocale(candidate)\n\t\tif (normalized) return normalized\n\t}\n\n\treturn normalizeSdkLocale(navigator.language)\n}\n\nexport function getAutoResolvedLocale(locale?: string): string {\n\treturn normalizeSdkLocale(locale) || getBrowserLocale() || SDK_DEFAULT_LOCALE\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","import {\n\tapplyLocaleToUrl,\n\tgetAutoResolvedLocale,\n\ttype HostedFrameMessage,\n} from \"./hosted-modal\"\n\nexport type LoginEventType =\n\t| \"LOGIN_SUCCESS\"\n\t| \"LOGIN_CANCELLED\"\n\t| \"LOGIN_CLOSE\"\n\t| \"LOGIN_RESIZE\"\n\t| \"LOGIN_ERROR\"\n\nexport interface LoginResult {\n\tavatar?: string | null\n\tchannel?: string\n\temail?: string | null\n\texpiresAt?: string\n\tlegacyCasdoorId?: string\n\tloginToken?: string\n\tname?: string | null\n\tuserId?: string\n\twechatOpenId?: string | null\n\twechatUnionId?: string | null\n}\n\nexport interface LoginEventData extends HostedFrameMessage, LoginResult {\n\ttype: LoginEventType\n\terror?: string\n\tmessage?: string\n}\n\nexport interface LoginUIOptions {\n\t/** Origin to validate postMessage (defaults to '*', set for security) */\n\tallowedOrigin?: string\n\tonCancel?: (result?: LoginResult) => void\n\tonClose?: () => void\n\tonError?: (message?: string, data?: LoginEventData) => void\n\tonSuccess?: (result: LoginResult) => void\n}\n\nexport interface LoginParams {\n\tappId: string\n\n\t/**\n\t * @deprecated Use loginUrl instead\n\t * Defaults to https://pay.imgto.link\n\t */\n\tbaseUrl?: string\n\n\t/**\n\t * Hosted login page base URL (e.g. https://pay.youidian.com)\n\t * Defaults to https://pay.imgto.link\n\t */\n\tloginUrl?: string\n\n\t/**\n\t * Optional locale prefix to prepend to the hosted login path.\n\t * If omitted, the SDK automatically resolves the locale from the browser language.\n\t */\n\tlocale?: string\n\n\t/** Preferred provider/channel code */\n\tpreferredChannel?: string\n}\n\nfunction getOrigin(value?: string): string | null {\n\tif (!value) return null\n\ttry {\n\t\treturn new URL(value).origin\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction buildLoginUrl(params: LoginParams): string {\n\tconst { appId, baseUrl = \"https://pay.imgto.link\", loginUrl } = params\n\tconst rawBase = (loginUrl || baseUrl).replace(/\\/$/, \"\")\n\tconst parentOrigin =\n\t\ttypeof window !== \"undefined\" ? window.location.origin : undefined\n\n\tlet finalUrl: string\n\ttry {\n\t\tconst url = new URL(rawBase)\n\t\tif (!/\\/auth\\/connect\\/[^/]+$/.test(url.pathname)) {\n\t\t\turl.pathname =\n\t\t\t\t`${url.pathname.replace(/\\/$/, \"\")}/auth/connect/${appId}`.replace(\n\t\t\t\t\t/\\/{2,}/g,\n\t\t\t\t\t\"/\",\n\t\t\t\t)\n\t\t}\n\t\tfinalUrl = url.toString()\n\t} catch (_error) {\n\t\tif (/\\/auth\\/connect\\/[^/]+$/.test(rawBase)) {\n\t\t\tfinalUrl = rawBase\n\t\t} else {\n\t\t\tfinalUrl = `${rawBase}/auth/connect/${appId}`.replace(/\\/{2,}/g, \"/\")\n\t\t}\n\t}\n\n\tfinalUrl = applyLocaleToUrl(finalUrl, getAutoResolvedLocale(params.locale))\n\n\ttry {\n\t\tconst url = new URL(finalUrl)\n\t\tif (params.preferredChannel) {\n\t\t\turl.searchParams.set(\"preferredChannel\", params.preferredChannel)\n\t\t}\n\t\tif (parentOrigin) {\n\t\t\turl.searchParams.set(\"origin\", parentOrigin)\n\t\t}\n\t\treturn url.toString()\n\t} catch (_error) {\n\t\tconst searchParams = new URLSearchParams()\n\t\tif (params.preferredChannel) {\n\t\t\tsearchParams.set(\"preferredChannel\", params.preferredChannel)\n\t\t}\n\t\tif (parentOrigin) {\n\t\t\tsearchParams.set(\"origin\", parentOrigin)\n\t\t}\n\t\tconst query = searchParams.toString()\n\t\tif (!query) {\n\t\t\treturn finalUrl\n\t\t}\n\t\tconst separator = finalUrl.includes(\"?\") ? \"&\" : \"?\"\n\t\treturn `${finalUrl}${separator}${query}`\n\t}\n}\n\nfunction extractLoginResult(data: LoginEventData): LoginResult {\n\treturn {\n\t\tavatar: data.avatar,\n\t\tchannel: data.channel,\n\t\temail: data.email,\n\t\texpiresAt: data.expiresAt,\n\t\tlegacyCasdoorId: data.legacyCasdoorId,\n\t\tloginToken: data.loginToken,\n\t\tname: data.name,\n\t\tuserId: data.userId,\n\t\twechatOpenId: data.wechatOpenId,\n\t\twechatUnionId: data.wechatUnionId,\n\t}\n}\n\nexport class LoginUI {\n\tprivate popup: Window | null = null\n\tprivate messageHandler: ((event: MessageEvent) => void) | null = null\n\tprivate closeMonitor: number | null = null\n\tprivate completed = false\n\n\tprivate cleanup() {\n\t\tif (typeof window !== \"undefined\" && this.messageHandler) {\n\t\t\twindow.removeEventListener(\"message\", this.messageHandler)\n\t\t}\n\t\tif (typeof window !== \"undefined\" && this.closeMonitor) {\n\t\t\twindow.clearInterval(this.closeMonitor)\n\t\t}\n\t\tthis.messageHandler = null\n\t\tthis.closeMonitor = null\n\t\tthis.popup = null\n\t\tthis.completed = false\n\t}\n\n\tclose() {\n\t\tif (this.popup && !this.popup.closed) {\n\t\t\tthis.popup.close()\n\t\t}\n\t\tthis.cleanup()\n\t}\n\n\topenLogin(params: LoginParams, options?: LoginUIOptions) {\n\t\tif (typeof window === \"undefined\") return\n\t\tif (this.popup && !this.popup.closed) return\n\n\t\tconst finalUrl = buildLoginUrl(params)\n\t\tconst popupWidth = 520\n\t\tconst popupHeight = 720\n\t\tconst left = Math.max(\n\t\t\t0,\n\t\t\tMath.round(window.screenX + (window.outerWidth - popupWidth) / 2),\n\t\t)\n\t\tconst top = Math.max(\n\t\t\t0,\n\t\t\tMath.round(window.screenY + (window.outerHeight - popupHeight) / 2),\n\t\t)\n\t\tconst features = [\n\t\t\t\"popup=yes\",\n\t\t\t`width=${popupWidth}`,\n\t\t\t`height=${popupHeight}`,\n\t\t\t`left=${left}`,\n\t\t\t`top=${top}`,\n\t\t\t\"resizable=yes\",\n\t\t\t\"scrollbars=yes\",\n\t\t].join(\",\")\n\n\t\tconst popup = window.open(finalUrl, \"youidian-login\", features)\n\t\tif (!popup) {\n\t\t\toptions?.onError?.(\"Login popup was blocked\")\n\t\t\treturn\n\t\t}\n\n\t\tthis.popup = popup\n\t\tthis.completed = false\n\n\t\tconst allowedOrigin = options?.allowedOrigin\n\t\tconst popupOrigin = getOrigin(finalUrl)\n\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\tif (\n\t\t\t\tallowedOrigin &&\n\t\t\t\tallowedOrigin !== \"*\" &&\n\t\t\t\tevent.origin !== allowedOrigin\n\t\t\t) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!allowedOrigin && popupOrigin && event.origin !== popupOrigin) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst data = event.data as LoginEventData\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 \"LOGIN_SUCCESS\":\n\t\t\t\t\tthis.completed = true\n\t\t\t\t\toptions?.onSuccess?.(extractLoginResult(data))\n\t\t\t\t\tbreak\n\t\t\t\tcase \"LOGIN_CANCELLED\":\n\t\t\t\t\toptions?.onCancel?.(extractLoginResult(data))\n\t\t\t\t\tbreak\n\t\t\t\tcase \"LOGIN_RESIZE\":\n\t\t\t\t\tbreak\n\t\t\t\tcase \"LOGIN_ERROR\":\n\t\t\t\t\toptions?.onError?.(data.message || data.error, data)\n\t\t\t\t\tbreak\n\t\t\t\tcase \"LOGIN_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\n\t\twindow.addEventListener(\"message\", this.messageHandler)\n\n\t\tthis.closeMonitor = window.setInterval(() => {\n\t\t\tif (!this.popup || this.popup.closed) {\n\t\t\t\tconst shouldTreatAsCancel = !this.completed\n\t\t\t\tthis.cleanup()\n\t\t\t\tif (shouldTreatAsCancel) {\n\t\t\t\t\toptions?.onCancel?.()\n\t\t\t\t}\n\t\t\t\toptions?.onClose?.()\n\t\t\t}\n\t\t}, 500)\n\t}\n}\n\nexport function createLoginUI(): LoginUI {\n\treturn new LoginUI()\n}\n"],"mappings":"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","names":[]}
|
package/dist/server.cjs
CHANGED
|
@@ -245,6 +245,16 @@ var PaymentClient = class {
|
|
|
245
245
|
getCheckoutUrl(productId, priceId) {
|
|
246
246
|
return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Verify a hosted login token and return the normalized login profile.
|
|
250
|
+
* This request is signed with your app credentials and routed through the worker API.
|
|
251
|
+
*/
|
|
252
|
+
async verifyLoginToken(token) {
|
|
253
|
+
if (!token?.trim()) {
|
|
254
|
+
throw new Error("login token is required");
|
|
255
|
+
}
|
|
256
|
+
return this.request("POST", "/login/tokens/verify", { token: token.trim() });
|
|
257
|
+
}
|
|
248
258
|
};
|
|
249
259
|
// Annotate the CommonJS export names for ESM import in node:
|
|
250
260
|
0 && (module.exports = {
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\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":["crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * 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":["crypto"]}
|
package/dist/server.d.cts
CHANGED
|
@@ -72,6 +72,21 @@ interface WechatJsapiPayParams {
|
|
|
72
72
|
signType: "RSA";
|
|
73
73
|
paySign: string;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Verified hosted login token payload.
|
|
77
|
+
*/
|
|
78
|
+
interface VerifiedLoginToken {
|
|
79
|
+
appId: string;
|
|
80
|
+
userId: string;
|
|
81
|
+
legacyCasdoorId?: string | null;
|
|
82
|
+
channel: string;
|
|
83
|
+
email?: string | null;
|
|
84
|
+
name?: string | null;
|
|
85
|
+
avatar?: string | null;
|
|
86
|
+
wechatOpenId?: string | null;
|
|
87
|
+
wechatUnionId?: string | null;
|
|
88
|
+
expiresAt: string;
|
|
89
|
+
}
|
|
75
90
|
/**
|
|
76
91
|
* SDK Client Options
|
|
77
92
|
*/
|
|
@@ -305,6 +320,11 @@ declare class PaymentClient {
|
|
|
305
320
|
* @returns Checkout page URL
|
|
306
321
|
*/
|
|
307
322
|
getCheckoutUrl(productId: string, priceId: string): string;
|
|
323
|
+
/**
|
|
324
|
+
* Verify a hosted login token and return the normalized login profile.
|
|
325
|
+
* This request is signed with your app credentials and routed through the worker API.
|
|
326
|
+
*/
|
|
327
|
+
verifyLoginToken(token: string): Promise<VerifiedLoginToken>;
|
|
308
328
|
}
|
|
309
329
|
|
|
310
|
-
export { type CreateMiniProgramOrderParams, type CreateOrderParams, type CreateOrderResponse, type GetOrdersParams, type GetOrdersResponse, type OrderDetails, type OrderListItem, type OrderStatus, type PaymentCallbackData, PaymentClient, type PaymentClientOptions, type PaymentNotification, type Product, type ProductEntitlements, type ProductPrice, type WechatJsapiPayParams };
|
|
330
|
+
export { type CreateMiniProgramOrderParams, type CreateOrderParams, type CreateOrderResponse, type GetOrdersParams, type GetOrdersResponse, type OrderDetails, type OrderListItem, type OrderStatus, type PaymentCallbackData, PaymentClient, type PaymentClientOptions, type PaymentNotification, type Product, type ProductEntitlements, type ProductPrice, type VerifiedLoginToken, type WechatJsapiPayParams };
|
package/dist/server.d.ts
CHANGED
|
@@ -72,6 +72,21 @@ interface WechatJsapiPayParams {
|
|
|
72
72
|
signType: "RSA";
|
|
73
73
|
paySign: string;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Verified hosted login token payload.
|
|
77
|
+
*/
|
|
78
|
+
interface VerifiedLoginToken {
|
|
79
|
+
appId: string;
|
|
80
|
+
userId: string;
|
|
81
|
+
legacyCasdoorId?: string | null;
|
|
82
|
+
channel: string;
|
|
83
|
+
email?: string | null;
|
|
84
|
+
name?: string | null;
|
|
85
|
+
avatar?: string | null;
|
|
86
|
+
wechatOpenId?: string | null;
|
|
87
|
+
wechatUnionId?: string | null;
|
|
88
|
+
expiresAt: string;
|
|
89
|
+
}
|
|
75
90
|
/**
|
|
76
91
|
* SDK Client Options
|
|
77
92
|
*/
|
|
@@ -305,6 +320,11 @@ declare class PaymentClient {
|
|
|
305
320
|
* @returns Checkout page URL
|
|
306
321
|
*/
|
|
307
322
|
getCheckoutUrl(productId: string, priceId: string): string;
|
|
323
|
+
/**
|
|
324
|
+
* Verify a hosted login token and return the normalized login profile.
|
|
325
|
+
* This request is signed with your app credentials and routed through the worker API.
|
|
326
|
+
*/
|
|
327
|
+
verifyLoginToken(token: string): Promise<VerifiedLoginToken>;
|
|
308
328
|
}
|
|
309
329
|
|
|
310
|
-
export { type CreateMiniProgramOrderParams, type CreateOrderParams, type CreateOrderResponse, type GetOrdersParams, type GetOrdersResponse, type OrderDetails, type OrderListItem, type OrderStatus, type PaymentCallbackData, PaymentClient, type PaymentClientOptions, type PaymentNotification, type Product, type ProductEntitlements, type ProductPrice, type WechatJsapiPayParams };
|
|
330
|
+
export { type CreateMiniProgramOrderParams, type CreateOrderParams, type CreateOrderResponse, type GetOrdersParams, type GetOrdersResponse, type OrderDetails, type OrderListItem, type OrderStatus, type PaymentCallbackData, PaymentClient, type PaymentClientOptions, type PaymentNotification, type Product, type ProductEntitlements, type ProductPrice, type VerifiedLoginToken, type WechatJsapiPayParams };
|
package/dist/server.js
CHANGED
|
@@ -213,6 +213,16 @@ var PaymentClient = class {
|
|
|
213
213
|
getCheckoutUrl(productId, priceId) {
|
|
214
214
|
return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;
|
|
215
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Verify a hosted login token and return the normalized login profile.
|
|
218
|
+
* This request is signed with your app credentials and routed through the worker API.
|
|
219
|
+
*/
|
|
220
|
+
async verifyLoginToken(token) {
|
|
221
|
+
if (!token?.trim()) {
|
|
222
|
+
throw new Error("login token is required");
|
|
223
|
+
}
|
|
224
|
+
return this.request("POST", "/login/tokens/verify", { token: token.trim() });
|
|
225
|
+
}
|
|
216
226
|
};
|
|
217
227
|
export {
|
|
218
228
|
PaymentClient
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\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/server.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Server Module\n * 用于服务端集成,包含签名、订单创建、回调解密等功能\n */\n\nimport crypto from \"crypto\"\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n\torderId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string\n\tchannelTransactionId?: string\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tamount: number\n\tcurrency: string\n\tdescription?: string\n\tpaidAt?: string\n\tcreatedAt: string\n\tchannel?: string\n\tproduct?: {\n\t\tcode: string\n\t\ttype: string\n\t\tname: string\n\t\tdescription?: string\n\t\tentitlements: ProductEntitlements\n\t}\n}\n\n/**\n * Product Entitlements\n */\nexport interface ProductEntitlements {\n\t[key: string]: any\n}\n\n/**\n * Product Price\n */\nexport interface ProductPrice {\n\tid: string\n\tcurrency: string\n\tamount: number\n\tdisplayAmount: string\n\tlocale: string | null\n\tisDefault: boolean\n}\n\n/**\n * Product Data\n */\nexport interface Product {\n\tid: string\n\tcode: string\n\ttype: string\n\tname: string\n\tdescription?: string\n\tentitlements: ProductEntitlements\n\tprices: ProductPrice[]\n}\n\n/**\n * 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":";;;;;AAKA,OAAO,YAAY;AAmOZ,IAAM,gBAAN,MAAoB;AAAA;AAAA,EAM1B,YAAY,SAA+B;AAL3C,wBAAiB;AACjB,wBAAiB;AACjB,wBAAiB;AACjB;AAAA,wBAAiB;AAGhB,QAAI,CAAC,QAAQ,MAAO,OAAM,IAAI,MAAM,mBAAmB;AACvD,QAAI,CAAC,QAAQ,UAAW,OAAM,IAAI,MAAM,uBAAuB;AAE/D,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ;AAGzB,UAAM,SACL,QAAQ,UAAU,QAAQ,WAAW;AACtC,SAAK,SAAS,OAAO,QAAQ,OAAO,EAAE;AAGtC,UAAM,cACL,QAAQ,eAAe,QAAQ,WAAW;AAC3C,SAAK,cAAc,YAAY,QAAQ,OAAO,EAAE;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,WAA2B;AACpD,UAAM,MAAM,GAAG,KAAK,KAAK,GAAG,KAAK,SAAS,GAAG,SAAS;AACtD,WAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,QACb,QACA,MACA,MACa;AACb,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,YAAY,KAAK,kBAAkB,SAAS;AAElD,UAAM,MAAM,GAAG,KAAK,MAAM,mBAAmB,KAAK,KAAK,GAAG,IAAI;AAE9D,UAAM,UAAuB;AAAA,MAC5B,gBAAgB;AAAA,MAChB,mBAAmB,UAAU,SAAS;AAAA,MACtC,cAAc;AAAA,IACf;AAEA,UAAM,UAAuB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACrC;AAEA,UAAM,WAAW,MAAM,MAAM,KAAK,OAAO;AAEzC,QAAI,CAAC,SAAS,IAAI;AACjB,YAAM,YAAY,MAAM,SAAS,KAAK;AACtC,YAAM,IAAI,MAAM,sBAAsB,SAAS,MAAM,MAAM,SAAS,EAAE;AAAA,IACvE;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,KAAK,OAAO;AACf,YAAM,IAAI,MAAM,sBAAsB,KAAK,KAAK,EAAE;AAAA,IACnD;AAEA,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,cAAwD;AACvE,QAAI;AACH,YAAM,EAAE,IAAI,eAAe,QAAQ,IAAI;AACvC,YAAM,MAAM,OAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,SAAS,EAAE,OAAO;AACtE,YAAM,WAAW,OAAO;AAAA,QACvB;AAAA,QACA;AAAA,QACA,OAAO,KAAK,IAAI,KAAK;AAAA,MACtB;AAEA,eAAS,WAAW,OAAO,KAAK,SAAS,KAAK,CAAC;AAE/C,UAAI,YAAY,SAAS,OAAO,eAAe,OAAO,MAAM;AAC5D,mBAAa,SAAS,MAAM,MAAM;AAElC,aAAO,KAAK,MAAM,SAAS;AAAA,IAC5B,SAAS,OAAO;AACf,YAAM,IAAI;AAAA,QACT;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,SAGK;AACtB,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,SAAS,OAAQ,QAAO,OAAO,UAAU,QAAQ,MAAM;AAC3D,QAAI,SAAS,SAAU,QAAO,OAAO,YAAY,QAAQ,QAAQ;AAEjE,UAAM,OAAO,OAAO,SAAS,IAC1B,aAAa,OAAO,SAAS,CAAC,KAC9B;AACH,WAAO,KAAK,QAAQ,OAAO,IAAI;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,QAAyD;AAC1E,WAAO,KAAK,QAAQ,QAAQ,WAAW,MAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,uBACL,QAC+B;AAC/B,UAAM,EAAE,QAAQ,GAAG,KAAK,IAAI;AAC5B,WAAO,KAAK,QAAQ,QAAQ,WAAW;AAAA,MACtC,GAAG;AAAA,MACH,SAAS;AAAA,MACT;AAAA,MACA,UAAU,EAAE,OAAO;AAAA,IACpB,CAA6B;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SACL,SACA,QAM+B;AAC/B,WAAO,KAAK,QAAQ,QAAQ,WAAW,OAAO,QAAQ,MAAM;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAe,SAAuC;AAC3D,WAAO,KAAK,QAAQ,OAAO,WAAW,OAAO,EAAE;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAgB,SAAwC;AAC7D,WAAO,KAAK,QAAQ,OAAO,WAAW,OAAO,UAAU;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,QAAsD;AACrE,UAAM,cAAc,IAAI,gBAAgB;AACxC,QAAI,QAAQ,KAAM,aAAY,OAAO,QAAQ,OAAO,KAAK,SAAS,CAAC;AACnE,QAAI,QAAQ;AACX,kBAAY,OAAO,YAAY,OAAO,SAAS,SAAS,CAAC;AAC1D,QAAI,QAAQ,OAAQ,aAAY,OAAO,UAAU,OAAO,MAAM;AAC9D,QAAI,QAAQ,OAAQ,aAAY,OAAO,UAAU,OAAO,MAAM;AAC9D,QAAI,QAAQ,UAAW,aAAY,OAAO,aAAa,OAAO,SAAS;AACvE,QAAI,QAAQ,QAAS,aAAY,OAAO,WAAW,OAAO,OAAO;AAEjE,UAAM,OAAO,YAAY,SAAS,IAC/B,WAAW,YAAY,SAAS,CAAC,KACjC;AACH,WAAO,KAAK,QAA2B,OAAO,IAAI;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAgB,QAA8C;AACnE,WAAO,KAAK,QAAQ,OAAO,UAAU,MAAM,eAAe;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBAAoB,QAAgB,KAA2B;AACpE,UAAM,eAAe,MAAM,KAAK,gBAAgB,MAAM;AACtD,WAAO,aAAa,GAAG;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBACL,QACA,KACA,QAC+B;AAC/B,WAAO,KAAK,QAAQ,QAAQ,UAAU,MAAM,yBAAyB;AAAA,MACpE;AAAA,MACA;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eACL,QACA,KACA,QAC+B;AAC/B,WAAO,KAAK,QAAQ,QAAQ,UAAU,MAAM,qBAAqB;AAAA,MAChE;AAAA,MACA;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,kBACL,QACA,KACA,SACkC;AAIlC,WAAO,KAAK,QAAQ,QAAQ,UAAU,MAAM,wBAAwB;AAAA,MACnE;AAAA,MACA;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,WAAmB,SAAyB;AAC1D,WAAO,GAAG,KAAK,WAAW,aAAa,KAAK,KAAK,IAAI,SAAS,IAAI,OAAO;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAiB,OAA4C;AAClE,QAAI,CAAC,OAAO,KAAK,GAAG;AACnB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC1C;AACA,WAAO,KAAK,QAAQ,QAAQ,wBAAwB,EAAE,OAAO,MAAM,KAAK,EAAE,CAAC;AAAA,EAC5E;AACD;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youidian/pay-sdk",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Youidian Payment SDK -
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Youidian Payment SDK - 统一支付与登录集成 SDK",
|
|
5
5
|
"author": "Youidian",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"import": "./dist/client.js",
|
|
20
20
|
"require": "./dist/client.cjs"
|
|
21
21
|
},
|
|
22
|
+
"./login": {
|
|
23
|
+
"types": "./dist/login.d.ts",
|
|
24
|
+
"import": "./dist/login.js",
|
|
25
|
+
"require": "./dist/login.cjs"
|
|
26
|
+
},
|
|
22
27
|
"./server": {
|
|
23
28
|
"types": "./dist/server.d.ts",
|
|
24
29
|
"import": "./dist/server.js",
|