You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
4.0 KiB

<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class WeChatMiniProgramService
{
public function getAccessToken(): string
{
$appId = config('services.wechat.mini_program.app_id');
$secret = config('services.wechat.mini_program.secret');
if (! $appId || ! $secret) {
throw new RuntimeException('微信小程序 AppID/Secret 未配置');
}
return Cache::remember('wechat_miniapp_access_token', 7000, function () use ($appId, $secret) {
$response = Http::timeout(10)->get('https://api.weixin.qq.com/cgi-bin/token', [
'grant_type' => 'client_credential',
'appid' => $appId,
'secret' => $secret,
]);
$payload = $response->json();
if (! is_array($payload) || ! empty($payload['errcode'])) {
throw new RuntimeException((string) ($payload['errmsg'] ?? '获取微信 access_token 失败'));
}
$token = $payload['access_token'] ?? null;
if (! is_string($token) || $token === '') {
throw new RuntimeException('微信 access_token 响应异常');
}
return $token;
});
}
public function getUnlimitedWxaCode(string $page, string $scene): string
{
$scene = trim($scene);
if ($scene === '') {
throw new RuntimeException('签到码不能为空');
}
if (strlen($scene) > 32) {
throw new RuntimeException('签到码不能超过 32 个字符');
}
$token = $this->getAccessToken();
$response = Http::timeout(20)
->withHeaders(['Content-Type' => 'application/json'])
->post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$token}", [
'scene' => $scene,
'page' => ltrim($page, '/'),
'check_path' => false,
'env_version' => config('services.wechat.mini_program.env_version', 'develop'),
'width' => 430,
]);
$contentType = (string) $response->header('Content-Type');
if (str_contains($contentType, 'json') || str_starts_with(trim($response->body()), '{')) {
$payload = $response->json();
if (is_array($payload) && ! empty($payload['errcode'])) {
throw new RuntimeException((string) ($payload['errmsg'] ?? '生成小程序码失败'));
}
}
$body = $response->body();
if ($body === '') {
throw new RuntimeException('生成小程序码失败:空响应');
}
return $body;
}
/**
* @return array{openid: string, unionid?: string, session_key: string}
*/
public function codeToSession(string $code): array
{
$appId = config('services.wechat.mini_program.app_id');
$secret = config('services.wechat.mini_program.secret');
if (! $appId || ! $secret) {
throw new RuntimeException('微信小程序 AppID/Secret 未配置');
}
$response = Http::timeout(10)->get('https://api.weixin.qq.com/sns/jscode2session', [
'appid' => $appId,
'secret' => $secret,
'js_code' => $code,
'grant_type' => 'authorization_code',
]);
$payload = $response->json();
if (! is_array($payload)) {
throw new RuntimeException('微信登录响应异常');
}
if (! empty($payload['errcode'])) {
throw new RuntimeException((string) ($payload['errmsg'] ?? '微信登录失败'));
}
if (empty($payload['openid'])) {
throw new RuntimeException('微信登录未返回 openid');
}
return [
'openid' => (string) $payload['openid'],
'unionid' => isset($payload['unionid']) ? (string) $payload['unionid'] : null,
'session_key' => (string) ($payload['session_key'] ?? ''),
];
}
}