BasicAliPay.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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 = $_POST;
  145. file_put_contents("aplopenvip1.txt", json_encode($data,true) . "\n" . "\n", FILE_APPEND);
  146. file_put_contents("aplopenvip2.txt", $data . "\n" . "\n", FILE_APPEND);
  147. if (empty($data) || empty($data['sign'])) {
  148. throw new InvalidResponseException('Illegal push request.', 0, $data);
  149. }
  150. $string = $this->getSignContent($data, $needSignType);
  151. $content = wordwrap($this->config->get('public_key'), 64, "\n", true);
  152. $res = "-----BEGIN PUBLIC KEY-----\n{$content}\n-----END PUBLIC KEY-----";
  153. if (openssl_verify($string, base64_decode($data['sign']), $res, OPENSSL_ALGO_SHA256) !== 1) {
  154. throw new InvalidResponseException('Data signature verification failed.', 0, $data);
  155. }
  156. return $data;
  157. }
  158. /**
  159. * 验证接口返回的数据签名
  160. * @param array $data 通知数据
  161. * @param null|string $sign 数据签名
  162. * @return array|boolean
  163. * @throws InvalidResponseException
  164. */
  165. protected function verify($data, $sign)
  166. {
  167. $content = wordwrap($this->config->get('public_key'), 64, "\n", true);
  168. $res = "-----BEGIN PUBLIC KEY-----\n{$content}\n-----END PUBLIC KEY-----";
  169. if ($this->options->get('sign_type') === 'RSA2') {
  170. if (openssl_verify(json_encode($data, 256), base64_decode($sign), $res, OPENSSL_ALGO_SHA256) !== 1) {
  171. throw new InvalidResponseException('Data signature verification failed.');
  172. }
  173. } else {
  174. if (openssl_verify(json_encode($data, 256), base64_decode($sign), $res, OPENSSL_ALGO_SHA1) !== 1) {
  175. throw new InvalidResponseException('Data signature verification failed.');
  176. }
  177. }
  178. return $data;
  179. }
  180. /**
  181. * 获取数据签名
  182. * @return string
  183. */
  184. protected function getSign()
  185. {
  186. $content = wordwrap($this->trimCert($this->config->get('private_key')), 64, "\n", true);
  187. $string = "-----BEGIN RSA PRIVATE KEY-----\n{$content}\n-----END RSA PRIVATE KEY-----";
  188. if ($this->options->get('sign_type') === 'RSA2') {
  189. openssl_sign($this->getSignContent($this->options->get(), true), $sign, $string, OPENSSL_ALGO_SHA256);
  190. } else {
  191. openssl_sign($this->getSignContent($this->options->get(), true), $sign, $string, OPENSSL_ALGO_SHA1);
  192. }
  193. return base64_encode($sign);
  194. }
  195. /**
  196. * 去除证书前后内容及空白
  197. * @param string $sign
  198. * @return string
  199. */
  200. protected function trimCert($sign)
  201. {
  202. // if (file_exists($sign)) $sign = file_get_contents($sign);
  203. return preg_replace(['/\s+/', '/\-{5}.*?\-{5}/'], '', $sign);
  204. }
  205. /**
  206. * 数据签名处理
  207. * @param array $data 需要进行签名数据
  208. * @param boolean $needSignType 是否需要sign_type字段
  209. * @return bool|string
  210. */
  211. private function getSignContent(array $data, $needSignType = false)
  212. {
  213. list($attrs,) = [[], ksort($data)];
  214. if (isset($data['sign'])) unset($data['sign']);
  215. if (empty($needSignType)) unset($data['sign_type']);
  216. foreach ($data as $key => $value) {
  217. if ($value === '' || is_null($value)) continue;
  218. array_push($attrs, "{$key}={$value}");
  219. }
  220. return join('&', $attrs);
  221. }
  222. /**
  223. * 数据包生成及数据签名
  224. * @param array $options
  225. */
  226. protected function applyData($options)
  227. {
  228. $this->options->set('biz_content', json_encode($this->params->merge($options), 256));
  229. $this->options->set('sign', $this->getSign());
  230. }
  231. /**
  232. * 请求接口并验证访问数据
  233. * @param array $options
  234. * @return array|boolean
  235. * @throws InvalidResponseException
  236. * @throws \WeChat\Exceptions\LocalCacheException
  237. */
  238. protected function getResult($options)
  239. {
  240. $this->applyData($options);
  241. $method = str_replace('.', '_', $this->options['method']) . '_response';
  242. $data = json_decode(Tools::get($this->gateway, $this->options->get()), true);
  243. if (!isset($data[$method]['code']) || $data[$method]['code'] !== '10000') {
  244. throw new InvalidResponseException(
  245. "Error: " .
  246. (empty($data[$method]['code']) ? '' : "{$data[$method]['msg']} [{$data[$method]['code']}]\r\n") .
  247. (empty($data[$method]['sub_code']) ? '' : "{$data[$method]['sub_msg']} [{$data[$method]['sub_code']}]\r\n"),
  248. $data[$method]['code'], $data
  249. );
  250. }
  251. return $data[$method];
  252. // 去除返回结果签名检查
  253. // return $this->verify($data[$method], $data['sign']);
  254. }
  255. /**
  256. * 生成支付HTML代码
  257. * @return string
  258. */
  259. protected function buildPayHtml()
  260. {
  261. $html = "<form id='alipaysubmit' name='alipaysubmit' action='{$this->gateway}' method='post'>";
  262. foreach ($this->options->get() as $key => $value) {
  263. $value = str_replace("'", '&apos;', $value);
  264. $html .= "<input type='hidden' name='{$key}' value='{$value}'/>";
  265. }
  266. $html .= "<input type='submit' value='ok' style='display:none;'></form>";
  267. return "{$html}<script>document.forms['alipaysubmit'].submit();</script>";
  268. }
  269. /**
  270. * 新版 从证书中提取序列号
  271. * @param string $sign
  272. * @return string
  273. */
  274. public function getCertSN($sign)
  275. {
  276. // if (file_exists($sign)) $sign = file_get_contents($sign);
  277. $ssl = openssl_x509_parse($sign);
  278. return md5($this->_arr2str(array_reverse($ssl['issuer'])) . $ssl['serialNumber']);
  279. }
  280. /**
  281. * 新版 提取根证书序列号
  282. * @param string $sign
  283. * @return string|null
  284. */
  285. public function getRootCertSN($sign)
  286. {
  287. $sn = null;
  288. // if (file_exists($sign)) $sign = file_get_contents($sign);
  289. $array = explode("-----END CERTIFICATE-----", $sign);
  290. for ($i = 0; $i < count($array) - 1; $i++) {
  291. $ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
  292. if (strpos($ssl[$i]['serialNumber'], '0x') === 0) {
  293. $ssl[$i]['serialNumber'] = $this->_hex2dec($ssl[$i]['serialNumber']);
  294. }
  295. if ($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption") {
  296. if ($sn == null) {
  297. $sn = md5($this->_arr2str(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
  298. } else {
  299. $sn = $sn . "_" . md5($this->_arr2str(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
  300. }
  301. }
  302. }
  303. return $sn;
  304. }
  305. /**
  306. * 新版 数组转字符串
  307. * @param array $array
  308. * @return string
  309. */
  310. private function _arr2str($array)
  311. {
  312. $string = [];
  313. if ($array && is_array($array)) {
  314. foreach ($array as $key => $value) {
  315. $string[] = $key . '=' . $value;
  316. }
  317. }
  318. return implode(',', $string);
  319. }
  320. /**
  321. * 新版 0x转高精度数字
  322. * @param string $hex
  323. * @return int|string
  324. */
  325. private function _hex2dec($hex)
  326. {
  327. list($dec, $len) = [0, strlen($hex)];
  328. for ($i = 1; $i <= $len; $i++) {
  329. $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
  330. }
  331. return $dec;
  332. }
  333. /**
  334. * 应用数据操作
  335. * @param array $options
  336. * @return mixed
  337. */
  338. abstract public function apply($options);
  339. }