BasicAliPay.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | WeChatDeveloper
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2014~2022 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://think.ctolog.com
  8. // +----------------------------------------------------------------------
  9. // | 开源协议 ( https://mit-license.org )
  10. // +----------------------------------------------------------------------
  11. // | github开源项目:https://github.com/zoujingli/WeChatDeveloper
  12. // +----------------------------------------------------------------------
  13. namespace WeChat\Contracts;
  14. use WeChat\Exceptions\InvalidArgumentException;
  15. use WeChat\Exceptions\InvalidResponseException;
  16. /**
  17. * 支付宝支付基类
  18. * Class AliPay
  19. * @package AliPay\Contracts
  20. */
  21. abstract class BasicAliPay
  22. {
  23. /**
  24. * 支持配置
  25. * @var DataArray
  26. */
  27. protected $config;
  28. /**
  29. * 当前请求数据
  30. * @var DataArray
  31. */
  32. protected $options;
  33. /**
  34. * DzContent数据
  35. * @var DataArray
  36. */
  37. protected $params;
  38. /**
  39. * 静态缓存
  40. * @var static
  41. */
  42. protected static $cache;
  43. /**
  44. * 正常请求网关
  45. * @var string
  46. */
  47. protected $gateway = 'https://openapi.alipay.com/gateway.do?charset=utf-8';
  48. /**
  49. * AliPay constructor.
  50. * @param array $options
  51. */
  52. public function __construct($options)
  53. {
  54. $this->params = new DataArray([]);
  55. $this->config = new DataArray($options);
  56. if (empty($options['appid'])) {
  57. throw new InvalidArgumentException("Missing Config -- [appid]");
  58. }
  59. if (empty($options['public_key'])) {
  60. throw new InvalidArgumentException("Missing Config -- [public_key]");
  61. }
  62. if (empty($options['private_key'])) {
  63. throw new InvalidArgumentException("Missing Config -- [private_key]");
  64. }
  65. if (!empty($options['debug'])) {
  66. $this->gateway = 'https://openapi.alipaydev.com/gateway.do?charset=utf-8';
  67. }
  68. $this->options = new DataArray([
  69. 'app_id' => $this->config->get('appid'),
  70. 'charset' => empty($options['charset']) ? 'utf-8' : $options['charset'],
  71. 'format' => 'JSON',
  72. 'version' => '1.0',
  73. 'sign_type' => empty($options['sign_type']) ? 'RSA2' : $options['sign_type'],
  74. 'timestamp' => date('Y-m-d H:i:s'),
  75. ]);
  76. if (isset($options['notify_url']) && $options['notify_url'] !== '') {
  77. $this->options->set('notify_url', $options['notify_url']);
  78. }
  79. if (isset($options['return_url']) && $options['return_url'] !== '') {
  80. $this->options->set('return_url', $options['return_url']);
  81. }
  82. if (isset($options['app_auth_token']) && $options['app_auth_token'] !== '') {
  83. $this->options->set('app_auth_token', $options['app_auth_token']);
  84. }
  85. }
  86. /**
  87. * 静态创建对象
  88. * @param array $config
  89. * @return static
  90. */
  91. public static function instance(array $config)
  92. {
  93. $key = md5(get_called_class() . serialize($config));
  94. if (isset(self::$cache[$key])) return self::$cache[$key];
  95. return self::$cache[$key] = new static($config);
  96. }
  97. /**
  98. * 查询支付宝订单状态
  99. * @param string $out_trade_no
  100. * @return array|boolean
  101. * @throws InvalidResponseException
  102. * @throws \WeChat\Exceptions\LocalCacheException
  103. */
  104. public function query($out_trade_no = '')
  105. {
  106. $this->options->set('method', 'alipay.trade.query');
  107. return $this->getResult(['out_trade_no' => $out_trade_no]);
  108. }
  109. /**
  110. * 支付宝订单退款操作
  111. * @param array|string $options 退款参数或退款商户订单号
  112. * @param null $refund_amount 退款金额
  113. * @return array|boolean
  114. * @throws InvalidResponseException
  115. * @throws \WeChat\Exceptions\LocalCacheException
  116. */
  117. public function refund($options, $refund_amount = null)
  118. {
  119. if (!is_array($options)) $options = ['out_trade_no' => $options, 'refund_amount' => $refund_amount];
  120. $this->options->set('method', 'alipay.trade.refund');
  121. return $this->getResult($options);
  122. }
  123. /**
  124. * 关闭支付宝进行中的订单
  125. * @param array|string $options
  126. * @return array|boolean
  127. * @throws InvalidResponseException
  128. * @throws \WeChat\Exceptions\LocalCacheException
  129. */
  130. public function close($options)
  131. {
  132. if (!is_array($options)) $options = ['out_trade_no' => $options];
  133. $this->options->set('method', 'alipay.trade.close');
  134. return $this->getResult($options);
  135. }
  136. /**
  137. * 获取通知数据
  138. * @param boolean $needSignType 是否需要sign_type字段
  139. * @return boolean|array
  140. * @throws InvalidResponseException
  141. */
  142. public function notify($needSignType = false)
  143. {
  144. $data = ['aa'=>1];
  145. echo 2222;die;
  146. file_put_contents('pay1.txt',json_encode($data));
  147. die;
  148. $data = $_POST;
  149. file_put_contents("aplopenvip1.txt", json_encode($data,true) . "\n" . "\n", FILE_APPEND);
  150. file_put_contents("aplopenvip2.txt", $data . "\n" . "\n", FILE_APPEND);
  151. if (empty($data) || empty($data['sign'])) {
  152. throw new InvalidResponseException('Illegal push request.', 0, $data);
  153. }
  154. $string = $this->getSignContent($data, $needSignType);
  155. $content = wordwrap($this->config->get('public_key'), 64, "\n", true);
  156. $res = "-----BEGIN PUBLIC KEY-----\n{$content}\n-----END PUBLIC KEY-----";
  157. if (openssl_verify($string, base64_decode($data['sign']), $res, OPENSSL_ALGO_SHA256) !== 1) {
  158. throw new InvalidResponseException('Data signature verification failed.', 0, $data);
  159. }
  160. return $data;
  161. }
  162. /**
  163. * 验证接口返回的数据签名
  164. * @param array $data 通知数据
  165. * @param null|string $sign 数据签名
  166. * @return array|boolean
  167. * @throws InvalidResponseException
  168. */
  169. protected function verify($data, $sign)
  170. {
  171. $content = wordwrap($this->config->get('public_key'), 64, "\n", true);
  172. $res = "-----BEGIN PUBLIC KEY-----\n{$content}\n-----END PUBLIC KEY-----";
  173. if ($this->options->get('sign_type') === 'RSA2') {
  174. if (openssl_verify(json_encode($data, 256), base64_decode($sign), $res, OPENSSL_ALGO_SHA256) !== 1) {
  175. throw new InvalidResponseException('Data signature verification failed.');
  176. }
  177. } else {
  178. if (openssl_verify(json_encode($data, 256), base64_decode($sign), $res, OPENSSL_ALGO_SHA1) !== 1) {
  179. throw new InvalidResponseException('Data signature verification failed.');
  180. }
  181. }
  182. return $data;
  183. }
  184. /**
  185. * 获取数据签名
  186. * @return string
  187. */
  188. protected function getSign()
  189. {
  190. $content = wordwrap($this->trimCert($this->config->get('private_key')), 64, "\n", true);
  191. $string = "-----BEGIN RSA PRIVATE KEY-----\n{$content}\n-----END RSA PRIVATE KEY-----";
  192. if ($this->options->get('sign_type') === 'RSA2') {
  193. openssl_sign($this->getSignContent($this->options->get(), true), $sign, $string, OPENSSL_ALGO_SHA256);
  194. } else {
  195. openssl_sign($this->getSignContent($this->options->get(), true), $sign, $string, OPENSSL_ALGO_SHA1);
  196. }
  197. return base64_encode($sign);
  198. }
  199. /**
  200. * 去除证书前后内容及空白
  201. * @param string $sign
  202. * @return string
  203. */
  204. protected function trimCert($sign)
  205. {
  206. // if (file_exists($sign)) $sign = file_get_contents($sign);
  207. return preg_replace(['/\s+/', '/\-{5}.*?\-{5}/'], '', $sign);
  208. }
  209. /**
  210. * 数据签名处理
  211. * @param array $data 需要进行签名数据
  212. * @param boolean $needSignType 是否需要sign_type字段
  213. * @return bool|string
  214. */
  215. private function getSignContent(array $data, $needSignType = false)
  216. {
  217. list($attrs,) = [[], ksort($data)];
  218. if (isset($data['sign'])) unset($data['sign']);
  219. if (empty($needSignType)) unset($data['sign_type']);
  220. foreach ($data as $key => $value) {
  221. if ($value === '' || is_null($value)) continue;
  222. array_push($attrs, "{$key}={$value}");
  223. }
  224. return join('&', $attrs);
  225. }
  226. /**
  227. * 数据包生成及数据签名
  228. * @param array $options
  229. */
  230. protected function applyData($options)
  231. {
  232. $this->options->set('biz_content', json_encode($this->params->merge($options), 256));
  233. $this->options->set('sign', $this->getSign());
  234. }
  235. /**
  236. * 请求接口并验证访问数据
  237. * @param array $options
  238. * @return array|boolean
  239. * @throws InvalidResponseException
  240. * @throws \WeChat\Exceptions\LocalCacheException
  241. */
  242. protected function getResult($options)
  243. {
  244. $this->applyData($options);
  245. $method = str_replace('.', '_', $this->options['method']) . '_response';
  246. $data = json_decode(Tools::get($this->gateway, $this->options->get()), true);
  247. if (!isset($data[$method]['code']) || $data[$method]['code'] !== '10000') {
  248. throw new InvalidResponseException(
  249. "Error: " .
  250. (empty($data[$method]['code']) ? '' : "{$data[$method]['msg']} [{$data[$method]['code']}]\r\n") .
  251. (empty($data[$method]['sub_code']) ? '' : "{$data[$method]['sub_msg']} [{$data[$method]['sub_code']}]\r\n"),
  252. $data[$method]['code'], $data
  253. );
  254. }
  255. return $data[$method];
  256. // 去除返回结果签名检查
  257. // return $this->verify($data[$method], $data['sign']);
  258. }
  259. /**
  260. * 生成支付HTML代码
  261. * @return string
  262. */
  263. protected function buildPayHtml()
  264. {
  265. $html = "<form id='alipaysubmit' name='alipaysubmit' action='{$this->gateway}' method='post'>";
  266. foreach ($this->options->get() as $key => $value) {
  267. $value = str_replace("'", '&apos;', $value);
  268. $html .= "<input type='hidden' name='{$key}' value='{$value}'/>";
  269. }
  270. $html .= "<input type='submit' value='ok' style='display:none;'></form>";
  271. return "{$html}<script>document.forms['alipaysubmit'].submit();</script>";
  272. }
  273. /**
  274. * 新版 从证书中提取序列号
  275. * @param string $sign
  276. * @return string
  277. */
  278. public function getCertSN($sign)
  279. {
  280. // if (file_exists($sign)) $sign = file_get_contents($sign);
  281. $ssl = openssl_x509_parse($sign);
  282. return md5($this->_arr2str(array_reverse($ssl['issuer'])) . $ssl['serialNumber']);
  283. }
  284. /**
  285. * 新版 提取根证书序列号
  286. * @param string $sign
  287. * @return string|null
  288. */
  289. public function getRootCertSN($sign)
  290. {
  291. $sn = null;
  292. // if (file_exists($sign)) $sign = file_get_contents($sign);
  293. $array = explode("-----END CERTIFICATE-----", $sign);
  294. for ($i = 0; $i < count($array) - 1; $i++) {
  295. $ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
  296. if (strpos($ssl[$i]['serialNumber'], '0x') === 0) {
  297. $ssl[$i]['serialNumber'] = $this->_hex2dec($ssl[$i]['serialNumber']);
  298. }
  299. if ($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption") {
  300. if ($sn == null) {
  301. $sn = md5($this->_arr2str(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
  302. } else {
  303. $sn = $sn . "_" . md5($this->_arr2str(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
  304. }
  305. }
  306. }
  307. return $sn;
  308. }
  309. /**
  310. * 新版 数组转字符串
  311. * @param array $array
  312. * @return string
  313. */
  314. private function _arr2str($array)
  315. {
  316. $string = [];
  317. if ($array && is_array($array)) {
  318. foreach ($array as $key => $value) {
  319. $string[] = $key . '=' . $value;
  320. }
  321. }
  322. return implode(',', $string);
  323. }
  324. /**
  325. * 新版 0x转高精度数字
  326. * @param string $hex
  327. * @return int|string
  328. */
  329. private function _hex2dec($hex)
  330. {
  331. list($dec, $len) = [0, strlen($hex)];
  332. for ($i = 1; $i <= $len; $i++) {
  333. $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
  334. }
  335. return $dec;
  336. }
  337. /**
  338. * 应用数据操作
  339. * @param array $options
  340. * @return mixed
  341. */
  342. abstract public function apply($options);
  343. }