@youidian/sdk 3.4.5 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +39 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +38 -5
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +39 -5
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +113 -2
- package/dist/server.d.ts +113 -2
- package/dist/server.js +38 -5
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -32,12 +32,23 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
32
32
|
// src/server.ts
|
|
33
33
|
var server_exports = {};
|
|
34
34
|
__export(server_exports, {
|
|
35
|
+
PaymentApiError: () => PaymentApiError,
|
|
35
36
|
PaymentClient: () => PaymentClient,
|
|
36
37
|
getCustomAmountRechargeRule: () => getCustomAmountRechargeRule,
|
|
37
38
|
validateCustomAmountRecharge: () => validateCustomAmountRecharge
|
|
38
39
|
});
|
|
39
40
|
module.exports = __toCommonJS(server_exports);
|
|
40
41
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
42
|
+
var PaymentApiError = class extends Error {
|
|
43
|
+
constructor(message, status, code) {
|
|
44
|
+
super(message);
|
|
45
|
+
__publicField(this, "status");
|
|
46
|
+
__publicField(this, "code");
|
|
47
|
+
this.name = "PaymentApiError";
|
|
48
|
+
this.status = status;
|
|
49
|
+
this.code = code;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
41
52
|
function getCustomAmountRechargeRule(product, currency) {
|
|
42
53
|
if (!product || product.type !== "CREDIT") return null;
|
|
43
54
|
const normalizedCurrency = currency.trim().toUpperCase();
|
|
@@ -152,14 +163,19 @@ var PaymentClient = class {
|
|
|
152
163
|
} catch {
|
|
153
164
|
}
|
|
154
165
|
const message = parsedError?.message || parsedError?.error || errorText || "Request failed";
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
166
|
+
throw new PaymentApiError(
|
|
167
|
+
message,
|
|
168
|
+
response.status,
|
|
169
|
+
parsedError?.code || void 0
|
|
170
|
+
);
|
|
159
171
|
}
|
|
160
172
|
const json = await response.json();
|
|
161
173
|
if (json.error) {
|
|
162
|
-
throw new
|
|
174
|
+
throw new PaymentApiError(
|
|
175
|
+
`Payment API Error: ${json.error}`,
|
|
176
|
+
response.status,
|
|
177
|
+
json.code
|
|
178
|
+
);
|
|
163
179
|
}
|
|
164
180
|
return json.data;
|
|
165
181
|
}
|
|
@@ -233,6 +249,11 @@ var PaymentClient = class {
|
|
|
233
249
|
const params = new URLSearchParams();
|
|
234
250
|
if (options?.locale) params.append("locale", options.locale);
|
|
235
251
|
if (options?.currency) params.append("currency", options.currency);
|
|
252
|
+
if (options?.userId !== void 0) {
|
|
253
|
+
const userId = options.userId.trim();
|
|
254
|
+
if (!userId) throw new Error("userId must be a non-empty string");
|
|
255
|
+
params.append("userId", userId);
|
|
256
|
+
}
|
|
236
257
|
const path = params.toString() ? `/products?${params.toString()}` : "/products";
|
|
237
258
|
return this.request("GET", path);
|
|
238
259
|
}
|
|
@@ -421,6 +442,18 @@ var PaymentClient = class {
|
|
|
421
442
|
async getActiveSubscription(userId) {
|
|
422
443
|
return this.request("GET", `/users/${userId}/active-subscription`);
|
|
423
444
|
}
|
|
445
|
+
async getSubscriptionUpgradeOptions(userId, options) {
|
|
446
|
+
const value = userId?.trim();
|
|
447
|
+
if (!value) throw new Error("userId is required");
|
|
448
|
+
const params = new URLSearchParams();
|
|
449
|
+
if (options?.locale) params.set("locale", options.locale);
|
|
450
|
+
if (options?.currency) params.set("currency", options.currency);
|
|
451
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
452
|
+
return this.request(
|
|
453
|
+
"GET",
|
|
454
|
+
`/users/${encodeURIComponent(value)}/subscription-upgrade-options${query}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
424
457
|
/**
|
|
425
458
|
* Ensure user exists and auto-assign trial product if new user
|
|
426
459
|
* This should be called when user first logs in or registers
|
|
@@ -613,6 +646,7 @@ var PaymentClient = class {
|
|
|
613
646
|
};
|
|
614
647
|
// Annotate the CommonJS export names for ESM import in node:
|
|
615
648
|
0 && (module.exports = {
|
|
649
|
+
PaymentApiError,
|
|
616
650
|
PaymentClient,
|
|
617
651
|
getCustomAmountRechargeRule,
|
|
618
652
|
validateCustomAmountRecharge
|
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\tmerchantPricing?: MerchantPricingSnapshot\n\tpricingBreakdown?: PricingBreakdown\n\tupgrade?: {\n\t\tisUpgrade: boolean\n\t\tfromProductId?: string | null\n\t\tfromProductCode?: string | null\n\t\tfromPriceId?: string | null\n\t\tfromOrderId?: string | null\n\t\tfromSourceKind?: string | null\n\t\tfromSortOrder?: number | null\n\t\ttoSortOrder?: number | null\n\t\toriginalAmount?: number | null\n\t\tcreditAmount?: number | null\n\t\tfinalPayableAmount?: number | null\n\t\tremainingRatio?: number | null\n\t\tnewPeriodStartsAt?: string | null\n\t\tnewPeriodValue?: number | null\n\t\tnewPeriodUnit?: \"days\" | \"months\" | \"years\" | null\n\t} | null\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: \"day\" | \"month\"\n}\n\nexport interface ProductCustomAmountCurrency {\n\tminAmount: number\n\tmaxAmount: number\n\tstepAmount?: number\n\tunitsPerCurrencyUnit: number\n\tunitsPerCurrencyUnitBasis?: \"MINOR\" | \"MAJOR\"\n}\n\nexport interface ProductCustomAmount {\n\tenabled: boolean\n\tentitlementKey: string\n\tcurrencies: Record<string, ProductCustomAmountCurrency>\n}\n\nexport interface ProductInventory {\n\tenabled: boolean\n\ttotalQuantity: number\n\treserveTimeoutSeconds?: number\n}\n\nexport interface ProductMetadata {\n\tsubscriptionPeriod?: ProductSubscriptionPeriod\n\tsubscriptionGroup?: string\n\trestrictSubscriptionPurchase?: boolean\n\texpiringEntitlements?: string[]\n\tresetEntitlements?: Record<string, ProductResetRule>\n\tautoAssignOnNewUser?: boolean\n\ttrialDurationDays?: number\n\tcustomAmount?: ProductCustomAmount\n\tinventory?: ProductInventory\n}\n\nexport type ProductStockLookupMode = \"auto\" | \"id\" | \"code\"\n\nexport interface ProductStock {\n\tproductId: string\n\tproductCode: string\n\tlimited: boolean\n\ttotal: number | null\n\treserved: number\n\tsold: number\n\tavailable: number | null\n\tupdatedAt: string\n\treserveTimeoutSeconds: number | null\n}\n\nexport interface ProductStockQueryOptions {\n\tlookupBy?: ProductStockLookupMode\n\tlocale?: string\n\tcurrency?: string\n}\n\nexport interface ProductStocksQueryParams {\n\tproductIds?: string[]\n\tproductCodes?: string[]\n}\n\nexport interface PricingBreakdown {\n\tisUpgrade: boolean\n\toriginalAmount: number\n\tcreditAmount: number\n\tfinalPayableAmount: number\n\tfromProductCode?: string | null\n}\n\nexport interface ConsumeEntitlementPoolResult {\n\tbalance: number\n\tbalances: Record<string, number>\n\tconsumed: Record<string, number>\n\tconsumedBuckets?: EntitlementCreditBucketConsumption[]\n}\n\nexport interface EntitlementCreditBucket {\n\tkey: string\n\tgrantId: string | null\n\tsourceKind: string\n\tsourceProductId: string | null\n\tsourceOrderId: string | null\n\tamount: number\n\tremaining: number\n\texpiresAt: string | null\n\tgrantedAt: string | null\n\tmetadata?: Record<string, any> | null\n}\n\nexport interface EntitlementCreditBucketConsumption {\n\tkey: string\n\tgrantId: string | null\n\tamount: number\n\texpiresAt: string | null\n\tsourceProductId: string | null\n\tsourceOrderId: string | null\n}\n\nexport interface ActiveSubscriptionInfo {\n\tproductId: string\n\tproductCode: string\n\tsortOrder: number\n\tpaidAt?: string | null\n\texpiresAt: string\n\tpriceId?: string | null\n\torderId?: string | null\n\tsourceKind?: string | null\n}\n\nexport interface DiscountPolicy {\n\tcode: string\n\tname: string\n\tdescription: string | null\n\tqualificationCode: string\n\tproductId: string\n\tproductCode: string\n\tpriceId: string\n\tcurrency: string\n\tstandardAmount: number\n\tdiscountAmount: number\n\tstartsAt: string | null\n\tendsAt: string | null\n}\n\nexport interface UserProductDiscountEligibility {\n\tuserId: string\n\tproductId: string\n\tproductCode: string\n\teligible: boolean\n\tcoupons: Array<{\n\t\tcoupon: DiscountPolicy\n\t\teligible: boolean\n\t\tgrantedAt: string | null\n\t}>\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\tsubscriptionGroup?: string | null\n\tsubscriptionLevel?: number | null\n\tsubscriptionPeriodDays?: number | null\n\tsubscriptionTimezone?: string | null\n\tmetadata?: ProductMetadata | null\n}\n\nexport interface CustomAmountRechargeRule extends ProductCustomAmountCurrency {\n\tproductId: string\n\tproductCode: string\n\tentitlementKey: string\n\tcurrency: string\n\tconfiguredMinAmount: number\n\tminimumGrantAmount: number\n}\n\nexport type CustomAmountRechargeValidationResult =\n\t| {\n\t\t\tvalid: true\n\t\t\trule: CustomAmountRechargeRule\n\t }\n\t| {\n\t\t\tvalid: false\n\t\t\tcode:\n\t\t\t\t| \"CUSTOM_AMOUNT_UNAVAILABLE\"\n\t\t\t\t| \"CUSTOM_AMOUNT_INVALID_AMOUNT\"\n\t\t\t\t| \"CUSTOM_AMOUNT_OUT_OF_RANGE\"\n\t\t\t\t| \"CUSTOM_AMOUNT_INVALID_STEP\"\n\t\t\terror: string\n\t\t\trule?: CustomAmountRechargeRule\n\t }\n\n/**\n * Resolve the custom amount recharge rule for a product and currency.\n *\n * Amounts are in the smallest currency unit. `unitsPerCurrencyUnit` defaults\n * to the smallest currency unit. When `unitsPerCurrencyUnitBasis` is `MAJOR`,\n * fractional entitlement amounts are rounded to the nearest whole unit. The\n * returned `minAmount` is the effective lower bound that both satisfies product\n * metadata and grants at least one entitlement unit. `configuredMinAmount`\n * keeps the raw product metadata value for display or diagnostics.\n */\nexport function getCustomAmountRechargeRule(\n\tproduct: Product | null | undefined,\n\tcurrency: string,\n): CustomAmountRechargeRule | null {\n\tif (!product || product.type !== \"CREDIT\") return null\n\tconst normalizedCurrency = currency.trim().toUpperCase()\n\tif (!/^[A-Z]{3}$/.test(normalizedCurrency)) return null\n\n\tconst customAmount = product.metadata?.customAmount\n\tif (customAmount?.enabled !== true) return null\n\n\tconst currencyConfig = customAmount.currencies?.[normalizedCurrency]\n\tif (!currencyConfig) return null\n\n\tconst minimumGrantAmount =\n\t\tcurrencyConfig.unitsPerCurrencyUnitBasis === \"MAJOR\"\n\t\t\t? Math.ceil(50 / currencyConfig.unitsPerCurrencyUnit)\n\t\t\t: Math.ceil(1 / currencyConfig.unitsPerCurrencyUnit)\n\tlet minAmount = Math.max(currencyConfig.minAmount, minimumGrantAmount)\n\tif (currencyConfig.stepAmount && minAmount > currencyConfig.minAmount) {\n\t\tconst stepCount = Math.ceil(\n\t\t\t(minAmount - currencyConfig.minAmount) / currencyConfig.stepAmount,\n\t\t)\n\t\tminAmount = currencyConfig.minAmount + stepCount * currencyConfig.stepAmount\n\t}\n\n\treturn {\n\t\t...currencyConfig,\n\t\tproductId: product.id,\n\t\tproductCode: product.code,\n\t\tentitlementKey: customAmount.entitlementKey,\n\t\tcurrency: normalizedCurrency,\n\t\tconfiguredMinAmount: currencyConfig.minAmount,\n\t\tminimumGrantAmount,\n\t\tminAmount,\n\t}\n}\n\n/**\n * Validate a custom recharge amount before calling `PaymentUI.openPayment`.\n * This is an SDK-side guardrail for integrator-owned amount inputs; the worker\n * still performs authoritative server-side validation when creating the order.\n */\nexport function validateCustomAmountRecharge(\n\tproduct: Product | null | undefined,\n\tcustomAmount: { amount: number; currency: string },\n): CustomAmountRechargeValidationResult {\n\tconst rule = getCustomAmountRechargeRule(product, customAmount.currency)\n\tif (!rule) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_UNAVAILABLE\",\n\t\t\terror:\n\t\t\t\t\"Product does not support custom amount recharge for this currency.\",\n\t\t}\n\t}\n\n\tconst amount = Number(customAmount.amount)\n\tif (!Number.isInteger(amount) || amount <= 0) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_INVALID_AMOUNT\",\n\t\t\terror:\n\t\t\t\t\"Custom amount must be a positive integer in the smallest currency unit.\",\n\t\t\trule,\n\t\t}\n\t}\n\n\tif (amount < rule.minAmount || amount > rule.maxAmount) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_OUT_OF_RANGE\",\n\t\t\terror: `Custom amount must be between ${rule.minAmount} and ${rule.maxAmount}.`,\n\t\t\trule,\n\t\t}\n\t}\n\n\tif (\n\t\trule.stepAmount &&\n\t\t(amount - rule.configuredMinAmount) % rule.stepAmount !== 0\n\t) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_INVALID_STEP\",\n\t\t\terror: `Custom amount must follow step ${rule.stepAmount}.`,\n\t\t\trule,\n\t\t}\n\t}\n\n\treturn { valid: true, rule }\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\temailVerified?: boolean | null\n\tname?: string | null\n\tusername?: string | null\n\tavatar?: string | null\n\tphoneCountryCode?: string | null\n\tphoneNumber?: string | null\n\tphoneE164?: string | null\n\tphoneVerifiedAt?: string | null\n\twechatOpenId?: string | null\n\twechatUnionId?: string | null\n\texpiresAt: string\n}\n\nexport interface SendPhoneVerificationCodeParams {\n\tuserId: string\n\tphoneCountryCode?: string\n\tcountryCode?: string\n\tphoneNumber: string\n}\n\nexport interface SendPhoneVerificationCodeResponse {\n\tphoneCountryCode?: string | null\n\tphoneNumber?: string | null\n\tphoneE164: string\n\texpiresAt: string\n\tcooldownSeconds?: number\n\tresendAfterSeconds?: number\n}\n\nexport interface PhoneBinding {\n\tuserId: string\n\tphoneCountryCode?: string | null\n\tphoneNumber?: string | null\n\tphoneE164?: string | null\n\tphoneVerifiedAt?: string | null\n\tbound: boolean\n}\n\nexport interface GetPhoneBindingParams {\n\tuserId: string\n}\n\nexport interface BindPhoneNumberParams {\n\tuserId: string\n\tphoneCountryCode?: string\n\tcountryCode?: string\n\tphoneNumber: string\n\tcode: string\n}\n\nexport type UpdatePhoneNumberParams = BindPhoneNumberParams\n\nexport interface BindPhoneNumberResponse {\n\tuser: {\n\t\tuserId: string\n\t\tphoneCountryCode?: string | null\n\t\tphoneNumber?: string | null\n\t\tphoneE164?: string | null\n\t\tphoneVerifiedAt?: string | null\n\t}\n\tmerged: boolean\n\tmergeSummary?: Record<string, any>\n}\n\nexport type UpdatePhoneNumberResponse = BindPhoneNumberResponse\n\nexport interface CreateWechatMessageBindingParams {\n\t/** Hosted login external user ID returned by Youidian login. */\n\tuserId?: string\n\tloginExternalUserId?: string\n\t/** Integrator-owned user ID when hosted login is not used. */\n\tmerchantUserId?: string\n\t/** Optional linked template ID or code to infer the WeChat channel. */\n\ttemplateId?: string\n\ttemplateCode?: string\n\t/** Optional direct channel ID when no template is selected yet. */\n\tchannelId?: string\n\t/** Optional locale for the hosted binding page. */\n\tlocale?: string\n\t/** Optional URL to return to after binding succeeds. */\n\treturnUrl?: string\n}\n\nexport interface CreateWechatMessageBindingResponse {\n\tticketId: string\n\tstatus: \"PENDING\" | \"BOUND\" | \"EXPIRED\" | \"FAILED\"\n\tbindingUrl: string\n\tqrCodeUrl?: string | null\n\texpiresAt: string\n}\n\nexport type MessageTemplateDataValue =\n\t| string\n\t| number\n\t| boolean\n\t| {\n\t\t\tvalue: string | number | boolean\n\t\t\tcolor?: string\n\t }\n\nexport interface SendMessageParams {\n\ttemplateId?: string\n\ttemplateCode?: string\n\tuserId?: string\n\tloginExternalUserId?: string\n\tmerchantUserId?: string\n\t/** Direct SMS recipient phone number. When provided for SMS templates, binding is not required before sending. */\n\tphoneNumber?: string\n\t/** Country/region code for phoneNumber. Defaults to +86 on the gateway. */\n\tphoneCountryCode?: string\n\t/** Alias for phoneCountryCode. */\n\tcountryCode?: string\n\t/** Direct E.164 SMS recipient. Alternative to phoneNumber. */\n\tphoneE164?: string\n\tdata: Record<string, MessageTemplateDataValue> | MessageTemplateDataValue[]\n\turl?: string\n\tredirectUrl?: string\n\tminiProgram?: {\n\t\tappid: string\n\t\tpagepath?: string\n\t}\n\tidempotencyKey?: string\n}\n\nexport interface SendMessageResponse {\n\tlogId: string\n\tstatus: \"PENDING\" | \"SENT\" | \"FAILED\" | \"SKIPPED\"\n\tproviderMessageId?: string | null\n\terrorCode?: string | null\n\terrorMessage?: string | null\n\tbindRequired?: boolean\n\tidempotent?: boolean\n\tphoneBinding?: {\n\t\tbound: boolean\n\t\tuserId?: string\n\t\tphoneE164?: string | null\n\t\tmerged?: boolean\n\t\talreadyBound?: boolean\n\t\terror?: string\n\t} | null\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\nexport interface MerchantPricingBreakdownItem {\n\ttype: \"coupon\" | \"promotion\" | \"membership\" | \"manual\" | \"other\"\n\tamount: number\n\tlabel?: string\n\tcode?: string\n}\n\nexport interface MerchantPricing {\n\tamount: number\n\tcurrency: string\n\toriginalAmount?: number\n\tdiscountAmount?: number\n\tdiscountReason?: string\n\tdiscountCode?: string\n\tbreakdown?: MerchantPricingBreakdownItem[]\n}\n\nexport interface MerchantPricingSnapshot extends MerchantPricing {\n\toriginalAmount: number\n\tdiscountAmount: number\n\tpriceId: string\n\tproductId: string\n\tproductCode: 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\tcallbackUrl?: string\n\tmetadata?: Record<string, any>\n\tmerchantOrderId?: string\n\topenid?: string\n\tlocale?: string\n\tcustomAmount?: {\n\t\tamount: number\n\t\tcurrency: string\n\t}\n\tmerchantPricing?: MerchantPricing\n}\n\nexport type CreateBankTransferOrderParams = Omit<CreateOrderParams, \"channel\">\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\tpricingBreakdown?: PricingBreakdown\n\tstatus?: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string | null\n\tchannel?: string\n\tisSandbox?: boolean\n\tmerchantPricing?: MerchantPricingSnapshot\n}\n\nexport interface CancelOrderResponse {\n\tcancelled: boolean\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\torderId: string\n\tinternalId?: string | null\n}\n\nexport interface CompleteFreeOrderResponse {\n\tcompleted: boolean\n\tstatus: \"paid\" | \"pending\" | \"cancelled\" | \"refunded\" | \"failed\" | \"unknown\"\n\torderId: string\n\tinternalId?: string | null\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 * Platform webhook payload.\n *\n * The platform delivers this object as plaintext JSON in the webhook request\n * body, signed with `X-UniPay-Signature` / `X-UniPay-Timestamp` headers.\n * Validate with {@link PaymentClient.verifyWebhookSignature} using the raw\n * body string before parsing.\n */\nexport interface WebhookPayload {\n\tevent: \"ORDER_PAID\" | \"ORDER_CANCELLED\" | \"ORDER_REFUNDED\" | \"ORDER_FAILED\"\n\torderId: string\n\tmerchantOrderId: string\n\tmerchantUserId: string\n\tproductCode: string\n\tbilling: unknown\n\tamount: number\n\tcurrency: string\n\tpaidAt: string | null\n\tchannelTransactionId?: string\n\tcancelSubscription?: boolean\n\trevokeEntitlements?: boolean\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\tlet parsedError: any = null\n\t\t\ttry {\n\t\t\t\tparsedError = JSON.parse(errorText)\n\t\t\t} catch {}\n\t\t\tconst message =\n\t\t\t\tparsedError?.message ||\n\t\t\t\tparsedError?.error ||\n\t\t\t\terrorText ||\n\t\t\t\t\"Request failed\"\n\t\t\tconst error = new Error(message)\n\t\t\t;(error as Error & { status?: number; code?: string }).status =\n\t\t\t\tresponse.status\n\t\t\t;(error as Error & { status?: number; code?: string }).code =\n\t\t\t\tparsedError?.code || parsedError?.error || undefined\n\t\t\tthrow error\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 *\n\t * @deprecated This legacy encrypted-envelope protocol is NOT what the platform\n\t * actually delivers. The platform sends an HMAC-signed plaintext JSON webhook\n\t * with `X-UniPay-Signature` / `X-UniPay-Timestamp` headers; use\n\t * {@link PaymentClient.verifyWebhookSignature} to validate those instead.\n\t *\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 {\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 * Verify an inbound platform webhook signature.\n\t *\n\t * The platform delivers webhooks as plaintext JSON with two headers:\n\t * `X-UniPay-Signature` (HMAC-SHA256 of `${timestamp}.${rawBodyString}`) and\n\t * `X-UniPay-Timestamp`. The body itself is NOT encrypted; verify the signature\n\t * then `JSON.parse` the body to obtain a {@link WebhookPayload}.\n\t *\n\t * @param rawBody - The raw request body string (exactly as received)\n\t * @param signature - The `X-UniPay-Signature` header value (hex)\n\t * @param timestamp - The `X-UniPay-Timestamp` header value (ms epoch string)\n\t * @param options - Optional `toleranceMs` for timestamp freshness (default 5 min)\n\t * @returns `true` if the signature is valid and the timestamp is fresh\n\t */\n\tverifyWebhookSignature(\n\t\trawBody: string,\n\t\tsignature: string,\n\t\ttimestamp: string,\n\t\toptions?: { toleranceMs?: number },\n\t): boolean {\n\t\ttry {\n\t\t\tconst toleranceMs = options?.toleranceMs ?? 5 * 60 * 1000\n\t\t\tconst timestampMs = Number.parseInt(timestamp, 10)\n\t\t\tif (\n\t\t\t\tNumber.isNaN(timestampMs) ||\n\t\t\t\tMath.abs(Date.now() - timestampMs) > toleranceMs\n\t\t\t) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst rawString = `${timestamp}.${rawBody}`\n\t\t\tconst expected = crypto\n\t\t\t\t.createHmac(\"sha256\", this.appSecret)\n\t\t\t\t.update(rawString)\n\t\t\t\t.digest(\"hex\")\n\n\t\t\tconst signatureBuffer = Buffer.from(signature)\n\t\t\tconst expectedBuffer = Buffer.from(expected)\n\t\t\tif (signatureBuffer.length !== expectedBuffer.length) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn crypto.timingSafeEqual(signatureBuffer, expectedBuffer)\n\t\t} catch {\n\t\t\treturn false\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/** Fetch all currently active discount policies for the configured app. */\n\tasync getDiscountPolicies(): Promise<DiscountPolicy[]> {\n\t\treturn this.request(\"GET\", \"/discount-policies\")\n\t}\n\n\t/** Fetch currently active discount policies for one product. */\n\tasync getProductDiscountPolicies(\n\t\tproductIdOrCode: string,\n\t): Promise<DiscountPolicy[]> {\n\t\tconst value = productIdOrCode?.trim()\n\t\tif (!value) throw new Error(\"productIdOrCode is required\")\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/products/${encodeURIComponent(value)}/discount-policies`,\n\t\t)\n\t}\n\n\t/** Query one user's discount eligibility for one product. */\n\tasync getUserProductDiscountEligibility(\n\t\tuserId: string,\n\t\tproductIdOrCode: string,\n\t): Promise<UserProductDiscountEligibility> {\n\t\tconst userValue = userId?.trim()\n\t\tconst productValue = productIdOrCode?.trim()\n\t\tif (!userValue) throw new Error(\"userId is required\")\n\t\tif (!productValue) throw new Error(\"productIdOrCode is required\")\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/users/${encodeURIComponent(userValue)}/products/${encodeURIComponent(productValue)}/discount-eligibility`,\n\t\t)\n\t}\n\n\t/**\n\t * Fetch the realtime stock snapshot for a single product.\n\t */\n\tasync getProductStock(\n\t\tproductIdOrCode: string,\n\t\toptions?: ProductStockQueryOptions,\n\t): Promise<ProductStock> {\n\t\tconst value = productIdOrCode?.trim()\n\t\tif (!value) {\n\t\t\tthrow new Error(\"productIdOrCode is required\")\n\t\t}\n\n\t\tconst params = new URLSearchParams()\n\t\tparams.set(\"productIdOrCode\", value)\n\t\tif (options?.lookupBy) params.set(\"lookupBy\", options.lookupBy)\n\t\tif (options?.locale) params.set(\"locale\", options.locale)\n\t\tif (options?.currency) params.set(\"currency\", options.currency)\n\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/products/${encodeURIComponent(value)}/stock?${params.toString()}`,\n\t\t)\n\t}\n\n\t/**\n\t * Fetch realtime stock snapshots for multiple products.\n\t */\n\tasync getProductStocks(\n\t\tparams: ProductStocksQueryParams,\n\t\toptions?: ProductStockQueryOptions,\n\t): Promise<ProductStock[]>\n\tasync getProductStocks(\n\t\tparams: ProductStocksQueryParams & ProductStockQueryOptions,\n\t): Promise<ProductStock[]>\n\tasync getProductStocks(\n\t\tparams: ProductStocksQueryParams & ProductStockQueryOptions,\n\t\toptions?: ProductStockQueryOptions,\n\t): Promise<ProductStock[]> {\n\t\tconst productIds = [\n\t\t\t...new Set(\n\t\t\t\t(params.productIds || []).map((item) => item.trim()).filter(Boolean),\n\t\t\t),\n\t\t]\n\t\tconst productCodes = [\n\t\t\t...new Set(\n\t\t\t\t(params.productCodes || []).map((item) => item.trim()).filter(Boolean),\n\t\t\t),\n\t\t]\n\t\tif (productIds.length === 0 && productCodes.length === 0) {\n\t\t\tthrow new Error(\"productIds or productCodes is required\")\n\t\t}\n\n\t\tconst queryOptions = options || params\n\t\tconst query = new URLSearchParams()\n\t\tif (productIds.length > 0) query.set(\"productIds\", productIds.join(\",\"))\n\t\tif (productCodes.length > 0)\n\t\t\tquery.set(\"productCodes\", productCodes.join(\",\"))\n\t\tif (queryOptions.lookupBy) query.set(\"lookupBy\", queryOptions.lookupBy)\n\t\tif (queryOptions.locale) query.set(\"locale\", queryOptions.locale)\n\t\tif (queryOptions.currency) query.set(\"currency\", queryOptions.currency)\n\n\t\treturn this.request(\"GET\", `/products/stocks?${query.toString()}`)\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 paid manual bank transfer order.\n\t * @param params - Order creation parameters without channel\n\t * @returns Paid order details\n\t */\n\tasync createBankTransferOrder(\n\t\tparams: CreateBankTransferOrderParams,\n\t): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", \"/orders\", {\n\t\t\t...params,\n\t\t\tchannel: \"BANK_TRANSFER\",\n\t\t} satisfies CreateOrderParams)\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 * Cancel a pending order and release its inventory reservation.\n\t * Paid/refunded/failed orders are returned unchanged by the gateway.\n\t */\n\tasync cancelOrder(\n\t\torderId: string,\n\t\tparams?: { reason?: string },\n\t): Promise<CancelOrderResponse> {\n\t\tconst value = orderId?.trim()\n\t\tif (!value) {\n\t\t\tthrow new Error(\"orderId is required\")\n\t\t}\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/orders/${encodeURIComponent(value)}/cancel`,\n\t\t\tparams || {},\n\t\t)\n\t}\n\n\t/**\n\t * Complete a zero-amount FREE order.\n\t * This marks the pending order as paid and triggers normal paid-order side effects.\n\t */\n\tasync completeFreeOrder(orderId: string): Promise<CompleteFreeOrderResponse> {\n\t\tconst value = orderId?.trim()\n\t\tif (!value) {\n\t\t\tthrow new Error(\"orderId is required\")\n\t\t}\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/orders/${encodeURIComponent(value)}/free/complete`,\n\t\t\t{},\n\t\t)\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\tasync getActiveSubscription(\n\t\tuserId: string,\n\t): Promise<ActiveSubscriptionInfo | null> {\n\t\treturn this.request(\"GET\", `/users/${userId}/active-subscription`)\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 * Consume numeric entitlements from an ordered key pool.\n\t * Useful when subscription credits and perpetual credits use separate keys.\n\t */\n\tasync consumeEntitlementPool(\n\t\tuserId: string,\n\t\tkeys: 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<ConsumeEntitlementPoolResult> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/consume-pool`, {\n\t\t\tkeys,\n\t\t\tamount,\n\t\t\t...options,\n\t\t})\n\t}\n\n\t/**\n\t * Get active numeric entitlement buckets ordered by expiration.\n\t */\n\tasync getEntitlementCreditBuckets(\n\t\tuserId: string,\n\t\tkeys?: string[],\n\t): Promise<EntitlementCreditBucket[]> {\n\t\tconst query =\n\t\t\tkeys && keys.length > 0\n\t\t\t\t? `?keys=${encodeURIComponent(keys.join(\",\"))}`\n\t\t\t\t: \"\"\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/users/${userId}/entitlements/credit-buckets${query}`,\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\t/**\n\t * Send a phone verification code for binding a phone number to a hosted login user.\n\t */\n\tasync sendPhoneVerificationCode(\n\t\tparams: SendPhoneVerificationCodeParams,\n\t): Promise<SendPhoneVerificationCodeResponse> {\n\t\tconst userId = params.userId?.trim()\n\t\tconst phoneNumber = params.phoneNumber?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\t\tif (!phoneNumber) throw new Error(\"phoneNumber is required\")\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone/code`,\n\t\t\t{\n\t\t\t\tphoneCountryCode: params.phoneCountryCode || params.countryCode,\n\t\t\t\tphoneNumber,\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Query the verified phone binding for a hosted login user.\n\t */\n\tasync getPhoneBinding(params: GetPhoneBindingParams): Promise<PhoneBinding> {\n\t\tconst userId = params.userId?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone`,\n\t\t)\n\t}\n\n\t/**\n\t * Bind a verified phone number to a hosted login user, merging existing accounts when needed.\n\t */\n\tasync bindPhoneNumber(\n\t\tparams: BindPhoneNumberParams,\n\t): Promise<BindPhoneNumberResponse> {\n\t\tconst userId = params.userId?.trim()\n\t\tconst phoneNumber = params.phoneNumber?.trim()\n\t\tconst code = params.code?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\t\tif (!phoneNumber) throw new Error(\"phoneNumber is required\")\n\t\tif (!code) throw new Error(\"code is required\")\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone/bind`,\n\t\t\t{\n\t\t\t\tcode,\n\t\t\t\tphoneCountryCode: params.phoneCountryCode || params.countryCode,\n\t\t\t\tphoneNumber,\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Update a hosted login user's phone number after verifying the new number by SMS code.\n\t */\n\tasync updatePhoneNumber(\n\t\tparams: UpdatePhoneNumberParams,\n\t): Promise<UpdatePhoneNumberResponse> {\n\t\tconst userId = params.userId?.trim()\n\t\tconst phoneNumber = params.phoneNumber?.trim()\n\t\tconst code = params.code?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\t\tif (!phoneNumber) throw new Error(\"phoneNumber is required\")\n\t\tif (!code) throw new Error(\"code is required\")\n\n\t\treturn this.request(\n\t\t\t\"PUT\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone`,\n\t\t\t{\n\t\t\t\tcode,\n\t\t\t\tphoneCountryCode: params.phoneCountryCode || params.countryCode,\n\t\t\t\tphoneNumber,\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Create a hosted WeChat official account binding URL for a user.\n\t */\n\tasync createWechatMessageBinding(\n\t\tparams: CreateWechatMessageBindingParams,\n\t): Promise<CreateWechatMessageBindingResponse> {\n\t\tconst loginExternalUserId =\n\t\t\tparams.loginExternalUserId?.trim() || params.userId?.trim()\n\t\tconst merchantUserId = params.merchantUserId?.trim()\n\t\tif (!loginExternalUserId && !merchantUserId) {\n\t\t\tthrow new Error(\"userId or merchantUserId is required\")\n\t\t}\n\n\t\treturn this.request(\"POST\", \"/messages/wechat/bindings\", {\n\t\t\t...params,\n\t\t\tloginExternalUserId,\n\t\t\tmerchantUserId,\n\t\t})\n\t}\n\n\t/**\n\t * Send an app-linked message template to a bound recipient.\n\t */\n\tasync sendMessage(params: SendMessageParams): Promise<SendMessageResponse> {\n\t\tif (!params.templateId?.trim() && !params.templateCode?.trim()) {\n\t\t\tthrow new Error(\"templateId or templateCode is required\")\n\t\t}\n\t\tif (!params.data || typeof params.data !== \"object\") {\n\t\t\tthrow new Error(\"data is required\")\n\t\t}\n\n\t\treturn this.request(\"POST\", \"/messages/send\", params)\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\nexport type SubscriptionAction = \"NEW\" | \"UPGRADE\" | \"DOWNGRADE\"\nexport type SubscriptionIntent = \"STANDARD\" | \"UPGRADE\"\n\nexport type SubscriptionStatus =\n\t| \"PENDING\"\n\t| \"ACTIVE\"\n\t| \"EXPIRED\"\n\t| \"REPLACED\"\n\t| \"CANCELLED\"\n\nexport type SubscriptionAutoRefundStatus = \"PROCESSING\" | \"SUCCEEDED\" | \"FAILED\"\n\nexport type SubscriptionErrorCode =\n\t| \"SUBSCRIPTION_UPGRADE_NOT_AVAILABLE\"\n\t| \"SUBSCRIPTION_SAME_LEVEL_NOT_ALLOWED\"\n\t| \"SUBSCRIPTION_DOWNGRADE_NOT_ALLOWED\"\n\t| \"SUBSCRIPTION_PENDING_ORDER_EXISTS\"\n\t| \"SUBSCRIPTION_NO_REMAINING_DAYS\"\n\t| \"SUBSCRIPTION_CURRENCY_MISMATCH\"\n\t| \"SUBSCRIPTION_PERIOD_MISMATCH\"\n\t| \"SUBSCRIPTION_PRICING_MANAGED\"\n\nexport interface SubscriptionBillingSnapshot {\n\tversion: number\n\taction: SubscriptionAction\n\tisUpgrade: boolean\n\tquoteBusinessDate: string\n\ttimezone: string\n\tperiodDays: number\n\tsubscriptionGroup: string\n\tsubscriptionLevel: number\n\tproductCode: string\n\tstandardAmount: number\n\tpolicyDiscountAmount: number\n\tpolicyId: string | null\n\tpolicyCode: string | null\n\tpolicyName: string | null\n\tpolicyRevision: number | null\n\tqualificationCode: string | null\n\tintent?: SubscriptionIntent | null\n\tfromSubscriptionId?: string | null\n\tmanagedSubscription?: boolean\n\tfromProductCode?: string | null\n\tremainingDays?: number | null\n\tcurrentRemainingValueAmount?: number | null\n\ttargetRemainingValueAmount?: number | null\n\tappliedDiscountAmount?: number | null\n\tfinalAmount?: number | null\n\tstartsAt?: string | null\n\tcycleStartsAt?: string | null\n\texpiresAt?: string | null\n}\n\nexport interface BillingSnapshot {\n\tversion: number\n\tstandardAmount: number\n\tdiscountAmount: number\n\tupgradeCreditAmount: number\n\ttargetRemainingValueAmount: number | null\n\tfullPlanValueAmount: number | null\n\tdiscountPolicyCode: string | null\n\tdiscountPolicyRevision: number | null\n\tqualificationCode: string | null\n\trefundRequired: boolean\n\tautoRefundStatus: SubscriptionAutoRefundStatus | null\n\tsubscription: SubscriptionBillingSnapshot | null\n\tcustomAmount: Record<string, unknown> | null\n\tmerchantPricing: MerchantPricingSnapshot | null\n}\n\nexport interface SubscriptionDetails {\n\tid: string\n\taction: SubscriptionAction\n\tstatus: SubscriptionStatus\n\tproductId: string\n\tproductCode: string\n\tpriceId: string\n\tsubscriptionGroup: string\n\tsubscriptionLevel: number\n\tstartsAt: string\n\tcycleStartsAt: string\n\texpiresAt: string\n\tperiodDays: number\n\ttimezone: string\n\tfullPlanValueAmount: number\n\tcurrency: string\n\treplacesSubscriptionId: string | null\n\tactivatedAt: string | null\n\treplacedAt: string | null\n\tcancelledAt: string | null\n}\n\nexport class PaymentApiError extends Error {\n\treadonly status: number\n\treadonly code?: string\n\n\tconstructor(message: string, status: number, code?: string) {\n\t\tsuper(message)\n\t\tthis.name = \"PaymentApiError\"\n\t\tthis.status = status\n\t\tthis.code = code\n\t}\n}\n\n/**\n * Order details response (full order information)\n */\nexport interface OrderDetails {\n\torderId: string\n\tinternalId: string\n\tmerchantUserId: string\n\tproductCode: string | null\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\tmerchantPricing?: MerchantPricingSnapshot\n\tbilling: BillingSnapshot\n\tsubscription: SubscriptionDetails | null\n\tpricingBreakdown?: PricingBreakdown\n\tupgrade?: {\n\t\tisUpgrade: boolean\n\t\tfromProductId?: string | null\n\t\tfromProductCode?: string | null\n\t\tfromPriceId?: string | null\n\t\tfromOrderId?: string | null\n\t\tfromSourceKind?: string | null\n\t\tfromSortOrder?: number | null\n\t\ttoSortOrder?: number | null\n\t\toriginalAmount?: number | null\n\t\tcreditAmount?: number | null\n\t\tfinalPayableAmount?: number | null\n\t\tremainingRatio?: number | null\n\t\tnewPeriodStartsAt?: string | null\n\t\tnewPeriodValue?: number | null\n\t\tnewPeriodUnit?: \"days\" | \"months\" | \"years\" | null\n\t} | null\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: \"day\" | \"month\"\n}\n\nexport interface ProductCustomAmountCurrency {\n\tminAmount: number\n\tmaxAmount: number\n\tstepAmount?: number\n\tunitsPerCurrencyUnit: number\n\tunitsPerCurrencyUnitBasis?: \"MINOR\" | \"MAJOR\"\n}\n\nexport interface ProductCustomAmount {\n\tenabled: boolean\n\tentitlementKey: string\n\tcurrencies: Record<string, ProductCustomAmountCurrency>\n}\n\nexport interface ProductInventory {\n\tenabled: boolean\n\ttotalQuantity: number\n\treserveTimeoutSeconds?: number\n}\n\nexport interface ProductMetadata {\n\tsubscriptionPeriod?: ProductSubscriptionPeriod\n\tsubscriptionGroup?: string\n\trestrictSubscriptionPurchase?: boolean\n\texpiringEntitlements?: string[]\n\tresetEntitlements?: Record<string, ProductResetRule>\n\tautoAssignOnNewUser?: boolean\n\ttrialDurationDays?: number\n\tcustomAmount?: ProductCustomAmount\n\tinventory?: ProductInventory\n}\n\nexport type ProductStockLookupMode = \"auto\" | \"id\" | \"code\"\n\nexport interface ProductStock {\n\tproductId: string\n\tproductCode: string\n\tlimited: boolean\n\ttotal: number | null\n\treserved: number\n\tsold: number\n\tavailable: number | null\n\tupdatedAt: string\n\treserveTimeoutSeconds: number | null\n}\n\nexport interface ProductStockQueryOptions {\n\tlookupBy?: ProductStockLookupMode\n\tlocale?: string\n\tcurrency?: string\n}\n\nexport interface ProductStocksQueryParams {\n\tproductIds?: string[]\n\tproductCodes?: string[]\n}\n\nexport interface PricingBreakdown {\n\tisUpgrade: boolean\n\toriginalAmount: number\n\tcreditAmount: number\n\tfinalPayableAmount: number\n\tfromProductCode?: string | null\n}\n\nexport interface ConsumeEntitlementPoolResult {\n\tbalance: number\n\tbalances: Record<string, number>\n\tconsumed: Record<string, number>\n\tconsumedBuckets?: EntitlementCreditBucketConsumption[]\n}\n\nexport interface EntitlementCreditBucket {\n\tkey: string\n\tgrantId: string | null\n\tsourceKind: string\n\tsourceProductId: string | null\n\tsourceOrderId: string | null\n\tamount: number\n\tremaining: number\n\texpiresAt: string | null\n\tgrantedAt: string | null\n\tmetadata?: Record<string, any> | null\n}\n\nexport interface EntitlementCreditBucketConsumption {\n\tkey: string\n\tgrantId: string | null\n\tamount: number\n\texpiresAt: string | null\n\tsourceProductId: string | null\n\tsourceOrderId: string | null\n}\n\nexport interface ActiveSubscriptionInfo {\n\tsubscriptionId?: string\n\tproductId: string\n\tproductCode: string\n\tsortOrder: number\n\tsubscriptionLevel?: number\n\tsubscriptionGroup?: string | null\n\tpaidAt?: string | null\n\tcycleStartsAt?: string\n\texpiresAt: string\n\tperiodDays?: number\n\ttimezone?: string\n\tpriceId?: string | null\n\torderId?: string | null\n\tsourceKind?: string | null\n\tcurrency?: string | null\n}\n\nexport interface DiscountPolicy {\n\tcode: string\n\tname: string\n\tdescription: string | null\n\tqualificationCode: string\n\tproductId: string\n\tproductCode: string\n\tpriceId: string\n\tcurrency: string\n\tstandardAmount: number\n\tdiscountAmount: number\n\tstartsAt: string | null\n\tendsAt: string | null\n}\n\nexport interface UserProductDiscountEligibility {\n\tuserId: string\n\tproductId: string\n\tproductCode: string\n\teligible: boolean\n\tcoupons: Array<{\n\t\tcoupon: DiscountPolicy\n\t\teligible: boolean\n\t\tgrantedAt: string | null\n\t}>\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\tsubscriptionGroup?: string | null\n\tsubscriptionLevel?: number | null\n\tsubscriptionPeriodDays?: number | null\n\tsubscriptionTimezone?: string | null\n\tmetadata?: ProductMetadata | null\n\tdiscountEligibility?: ProductDiscountEligibility\n}\n\nexport interface ProductDiscountEligibility {\n\teligible: boolean\n\tcoupons: Array<{\n\t\tcode: string\n\t\tname: string\n\t\tcurrency: string\n\t\tstandardAmount: number\n\t\tdiscountAmount: number\n\t\tdiscountedAmount: number\n\t\tgrantedAt: string | null\n\t}>\n}\n\nexport interface CustomAmountRechargeRule extends ProductCustomAmountCurrency {\n\tproductId: string\n\tproductCode: string\n\tentitlementKey: string\n\tcurrency: string\n\tconfiguredMinAmount: number\n\tminimumGrantAmount: number\n}\n\nexport type CustomAmountRechargeValidationResult =\n\t| {\n\t\t\tvalid: true\n\t\t\trule: CustomAmountRechargeRule\n\t }\n\t| {\n\t\t\tvalid: false\n\t\t\tcode:\n\t\t\t\t| \"CUSTOM_AMOUNT_UNAVAILABLE\"\n\t\t\t\t| \"CUSTOM_AMOUNT_INVALID_AMOUNT\"\n\t\t\t\t| \"CUSTOM_AMOUNT_OUT_OF_RANGE\"\n\t\t\t\t| \"CUSTOM_AMOUNT_INVALID_STEP\"\n\t\t\terror: string\n\t\t\trule?: CustomAmountRechargeRule\n\t }\n\n/**\n * Resolve the custom amount recharge rule for a product and currency.\n *\n * Amounts are in the smallest currency unit. `unitsPerCurrencyUnit` defaults\n * to the smallest currency unit. When `unitsPerCurrencyUnitBasis` is `MAJOR`,\n * fractional entitlement amounts are rounded to the nearest whole unit. The\n * returned `minAmount` is the effective lower bound that both satisfies product\n * metadata and grants at least one entitlement unit. `configuredMinAmount`\n * keeps the raw product metadata value for display or diagnostics.\n */\nexport function getCustomAmountRechargeRule(\n\tproduct: Product | null | undefined,\n\tcurrency: string,\n): CustomAmountRechargeRule | null {\n\tif (!product || product.type !== \"CREDIT\") return null\n\tconst normalizedCurrency = currency.trim().toUpperCase()\n\tif (!/^[A-Z]{3}$/.test(normalizedCurrency)) return null\n\n\tconst customAmount = product.metadata?.customAmount\n\tif (customAmount?.enabled !== true) return null\n\n\tconst currencyConfig = customAmount.currencies?.[normalizedCurrency]\n\tif (!currencyConfig) return null\n\n\tconst minimumGrantAmount =\n\t\tcurrencyConfig.unitsPerCurrencyUnitBasis === \"MAJOR\"\n\t\t\t? Math.ceil(50 / currencyConfig.unitsPerCurrencyUnit)\n\t\t\t: Math.ceil(1 / currencyConfig.unitsPerCurrencyUnit)\n\tlet minAmount = Math.max(currencyConfig.minAmount, minimumGrantAmount)\n\tif (currencyConfig.stepAmount && minAmount > currencyConfig.minAmount) {\n\t\tconst stepCount = Math.ceil(\n\t\t\t(minAmount - currencyConfig.minAmount) / currencyConfig.stepAmount,\n\t\t)\n\t\tminAmount = currencyConfig.minAmount + stepCount * currencyConfig.stepAmount\n\t}\n\n\treturn {\n\t\t...currencyConfig,\n\t\tproductId: product.id,\n\t\tproductCode: product.code,\n\t\tentitlementKey: customAmount.entitlementKey,\n\t\tcurrency: normalizedCurrency,\n\t\tconfiguredMinAmount: currencyConfig.minAmount,\n\t\tminimumGrantAmount,\n\t\tminAmount,\n\t}\n}\n\n/**\n * Validate a custom recharge amount before calling `PaymentUI.openPayment`.\n * This is an SDK-side guardrail for integrator-owned amount inputs; the worker\n * still performs authoritative server-side validation when creating the order.\n */\nexport function validateCustomAmountRecharge(\n\tproduct: Product | null | undefined,\n\tcustomAmount: { amount: number; currency: string },\n): CustomAmountRechargeValidationResult {\n\tconst rule = getCustomAmountRechargeRule(product, customAmount.currency)\n\tif (!rule) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_UNAVAILABLE\",\n\t\t\terror:\n\t\t\t\t\"Product does not support custom amount recharge for this currency.\",\n\t\t}\n\t}\n\n\tconst amount = Number(customAmount.amount)\n\tif (!Number.isInteger(amount) || amount <= 0) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_INVALID_AMOUNT\",\n\t\t\terror:\n\t\t\t\t\"Custom amount must be a positive integer in the smallest currency unit.\",\n\t\t\trule,\n\t\t}\n\t}\n\n\tif (amount < rule.minAmount || amount > rule.maxAmount) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_OUT_OF_RANGE\",\n\t\t\terror: `Custom amount must be between ${rule.minAmount} and ${rule.maxAmount}.`,\n\t\t\trule,\n\t\t}\n\t}\n\n\tif (\n\t\trule.stepAmount &&\n\t\t(amount - rule.configuredMinAmount) % rule.stepAmount !== 0\n\t) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tcode: \"CUSTOM_AMOUNT_INVALID_STEP\",\n\t\t\terror: `Custom amount must follow step ${rule.stepAmount}.`,\n\t\t\trule,\n\t\t}\n\t}\n\n\treturn { valid: true, rule }\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\temailVerified?: boolean | null\n\tname?: string | null\n\tusername?: string | null\n\tavatar?: string | null\n\tphoneCountryCode?: string | null\n\tphoneNumber?: string | null\n\tphoneE164?: string | null\n\tphoneVerifiedAt?: string | null\n\twechatOpenId?: string | null\n\twechatUnionId?: string | null\n\texpiresAt: string\n}\n\nexport interface SendPhoneVerificationCodeParams {\n\tuserId: string\n\tphoneCountryCode?: string\n\tcountryCode?: string\n\tphoneNumber: string\n}\n\nexport interface SendPhoneVerificationCodeResponse {\n\tphoneCountryCode?: string | null\n\tphoneNumber?: string | null\n\tphoneE164: string\n\texpiresAt: string\n\tcooldownSeconds?: number\n\tresendAfterSeconds?: number\n}\n\nexport interface PhoneBinding {\n\tuserId: string\n\tphoneCountryCode?: string | null\n\tphoneNumber?: string | null\n\tphoneE164?: string | null\n\tphoneVerifiedAt?: string | null\n\tbound: boolean\n}\n\nexport interface GetPhoneBindingParams {\n\tuserId: string\n}\n\nexport interface BindPhoneNumberParams {\n\tuserId: string\n\tphoneCountryCode?: string\n\tcountryCode?: string\n\tphoneNumber: string\n\tcode: string\n}\n\nexport type UpdatePhoneNumberParams = BindPhoneNumberParams\n\nexport interface BindPhoneNumberResponse {\n\tuser: {\n\t\tuserId: string\n\t\tphoneCountryCode?: string | null\n\t\tphoneNumber?: string | null\n\t\tphoneE164?: string | null\n\t\tphoneVerifiedAt?: string | null\n\t}\n\tmerged: boolean\n\tmergeSummary?: Record<string, any>\n}\n\nexport type UpdatePhoneNumberResponse = BindPhoneNumberResponse\n\nexport interface CreateWechatMessageBindingParams {\n\t/** Hosted login external user ID returned by Youidian login. */\n\tuserId?: string\n\tloginExternalUserId?: string\n\t/** Integrator-owned user ID when hosted login is not used. */\n\tmerchantUserId?: string\n\t/** Optional linked template ID or code to infer the WeChat channel. */\n\ttemplateId?: string\n\ttemplateCode?: string\n\t/** Optional direct channel ID when no template is selected yet. */\n\tchannelId?: string\n\t/** Optional locale for the hosted binding page. */\n\tlocale?: string\n\t/** Optional URL to return to after binding succeeds. */\n\treturnUrl?: string\n}\n\nexport interface CreateWechatMessageBindingResponse {\n\tticketId: string\n\tstatus: \"PENDING\" | \"BOUND\" | \"EXPIRED\" | \"FAILED\"\n\tbindingUrl: string\n\tqrCodeUrl?: string | null\n\texpiresAt: string\n}\n\nexport type MessageTemplateDataValue =\n\t| string\n\t| number\n\t| boolean\n\t| {\n\t\t\tvalue: string | number | boolean\n\t\t\tcolor?: string\n\t }\n\nexport interface SendMessageParams {\n\ttemplateId?: string\n\ttemplateCode?: string\n\tuserId?: string\n\tloginExternalUserId?: string\n\tmerchantUserId?: string\n\t/** Direct SMS recipient phone number. When provided for SMS templates, binding is not required before sending. */\n\tphoneNumber?: string\n\t/** Country/region code for phoneNumber. Defaults to +86 on the gateway. */\n\tphoneCountryCode?: string\n\t/** Alias for phoneCountryCode. */\n\tcountryCode?: string\n\t/** Direct E.164 SMS recipient. Alternative to phoneNumber. */\n\tphoneE164?: string\n\tdata: Record<string, MessageTemplateDataValue> | MessageTemplateDataValue[]\n\turl?: string\n\tredirectUrl?: string\n\tminiProgram?: {\n\t\tappid: string\n\t\tpagepath?: string\n\t}\n\tidempotencyKey?: string\n}\n\nexport interface SendMessageResponse {\n\tlogId: string\n\tstatus: \"PENDING\" | \"SENT\" | \"FAILED\" | \"SKIPPED\"\n\tproviderMessageId?: string | null\n\terrorCode?: string | null\n\terrorMessage?: string | null\n\tbindRequired?: boolean\n\tidempotent?: boolean\n\tphoneBinding?: {\n\t\tbound: boolean\n\t\tuserId?: string\n\t\tphoneE164?: string | null\n\t\tmerged?: boolean\n\t\talreadyBound?: boolean\n\t\terror?: string\n\t} | null\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\nexport interface MerchantPricingBreakdownItem {\n\ttype: \"coupon\" | \"promotion\" | \"membership\" | \"manual\" | \"other\"\n\tamount: number\n\tlabel?: string\n\tcode?: string\n}\n\nexport interface MerchantPricing {\n\tamount: number\n\tcurrency: string\n\toriginalAmount?: number\n\tdiscountAmount?: number\n\tdiscountReason?: string\n\tdiscountCode?: string\n\tbreakdown?: MerchantPricingBreakdownItem[]\n}\n\nexport interface MerchantPricingSnapshot extends MerchantPricing {\n\toriginalAmount: number\n\tdiscountAmount: number\n\tpriceId: string\n\tproductId: string\n\tproductCode: 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\tcallbackUrl?: string\n\tmetadata?: Record<string, any>\n\tmerchantOrderId?: string\n\topenid?: string\n\tlocale?: string\n\tsubscriptionIntent?: SubscriptionIntent\n\tcustomAmount?: {\n\t\tamount: number\n\t\tcurrency: string\n\t}\n\tmerchantPricing?: MerchantPricing\n}\n\nexport type CreateBankTransferOrderParams = Omit<CreateOrderParams, \"channel\">\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\tsubscriptionIntent?: SubscriptionIntent\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\tpricingBreakdown?: PricingBreakdown\n\tstatus?: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\tpaidAt?: string | null\n\tchannel?: string\n\tisSandbox?: boolean\n\tmerchantPricing?: MerchantPricingSnapshot\n\tbilling?: BillingSnapshot\n\tsubscription?: SubscriptionDetails | null\n}\n\nexport interface CancelOrderResponse {\n\tcancelled: boolean\n\tstatus: \"PENDING\" | \"PAID\" | \"CANCELLED\" | \"REFUNDED\" | \"FAILED\"\n\torderId: string\n\tinternalId?: string | null\n}\n\nexport interface CompleteFreeOrderResponse {\n\tcompleted: boolean\n\tstatus: \"paid\" | \"pending\" | \"cancelled\" | \"refunded\" | \"failed\" | \"unknown\"\n\torderId: string\n\tinternalId?: string | null\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 * Platform webhook payload.\n *\n * The platform delivers this object as plaintext JSON in the webhook request\n * body, signed with `X-UniPay-Signature` / `X-UniPay-Timestamp` headers.\n * Validate with {@link PaymentClient.verifyWebhookSignature} using the raw\n * body string before parsing.\n */\nexport interface WebhookPayload {\n\tevent: \"ORDER_PAID\" | \"ORDER_CANCELLED\" | \"ORDER_REFUNDED\" | \"ORDER_FAILED\"\n\torderId: string\n\tmerchantOrderId: string\n\tmerchantUserId: string\n\tproductCode: string\n\tbilling: BillingSnapshot\n\tsubscription: SubscriptionDetails | null\n\tamount: number\n\tcurrency: string\n\tpaidAt: string | null\n\tchannelTransactionId?: string\n\tcancelSubscription?: boolean\n\trevokeEntitlements?: boolean\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\tlet parsedError: any = null\n\t\t\ttry {\n\t\t\t\tparsedError = JSON.parse(errorText)\n\t\t\t} catch {}\n\t\t\tconst message =\n\t\t\t\tparsedError?.message ||\n\t\t\t\tparsedError?.error ||\n\t\t\t\terrorText ||\n\t\t\t\t\"Request failed\"\n\t\t\tthrow new PaymentApiError(\n\t\t\t\tmessage,\n\t\t\t\tresponse.status,\n\t\t\t\tparsedError?.code || undefined,\n\t\t\t)\n\t\t}\n\n\t\tconst json = await response.json()\n\t\tif (json.error) {\n\t\t\tthrow new PaymentApiError(\n\t\t\t\t`Payment API Error: ${json.error}`,\n\t\t\t\tresponse.status,\n\t\t\t\tjson.code,\n\t\t\t)\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 *\n\t * @deprecated This legacy encrypted-envelope protocol is NOT what the platform\n\t * actually delivers. The platform sends an HMAC-signed plaintext JSON webhook\n\t * with `X-UniPay-Signature` / `X-UniPay-Timestamp` headers; use\n\t * {@link PaymentClient.verifyWebhookSignature} to validate those instead.\n\t *\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 {\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 * Verify an inbound platform webhook signature.\n\t *\n\t * The platform delivers webhooks as plaintext JSON with two headers:\n\t * `X-UniPay-Signature` (HMAC-SHA256 of `${timestamp}.${rawBodyString}`) and\n\t * `X-UniPay-Timestamp`. The body itself is NOT encrypted; verify the signature\n\t * then `JSON.parse` the body to obtain a {@link WebhookPayload}.\n\t *\n\t * @param rawBody - The raw request body string (exactly as received)\n\t * @param signature - The `X-UniPay-Signature` header value (hex)\n\t * @param timestamp - The `X-UniPay-Timestamp` header value (ms epoch string)\n\t * @param options - Optional `toleranceMs` for timestamp freshness (default 5 min)\n\t * @returns `true` if the signature is valid and the timestamp is fresh\n\t */\n\tverifyWebhookSignature(\n\t\trawBody: string,\n\t\tsignature: string,\n\t\ttimestamp: string,\n\t\toptions?: { toleranceMs?: number },\n\t): boolean {\n\t\ttry {\n\t\t\tconst toleranceMs = options?.toleranceMs ?? 5 * 60 * 1000\n\t\t\tconst timestampMs = Number.parseInt(timestamp, 10)\n\t\t\tif (\n\t\t\t\tNumber.isNaN(timestampMs) ||\n\t\t\t\tMath.abs(Date.now() - timestampMs) > toleranceMs\n\t\t\t) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst rawString = `${timestamp}.${rawBody}`\n\t\t\tconst expected = crypto\n\t\t\t\t.createHmac(\"sha256\", this.appSecret)\n\t\t\t\t.update(rawString)\n\t\t\t\t.digest(\"hex\")\n\n\t\t\tconst signatureBuffer = Buffer.from(signature)\n\t\t\tconst expectedBuffer = Buffer.from(expected)\n\t\t\tif (signatureBuffer.length !== expectedBuffer.length) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn crypto.timingSafeEqual(signatureBuffer, expectedBuffer)\n\t\t} catch {\n\t\t\treturn false\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\tuserId?: 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\t\tif (options?.userId !== undefined) {\n\t\t\tconst userId = options.userId.trim()\n\t\t\tif (!userId) throw new Error(\"userId must be a non-empty string\")\n\t\t\tparams.append(\"userId\", userId)\n\t\t}\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/** Fetch all currently active discount policies for the configured app. */\n\tasync getDiscountPolicies(): Promise<DiscountPolicy[]> {\n\t\treturn this.request(\"GET\", \"/discount-policies\")\n\t}\n\n\t/** Fetch currently active discount policies for one product. */\n\tasync getProductDiscountPolicies(\n\t\tproductIdOrCode: string,\n\t): Promise<DiscountPolicy[]> {\n\t\tconst value = productIdOrCode?.trim()\n\t\tif (!value) throw new Error(\"productIdOrCode is required\")\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/products/${encodeURIComponent(value)}/discount-policies`,\n\t\t)\n\t}\n\n\t/** Query one user's discount eligibility for one product. */\n\tasync getUserProductDiscountEligibility(\n\t\tuserId: string,\n\t\tproductIdOrCode: string,\n\t): Promise<UserProductDiscountEligibility> {\n\t\tconst userValue = userId?.trim()\n\t\tconst productValue = productIdOrCode?.trim()\n\t\tif (!userValue) throw new Error(\"userId is required\")\n\t\tif (!productValue) throw new Error(\"productIdOrCode is required\")\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/users/${encodeURIComponent(userValue)}/products/${encodeURIComponent(productValue)}/discount-eligibility`,\n\t\t)\n\t}\n\n\t/**\n\t * Fetch the realtime stock snapshot for a single product.\n\t */\n\tasync getProductStock(\n\t\tproductIdOrCode: string,\n\t\toptions?: ProductStockQueryOptions,\n\t): Promise<ProductStock> {\n\t\tconst value = productIdOrCode?.trim()\n\t\tif (!value) {\n\t\t\tthrow new Error(\"productIdOrCode is required\")\n\t\t}\n\n\t\tconst params = new URLSearchParams()\n\t\tparams.set(\"productIdOrCode\", value)\n\t\tif (options?.lookupBy) params.set(\"lookupBy\", options.lookupBy)\n\t\tif (options?.locale) params.set(\"locale\", options.locale)\n\t\tif (options?.currency) params.set(\"currency\", options.currency)\n\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/products/${encodeURIComponent(value)}/stock?${params.toString()}`,\n\t\t)\n\t}\n\n\t/**\n\t * Fetch realtime stock snapshots for multiple products.\n\t */\n\tasync getProductStocks(\n\t\tparams: ProductStocksQueryParams,\n\t\toptions?: ProductStockQueryOptions,\n\t): Promise<ProductStock[]>\n\tasync getProductStocks(\n\t\tparams: ProductStocksQueryParams & ProductStockQueryOptions,\n\t): Promise<ProductStock[]>\n\tasync getProductStocks(\n\t\tparams: ProductStocksQueryParams & ProductStockQueryOptions,\n\t\toptions?: ProductStockQueryOptions,\n\t): Promise<ProductStock[]> {\n\t\tconst productIds = [\n\t\t\t...new Set(\n\t\t\t\t(params.productIds || []).map((item) => item.trim()).filter(Boolean),\n\t\t\t),\n\t\t]\n\t\tconst productCodes = [\n\t\t\t...new Set(\n\t\t\t\t(params.productCodes || []).map((item) => item.trim()).filter(Boolean),\n\t\t\t),\n\t\t]\n\t\tif (productIds.length === 0 && productCodes.length === 0) {\n\t\t\tthrow new Error(\"productIds or productCodes is required\")\n\t\t}\n\n\t\tconst queryOptions = options || params\n\t\tconst query = new URLSearchParams()\n\t\tif (productIds.length > 0) query.set(\"productIds\", productIds.join(\",\"))\n\t\tif (productCodes.length > 0)\n\t\t\tquery.set(\"productCodes\", productCodes.join(\",\"))\n\t\tif (queryOptions.lookupBy) query.set(\"lookupBy\", queryOptions.lookupBy)\n\t\tif (queryOptions.locale) query.set(\"locale\", queryOptions.locale)\n\t\tif (queryOptions.currency) query.set(\"currency\", queryOptions.currency)\n\n\t\treturn this.request(\"GET\", `/products/stocks?${query.toString()}`)\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 paid manual bank transfer order.\n\t * @param params - Order creation parameters without channel\n\t * @returns Paid order details\n\t */\n\tasync createBankTransferOrder(\n\t\tparams: CreateBankTransferOrderParams,\n\t): Promise<CreateOrderResponse> {\n\t\treturn this.request(\"POST\", \"/orders\", {\n\t\t\t...params,\n\t\t\tchannel: \"BANK_TRANSFER\",\n\t\t} satisfies CreateOrderParams)\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 * Cancel a pending order and release its inventory reservation.\n\t * Paid/refunded/failed orders are returned unchanged by the gateway.\n\t */\n\tasync cancelOrder(\n\t\torderId: string,\n\t\tparams?: { reason?: string },\n\t): Promise<CancelOrderResponse> {\n\t\tconst value = orderId?.trim()\n\t\tif (!value) {\n\t\t\tthrow new Error(\"orderId is required\")\n\t\t}\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/orders/${encodeURIComponent(value)}/cancel`,\n\t\t\tparams || {},\n\t\t)\n\t}\n\n\t/**\n\t * Complete a zero-amount FREE order.\n\t * This marks the pending order as paid and triggers normal paid-order side effects.\n\t */\n\tasync completeFreeOrder(orderId: string): Promise<CompleteFreeOrderResponse> {\n\t\tconst value = orderId?.trim()\n\t\tif (!value) {\n\t\t\tthrow new Error(\"orderId is required\")\n\t\t}\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/orders/${encodeURIComponent(value)}/free/complete`,\n\t\t\t{},\n\t\t)\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\tasync getActiveSubscription(\n\t\tuserId: string,\n\t): Promise<ActiveSubscriptionInfo | null> {\n\t\treturn this.request(\"GET\", `/users/${userId}/active-subscription`)\n\t}\n\n\tasync getSubscriptionUpgradeOptions(\n\t\tuserId: string,\n\t\toptions?: { locale?: string; currency?: string },\n\t): Promise<Product[]> {\n\t\tconst value = userId?.trim()\n\t\tif (!value) throw new Error(\"userId is required\")\n\t\tconst params = new URLSearchParams()\n\t\tif (options?.locale) params.set(\"locale\", options.locale)\n\t\tif (options?.currency) params.set(\"currency\", options.currency)\n\t\tconst query = params.toString() ? `?${params.toString()}` : \"\"\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/users/${encodeURIComponent(value)}/subscription-upgrade-options${query}`,\n\t\t)\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 * Consume numeric entitlements from an ordered key pool.\n\t * Useful when subscription credits and perpetual credits use separate keys.\n\t */\n\tasync consumeEntitlementPool(\n\t\tuserId: string,\n\t\tkeys: 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<ConsumeEntitlementPoolResult> {\n\t\treturn this.request(\"POST\", `/users/${userId}/entitlements/consume-pool`, {\n\t\t\tkeys,\n\t\t\tamount,\n\t\t\t...options,\n\t\t})\n\t}\n\n\t/**\n\t * Get active numeric entitlement buckets ordered by expiration.\n\t */\n\tasync getEntitlementCreditBuckets(\n\t\tuserId: string,\n\t\tkeys?: string[],\n\t): Promise<EntitlementCreditBucket[]> {\n\t\tconst query =\n\t\t\tkeys && keys.length > 0\n\t\t\t\t? `?keys=${encodeURIComponent(keys.join(\",\"))}`\n\t\t\t\t: \"\"\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/users/${userId}/entitlements/credit-buckets${query}`,\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\t/**\n\t * Send a phone verification code for binding a phone number to a hosted login user.\n\t */\n\tasync sendPhoneVerificationCode(\n\t\tparams: SendPhoneVerificationCodeParams,\n\t): Promise<SendPhoneVerificationCodeResponse> {\n\t\tconst userId = params.userId?.trim()\n\t\tconst phoneNumber = params.phoneNumber?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\t\tif (!phoneNumber) throw new Error(\"phoneNumber is required\")\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone/code`,\n\t\t\t{\n\t\t\t\tphoneCountryCode: params.phoneCountryCode || params.countryCode,\n\t\t\t\tphoneNumber,\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Query the verified phone binding for a hosted login user.\n\t */\n\tasync getPhoneBinding(params: GetPhoneBindingParams): Promise<PhoneBinding> {\n\t\tconst userId = params.userId?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone`,\n\t\t)\n\t}\n\n\t/**\n\t * Bind a verified phone number to a hosted login user, merging existing accounts when needed.\n\t */\n\tasync bindPhoneNumber(\n\t\tparams: BindPhoneNumberParams,\n\t): Promise<BindPhoneNumberResponse> {\n\t\tconst userId = params.userId?.trim()\n\t\tconst phoneNumber = params.phoneNumber?.trim()\n\t\tconst code = params.code?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\t\tif (!phoneNumber) throw new Error(\"phoneNumber is required\")\n\t\tif (!code) throw new Error(\"code is required\")\n\n\t\treturn this.request(\n\t\t\t\"POST\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone/bind`,\n\t\t\t{\n\t\t\t\tcode,\n\t\t\t\tphoneCountryCode: params.phoneCountryCode || params.countryCode,\n\t\t\t\tphoneNumber,\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Update a hosted login user's phone number after verifying the new number by SMS code.\n\t */\n\tasync updatePhoneNumber(\n\t\tparams: UpdatePhoneNumberParams,\n\t): Promise<UpdatePhoneNumberResponse> {\n\t\tconst userId = params.userId?.trim()\n\t\tconst phoneNumber = params.phoneNumber?.trim()\n\t\tconst code = params.code?.trim()\n\t\tif (!userId) throw new Error(\"userId is required\")\n\t\tif (!phoneNumber) throw new Error(\"phoneNumber is required\")\n\t\tif (!code) throw new Error(\"code is required\")\n\n\t\treturn this.request(\n\t\t\t\"PUT\",\n\t\t\t`/login/users/${encodeURIComponent(userId)}/phone`,\n\t\t\t{\n\t\t\t\tcode,\n\t\t\t\tphoneCountryCode: params.phoneCountryCode || params.countryCode,\n\t\t\t\tphoneNumber,\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Create a hosted WeChat official account binding URL for a user.\n\t */\n\tasync createWechatMessageBinding(\n\t\tparams: CreateWechatMessageBindingParams,\n\t): Promise<CreateWechatMessageBindingResponse> {\n\t\tconst loginExternalUserId =\n\t\t\tparams.loginExternalUserId?.trim() || params.userId?.trim()\n\t\tconst merchantUserId = params.merchantUserId?.trim()\n\t\tif (!loginExternalUserId && !merchantUserId) {\n\t\t\tthrow new Error(\"userId or merchantUserId is required\")\n\t\t}\n\n\t\treturn this.request(\"POST\", \"/messages/wechat/bindings\", {\n\t\t\t...params,\n\t\t\tloginExternalUserId,\n\t\t\tmerchantUserId,\n\t\t})\n\t}\n\n\t/**\n\t * Send an app-linked message template to a bound recipient.\n\t */\n\tasync sendMessage(params: SendMessageParams): Promise<SendMessageResponse> {\n\t\tif (!params.templateId?.trim() && !params.templateCode?.trim()) {\n\t\t\tthrow new Error(\"templateId or templateCode is required\")\n\t\t}\n\t\tif (!params.data || typeof params.data !== \"object\") {\n\t\t\tthrow new Error(\"data is required\")\n\t\t}\n\n\t\treturn this.request(\"POST\", \"/messages/send\", params)\n\t}\n}\n"],"mappings":"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","names":["crypto"]}
|
package/dist/server.d.cts
CHANGED
|
@@ -11,12 +11,91 @@ interface OrderStatus {
|
|
|
11
11
|
paidAt?: string;
|
|
12
12
|
channelTransactionId?: string;
|
|
13
13
|
}
|
|
14
|
+
type SubscriptionAction = "NEW" | "UPGRADE" | "DOWNGRADE";
|
|
15
|
+
type SubscriptionIntent = "STANDARD" | "UPGRADE";
|
|
16
|
+
type SubscriptionStatus = "PENDING" | "ACTIVE" | "EXPIRED" | "REPLACED" | "CANCELLED";
|
|
17
|
+
type SubscriptionAutoRefundStatus = "PROCESSING" | "SUCCEEDED" | "FAILED";
|
|
18
|
+
type SubscriptionErrorCode = "SUBSCRIPTION_UPGRADE_NOT_AVAILABLE" | "SUBSCRIPTION_SAME_LEVEL_NOT_ALLOWED" | "SUBSCRIPTION_DOWNGRADE_NOT_ALLOWED" | "SUBSCRIPTION_PENDING_ORDER_EXISTS" | "SUBSCRIPTION_NO_REMAINING_DAYS" | "SUBSCRIPTION_CURRENCY_MISMATCH" | "SUBSCRIPTION_PERIOD_MISMATCH" | "SUBSCRIPTION_PRICING_MANAGED";
|
|
19
|
+
interface SubscriptionBillingSnapshot {
|
|
20
|
+
version: number;
|
|
21
|
+
action: SubscriptionAction;
|
|
22
|
+
isUpgrade: boolean;
|
|
23
|
+
quoteBusinessDate: string;
|
|
24
|
+
timezone: string;
|
|
25
|
+
periodDays: number;
|
|
26
|
+
subscriptionGroup: string;
|
|
27
|
+
subscriptionLevel: number;
|
|
28
|
+
productCode: string;
|
|
29
|
+
standardAmount: number;
|
|
30
|
+
policyDiscountAmount: number;
|
|
31
|
+
policyId: string | null;
|
|
32
|
+
policyCode: string | null;
|
|
33
|
+
policyName: string | null;
|
|
34
|
+
policyRevision: number | null;
|
|
35
|
+
qualificationCode: string | null;
|
|
36
|
+
intent?: SubscriptionIntent | null;
|
|
37
|
+
fromSubscriptionId?: string | null;
|
|
38
|
+
managedSubscription?: boolean;
|
|
39
|
+
fromProductCode?: string | null;
|
|
40
|
+
remainingDays?: number | null;
|
|
41
|
+
currentRemainingValueAmount?: number | null;
|
|
42
|
+
targetRemainingValueAmount?: number | null;
|
|
43
|
+
appliedDiscountAmount?: number | null;
|
|
44
|
+
finalAmount?: number | null;
|
|
45
|
+
startsAt?: string | null;
|
|
46
|
+
cycleStartsAt?: string | null;
|
|
47
|
+
expiresAt?: string | null;
|
|
48
|
+
}
|
|
49
|
+
interface BillingSnapshot {
|
|
50
|
+
version: number;
|
|
51
|
+
standardAmount: number;
|
|
52
|
+
discountAmount: number;
|
|
53
|
+
upgradeCreditAmount: number;
|
|
54
|
+
targetRemainingValueAmount: number | null;
|
|
55
|
+
fullPlanValueAmount: number | null;
|
|
56
|
+
discountPolicyCode: string | null;
|
|
57
|
+
discountPolicyRevision: number | null;
|
|
58
|
+
qualificationCode: string | null;
|
|
59
|
+
refundRequired: boolean;
|
|
60
|
+
autoRefundStatus: SubscriptionAutoRefundStatus | null;
|
|
61
|
+
subscription: SubscriptionBillingSnapshot | null;
|
|
62
|
+
customAmount: Record<string, unknown> | null;
|
|
63
|
+
merchantPricing: MerchantPricingSnapshot | null;
|
|
64
|
+
}
|
|
65
|
+
interface SubscriptionDetails {
|
|
66
|
+
id: string;
|
|
67
|
+
action: SubscriptionAction;
|
|
68
|
+
status: SubscriptionStatus;
|
|
69
|
+
productId: string;
|
|
70
|
+
productCode: string;
|
|
71
|
+
priceId: string;
|
|
72
|
+
subscriptionGroup: string;
|
|
73
|
+
subscriptionLevel: number;
|
|
74
|
+
startsAt: string;
|
|
75
|
+
cycleStartsAt: string;
|
|
76
|
+
expiresAt: string;
|
|
77
|
+
periodDays: number;
|
|
78
|
+
timezone: string;
|
|
79
|
+
fullPlanValueAmount: number;
|
|
80
|
+
currency: string;
|
|
81
|
+
replacesSubscriptionId: string | null;
|
|
82
|
+
activatedAt: string | null;
|
|
83
|
+
replacedAt: string | null;
|
|
84
|
+
cancelledAt: string | null;
|
|
85
|
+
}
|
|
86
|
+
declare class PaymentApiError extends Error {
|
|
87
|
+
readonly status: number;
|
|
88
|
+
readonly code?: string;
|
|
89
|
+
constructor(message: string, status: number, code?: string);
|
|
90
|
+
}
|
|
14
91
|
/**
|
|
15
92
|
* Order details response (full order information)
|
|
16
93
|
*/
|
|
17
94
|
interface OrderDetails {
|
|
18
95
|
orderId: string;
|
|
19
96
|
internalId: string;
|
|
97
|
+
merchantUserId: string;
|
|
98
|
+
productCode: string | null;
|
|
20
99
|
status: "PENDING" | "PAID" | "CANCELLED" | "REFUNDED" | "FAILED";
|
|
21
100
|
amount: number;
|
|
22
101
|
currency: string;
|
|
@@ -25,6 +104,8 @@ interface OrderDetails {
|
|
|
25
104
|
createdAt: string;
|
|
26
105
|
channel?: string;
|
|
27
106
|
merchantPricing?: MerchantPricingSnapshot;
|
|
107
|
+
billing: BillingSnapshot;
|
|
108
|
+
subscription: SubscriptionDetails | null;
|
|
28
109
|
pricingBreakdown?: PricingBreakdown;
|
|
29
110
|
upgrade?: {
|
|
30
111
|
isUpgrade: boolean;
|
|
@@ -159,14 +240,21 @@ interface EntitlementCreditBucketConsumption {
|
|
|
159
240
|
sourceOrderId: string | null;
|
|
160
241
|
}
|
|
161
242
|
interface ActiveSubscriptionInfo {
|
|
243
|
+
subscriptionId?: string;
|
|
162
244
|
productId: string;
|
|
163
245
|
productCode: string;
|
|
164
246
|
sortOrder: number;
|
|
247
|
+
subscriptionLevel?: number;
|
|
248
|
+
subscriptionGroup?: string | null;
|
|
165
249
|
paidAt?: string | null;
|
|
250
|
+
cycleStartsAt?: string;
|
|
166
251
|
expiresAt: string;
|
|
252
|
+
periodDays?: number;
|
|
253
|
+
timezone?: string;
|
|
167
254
|
priceId?: string | null;
|
|
168
255
|
orderId?: string | null;
|
|
169
256
|
sourceKind?: string | null;
|
|
257
|
+
currency?: string | null;
|
|
170
258
|
}
|
|
171
259
|
interface DiscountPolicy {
|
|
172
260
|
code: string;
|
|
@@ -209,6 +297,19 @@ interface Product {
|
|
|
209
297
|
subscriptionPeriodDays?: number | null;
|
|
210
298
|
subscriptionTimezone?: string | null;
|
|
211
299
|
metadata?: ProductMetadata | null;
|
|
300
|
+
discountEligibility?: ProductDiscountEligibility;
|
|
301
|
+
}
|
|
302
|
+
interface ProductDiscountEligibility {
|
|
303
|
+
eligible: boolean;
|
|
304
|
+
coupons: Array<{
|
|
305
|
+
code: string;
|
|
306
|
+
name: string;
|
|
307
|
+
currency: string;
|
|
308
|
+
standardAmount: number;
|
|
309
|
+
discountAmount: number;
|
|
310
|
+
discountedAmount: number;
|
|
311
|
+
grantedAt: string | null;
|
|
312
|
+
}>;
|
|
212
313
|
}
|
|
213
314
|
interface CustomAmountRechargeRule extends ProductCustomAmountCurrency {
|
|
214
315
|
productId: string;
|
|
@@ -453,6 +554,7 @@ interface CreateOrderParams {
|
|
|
453
554
|
merchantOrderId?: string;
|
|
454
555
|
openid?: string;
|
|
455
556
|
locale?: string;
|
|
557
|
+
subscriptionIntent?: SubscriptionIntent;
|
|
456
558
|
customAmount?: {
|
|
457
559
|
amount: number;
|
|
458
560
|
currency: string;
|
|
@@ -468,6 +570,7 @@ type CreateMiniProgramOrderParams = {
|
|
|
468
570
|
openid: string;
|
|
469
571
|
merchantOrderId?: string;
|
|
470
572
|
priceId: string;
|
|
573
|
+
subscriptionIntent?: SubscriptionIntent;
|
|
471
574
|
};
|
|
472
575
|
/**
|
|
473
576
|
* Create Order Response
|
|
@@ -484,6 +587,8 @@ interface CreateOrderResponse {
|
|
|
484
587
|
channel?: string;
|
|
485
588
|
isSandbox?: boolean;
|
|
486
589
|
merchantPricing?: MerchantPricingSnapshot;
|
|
590
|
+
billing?: BillingSnapshot;
|
|
591
|
+
subscription?: SubscriptionDetails | null;
|
|
487
592
|
}
|
|
488
593
|
interface CancelOrderResponse {
|
|
489
594
|
cancelled: boolean;
|
|
@@ -532,7 +637,8 @@ interface WebhookPayload {
|
|
|
532
637
|
merchantOrderId: string;
|
|
533
638
|
merchantUserId: string;
|
|
534
639
|
productCode: string;
|
|
535
|
-
billing:
|
|
640
|
+
billing: BillingSnapshot;
|
|
641
|
+
subscription: SubscriptionDetails | null;
|
|
536
642
|
amount: number;
|
|
537
643
|
currency: string;
|
|
538
644
|
paidAt: string | null;
|
|
@@ -656,6 +762,7 @@ declare class PaymentClient {
|
|
|
656
762
|
getProducts(options?: {
|
|
657
763
|
locale?: string;
|
|
658
764
|
currency?: string;
|
|
765
|
+
userId?: string;
|
|
659
766
|
}): Promise<Product[]>;
|
|
660
767
|
/** Fetch all currently active discount policies for the configured app. */
|
|
661
768
|
getDiscountPolicies(): Promise<DiscountPolicy[]>;
|
|
@@ -740,6 +847,10 @@ declare class PaymentClient {
|
|
|
740
847
|
*/
|
|
741
848
|
getEntitlementsDetail(userId: string): Promise<EntitlementDetail>;
|
|
742
849
|
getActiveSubscription(userId: string): Promise<ActiveSubscriptionInfo | null>;
|
|
850
|
+
getSubscriptionUpgradeOptions(userId: string, options?: {
|
|
851
|
+
locale?: string;
|
|
852
|
+
currency?: string;
|
|
853
|
+
}): Promise<Product[]>;
|
|
743
854
|
/**
|
|
744
855
|
* Ensure user exists and auto-assign trial product if new user
|
|
745
856
|
* This should be called when user first logs in or registers
|
|
@@ -832,4 +943,4 @@ declare class PaymentClient {
|
|
|
832
943
|
sendMessage(params: SendMessageParams): Promise<SendMessageResponse>;
|
|
833
944
|
}
|
|
834
945
|
|
|
835
|
-
export { type ActiveSubscriptionInfo, type BindPhoneNumberParams, type BindPhoneNumberResponse, type CancelOrderResponse, type CompleteFreeOrderResponse, type ConsumeEntitlementPoolResult, type CreateBankTransferOrderParams, type CreateMiniProgramOrderParams, type CreateOrderParams, type CreateOrderResponse, type CreateWechatMessageBindingParams, type CreateWechatMessageBindingResponse, type CustomAmountRechargeRule, type CustomAmountRechargeValidationResult, type DiscountPolicy, type EnsureUserWithTrialResponse, type EntitlementCreditBucket, type EntitlementCreditBucketConsumption, type EntitlementDetail, type EntitlementDetailItem, type GetOrdersParams, type GetOrdersResponse, type GetPhoneBindingParams, type MerchantPricing, type MerchantPricingBreakdownItem, type MerchantPricingSnapshot, type MessageTemplateDataValue, type OrderDetails, type OrderListItem, type OrderStatus, type PaymentCallbackData, PaymentClient, type PaymentClientOptions, type PaymentNotification, type PhoneBinding, type PricingBreakdown, type Product, type ProductCustomAmount, type ProductCustomAmountCurrency, type ProductEntitlements, type ProductInventory, type ProductMetadata, type ProductPrice, type ProductResetRule, type ProductStock, type ProductStockLookupMode, type ProductStockQueryOptions, type ProductStocksQueryParams, type ProductSubscriptionPeriod, type SendMessageParams, type SendMessageResponse, type SendPhoneVerificationCodeParams, type SendPhoneVerificationCodeResponse, type UpdatePhoneNumberParams, type UpdatePhoneNumberResponse, type UserProductDiscountEligibility, type VerifiedLoginToken, type WebhookPayload, type WechatJsapiPayParams, getCustomAmountRechargeRule, validateCustomAmountRecharge };
|
|
946
|
+
export { type ActiveSubscriptionInfo, type BillingSnapshot, type BindPhoneNumberParams, type BindPhoneNumberResponse, type CancelOrderResponse, type CompleteFreeOrderResponse, type ConsumeEntitlementPoolResult, type CreateBankTransferOrderParams, type CreateMiniProgramOrderParams, type CreateOrderParams, type CreateOrderResponse, type CreateWechatMessageBindingParams, type CreateWechatMessageBindingResponse, type CustomAmountRechargeRule, type CustomAmountRechargeValidationResult, type DiscountPolicy, type EnsureUserWithTrialResponse, type EntitlementCreditBucket, type EntitlementCreditBucketConsumption, type EntitlementDetail, type EntitlementDetailItem, type GetOrdersParams, type GetOrdersResponse, type GetPhoneBindingParams, type MerchantPricing, type MerchantPricingBreakdownItem, type MerchantPricingSnapshot, type MessageTemplateDataValue, type OrderDetails, type OrderListItem, type OrderStatus, PaymentApiError, type PaymentCallbackData, PaymentClient, type PaymentClientOptions, type PaymentNotification, type PhoneBinding, type PricingBreakdown, type Product, type ProductCustomAmount, type ProductCustomAmountCurrency, type ProductDiscountEligibility, type ProductEntitlements, type ProductInventory, type ProductMetadata, type ProductPrice, type ProductResetRule, type ProductStock, type ProductStockLookupMode, type ProductStockQueryOptions, type ProductStocksQueryParams, type ProductSubscriptionPeriod, type SendMessageParams, type SendMessageResponse, type SendPhoneVerificationCodeParams, type SendPhoneVerificationCodeResponse, type SubscriptionAction, type SubscriptionAutoRefundStatus, type SubscriptionBillingSnapshot, type SubscriptionDetails, type SubscriptionErrorCode, type SubscriptionIntent, type SubscriptionStatus, type UpdatePhoneNumberParams, type UpdatePhoneNumberResponse, type UserProductDiscountEligibility, type VerifiedLoginToken, type WebhookPayload, type WechatJsapiPayParams, getCustomAmountRechargeRule, validateCustomAmountRecharge };
|