trustbase-seller-backend 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/middleware/api-key-auth.js +4 -1
- package/package.json +1 -1
- package/routes/agent-pay.js +28 -9
- package/routes/buyer-shop.js +53 -9
- package/test-laneC.js +85 -0
|
@@ -165,7 +165,10 @@ function isTunnelBuyerRequest(req, clientIP) {
|
|
|
165
165
|
const fromLoopback = clientIP === '127.0.0.1' || clientIP === '::1' || /^127\./.test(clientIP);
|
|
166
166
|
if (!fromLoopback) return false;
|
|
167
167
|
if (String(req.headers['x-tb-tunnel'] || '') !== '1') return false;
|
|
168
|
-
|
|
168
|
+
// 注意: 本中间件挂在 '/api' 上, req.path 是挂载后路径 ('/products'), req.originalUrl 才带 '/api'
|
|
169
|
+
// → 两种前缀都要判, 否则放行条件恒为假 (2026-10-03 .211 实测 401 才发现)
|
|
170
|
+
const paths = [String(req.path || ''), String(req.originalUrl || '').split('?')[0]];
|
|
171
|
+
return TUNNEL_BUYER_PATHS.some(p => paths.some(x => x === p || x.startsWith(p + '/') || x.startsWith(p)));
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
function requireAPIKey(skipLAN = true, exemptPaths = []) { return (req, res, next) => {
|
package/package.json
CHANGED
package/routes/agent-pay.js
CHANGED
|
@@ -44,6 +44,7 @@ CREATE TABLE IF NOT EXISTS agent_pay_sessions (
|
|
|
44
44
|
pay_token_hash TEXT NOT NULL,
|
|
45
45
|
status_token_hash TEXT NOT NULL,
|
|
46
46
|
state TEXT NOT NULL DEFAULT 'CREATED', -- CREATED/PAID/ON_CHAIN/SHIPPED/DELIVERED/CONFIRMED/CLOSED/EXPIRED/REFUNDED/DISPUTED
|
|
47
|
+
sub_mchid TEXT DEFAULT '', -- Lane C: 本单所属特约商户 (多商户; 查单/对账按它选 partner 端点)
|
|
47
48
|
psp_code_url TEXT DEFAULT '',
|
|
48
49
|
psp_txn_id TEXT DEFAULT '',
|
|
49
50
|
shopxo_order_no TEXT DEFAULT '',
|
|
@@ -62,7 +63,13 @@ CREATE TABLE IF NOT EXISTS psp_txns (
|
|
|
62
63
|
created_at INTEGER NOT NULL
|
|
63
64
|
);
|
|
64
65
|
CREATE INDEX IF NOT EXISTS idx_aps_state ON agent_pay_sessions(state);
|
|
65
|
-
`);
|
|
66
|
+
`);
|
|
67
|
+
// Lane C (2026-10-03): 旧库补列 — CREATE TABLE IF NOT EXISTS 不会给已有表加列
|
|
68
|
+
try {
|
|
69
|
+
const _c = db.prepare("PRAGMA table_info(agent_pay_sessions)").all().map((x) => x.name);
|
|
70
|
+
if (!_c.includes("sub_mchid")) db.exec("ALTER TABLE agent_pay_sessions ADD COLUMN sub_mchid TEXT DEFAULT ''");
|
|
71
|
+
} catch (_) {}
|
|
72
|
+
|
|
66
73
|
// 2026-09-20 E4': 物流列 (老库兼容)
|
|
67
74
|
for (const col of ["carrier TEXT DEFAULT ''", "tracking_no TEXT DEFAULT ''", "logistics_state TEXT DEFAULT ''", "timeline TEXT DEFAULT '[]'", "shopxo_order_no_ref TEXT DEFAULT ''"]) {
|
|
68
75
|
try { db.exec("ALTER TABLE agent_pay_sessions ADD COLUMN " + col); } catch (e) { /* 已存在 */ }
|
|
@@ -138,13 +145,21 @@ async function wxApiCall(method, urlPath, bodyObj, override) {
|
|
|
138
145
|
// opts.settleInfo (2026-10-06 U7): 可选, 如 { profit_sharing: true } — 微信规则: 订单下单时
|
|
139
146
|
// 不带 settle_info.profit_sharing 的分账请求会被拒; 默认不带 (向后兼容, 直连模式不用传)
|
|
140
147
|
async function wxNativeOrder(outTradeNo, description, totalCents, expireSec, opts) {
|
|
148
|
+
// Lane C (2026-10-03): sub_mchid 按本单商家传入 (多商户), 未传才回落全局 env (单商户旧部署)
|
|
149
|
+
const subMchid = String((opts && opts.subMchid) || process.env.WXPAY_SUB_MCHID || "");
|
|
150
|
+
if (opts && opts.subMchidSource && opts.subMchidSource !== "registry") {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[agent-pay] 本单 sub_mchid 来源=${opts.subMchidSource} (非登记表), 使用 ${subMchid || "直连模式"}` +
|
|
153
|
+
(subMchid ? " — 多商户场景请核对该商户是否已进件登记" : "")
|
|
154
|
+
);
|
|
155
|
+
}
|
|
141
156
|
// SP 模式: 用服务商身份签名 + 请求体带 sp_mchid + sub_mchid
|
|
142
|
-
const isSp = !!(WX.spMchid &&
|
|
157
|
+
const isSp = !!(WX.spMchid && subMchid);
|
|
143
158
|
const override = isSp ? { mchid: WX.spMchid, serial: WX.spSerial, keyPath: WX.spKeyPath } : undefined;
|
|
144
159
|
const body = {
|
|
145
160
|
// 直连模式: appid = 商户公众号; SP 模式: 微信不认 appid, 要 sp_appid(服务商) + sub_appid(特约商户)
|
|
146
161
|
...(isSp
|
|
147
|
-
? { sp_mchid: WX.spMchid, sp_appid: WX.spAppid, sub_mchid:
|
|
162
|
+
? { sp_mchid: WX.spMchid, sp_appid: WX.spAppid, sub_mchid: subMchid, sub_appid: WX.subAppid || WX.appid }
|
|
148
163
|
: { appid: WX.appid, mchid: WX.mchid }),
|
|
149
164
|
description: String(description).slice(0, 127),
|
|
150
165
|
out_trade_no: outTradeNo, time_expire: new Date(expireSec * 1000).toISOString(),
|
|
@@ -161,11 +176,13 @@ async function wxNativeOrder(outTradeNo, description, totalCents, expireSec, opt
|
|
|
161
176
|
return r.json.code_url;
|
|
162
177
|
}
|
|
163
178
|
// 查单: trade_state ∈ {SUCCESS, NOTPAY, CLOSED, ...} (SP 模式走 partner endpoint + 双 mchid 查询串)
|
|
164
|
-
async function wxQueryOrder(outTradeNo) {
|
|
165
|
-
|
|
179
|
+
async function wxQueryOrder(outTradeNo, opts) {
|
|
180
|
+
// Lane C: 本单商户号优先, 回落全局 env (多商户)
|
|
181
|
+
const qSubMchid = String((opts && opts.subMchid) || process.env.WXPAY_SUB_MCHID || "");
|
|
182
|
+
const isSp = !!(WX.spMchid && qSubMchid);
|
|
166
183
|
const override = isSp ? { mchid: WX.spMchid, serial: WX.spSerial, keyPath: WX.spKeyPath } : undefined;
|
|
167
184
|
const endpoint = isSp
|
|
168
|
-
? `/v3/pay/partner/transactions/out-trade-no/${outTradeNo}?sp_mchid=${WX.spMchid}&sub_mchid=${
|
|
185
|
+
? `/v3/pay/partner/transactions/out-trade-no/${outTradeNo}?sp_mchid=${WX.spMchid}&sub_mchid=${qSubMchid}`
|
|
169
186
|
: `/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${WX.mchid}`;
|
|
170
187
|
const r = await wxApiCall("GET", endpoint, null, override);
|
|
171
188
|
if (r.status !== 200) return { state: "UNKNOWN", raw: r.json };
|
|
@@ -210,7 +227,8 @@ function loadPlatformCertsFromDisk() {
|
|
|
210
227
|
|
|
211
228
|
/** 拉取微信平台证书 (GET /v3/certificates, 密文用调用方 APIv3 key 解密) → 内存+磁盘缓存 */
|
|
212
229
|
async function refreshPlatformCerts() {
|
|
213
|
-
|
|
230
|
+
// Lane C: SP 身份只判 sp_mchid 是否配置 — 不能依赖全局 sub_mchid (多商户部署常不设默认值)
|
|
231
|
+
const isSp = !!WX.spMchid;
|
|
214
232
|
const override = isSp ? { mchid: WX.spMchid, serial: WX.spSerial, keyPath: WX.spKeyPath } : undefined;
|
|
215
233
|
const r = await wxApiCall("GET", "/v3/certificates", null, override);
|
|
216
234
|
if (r.status !== 200 || !Array.isArray(r.json && r.json.data)) {
|
|
@@ -260,7 +278,8 @@ function wxVerifyNotifySignature(serial, timestamp, nonce, rawBody, signature) {
|
|
|
260
278
|
|
|
261
279
|
/** 回调资源解密 (SP 模式优先 sp APIv3 key) */
|
|
262
280
|
function wxDecryptNotifyResource(resource) {
|
|
263
|
-
|
|
281
|
+
// Lane C: 回调是发给服务商的, 必须用服务商 apiv3 key — 不能依赖全局 sub_mchid
|
|
282
|
+
const isSp = !!WX.spMchid;
|
|
264
283
|
const apiv3 = isSp && WX.spApiv3Key ? WX.spApiv3Key : WX.apiv3Key;
|
|
265
284
|
const plain = wxAesGcmDecrypt(apiv3, resource.nonce, resource.associated_data, resource.ciphertext);
|
|
266
285
|
return JSON.parse(plain.toString("utf8"));
|
|
@@ -624,7 +643,7 @@ async function runReconcileOnce(db) {
|
|
|
624
643
|
scanned = pend.length;
|
|
625
644
|
for (const row of pend) {
|
|
626
645
|
try {
|
|
627
|
-
const q = await wxQueryOrder(row.order_ref);
|
|
646
|
+
const q = await wxQueryOrder(row.order_ref, { subMchid: row.sub_mchid });
|
|
628
647
|
if (q.state === "SUCCESS") {
|
|
629
648
|
settlePayment(row, q.txnId || ("wxq-" + row.order_ref), "wechat_native", q.amount || row.amount_total, JSON.stringify(q.raw).slice(0, 2000));
|
|
630
649
|
settled++;
|
package/routes/buyer-shop.js
CHANGED
|
@@ -139,13 +139,14 @@ const RATE_CACHE_MS = 60 * 1000; // U7: 费率网关查询 60s 内存缓
|
|
|
139
139
|
function makeCommissionRateResolver(db, fetcher) {
|
|
140
140
|
const cache = new Map(); // sub_mchid → {bp, expiresAt}
|
|
141
141
|
const fetchFn = fetcher || globalThis.fetch;
|
|
142
|
-
return async function resolveRateBp() {
|
|
142
|
+
return async function resolveRateBp(subMchidArg) {
|
|
143
143
|
// ① env 显式覆盖 (最高优先, 不调网关不读表)
|
|
144
144
|
if (process.env.SP_COMMISSION_BP !== undefined && process.env.SP_COMMISSION_BP !== "") {
|
|
145
145
|
const v = Number(process.env.SP_COMMISSION_BP);
|
|
146
146
|
return Number.isInteger(v) && v >= 0 ? v : 0;
|
|
147
147
|
}
|
|
148
|
-
|
|
148
|
+
// Lane C (2026-10-03): 按传入的商户号解析; 未传才回落全局 env (多商户)
|
|
149
|
+
const subMchid = String(subMchidArg || process.env.WXPAY_SUB_MCHID || "");
|
|
149
150
|
if (!subMchid) return 0; // 直连模式不标记
|
|
150
151
|
const hit = cache.get(subMchid);
|
|
151
152
|
if (hit && Date.now() < hit.expiresAt) return hit.bp;
|
|
@@ -176,12 +177,48 @@ function makeCommissionRateResolver(db, fetcher) {
|
|
|
176
177
|
};
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
/** Lane C (2026-10-03): 按「商品卖家地址」解析特约商户号 —— 多商户的核心
|
|
181
|
+
* 数据源: sp_sub_merchants.seller_address → sub_mchid (进件时写入, 网关与商家机各一份)
|
|
182
|
+
* 兜底: 全局 env WXPAY_SUB_MCHID (单商户旧部署) —— **必须显式告警**, 不静默错分
|
|
183
|
+
* 返回 { subMchid, source: registry | env-fallback | none } */
|
|
184
|
+
function makeSubMchidResolver(db) {
|
|
185
|
+
const cache = new Map(); // seller_address → {subMchid, source}
|
|
186
|
+
return function resolveSubMchid(sellerAddress) {
|
|
187
|
+
const env = String(process.env.WXPAY_SUB_MCHID || "");
|
|
188
|
+
const key = String(sellerAddress || "");
|
|
189
|
+
if (!key) return { subMchid: env, source: env ? "env-fallback-no-seller" : "none" };
|
|
190
|
+
const hit = cache.get(key);
|
|
191
|
+
if (hit) return hit;
|
|
192
|
+
let out = { subMchid: env, source: env ? "env-fallback" : "none" };
|
|
193
|
+
try {
|
|
194
|
+
if (hasTable(db, "sp_sub_merchants")) {
|
|
195
|
+
const row = db.prepare(
|
|
196
|
+
"SELECT sub_mchid FROM sp_sub_merchants WHERE seller_address = ? AND status = 'active'"
|
|
197
|
+
).get(key);
|
|
198
|
+
if (row && row.sub_mchid) out = { subMchid: String(row.sub_mchid), source: "registry" };
|
|
199
|
+
}
|
|
200
|
+
} catch (_) { /* 表缺失/异常 → 走兜底 */ }
|
|
201
|
+
if (out.source !== "registry") {
|
|
202
|
+
console.warn(
|
|
203
|
+
`[buyer-shop] 卖家 ${key.slice(0, 14)}… 未在 sp_sub_merchants 登记 → ` +
|
|
204
|
+
(out.subMchid
|
|
205
|
+
? `回退全局 WXPAY_SUB_MCHID=${out.subMchid}(多商户场景可能分错商户!)`
|
|
206
|
+
: "无 sub_mchid(按直连模式,本单不分账)")
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
cache.set(key, out);
|
|
210
|
+
return out;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
179
214
|
function createBuyerShopRouter(deps) {
|
|
180
215
|
const router = express.Router();
|
|
181
216
|
const { db } = deps;
|
|
182
217
|
ensureBuyerTables(db);
|
|
183
218
|
const rateLimit = makeRateLimiter();
|
|
184
219
|
const resolveRateBp = makeCommissionRateResolver(db, deps.fetcher);
|
|
220
|
+
// Lane C: 按商品卖家解析 sub_mchid (多商户); 表缺失时回落 env 并告警
|
|
221
|
+
const resolveSubMchid = makeSubMchidResolver(db);
|
|
185
222
|
|
|
186
223
|
const MODE = process.env.AGENT_PAY_MODE || "mock"; // mock | wechat (同 agent-pay 口径)
|
|
187
224
|
// 测试可注入假 wxNativeOrder; 生产默认 agent-pay 真实现 (SP 模式其内部自动切换)
|
|
@@ -338,9 +375,13 @@ function createBuyerShopRouter(deps) {
|
|
|
338
375
|
if (exist) return res.json(orderToJson(exist));
|
|
339
376
|
}
|
|
340
377
|
|
|
378
|
+
// Lane C: 本单属于哪个特约商户 = 商品卖家的登记商户号 (不再是全局 env)
|
|
379
|
+
const sellerResolved = resolveSubMchid(product.seller_address);
|
|
380
|
+
const subMchid = sellerResolved.subMchid;
|
|
381
|
+
|
|
341
382
|
// U7: rate>0 且 SP 模式 → 微信下单带 settle_info.profit_sharing (不带标记的订单分账被拒)
|
|
342
|
-
const rateBp = await resolveRateBp();
|
|
343
|
-
const psFlag =
|
|
383
|
+
const rateBp = await resolveRateBp(subMchid);
|
|
384
|
+
const psFlag = subMchid && rateBp > 0 ? 1 : 0;
|
|
344
385
|
|
|
345
386
|
// 创建订单 (订单号 = order_ref, 对外 id)
|
|
346
387
|
const orderRef = "tb-" + crypto.randomBytes(8).toString("hex");
|
|
@@ -353,8 +394,11 @@ function createBuyerShopRouter(deps) {
|
|
|
353
394
|
codeUrl = `weixin://mock-pay/${orderRef}`;
|
|
354
395
|
} else {
|
|
355
396
|
try {
|
|
356
|
-
codeUrl = await wxNativeOrder(orderRef, String(product.title || productId), totalFen, exp,
|
|
357
|
-
|
|
397
|
+
codeUrl = await wxNativeOrder(orderRef, String(product.title || productId), totalFen, exp, {
|
|
398
|
+
subMchid,
|
|
399
|
+
subMchidSource: sellerResolved.source,
|
|
400
|
+
...(psFlag ? { settleInfo: { profit_sharing: true } } : {}),
|
|
401
|
+
});
|
|
358
402
|
} catch (e) {
|
|
359
403
|
console.error("[buyer-shop] wx order fail:", e && e.message);
|
|
360
404
|
return err("PAYMENT_UPSTREAM", 502, "支付通道暂时不可用,请稍后再试");
|
|
@@ -365,11 +409,11 @@ function createBuyerShopRouter(deps) {
|
|
|
365
409
|
// 买家面无账号体系, 订单 id 即凭据 — 与 pwa-shop-demo 轮询模型一致)
|
|
366
410
|
// U7: ps_flag 台账标注该单是否带分账标记 (运营台区分用)
|
|
367
411
|
db.prepare(`INSERT INTO agent_pay_sessions
|
|
368
|
-
(client_session_id, sku_id, qty, amount_total, currency, agent_addr, order_ref, pay_token_hash, status_token_hash, state, psp_code_url, callback_url, expires_at, created_at, updated_at, ps_flag)
|
|
369
|
-
VALUES (
|
|
412
|
+
(client_session_id, sku_id, qty, amount_total, currency, agent_addr, order_ref, pay_token_hash, status_token_hash, state, psp_code_url, callback_url, expires_at, created_at, updated_at, ps_flag, sub_mchid)
|
|
413
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
|
|
370
414
|
.run(clientSessionId, productId, qty, totalFen, "CNY", "buyer:" + buyerCard, orderRef,
|
|
371
415
|
crypto.randomBytes(32).toString("hex"), crypto.randomBytes(32).toString("hex"),
|
|
372
|
-
"CREATED", codeUrl, "", exp, now, now, psFlag);
|
|
416
|
+
"CREATED", codeUrl, "", exp, now, now, psFlag, subMchid);
|
|
373
417
|
console.log(`[buyer-shop] 下单 ${orderRef} product=${productId} qty=${qty} total=${totalFen}分 ps_flag=${psFlag}`);
|
|
374
418
|
const row = db.prepare("SELECT * FROM agent_pay_sessions WHERE order_ref = ?").get(orderRef);
|
|
375
419
|
res.json(orderToJson(row));
|
package/test-laneC.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lane C 多商户路由 单测 (2026-10-03 kimi)
|
|
3
|
+
* 验证: ① 按商品卖家解析 sub_mchid ② 兜底回退 env 且有告警语义 ③ 费率按商户号解析
|
|
4
|
+
* 跑法: node test-laneC.js
|
|
5
|
+
*/
|
|
6
|
+
const sqlite3 = require("better-sqlite3");
|
|
7
|
+
const assert = require("assert");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const os = require("os");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
const tmp = path.join(os.tmpdir(), "lanec-test-" + Date.now() + ".db");
|
|
13
|
+
const db = new sqlite3(tmp);
|
|
14
|
+
|
|
15
|
+
// 造最小 schema (与线上同结构)
|
|
16
|
+
db.exec(`
|
|
17
|
+
CREATE TABLE IF NOT EXISTS sp_sub_merchants (sub_mchid TEXT, seller_address TEXT, shop_name TEXT, webhook_url TEXT, status TEXT, created_at INTEGER);
|
|
18
|
+
CREATE TABLE IF NOT EXISTS sp_commission_rates (sub_mchid TEXT, rate_bp INTEGER);
|
|
19
|
+
CREATE TABLE IF NOT EXISTS store_products (id TEXT, seller_address TEXT, title TEXT, price_utct INTEGER, stock INTEGER, status TEXT);
|
|
20
|
+
`);
|
|
21
|
+
db.prepare("INSERT INTO sp_sub_merchants VALUES (?,?,?,?,?,?)").run("1118152327", "tct1v75dSellerA", "港思恒", "", "active", 1);
|
|
22
|
+
db.prepare("INSERT INTO sp_sub_merchants VALUES (?,?,?,?,?,?)").run("1900000109", "tct1v75dSellerB", "第二商户", "", "active", 1);
|
|
23
|
+
db.prepare("INSERT INTO sp_commission_rates VALUES (?,?)").run("1900000109", 100); // 第二商户 1%
|
|
24
|
+
|
|
25
|
+
process.env.WXPAY_SUB_MCHID = "1118152327"; // 全局默认(单商户旧部署)
|
|
26
|
+
delete process.env.SP_COMMISSION_BP; // 走解析路径
|
|
27
|
+
|
|
28
|
+
// 载入被测函数 (从 buyer-shop.js 里抠出来, 避免 require 整个应用)
|
|
29
|
+
const src = fs.readFileSync(path.join(__dirname, "routes", "buyer-shop.js"), "utf8");
|
|
30
|
+
function extract(name) {
|
|
31
|
+
const i = src.indexOf("function " + name);
|
|
32
|
+
if (i < 0) throw new Error("找不到函数 " + name);
|
|
33
|
+
// 找到函数体结束
|
|
34
|
+
let depth = 0, started = false, j = i;
|
|
35
|
+
for (; j < src.length; j++) {
|
|
36
|
+
if (src[j] === "{") { depth++; started = true; }
|
|
37
|
+
else if (src[j] === "}") { depth--; if (started && depth === 0) { j++; break; } }
|
|
38
|
+
}
|
|
39
|
+
return src.slice(i, j);
|
|
40
|
+
}
|
|
41
|
+
global.hasTable = eval("(" + extract("hasTable") + ")");
|
|
42
|
+
const makeSubMchidResolver = eval("(" + extract("makeSubMchidResolver") + ")");
|
|
43
|
+
const RATE_CACHE_MS = 60 * 1000; // 与 buyer-shop.js 同值
|
|
44
|
+
const makeCommissionRateResolver = eval("(" + extract("makeCommissionRateResolver") + ")");
|
|
45
|
+
|
|
46
|
+
const resolveSubMchid = makeSubMchidResolver(db);
|
|
47
|
+
const resolveRateBp = makeCommissionRateResolver(db, null);
|
|
48
|
+
|
|
49
|
+
let pass = 0, fail = 0;
|
|
50
|
+
function check(name, got, want) {
|
|
51
|
+
const okv = JSON.stringify(got) === JSON.stringify(want);
|
|
52
|
+
console.log((okv ? " ✔ " : " ✘ ") + name + (okv ? "" : ` got=${JSON.stringify(got)} want=${JSON.stringify(want)}`));
|
|
53
|
+
okv ? pass++ : fail++;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log("Lane C 多商户路由单测:");
|
|
57
|
+
|
|
58
|
+
// ① 已登记商户 → 走登记表
|
|
59
|
+
check("港思恒(1118152327) 解析", resolveSubMchid("tct1v75dSellerA").subMchid, "1118152327");
|
|
60
|
+
check("第二商户(1900000109) 解析", resolveSubMchid("tct1v75dSellerB").subMchid, "1900000109");
|
|
61
|
+
check("第二商户 source=registry", resolveSubMchid("tct1v75dSellerB").source, "registry");
|
|
62
|
+
|
|
63
|
+
// ② 未登记卖家 → 回落 env + source 标记(不静默)
|
|
64
|
+
const r3 = resolveSubMchid("tct1unknown");
|
|
65
|
+
check("未登记卖家回落 env", r3.subMchid, "1118152327");
|
|
66
|
+
check("未登记卖家 source=env-fallback", r3.source, "env-fallback");
|
|
67
|
+
|
|
68
|
+
// ③ 无卖家地址 → 也不静默
|
|
69
|
+
check("无 seller 回落 env", resolveSubMchid("").source, "env-fallback-no-seller");
|
|
70
|
+
|
|
71
|
+
// ④ 费率按传入商户号解析 (第二商户 1% = 100bp; 全局默认未登记 → 0)
|
|
72
|
+
resolveRateBp.cache = null;
|
|
73
|
+
(async () => {
|
|
74
|
+
const bp2 = await resolveRateBp("1900000109");
|
|
75
|
+
check("费率按第二商户解析 (1%=100bp)", bp2, 100);
|
|
76
|
+
const bp1 = await resolveRateBp("1118152327");
|
|
77
|
+
check("费率按港思恒解析 (未登记→0)", bp1, 0);
|
|
78
|
+
// 不传参 → 回落 env 商户号
|
|
79
|
+
const bp0 = await resolveRateBp();
|
|
80
|
+
check("费率不传参回落 env", bp0, 0);
|
|
81
|
+
|
|
82
|
+
console.log(`\n结果: ${pass} 通过, ${fail} 失败`);
|
|
83
|
+
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
84
|
+
process.exit(fail ? 1 : 0);
|
|
85
|
+
})();
|