242 lines
8.2 KiB
PHP
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'];
|
|
}
|
|
}
|