Files
2026-09-15 10:26:34 +08:00

242 lines
8.2 KiB
PHP

<?php
/**
* 2025 D1m Group
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0) that is available
* through the world-wide-web at this URL: http://www.opensource.org/licenses/OSL-3.0
*
* @copyright 2025 D1m Group
* @license http://www.opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
namespace Stemcell\Commerce\Model\Wechat;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\HTTP\Client\Curl;
use Psr\Log\LoggerInterface;
/**
* 微信小程序API客户端
*/
class Client
{
/**
* Client constructor.
*
* @param Config $config
* @param Curl $curl
* @param LoggerInterface $logger
*/
public function __construct(
protected Config $config,
protected Curl $curl,
protected LoggerInterface $logger
) {
// 使用PHP 7.4+构造函数属性提升,无需单独赋值
}
/**
* 获取微信小程序session_key和openid
*
* @param string $code 微信登录临时凭证
* @return array
* @throws LocalizedException
*/
public function getSessionKey($code)
{
$this->logger->info('Wechat API: Requesting session key', ['code' => $code]);
$url = $this->config->getLoginUrl() . '?appid=' . $this->config->getAppId()
. '&secret=' . $this->config->getAppSecret()
. '&js_code=' . $code
. '&grant_type=authorization_code';
$this->curl->get($url);
$response = $this->curl->getBody();
$result = json_decode($response, true);
$this->logger->info('Wechat API: Session key response', ['url' => $url,'response' => $result]);
if (isset($result['errcode'])) {
$this->logger->error('Wechat API: Failed to get session key', ['error' => $result]);
throw new LocalizedException(__('获取微信session_key失败: %1', $result['errmsg']));
}
return $result;
}
/**
* 解密微信小程序用户数据
*
* @param string $encryptedData 加密数据
* @param string $iv 初始向量
* @param string $sessionKey 会话密钥
* @return array
* @throws LocalizedException
*/
public function decryptData($encryptedData, $iv, $sessionKey)
{
$this->logger->info('Wechat API: Attempting to decrypt data');
if (strlen($sessionKey) != 24) {
$this->logger->error('Wechat API: Session key length error');
throw new LocalizedException(__('会话密钥长度错误'));
}
$aesKey = base64_decode($sessionKey);
if (strlen($iv) != 24) {
$this->logger->error('Wechat API: IV length error');
throw new LocalizedException(__('初始向量长度错误'));
}
$aesIV = base64_decode($iv);
$aesCipher = base64_decode($encryptedData);
$result = openssl_decrypt(
$aesCipher,
'AES-128-CBC',
$aesKey,
OPENSSL_RAW_DATA,
$aesIV
);
if (!$result) {
$this->logger->error('Wechat API: Data decryption failed');
throw new LocalizedException(__('数据解密失败'));
}
$dataObj = json_decode($result, true);
if (!$dataObj) {
$this->logger->error('Wechat API: Decrypted data format error');
throw new LocalizedException(__('解密数据格式错误'));
}
if ($dataObj['watermark']['appid'] != $this->config->getAppId()) {
$this->logger->error('Wechat API: Data source error', ['expected' => $this->config->getAppId(), 'actual' => $dataObj['watermark']['appid']]);
throw new LocalizedException(__('数据来源错误'));
}
$this->logger->info('Wechat API: Data decrypted successfully');
return $dataObj;
}
/**
* 解析微信小程序手机号(本地解密方式)
*
* @param string $encryptedData 加密的手机号数据
* @param string $iv 初始向量
* @param string $code 微信登录临时凭证
* @return string
* @throws LocalizedException
*/
public function getPhoneNumberByDecrypt($encryptedData, $iv, $code)
{
$this->logger->info('Wechat API: Requesting phone number via local decryption', ['code' => $code]);
// 获取session_key
$sessionData = $this->getSessionKey($code);
if (!isset($sessionData['session_key'])) {
$this->logger->error('Wechat API: Failed to get session key');
throw new LocalizedException(__('获取会话密钥失败'));
}
// 解密手机号
$decryptedData = $this->decryptData($encryptedData, $iv, $sessionData['session_key']);
if (!isset($decryptedData['phoneNumber'])) {
$this->logger->error('Wechat API: Failed to parse phone number');
throw new LocalizedException(__('解析手机号失败'));
}
$this->logger->info('Wechat API: Phone number obtained via local decryption');
return $decryptedData['phoneNumber'];
}
/**
* 通过微信API获取用户手机号
*
* @param string $code 手机号获取凭证
* @return string
* @throws LocalizedException
*/
public function getPhoneNumberByApi($code)
{
try {
$this->logger->info('Wechat API: Requesting phone number via API', ['code' => $code]);
// 获取access_token
$accessToken = $this->getAccessToken();
// 构建请求URL
$url = $this->config->getPhoneNumberUrl() . '?access_token=' . $accessToken;
// 构建请求数据
$requestData = [
'code' => $code
];
// 发送POST请求
$this->curl->addHeader('Content-Type', 'application/json');
$this->curl->post($url, json_encode($requestData));
// 获取响应
$response = $this->curl->getBody();
$result = json_decode($response, true);
$this->logger->info('Wechat API: Phone number API response', ['response' => $result]);
// 检查错误
if (isset($result['errcode']) && $result['errcode'] != 0) {
$this->logger->error('Wechat API: Failed to get phone number', ['error' => $result]);
throw new LocalizedException(__('获取手机号失败: %1', $result['errmsg']));
}
// 检查返回数据
if (!isset($result['phone_info']['phoneNumber'])) {
$this->logger->error('Wechat API: Phone number data missing in response');
throw new LocalizedException(__('手机号数据获取失败'));
}
$this->logger->info('Wechat API: Phone number obtained via API');
return $result['phone_info']['phoneNumber'];
} catch (\Exception $e) {
$this->logger->error('Wechat API: Error when getting phone number', ['exception' => $e->getMessage()]);
throw new LocalizedException(__('获取手机号时发生错误: %1', $e->getMessage()));
}
}
/**
* 获取微信access_token
*
* @return string
* @throws LocalizedException
*/
private function getAccessToken()
{
$this->logger->info('Wechat API: Requesting access token');
$url = $this->config->getApiUrl() . '/cgi-bin/token?grant_type=client_credential'
. '&appid=' . $this->config->getAppId()
. '&secret=' . $this->config->getAppSecret();
$this->curl->get($url);
$response = $this->curl->getBody();
$result = json_decode($response, true);
$this->logger->info('Wechat API: Access token response', ['response' => $result]);
if (isset($result['errcode'])) {
$this->logger->error('Wechat API: Failed to get access token', ['error' => $result]);
throw new LocalizedException(__('获取access_token失败: %1', $result['errmsg']));
}
if (!isset($result['access_token'])) {
$this->logger->error('Wechat API: Access token missing in response');
throw new LocalizedException(__('access_token获取失败'));
}
$this->logger->info('Wechat API: Access token obtained successfully');
return $result['access_token'];
}
}