SafePay.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. namespace App\Services;
  3. use App\Util;
  4. class SafePay
  5. {
  6. public $config;
  7. public $merNo;
  8. public $apiUrl;
  9. public $privateKey;
  10. public $platformPublicKey;
  11. public function __construct()
  12. {
  13. $payConfigService = new PayConfig();
  14. $this->config = $payConfigService->getConfig('SafePay');
  15. $this->merNo = $this->config['mer_no'] ?? '';
  16. $this->apiUrl = $this->config['apiUrl'] ?? 'https://api.safepay.wang';
  17. $this->privateKey = $this->config['private_key'] ?? '';
  18. $this->platformPublicKey = $this->config['platform_public_key'] ?? '';
  19. }
  20. /**
  21. * 构建待签名数据(ASCII升序、QueryString格式、跳过sign和空值)
  22. */
  23. public function buildSignData(array $params): string
  24. {
  25. // 移除sign字段和空值
  26. $signData = [];
  27. foreach ($params as $key => $value) {
  28. if ($key === 'sign') {
  29. continue;
  30. }
  31. if ($value === null || $value === '' || (is_array($value) && empty($value))) {
  32. continue;
  33. }
  34. if (is_array($value) || is_object($value)) {
  35. $signData[$key] = json_encode($value, JSON_UNESCAPED_UNICODE);
  36. } else {
  37. $signData[$key] = (string)$value;
  38. }
  39. }
  40. // ASCII升序(字典序)
  41. ksort($signData);
  42. // 构建 QueryString: key1=value1&key2=value2
  43. $result = '';
  44. foreach ($signData as $key => $value) {
  45. $result .= '&' . $key . '=' . $value;
  46. }
  47. return substr($result, 1);
  48. }
  49. /**
  50. * RSA SHA256 签名(使用商户私钥)
  51. *
  52. * @param array $params 请求参数
  53. * @return array 添加sign后的参数
  54. */
  55. public function sign(array $params): array
  56. {
  57. $signData = $this->buildSignData($params);
  58. $prvKeyPem = $this->convertPrivateKeyToPem($this->privateKey);
  59. $signature = '';
  60. $signed = openssl_sign($signData, $signature, $prvKeyPem, OPENSSL_ALGO_SHA256);
  61. if (!$signed) {
  62. throw new \Exception('SafePay RSA签名失败');
  63. }
  64. $params['sign'] = base64_encode($signature);
  65. Util::WriteLog('SafePay_sign', "待签名字符串: " . $signData);
  66. Util::WriteLog('SafePay_sign', "签名结果: " . $params['sign']);
  67. return $params;
  68. }
  69. /**
  70. * RSA SHA256 验签(使用平台公钥)
  71. *
  72. * @param array $params 回调参数(含sign)
  73. * @return bool
  74. */
  75. public function verifySign(array $params): bool
  76. {
  77. $receivedSign = $params['sign'] ?? '';
  78. if (empty($receivedSign)) {
  79. Util::WriteLog('SafePay', '验签失败:sign字段为空');
  80. return false;
  81. }
  82. $signData = $this->buildSignData($params);
  83. $pubKeyPem = $this->convertPublicKeyToPem($this->platformPublicKey);
  84. $isVerified = openssl_verify(
  85. $signData,
  86. base64_decode($receivedSign),
  87. $pubKeyPem,
  88. OPENSSL_ALGO_SHA256
  89. );
  90. Util::WriteLog('SafePay_verify', "验签数据: " . $signData);
  91. Util::WriteLog('SafePay_verify', "验签结果: " . ($isVerified === 1 ? '通过' : '失败'));
  92. return $isVerified === 1;
  93. }
  94. /**
  95. * 转换私钥为PEM格式
  96. */
  97. private function convertPrivateKeyToPem($privateKeyString)
  98. {
  99. if (strpos($privateKeyString, '-----BEGIN PRIVATE KEY-----') !== false) {
  100. return $privateKeyString;
  101. }
  102. $key = trim($privateKeyString);
  103. if (!preg_match('/^-----BEGIN/', $key)) {
  104. if (base64_decode($key, true)) {
  105. $key = base64_decode($key);
  106. }
  107. $key = "-----BEGIN PRIVATE KEY-----\n" . chunk_split(base64_encode($key), 64, "\n") . "-----END PRIVATE KEY-----";
  108. }
  109. return $key;
  110. }
  111. /**
  112. * 转换公钥为PEM格式
  113. */
  114. private function convertPublicKeyToPem($publicKeyString)
  115. {
  116. if (strpos($publicKeyString, '-----BEGIN PUBLIC KEY-----') !== false) {
  117. return $publicKeyString;
  118. }
  119. $key = trim($publicKeyString);
  120. if (!preg_match('/^-----BEGIN/', $key)) {
  121. if (base64_decode($key, true)) {
  122. $key = base64_decode($key);
  123. }
  124. $key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($key), 64, "\n") . "-----END PUBLIC KEY-----";
  125. }
  126. return $key;
  127. }
  128. /**
  129. * POST JSON请求
  130. */
  131. public function curlPost($url, $payload)
  132. {
  133. $timeout = 20;
  134. $data = json_encode($payload, JSON_UNESCAPED_UNICODE);
  135. $headers = [
  136. 'Content-Type: application/json',
  137. ];
  138. $ch = curl_init();
  139. curl_setopt($ch, CURLOPT_URL, $url);
  140. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  141. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  142. curl_setopt($ch, CURLOPT_POST, 1);
  143. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  144. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
  145. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  146. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  147. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  148. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  149. $result = curl_exec($ch);
  150. if (curl_errno($ch)) {
  151. $error = curl_error($ch);
  152. Util::WriteLog('SafePay_error', 'CURL Error: ' . $error);
  153. curl_close($ch);
  154. return false;
  155. }
  156. curl_close($ch);
  157. return $result;
  158. }
  159. }