pay/includes/vendor/cccyun/wechatpay-sdk/src/BaseService.php
2025-11-28 10:08:12 +08:00

226 lines
6.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace WeChatPay;
use Exception;
class BaseService
{
//SDK版本号
static $VERSION = "3.0.10";
//应用APPID
protected $appId;
//商户号
protected $mchId;
//商户API密钥
protected $apiKey;
//子商户号
protected $subMchId;
//子商户公众账号ID
protected $subAppId;
//商户证书路径
protected $sslCertPath;
//商户证书私钥路径
protected $sslKeyPath;
//公共请求参数
protected $publicParams = [];
/**
* @param array $config 微信支付配置信息
*/
public function __construct(array $config)
{
if (empty($config['appid'])) {
throw new \InvalidArgumentException('应用APPID不能为空');
}
if (empty($config['mchid'])) {
throw new \InvalidArgumentException("商户号不能为空");
}
if (empty($config['apikey'])) {
throw new \InvalidArgumentException("商户API密钥不能为空");
}
$this->appId = $config['appid'];
$this->mchId = $config['mchid'];
$this->apiKey = $config['apikey'];
$this->sslCertPath = $config['sslcert_path'];
$this->sslKeyPath = $config['sslkey_path'];
if (isset($config['sub_mchid'])) {
$this->subMchId = $config['sub_mchid'];
}
if (isset($config['sub_appid'])) {
$this->subAppId = $config['sub_appid'];
}
}
/**
* 请求接口并解析返回数据
* @param string $url url
* @param array $params 请求参数
* @param bool $cert 是否需要证书
* @return mixed
* @throws Exception
*/
public function execute(string $url, array $params, bool $cert = false)
{
$params = array_merge($this->publicParams, $params);
$params['sign'] = $this->makeSign($params);
$xml = $this->array2Xml($params);
$response = $this->curl($url, $xml, $cert);
$result = $this->xml2array($response);
if (isset($result['return_code']) && $result['return_code'] == 'SUCCESS') {
if (isset($result['result_code']) && $result['result_code'] == 'SUCCESS') {
if (isset($result['sign']) && !$this->checkSign($result)) {
throw new Exception('返回数据验签失败');
}
return $result;
}
}
throw new WeChatPayException($result);
}
/**
* 验签
* @param $data
* @return bool
*/
protected function checkSign($data): bool
{
if (!isset($data['sign'])) return false;
$sign = $this->makeSign($data);
return $sign === $data['sign'];
}
/**
* 生成签名
* @param $data
* @return string
*/
protected function makeSign($data): string
{
ksort($data);
$signStr = '';
foreach ($data as $k => $v) {
if($k != 'sign' && !is_array($v) && !$this->isEmpty($v)){
$signStr .= $k . '=' . $v . '&';
}
}
$signStr = trim($signStr, '&') . '&key=' . $this->apiKey;
if (isset($data['sign_type']) && $data['sign_type'] == 'HMAC-SHA256') {
$sign = hash_hmac("sha256", $signStr, $this->apiKey);
} else {
$sign = md5($signStr);
}
return strtoupper($sign);
}
/**
* 校验某字符串或可被转换为字符串的数据,是否为 NULL 或均为空白字符.
*
* @param string|null $value
*
* @return bool
*/
protected function isEmpty(?string $value): bool
{
return $value === null || $value === '';
}
/**
* 产生随机字符串不长于32位
* @param int $length
* @return string 产生的随机字符串
*/
protected function getNonceStr(int $length = 32): string
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 转为XML数据
* @param array $data 源数据
* @return string
*/
protected function array2Xml(array $data): string
{
$xml = '<xml>';
foreach ($data as $key => $val) {
$xml .= (is_numeric($val) ? "<{$key}>{$val}</{$key}>" : "<{$key}><![CDATA[{$val}]]></{$key}>");
}
return $xml . '</xml>';
}
/**
* 解析XML数据
* @param string $xml 源数据
* @return mixed
*/
protected function xml2array(string $xml)
{
if (!$xml) {
return false;
}
LIBXML_VERSION < 20900 && libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
}
/**
* 以post方式提交xml到对应的接口url
* @param string $url url
* @param mixed $xml 需要post的xml数据
* @param bool $useCert 是否需要证书
* @param int $second url执行超时时间
* @return string
* @throws Exception
*/
protected function curl(string $url, $xml, bool $useCert = false, int $second = 10): string
{
$ch = curl_init();
$curlVersion = curl_version();
$ua = "WXPaySDK/" . self::$VERSION . " (" . PHP_OS . ") PHP/" . PHP_VERSION . " CURL/" . $curlVersion['version'] . " ". $this->mchId;
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($useCert) {
if (!file_exists($this->sslCertPath) || !file_exists($this->sslKeyPath)) {
throw new Exception('商户证书文件不存在');
}
//使用证书cert 与 key 分别属于两个.pem文件
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, $this->sslCertPath);
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, $this->sslKeyPath);
}
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
$data = curl_exec($ch);
if (curl_errno($ch) > 0) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception($errmsg, 0);
}
curl_close($ch);
return $data;
}
}