转账功能优化

This commit is contained in:
Dev
2026-10-10 15:11:14 +08:00
parent 2541fc1dfb
commit 352b633a5c
2 changed files with 43 additions and 34 deletions
@@ -24,9 +24,10 @@ class TransferAuthorizationService
/** @var TransferAuthorizations */
public static $Model = TransferAuthorizations::class;
// 授权状态
// 授权状态(与微信查询授权单接口 state 取值一致)
const STATE_WAIT_USER_CONFIRM = 'WAIT_USER_CONFIRM'; //已受理,待用户确认
const STATE_TAKING_EFFECT = 'TAKING_EFFECT'; //已授权,生效中(长期有效,直至主动解除)
const STATE_CLOSED = 'CLOSED'; //已关闭,无法转账至该授权对应的用户
/**
* 生成商户侧授权单号:一个用户固定一个单号。
@@ -39,6 +40,18 @@ class TransferAuthorizationService
return 'TXAUTH' . str_pad((string)intval($userId), 10, '0', STR_PAD_LEFT);
}
/**
* 生成新的商户侧授权单号:授权已被关闭/解除时使用。
* 微信仅明确“未确认的授权单 30 天后单号可复用”,已确认后又被关闭的单号复用存在失败风险,
* 因此关闭后重新授权使用全新单号(原单号 ≤22 位,仍满足 32 位、字母数字限制)
* @param int $userId
* @return string
*/
public static function generateAuthNoUnique($userId): string
{
return self::generateAuthNo($userId) . strtoupper(substr(md5(uniqid('', true)), 0, 6));
}
/**
* 获取用户的授权记录
* @param int $userId
@@ -46,7 +59,10 @@ class TransferAuthorizationService
*/
public static function getByUserId($userId)
{
return self::getOne(['user_id' => $userId]);
//优先取生效中的授权记录(历史被关闭的记录仍保留,故按状态+时间取最新的)
$record = self::getOne(['user_id' => $userId, 'state' => self::STATE_TAKING_EFFECT], [], ['id', 'desc']);
return $record ?: self::getOne(['user_id' => $userId], [], ['id', 'desc']);
}
/**
@@ -76,44 +92,35 @@ class TransferAuthorizationService
throw new \Exception('无法获取用户微信身份,请先登录小程序');
}
//已授权的用户直接返回,不再重复发起
$record = self::getByUserId($userId);
if ($record && self::isAuthorized($userId)) {
return [
'authorized' => true,
'state' => $record->state,
];
}
$authNo = self::generateAuthNo($userId);
$wechat = new EasyWeChatService();
//本地状态可能与微信侧不一致(如授权回调未收到):先查单同步,微信侧已生效则直接放行
//每次提现前都回查微信侧最新授权状态并同步到本地:
//授权生效后长期有效(TAKING_EFFECT),但用户可能在微信端主动解除授权,
//只看本地记录会导致解除后仍按已授权放行,直到后台转账失败才暴露
$query = $wechat->queryTransferAuthorization($authNo);
if (($query['state'] ?? '') === self::STATE_TAKING_EFFECT) {
self::saveAuthorization($userId, $authNo, self::STATE_TAKING_EFFECT, $query['authorization_id'] ?? '');
return [
'authorized' => true,
'state' => self::STATE_TAKING_EFFECT,
];
$queryState = (string)($query['state'] ?? '');
if ($queryState !== '') {
self::saveAuthorization($userId, $authNo, $queryState, $query['authorization_id'] ?? '');
if ($queryState === self::STATE_TAKING_EFFECT) {
return [
'authorized' => true,
'state' => $queryState,
];
}
}
//发起授权申请(授权单号固定,复用同一单号可重新发起)
//未生效:发起授权申请(未确认的单号可复用重新发起)
//CLOSED(用户或商户已关闭) 时复用原单号存在发起失败风险,改用全新单号并新建记录
$forceCreate = ($queryState === self::STATE_CLOSED);
if ($forceCreate) {
$authNo = self::generateAuthNoUnique($userId);
}
$displayName = mb_substr($displayName ?: ($user->nickname ?: ('用户' . $userId)), 0, 32);
$res = $wechat->applyTransferAuthorization($authNo, $user->openid, $displayName);
if (empty($res['package_info'])) {
//package 仅在授权状态为 WAIT_USER_CONFIRM 时返回;拿不到时再查单确认微信侧是否已生效
$query = $wechat->queryTransferAuthorization($authNo);
if (($query['state'] ?? '') === self::STATE_TAKING_EFFECT) {
self::saveAuthorization($userId, $authNo, self::STATE_TAKING_EFFECT, $query['authorization_id'] ?? '');
return [
'authorized' => true,
'state' => self::STATE_TAKING_EFFECT,
];
}
//package 仅在授权状态为 WAIT_USER_CONFIRM 时返回,拿不到时透出微信返回的错误信息
throw new \Exception($res['message'] ?? ($query['message'] ?? '发起免确认收款授权失败'));
}
@@ -123,7 +130,8 @@ class TransferAuthorizationService
$authNo,
$res['state'] ?? self::STATE_WAIT_USER_CONFIRM,
'',
$res['package_info']
$res['package_info'],
$forceCreate
);
return [
@@ -143,11 +151,12 @@ class TransferAuthorizationService
* @param string $state 授权状态
* @param string $authorizationId 微信侧授权单号(可为空)
* @param string $packageInfo 拉起授权页的 package(可为空)
* @param bool $forceCreate 是否强制新建记录(授权单已关闭换新单号时使用)
* @return void
*/
private static function saveAuthorization($userId, string $authNo, string $state, string $authorizationId = '', string $packageInfo = '')
private static function saveAuthorization($userId, string $authNo, string $state, string $authorizationId = '', string $packageInfo = '', bool $forceCreate = false)
{
$record = self::getByUserId($userId);
$record = $forceCreate ? null : self::getByUserId($userId);
$data = ['state' => $state];
if ($authorizationId !== '') {
$data['authorization_id'] = $authorizationId;