171 lines
5.3 KiB
PHP
171 lines
5.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Yansongda\Pay\Plugin\Wechat;
|
|
|
|
use Closure;
|
|
use GuzzleHttp\Psr7\Utils;
|
|
use Psr\Http\Message\RequestInterface;
|
|
use Yansongda\Pay\Contract\PluginInterface;
|
|
use Yansongda\Pay\Exception\ContainerException;
|
|
use Yansongda\Pay\Exception\Exception;
|
|
use Yansongda\Pay\Exception\InvalidConfigException;
|
|
use Yansongda\Pay\Exception\InvalidParamsException;
|
|
use Yansongda\Pay\Exception\ServiceNotFoundException;
|
|
use Yansongda\Pay\Logger;
|
|
use Yansongda\Pay\Packer\JsonPacker;
|
|
use Yansongda\Pay\Packer\XmlPacker;
|
|
use Yansongda\Pay\Rocket;
|
|
use Yansongda\Supports\Collection;
|
|
use Yansongda\Supports\Str;
|
|
|
|
use function Yansongda\Pay\get_public_cert;
|
|
use function Yansongda\Pay\get_wechat_config;
|
|
use function Yansongda\Pay\get_wechat_sign;
|
|
use function Yansongda\Pay\get_wechat_sign_v2;
|
|
|
|
class RadarSignPlugin implements PluginInterface
|
|
{
|
|
protected JsonPacker $jsonPacker;
|
|
|
|
protected XmlPacker $xmlPacker;
|
|
|
|
public function __construct(?JsonPacker $jsonPacker = null, ?XmlPacker $xmlPacker = null)
|
|
{
|
|
$this->jsonPacker = $jsonPacker ?? new JsonPacker();
|
|
$this->xmlPacker = $xmlPacker ?? new XmlPacker();
|
|
}
|
|
|
|
/**
|
|
* @throws ContainerException
|
|
* @throws InvalidConfigException
|
|
* @throws InvalidParamsException
|
|
* @throws ServiceNotFoundException
|
|
*/
|
|
public function assembly(Rocket $rocket, Closure $next): Rocket
|
|
{
|
|
Logger::debug('[wechat][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
|
|
|
|
switch ($rocket->getParams()['_version'] ?? 'default') {
|
|
case 'v2':
|
|
$radar = $this->v2($rocket);
|
|
|
|
break;
|
|
|
|
default:
|
|
$radar = $this->v3($rocket);
|
|
|
|
break;
|
|
}
|
|
|
|
$rocket->setRadar($radar);
|
|
|
|
Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
|
|
|
|
return $next($rocket);
|
|
}
|
|
|
|
/**
|
|
* @throws ContainerException
|
|
* @throws ServiceNotFoundException
|
|
* @throws InvalidConfigException
|
|
* @throws \Exception
|
|
*/
|
|
protected function v2(Rocket $rocket): RequestInterface
|
|
{
|
|
$rocket->mergePayload(['nonce_str' => Str::random(32)]);
|
|
$rocket->mergePayload([
|
|
'sign' => get_wechat_sign_v2($rocket->getParams(), $rocket->getPayload()->all()),
|
|
]);
|
|
|
|
return $rocket->getRadar()->withBody(
|
|
Utils::streamFor($this->xmlPacker->pack($rocket->getPayload()->all()))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @throws ContainerException
|
|
* @throws InvalidConfigException
|
|
* @throws InvalidParamsException
|
|
* @throws ServiceNotFoundException
|
|
* @throws \Exception
|
|
*/
|
|
protected function v3(Rocket $rocket): RequestInterface
|
|
{
|
|
$timestamp = time();
|
|
$random = Str::random(32);
|
|
$body = $this->v3PayloadToString($rocket->getPayload());
|
|
$contents = $this->v3GetContents($rocket, $timestamp, $random);
|
|
$authorization = $this->v3GetWechatAuthorization($rocket->getParams(), $timestamp, $random, $contents);
|
|
$radar = $rocket->getRadar()->withHeader('Authorization', $authorization);
|
|
|
|
if (!empty($rocket->getParams()['_serial_no'])) {
|
|
$radar = $radar->withHeader('Wechatpay-Serial', $rocket->getParams()['_serial_no']);
|
|
}
|
|
|
|
if (!empty($body)) {
|
|
$radar = $radar->withBody(Utils::streamFor($body));
|
|
}
|
|
|
|
return $radar;
|
|
}
|
|
|
|
/**
|
|
* @throws ContainerException
|
|
* @throws InvalidConfigException
|
|
* @throws ServiceNotFoundException
|
|
*/
|
|
protected function v3GetWechatAuthorization(array $params, int $timestamp, string $random, string $contents): string
|
|
{
|
|
$config = get_wechat_config($params);
|
|
$mchPublicCertPath = $config['mch_public_cert_path'] ?? null;
|
|
|
|
if (empty($mchPublicCertPath)) {
|
|
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_public_cert_path]');
|
|
}
|
|
|
|
$ssl = openssl_x509_parse(get_public_cert($mchPublicCertPath));
|
|
|
|
if (empty($ssl['serialNumberHex'])) {
|
|
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Parse [mch_public_cert_path] Serial Number Error');
|
|
}
|
|
|
|
$auth = sprintf(
|
|
'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
|
|
$config['mch_id'] ?? '',
|
|
$random,
|
|
$timestamp,
|
|
$ssl['serialNumberHex'],
|
|
get_wechat_sign($params, $contents),
|
|
);
|
|
|
|
return 'WECHATPAY2-SHA256-RSA2048 '.$auth;
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidParamsException
|
|
*/
|
|
protected function v3GetContents(Rocket $rocket, int $timestamp, string $random): string
|
|
{
|
|
$request = $rocket->getRadar();
|
|
|
|
if (is_null($request)) {
|
|
throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
|
|
}
|
|
|
|
$uri = $request->getUri();
|
|
|
|
return $request->getMethod()."\n".
|
|
$uri->getPath().(empty($uri->getQuery()) ? '' : '?'.$uri->getQuery())."\n".
|
|
$timestamp."\n".
|
|
$random."\n".
|
|
$this->v3PayloadToString($rocket->getPayload())."\n";
|
|
}
|
|
|
|
protected function v3PayloadToString(?Collection $payload): string
|
|
{
|
|
return (is_null($payload) || 0 === $payload->count()) ? '' : $this->jsonPacker->pack($payload->all());
|
|
}
|
|
}
|