Client.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace crmeb\services\pay\extend\allinpay;
  12. use crmeb\exceptions\ApiException;
  13. use crmeb\services\HttpService;
  14. use think\facade\Log;
  15. /**
  16. * Class Client
  17. * @author 等风来
  18. * @email 136327134@qq.com
  19. * @date 2022/12/27
  20. * @package crmeb\services\pay\extend\allinpay
  21. */
  22. class Client
  23. {
  24. //生产地址
  25. const API_URL = 'https://vsp.allinpay.com/apiweb/';
  26. //测试接口地址
  27. const BETA_API_URL = 'https://syb-test.allinpay.com/apiweb/';
  28. //版本号
  29. const VERSION_NUM_11 = '11';
  30. //版本号
  31. const VERSION_NUM_12 = '12';
  32. protected $signType = 'MD5';
  33. /**
  34. * @var string
  35. */
  36. protected $cusid = '';
  37. /**
  38. * @var string
  39. */
  40. protected $appid = '';
  41. /**
  42. * @var string
  43. */
  44. protected $privateKey = '';
  45. /**
  46. * @var
  47. */
  48. protected $publicKey = '';
  49. /**
  50. * 回调地址
  51. * @var string
  52. */
  53. protected $notifyUrl = '';
  54. /**
  55. * 是否测试
  56. * @var bool
  57. */
  58. protected $isBeta = true;
  59. /**
  60. * debug模式
  61. * @var bool
  62. */
  63. protected $isDebug = true;
  64. /**
  65. * Client constructor.
  66. * @param array $config
  67. */
  68. public function __construct(array $config = [])
  69. {
  70. $this->appid = $config['appid'] ?? '';
  71. $this->cusid = $config['cusid'] ?? '';
  72. $this->privateKey = $config['privateKey'] ?? '';
  73. $this->publicKey = $config['publicKey'] ?? '';
  74. $this->notifyUrl = $config['notifyUrl'] ?? '';
  75. $this->isBeta = $config['isBeta'] ?? true;
  76. }
  77. /**
  78. * 发送请求
  79. * @param string $url
  80. * @param array $options
  81. * @return mixed
  82. */
  83. public function send(string $url, array $options = [])
  84. {
  85. $data = $options['data'] ?? [];
  86. $header = $options['header'] ?? [];
  87. $data['cusid'] = $this->cusid;
  88. $data['appid'] = $this->appid;
  89. if (!isset($data['version'])) {
  90. $data['version'] = self::VERSION_NUM_11;
  91. }
  92. $data['signtype'] = $this->signType;
  93. $data['randomstr'] = uniqid();
  94. if (!empty($options['form'])) {
  95. $data['charset'] = 'UTF-8';
  96. $data['version'] = self::VERSION_NUM_12;
  97. $data['sign'] = $this->sign($data);
  98. return $data;
  99. }
  100. $data['sign'] = $this->sign($data);
  101. $response = $this->request($url, $data, 'post', $header);
  102. if ('SUCCESS' !== $response['retcode']) {
  103. throw new ApiException($response['retmsg']);
  104. }
  105. if (!empty($response['trxstatus']) && !in_array($response['trxstatus'], ['0000', '2008', '2000'])) {
  106. throw new ApiException($response['errmsg']);
  107. }
  108. if ($this->validSign($response)) {
  109. return $response;
  110. }
  111. throw new ApiException('创建订单成功验签失败');
  112. }
  113. /**
  114. * @param string $url
  115. * @param array $data
  116. * @param string $method
  117. * @param array $header
  118. * @param int $timeout
  119. * @return mixed
  120. */
  121. public function request(string $url, array $data = [], string $method = 'post', array $header = [], int $timeout = 10)
  122. {
  123. $headerData = [];
  124. if ($header) {
  125. foreach ($header as $key => $item) {
  126. $headerData[] = $key . ':' . $item;
  127. }
  128. }
  129. $content = HttpService::request($this->baseUrl($url), $method, $data, $headerData, $timeout);
  130. $respones = json_decode($content, true, 512, JSON_BIGINT_AS_STRING);
  131. $this->debugLog('API response decoded:', ['content' => $respones, 'header' => $header]);
  132. if (JSON_ERROR_NONE !== json_last_error()) {
  133. throw new ApiException('Failed to parse JSON: ' . json_last_error_msg());
  134. }
  135. return $respones;
  136. }
  137. /**
  138. * @param string $message
  139. * @param array $contents
  140. */
  141. protected function debugLog(string $message, array $contents = [])
  142. {
  143. $this->isDebug && Log::debug($message, $contents);
  144. }
  145. /**
  146. * @param string|null $url
  147. * @return string
  148. */
  149. protected function baseUrl(string $url = null)
  150. {
  151. $baseUrl = $this->isBeta ? self::BETA_API_URL : self::API_URL;
  152. if ($url) {
  153. $baseUrl .= $url;
  154. }
  155. return $baseUrl;
  156. }
  157. /**
  158. * @param array $data
  159. * @return string
  160. */
  161. public function sign(array $data)
  162. {
  163. $private_key = $this->privateKey;
  164. if ($this->signType === 'MD5') {
  165. $data['key'] = $private_key;
  166. ksort($data);
  167. $bufSignSrc = $this->toUrlParams($data);
  168. return md5($bufSignSrc);
  169. } else {
  170. ksort($data);
  171. $bufSignSrc = $this->toUrlParams($data);
  172. $private_key = chunk_split($private_key, 64, "\n");
  173. $key = "-----BEGIN RSA PRIVATE KEY-----\n" . wordwrap($private_key) . "-----END RSA PRIVATE KEY-----";
  174. openssl_sign($bufSignSrc, $signature, $key);
  175. $sign = base64_encode($signature);//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
  176. return $sign;
  177. }
  178. }
  179. /**
  180. * @param array $data
  181. * @return string
  182. */
  183. public function toUrlParams(array $data)
  184. {
  185. $buff = "";
  186. foreach ($data as $k => $v) {
  187. if ($v != "" && !is_array($v)) {
  188. $buff .= $k . "=" . $v . "&";
  189. }
  190. }
  191. $buff = trim($buff, "&");
  192. return $buff;
  193. }
  194. /**
  195. * @param array $data
  196. * @return false|int
  197. */
  198. public function validSign(array $data)
  199. {
  200. $sign = $data['sign'];
  201. unset($data['sign']);
  202. if ($this->signType === 'MD5') {
  203. $data['key'] = $this->privateKey;
  204. ksort($data);
  205. $bufSignSrc = $this->toUrlParams($data);
  206. return strtolower($sign) == strtolower(md5($bufSignSrc));
  207. } else {
  208. ksort($data);
  209. $bufSignSrc = $this->toUrlParams($data);
  210. $public_key = $this->publicKey;
  211. $public_key = chunk_split($public_key, 64, "\n");
  212. $key = "-----BEGIN PUBLIC KEY-----\n$public_key-----END PUBLIC KEY-----\n";
  213. return openssl_verify($bufSignSrc, base64_decode($sign), $key);
  214. }
  215. }
  216. /**
  217. * @param string $notifyUrl
  218. * @return $this
  219. * @author 等风来
  220. * @email 136327134@qq.com
  221. * @date 2023/2/7
  222. */
  223. public function setNotifyUrl(string $notifyUrl)
  224. {
  225. $this->notifyUrl = $notifyUrl;
  226. return $this;
  227. }
  228. }