feat(security): 三端闭环加固 + 微信提现打款/订阅消息落地
后端: 身份与归属鉴权收口; 资金安全(结算闸门/支付防篡改/报名费真实扣款/赠金/结算唯一入账/提现打款); 数据泄露与隐私; 登录与密钥安全(默认密钥闸门/登录限流); 可观测性; 启动自检. 微信: 商家转账到零钱提现打款闭环(含回调/查单/失败退回佣金); 订阅消息真实下发闭环与后台模板字段对齐. 后台: 财务/微信支付配置读取失败锁定; 看板/待办/走势去假数据; 打款与测试推送文案修正. 小程序: 请求层错误透传与登录过期提示; 多页失败反馈与乐观回滚; 帮助中心FAQ页面化. 测试: 新增 40+ 回归护栏与真实 MySQL 冒烟(75 套件/599 用例全绿).
这个提交包含在:
@@ -136,11 +136,11 @@
|
||||
|
||||
const quickApproveWithdraw = async (id: string) => {
|
||||
try {
|
||||
await axios.post(`/api/finance/withdrawals/${id}/review`, { action: 'approve' });
|
||||
Message.success('已审核通过并触发微信商户零钱打款');
|
||||
const res: any = await axios.post(`/api/finance/withdrawals/${id}/review`, { action: 'approve' });
|
||||
Message.success(res?.data?.message || '提现审核已处理');
|
||||
fetchQuickTodos();
|
||||
} catch (err: any) {
|
||||
Message.error(err.message || '打款失败');
|
||||
Message.error(err?.response?.data?.message || err.message || '打款失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -287,6 +287,48 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider orientation="left">提现打款(微信商家转账到零钱)</a-divider>
|
||||
<a-alert type="info" style="margin-bottom: 12px">
|
||||
提现审核"通过"将对大神发起真实的微信商家转账到零钱。需商户号已开通该产品并配置「转账场景ID」;未配置时生产环境会拒绝打款(不会只改状态、不发钱)。
|
||||
</a-alert>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
label="转账场景ID (transfer_scene_id)"
|
||||
help="商户平台「商家转账到零钱」配置的转账场景编号"
|
||||
>
|
||||
<a-input v-model="wxPayForm.transferSceneId" placeholder="如 佣金报酬场景ID" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="转账备注 (transfer_remark)" help="展示给收款用户的转账备注,≤32字符">
|
||||
<a-input v-model="wxPayForm.transferRemark" placeholder="佣金提现" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="收款人姓名加密" help="启用后按平台公钥加密提交收款人姓名(部分场景必填)">
|
||||
<a-switch v-model="wxPayForm.transferUserNameEnabled" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="转账结果回调地址 (transfer_notify_url,选填)">
|
||||
<a-input
|
||||
v-model="wxPayForm.transferNotifyUrl"
|
||||
placeholder="https://peiww.gynhc.com/api/pay/wechat/transfer-notify"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="转账场景信息 (transfer_scene_report_infos,部分场景必填)"
|
||||
help='JSON 数组,元素形如 {"info_type":"岗位类型","info_content":"外卖骑手"};不同转账场景要求的 info_type 不同,请以微信商户平台「商家转账到零钱」该场景说明为准。留空则不提交。'
|
||||
>
|
||||
<a-textarea
|
||||
v-model="wxPayForm.transferSceneReportInfosText"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
placeholder='[{"info_type":"岗位类型","info_content":"外卖骑手"}]'
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<input
|
||||
ref="pemFileInputRef"
|
||||
type="file"
|
||||
@@ -375,6 +417,12 @@
|
||||
platformPublicKeyId: '',
|
||||
platformPublicKey: '',
|
||||
notifyUrl: 'https://peiww.gynhc.com/api/pay/wechat/notify',
|
||||
transferSceneId: '',
|
||||
transferRemark: '佣金提现',
|
||||
transferNotifyUrl: 'https://peiww.gynhc.com/api/pay/wechat/transfer-notify',
|
||||
transferUserNameEnabled: false,
|
||||
transferSceneReportInfos: [] as Array<{ info_type: string; info_content: string }>,
|
||||
transferSceneReportInfosText: '',
|
||||
remark: '微信支付V3直连商户参数配置'
|
||||
});
|
||||
|
||||
@@ -399,6 +447,11 @@
|
||||
...wxPayForm.value,
|
||||
...data
|
||||
};
|
||||
wxPayForm.value.transferSceneReportInfosText = JSON.stringify(
|
||||
wxPayForm.value.transferSceneReportInfos || [],
|
||||
null,
|
||||
2
|
||||
);
|
||||
if (data.effectiveMode) {
|
||||
wxPayEffectiveMode.value = data.effectiveMode;
|
||||
}
|
||||
@@ -458,6 +511,22 @@
|
||||
}
|
||||
wxPaySaving.value = true;
|
||||
try {
|
||||
// 解析"转账场景信息"JSON(部分转账场景必填)
|
||||
const rawSceneInfos = String(wxPayForm.value.transferSceneReportInfosText || '').trim();
|
||||
let sceneInfos: Array<{ info_type: string; info_content: string }> = [];
|
||||
if (rawSceneInfos) {
|
||||
try {
|
||||
const parsed = JSON.parse(rawSceneInfos);
|
||||
if (!Array.isArray(parsed)) throw new Error('not_array');
|
||||
sceneInfos = parsed;
|
||||
} catch (e) {
|
||||
Message.error('转账场景信息 (transfer_scene_report_infos) 需为合法 JSON 数组');
|
||||
wxPaySaving.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
wxPayForm.value.transferSceneReportInfos = sceneInfos;
|
||||
|
||||
const res: any = await axios.post('/api/admin/pay/wechat/config', wxPayForm.value);
|
||||
const data = res?.data?.data || res?.data || res;
|
||||
if (data && data.effectiveMode) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户