Commit 48bd27d5 by 冷斌

fix bug

parent 49bcbc73
This diff is collapsed. Click to expand it.
## 问题描述
## 代码
## 报错详情
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</phpunit>
<?php
namespace Yansongda\Pay\Gateways\Alipay;
use Yansongda\Pay\Contracts\GatewayInterface;
use Yansongda\Pay\Exceptions\GatewayException;
use Yansongda\Pay\Exceptions\InvalidArgumentException;
use Yansongda\Pay\Support\Config;
use Yansongda\Pay\Traits\HasHttpRequest;
abstract class Alipay implements GatewayInterface
{
use HasHttpRequest;
/**
* @var string
*/
protected $gateway = 'https://openapi.alipay.com/gateway.do?charset=UTF-8';
/**
* alipay global config params.
*
* @var array
*/
protected $config;
/**
* user's config params.
*
* @var \Yansongda\Pay\Support\Config
*/
protected $user_config;
/**
* [__construct description].
*
* @author yansongda <me@yansongda.cn>
*
* @param array $config [description]
*/
public function __construct(array $config)
{
$this->user_config = new Config($config);
if (is_null($this->user_config->get('app_id'))) {
throw new InvalidArgumentException('Missing Config -- [app_id]');
}
$this->config = [
'app_id' => $this->user_config->get('app_id'),
'method' => '',
'format' => 'JSON',
'charset' => 'UTF-8',
'sign_type' => 'RSA2',
'version' => '1.0',
'return_url' => $this->user_config->get('return_url', ''),
'notify_url' => $this->user_config->get('notify_url', ''),
'timestamp' => date('Y-m-d H:i:s'),
'sign' => '',
'biz_content' => '',
];
}
/**
* pay a order.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $config_biz
*
* @return mixed
*/
public function pay(array $config_biz)
{
$config_biz['product_code'] = $this->getProductCode();
$this->config['method'] = $this->getMethod();
$this->config['biz_content'] = json_encode($config_biz);
$this->config['sign'] = $this->getSign();
}
/**
* refund a order.
*
* @author yansongda <me@yansongda.cn>
*
* @param mixed $config_biz
*
* @return array|bool
*/
public function refund($config_biz, $refund_amount = null)
{
if (!is_array($config_biz)) {
$config_biz = [
'out_trade_no' => $config_biz,
'refund_amount' => $refund_amount,
];
}
return $this->getResult($config_biz, 'alipay.trade.refund');
}
/**
* close a order.
*
* @author yansongda <me@yansongda.cn>
*
* @param array|string $config_biz
*
* @return array|bool
*/
public function close($config_biz)
{
if (!is_array($config_biz)) {
$config_biz = [
'out_trade_no' => $config_biz,
];
}
return $this->getResult($config_biz, 'alipay.trade.close');
}
/**
* find a order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $out_trade_no
*
* @return array|bool
*/
public function find($out_trade_no = '')
{
$config_biz = [
'out_trade_no' => $out_trade_no,
];
return $this->getResult($config_biz, 'alipay.trade.query');
}
/**
* verify the notify.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $data
* @param string $sign
* @param bool $sync
*
* @return array|bool
*/
public function verify($data, $sign = null, $sync = false)
{
if (is_null($this->user_config->get('ali_public_key'))) {
throw new InvalidArgumentException('Missing Config -- [ali_public_key]');
}
$sign = is_null($sign) ? $data['sign'] : $sign;
$res = "-----BEGIN PUBLIC KEY-----\n".
wordwrap($this->user_config->get('ali_public_key'), 64, "\n", true).
"\n-----END PUBLIC KEY-----";
$toVerify = $sync ? json_encode($data) : $this->getSignContent($data, true);
return openssl_verify($toVerify, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1 ? $data : false;
}
/**
* get method config.
*
* @author yansongda <me@yansongda.cn>
*
* @return string
*/
abstract protected function getMethod();
/**
* get productCode config.
*
* @author yansongda <me@yansongda.cn>
*
* @return string
*/
abstract protected function getProductCode();
/**
* build pay html.
*
* @author yansongda <me@yansongda.cn>
*
* @return string
*/
protected function buildPayHtml()
{
$sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->gateway."' method='POST'>";
foreach ($this->config as $key => $val) {
$val = str_replace("'", '&apos;', $val);
$sHtml .= "<input type='hidden' name='".$key."' value='".$val."'/>";
}
$sHtml .= "<input type='submit' value='ok' style='display:none;'></form>";
$sHtml .= "<script>document.forms['alipaysubmit'].submit();</script>";
return $sHtml;
}
/**
* get alipay api result.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $config_biz
* @param string $method
*
* @return array|bool
*/
protected function getResult($config_biz, $method)
{
$this->config['biz_content'] = json_encode($config_biz);
$this->config['method'] = $method;
$this->config['sign'] = $this->getSign();
$this->config = array_filter($this->config, function ($value) {
return $value !== '' && !is_null($value);
});
$method = str_replace('.', '_', $method).'_response';
$data = json_decode($this->post($this->gateway, $this->config), true);
if (!isset($data[$method]['code']) || $data[$method]['code'] !== '10000') {
throw new GatewayException(
'get result error:'.$data[$method]['msg'].' - '.$data[$method]['sub_code'],
$data[$method]['code'],
$data);
}
return $this->verify($data[$method], $data['sign'], true);
}
/**
* get sign.
*
* @author yansongda <me@yansongda.cn>
*
* @return string
*/
protected function getSign()
{
if (is_null($this->user_config->get('private_key'))) {
throw new InvalidArgumentException('Missing Config -- [private_key]');
}
$res = "-----BEGIN RSA PRIVATE KEY-----\n".
wordwrap($this->user_config->get('private_key'), 64, "\n", true).
"\n-----END RSA PRIVATE KEY-----";
openssl_sign($this->getSignContent($this->config), $sign, $res, OPENSSL_ALGO_SHA256);
return base64_encode($sign);
}
/**
* get signContent that is to be signed.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $toBeSigned
* @param bool $verify
*
* @return string
*/
protected function getSignContent(array $toBeSigned, $verify = false)
{
ksort($toBeSigned);
$stringToBeSigned = '';
foreach ($toBeSigned as $k => $v) {
if ($verify && $k != 'sign' && $k != 'sign_type') {
$stringToBeSigned .= $k.'='.$v.'&';
}
if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
$stringToBeSigned .= $k.'='.$v.'&';
}
}
$stringToBeSigned = substr($stringToBeSigned, 0, -1);
unset($k, $v);
return $stringToBeSigned;
}
}
<?php
namespace Yansongda\Pay\Gateways\Wechat;
use Yansongda\Pay\Contracts\GatewayInterface;
use Yansongda\Pay\Exceptions\GatewayException;
use Yansongda\Pay\Exceptions\InvalidArgumentException;
use Yansongda\Pay\Support\Config;
use Yansongda\Pay\Traits\HasHttpRequest;
abstract class Wechat implements GatewayInterface
{
use HasHttpRequest;
/**
* @var string
*/
protected $endpoint = 'https://api.mch.weixin.qq.com/';
/**
* @var string
*/
protected $gateway_order = 'pay/unifiedorder';
/**
* @var string
*/
protected $gateway_query = 'pay/orderquery';
/**
* @var string
*/
protected $gateway_close = 'pay/closeorder';
/**
* @var string
*/
protected $gateway_refund = 'secapi/pay/refund';
/**
* @var array
*/
protected $config;
/**
* @var \Yansongda\Pay\Support\Config
*/
protected $user_config;
/**
* [__construct description].
*
* @author yansongda <me@yansongda.cn>
*
* @param array $config
*/
public function __construct(array $config)
{
$this->user_config = new Config($config);
$this->config = [
'appid' => $this->user_config->get('app_id', ''),
'mch_id' => $this->user_config->get('mch_id', ''),
'nonce_str' => $this->createNonceStr(),
'sign_type' => 'MD5',
'notify_url' => $this->user_config->get('notify_url', ''),
'trade_type' => $this->getTradeType(),
];
if ($endpoint = $this->user_config->get('endpoint_url')) {
$this->endpoint = $endpoint;
}
}
/**
* pay a order.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $config_biz
*
* @return mixed
*/
abstract public function pay(array $config_biz = []);
/**
* refund.
*
* @author yansongda <me@yansongda.cn>
*
* @return string|bool
*/
public function refund($config_biz = [])
{
if (isset($config_biz['miniapp'])) {
$this->config['appid'] = $this->user_config->get('miniapp_id');
unset($config_biz['miniapp']);
}
$this->config = array_merge($this->config, $config_biz);
$this->unsetTradeTypeAndNotifyUrl();
return $this->getResult($this->gateway_refund, true);
}
/**
* close a order.
*
* @author yansongda <me@yansongda.cn>
*
* @return array|bool
*/
public function close($out_trade_no = '')
{
$this->config['out_trade_no'] = $out_trade_no;
$this->unsetTradeTypeAndNotifyUrl();
return $this->getResult($this->gateway_close);
}
/**
* find a order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $out_trade_no
*
* @return array|bool
*/
public function find($out_trade_no = '')
{
$this->config['out_trade_no'] = $out_trade_no;
$this->unsetTradeTypeAndNotifyUrl();
return $this->getResult($this->gateway_query);
}
/**
* verify the notify.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $data
* @param string $sign
* @param bool $sync
*
* @return array|bool
*/
public function verify($data, $sign = null, $sync = false)
{
$data = $this->fromXml($data);
$sign = is_null($sign) ? $data['sign'] : $sign;
return $this->getSign($data) === $sign ? $data : false;
}
/**
* get trade type config.
*
* @author yansongda <me@yansongda.cn>
*
* @return string
*/
abstract protected function getTradeType();
/**
* pre order.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $config_biz
*
* @return array
*/
protected function preOrder($config_biz = [])
{
$this->config = array_merge($this->config, $config_biz);
return $this->getResult($this->gateway_order);
}
/**
* get api result.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $path
* @param bool $cert
*
* @return array
*/
protected function getResult($path, $cert = false)
{
$this->config['sign'] = $this->getSign($this->config);
if ($cert) {
$data = $this->fromXml($this->post(
$this->endpoint.$path,
$this->toXml($this->config),
[
'cert' => $this->user_config->get('cert_client', ''),
'ssl_key' => $this->user_config->get('cert_key', ''),
]
));
} else {
$data = $this->fromXml($this->post($this->endpoint.$path, $this->toXml($this->config)));
}
if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
$error = 'getResult error:'.$data['return_msg'];
$error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
}
if (!isset($error) && $this->getSign($data) !== $data['sign']) {
$error = 'getResult error: return data sign error';
}
if (isset($error)) {
throw new GatewayException(
$error,
20000,
$data);
}
return $data;
}
/**
* sign.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $data
*
* @return string
*/
protected function getSign($data)
{
if (is_null($this->user_config->get('key'))) {
throw new InvalidArgumentException('Missing Config -- [key]');
}
ksort($data);
$string = md5($this->getSignContent($data).'&key='.$this->user_config->get('key'));
return strtoupper($string);
}
/**
* get sign content.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $data
*
* @return string
*/
protected function getSignContent($data)
{
$buff = '';
foreach ($data as $k => $v) {
$buff .= ($k != 'sign' && $v != '' && !is_array($v)) ? $k.'='.$v.'&' : '';
}
return trim($buff, '&');
}
/**
* create random string.
*
* @author yansongda <me@yansongda.cn>
*
* @param int $length
*
* @return string
*/
protected function createNonceStr($length = 16)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$str = '';
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* convert to xml.
*
* @author yansongda <me@yansongda.cn>
*
* @param array $data
*
* @return string
*/
protected function toXml($data)
{
if (!is_array($data) || count($data) <= 0) {
throw new InvalidArgumentException('convert to xml error!invalid array!');
}
$xml = '<xml>';
foreach ($data as $key => $val) {
$xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
'<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
}
$xml .= '</xml>';
return $xml;
}
/**
* convert to array.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $xml
*
* @return array
*/
protected function fromXml($xml)
{
if (!$xml) {
throw new InvalidArgumentException('convert to array error !invalid xml');
}
libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
}
/**
* delete trade_type and notify_url.
*
* @author yansongda <me@yansongda.cn>
*
* @return bool
*/
protected function unsetTradeTypeAndNotifyUrl()
{
unset($this->config['notify_url']);
unset($this->config['trade_type']);
return true;
}
}
<?php
namespace Yansongda\Pay\Support;
use ArrayAccess;
use Yansongda\Pay\Exceptions\InvalidArgumentException;
class Config implements ArrayAccess
{
/**
* @var array
*/
protected $config;
/**
* Config constructor.
*
* @param array $config
*/
public function __construct(array $config = [])
{
$this->config = $config;
}
/**
* get a config.
*
* @author JasonYan <me@yansongda.cn>
*
* @param string $key
* @param string $default
*
* @return mixed
*/
public function get($key = null, $default = null)
{
$config = $this->config;
if (is_null($key)) {
return $config;
}
if (isset($config[$key])) {
return $config[$key];
}
foreach (explode('.', $key) as $segment) {
if (!is_array($config) || !array_key_exists($segment, $config)) {
return $default;
}
$config = $config[$segment];
}
return $config;
}
/**
* set a config.
*
* @author JasonYan <me@yansongda.cn>
*
* @param string $key
* @param array $value
*/
public function set(string $key, $value)
{
if ($key == '') {
throw new InvalidArgumentException('Invalid config key.');
}
// 只支持三维数组,多余无意义
$keys = explode('.', $key);
switch (count($keys)) {
case '1':
$this->config[$key] = $value;
break;
case '2':
$this->config[$keys[0]][$keys[1]] = $value;
break;
case '3':
$this->config[$keys[0]][$keys[1]][$keys[2]] = $value;
break;
default:
throw new InvalidArgumentException('Invalid config key.');
}
return $this->config;
}
/**
* [offsetExists description].
*
* @author JasonYan <me@yansongda.cn>
*
* @param string $offset
*
* @return bool
*/
public function offsetExists($offset)
{
return array_key_exists($offset, $this->config);
}
/**
* [offsetGet description].
*
* @author JasonYan <me@yansongda.cn>
*
* @param string $offset
*
* @return mixed
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* [offsetSet description].
*
* @author JasonYan <me@yansongda.cn>
*
* @param string $offset
* @param string $value
*
* @return array
*/
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
/**
* [offsetUnset description].
*
* @author JasonYan <me@yansongda.cn>
*
* @param string $offset
*
* @return array
*/
public function offsetUnset($offset)
{
$this->set($offset, null);
}
}
<?php
/*
* (c) overtrue <i@overtrue.me>
*
* Modified By yansongda <me@yansongda.cn>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Yansongda\Pay\Traits;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
trait HasHttpRequest
{
/**
* Make a get request.
*
* @param string $endpoint
* @param array $query
* @param array $headers
*
* @return array
*/
protected function get($endpoint, $query = [], $headers = [])
{
return $this->request('get', $endpoint, [
'headers' => $headers,
'query' => $query,
]);
}
/**
* Make a post request.
*
* @param string $endpoint
* @param mixed $params
* @param array $options
*
* @return string
*/
protected function post($endpoint, $params = [], ...$options)
{
$options = isset($options[0]) ? $options[0] : [];
if (!is_array($params)) {
$options['body'] = $params;
} else {
$options['form_params'] = $params;
}
return $this->request('post', $endpoint, $options);
}
/**
* Make a http request.
*
* @param string $method
* @param string $endpoint
* @param array $options http://docs.guzzlephp.org/en/latest/request-options.html
*
* @return array
*/
protected function request($method, $endpoint, $options = [])
{
return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($endpoint, $options));
}
/**
* Return base Guzzle options.
*
* @return array
*/
protected function getBaseOptions()
{
$options = [
'base_uri' => method_exists($this, 'getBaseUri') ? $this->getBaseUri() : '',
'timeout' => property_exists($this, 'timeout') ? $this->timeout : 5.0,
];
return $options;
}
/**
* Return http client.
*
* @param array $options
*
* @return \GuzzleHttp\Client
*/
protected function getHttpClient(array $options = [])
{
return new Client($options);
}
/**
* Convert response contents to json.
*
* @param \Psr\Http\Message\ResponseInterface $response
*
* @return array
*/
protected function unwrapResponse(ResponseInterface $response)
{
$contentType = $response->getHeaderLine('Content-Type');
$contents = $response->getBody()->getContents();
if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
return json_decode($contents, true);
} elseif (false !== stripos($contentType, 'xml')) {
return json_decode(json_encode(simplexml_load_string($contents)), true);
}
return $contents;
}
}
<?php
namespace Yansongda\Pay\Tests\Gateways\Alipay;
<?php
namespace Yansongda\Pay\Tests\Gateways\Alipay;
<?php
namespace Yansongda\Pay\Tests\Gateways\Alipay;
<?php
namespace Yansongda\Pay\Tests\Gateways\Wechat;
<?php
namespace Yansongda\Pay\Tests\Gateways\Wechat;
<?php
namespace Yansongda\Pay\Tests\Gateways\Wechat;
<?php
namespace Yansongda\Pay\Tests\Gateways\Wechat;
<?php
namespace Yansongda\Pay\Tests\Gateways\Wechat;
<?php
namespace Yansongda\Pay\Tests\Support;
use Yansongda\Pay\Exceptions\InvalidArgumentException;
use Yansongda\Pay\Support\Config;
use Yansongda\Pay\Tests\TestCase;
class ConfigTest extends TestCase
{
public function testGetConfig()
{
$array = [
'foo' => 'bar',
'bar' => [
'id' => '18',
'key' => [
'public' => 'qwer',
'private' => 'asdf',
],
],
];
$config = new Config($array);
$this->assertTrue(isset($config['foo']));
$this->assertSame('bar', $config['foo']);
$this->assertSame('bar', $config->get('foo'));
$this->assertSame($array, $config->get());
$this->assertSame('qwer', $config->get('bar.key.public'));
$this->assertNull($config->get('bar.key.public.foo'));
$this->assertNull($config->get('bar.foo.foo.foo'));
}
public function testSetConfig()
{
$config = new Config([]);
$this->assertArrayHasKey('foo', $config->set('foo', 'bar'));
$this->assertSame('bar', $config->get('foo'));
$this->assertArrayHasKey('bar', $config->set('bar.key.public', 'qwer'));
$this->assertSame('qwer', $config->get('bar.key.public'));
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid config key.');
$config->set('', '');
$this->assertArrayHasKey('error', $config->set('error.foo.foo.foo.foo', 'foo'));
}
}
<?php
namespace Yansongda\Pay\Tests\Traits;
use Yansongda\Pay\Tests\TestCase;
class HasHttpRequestTest extends TestCase
{
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment