trustbase-seller-backend 0.1.11 → 0.2.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/core/chain-signer.js +15 -5
- package/oracle/c5-glue.js +60 -3
- package/package.json +1 -1
- package/payments/wechat/profitsharing.js +2 -0
- package/proto/trustchain/reward/v1/params.proto +44 -0
- package/proto/trustchain/reward/v1/tx.proto +62 -0
- package/routes/chain-writes.js +4 -2
- package/routes/onboard.html +186 -16
- package/routes/onboard.js +78 -25
- package/routes/sp-applyment.js +63 -22
- package/run-unified.js +11 -0
- package/test-chain-signer.js +61 -0
- package/test-onboard-applyment.js +492 -278
- package/test-oracle-glue.js +69 -0
- package/test-oracle-pool-refill.js +8 -0
- package/test-sp-applyment.js +28 -6
- package/test-sp-gateway.js +38 -0
package/core/chain-signer.js
CHANGED
|
@@ -47,6 +47,8 @@ const CUSTOM_TYPE_URLS = [
|
|
|
47
47
|
"/trustchain.economy.v1.MsgGrantCredits",
|
|
48
48
|
// N13 (2026-09-23): trustcoin 账户自助注册 (上架/下单前置; signer=creator, 商户本人签)
|
|
49
49
|
"/trustchain.trustcoin.v1.MsgRegisterAccount",
|
|
50
|
+
// N17 (2026-10): 引导金池自动回补 (oracle c5-glue stepPoolRefill, authority 签)
|
|
51
|
+
"/trustchain.reward.v1.MsgRewardPoolTransfer",
|
|
50
52
|
];
|
|
51
53
|
|
|
52
54
|
let _root = null;
|
|
@@ -68,6 +70,8 @@ function protoRoot() {
|
|
|
68
70
|
"trustchain/economy/v1/tx.proto",
|
|
69
71
|
// N13 (2026-09-23): trustcoin 账户注册
|
|
70
72
|
"trustchain/trustcoin/v1/tx.proto",
|
|
73
|
+
// N17 (2026-10): reward 池划转 (MsgRewardPoolTransfer)
|
|
74
|
+
"trustchain/reward/v1/tx.proto",
|
|
71
75
|
], { keepCase: true });
|
|
72
76
|
_root = root;
|
|
73
77
|
return root;
|
|
@@ -192,7 +196,12 @@ async function pollFinalTx(lcdUrl, txhash) {
|
|
|
192
196
|
return null;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
|
|
199
|
+
/** P4: sequence mismatch 统一抛 routes/chain-writes 的 SequenceMismatchError —
|
|
200
|
+
* c5-glue/sp-notify/chain-writes 三处 "重查序号重试一次" 的 instanceof 判断对 JS 通道同样生效。
|
|
201
|
+
* 延迟 require 规避循环依赖 (chain-writes.js 顶部即 require 本模块, 加载期取不到其 class)。 */
|
|
202
|
+
function newSequenceMismatchError(msg) {
|
|
203
|
+
return new (require("../routes/chain-writes").SequenceMismatchError)(msg);
|
|
204
|
+
}
|
|
196
205
|
|
|
197
206
|
/**
|
|
198
207
|
* JS 直签 + 广播完整流程。入参/返回与 chain-writes.js 的 CLI 路径对齐:
|
|
@@ -201,7 +210,8 @@ class SequenceMismatchErrorJs extends Error {}
|
|
|
201
210
|
* account: {accountNumber, sequence} (字符串)
|
|
202
211
|
* 返回 {txhash, code, raw_log} (code!=0 也返回, 由调用方判 502)
|
|
203
212
|
* memo: 可选; 下单 (MsgCreateOrder) 走 "tb-auth:<sha256>" 意愿摘要, 其余写路径留空
|
|
204
|
-
* 抛
|
|
213
|
+
* 抛 chain-writes.SequenceMismatchError (message 含 "account sequence mismatch") — P4 统一后
|
|
214
|
+
* 调用方 instanceof chainWrites.SequenceMismatchError 的重试逻辑对 JS 通道同样生效
|
|
205
215
|
* 抛 Error("tx_delivery_timeout: ...") 当 15s 未入块
|
|
206
216
|
*/
|
|
207
217
|
async function jsSignAndBroadcast(c, messages, account, baseDir, memo) {
|
|
@@ -228,7 +238,7 @@ async function jsSignAndBroadcast(c, messages, account, baseDir, memo) {
|
|
|
228
238
|
const txBytes = TxRaw.encode(txRaw).finish();
|
|
229
239
|
const checkTx = await broadcastTxSync(c.rpcUrl, txBytes);
|
|
230
240
|
if (/account sequence mismatch/i.test(checkTx.raw_log)) {
|
|
231
|
-
throw
|
|
241
|
+
throw newSequenceMismatchError(`account sequence mismatch: ${checkTx.raw_log}`);
|
|
232
242
|
}
|
|
233
243
|
if (checkTx.code !== 0) {
|
|
234
244
|
// CheckTx 拒绝 (码/ccodespace/log 原样回传, 语义同 CLI broadcast 的 code!=0)
|
|
@@ -237,7 +247,7 @@ async function jsSignAndBroadcast(c, messages, account, baseDir, memo) {
|
|
|
237
247
|
const final = await pollFinalTx(c.lcdUrl, checkTx.txhash);
|
|
238
248
|
if (final) {
|
|
239
249
|
if (/account sequence mismatch/i.test(final.raw_log)) {
|
|
240
|
-
throw
|
|
250
|
+
throw newSequenceMismatchError(`account sequence mismatch: ${final.raw_log}`);
|
|
241
251
|
}
|
|
242
252
|
return final;
|
|
243
253
|
}
|
|
@@ -254,5 +264,5 @@ module.exports = {
|
|
|
254
264
|
jsSignerAddress,
|
|
255
265
|
jsSignAndBroadcast,
|
|
256
266
|
toEncodeObjects,
|
|
257
|
-
|
|
267
|
+
newSequenceMismatchError,
|
|
258
268
|
};
|
package/oracle/c5-glue.js
CHANGED
|
@@ -80,6 +80,9 @@ const SIGNER_FORBIDDEN_RE = /intended signer does not match|signature verificati
|
|
|
80
80
|
// N14: settle 幂等 (已结算/状态已变/订单不存在 → 按完成处理, 不再重试)
|
|
81
81
|
const SETTLE_DONE_RE = /already settled|not in confirmed status|order not found/i;
|
|
82
82
|
|
|
83
|
+
// E6 P2: 链上 reason_kind+ref_id 幂等命中 (上轮 tx 实际已入块但本地未记 done, 如交付超时后重发被链拒)
|
|
84
|
+
const GRANT_DUP_RE = /already granted|duplicate|grant already exists/i;
|
|
85
|
+
|
|
83
86
|
function sha256Hex(s) {
|
|
84
87
|
return crypto.createHash("sha256").update(String(s)).digest("hex");
|
|
85
88
|
}
|
|
@@ -566,8 +569,21 @@ function createOracleGlue(deps) {
|
|
|
566
569
|
amount: String(g.amount),
|
|
567
570
|
reason_kind: reasonKind,
|
|
568
571
|
ref_id: g.refId,
|
|
569
|
-
}])
|
|
572
|
+
}]).catch((e) => {
|
|
573
|
+
// sendTx 异常 (链抖动/超时): 留 pending 等下轮 retryPendingGrants 补发,
|
|
574
|
+
// 不上抛 — 避免中断本批后续 grant (此前异常会丢失同批剩余 grant 的台账行)
|
|
575
|
+
warn(`[oracle-glue] E6 发放异常 ${reasonKind}/${g.refId}: ${e.message} (留 pending 下轮重试)`);
|
|
576
|
+
return null;
|
|
577
|
+
});
|
|
578
|
+
if (!r) continue;
|
|
570
579
|
if (r.code !== 0) {
|
|
580
|
+
if (GRANT_DUP_RE.test(r.raw_log)) {
|
|
581
|
+
// 链上幂等命中: 该笔实际已发放 (本地没落 done), 按完成处理
|
|
582
|
+
db.prepare("UPDATE oracle_credit_grants SET status='done', note=?, updated_at=? WHERE reason_kind=? AND ref_id=?")
|
|
583
|
+
.run(`链上幂等命中: ${String(r.raw_log).slice(0, 160)}`, nowSec(), reasonKind, g.refId);
|
|
584
|
+
log(`[oracle-glue] E6 ${reasonKind}/${g.refId} 链上已发放 (幂等命中), 按完成处理`);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
571
587
|
const status = MODULE_NOT_LIVE_RE.test(r.raw_log) ? "pending" : "failed";
|
|
572
588
|
db.prepare("UPDATE oracle_credit_grants SET status=?, note=?, updated_at=? WHERE reason_kind=? AND ref_id=?")
|
|
573
589
|
.run(status, `code=${r.code}: ${String(r.raw_log).slice(0, 200)}`, nowSec(), reasonKind, g.refId);
|
|
@@ -580,8 +596,49 @@ function createOracleGlue(deps) {
|
|
|
580
596
|
}
|
|
581
597
|
}
|
|
582
598
|
|
|
583
|
-
/**
|
|
599
|
+
/** P2: 存量 pending 行补发 (上轮 economy 未上线/sendTx 异常遗留 — 此前永不再试)。
|
|
600
|
+
* 每轮结算开头先扫台账重发; 链上 reason_kind+ref_id 幂等是第二道保险:
|
|
601
|
+
* 重复发放被链拒 (GRANT_DUP_RE) 按完成处理。返回本轮处理的 pending 行数。 */
|
|
602
|
+
async function retryPendingGrants(oracleAddr) {
|
|
603
|
+
const rows = db.prepare("SELECT * FROM oracle_credit_grants WHERE status='pending'").all();
|
|
604
|
+
for (const row of rows) {
|
|
605
|
+
let r;
|
|
606
|
+
try {
|
|
607
|
+
r = await sendTx([{
|
|
608
|
+
"@type": "/trustchain.economy.v1.MsgGrantCredits",
|
|
609
|
+
issuer: oracleAddr,
|
|
610
|
+
recipient: row.recipient,
|
|
611
|
+
amount: String(row.amount),
|
|
612
|
+
reason_kind: row.reason_kind,
|
|
613
|
+
ref_id: row.ref_id,
|
|
614
|
+
}]);
|
|
615
|
+
} catch (e) {
|
|
616
|
+
warn(`[oracle-glue] E6 pending 补发 ${row.reason_kind}/${row.ref_id} 异常: ${e.message} (留 pending 下轮重试)`);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (r.code !== 0) {
|
|
620
|
+
if (GRANT_DUP_RE.test(r.raw_log)) {
|
|
621
|
+
db.prepare("UPDATE oracle_credit_grants SET status='done', note=?, updated_at=? WHERE reason_kind=? AND ref_id=?")
|
|
622
|
+
.run(`链上幂等命中: ${String(r.raw_log).slice(0, 160)}`, nowSec(), row.reason_kind, row.ref_id);
|
|
623
|
+
log(`[oracle-glue] E6 pending 补发 ${row.reason_kind}/${row.ref_id} 链上已发放 (幂等命中), 按完成处理`);
|
|
624
|
+
} else if (!MODULE_NOT_LIVE_RE.test(r.raw_log)) {
|
|
625
|
+
db.prepare("UPDATE oracle_credit_grants SET status='failed', note=?, updated_at=? WHERE reason_kind=? AND ref_id=?")
|
|
626
|
+
.run(`补发 code=${r.code}: ${String(r.raw_log).slice(0, 200)}`, nowSec(), row.reason_kind, row.ref_id);
|
|
627
|
+
warn(`[oracle-glue] E6 pending 补发失败 ${row.reason_kind}/${row.ref_id} code=${r.code}: ${String(r.raw_log).slice(0, 200)}`);
|
|
628
|
+
}
|
|
629
|
+
// 模块未上线 → 留 pending 下轮再试
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
db.prepare("UPDATE oracle_credit_grants SET status='done', txhash=?, updated_at=? WHERE reason_kind=? AND ref_id=?")
|
|
633
|
+
.run(r.txhash, nowSec(), row.reason_kind, row.ref_id);
|
|
634
|
+
log(`[oracle-glue] E6 pending 已补发 ${row.reason_kind}/${row.ref_id} → ${row.recipient} +${row.amount} (tx=${r.txhash})`);
|
|
635
|
+
}
|
|
636
|
+
return rows.length;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** E6 一轮: 先补发存量 pending (P2) → 拉 CONFIRMED 事实 → 逐笔结算 */
|
|
584
640
|
async function settleConfirmedFacts(oracleAddr) {
|
|
641
|
+
await retryPendingGrants(oracleAddr); // 不依赖链上 params, 先清存量
|
|
585
642
|
const params = await getEconomyParams();
|
|
586
643
|
if (!params) { warn("[oracle-glue] economy params 查询失败 (模块未上线?), 本轮 E6 跳过"); return; }
|
|
587
644
|
const pj = await lcdGet("/oxiaom/trustchain/serviceprovider/v1/providers");
|
|
@@ -732,7 +789,7 @@ function createOracleGlue(deps) {
|
|
|
732
789
|
await settleExpiredOrders(oracleAddr);
|
|
733
790
|
}
|
|
734
791
|
|
|
735
|
-
return { tick, enqueueBootstrapGrant, settleConfirmedFacts, settleExpiredOrders, stepBootstrapGrant, getEconomyParams, monthlyUsed, resolveOrderParties, settleFact, grantFeegrant, grantFeegrantFor, findFeegrant, stepPoolRefill, refillEnabled, poolBalanceUtct, refillWindow };
|
|
792
|
+
return { tick, enqueueBootstrapGrant, settleConfirmedFacts, settleExpiredOrders, stepBootstrapGrant, getEconomyParams, monthlyUsed, resolveOrderParties, settleFact, retryPendingGrants, grantFeegrant, grantFeegrantFor, findFeegrant, stepPoolRefill, refillEnabled, poolBalanceUtct, refillWindow };
|
|
736
793
|
}
|
|
737
794
|
|
|
738
795
|
module.exports = { createOracleGlue, oracleCfg, sha256Hex, MODULE_NOT_LIVE_RE };
|
package/package.json
CHANGED
|
@@ -49,6 +49,8 @@ async function wxCall(method, urlPath, bodyObj) {
|
|
|
49
49
|
"Authorization": agentPay.wxSignAuth(method, urlPath, bodyStr, spOverride(c)),
|
|
50
50
|
"Content-Type": "application/json",
|
|
51
51
|
"Accept": "application/json",
|
|
52
|
+
// 微信 APIv3 拒 undici 默认 Accept-Language: * (2026-09-25 实测 PARAM_ERROR) — 与 agent-pay wxApiCall 对齐
|
|
53
|
+
"Accept-Language": "zh-CN",
|
|
52
54
|
"User-Agent": "trustbase-seller-backend/1.0",
|
|
53
55
|
},
|
|
54
56
|
body: method === "GET" ? undefined : bodyStr,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
package trustchain.reward.v1;
|
|
3
|
+
|
|
4
|
+
import "amino/amino.proto";
|
|
5
|
+
import "gogoproto/gogo.proto";
|
|
6
|
+
|
|
7
|
+
option go_package = "github.com/oxiaom/trustchain/x/reward/types";
|
|
8
|
+
|
|
9
|
+
message Params {
|
|
10
|
+
option (amino.name) = "trustchain/x/reward/Params";
|
|
11
|
+
option (gogoproto.equal) = true;
|
|
12
|
+
|
|
13
|
+
// reward per purchase (microTCT)
|
|
14
|
+
string purchase_reward = 1;
|
|
15
|
+
|
|
16
|
+
// reward per positive review (microTCT)
|
|
17
|
+
string review_reward = 2;
|
|
18
|
+
|
|
19
|
+
// reward per invitation (microTCT)
|
|
20
|
+
string invite_reward = 3;
|
|
21
|
+
|
|
22
|
+
// daily reward cap per user (microTCT)
|
|
23
|
+
string daily_cap = 4;
|
|
24
|
+
|
|
25
|
+
// registration reward, locked until verified (microTCT) — v0.20.0
|
|
26
|
+
string registration_reward = 5;
|
|
27
|
+
|
|
28
|
+
// invite reward when invitee role=buyer/both activates (microTCT) — v0.20.0 (PRD §4.9 default 5 TCT)
|
|
29
|
+
string invite_buyer_reward = 6;
|
|
30
|
+
|
|
31
|
+
// invite reward when invitee role=seller activates (microTCT) — v0.20.0 (PRD §4.9 default 10 TCT)
|
|
32
|
+
string invite_seller_reward = 7;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// RewardEvent tracks a single reward distribution.
|
|
36
|
+
message RewardEvent {
|
|
37
|
+
option (amino.name) = "trustchain/x/reward/RewardEvent";
|
|
38
|
+
|
|
39
|
+
string recipient = 1;
|
|
40
|
+
string amount = 2; // microTCT
|
|
41
|
+
string reward_type = 3; // "purchase", "review", "invite", "listing"
|
|
42
|
+
uint64 reference_id = 4; // order_id or invite_id
|
|
43
|
+
uint64 created_at = 5;
|
|
44
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
package trustchain.reward.v1;
|
|
3
|
+
|
|
4
|
+
import "amino/amino.proto";
|
|
5
|
+
import "cosmos/msg/v1/msg.proto";
|
|
6
|
+
import "gogoproto/gogo.proto";
|
|
7
|
+
import "trustchain/reward/v1/params.proto";
|
|
8
|
+
|
|
9
|
+
option go_package = "github.com/oxiaom/trustchain/x/reward/types";
|
|
10
|
+
|
|
11
|
+
// Msg defines the Msg service.
|
|
12
|
+
service Msg {
|
|
13
|
+
option (cosmos.msg.v1.service) = true;
|
|
14
|
+
|
|
15
|
+
// UpdateParams defines a (governance) operation for updating the module
|
|
16
|
+
// parameters. The authority defaults to the x/gov module account.
|
|
17
|
+
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);
|
|
18
|
+
|
|
19
|
+
// RewardPoolTransfer — 奖励池定向划转 (authority/emergency 双轨, 池间调拨用)
|
|
20
|
+
rpc RewardPoolTransfer(MsgRewardPoolTransfer) returns (MsgRewardPoolTransferResponse);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// MsgRewardPoolTransfer 从 x/reward 奖励池向目标地址划转 TCT (池间调拨, 如: 奖励池 → 引导金池)
|
|
24
|
+
message MsgRewardPoolTransfer {
|
|
25
|
+
option (cosmos.msg.v1.signer) = "authority";
|
|
26
|
+
option (amino.name) = "trustchain/x/reward/MsgRewardPoolTransfer";
|
|
27
|
+
|
|
28
|
+
// authority 是划转签名人 (gov 模块地址或应急密钥, 双轨)
|
|
29
|
+
string authority = 1;
|
|
30
|
+
|
|
31
|
+
// recipient 是收款地址 (普通账户或目标模块账户, 如 subchain 引导金池)
|
|
32
|
+
string recipient = 2;
|
|
33
|
+
|
|
34
|
+
// amount 是划转金额 (utct)
|
|
35
|
+
string amount = 3;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
message MsgRewardPoolTransferResponse {
|
|
39
|
+
// amount 回显实际划转金额 (utct)
|
|
40
|
+
string amount = 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// MsgUpdateParams is the Msg/UpdateParams request type.
|
|
44
|
+
message MsgUpdateParams {
|
|
45
|
+
option (cosmos.msg.v1.signer) = "authority";
|
|
46
|
+
option (amino.name) = "trustchain/x/reward/MsgUpdateParams";
|
|
47
|
+
|
|
48
|
+
// authority is the address that controls the module (defaults to x/gov unless overwritten).
|
|
49
|
+
string authority = 1;
|
|
50
|
+
|
|
51
|
+
// params defines the module parameters to update.
|
|
52
|
+
//
|
|
53
|
+
// NOTE: All parameters must be supplied.
|
|
54
|
+
Params params = 2 [
|
|
55
|
+
(gogoproto.nullable) = false,
|
|
56
|
+
(amino.dont_omitempty) = true
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// MsgUpdateParamsResponse defines the response structure for executing a
|
|
61
|
+
// MsgUpdateParams message.
|
|
62
|
+
message MsgUpdateParamsResponse {}
|
package/routes/chain-writes.js
CHANGED
|
@@ -89,10 +89,12 @@ const EXEC_TIMEOUT_MS = 30_000;
|
|
|
89
89
|
class HttpError extends Error {
|
|
90
90
|
status;
|
|
91
91
|
code;
|
|
92
|
-
|
|
92
|
+
field;
|
|
93
|
+
constructor(status, code, message, field) {
|
|
93
94
|
super(message);
|
|
94
95
|
this.status = status;
|
|
95
96
|
this.code = code;
|
|
97
|
+
this.field = field; // 可选: 出错字段名 (前端定位高亮用, 2026-10-03 进件表单错误提示)
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
exports.HttpError = HttpError;
|
|
@@ -650,7 +652,7 @@ function createChainWritesRouter(deps) {
|
|
|
650
652
|
}
|
|
651
653
|
function handleError(res, e, tag) {
|
|
652
654
|
if (e instanceof HttpError) {
|
|
653
|
-
return res.status(e.status).json({ error: e.code, message: e.message });
|
|
655
|
+
return res.status(e.status).json({ error: e.code, message: e.message, ...(e.field ? { field: e.field } : {}) });
|
|
654
656
|
}
|
|
655
657
|
console.error(`[chain-writes] ${tag} 失败:`, e?.message ?? e);
|
|
656
658
|
return res.status(502).json({ error: 'chain_write_error', message: e?.message || String(e) });
|
package/routes/onboard.html
CHANGED
|
@@ -85,9 +85,23 @@ img.auprev{display:none;width:100%;max-height:140px;object-fit:cover;border-radi
|
|
|
85
85
|
#apply-status[data-state="pending"]{background:#FFF7ED;border-color:#FED7AA}
|
|
86
86
|
#apply-status a{display:inline-block;background:#C4633C;color:#fff !important;border-radius:8px;padding:10px 18px;font-size:15px;font-weight:600;text-decoration:none;margin-top:8px}
|
|
87
87
|
#apply-status .aqr{display:flex;align-items:center;justify-content:center;width:120px;height:120px;border:2px dashed #93C5FD;border-radius:10px;background:#fff;color:#8A857B;font-size:12px;text-align:center;margin:10px 0 2px;line-height:1.6}
|
|
88
|
+
/* 经营场景选择卡 (2026-10-03 三场景) */
|
|
89
|
+
#apply-form .ascene-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
|
|
90
|
+
#apply-form .ascene{display:flex;flex-direction:column;align-items:center;gap:2px;border:2px solid #EAE3D6;border-radius:12px;background:#FFFDF9;padding:14px 8px;cursor:pointer;text-align:center;transition:border-color .15s,background .15s}
|
|
91
|
+
#apply-form .ascene input{display:none}
|
|
92
|
+
#apply-form .ascene .ascene-ico{font-size:24px;line-height:1}
|
|
93
|
+
#apply-form .ascene .ascene-name{font-size:14px;font-weight:600;color:#33302B}
|
|
94
|
+
#apply-form .ascene .ascene-sub{font-size:11px;color:#8A857B;line-height:1.5}
|
|
95
|
+
#apply-form .ascene.selected{border-color:#C4633C;background:#FBF1EA;box-shadow:0 0 0 3px rgba(196,99,60,.12)}
|
|
96
|
+
#apply-form .ahint{font-size:12px;color:#B45309;background:#FFFBEB;border:1px solid #FDE68A;border-radius:8px;padding:8px 10px;margin-top:10px}
|
|
97
|
+
/* 错误提示红卡 + 字段高亮 (2026-10-03 xiaomu 实测 "没反应" fix) */
|
|
98
|
+
#apply-err{background:#FEF2F2;border:1px solid #FCA5A5;border-left:4px solid #DC2626;border-radius:10px;padding:12px 14px;font-size:14px;color:#991B1B;margin:4px 0 12px;line-height:1.8;word-break:break-all}
|
|
99
|
+
#apply-form .aerr-field,#apply-form input.aerr-field,#apply-form select.aerr-field{border-color:#DC2626 !important;box-shadow:0 0 0 3px rgba(220,38,38,.18) !important}
|
|
100
|
+
#apply-form div[id^="a_up_"].aerr-field label.sbtn{border-color:#DC2626;background:#FEF2F2}
|
|
88
101
|
@media (max-width:640px){
|
|
89
102
|
#apply-form .agrid{grid-template-columns:1fr}
|
|
90
103
|
#apply-form .auploads{grid-template-columns:1fr}
|
|
104
|
+
#apply-form .ascene-row{grid-template-columns:1fr}
|
|
91
105
|
}
|
|
92
106
|
@media print{
|
|
93
107
|
body *{visibility:hidden}
|
|
@@ -128,6 +142,18 @@ img.auprev{display:none;width:100%;max-height:140px;object-fit:cover;border-radi
|
|
|
128
142
|
<div id="apply-form" style="display:none;margin-top:12px">
|
|
129
143
|
<div class="desc" style="margin-bottom:12px">以下资料由你的电脑<b>本地加密</b>后交给服务商,向微信申请特约商户收款账户。身份证号/银行卡号在你电脑上就已加密,任何人 (含服务商) 都看不到明文。</div>
|
|
130
144
|
|
|
145
|
+
<div class="acard">
|
|
146
|
+
<div class="acard-h">经营场景 <span class="aopt">三选一, 决定第 4/5 张卡片要什么资料</span></div>
|
|
147
|
+
<div class="ascene-row">
|
|
148
|
+
<label class="ascene selected"><input type="radio" name="sales_scene" value="SALES_SCENES_STORE" checked onchange="setScene(this.value)">
|
|
149
|
+
<span class="ascene-ico">🏬</span><span class="ascene-name">线下门店</span><span class="ascene-sub">门店地址 + 门头/店内照片</span></label>
|
|
150
|
+
<label class="ascene"><input type="radio" name="sales_scene" value="SALES_SCENES_WEB" onchange="setScene(this.value)">
|
|
151
|
+
<span class="ascene-ico">🌐</span><span class="ascene-name">互联网网站</span><span class="ascene-sub">网址 + 截图, 需 ICP 备案</span></label>
|
|
152
|
+
<label class="ascene"><input type="radio" name="sales_scene" value="SALES_SCENES_MP" onchange="setScene(this.value)">
|
|
153
|
+
<span class="ascene-ico">📱</span><span class="ascene-name">公众号/小程序</span><span class="ascene-sub">AppID + 页面截图</span></label>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
131
157
|
<div class="acard">
|
|
132
158
|
<div class="acard-h"><span class="anum">1</span>主体资料</div>
|
|
133
159
|
<div class="agrid">
|
|
@@ -152,6 +178,10 @@ img.auprev{display:none;width:100%;max-height:140px;object-fit:cover;border-radi
|
|
|
152
178
|
<div class="agrid">
|
|
153
179
|
<div class="afield"><label for="a_legal_name">法人/经营者姓名</label><input id="a_legal_name" placeholder="与身份证一致"></div>
|
|
154
180
|
<div class="afield"><label for="a_legal_id_number">法人身份证号</label><input id="a_legal_id_number" placeholder="17 位数字 + 数字或 X"></div>
|
|
181
|
+
<div class="afield afield-full" id="afield_idaddr" style="display:none"><label for="a_legal_id_address">身份证居住地址 <span class="aopt">企业主体必填, 本机加密</span></label><input id="a_legal_id_address" placeholder="与身份证住址一致, 如: 广东省深圳市南山区xx路xx号"></div>
|
|
182
|
+
<div class="afield afield-full" id="afield_ubo_note" style="display:none">
|
|
183
|
+
<div class="ahint" style="margin-top:0">企业主体:若最终受益人只有法定代表人本人,无需填写受益人信息 (微信自动回填);否则暂不支持,请联系服务商协助进件</div>
|
|
184
|
+
</div>
|
|
155
185
|
<div class="afield"><label for="a_period_begin">身份证有效期开始</label><input id="a_period_begin" placeholder="YYYY-MM-DD"></div>
|
|
156
186
|
<div class="afield"><label for="a_period_end">有效期结束</label><input id="a_period_end" placeholder="YYYY-MM-DD 或 长期"></div>
|
|
157
187
|
<div class="afield"><label for="a_contact_name">超级管理员姓名</label><input id="a_contact_name" placeholder="与微信实名一致"></div>
|
|
@@ -190,8 +220,8 @@ img.auprev{display:none;width:100%;max-height:140px;object-fit:cover;border-radi
|
|
|
190
220
|
</div>
|
|
191
221
|
|
|
192
222
|
<div class="acard">
|
|
193
|
-
<div class="acard-h"><span class="anum">4</span>门店信息</div>
|
|
194
|
-
<div class="agrid">
|
|
223
|
+
<div class="acard-h"><span class="anum">4</span><span id="acard4-title">门店信息</span></div>
|
|
224
|
+
<div class="agrid" id="scene_fields_store">
|
|
195
225
|
<div class="afield"><label for="a_store_name">门店名称</label><input id="a_store_name" placeholder="线下场所名称"></div>
|
|
196
226
|
<div class="afield"><label for="a_store_address_code">门店省市编码</label>
|
|
197
227
|
<select id="a_store_address_code" data-citycodes>
|
|
@@ -201,6 +231,20 @@ img.auprev{display:none;width:100%;max-height:140px;object-fit:cover;border-radi
|
|
|
201
231
|
<input id="a_storecode_manual" class="amanual" placeholder="6 位国标区划码, 如: 320200" style="display:none"></div>
|
|
202
232
|
<div class="afield afield-full"><label for="a_store_address">门店详细地址</label><input id="a_store_address" placeholder="xx 路 xx 号"></div>
|
|
203
233
|
</div>
|
|
234
|
+
<div id="scene_fields_web" style="display:none">
|
|
235
|
+
<div class="agrid">
|
|
236
|
+
<div class="afield afield-full"><label for="a_website_url">网站网址</label><input id="a_website_url" placeholder="https:// 开头, 需已上线可访问"></div>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="ahint">⚠️ 网站需已上线且有 ICP 备案, 微信审核会实际访问验证</div>
|
|
239
|
+
</div>
|
|
240
|
+
<div id="scene_fields_mp" style="display:none">
|
|
241
|
+
<div class="agrid">
|
|
242
|
+
<div class="afield"><label for="a_appid">公众号/小程序 AppID</label><input id="a_appid" placeholder="wx 开头"></div>
|
|
243
|
+
<div class="afield"><label for="a_appid_type">类型</label>
|
|
244
|
+
<select id="a_appid_type"><option value="mp">公众号</option><option value="mini">小程序</option></select></div>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="ahint">⚠️ 需已认证的公众号或小程序, 且主体与本商户一致</div>
|
|
247
|
+
</div>
|
|
204
248
|
</div>
|
|
205
249
|
|
|
206
250
|
<div class="acard">
|
|
@@ -209,11 +253,12 @@ img.auprev{display:none;width:100%;max-height:140px;object-fit:cover;border-radi
|
|
|
209
253
|
<div id="a_up_license"></div>
|
|
210
254
|
<div id="a_up_idfront"></div>
|
|
211
255
|
<div id="a_up_idback"></div>
|
|
212
|
-
<div id="
|
|
213
|
-
<div id="
|
|
256
|
+
<div id="a_up_s1"></div>
|
|
257
|
+
<div id="a_up_s2"></div>
|
|
214
258
|
</div>
|
|
215
259
|
</div>
|
|
216
260
|
|
|
261
|
+
<div id="apply-err" style="display:none"></div>
|
|
217
262
|
<button class="abtn" id="btn-apply-submit" onclick="submitApplyment()">加密并提交进件</button>
|
|
218
263
|
<div class="aprivacy">🔒 资料只保存在这台电脑; 敏感字段本机加密后交服务商; 服务商暂时不可达时加密暂存本机, 可重试。</div>
|
|
219
264
|
</div>
|
|
@@ -408,11 +453,36 @@ function mountApplyUpload(slotId,field,label){
|
|
|
408
453
|
mountApplyUpload('a_up_license','license_media_id','营业执照照片 (必传)');
|
|
409
454
|
mountApplyUpload('a_up_idfront','legal_id_front_media_id','身份证人像面 (必传)');
|
|
410
455
|
mountApplyUpload('a_up_idback','legal_id_back_media_id','身份证国徽面 (必传)');
|
|
411
|
-
|
|
412
|
-
|
|
456
|
+
// 经营场景 (2026-10-03 xiaomu 拍板三选一): 场景决定第 4 卡片字段 + 第 5 卡片场景照片槽
|
|
457
|
+
var SCENE_UPLOADS={
|
|
458
|
+
SALES_SCENES_STORE:[['a_up_s1','store_entrance_media_id','门店门头照片 (必传)'],['a_up_s2','store_indoor_media_id','门店内部照片 (必传)']],
|
|
459
|
+
SALES_SCENES_WEB:[['a_up_s1','web_home_media_id','网站首页截图 (必传)'],['a_up_s2','web_product_media_id','商品/服务页截图 (必传)']],
|
|
460
|
+
SALES_SCENES_MP:[['a_up_s1','mp_media_id','公众号/小程序页面截图 (必传)']]
|
|
461
|
+
};
|
|
462
|
+
var SCENE_TITLES={SALES_SCENES_STORE:'门店信息',SALES_SCENES_WEB:'网站信息',SALES_SCENES_MP:'公众号 / 小程序'};
|
|
463
|
+
function currentScene(){
|
|
464
|
+
var r=document.querySelector('input[name="sales_scene"]:checked');
|
|
465
|
+
return r?r.value:'SALES_SCENES_STORE';
|
|
466
|
+
}
|
|
467
|
+
function setScene(scene){
|
|
468
|
+
document.getElementById('scene_fields_store').style.display=scene==='SALES_SCENES_STORE'?'':'none';
|
|
469
|
+
document.getElementById('scene_fields_web').style.display=scene==='SALES_SCENES_WEB'?'':'none';
|
|
470
|
+
document.getElementById('scene_fields_mp').style.display=scene==='SALES_SCENES_MP'?'':'none';
|
|
471
|
+
document.getElementById('acard4-title').textContent=SCENE_TITLES[scene]||'门店信息';
|
|
472
|
+
document.querySelectorAll('.ascene').forEach(function(c){
|
|
473
|
+
c.classList.toggle('selected', c.querySelector('input').value===scene);
|
|
474
|
+
});
|
|
475
|
+
// 场景照片槽重挂 + 清掉旧场景 media_id (避免跨场景串数据)
|
|
476
|
+
['store_entrance_media_id','store_indoor_media_id','web_home_media_id','web_product_media_id','mp_media_id'].forEach(function(k){delete _aMedia[k];});
|
|
477
|
+
var slots=SCENE_UPLOADS[scene]||SCENE_UPLOADS.SALES_SCENES_STORE;
|
|
478
|
+
slots.forEach(function(s){var el=document.getElementById(s[0]);el.style.display='';mountApplyUpload(s[0],s[1],s[2]);});
|
|
479
|
+
if(!slots.some(function(s){return s[0]==='a_up_s2';})) document.getElementById('a_up_s2').style.display='none';
|
|
480
|
+
}
|
|
481
|
+
setScene('SALES_SCENES_STORE'); // 默认线下门店
|
|
413
482
|
function aval(id){var e=document.getElementById(id);return e?e.value.trim():''}
|
|
414
483
|
function applyFormBody(){
|
|
415
|
-
|
|
484
|
+
var scene=currentScene();
|
|
485
|
+
var body={
|
|
416
486
|
subject_type:aval('a_subject_type'),merchant_shortname:aval('a_shortname'),merchant_name:aval('a_merchant_name'),
|
|
417
487
|
license_number:aval('a_license_number'),service_phone:aval('a_service_phone'),
|
|
418
488
|
settlement_id:aval('a_settlement_id'),qualification_type:aval('a_qualification_type'),
|
|
@@ -421,12 +491,49 @@ function applyFormBody(){
|
|
|
421
491
|
legal_id_period_begin:aval('a_period_begin'),legal_id_period_end:aval('a_period_end'),
|
|
422
492
|
account_type:aval('a_account_type'),account_name:aval('a_account_name'),account_number:aval('a_account_number'),
|
|
423
493
|
account_bank:aval('a_account_bank'),bank_name:aval('a_bank_name'),bank_address_code:aval('a_bank_address_code'),
|
|
424
|
-
|
|
494
|
+
legal_id_address:aval('a_legal_id_address'), // 企业主体必填 (F4), 个体户留空
|
|
495
|
+
sales_scene:scene,
|
|
425
496
|
license_media_id:_aMedia.license_media_id||'',legal_id_front_media_id:_aMedia.legal_id_front_media_id||'',
|
|
426
|
-
legal_id_back_media_id:_aMedia.legal_id_back_media_id||''
|
|
427
|
-
store_indoor_media_id:_aMedia.store_indoor_media_id||''
|
|
497
|
+
legal_id_back_media_id:_aMedia.legal_id_back_media_id||''
|
|
428
498
|
};
|
|
499
|
+
if(scene==='SALES_SCENES_STORE'){
|
|
500
|
+
body.store_name=aval('a_store_name');body.store_address_code=aval('a_store_address_code');body.store_address=aval('a_store_address');
|
|
501
|
+
body.store_entrance_media_id=_aMedia.store_entrance_media_id||'';body.store_indoor_media_id=_aMedia.store_indoor_media_id||'';
|
|
502
|
+
}else if(scene==='SALES_SCENES_WEB'){
|
|
503
|
+
body.website_url=aval('a_website_url');
|
|
504
|
+
body.web_home_media_id=_aMedia.web_home_media_id||'';body.web_product_media_id=_aMedia.web_product_media_id||'';
|
|
505
|
+
}else{
|
|
506
|
+
body.appid=aval('a_appid');body.appid_type=aval('a_appid_type');
|
|
507
|
+
body.mp_media_id=_aMedia.mp_media_id||'';
|
|
508
|
+
}
|
|
509
|
+
return body;
|
|
429
510
|
}
|
|
511
|
+
// 错误提示 (2026-10-03 硬性要求): 红卡 + 平滑滚动 + 字段级红框定位
|
|
512
|
+
var FIELD_EL={ // 字段名 → 页面元素 id (媒体字段 → 上传槽; 文本字段默认 a_+字段)
|
|
513
|
+
license_media_id:'a_up_license',legal_id_front_media_id:'a_up_idfront',legal_id_back_media_id:'a_up_idback',
|
|
514
|
+
store_entrance_media_id:'a_up_s1',store_indoor_media_id:'a_up_s2',
|
|
515
|
+
web_home_media_id:'a_up_s1',web_product_media_id:'a_up_s2',mp_media_id:'a_up_s1',
|
|
516
|
+
website_url:'a_website_url',appid:'a_appid'
|
|
517
|
+
};
|
|
518
|
+
function showApplyErr(msg,field){
|
|
519
|
+
var card=document.getElementById('apply-err');
|
|
520
|
+
card.textContent='⚠️ '+msg;
|
|
521
|
+
card.style.display='block';
|
|
522
|
+
// 清旧高亮
|
|
523
|
+
document.querySelectorAll('#apply-form .aerr-field').forEach(function(e){e.classList.remove('aerr-field');});
|
|
524
|
+
var t=null;
|
|
525
|
+
if(field){
|
|
526
|
+
// 手填接管 id 的场景 (其他银行/手动编码): getElementById 拿到的就是当前持有 id 的可见控件
|
|
527
|
+
t=document.getElementById(FIELD_EL[field]||('a_'+field));
|
|
528
|
+
}
|
|
529
|
+
if(t){
|
|
530
|
+
t.classList.add('aerr-field');
|
|
531
|
+
t.scrollIntoView({behavior:'smooth',block:'center'}); // 滚动到字段, 提示卡跟随在提交按钮上方
|
|
532
|
+
}else{
|
|
533
|
+
card.scrollIntoView({behavior:'smooth',block:'center'});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function hideApplyErr(){document.getElementById('apply-err').style.display='none';}
|
|
430
537
|
// 状态机渲染: 未提交(表单) → 已提交(business_code+轮询) → TO_BE_SIGNED(签约链接) → FINISHED(sub_mchid+引导金)
|
|
431
538
|
function renderApplyment(j){
|
|
432
539
|
var box=document.getElementById('apply-status');
|
|
@@ -451,20 +558,33 @@ function renderApplyment(j){
|
|
|
451
558
|
async function submitApplyment(){
|
|
452
559
|
var btn=document.getElementById('btn-apply-submit');
|
|
453
560
|
var msg=document.getElementById('apply-msg');
|
|
454
|
-
|
|
561
|
+
hideApplyErr();
|
|
562
|
+
btn.disabled=true;btn.textContent='加密并提交中…';
|
|
563
|
+
// 前端 40s 兜底超时 (后端 SP 转发自身有 15s 超时; AbortController 防页面卡死)
|
|
564
|
+
var ctrl=new AbortController();
|
|
565
|
+
var timer=setTimeout(function(){ctrl.abort();},40000);
|
|
455
566
|
try{
|
|
456
|
-
var j=await readJson(await fetch('/api/onboard/applyment',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(applyFormBody())}));
|
|
567
|
+
var j=await readJson(await fetch('/api/onboard/applyment',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(applyFormBody()),signal:ctrl.signal}));
|
|
568
|
+
clearTimeout(timer);
|
|
457
569
|
if(j.ok){
|
|
458
570
|
msg.style.display='block';
|
|
459
571
|
msg.textContent='✅ 已提交微信审核 (敏感字段已在本机加密)'+(j.idempotent?' — 此前已提交过, 幂等忽略':'');
|
|
460
572
|
document.getElementById('apply-form').style.display='none';
|
|
461
573
|
queryApplyment();
|
|
462
574
|
} else {
|
|
463
|
-
|
|
464
|
-
|
|
575
|
+
// 后端/SP/微信错误 → 醒目红卡 + 字段定位 (message 已是中文, 含字段名)
|
|
576
|
+
showApplyErr(j.message||j.error||'未知错误', j.field);
|
|
465
577
|
loadApplymentStatus(); // PENDING_PUSH 时状态框给出重试按钮
|
|
466
578
|
}
|
|
467
|
-
}catch(e){
|
|
579
|
+
}catch(e){
|
|
580
|
+
clearTimeout(timer);
|
|
581
|
+
if(e && e.name==='AbortError'){
|
|
582
|
+
showApplyErr('服务商响应超时,资料已加密暂存本机,可点重试');
|
|
583
|
+
loadApplymentStatus();
|
|
584
|
+
}else{
|
|
585
|
+
showApplyErr('提交失败: 服务暂时不可用,请刷新重试 ('+e.message+')');
|
|
586
|
+
}
|
|
587
|
+
}
|
|
468
588
|
btn.disabled=false;btn.textContent='加密并提交进件';
|
|
469
589
|
}
|
|
470
590
|
async function retryApplyment(){
|
|
@@ -473,7 +593,53 @@ async function retryApplyment(){
|
|
|
473
593
|
else{var msg=document.getElementById('apply-msg');msg.style.display='block';msg.textContent=j.message||'重试失败, 请稍后再试';}
|
|
474
594
|
}
|
|
475
595
|
async function queryApplyment(){
|
|
476
|
-
try{
|
|
596
|
+
try{
|
|
597
|
+
var j=await readJson(await fetch('/api/onboard/applyment/status'));
|
|
598
|
+
renderApplyment(j);
|
|
599
|
+
if(j && j.form_snapshot) prefillApplyForm(j.form_snapshot); // F1: 驳回/暂存后预填
|
|
600
|
+
}catch(e){}
|
|
601
|
+
}
|
|
602
|
+
// F1 预填: 台账快照回填非敏感字段 + 照片 media_id 复用 (敏感字段是密文/脱敏, 需重填)
|
|
603
|
+
var PREFILL_MAP={subject_type:'a_subject_type',merchant_shortname:'a_shortname',merchant_name:'a_merchant_name',
|
|
604
|
+
license_number:'a_license_number',service_phone:'a_service_phone',settlement_id:'a_settlement_id',
|
|
605
|
+
qualification_type:'a_qualification_type',contact_email:'a_contact_email',legal_name:'a_legal_name',
|
|
606
|
+
legal_id_period_begin:'a_period_begin',legal_id_period_end:'a_period_end',
|
|
607
|
+
account_type:'a_account_type',account_bank:'a_account_bank',bank_name:'a_bank_name',bank_address_code:'a_bank_address_code',
|
|
608
|
+
store_name:'a_store_name',store_address_code:'a_store_address_code',store_address:'a_store_address',
|
|
609
|
+
website_url:'a_website_url',appid:'a_appid',appid_type:'a_appid_type'};
|
|
610
|
+
var PREFILL_MEDIA=['license_media_id','legal_id_front_media_id','legal_id_back_media_id',
|
|
611
|
+
'store_entrance_media_id','store_indoor_media_id','web_home_media_id','web_product_media_id','mp_media_id'];
|
|
612
|
+
var _prefilled=false;
|
|
613
|
+
function prefillApplyForm(snap){
|
|
614
|
+
if(_prefilled||!snap||typeof snap!=='object') return;
|
|
615
|
+
_prefilled=true;
|
|
616
|
+
// 场景先切 (setScene 会重挂上传槽, 必须在写媒体状态前)
|
|
617
|
+
if(snap.sales_scene){
|
|
618
|
+
var r=document.querySelector('input[name="sales_scene"][value="'+snap.sales_scene+'"]');
|
|
619
|
+
if(r){r.checked=true;setScene(snap.sales_scene);}
|
|
620
|
+
}
|
|
621
|
+
// 主体类型联动先触发 (居住地址/UBO 说明显示 + 类目默认), 再写快照值 (避免类目被联动覆盖)
|
|
622
|
+
var subj=document.getElementById('a_subject_type');
|
|
623
|
+
if(subj&&snap.subject_type){subj.value=snap.subject_type;subj.dispatchEvent(new Event('change'));}
|
|
624
|
+
for(var k in PREFILL_MAP){
|
|
625
|
+
if(k==='subject_type') continue; // 上面已处理
|
|
626
|
+
var v=snap[k];
|
|
627
|
+
if(!v||v==='无') continue; // 敏感字段快照是脱敏的 ***后4位, 不会命中这里 (值带 *** 前缀也跳过)
|
|
628
|
+
if(String(v).indexOf('***')===0) continue;
|
|
629
|
+
var e=document.getElementById(PREFILL_MAP[k]);
|
|
630
|
+
if(e&&!e.value) e.value=v;
|
|
631
|
+
}
|
|
632
|
+
// 照片复用: media_id 还在微信侧有效, 标已传
|
|
633
|
+
var reused=0;
|
|
634
|
+
PREFILL_MEDIA.forEach(function(k){
|
|
635
|
+
if(!snap[k]) return;
|
|
636
|
+
_aMedia[k]=snap[k]; reused++;
|
|
637
|
+
var st=document.getElementById('st_'+k);
|
|
638
|
+
if(st){st.style.color='#16A34A';st.textContent='✓ 已上传过, 无需重传';}
|
|
639
|
+
});
|
|
640
|
+
var msg=document.getElementById('apply-msg');
|
|
641
|
+
msg.style.display='block';
|
|
642
|
+
msg.textContent='ℹ️ 已预填上次提交的资料; 身份证号/手机号/卡号等敏感字段需重新填写'+(reused?' (照片已传过, 无需重传)':'');
|
|
477
643
|
}
|
|
478
644
|
async function loadApplymentStatus(){queryApplyment();}
|
|
479
645
|
function markStepDone(id){
|
|
@@ -544,6 +710,10 @@ async function submit(){
|
|
|
544
710
|
if(subj&&stl){
|
|
545
711
|
subj.addEventListener('change',function(){
|
|
546
712
|
stl.value = subj.value==='SUBJECT_TYPE_ENTERPRISE' ? '716' : '719';
|
|
713
|
+
// F4: 企业主体显示身份证居住地址(必填) + UBO 说明; 个体户隐藏
|
|
714
|
+
var ent = subj.value==='SUBJECT_TYPE_ENTERPRISE';
|
|
715
|
+
var ia=document.getElementById('afield_idaddr'); if(ia) ia.style.display=ent?'':'none';
|
|
716
|
+
var un=document.getElementById('afield_ubo_note'); if(un) un.style.display=ent?'':'none';
|
|
547
717
|
});
|
|
548
718
|
}
|
|
549
719
|
// 省市编码选项 (国标区划码, 两处下拉共用; 微信官方对照表口径)
|