WDPay.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <?php
  2. namespace App\Services;
  3. use App\Util;
  4. class WDPay
  5. {
  6. public $config;
  7. public $key;
  8. public $customerNo;
  9. public $apiUrl;
  10. public function __construct()
  11. {
  12. $payConfigService = new PayConfig();
  13. $this->config = $payConfigService->getConfig('WDPay');
  14. $this->key = $this->config['key'] ?? '';
  15. $this->customerNo = $this->config['customerNo'] ?? '';
  16. // WDPay官方接口:/charging/create-pay-in
  17. $this->apiUrl = $this->config['apiUrl'] ?? '';
  18. }
  19. /**
  20. * 签名 - WDPay专用签名算法
  21. *
  22. * 规则:
  23. * 1. 按参数名ASCII码从小到大排序(字典序)
  24. * 2. 空值不参与签名
  25. * 3. sign参数不参与签名
  26. * 4. 拼接格式:key1=value1&key2=value2&key=商户密钥
  27. * 5. MD5后转大写
  28. */
  29. public function sign(array $params): array
  30. {
  31. // 移除sign和空值
  32. $signParams = [];
  33. foreach ($params as $key => $value) {
  34. if ($key !== 'sign' && $value !== null && $value !== '') {
  35. // 如果是对象或数组,需要递归处理
  36. if (is_array($value) || is_object($value)) {
  37. $signParams[$key] = $this->buildObjectString($value);
  38. } else {
  39. $signParams[$key] = $value;
  40. }
  41. }
  42. }
  43. // 按ASCII码排序
  44. ksort($signParams);
  45. // 拼接字符串:key1=value1&key2=value2
  46. $stringA = urldecode(http_build_query($signParams));
  47. // 拼接商户密钥:stringA&key=商户密钥
  48. $stringSignTemp = $stringA . '&key=' . $this->key;
  49. // MD5并转大写
  50. $sign = strtoupper(md5($stringSignTemp));
  51. // 将签名添加到参数中
  52. $params['sign'] = $sign;
  53. Util::WriteLog('WDPay_sign', "待签名字符串: " . $stringSignTemp);
  54. Util::WriteLog('WDPay_sign', "签名结果: " . $sign);
  55. return $params;
  56. }
  57. /**
  58. * 验签 - WDPay专用验签算法
  59. */
  60. public function verifySign(array $params): bool
  61. {
  62. if (!isset($params['sign'])) {
  63. return false;
  64. }
  65. $receivedSign = $params['sign'];
  66. // 移除sign和空值
  67. $signParams = [];
  68. foreach ($params as $key => $value) {
  69. if ($key !== 'sign' && $value !== null && $value !== '') {
  70. if (is_array($value) || is_object($value)) {
  71. $signParams[$key] = $this->buildObjectString($value);
  72. } else {
  73. $signParams[$key] = $value;
  74. }
  75. }
  76. }
  77. // 按ASCII码排序
  78. ksort($signParams);
  79. // 拼接字符串
  80. $stringA = urldecode(http_build_query($signParams));
  81. // 拼接商户密钥
  82. $stringSignTemp = $stringA . '&key=' . $this->key;
  83. // MD5并转大写
  84. $calculatedSign = strtoupper(md5($stringSignTemp));
  85. Util::WriteLog('WDPay_verify', "待验签字符串: " . $stringSignTemp);
  86. Util::WriteLog('WDPay_verify', "计算签名: " . $calculatedSign);
  87. Util::WriteLog('WDPay_verify', "接收签名: " . $receivedSign);
  88. return $calculatedSign === $receivedSign;
  89. }
  90. /**
  91. * 递归处理对象/数组为字符串
  92. */
  93. private function buildObjectString($data): string
  94. {
  95. if (is_array($data)) {
  96. $parts = [];
  97. ksort($data);
  98. foreach ($data as $k => $v) {
  99. if ($v !== null && $v !== '') {
  100. if (is_array($v) || is_object($v)) {
  101. $parts[] = $k . '=' . $this->buildObjectString($v);
  102. } else {
  103. $parts[] = $k . '=' . $v;
  104. }
  105. }
  106. }
  107. return implode('&', $parts);
  108. }
  109. return (string)$data;
  110. }
  111. /**
  112. * 生成随机字符串
  113. */
  114. public function getNonceStr($length = 16): string
  115. {
  116. $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
  117. $str = "";
  118. for ($i = 0; $i < $length; $i++) {
  119. $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
  120. }
  121. return $str;
  122. }
  123. /**
  124. * POST请求 - application/x-www-form-urlencoded
  125. */
  126. public function curlPost($url, $payload)
  127. {
  128. $timeout = 20;
  129. // WDPay使用 application/x-www-form-urlencoded 格式
  130. $data = http_build_query($payload);
  131. $headers = [
  132. 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
  133. ];
  134. $ch = curl_init();
  135. curl_setopt($ch, CURLOPT_URL, $url);
  136. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  137. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  138. curl_setopt($ch, CURLOPT_POST, 1);
  139. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  140. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
  141. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  142. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  143. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  144. $result = curl_exec($ch);
  145. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  146. if (curl_errno($ch)) {
  147. $error = curl_error($ch);
  148. Util::WriteLog('WDPay_error', 'CURL Error: ' . $error);
  149. curl_close($ch);
  150. return false;
  151. }
  152. if ($httpCode != 200) {
  153. Util::WriteLog('WDPay_error', 'HTTP Code: ' . $httpCode . " | Response: " . $result);
  154. }
  155. curl_close($ch);
  156. return $result;
  157. }
  158. }