转账功能优化
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user