feat: 新增提现文案翻译工具类,统一处理失败与驳回原因

这个提交包含在:
2026-10-28 18:21:12 +08:00
父节点 589d6aa216
当前提交 f332dfd2b0
共有 6 个文件被更改,包括 184 次插入 和 12 次删除

查看文件

@@ -3,6 +3,7 @@
namespace app\admin\controller\fc;
use app\common\controller\Backend;
use app\common\library\WithdrawText;
use think\Db;
/**
@@ -131,6 +132,8 @@ class Withdraw extends Backend
'totalWithdrawn' => round($totalWithdrawn, 2),
'withdrawable' => round($withdrawable, 2),
'rejectReasons' => $rejectReasons,
'failReasonText' => WithdrawText::fail($row['fail_reason']),
'reasonText' => WithdrawText::reason($row['reason']),
]);
return $this->view->fetch();
@@ -293,7 +296,7 @@ class Withdraw extends Backend
if ($latest['status'] === 'paid') {
$this->success('微信转账已完成,单据已更新为「已成功到账」');
} elseif ($latest['status'] === 'failed') {
$this->error('微信转账已失败/已撤销:' . ($latest['fail_reason'] ?: '未知原因'));
$this->error('微信转账已失败/已撤销:' . (WithdrawText::fail($latest['fail_reason']) ?: '未知原因'));
} else {
$this->success('已同步微信最新状态:' . $stText . '(微信已受理,需收款人在微信完成确认方可到账)');
}

查看文件

@@ -186,15 +186,15 @@
</div>
{/if}
{if condition="!empty($row.fail_reason)"}
{if condition="!empty($failReasonText)"}
<div class="alert alert-danger" style="margin-top:6px;margin-bottom:8px;padding:10px 14px;">
<i class="fa fa-exclamation-triangle"></i> <strong>打款失败说明:</strong>{$row.fail_reason|htmlentities}
<i class="fa fa-exclamation-triangle"></i> <strong>打款失败说明:</strong>{$failReasonText|htmlentities}
</div>
{/if}
{if condition="!empty($row.reason)"}
{if condition="!empty($reasonText)"}
<div class="alert alert-warning" style="margin-top:6px;margin-bottom:8px;padding:10px 14px;">
<i class="fa fa-info-circle"></i> <strong>拒绝/驳回原因:</strong>{$row.reason|htmlentities}
<i class="fa fa-info-circle"></i> <strong>拒绝/驳回原因:</strong>{$reasonText|htmlentities}
</div>
{/if}
</div>

查看文件

@@ -2,6 +2,7 @@
namespace app\api\controller;
use app\common\library\WithdrawText;
use think\Db;
/**
@@ -105,7 +106,7 @@ class AdminDist extends V1
'status' => (string)$row['status'],
'channel' => (string)$row['channel'],
'transferStatus' => (string)(isset($row['transfer_status']) ? $row['transfer_status'] : ''),
'failReason' => (string)(isset($row['fail_reason']) ? $row['fail_reason'] : ''),
'failReason' => WithdrawText::fail(isset($row['fail_reason']) ? $row['fail_reason'] : ''),
'createdAt' => $this->iso($row['createtime']),
];
}

查看文件

@@ -2,6 +2,7 @@
namespace app\api\controller;
use app\common\library\WithdrawText;
use think\Db;
/**
@@ -366,7 +367,7 @@ class Distribution extends V1
'status' => (string)$row['status'],
'transferStatus' => (string)(isset($row['transfer_status']) ? $row['transfer_status'] : ''),
'packageInfo' => (string)(isset($row['package_info']) ? $row['package_info'] : ''),
'failReason' => (string)(isset($row['fail_reason']) ? $row['fail_reason'] : ''),
'failReason' => WithdrawText::fail(isset($row['fail_reason']) ? $row['fail_reason'] : ''),
'createdAt' => $this->iso($row['createtime']),
'transferMchId' => $mchId,
'transferAppId' => $appId,
@@ -413,12 +414,12 @@ class Distribution extends V1
'amount' => round((float)$row['amount'], 2),
'channel' => (string)$row['channel'],
'status' => (string)$row['status'],
'reason' => (string)$row['reason'],
'reason' => WithdrawText::reason($row['reason']),
'outBillNo' => (string)$row['out_bill_no'],
'transferStatus' => (string)$row['transfer_status'],
'transferId' => (string)$row['transfer_id'],
'packageInfo' => (string)$row['package_info'],
'failReason' => (string)$row['fail_reason'],
'failReason' => WithdrawText::fail($row['fail_reason']),
'createdAt' => $this->iso($row['createtime']),
'payTime' => !empty($row['paytime']) ? $this->iso($row['paytime']) : '',
'transferTime' => !empty($row['transfer_time']) ? $this->iso($row['transfer_time']) : '',

查看文件

@@ -0,0 +1,100 @@
<?php
namespace app\common\library;
/**
* 提现文案翻译
*
* 微信商家转账会把失败/撤销以「错误码」形式返回(如 MCH_CANCEL、NOT_ENOUGH),
* 早期驳回原因也存在数字编码,这些标识对用户不可读。
* 此处统一把码值翻译为中文,数据库仍保留原始码值以便排查。
*/
class WithdrawText
{
/** 微信转账失败/撤销错误码 → 中文说明 */
protected static $failMap = [
'NOT_ENOUGH' => '商户转账账户余额不足',
'ACCOUNT_ERROR' => '收款账户状态异常',
'V2_ACCOUNT_SIMPLE_BAN' => '收款账户被限制收款',
'V3_ACCOUNT_NOT_EXIST' => '收款微信账户不存在',
'NAME_NOT_MATCH' => '收款人姓名与微信实名不一致',
'USER_NAME_NOT_MATCH' => '收款人姓名与微信实名不一致',
'REAL_NAME_CHECK_FAIL' => '收款人微信实名核验未通过',
'INVALID_OPENID' => '收款人微信标识无效',
'OPENID_ERROR' => '收款人微信标识异常',
'AMOUNT_EXCEED' => '超出单笔转账限额',
'DAILY_LIMIT_EXCEED' => '超出当日转账限额',
'MCH_CANCEL' => '商户主动撤销转账',
'MCH_REVOKE' => '商户主动撤销转账',
'TRANSFER_FAILED' => '微信转账未成功',
'FAIL' => '微信转账未成功',
'CANCEL' => '转账已撤销',
'CANCELLED' => '转账已撤销',
'PARAM_ERROR' => '转账参数有误,请联系客服',
'SYSTEM_ERROR' => '微信系统繁忙,请稍后重试',
'FREQUENCY_LIMITED' => '操作过于频繁,请稍后重试',
'RULE_LIMIT' => '触发微信风控限制',
'ACCOUNT_FROZEN' => '收款账户已被冻结',
'NO_AUTH' => '商户无转账接口权限',
'APPID_MCHID_NOT_MATCH' => '小程序与商户号不匹配',
'SIGN_ERROR' => '商户签名错误',
'IP_NOT_ALLOWED' => '服务器IP未加入微信白名单',
'IP_FORBIDDEN' => '服务器IP未加入微信白名单',
];
/** 未识别码值的兜底文案 */
const FAIL_FALLBACK = '微信转账未成功,请稍后重试或联系客服';
const REASON_FALLBACK = '审核未通过,请联系客服了解详情';
/**
* 转账失败原因翻译
*
* @param string $raw 数据库原始 fail_reason
* @return string 可读中文
*/
public static function fail($raw)
{
$text = trim((string)$raw);
if ($text === '') {
return '';
}
// 去掉「微信转账失败:」这类前缀,只留原因本体
$text = preg_replace('/^微信转账(失败|撤销)[::]\s*/u', '', $text);
$text = trim((string)$text);
$code = strtoupper($text);
if (isset(self::$failMap[$code])) {
return self::$failMap[$code];
}
// 纯码值(如 V3_XXX / ERR_123):翻译不出就兜底,不把码抛给用户
if (preg_match('/^[A-Z0-9_\-\.\/]{2,60}$/i', $text)) {
return self::FAIL_FALLBACK;
}
// 已是中文(微信返回的描述)则直接展示
return $text;
}
/**
* 驳回原因翻译
*
* @param string $raw 数据库原始 reason
* @return string 可读中文
*/
public static function reason($raw)
{
$text = trim((string)$raw);
if ($text === '') {
return '';
}
// 数字编码或纯码值(早期数据)→ 兜底文案
if (preg_match('/^[0-9]+$/', $text) || preg_match('/^[A-Z0-9_\-\.\/]{2,60}$/i', $text)) {
return self::REASON_FALLBACK;
}
return $text;
}
}

查看文件

@@ -33,6 +33,70 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
'</div>';
}
/**
* 微信转账失败/撤销错误码 → 中文说明
* 与 application/common/library/WithdrawText.php 保持同一份字典
*/
var FAIL_TEXT = {
NOT_ENOUGH: '商户转账账户余额不足',
ACCOUNT_ERROR: '收款账户状态异常',
V2_ACCOUNT_SIMPLE_BAN: '收款账户被限制收款',
V3_ACCOUNT_NOT_EXIST: '收款微信账户不存在',
NAME_NOT_MATCH: '收款人姓名与微信实名不一致',
USER_NAME_NOT_MATCH: '收款人姓名与微信实名不一致',
REAL_NAME_CHECK_FAIL: '收款人微信实名核验未通过',
INVALID_OPENID: '收款人微信标识无效',
OPENID_ERROR: '收款人微信标识异常',
AMOUNT_EXCEED: '超出单笔转账限额',
DAILY_LIMIT_EXCEED: '超出当日转账限额',
MCH_CANCEL: '商户主动撤销转账',
MCH_REVOKE: '商户主动撤销转账',
TRANSFER_FAILED: '微信转账未成功',
FAIL: '微信转账未成功',
CANCEL: '转账已撤销',
CANCELLED: '转账已撤销',
PARAM_ERROR: '转账参数有误,请联系客服',
SYSTEM_ERROR: '微信系统繁忙,请稍后重试',
FREQUENCY_LIMITED: '操作过于频繁,请稍后重试',
RULE_LIMIT: '触发微信风控限制',
ACCOUNT_FROZEN: '收款账户已被冻结',
NO_AUTH: '商户无转账接口权限',
APPID_MCHID_NOT_MATCH: '小程序与商户号不匹配',
SIGN_ERROR: '商户签名错误',
IP_NOT_ALLOWED: '服务器IP未加入微信白名单',
IP_FORBIDDEN: '服务器IP未加入微信白名单'
};
var FAIL_FALLBACK = '微信转账未成功,请稍后重试或联系客服';
var REASON_FALLBACK = '审核未通过,请联系客服了解详情';
/**
* 失败原因码值转中文
*
* @param {string} raw 数据库原始 fail_reason
* @return {string} 可读中文
*/
function failText(raw) {
var text = $.trim(raw || '');
if (!text) return '';
text = $.trim(text.replace(/^微信转账(失败|撤销)[::]\s*/, ''));
if (FAIL_TEXT[text.toUpperCase()]) return FAIL_TEXT[text.toUpperCase()];
if (/^[A-Z0-9_\-.\/]{2,60}$/i.test(text)) return FAIL_FALLBACK;
return text;
}
/**
* 驳回原因码值转中文
*
* @param {string} raw 数据库原始 reason
* @return {string} 可读中文
*/
function reasonText(raw) {
var text = $.trim(raw || '');
if (!text) return '';
if (/^[0-9]+$/.test(text) || /^[A-Z0-9_\-.\/]{2,60}$/i.test(text)) return REASON_FALLBACK;
return text;
}
var Controller = {
/**
* 提现列表页初始化
@@ -115,10 +179,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
return value && m[value] ? m[value] : (value || '-');
}
},
{field: 'reason', title: '拒绝原因', operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'reason', title: '拒绝原因', operate: 'LIKE', table: table, class: 'autocontent', formatter: function (value) {
return reasonText(value) || '-';
}},
{field: 'fail_reason', title: '转账失败原因', operate: 'LIKE', table: table, class: 'autocontent', formatter: function (value) {
if (!value) return '-';
return '<span class="text-danger" title="' + value + '">' + value + '</span>';
var text = failText(value);
if (!text) return '-';
return '<span class="text-danger">' + text + '</span>';
}},
{field: 'paytime', title: __('Paytime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
{field: 'createtime', title: __('Createtime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},