@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/client.cjs +263 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +103 -0
- package/dist/client.d.ts +103 -0
- package/dist/client.js +237 -0
- package/dist/client.js.map +1 -0
- package/dist/hosted-modal-BZmYmXTU.d.cts +20 -0
- package/dist/hosted-modal-BZmYmXTU.d.ts +20 -0
- package/dist/index.cjs +823 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +784 -0
- package/dist/index.js.map +1 -0
- package/dist/login.cjs +358 -0
- package/dist/login.cjs.map +1 -0
- package/dist/login.d.cts +62 -0
- package/dist/login.d.ts +62 -0
- package/dist/login.js +332 -0
- package/dist/login.js.map +1 -0
- package/dist/server.cjs +279 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +385 -0
- package/dist/server.d.ts +385 -0
- package/dist/server.js +246 -0
- package/dist/server.js.map +1 -0
- package/package.json +57 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Youidian Payment SDK - Server Module
|
|
3
|
+
* 用于服务端集成,包含签名、订单创建、回调解密等功能
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Order status response
|
|
7
|
+
*/
|
|
8
|
+
interface OrderStatus {
|
|
9
|
+
orderId: string;
|
|
10
|
+
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
11
|
+
paidAt?: string;
|
|
12
|
+
channelTransactionId?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Order details response (full order information)
|
|
16
|
+
*/
|
|
17
|
+
interface OrderDetails {
|
|
18
|
+
orderId: string;
|
|
19
|
+
internalId: string;
|
|
20
|
+
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
21
|
+
amount: number;
|
|
22
|
+
currency: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
paidAt?: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
channel?: string;
|
|
27
|
+
product?: {
|
|
28
|
+
code: string;
|
|
29
|
+
type: string;
|
|
30
|
+
name: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
entitlements: ProductEntitlements;
|
|
33
|
+
metadata?: ProductMetadata | null;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Product Entitlements
|
|
38
|
+
*/
|
|
39
|
+
interface ProductEntitlements {
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Product Price
|
|
44
|
+
*/
|
|
45
|
+
interface ProductPrice {
|
|
46
|
+
id: string;
|
|
47
|
+
currency: string;
|
|
48
|
+
amount: number;
|
|
49
|
+
displayAmount: string;
|
|
50
|
+
locale: string | null;
|
|
51
|
+
isDefault: boolean;
|
|
52
|
+
}
|
|
53
|
+
interface ProductSubscriptionPeriod {
|
|
54
|
+
value: number;
|
|
55
|
+
unit: "days" | "months" | "years";
|
|
56
|
+
}
|
|
57
|
+
interface ProductResetRule {
|
|
58
|
+
resetInterval: "month";
|
|
59
|
+
}
|
|
60
|
+
interface ProductMetadata {
|
|
61
|
+
subscriptionPeriod?: ProductSubscriptionPeriod;
|
|
62
|
+
expiringEntitlements?: string[];
|
|
63
|
+
resetEntitlements?: Record<string, ProductResetRule>;
|
|
64
|
+
autoAssignOnNewUser?: boolean;
|
|
65
|
+
trialDurationDays?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Product Data
|
|
69
|
+
*/
|
|
70
|
+
interface Product {
|
|
71
|
+
id: string;
|
|
72
|
+
code: string;
|
|
73
|
+
type: string;
|
|
74
|
+
name: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
entitlements: ProductEntitlements;
|
|
77
|
+
prices: ProductPrice[];
|
|
78
|
+
metadata?: ProductMetadata | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* WeChat JSAPI Payment Parameters (for wx.requestPayment)
|
|
82
|
+
*/
|
|
83
|
+
interface WechatJsapiPayParams {
|
|
84
|
+
appId: string;
|
|
85
|
+
timeStamp: string;
|
|
86
|
+
nonceStr: string;
|
|
87
|
+
package: string;
|
|
88
|
+
signType: "RSA";
|
|
89
|
+
paySign: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Verified hosted login token payload.
|
|
93
|
+
*/
|
|
94
|
+
interface VerifiedLoginToken {
|
|
95
|
+
appId: string;
|
|
96
|
+
userId: string;
|
|
97
|
+
legacyCasdoorId?: string | null;
|
|
98
|
+
channel: string;
|
|
99
|
+
email?: string | null;
|
|
100
|
+
name?: string | null;
|
|
101
|
+
avatar?: string | null;
|
|
102
|
+
wechatOpenId?: string | null;
|
|
103
|
+
wechatUnionId?: string | null;
|
|
104
|
+
expiresAt: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* SDK Client Options
|
|
108
|
+
*/
|
|
109
|
+
interface PaymentClientOptions {
|
|
110
|
+
/** Application ID (Required) */
|
|
111
|
+
appId: string;
|
|
112
|
+
/** Application Secret (Required for server-side operations) */
|
|
113
|
+
appSecret: string;
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated Use apiUrl and checkoutUrl instead
|
|
116
|
+
* API Base URL (e.g. https://pay.youidian.com)
|
|
117
|
+
* If apiUrl or checkoutUrl is not provided, this will be used as fallback
|
|
118
|
+
* Default: https://pay.imgto.link
|
|
119
|
+
*/
|
|
120
|
+
baseUrl?: string;
|
|
121
|
+
/**
|
|
122
|
+
* API server URL for backend requests (e.g. https://api.youidian.com)
|
|
123
|
+
* Default: https://pay-api.imgto.link
|
|
124
|
+
*/
|
|
125
|
+
apiUrl?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Checkout page URL for client-side payment (e.g. https://pay.youidian.com)
|
|
128
|
+
* Default: https://pay.imgto.link
|
|
129
|
+
*/
|
|
130
|
+
checkoutUrl?: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create Order Parameters
|
|
134
|
+
*/
|
|
135
|
+
interface CreateOrderParams {
|
|
136
|
+
productId?: string;
|
|
137
|
+
priceId?: string;
|
|
138
|
+
channel?: string;
|
|
139
|
+
userId: string;
|
|
140
|
+
returnUrl?: string;
|
|
141
|
+
metadata?: Record<string, any>;
|
|
142
|
+
merchantOrderId?: string;
|
|
143
|
+
openid?: string;
|
|
144
|
+
locale?: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create WeChat Mini Program Order Parameters
|
|
148
|
+
*/
|
|
149
|
+
type CreateMiniProgramOrderParams = {
|
|
150
|
+
userId: string;
|
|
151
|
+
openid: string;
|
|
152
|
+
merchantOrderId?: string;
|
|
153
|
+
priceId: string;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Create Order Response
|
|
157
|
+
*/
|
|
158
|
+
interface CreateOrderResponse {
|
|
159
|
+
orderId: string;
|
|
160
|
+
internalId: string;
|
|
161
|
+
amount: number;
|
|
162
|
+
currency: string;
|
|
163
|
+
payParams: any;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Payment Callback Notification
|
|
167
|
+
*/
|
|
168
|
+
interface PaymentNotification {
|
|
169
|
+
iv: string;
|
|
170
|
+
encryptedData: string;
|
|
171
|
+
authTag: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Decrypted Payment Callback Data
|
|
175
|
+
*/
|
|
176
|
+
interface PaymentCallbackData {
|
|
177
|
+
orderId: string;
|
|
178
|
+
merchantOrderId?: string;
|
|
179
|
+
status: "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
180
|
+
amount: number;
|
|
181
|
+
currency: string;
|
|
182
|
+
paidAt: string;
|
|
183
|
+
channelTransactionId?: string;
|
|
184
|
+
metadata?: Record<string, any>;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get Orders Parameters
|
|
188
|
+
*/
|
|
189
|
+
interface GetOrdersParams {
|
|
190
|
+
page?: number;
|
|
191
|
+
pageSize?: number;
|
|
192
|
+
userId?: string;
|
|
193
|
+
status?: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
194
|
+
startDate?: string;
|
|
195
|
+
endDate?: string;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Order List Item
|
|
199
|
+
*/
|
|
200
|
+
interface OrderListItem {
|
|
201
|
+
orderId: string;
|
|
202
|
+
internalId: string;
|
|
203
|
+
merchantUserId: string;
|
|
204
|
+
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
205
|
+
amount: number;
|
|
206
|
+
currency: string;
|
|
207
|
+
channel?: string;
|
|
208
|
+
paidAt?: string;
|
|
209
|
+
createdAt: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get Orders Response
|
|
213
|
+
*/
|
|
214
|
+
interface GetOrdersResponse {
|
|
215
|
+
orders: OrderListItem[];
|
|
216
|
+
pagination: {
|
|
217
|
+
total: number;
|
|
218
|
+
page: number;
|
|
219
|
+
pageSize: number;
|
|
220
|
+
totalPages: number;
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Entitlement Detail Item
|
|
225
|
+
*/
|
|
226
|
+
interface EntitlementDetailItem {
|
|
227
|
+
type: string;
|
|
228
|
+
current: number | boolean;
|
|
229
|
+
limit?: number;
|
|
230
|
+
expiresAt?: string | null;
|
|
231
|
+
resetInterval?: string | null;
|
|
232
|
+
nextResetAt?: string | null;
|
|
233
|
+
sourceKind?: string | null;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Entitlement Detail - returned by getEntitlementsDetail
|
|
237
|
+
*/
|
|
238
|
+
type EntitlementDetail = Record<string, EntitlementDetailItem>;
|
|
239
|
+
/**
|
|
240
|
+
* Ensure User With Trial Response
|
|
241
|
+
*/
|
|
242
|
+
interface EnsureUserWithTrialResponse {
|
|
243
|
+
isNew: boolean;
|
|
244
|
+
trialAssigned: boolean;
|
|
245
|
+
trialProductCode?: string;
|
|
246
|
+
entitlements: EntitlementDetail;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Server-side Payment Client
|
|
250
|
+
* 服务端支付客户端,用于创建订单、查询状态、解密回调
|
|
251
|
+
*/
|
|
252
|
+
declare class PaymentClient {
|
|
253
|
+
private readonly appId;
|
|
254
|
+
private readonly appSecret;
|
|
255
|
+
private readonly apiUrl;
|
|
256
|
+
private readonly checkoutUrl;
|
|
257
|
+
constructor(options: PaymentClientOptions);
|
|
258
|
+
/**
|
|
259
|
+
* Generate SHA256 signature for the request
|
|
260
|
+
* Logic: SHA256(appId + appSecret + timestamp)
|
|
261
|
+
*/
|
|
262
|
+
private generateSignature;
|
|
263
|
+
/**
|
|
264
|
+
* Internal request helper for Gateway API
|
|
265
|
+
*/
|
|
266
|
+
private request;
|
|
267
|
+
/**
|
|
268
|
+
* Decrypts the callback notification payload using AES-256-GCM.
|
|
269
|
+
* @param notification - The encrypted notification from payment webhook
|
|
270
|
+
* @returns Decrypted payment callback data
|
|
271
|
+
*/
|
|
272
|
+
decryptCallback(notification: PaymentNotification): PaymentCallbackData;
|
|
273
|
+
/**
|
|
274
|
+
* Fetch products for the configured app.
|
|
275
|
+
*/
|
|
276
|
+
getProducts(options?: {
|
|
277
|
+
locale?: string;
|
|
278
|
+
currency?: string;
|
|
279
|
+
}): Promise<Product[]>;
|
|
280
|
+
/**
|
|
281
|
+
* Create a new order
|
|
282
|
+
* @param params - Order creation parameters
|
|
283
|
+
* @returns Order details with payment parameters
|
|
284
|
+
*/
|
|
285
|
+
createOrder(params: CreateOrderParams): Promise<CreateOrderResponse>;
|
|
286
|
+
/**
|
|
287
|
+
* Create a WeChat Mini Program order (channel fixed to WECHAT_MINI)
|
|
288
|
+
* @param params - Mini program order parameters
|
|
289
|
+
* @returns Order details with payment parameters
|
|
290
|
+
*/
|
|
291
|
+
createMiniProgramOrder(params: CreateMiniProgramOrderParams): Promise<CreateOrderResponse>;
|
|
292
|
+
/**
|
|
293
|
+
* Pay for an existing order
|
|
294
|
+
* @param orderId - The order ID to pay
|
|
295
|
+
* @param params - Payment parameters including channel
|
|
296
|
+
*/
|
|
297
|
+
payOrder(orderId: string, params: {
|
|
298
|
+
channel: string;
|
|
299
|
+
returnUrl?: string;
|
|
300
|
+
openid?: string;
|
|
301
|
+
[key: string]: any;
|
|
302
|
+
}): Promise<CreateOrderResponse>;
|
|
303
|
+
/**
|
|
304
|
+
* Query order status
|
|
305
|
+
* @param orderId - The order ID to query
|
|
306
|
+
*/
|
|
307
|
+
getOrderStatus(orderId: string): Promise<OrderStatus>;
|
|
308
|
+
/**
|
|
309
|
+
* Get order details (full order information)
|
|
310
|
+
* @param orderId - The order ID to query
|
|
311
|
+
*/
|
|
312
|
+
getOrderDetails(orderId: string): Promise<OrderDetails>;
|
|
313
|
+
/**
|
|
314
|
+
* Get orders list with pagination
|
|
315
|
+
* @param params - Query parameters (pagination, filters)
|
|
316
|
+
* @returns Orders list and pagination info
|
|
317
|
+
*/
|
|
318
|
+
getOrders(params?: GetOrdersParams): Promise<GetOrdersResponse>;
|
|
319
|
+
/**
|
|
320
|
+
* Get user entitlements in the legacy flat shape.
|
|
321
|
+
* @param userId - User ID
|
|
322
|
+
*/
|
|
323
|
+
getEntitlements(userId: string): Promise<Record<string, any>>;
|
|
324
|
+
/**
|
|
325
|
+
* Get user entitlements with full details (type, expiry, reset config, source)
|
|
326
|
+
* @param userId - User ID
|
|
327
|
+
*/
|
|
328
|
+
getEntitlementsDetail(userId: string): Promise<EntitlementDetail>;
|
|
329
|
+
/**
|
|
330
|
+
* Ensure user exists and auto-assign trial product if new user
|
|
331
|
+
* This should be called when user first logs in or registers
|
|
332
|
+
* @param userId - User ID
|
|
333
|
+
*/
|
|
334
|
+
ensureUserWithTrial(userId: string): Promise<EnsureUserWithTrialResponse>;
|
|
335
|
+
/**
|
|
336
|
+
* Get a single entitlement value
|
|
337
|
+
* @param userId - User ID
|
|
338
|
+
* @param key - Entitlement key
|
|
339
|
+
*/
|
|
340
|
+
getEntitlementValue(userId: string, key: string): Promise<any>;
|
|
341
|
+
/**
|
|
342
|
+
* Consume numeric entitlement
|
|
343
|
+
* @param userId - User ID
|
|
344
|
+
* @param key - Entitlement key
|
|
345
|
+
* @param amount - Amount to consume
|
|
346
|
+
*/
|
|
347
|
+
consumeEntitlement(userId: string, key: string, amount: number, options?: {
|
|
348
|
+
idempotencyKey?: string;
|
|
349
|
+
metadata?: Record<string, any>;
|
|
350
|
+
}): Promise<{
|
|
351
|
+
balance: number;
|
|
352
|
+
}>;
|
|
353
|
+
/**
|
|
354
|
+
* Add numeric entitlement (e.g. refund)
|
|
355
|
+
* @param userId - User ID
|
|
356
|
+
* @param key - Entitlement key
|
|
357
|
+
* @param amount - Amount to add
|
|
358
|
+
*/
|
|
359
|
+
addEntitlement(userId: string, key: string, amount: number): Promise<{
|
|
360
|
+
balance: number;
|
|
361
|
+
}>;
|
|
362
|
+
/**
|
|
363
|
+
* Toggle boolean entitlement
|
|
364
|
+
* @param userId - User ID
|
|
365
|
+
* @param key - Entitlement key
|
|
366
|
+
* @param enabled - Whether to enable
|
|
367
|
+
*/
|
|
368
|
+
toggleEntitlement(userId: string, key: string, enabled: boolean): Promise<{
|
|
369
|
+
isEnabled: boolean;
|
|
370
|
+
}>;
|
|
371
|
+
/**
|
|
372
|
+
* Generate checkout URL for client-side payment
|
|
373
|
+
* @param productId - Product ID
|
|
374
|
+
* @param priceId - Price ID
|
|
375
|
+
* @returns Checkout page URL
|
|
376
|
+
*/
|
|
377
|
+
getCheckoutUrl(productId: string, priceId: string): string;
|
|
378
|
+
/**
|
|
379
|
+
* Verify a hosted login token and return the normalized login profile.
|
|
380
|
+
* This request is signed with your app credentials and routed through the worker API.
|
|
381
|
+
*/
|
|
382
|
+
verifyLoginToken(token: string): Promise<VerifiedLoginToken>;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export { type CreateMiniProgramOrderParams, type CreateOrderParams, type CreateOrderResponse, type EnsureUserWithTrialResponse, type EntitlementDetail, type EntitlementDetailItem, type GetOrdersParams, type GetOrdersResponse, type OrderDetails, type OrderListItem, type OrderStatus, type PaymentCallbackData, PaymentClient, type PaymentClientOptions, type PaymentNotification, type Product, type ProductEntitlements, type ProductMetadata, type ProductPrice, type ProductResetRule, type ProductSubscriptionPeriod, type VerifiedLoginToken, type WechatJsapiPayParams };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
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/server.ts
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
var PaymentClient = class {
|
|
8
|
+
// 用于生成 checkout URL
|
|
9
|
+
constructor(options) {
|
|
10
|
+
__publicField(this, "appId");
|
|
11
|
+
__publicField(this, "appSecret");
|
|
12
|
+
__publicField(this, "apiUrl");
|
|
13
|
+
// 用于 API 调用
|
|
14
|
+
__publicField(this, "checkoutUrl");
|
|
15
|
+
if (!options.appId) throw new Error("appId is required");
|
|
16
|
+
if (!options.appSecret) throw new Error("appSecret is required");
|
|
17
|
+
this.appId = options.appId;
|
|
18
|
+
this.appSecret = options.appSecret;
|
|
19
|
+
const apiUrl = options.apiUrl || options.baseUrl || "https://pay-api.imgto.link";
|
|
20
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
21
|
+
const checkoutUrl = options.checkoutUrl || options.baseUrl || "https://pay.imgto.link";
|
|
22
|
+
this.checkoutUrl = checkoutUrl.replace(/\/$/, "");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate SHA256 signature for the request
|
|
26
|
+
* Logic: SHA256(appId + appSecret + timestamp)
|
|
27
|
+
*/
|
|
28
|
+
generateSignature(timestamp) {
|
|
29
|
+
const str = `${this.appId}${this.appSecret}${timestamp}`;
|
|
30
|
+
return crypto.createHash("sha256").update(str).digest("hex");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Internal request helper for Gateway API
|
|
34
|
+
*/
|
|
35
|
+
async request(method, path, body) {
|
|
36
|
+
const timestamp = Date.now();
|
|
37
|
+
const signature = this.generateSignature(timestamp);
|
|
38
|
+
const url = `${this.apiUrl}/api/v1/gateway/${this.appId}${path}`;
|
|
39
|
+
const headers = {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"X-Pay-Timestamp": timestamp.toString(),
|
|
42
|
+
"X-Pay-Sign": signature
|
|
43
|
+
};
|
|
44
|
+
const options = {
|
|
45
|
+
method,
|
|
46
|
+
headers,
|
|
47
|
+
body: body ? JSON.stringify(body) : void 0
|
|
48
|
+
};
|
|
49
|
+
const response = await fetch(url, options);
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const errorText = await response.text();
|
|
52
|
+
throw new Error(`Payment SDK Error (${response.status}): ${errorText}`);
|
|
53
|
+
}
|
|
54
|
+
const json = await response.json();
|
|
55
|
+
if (json.error) {
|
|
56
|
+
throw new Error(`Payment API Error: ${json.error}`);
|
|
57
|
+
}
|
|
58
|
+
return json.data;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Decrypts the callback notification payload using AES-256-GCM.
|
|
62
|
+
* @param notification - The encrypted notification from payment webhook
|
|
63
|
+
* @returns Decrypted payment callback data
|
|
64
|
+
*/
|
|
65
|
+
decryptCallback(notification) {
|
|
66
|
+
try {
|
|
67
|
+
const { iv, encryptedData, authTag } = notification;
|
|
68
|
+
const key = crypto.createHash("sha256").update(this.appSecret).digest();
|
|
69
|
+
const decipher = crypto.createDecipheriv(
|
|
70
|
+
"aes-256-gcm",
|
|
71
|
+
key,
|
|
72
|
+
Buffer.from(iv, "hex")
|
|
73
|
+
);
|
|
74
|
+
decipher.setAuthTag(Buffer.from(authTag, "hex"));
|
|
75
|
+
let decrypted = decipher.update(encryptedData, "hex", "utf8");
|
|
76
|
+
decrypted += decipher.final("utf8");
|
|
77
|
+
return JSON.parse(decrypted);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Failed to decrypt payment callback: Invalid secret or tampered data."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fetch products for the configured app.
|
|
86
|
+
*/
|
|
87
|
+
async getProducts(options) {
|
|
88
|
+
const params = new URLSearchParams();
|
|
89
|
+
if (options?.locale) params.append("locale", options.locale);
|
|
90
|
+
if (options?.currency) params.append("currency", options.currency);
|
|
91
|
+
const path = params.toString() ? `/products?${params.toString()}` : "/products";
|
|
92
|
+
return this.request("GET", path);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create a new order
|
|
96
|
+
* @param params - Order creation parameters
|
|
97
|
+
* @returns Order details with payment parameters
|
|
98
|
+
*/
|
|
99
|
+
async createOrder(params) {
|
|
100
|
+
return this.request("POST", "/orders", params);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a WeChat Mini Program order (channel fixed to WECHAT_MINI)
|
|
104
|
+
* @param params - Mini program order parameters
|
|
105
|
+
* @returns Order details with payment parameters
|
|
106
|
+
*/
|
|
107
|
+
async createMiniProgramOrder(params) {
|
|
108
|
+
const { openid, ...rest } = params;
|
|
109
|
+
return this.request("POST", "/orders", {
|
|
110
|
+
...rest,
|
|
111
|
+
channel: "WECHAT_MINI",
|
|
112
|
+
openid,
|
|
113
|
+
metadata: { openid }
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Pay for an existing order
|
|
118
|
+
* @param orderId - The order ID to pay
|
|
119
|
+
* @param params - Payment parameters including channel
|
|
120
|
+
*/
|
|
121
|
+
async payOrder(orderId, params) {
|
|
122
|
+
return this.request("POST", `/orders/${orderId}/pay`, params);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Query order status
|
|
126
|
+
* @param orderId - The order ID to query
|
|
127
|
+
*/
|
|
128
|
+
async getOrderStatus(orderId) {
|
|
129
|
+
return this.request("GET", `/orders/${orderId}`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get order details (full order information)
|
|
133
|
+
* @param orderId - The order ID to query
|
|
134
|
+
*/
|
|
135
|
+
async getOrderDetails(orderId) {
|
|
136
|
+
return this.request("GET", `/orders/${orderId}/details`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get orders list with pagination
|
|
140
|
+
* @param params - Query parameters (pagination, filters)
|
|
141
|
+
* @returns Orders list and pagination info
|
|
142
|
+
*/
|
|
143
|
+
async getOrders(params) {
|
|
144
|
+
const queryParams = new URLSearchParams();
|
|
145
|
+
if (params?.page) queryParams.append("page", params.page.toString());
|
|
146
|
+
if (params?.pageSize)
|
|
147
|
+
queryParams.append("pageSize", params.pageSize.toString());
|
|
148
|
+
if (params?.userId) queryParams.append("userId", params.userId);
|
|
149
|
+
if (params?.status) queryParams.append("status", params.status);
|
|
150
|
+
if (params?.startDate) queryParams.append("startDate", params.startDate);
|
|
151
|
+
if (params?.endDate) queryParams.append("endDate", params.endDate);
|
|
152
|
+
const path = queryParams.toString() ? `/orders?${queryParams.toString()}` : "/orders";
|
|
153
|
+
return this.request("GET", path);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get user entitlements in the legacy flat shape.
|
|
157
|
+
* @param userId - User ID
|
|
158
|
+
*/
|
|
159
|
+
async getEntitlements(userId) {
|
|
160
|
+
return this.request("GET", `/users/${userId}/entitlements`);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get user entitlements with full details (type, expiry, reset config, source)
|
|
164
|
+
* @param userId - User ID
|
|
165
|
+
*/
|
|
166
|
+
async getEntitlementsDetail(userId) {
|
|
167
|
+
return this.request("GET", `/users/${userId}/entitlements/detail`);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Ensure user exists and auto-assign trial product if new user
|
|
171
|
+
* This should be called when user first logs in or registers
|
|
172
|
+
* @param userId - User ID
|
|
173
|
+
*/
|
|
174
|
+
async ensureUserWithTrial(userId) {
|
|
175
|
+
return this.request("POST", `/users/${userId}/entitlements/bootstrap`, {});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get a single entitlement value
|
|
179
|
+
* @param userId - User ID
|
|
180
|
+
* @param key - Entitlement key
|
|
181
|
+
*/
|
|
182
|
+
async getEntitlementValue(userId, key) {
|
|
183
|
+
const entitlements = await this.getEntitlements(userId);
|
|
184
|
+
return entitlements[key] ?? null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Consume numeric entitlement
|
|
188
|
+
* @param userId - User ID
|
|
189
|
+
* @param key - Entitlement key
|
|
190
|
+
* @param amount - Amount to consume
|
|
191
|
+
*/
|
|
192
|
+
async consumeEntitlement(userId, key, amount, options) {
|
|
193
|
+
return this.request("POST", `/users/${userId}/entitlements/consume`, {
|
|
194
|
+
key,
|
|
195
|
+
amount,
|
|
196
|
+
...options
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Add numeric entitlement (e.g. refund)
|
|
201
|
+
* @param userId - User ID
|
|
202
|
+
* @param key - Entitlement key
|
|
203
|
+
* @param amount - Amount to add
|
|
204
|
+
*/
|
|
205
|
+
async addEntitlement(userId, key, amount) {
|
|
206
|
+
return this.request("POST", `/users/${userId}/entitlements/add`, {
|
|
207
|
+
key,
|
|
208
|
+
amount
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Toggle boolean entitlement
|
|
213
|
+
* @param userId - User ID
|
|
214
|
+
* @param key - Entitlement key
|
|
215
|
+
* @param enabled - Whether to enable
|
|
216
|
+
*/
|
|
217
|
+
async toggleEntitlement(userId, key, enabled) {
|
|
218
|
+
return this.request("POST", `/users/${userId}/entitlements/toggle`, {
|
|
219
|
+
key,
|
|
220
|
+
enabled
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Generate checkout URL for client-side payment
|
|
225
|
+
* @param productId - Product ID
|
|
226
|
+
* @param priceId - Price ID
|
|
227
|
+
* @returns Checkout page URL
|
|
228
|
+
*/
|
|
229
|
+
getCheckoutUrl(productId, priceId) {
|
|
230
|
+
return `${this.checkoutUrl}/checkout/${this.appId}/${productId}/${priceId}`;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Verify a hosted login token and return the normalized login profile.
|
|
234
|
+
* This request is signed with your app credentials and routed through the worker API.
|
|
235
|
+
*/
|
|
236
|
+
async verifyLoginToken(token) {
|
|
237
|
+
if (!token?.trim()) {
|
|
238
|
+
throw new Error("login token is required");
|
|
239
|
+
}
|
|
240
|
+
return this.request("POST", "/login/tokens/verify", { token: token.trim() });
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
export {
|
|
244
|
+
PaymentClient
|
|
245
|
+
};
|
|
246
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +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\tmetadata?: ProductMetadata | null\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\nexport interface ProductSubscriptionPeriod {\n\tvalue: number\n\tunit: \"days\" | \"months\" | \"years\"\n}\n\nexport interface ProductResetRule {\n\tresetInterval: \"month\"\n}\n\nexport interface ProductMetadata {\n\tsubscriptionPeriod?: ProductSubscriptionPeriod\n\texpiringEntitlements?: string[]\n\tresetEntitlements?: Record<string, ProductResetRule>\n\tautoAssignOnNewUser?: boolean\n\ttrialDurationDays?: number\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\tmetadata?: ProductMetadata | null\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 * Entitlement Detail Item\n */\nexport interface EntitlementDetailItem {\n\ttype: string\n\tcurrent: number | boolean\n\tlimit?: number\n\texpiresAt?: string | null\n\tresetInterval?: string | null\n\tnextResetAt?: string | null\n\tsourceKind?: string | null\n}\n\n/**\n * Entitlement Detail - returned by getEntitlementsDetail\n */\nexport type EntitlementDetail = Record<string, EntitlementDetailItem>\n\n/**\n * Ensure User With Trial Response\n */\nexport interface EnsureUserWithTrialResponse {\n\tisNew: boolean\n\ttrialAssigned: boolean\n\ttrialProductCode?: string\n\tentitlements: EntitlementDetail\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 user entitlements in the legacy flat shape.\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 user entitlements with full details (type, expiry, reset config, source)\n\t * @param userId - User ID\n\t */\n\tasync getEntitlementsDetail(userId: string): Promise<EntitlementDetail> {\n\t\treturn this.request(\"GET\", `/users/${userId}/entitlements/detail`)\n\t}\n\n\t/**\n\t * Ensure user exists and auto-assign trial product if new user\n\t * This should be called when user first logs in or registers\n\t * @param userId - User ID\n\t */\n\tasync ensureUserWithTrial(\n\t\tuserId: string,\n\t): Promise<EnsureUserWithTrialResponse> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/bootstrap`, {})\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] ?? null\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\toptions?: {\n\t\t\tidempotencyKey?: string\n\t\t\tmetadata?: Record<string, any>\n\t\t},\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\t...options,\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":[]}
|