StartPlugin.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <?php
  2. declare(strict_types=1);
  3. namespace Yansongda\Pay\Plugin\Alipay\V2;
  4. use Closure;
  5. use Yansongda\Artful\Contract\ConfigInterface;
  6. use Yansongda\Artful\Contract\PluginInterface;
  7. use Yansongda\Artful\Exception\ContainerException;
  8. use Yansongda\Artful\Exception\InvalidConfigException;
  9. use Yansongda\Artful\Exception\ServiceNotFoundException;
  10. use Yansongda\Artful\Logger;
  11. use Yansongda\Artful\Rocket;
  12. use Yansongda\Pay\Exception\Exception;
  13. use Yansongda\Pay\Pay;
  14. use function Yansongda\Pay\get_provider_config;
  15. use function Yansongda\Pay\get_public_cert;
  16. use function Yansongda\Pay\get_tenant;
  17. class StartPlugin implements PluginInterface
  18. {
  19. /**
  20. * @throws ContainerException
  21. * @throws ServiceNotFoundException
  22. * @throws InvalidConfigException
  23. */
  24. public function assembly(Rocket $rocket, Closure $next): Rocket
  25. {
  26. Logger::debug('[Alipay][StartPlugin] 插件开始装载', ['rocket' => $rocket]);
  27. $rocket->mergePayload($this->getPayload($rocket->getParams()));
  28. Logger::info('[Alipay][StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
  29. return $next($rocket);
  30. }
  31. /**
  32. * @throws ContainerException
  33. * @throws ServiceNotFoundException
  34. * @throws InvalidConfigException
  35. */
  36. protected function getPayload(array $params): array
  37. {
  38. $tenant = get_tenant($params);
  39. $config = get_provider_config('alipay', $params);
  40. return [
  41. 'app_id' => $config['app_id'] ?? '',
  42. 'method' => '',
  43. 'format' => 'JSON',
  44. 'return_url' => $this->getReturnUrl($params, $config),
  45. 'charset' => 'utf-8',
  46. 'sign_type' => 'RSA2',
  47. 'sign' => '',
  48. 'timestamp' => date('Y-m-d H:i:s'),
  49. 'version' => '1.0',
  50. 'notify_url' => $this->getNotifyUrl($params, $config),
  51. 'app_auth_token' => $this->getAppAuthToken($params, $config),
  52. 'app_cert_sn' => $this->getAppCertSn($tenant, $config),
  53. 'alipay_root_cert_sn' => $this->getAlipayRootCertSn($tenant, $config),
  54. 'biz_content' => [],
  55. ];
  56. }
  57. protected function getReturnUrl(array $params, array $config): string
  58. {
  59. if (!empty($params['_return_url'])) {
  60. return $params['_return_url'];
  61. }
  62. return $config['return_url'] ?? '';
  63. }
  64. protected function getNotifyUrl(array $params, array $config): string
  65. {
  66. if (!empty($params['_notify_url'])) {
  67. return $params['_notify_url'];
  68. }
  69. return $config['notify_url'] ?? '';
  70. }
  71. protected function getAppAuthToken(array $params, array $config): string
  72. {
  73. if (!empty($params['_app_auth_token'])) {
  74. return $params['_app_auth_token'];
  75. }
  76. return $config['app_auth_token'] ?? '';
  77. }
  78. /**
  79. * @throws ContainerException
  80. * @throws InvalidConfigException
  81. * @throws ServiceNotFoundException
  82. */
  83. protected function getAppCertSn(string $tenant, array $config): string
  84. {
  85. if (!empty($config['app_public_cert_sn'])) {
  86. return $config['app_public_cert_sn'];
  87. }
  88. $path = $config['app_public_cert_path'] ?? null;
  89. if (is_null($path)) {
  90. throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [app_public_cert_path]');
  91. }
  92. $ssl = openssl_x509_parse(get_public_cert($path));
  93. if (false === $ssl) {
  94. throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 解析 `app_public_cert_path` 失败');
  95. }
  96. $result = $this->getCertSn($ssl['issuer'] ?? [], $ssl['serialNumber'] ?? '');
  97. Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.app_public_cert_sn', $result);
  98. return $result;
  99. }
  100. /**
  101. * @throws ContainerException
  102. * @throws InvalidConfigException
  103. * @throws ServiceNotFoundException
  104. */
  105. protected function getAlipayRootCertSn(string $tenant, array $config): string
  106. {
  107. if (!empty($config['alipay_root_cert_sn'])) {
  108. return $config['alipay_root_cert_sn'];
  109. }
  110. $path = $config['alipay_root_cert_path'] ?? null;
  111. if (is_null($path)) {
  112. throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [alipay_root_cert_path]');
  113. }
  114. $sn = '';
  115. $exploded = explode('-----END CERTIFICATE-----', get_public_cert($path));
  116. foreach ($exploded as $cert) {
  117. if (empty(trim($cert))) {
  118. continue;
  119. }
  120. $ssl = openssl_x509_parse($cert.'-----END CERTIFICATE-----');
  121. if (false === $ssl) {
  122. throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 解析 `alipay_root_cert` 失败');
  123. }
  124. $detail = $this->formatCert($ssl);
  125. if ('sha1WithRSAEncryption' == $detail['signatureTypeLN'] || 'sha256WithRSAEncryption' == $detail['signatureTypeLN']) {
  126. $sn .= $this->getCertSn($detail['issuer'], $detail['serialNumber']).'_';
  127. }
  128. }
  129. $result = substr($sn, 0, -1);
  130. Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.alipay_root_cert_sn', $result);
  131. return $result;
  132. }
  133. protected function getCertSn(array $issuer, string $serialNumber): string
  134. {
  135. return md5($this->array2string(array_reverse($issuer)).$serialNumber);
  136. }
  137. protected function array2string(array $array): string
  138. {
  139. $string = [];
  140. foreach ($array as $key => $value) {
  141. $string[] = $key.'='.$value;
  142. }
  143. return implode(',', $string);
  144. }
  145. protected function formatCert(array $ssl): array
  146. {
  147. if (str_starts_with($ssl['serialNumber'] ?? '', '0x')) {
  148. $ssl['serialNumber'] = $this->hex2dec($ssl['serialNumberHex'] ?? '');
  149. }
  150. return $ssl;
  151. }
  152. protected function hex2dec(string $hex): string
  153. {
  154. $dec = '0';
  155. $len = strlen($hex);
  156. for ($i = 1; $i <= $len; ++$i) {
  157. $dec = bcadd(
  158. $dec,
  159. bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i), 0), 0),
  160. 0
  161. );
  162. }
  163. return $dec;
  164. }
  165. }