RadarSignPlugin.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <?php
  2. declare(strict_types=1);
  3. namespace Yansongda\Pay\Plugin\Wechat;
  4. use Closure;
  5. use GuzzleHttp\Psr7\Utils;
  6. use Psr\Http\Message\RequestInterface;
  7. use Yansongda\Pay\Contract\PluginInterface;
  8. use Yansongda\Pay\Exception\ContainerException;
  9. use Yansongda\Pay\Exception\Exception;
  10. use Yansongda\Pay\Exception\InvalidConfigException;
  11. use Yansongda\Pay\Exception\InvalidParamsException;
  12. use Yansongda\Pay\Exception\ServiceNotFoundException;
  13. use Yansongda\Pay\Logger;
  14. use Yansongda\Pay\Packer\JsonPacker;
  15. use Yansongda\Pay\Packer\XmlPacker;
  16. use Yansongda\Pay\Rocket;
  17. use Yansongda\Supports\Collection;
  18. use Yansongda\Supports\Str;
  19. use function Yansongda\Pay\get_public_cert;
  20. use function Yansongda\Pay\get_wechat_config;
  21. use function Yansongda\Pay\get_wechat_sign;
  22. use function Yansongda\Pay\get_wechat_sign_v2;
  23. class RadarSignPlugin implements PluginInterface
  24. {
  25. protected JsonPacker $jsonPacker;
  26. protected XmlPacker $xmlPacker;
  27. public function __construct(?JsonPacker $jsonPacker = null, ?XmlPacker $xmlPacker = null)
  28. {
  29. $this->jsonPacker = $jsonPacker ?? new JsonPacker();
  30. $this->xmlPacker = $xmlPacker ?? new XmlPacker();
  31. }
  32. /**
  33. * @throws ContainerException
  34. * @throws InvalidConfigException
  35. * @throws InvalidParamsException
  36. * @throws ServiceNotFoundException
  37. */
  38. public function assembly(Rocket $rocket, Closure $next): Rocket
  39. {
  40. Logger::debug('[wechat][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
  41. switch ($rocket->getParams()['_version'] ?? 'default') {
  42. case 'v2':
  43. $radar = $this->v2($rocket);
  44. break;
  45. default:
  46. $radar = $this->v3($rocket);
  47. break;
  48. }
  49. $rocket->setRadar($radar);
  50. Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
  51. return $next($rocket);
  52. }
  53. /**
  54. * @throws ContainerException
  55. * @throws ServiceNotFoundException
  56. * @throws InvalidConfigException
  57. * @throws \Exception
  58. */
  59. protected function v2(Rocket $rocket): RequestInterface
  60. {
  61. $rocket->mergePayload(['nonce_str' => Str::random(32)]);
  62. $rocket->mergePayload([
  63. 'sign' => get_wechat_sign_v2($rocket->getParams(), $rocket->getPayload()->all()),
  64. ]);
  65. return $rocket->getRadar()->withBody(
  66. Utils::streamFor($this->xmlPacker->pack($rocket->getPayload()->all()))
  67. );
  68. }
  69. /**
  70. * @throws ContainerException
  71. * @throws InvalidConfigException
  72. * @throws InvalidParamsException
  73. * @throws ServiceNotFoundException
  74. * @throws \Exception
  75. */
  76. protected function v3(Rocket $rocket): RequestInterface
  77. {
  78. $timestamp = time();
  79. $random = Str::random(32);
  80. $body = $this->v3PayloadToString($rocket->getPayload());
  81. $contents = $this->v3GetContents($rocket, $timestamp, $random);
  82. $authorization = $this->v3GetWechatAuthorization($rocket->getParams(), $timestamp, $random, $contents);
  83. $radar = $rocket->getRadar()->withHeader('Authorization', $authorization);
  84. if (!empty($rocket->getParams()['_serial_no'])) {
  85. $radar = $radar->withHeader('Wechatpay-Serial', $rocket->getParams()['_serial_no']);
  86. }
  87. if (!empty($body)) {
  88. $radar = $radar->withBody(Utils::streamFor($body));
  89. }
  90. return $radar;
  91. }
  92. /**
  93. * @throws ContainerException
  94. * @throws InvalidConfigException
  95. * @throws ServiceNotFoundException
  96. */
  97. protected function v3GetWechatAuthorization(array $params, int $timestamp, string $random, string $contents): string
  98. {
  99. $config = get_wechat_config($params);
  100. $mchPublicCertPath = $config['mch_public_cert_path'] ?? null;
  101. if (empty($mchPublicCertPath)) {
  102. throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_public_cert_path]');
  103. }
  104. $ssl = openssl_x509_parse(get_public_cert($mchPublicCertPath));
  105. if (empty($ssl['serialNumberHex'])) {
  106. throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Parse [mch_public_cert_path] Serial Number Error');
  107. }
  108. $auth = sprintf(
  109. 'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
  110. $config['mch_id'] ?? '',
  111. $random,
  112. $timestamp,
  113. $ssl['serialNumberHex'],
  114. get_wechat_sign($params, $contents),
  115. );
  116. return 'WECHATPAY2-SHA256-RSA2048 '.$auth;
  117. }
  118. /**
  119. * @throws InvalidParamsException
  120. */
  121. protected function v3GetContents(Rocket $rocket, int $timestamp, string $random): string
  122. {
  123. $request = $rocket->getRadar();
  124. if (is_null($request)) {
  125. throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
  126. }
  127. $uri = $request->getUri();
  128. return $request->getMethod()."\n".
  129. $uri->getPath().(empty($uri->getQuery()) ? '' : '?'.$uri->getQuery())."\n".
  130. $timestamp."\n".
  131. $random."\n".
  132. $this->v3PayloadToString($rocket->getPayload())."\n";
  133. }
  134. protected function v3PayloadToString(?Collection $payload): string
  135. {
  136. return (is_null($payload) || 0 === $payload->count()) ? '' : $this->jsonPacker->pack($payload->all());
  137. }
  138. }