feat: update appraisal ordering and payment flows
This commit is contained in:
173
server-api/app/support/MiniProgramAuthService.php
Normal file
173
server-api/app/support/MiniProgramAuthService.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace app\support;
|
||||
|
||||
use support\think\Db;
|
||||
|
||||
class MiniProgramAuthService
|
||||
{
|
||||
public const AUTH_TYPE = 'wechat_mini_program';
|
||||
|
||||
public function bindOpenid(int $userId, string $code): array
|
||||
{
|
||||
$code = trim($code);
|
||||
if ($userId <= 0) {
|
||||
throw new \RuntimeException('用户登录状态无效');
|
||||
}
|
||||
if ($code === '') {
|
||||
throw new \RuntimeException('小程序登录 code 不能为空');
|
||||
}
|
||||
|
||||
$identity = $this->fetchOpenidByCode($code);
|
||||
$openid = (string)$identity['openid'];
|
||||
$unionid = (string)($identity['unionid'] ?? '');
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$existing = Db::name('user_auths')
|
||||
->where('auth_type', self::AUTH_TYPE)
|
||||
->where('auth_key', $openid)
|
||||
->lock(true)
|
||||
->find();
|
||||
if ($existing && (int)$existing['user_id'] !== $userId) {
|
||||
throw new \RuntimeException('该小程序微信身份已绑定其他账号');
|
||||
}
|
||||
|
||||
if ($unionid !== '') {
|
||||
$unionAuth = Db::name('user_auths')
|
||||
->where('auth_type', self::AUTH_TYPE)
|
||||
->where('auth_union_id', $unionid)
|
||||
->lock(true)
|
||||
->find();
|
||||
if ($unionAuth && (int)$unionAuth['user_id'] !== $userId) {
|
||||
throw new \RuntimeException('该小程序微信身份已绑定其他账号');
|
||||
}
|
||||
if (!$existing && $unionAuth) {
|
||||
$existing = $unionAuth;
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'user_id' => $userId,
|
||||
'auth_type' => self::AUTH_TYPE,
|
||||
'auth_key' => $openid,
|
||||
'auth_open_id' => $openid,
|
||||
'auth_union_id' => $unionid,
|
||||
'auth_extra' => json_encode([
|
||||
'session_key_present' => ((string)($identity['session_key'] ?? '')) !== '',
|
||||
'bound_at' => $now,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
if ($existing) {
|
||||
Db::name('user_auths')->where('id', (int)$existing['id'])->update($payload);
|
||||
} else {
|
||||
$payload['created_at'] = $now;
|
||||
Db::name('user_auths')->insert($payload);
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return [
|
||||
'openid' => $openid,
|
||||
'unionid' => $unionid,
|
||||
];
|
||||
}
|
||||
|
||||
public function openidForUser(int $userId): string
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string)Db::name('user_auths')
|
||||
->where('user_id', $userId)
|
||||
->where('auth_type', self::AUTH_TYPE)
|
||||
->order('id', 'desc')
|
||||
->value('auth_open_id');
|
||||
}
|
||||
|
||||
private function fetchOpenidByCode(string $code): array
|
||||
{
|
||||
if (str_starts_with($code, 'mock_mp_')) {
|
||||
return [
|
||||
'openid' => 'mock_mp_openid_' . substr($code, 8),
|
||||
'unionid' => '',
|
||||
'session_key' => 'mock_session_key',
|
||||
];
|
||||
}
|
||||
|
||||
$appId = $this->systemConfig('mini_program', 'app_id');
|
||||
$appSecret = $this->systemConfig('mini_program', 'app_secret');
|
||||
if ($appId === '' || $appSecret === '') {
|
||||
throw new \RuntimeException('小程序 AppID 或 AppSecret 未配置');
|
||||
}
|
||||
|
||||
$url = 'https://api.weixin.qq.com/sns/jscode2session?' . http_build_query([
|
||||
'appid' => $appId,
|
||||
'secret' => $appSecret,
|
||||
'js_code' => $code,
|
||||
'grant_type' => 'authorization_code',
|
||||
]);
|
||||
|
||||
$payload = $this->wechatApiGet($url);
|
||||
$openid = trim((string)($payload['openid'] ?? ''));
|
||||
if ($openid === '') {
|
||||
throw new \RuntimeException('微信小程序登录返回缺少 openid');
|
||||
}
|
||||
|
||||
return [
|
||||
'openid' => $openid,
|
||||
'unionid' => trim((string)($payload['unionid'] ?? '')),
|
||||
'session_key' => (string)($payload['session_key'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function wechatApiGet(string $url): array
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_CONNECTTIMEOUT => 4,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
$error = curl_error($ch);
|
||||
$httpStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($errno) {
|
||||
throw new \RuntimeException('微信小程序登录换取 openid 失败:' . $error);
|
||||
}
|
||||
if ($httpStatus < 200 || $httpStatus >= 300) {
|
||||
throw new \RuntimeException('微信小程序登录接口 HTTP 状态异常:' . $httpStatus);
|
||||
}
|
||||
|
||||
$payload = json_decode((string)$response, true);
|
||||
if (!is_array($payload)) {
|
||||
throw new \RuntimeException('微信小程序登录接口返回格式异常');
|
||||
}
|
||||
$errcode = (int)($payload['errcode'] ?? 0);
|
||||
if ($errcode !== 0) {
|
||||
throw new \RuntimeException((string)($payload['errmsg'] ?? '微信小程序登录接口返回错误'));
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function systemConfig(string $group, string $key): string
|
||||
{
|
||||
return trim((string)Db::name('system_configs')
|
||||
->where('config_group', $group)
|
||||
->where('config_key', $key)
|
||||
->value('config_value'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user