123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- <?php
- // +----------------------------------------------------------------------
- // | WeChatDeveloper
- // +----------------------------------------------------------------------
- // | 版权所有 2014~2022 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
- // +----------------------------------------------------------------------
- // | 官方网站: http://think.ctolog.com
- // +----------------------------------------------------------------------
- // | 开源协议 ( https://mit-license.org )
- // +----------------------------------------------------------------------
- // | github开源项目:https://github.com/zoujingli/WeChatDeveloper
- // +----------------------------------------------------------------------
- namespace WeChat\Contracts;
- use WeChat\Exceptions\InvalidArgumentException;
- use WeChat\Exceptions\InvalidResponseException;
- /**
- * 支付宝支付基类
- * Class AliPay
- * @package AliPay\Contracts
- */
- abstract class BasicAliPay
- {
- /**
- * 支持配置
- * @var DataArray
- */
- protected $config;
- /**
- * 当前请求数据
- * @var DataArray
- */
- protected $options;
- /**
- * DzContent数据
- * @var DataArray
- */
- protected $params;
- /**
- * 静态缓存
- * @var static
- */
- protected static $cache;
- /**
- * 正常请求网关
- * @var string
- */
- protected $gateway = 'https://openapi.alipay.com/gateway.do?charset=utf-8';
- /**
- * AliPay constructor.
- * @param array $options
- */
- public function __construct($options)
- {
- $this->params = new DataArray([]);
- $this->config = new DataArray($options);
- if (empty($options['appid'])) {
- throw new InvalidArgumentException("Missing Config -- [appid]");
- }
- if (empty($options['public_key'])) {
- throw new InvalidArgumentException("Missing Config -- [public_key]");
- }
- if (empty($options['private_key'])) {
- throw new InvalidArgumentException("Missing Config -- [private_key]");
- }
- if (!empty($options['debug'])) {
- $this->gateway = 'https://openapi.alipaydev.com/gateway.do?charset=utf-8';
- }
- $this->options = new DataArray([
- 'app_id' => $this->config->get('appid'),
- 'charset' => empty($options['charset']) ? 'utf-8' : $options['charset'],
- 'format' => 'JSON',
- 'version' => '1.0',
- 'sign_type' => empty($options['sign_type']) ? 'RSA2' : $options['sign_type'],
- 'timestamp' => date('Y-m-d H:i:s'),
- ]);
- if (isset($options['notify_url']) && $options['notify_url'] !== '') {
- $this->options->set('notify_url', $options['notify_url']);
- }
- if (isset($options['return_url']) && $options['return_url'] !== '') {
- $this->options->set('return_url', $options['return_url']);
- }
- if (isset($options['app_auth_token']) && $options['app_auth_token'] !== '') {
- $this->options->set('app_auth_token', $options['app_auth_token']);
- }
- }
- /**
- * 静态创建对象
- * @param array $config
- * @return static
- */
- public static function instance(array $config)
- {
- $key = md5(get_called_class() . serialize($config));
- if (isset(self::$cache[$key])) return self::$cache[$key];
- return self::$cache[$key] = new static($config);
- }
- /**
- * 查询支付宝订单状态
- * @param string $out_trade_no
- * @return array|boolean
- * @throws InvalidResponseException
- * @throws \WeChat\Exceptions\LocalCacheException
- */
- public function query($out_trade_no = '')
- {
- $this->options->set('method', 'alipay.trade.query');
- return $this->getResult(['out_trade_no' => $out_trade_no]);
- }
- /**
- * 支付宝订单退款操作
- * @param array|string $options 退款参数或退款商户订单号
- * @param null $refund_amount 退款金额
- * @return array|boolean
- * @throws InvalidResponseException
- * @throws \WeChat\Exceptions\LocalCacheException
- */
- public function refund($options, $refund_amount = null)
- {
- if (!is_array($options)) $options = ['out_trade_no' => $options, 'refund_amount' => $refund_amount];
- $this->options->set('method', 'alipay.trade.refund');
- return $this->getResult($options);
- }
- /**
- * 关闭支付宝进行中的订单
- * @param array|string $options
- * @return array|boolean
- * @throws InvalidResponseException
- * @throws \WeChat\Exceptions\LocalCacheException
- */
- public function close($options)
- {
- if (!is_array($options)) $options = ['out_trade_no' => $options];
- $this->options->set('method', 'alipay.trade.close');
- return $this->getResult($options);
- }
- /**
- * 获取通知数据
- * @param boolean $needSignType 是否需要sign_type字段
- * @return boolean|array
- * @throws InvalidResponseException
- */
- public function notify($needSignType = false)
- {
- $data = $_POST;
- file_put_contents("aplopenvip1.txt", json_encode($data,true) . "\n" . "\n", FILE_APPEND);
- file_put_contents("aplopenvip2.txt", $data . "\n" . "\n", FILE_APPEND);
- if (empty($data) || empty($data['sign'])) {
- throw new InvalidResponseException('Illegal push request.', 0, $data);
- }
- $string = $this->getSignContent($data, $needSignType);
- $content = wordwrap($this->config->get('public_key'), 64, "\n", true);
- $res = "-----BEGIN PUBLIC KEY-----\n{$content}\n-----END PUBLIC KEY-----";
- if (openssl_verify($string, base64_decode($data['sign']), $res, OPENSSL_ALGO_SHA256) !== 1) {
- throw new InvalidResponseException('Data signature verification failed.', 0, $data);
- }
- return $data;
- }
- /**
- * 验证接口返回的数据签名
- * @param array $data 通知数据
- * @param null|string $sign 数据签名
- * @return array|boolean
- * @throws InvalidResponseException
- */
- protected function verify($data, $sign)
- {
- $content = wordwrap($this->config->get('public_key'), 64, "\n", true);
- $res = "-----BEGIN PUBLIC KEY-----\n{$content}\n-----END PUBLIC KEY-----";
- if ($this->options->get('sign_type') === 'RSA2') {
- if (openssl_verify(json_encode($data, 256), base64_decode($sign), $res, OPENSSL_ALGO_SHA256) !== 1) {
- throw new InvalidResponseException('Data signature verification failed.');
- }
- } else {
- if (openssl_verify(json_encode($data, 256), base64_decode($sign), $res, OPENSSL_ALGO_SHA1) !== 1) {
- throw new InvalidResponseException('Data signature verification failed.');
- }
- }
- return $data;
- }
- /**
- * 获取数据签名
- * @return string
- */
- protected function getSign()
- {
- $content = wordwrap($this->trimCert($this->config->get('private_key')), 64, "\n", true);
- $string = "-----BEGIN RSA PRIVATE KEY-----\n{$content}\n-----END RSA PRIVATE KEY-----";
- if ($this->options->get('sign_type') === 'RSA2') {
- openssl_sign($this->getSignContent($this->options->get(), true), $sign, $string, OPENSSL_ALGO_SHA256);
- } else {
- openssl_sign($this->getSignContent($this->options->get(), true), $sign, $string, OPENSSL_ALGO_SHA1);
- }
- return base64_encode($sign);
- }
- /**
- * 去除证书前后内容及空白
- * @param string $sign
- * @return string
- */
- protected function trimCert($sign)
- {
- // if (file_exists($sign)) $sign = file_get_contents($sign);
- return preg_replace(['/\s+/', '/\-{5}.*?\-{5}/'], '', $sign);
- }
- /**
- * 数据签名处理
- * @param array $data 需要进行签名数据
- * @param boolean $needSignType 是否需要sign_type字段
- * @return bool|string
- */
- private function getSignContent(array $data, $needSignType = false)
- {
- list($attrs,) = [[], ksort($data)];
- if (isset($data['sign'])) unset($data['sign']);
- if (empty($needSignType)) unset($data['sign_type']);
- foreach ($data as $key => $value) {
- if ($value === '' || is_null($value)) continue;
- array_push($attrs, "{$key}={$value}");
- }
- return join('&', $attrs);
- }
- /**
- * 数据包生成及数据签名
- * @param array $options
- */
- protected function applyData($options)
- {
- $this->options->set('biz_content', json_encode($this->params->merge($options), 256));
- $this->options->set('sign', $this->getSign());
- }
- /**
- * 请求接口并验证访问数据
- * @param array $options
- * @return array|boolean
- * @throws InvalidResponseException
- * @throws \WeChat\Exceptions\LocalCacheException
- */
- protected function getResult($options)
- {
- $this->applyData($options);
- $method = str_replace('.', '_', $this->options['method']) . '_response';
- $data = json_decode(Tools::get($this->gateway, $this->options->get()), true);
- if (!isset($data[$method]['code']) || $data[$method]['code'] !== '10000') {
- throw new InvalidResponseException(
- "Error: " .
- (empty($data[$method]['code']) ? '' : "{$data[$method]['msg']} [{$data[$method]['code']}]\r\n") .
- (empty($data[$method]['sub_code']) ? '' : "{$data[$method]['sub_msg']} [{$data[$method]['sub_code']}]\r\n"),
- $data[$method]['code'], $data
- );
- }
- return $data[$method];
- // 去除返回结果签名检查
- // return $this->verify($data[$method], $data['sign']);
- }
- /**
- * 生成支付HTML代码
- * @return string
- */
- protected function buildPayHtml()
- {
- $html = "<form id='alipaysubmit' name='alipaysubmit' action='{$this->gateway}' method='post'>";
- foreach ($this->options->get() as $key => $value) {
- $value = str_replace("'", ''', $value);
- $html .= "<input type='hidden' name='{$key}' value='{$value}'/>";
- }
- $html .= "<input type='submit' value='ok' style='display:none;'></form>";
- return "{$html}<script>document.forms['alipaysubmit'].submit();</script>";
- }
- /**
- * 新版 从证书中提取序列号
- * @param string $sign
- * @return string
- */
- public function getCertSN($sign)
- {
- // if (file_exists($sign)) $sign = file_get_contents($sign);
- $ssl = openssl_x509_parse($sign);
- return md5($this->_arr2str(array_reverse($ssl['issuer'])) . $ssl['serialNumber']);
- }
- /**
- * 新版 提取根证书序列号
- * @param string $sign
- * @return string|null
- */
- public function getRootCertSN($sign)
- {
- $sn = null;
- // if (file_exists($sign)) $sign = file_get_contents($sign);
- $array = explode("-----END CERTIFICATE-----", $sign);
- for ($i = 0; $i < count($array) - 1; $i++) {
- $ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
- if (strpos($ssl[$i]['serialNumber'], '0x') === 0) {
- $ssl[$i]['serialNumber'] = $this->_hex2dec($ssl[$i]['serialNumber']);
- }
- if ($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption") {
- if ($sn == null) {
- $sn = md5($this->_arr2str(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
- } else {
- $sn = $sn . "_" . md5($this->_arr2str(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
- }
- }
- }
- return $sn;
- }
- /**
- * 新版 数组转字符串
- * @param array $array
- * @return string
- */
- private function _arr2str($array)
- {
- $string = [];
- if ($array && is_array($array)) {
- foreach ($array as $key => $value) {
- $string[] = $key . '=' . $value;
- }
- }
- return implode(',', $string);
- }
- /**
- * 新版 0x转高精度数字
- * @param string $hex
- * @return int|string
- */
- private function _hex2dec($hex)
- {
- list($dec, $len) = [0, strlen($hex)];
- for ($i = 1; $i <= $len; $i++) {
- $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
- }
- return $dec;
- }
- /**
- * 应用数据操作
- * @param array $options
- * @return mixed
- */
- abstract public function apply($options);
- }
|