$userId]); } /** * 用户是否已完成免确认收款授权 * @param int $userId * @return bool */ public static function isAuthorized($userId): bool { $record = self::getByUserId($userId); return $record && $record->state == self::STATE_TAKING_EFFECT && $record->authorization_id; } /** * 发起/获取免确认收款授权,供前端拉起授权页 * @param \app\common\model\Users $user 用户(需含 openid) * @param string $displayName 授权详情中展示的开通账号名称 * @return array {authorized: bool, state, package_info?, mch_id?, app_id?} * @throws \Exception */ public static function apply($user, string $displayName = ''): array { $userId = $user->id; if (empty($user->openid)) { 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(); $displayName = mb_substr($displayName ?: ($user->nickname ?: ('用户' . $userId)), 0, 32); $res = $wechat->applyTransferAuthorization($authNo, $user->openid, $displayName); if (empty($res['package_info'])) { throw new \Exception($res['message'] ?? '发起免确认收款授权失败'); } //保存/更新授权记录(package_info 用于前端拉起授权页,state 当前为待用户确认) $data = [ 'state' => $res['state'] ?? self::STATE_WAIT_USER_CONFIRM, 'package_info' => $res['package_info'], ]; if ($record) { $record->save($data); } else { self::create(array_merge([ 'user_id' => $userId, 'out_authorization_no' => $authNo, 'authorization_id' => '', ], $data)); } return [ 'authorized' => false, 'state' => $data['state'], 'package_info' => $res['package_info'], //前端 wx.requestMerchantTransfer 需要:商户号 + AppID + package 'mch_id' => (string)$wechat->payApp->getMerchant()->getMerchantId(), 'app_id' => $wechat->config['app_id'], ]; } /** * 处理免确认收款授权结果回调(用户授权成功后微信异步通知) * @param array $message 解密后的授权结果,含 out_authorization_no / authorization_id / state * @return bool */ public static function handleAuthorizationNotify(array $message): bool { $outAuthNo = trim((string)($message['out_authorization_no'] ?? '')); $authorizationId = trim((string)($message['authorization_id'] ?? '')); $state = trim((string)($message['state'] ?? '')); if (!$outAuthNo || !$authorizationId) { Log::error('免确认收款授权回调参数缺失:' . json_encode($message, JSON_UNESCAPED_UNICODE)); return false; } $record = self::getOne(['out_authorization_no' => $outAuthNo]); if (!$record) { Log::error('免确认收款授权回调找不到授权记录:' . $outAuthNo); return false; } //记录微信侧授权单号,标记授权生效(授权长期有效,直至用户或商户主动解除) $record->authorization_id = $authorizationId; $record->state = $state ?: self::STATE_TAKING_EFFECT; $record->save(); Log::info('免确认收款授权生效:' . json_encode([ 'user_id' => $record->user_id, 'state' => $record->state, ], JSON_UNESCAPED_UNICODE)); return true; } }