Browse Source

first commit

tags/v1.0.0
wuliangbo 1 year ago
commit
b9495b551a
100 changed files with 7932 additions and 0 deletions
  1. +9
    -0
      .gitignore
  2. +38
    -0
      composer.json
  3. +21
    -0
      src/Base/Application.php
  4. +60
    -0
      src/Base/Oauth/Client.php
  5. +19
    -0
      src/Base/Oauth/ServiceProvider.php
  6. +42
    -0
      src/Factory.php
  7. +262
    -0
      src/Kernel/AccessToken.php
  8. +250
    -0
      src/Kernel/BaseClient.php
  9. +55
    -0
      src/Kernel/Clauses/Clause.php
  10. +10
    -0
      src/Kernel/Config.php
  11. +30
    -0
      src/Kernel/Contracts/AccessTokenInterface.php
  12. +15
    -0
      src/Kernel/Contracts/Arrayable.php
  13. +16
    -0
      src/Kernel/Contracts/EventHandlerInterface.php
  14. +16
    -0
      src/Kernel/Contracts/MediaInterface.php
  15. +27
    -0
      src/Kernel/Contracts/MessageInterface.php
  16. +26
    -0
      src/Kernel/Decorators/FinallyResult.php
  17. +26
    -0
      src/Kernel/Decorators/TerminateResult.php
  18. +26
    -0
      src/Kernel/Events/AccessTokenRefreshed.php
  19. +26
    -0
      src/Kernel/Events/ApplicationInitialized.php
  20. +35
    -0
      src/Kernel/Events/HttpResponseCreated.php
  21. +35
    -0
      src/Kernel/Events/ServerGuardResponseCreated.php
  22. +7
    -0
      src/Kernel/Exceptions/BadMethodCallException.php
  23. +6
    -0
      src/Kernel/Exceptions/BadRequestException.php
  24. +9
    -0
      src/Kernel/Exceptions/Exception.php
  25. +43
    -0
      src/Kernel/Exceptions/HttpException.php
  26. +11
    -0
      src/Kernel/Exceptions/InvalidArgumentException.php
  27. +11
    -0
      src/Kernel/Exceptions/InvalidConfigException.php
  28. +14
    -0
      src/Kernel/Exceptions/InvalidSignException.php
  29. +7
    -0
      src/Kernel/Exceptions/RuntimeException.php
  30. +134
    -0
      src/Kernel/Extension.php
  31. +22
    -0
      src/Kernel/FileForm/FileField.php
  32. +16
    -0
      src/Kernel/FileForm/FileForm.php
  33. +295
    -0
      src/Kernel/FileForm/FileFormStream.php
  34. +124
    -0
      src/Kernel/Helpers.php
  35. +114
    -0
      src/Kernel/Http/Request.php
  36. +134
    -0
      src/Kernel/Http/Response.php
  37. +86
    -0
      src/Kernel/Http/StreamResponse.php
  38. +598
    -0
      src/Kernel/Log/LogManager.php
  39. +49
    -0
      src/Kernel/Messages/Article.php
  40. +43
    -0
      src/Kernel/Messages/Card.php
  41. +31
    -0
      src/Kernel/Messages/DeviceEvent.php
  42. +41
    -0
      src/Kernel/Messages/DeviceText.php
  43. +16
    -0
      src/Kernel/Messages/File.php
  44. +18
    -0
      src/Kernel/Messages/Image.php
  45. +27
    -0
      src/Kernel/Messages/Link.php
  46. +29
    -0
      src/Kernel/Messages/Location.php
  47. +61
    -0
      src/Kernel/Messages/Media.php
  48. +203
    -0
      src/Kernel/Messages/Message.php
  49. +22
    -0
      src/Kernel/Messages/MiniProgramPage.php
  50. +64
    -0
      src/Kernel/Messages/Music.php
  51. +64
    -0
      src/Kernel/Messages/News.php
  52. +47
    -0
      src/Kernel/Messages/NewsItem.php
  53. +46
    -0
      src/Kernel/Messages/Raw.php
  54. +21
    -0
      src/Kernel/Messages/ShortVideo.php
  55. +35
    -0
      src/Kernel/Messages/TaskCard.php
  56. +45
    -0
      src/Kernel/Messages/Text.php
  57. +30
    -0
      src/Kernel/Messages/TextCard.php
  58. +46
    -0
      src/Kernel/Messages/Transfer.php
  59. +56
    -0
      src/Kernel/Messages/Video.php
  60. +27
    -0
      src/Kernel/Messages/Voice.php
  61. +114
    -0
      src/Kernel/Model.php
  62. +25
    -0
      src/Kernel/Providers/ConfigServiceProvider.php
  63. +38
    -0
      src/Kernel/Providers/EventDispatcherServiceProvider.php
  64. +30
    -0
      src/Kernel/Providers/ExtensionServiceProvider.php
  65. +30
    -0
      src/Kernel/Providers/HttpClientServiceProvider.php
  66. +70
    -0
      src/Kernel/Providers/LogServiceProvider.php
  67. +30
    -0
      src/Kernel/Providers/RequestServiceProvider.php
  68. +408
    -0
      src/Kernel/ServerGuard.php
  69. +125
    -0
      src/Kernel/ServiceContainer.php
  70. +457
    -0
      src/Kernel/Support/Arr.php
  71. +411
    -0
      src/Kernel/Support/Collection.php
  72. +126
    -0
      src/Kernel/Support/File.php
  73. +220
    -0
      src/Kernel/Support/Str.php
  74. +158
    -0
      src/Kernel/Support/XML.php
  75. +242
    -0
      src/Kernel/Traits/HasAttributes.php
  76. +223
    -0
      src/Kernel/Traits/HasHttpRequests.php
  77. +96
    -0
      src/Kernel/Traits/InteractsWithCache.php
  78. +265
    -0
      src/Kernel/Traits/Observable.php
  79. +85
    -0
      src/Kernel/Traits/ResponseCastable.php
  80. +340
    -0
      src/Kernel/Traits/SingData.php
  81. +2
    -0
      src/Kernel/extensions.php
  82. +21
    -0
      src/Marketing/Application.php
  83. +111
    -0
      src/Marketing/Pass/Client.php
  84. +19
    -0
      src/Marketing/Pass/ServiceProvider.php
  85. +27
    -0
      src/Mini/Application.php
  86. +56
    -0
      src/Mini/Identification/Client.php
  87. +16
    -0
      src/Mini/Identification/ServiceProvider.php
  88. +33
    -0
      src/Mini/Qrcode/Client.php
  89. +16
    -0
      src/Mini/Qrcode/ServiceProvider.php
  90. +28
    -0
      src/Mini/Risk/Client.php
  91. +19
    -0
      src/Mini/Risk/ServiceProvider.php
  92. +36
    -0
      src/Mini/TemplateMessage/Client.php
  93. +16
    -0
      src/Mini/TemplateMessage/ServiceProvider.php
  94. +120
    -0
      src/OpenPlatform/Agent/Client.php
  95. +19
    -0
      src/OpenPlatform/Agent/ServiceProvider.php
  96. +87
    -0
      src/OpenPlatform/Application.php
  97. +14
    -0
      src/OpenPlatform/Auth/ServiceProvider.php
  98. +43
    -0
      src/OpenPlatform/Authorizer/Mini/Application.php
  99. +45
    -0
      src/OpenPlatform/Authorizer/Mini/BaseInfo/Client.php
  100. +15
    -0
      src/OpenPlatform/Authorizer/Mini/BaseInfo/ServiceProvider.php

+ 9
- 0
.gitignore View File

@@ -0,0 +1,9 @@
*.DS_Store
/vendor
sftp-config.json
/*.php
/.idea
/coverage
/.split
/composer.lock
.php_cs.cache

+ 38
- 0
composer.json View File

@@ -0,0 +1,38 @@
{
"name": "wuearl/alipay",
"description": "package for tp6",
"type": "library",
"license": "Apache-2.0",
"authors": [
{
"name": "wuliangbo",
"email": "wuearl@qq.com"
}
],
"autoload":{
"psr-4":{
"Alipay\\":"src/"
},
"files": [
"src/Kernel/Helpers.php"
]
},
"require": {
"php": ">=7.1",
"ext-curl": "*",
"ext-fileinfo": "*",
"ext-openssl": "*",
"ext-json": "*",
"ext-simplexml": "*",
"ext-ctype": "*",
"ext-bcmath": "*",
"monolog/monolog": "^1.22 || ^2.0",
"guzzlehttp/guzzle": "^6.2",
"pimple/pimple": "^3.0",
"psr/simple-cache": "^1.0",
"symfony/cache": "^3.3 || ^4.3 || ^5.0",
"symfony/event-dispatcher": "^4.3 || ^5.0",
"symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0",
"symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0"
}
}

+ 21
- 0
src/Base/Application.php View File

@@ -0,0 +1,21 @@
<?php

namespace Alipay\Base;

use Alipay\Kernel\ServiceContainer;

/**
* Class Application.
*
* @property \Alipay\Base\Oauth\Client $oauth
*
*/
class Application extends ServiceContainer
{
/**
* @var array
*/
protected $providers = [
Oauth\ServiceProvider::class,
];
}

+ 60
- 0
src/Base/Oauth/Client.php View File

@@ -0,0 +1,60 @@
<?php

namespace Alipay\Base\Oauth;

use Alipay\Kernel\BaseClient;
use function Alipay\Kernel\decrypt;

class Client extends BaseClient
{
/**
* 获取 token
* @param string $grant_type
* @param string $code
* @param string $refresh_token
* @return \Alipay\Kernel\Support\Collection|array|object|\Psr\Http\Message\ResponseInterface|string
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function getToken(string $grant_type, string $code, string $refresh_token = '')
{

$params = [
'grant_type' => $grant_type,
'code' => $code,
'refresh_token' => $refresh_token
];
$this->method = 'alipay.user.oauth.token';
$sysParams = $this->buildParams($params);
$url = $this->_toUrlEncodedRequestBody($sysParams);
return $this->httpPost($url, $params['params']);
}

/**
* 获取用户信息
* @param string $token
* @return \Alipay\Kernel\Support\Collection|array|object|\Psr\Http\Message\ResponseInterface|string
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Exception
*/
public function user(string $token)
{
$this->app->config->set('auth_token', $token);
$params = [];
$this->method = 'alipay.user.info.share';
$sysParams = $this->buildParams($params);
$url = $this->_toUrlEncodedRequestBody($sysParams);
return $this->httpPost($url);
}

/**
* @param $content
* @return mixed
*/
public function decryptData($content)
{
$data = decrypt($content, $this->app->config->get('encrypt_key'));
return @json_decode($data, 1);
}
}

+ 19
- 0
src/Base/Oauth/ServiceProvider.php View File

@@ -0,0 +1,19 @@
<?php

namespace Alipay\Base\Oauth;

use Pimple\Container;
use Pimple\ServiceProviderInterface;

class ServiceProvider implements ServiceProviderInterface
{
/**
* @param Container $app
*/
public function register(Container $app)
{
$app['oauth'] = function ($app) {
return new Client($app);
};
}
}

+ 42
- 0
src/Factory.php View File

@@ -0,0 +1,42 @@
<?php

namespace Alipay;

/**
* Class Factory.
*
* @method static \Alipay\Payment\Application payment(array $config)
* @method static \Alipay\Mini\Application mini(array $config)
* @method static \Alipay\OpenPublic\Application openPublic(array $config)
* @method static \Alipay\Marketing\Application marketing(array $config)
* @method static \Alipay\Base\Application base(array $config)
* @method static \Alipay\OpenPlatform\Application openPlatform(array $config)
*/
class Factory
{
/**
* @param string $name
* @param array $config
*
* @return \Alipay\Kernel\ServiceContainer
*/
public static function make($name, array $config)
{
$namespace = Kernel\Support\Str::studly($name);
$application = "\\Alipay\\{$namespace}\\Application";
return new $application($config);
}

/**
* Dynamically pass methods to the application.
*
* @param string $name
* @param array $arguments
*
* @return mixed
*/
public static function __callStatic($name, $arguments)
{
return self::make($name, ...$arguments);
}
}

+ 262
- 0
src/Kernel/AccessToken.php View File

@@ -0,0 +1,262 @@
<?php

namespace Alipay\Kernel;

use Alipay\Kernel\Contracts\AccessTokenInterface;
use Alipay\Kernel\Exceptions\HttpException;
use Alipay\Kernel\Exceptions\InvalidArgumentException;
use Alipay\Kernel\Exceptions\RuntimeException;
use Alipay\Kernel\Traits\HasHttpRequests;
use Alipay\Kernel\Traits\InteractsWithCache;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* Class AccessToken.
*
* @author overtrue <i@overtrue.me>
*/
abstract class AccessToken implements AccessTokenInterface
{
use HasHttpRequests;
use InteractsWithCache;

/**
* @var \Alipay\Kernel\ServiceContainer
*/
protected $app;

/**
* @var string
*/
protected $requestMethod = 'GET';

/**
* @var string
*/
protected $endpointToGetToken;

/**
* @var string
*/
protected $queryName;

/**
* @var array
*/
protected $token;

/**
* @var int
*/
protected $safeSeconds = 500;

/**
* @var string
*/
protected $tokenKey = 'access_token';

/**
* @var string
*/
protected $cachePrefix = 'easyalipay.kernel.auth_token.';

/**
* AuthToken constructor.
* @param ServiceContainer $app
*/
public function __construct(ServiceContainer $app)
{
$this->app = $app;
}

/**
* @return array
* @throws Exceptions\InvalidConfigException
* @throws HttpException
* @throws InvalidArgumentException
* @throws RuntimeException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function getRefreshedToken(): array
{
return $this->getToken(true);
}

/**
* @param bool $refresh
* @return array
* @throws Exceptions\InvalidConfigException
* @throws HttpException
* @throws InvalidArgumentException
* @throws RuntimeException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function getToken(bool $refresh = false): array
{
$cacheKey = $this->getCacheKey();
$cache = $this->getCache();

if (!$refresh && $cache->has($cacheKey)) {
return $cache->get($cacheKey);
}

/** @var array $token */
$token = $this->requestToken($this->getCredentials(), true);

$this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200);

$this->app->events->dispatch(new Events\AccessTokenRefreshed($this));

return $token;
}

/**
* @param string $token
* @param int $lifetime
* @return AccessTokenInterface
* @throws InvalidArgumentException
* @throws RuntimeException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface
{
$this->getCache()->set($this->getCacheKey(), [
$this->tokenKey => $token,
'expires_in' => $lifetime,
], $lifetime - $this->safeSeconds);

if (!$this->getCache()->has($this->getCacheKey())) {
throw new RuntimeException('Failed to cache access token.');
}

return $this;
}

/**
* @return AccessTokenInterface
* @throws Exceptions\InvalidConfigException
* @throws HttpException
* @throws InvalidArgumentException
* @throws RuntimeException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function refresh(): AccessTokenInterface
{
$this->getToken(true);

return $this;
}

/**
* @param array $credentials
* @param bool $toArray
* @return array|Support\Collection|mixed|object|ResponseInterface|string
* @throws Exceptions\InvalidConfigException
* @throws HttpException
* @throws InvalidArgumentException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function requestToken(array $credentials, $toArray = false)
{
$response = $this->sendRequest($credentials);
$result = json_decode($response->getBody()->getContents(), true);
$formatted = $this->castResponseToType($response, $this->app['config']->get('response_type'));

if (empty($result[$this->tokenKey])) {
throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted);
}

return $toArray ? $result : $formatted;
}

/**
* @param RequestInterface $request
* @param array $requestOptions
* @return RequestInterface
* @throws Exceptions\InvalidConfigException
* @throws HttpException
* @throws InvalidArgumentException
* @throws RuntimeException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface
{
parse_str($request->getUri()->getQuery(), $query);

$query = http_build_query(array_merge($this->getQuery(), $query));

return $request->withUri($request->getUri()->withQuery($query));
}

/**
* Send http request.
* @param array $credentials
* @return ResponseInterface
* @throws InvalidArgumentException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function sendRequest(array $credentials): ResponseInterface
{
$options = [
('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials,
];

return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options);
}

/**
* @return string
*/
protected function getCacheKey()
{
return $this->cachePrefix.md5(json_encode($this->getCredentials()));
}

/**
* The request query will be used to add to the request.
* @return array
* @throws Exceptions\InvalidConfigException
* @throws HttpException
* @throws InvalidArgumentException
* @throws RuntimeException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
protected function getQuery(): array
{
return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]];
}

/**
* @return string
* @throws InvalidArgumentException
*/
public function getEndpoint(): string
{
if (empty($this->endpointToGetToken)) {
throw new InvalidArgumentException('No endpoint for access token request.');
}

return $this->endpointToGetToken;
}

/**
* @return string
*/
public function getTokenKey()
{
return $this->tokenKey;
}

/**
* Credential for get token.
*
* @return array
*/
abstract protected function getCredentials(): array;
}

+ 250
- 0
src/Kernel/BaseClient.php View File

@@ -0,0 +1,250 @@
<?php

namespace Alipay\Kernel;

use Alipay\Kernel\Contracts\AccessTokenInterface;
use Alipay\Kernel\Http\Request;
use Alipay\Kernel\Http\Response;
use Alipay\Kernel\Traits\HasHttpRequests;
use Alipay\Kernel\Traits\SingData;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LogLevel;

class BaseClient
{
use SingData;
use HasHttpRequests {
request as performRequest;
send as performSend;
}

/**
* @var \Alipay\Kernel\ServiceContainer
*/
protected $app;
/**
* @var AccessTokenInterface
*/
protected $accessToken;
/**
* @var string
*/
protected $baseUri;
/**
* @var string
*/
protected $method;

/**
* BaseClient constructor.
* @param ServiceContainer $app
*/
public function __construct(ServiceContainer $app)
{
$this->app = $app;
}

/**
* GET request.
*
* @param string $url
* @param array $query
* @return \Psr\Http\Message\ResponseInterface|\Alipay\Kernel\Support\Collection|array|object|string
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function httpGet(string $url, array $query = [])
{
return $this->request($url, 'GET', ['query' => $query]);
}

/**
* POST request.
*
* @param string $url
* @param array $data
* @return \Psr\Http\Message\ResponseInterface|\Alipay\Kernel\Support\Collection|array|object|string
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function httpPost(string $url, array $data = [])
{
return $this->request($url, 'POST', ['form_params' => $data]);
}

/**
* JSON request.
* @param string $url
* @param array $data
* @param array $query
* @return \Psr\Http\Message\ResponseInterface|\Alipay\Kernel\Support\Collection|array|object|string
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function httpPostJson(string $url, array $data = [], array $query = [])
{
return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
}

/**
* Upload file.
*
* @param string $url
* @param $body
* @return Support\Collection|array|object|ResponseInterface|string
* @throws Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function httpUpload(string $url, $body = null)
{
return $this->request($url, 'POST', ['body' => $body, 'headers' => [
"content-type" => "multipart/form-data;charset=utf-8;boundary=" . $this->_getRandomBoundary()
]]);
}

/**
* @return AccessTokenInterface
*/
public function getAccessToken(): AccessTokenInterface
{
return $this->accessToken;
}

/**
* @param \Alipay\Kernel\Contracts\AccessTokenInterface $accessToken
* @return $this
*/
public function setAccessToken(AccessTokenInterface $accessToken)
{
$this->accessToken = $accessToken;
return $this;
}

/**
* @param Request $request
* @param array $config
* @param bool $returnRaw
* @return Support\Collection|array|object|ResponseInterface|string
* @throws Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function send(Request $request, array $config = [], $returnRaw = false)
{
if (empty($this->middlewares)) {
$this->registerHttpMiddlewares();
}
$response = $this->performSend($request, $config);
$this->app->events->dispatch(new Events\HttpResponseCreated($response));
return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
}

/**
* @param string $url
* @param string $method
* @param array $options
* @param bool $returnRaw
* @return \Psr\Http\Message\ResponseInterface|\Alipay\Kernel\Support\Collection|array|object|string
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false)
{
if (empty($this->middlewares)) {
$this->registerHttpMiddlewares();
}
$response = $this->performRequest($url, $method, $options);
$this->app->events->dispatch(new Events\HttpResponseCreated($response));
return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
}

/**
* @param string $url
* @param string $method
* @param array $options
* @return \Alipay\Kernel\Http\Response
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function requestRaw(string $url, string $method = 'GET', array $options = [])
{
return Response::buildFromPsrResponse($this->request($url, $method, $options, true), [
'method' => $this->method,
'sign_type' => $this->app->config->get('sign_type'),
'alipay_public_key' => $this->app->config->get('alipay_public_key'),
]);
}

/**
* Register Guzzle middlewares.
*/
protected function registerHttpMiddlewares()
{
// retry
$this->pushMiddleware($this->retryMiddleware(), 'retry');
// log
// $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token');

$this->pushMiddleware($this->logMiddleware(), 'log');
}

/**
* Attache access token to request query.
*
* @return \Closure
*/
protected function accessTokenMiddleware()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
if ($this->accessToken) {
$request = $this->accessToken->applyToRequest($request, $options);
}

return $handler($request, $options);
};
};
}

/**
* Log the request.
*
* @return \Closure
*/
protected function logMiddleware()
{
$formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);

return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG);
}

/**
* Return retry middleware.
*
* @return \Closure
*/
protected function retryMiddleware()
{
return Middleware::retry(function (
$retries,
RequestInterface $request,
ResponseInterface $response = null
) {
// Limit the number of retries to 2
if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
// Retry on server errors
$response = json_decode($body, true);
if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) {
$this->accessToken->refresh();
$this->app['logger']->debug('Retrying with refreshed access token.');
return true;
}
}
return false;
}, function () {
return abs($this->app->config->get('http.retry_delay', 500));
});
}
}

+ 55
- 0
src/Kernel/Clauses/Clause.php View File

@@ -0,0 +1,55 @@
<?php

namespace Alipay\Kernel\Clauses;

/**
* Class Clause.
*
* @author mingyoung <mingyoungcheung@gmail.com>
*/
class Clause
{
/**
* @var array
*/
protected $clauses = [
'where' => [],
];

/**
* @param mixed ...$args
*
* @return $this
*/
public function where(...$args)
{
array_push($this->clauses['where'], $args);

return $this;
}

/**
* @param mixed $payload
*
* @return bool
*/
public function intercepted($payload)
{
return (bool) $this->interceptWhereClause($payload);
}

/**
* @param mixed $payload
*
* @return bool
*/
protected function interceptWhereClause($payload)
{
foreach ($this->clauses['where'] as $item) {
list($key, $value) = $item;
if (isset($payload[$key]) && $payload[$key] !== $value) {
return true;
}
}
}
}

+ 10
- 0
src/Kernel/Config.php View File

@@ -0,0 +1,10 @@
<?php

namespace Alipay\Kernel;

use Alipay\Kernel\Support\Collection;

class Config extends Collection
{

}

+ 30
- 0
src/Kernel/Contracts/AccessTokenInterface.php View File

@@ -0,0 +1,30 @@
<?php
namespace Alipay\Kernel\Contracts;

use Psr\Http\Message\RequestInterface;

/**
* Interface AuthorizerAccessToken.
*
* @author overtrue <i@overtrue.me>
*/
interface AccessTokenInterface
{
/**
* @return array
*/
public function getToken(): array;

/**
* @return \Alipay\Kernel\Contracts\AccessTokenInterface
*/
public function refresh(): self;

/**
* @param \Psr\Http\Message\RequestInterface $request
* @param array $requestOptions
*
* @return \Psr\Http\Message\RequestInterface
*/
public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface;
}

+ 15
- 0
src/Kernel/Contracts/Arrayable.php View File

@@ -0,0 +1,15 @@
<?php

namespace Alipay\Kernel\Contracts;

use ArrayAccess;

interface Arrayable extends ArrayAccess
{
/**
* Get the instance as an array.
*
* @return array
*/
public function toArray();
}

+ 16
- 0
src/Kernel/Contracts/EventHandlerInterface.php View File

@@ -0,0 +1,16 @@
<?php

namespace Alipay\Kernel\Contracts;

/**
* Interface EventHandlerInterface.
*
* @author mingyoung <mingyoungcheung@gmail.com>
*/
interface EventHandlerInterface
{
/**
* @param mixed $payload
*/
public function handle($payload = null);
}

+ 16
- 0
src/Kernel/Contracts/MediaInterface.php View File

@@ -0,0 +1,16 @@
<?php

namespace Alipay\Kernel\Contracts;

/**
* Interface MediaInterface.
*
* @author overtrue <i@overtrue.me>
*/
interface MediaInterface extends MessageInterface
{
/**
* @return string
*/
public function getMediaId(): string;
}

+ 27
- 0
src/Kernel/Contracts/MessageInterface.php View File

@@ -0,0 +1,27 @@
<?php


namespace Alipay\Kernel\Contracts;

/**
* Interface MessageInterface.
*
* @author overtrue <i@overtrue.me>
*/
interface MessageInterface
{
/**
* @return string
*/
public function getType(): string;

/**
* @return array
*/
public function transformForJsonRequest(): array;

/**
* @return string
*/
public function transformToXml(): string;
}

+ 26
- 0
src/Kernel/Decorators/FinallyResult.php View File

@@ -0,0 +1,26 @@
<?php

namespace Alipay\Kernel\Decorators;

/**
* Class FinallyResult.
*
* @author overtrue <i@overtrue.me>
*/
class FinallyResult
{
/**
* @var mixed
*/
public $content;

/**
* FinallyResult constructor.
*
* @param mixed $content
*/
public function __construct($content)
{
$this->content = $content;
}
}

+ 26
- 0
src/Kernel/Decorators/TerminateResult.php View File

@@ -0,0 +1,26 @@
<?php

namespace Alipay\Kernel\Decorators;

/**
* Class TerminateResult.
*
* @author overtrue <i@overtrue.me>
*/
class TerminateResult
{
/**
* @var mixed
*/
public $content;

/**
* FinallyResult constructor.
*
* @param mixed $content
*/
public function __construct($content)
{
$this->content = $content;
}
}

+ 26
- 0
src/Kernel/Events/AccessTokenRefreshed.php View File

@@ -0,0 +1,26 @@
<?php

namespace Alipay\Kernel\Events;

use Alipay\Kernel\AccessToken;

/**
* Class AccessTokenRefreshed.
*
* @author mingyoung <mingyoungcheung@gmail.com>
*/
class AccessTokenRefreshed
{
/**
* @var \Alipay\Kernel\AccessToken
*/
public $accessToken;

/**
* @param \Alipay\Kernel\AccessToken $accessToken
*/
public function __construct(AccessToken $accessToken)
{
$this->accessToken = $accessToken;
}
}

+ 26
- 0
src/Kernel/Events/ApplicationInitialized.php View File

@@ -0,0 +1,26 @@
<?php

namespace Alipay\Kernel\Events;

use Alipay\Kernel\ServiceContainer;

/**
* Class ApplicationInitialized.
*
* @author mingyoung <mingyoungcheung@gmail.com>
*/
class ApplicationInitialized
{
/**
* @var \Alipay\Kernel\ServiceContainer
*/
public $app;

/**
* @param \Alipay\Kernel\ServiceContainer $app
*/
public function __construct(ServiceContainer $app)
{
$this->app = $app;
}
}

+ 35
- 0
src/Kernel/Events/HttpResponseCreated.php View File

@@ -0,0 +1,35 @@
<?php

/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Alipay\Kernel\Events;

use Psr\Http\Message\ResponseInterface;

/**
* Class HttpResponseCreated.
*
* @author mingyoung <mingyoungcheung@gmail.com>
*/
class HttpResponseCreated
{
/**
* @var \Psr\Http\Message\ResponseInterface
*/
public $response;

/**
* @param \Psr\Http\Message\ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
}

+ 35
- 0
src/Kernel/Events/ServerGuardResponseCreated.php View File

@@ -0,0 +1,35 @@
<?php

/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Alipay\Kernel\Events;

use Symfony\Component\HttpFoundation\Response;

/**
* Class ServerGuardResponseCreated.
*
* @author mingyoung <mingyoungcheung@gmail.com>
*/
class ServerGuardResponseCreated
{
/**
* @var \Symfony\Component\HttpFoundation\Response
*/
public $response;

/**
* @param \Symfony\Component\HttpFoundation\Response $response
*/
public function __construct(Response $response)
{
$this->response = $response;
}
}

+ 7
- 0
src/Kernel/Exceptions/BadMethodCallException.php View File

@@ -0,0 +1,7 @@
<?php

namespace Alipay\Kernel\Exceptions;
class BadMethodCallException extends Exception
{

}

+ 6
- 0
src/Kernel/Exceptions/BadRequestException.php View File

@@ -0,0 +1,6 @@
<?php

namespace Alipay\Kernel\Exceptions;
class BadRequestException extends Exception
{
}

+ 9
- 0
src/Kernel/Exceptions/Exception.php View File

@@ -0,0 +1,9 @@
<?php

namespace Alipay\Kernel\Exceptions;

use Exception as BaseException;

class Exception extends BaseException
{
}

+ 43
- 0
src/Kernel/Exceptions/HttpException.php View File

@@ -0,0 +1,43 @@
<?php

namespace Alipay\Kernel\Exceptions;

use Psr\Http\Message\ResponseInterface;

/**
* Class HttpException.
*
* @author overtrue <i@overtrue.me>
*/
class HttpException extends Exception
{
/**
* @var \Psr\Http\Message\ResponseInterface|null
*/
public $response;

/**
* @var \Psr\Http\Message\ResponseInterface|\Alipay\Kernel\Support\Collection|array|object|string|null
*/
public $formattedResponse;

/**
* HttpException constructor.
*
* @param string $message
* @param \Psr\Http\Message\ResponseInterface|null $response
* @param null $formattedResponse
* @param int|null $code
*/
public function __construct($message, ResponseInterface $response = null, $formattedResponse = null, $code = null)
{
parent::__construct($message, $code);

$this->response = $response;
$this->formattedResponse = $formattedResponse;

if ($response) {
$response->getBody()->rewind();
}
}
}

+ 11
- 0
src/Kernel/Exceptions/InvalidArgumentException.php View File

@@ -0,0 +1,11 @@
<?php
namespace Alipay\Kernel\Exceptions;

/**
* Class InvalidArgumentException.
*
* @author overtrue <i@overtrue.me>
*/
class InvalidArgumentException extends Exception
{
}

+ 11
- 0
src/Kernel/Exceptions/InvalidConfigException.php View File

@@ -0,0 +1,11 @@
<?php
namespace Alipay\Kernel\Exceptions;

/**
* Class InvalidConfigException.
*
* @author overtrue <i@overtrue.me>
*/
class InvalidConfigException extends Exception
{
}

+ 14
- 0
src/Kernel/Exceptions/InvalidSignException.php View File

@@ -0,0 +1,14 @@
<?php
/**
* Created by PhpStorm.
* User: wuliangbo
* Date: 2020/1/7
* Time: 09:49
*/


namespace Alipay\Kernel\Exceptions;
class InvalidSignException extends Exception
{

}

+ 7
- 0
src/Kernel/Exceptions/RuntimeException.php View File

@@ -0,0 +1,7 @@
<?php

namespace Alipay\Kernel\Exceptions;
class RuntimeException extends Exception
{

}

+ 134
- 0
src/Kernel/Extension.php View File

@@ -0,0 +1,134 @@
<?php

declare(strict_types=1);

namespace Alipay\Kernel;

use Alipay\Kernel\Contracts\EventHandlerInterface;
use Pimple\Container;
use ReflectionClass;

class Extension
{
/**
* @var \Pimple\Container
*/
protected $app;

/**
* @var string
*/
protected $manifestPath;

/**
* @var array|null
*/
protected $manifest;

/**
* @param \Pimple\Container $app
*/
public function __construct(Container $app)
{
$this->app = $app;
$this->manifestPath = __DIR__.'/extensions.php';
}

/**
* Get observers.
*
* @return array
*/
public function observers(): array
{
if ($this->shouldIgnore()) {
return [];
}

$observers = [];

foreach ($this->getManifest() as $name => $extra) {
$observers = array_merge($observers, $extra['observers'] ?? []);
}

return array_map([$this, 'listObserver'], array_filter($observers, [$this, 'validateObserver']));
}

/**
* @param mixed $observer
*
* @return bool
*/
protected function isDisable($observer): bool
{
return in_array($observer, $this->app->config->get('disable_observers', []));
}

/**
* Get the observers should be ignore.
*
* @return bool
*/
protected function shouldIgnore(): bool
{
return !file_exists($this->manifestPath) || $this->isDisable('*');
}

/**
* Validate the given observer.
*
* @param mixed $observer
*
* @return bool
*
* @throws \ReflectionException
*/
protected function validateObserver($observer): bool
{
return !$this->isDisable($observer)
&& (new ReflectionClass($observer))->implementsInterface(EventHandlerInterface::class)
&& $this->accessible($observer);
}

/**
* Determine whether the given observer is accessible.
*
* @param string $observer
*
* @return bool
*/
protected function accessible($observer): bool
{
if (!method_exists($observer, 'getAccessor')) {
return true;
}

return in_array(get_class($this->app), (array) $observer::getAccessor());
}

/**
* @param mixed $observer
*
* @return array
*/
protected function listObserver($observer): array
{
$condition = method_exists($observer, 'onCondition') ? $observer::onCondition() : '*';

return [$observer, $condition];
}

/**
* Get the easyalipay manifest.
*
* @return array
*/
protected function getManifest(): array
{
if (!is_null($this->manifest)) {
return $this->manifest;
}

return $this->manifest = file_exists($this->manifestPath) ? require $this->manifestPath : [];
}
}

+ 22
- 0
src/Kernel/FileForm/FileField.php View File

@@ -0,0 +1,22 @@
<?php

namespace Alipay\Kernel\FileForm;

use Alipay\Kernel\Model;

class FileField extends Model
{
public $filename;
public $contentType;
public $content;

public function __construct($config = [])
{
$this->_required = [
'filename' => true,
'contentType' => true,
'content' => true,
];
parent::__construct($config);
}
}

+ 16
- 0
src/Kernel/FileForm/FileForm.php View File

@@ -0,0 +1,16 @@
<?php

namespace Alipay\Kernel\FileForm;

class FileForm
{
public static function getBoundary()
{
return (string) (mt_rand(10000000000000, 99999999999999));
}

public static function toFileForm($map, $boundary)
{
return new FileFormStream($map, $boundary);
}
}

+ 295
- 0
src/Kernel/FileForm/FileFormStream.php View File

@@ -0,0 +1,295 @@
<?php

namespace Alipay\Kernel\FileForm;

use GuzzleHttp\Psr7\Stream;
use Psr\Http\Message\StreamInterface;

/**
* @internal
* @coversNothing
*/
class FileFormStream implements StreamInterface
{
/**
* @var resource
*/
private $stream;
private $index = 0;
private $form = [];
private $boundary = '';
private $streaming = false;
private $keys = [];

/**
* @var Stream
*/
private $currStream;

private $size;
private $uri;
private $seekable;
private $readable = true;
private $writable = true;

public function __construct($map, $boundary)
{
$this->stream = fopen('php://memory', 'a+');
$this->form = $map;
$this->boundary = $boundary;
$this->keys = array_keys($map);
do {
$read = $this->readForm(1024);
} while (null !== $read);
$meta = stream_get_meta_data($this->stream);
$this->seekable = $meta['seekable'];
$this->uri = $this->getMetadata('uri');
$this->seek(0);
$this->seek(0);
}

/**
* Closes the stream when the destructed.
*/
public function __destruct()
{
$this->close();
}

public function __toString()
{
try {
$this->seek(0);

return (string)stream_get_contents($this->stream);
} catch (\Exception $e) {
return '';
}
}

/**
* @param int $length
*
* @return false|int|string
*/
public function readForm($length)
{
if ($this->streaming) {
if (null !== $this->currStream) {
// @var string $content
$content = $this->currStream->read($length);
if (false !== $content && '' !== $content) {
fwrite($this->stream, $content);

return $content;
}

return $this->next("\r\n");
}

return $this->next();
}
$keysCount = \count($this->keys);
if ($this->index > $keysCount) {
return null;
}
if ($keysCount > 0) {
if ($this->index < $keysCount) {
$this->streaming = true;

$name = $this->keys[$this->index];
$field = $this->form[$name];
if (!empty($field) && $field instanceof FileField) {
if (!empty($field->content)) {
$this->currStream = $field->content;
$str = '--' . $this->boundary . "\r\n" .
'Content-Disposition: form-data; name="' . $name . '"; filename="' . $field->filename . "\"\r\n" .
'Content-Type: ' . $field->contentType . "\r\n\r\n";
$this->write($str);
return $str;
}
return $this->next();
}
$val = $field;
$str = '--' . $this->boundary . "\r\n" .
'Content-Disposition: form-data; name="' . $name . "\"\r\n\r\n" .
$val . "\r\n";
fwrite($this->stream, $str);
return $str;
}
if ($this->index == $keysCount) {
return $this->next('--' . $this->boundary . "--\r\n");
}
return null;
}
return null;
}

public function getContents()
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
$contents = stream_get_contents($this->stream);
if (false === $contents) {
throw new \RuntimeException('Unable to read stream contents');
}
return $contents;
}

public function close()
{
if (isset($this->stream)) {
if (\is_resource($this->stream)) {
fclose($this->stream);
}
$this->detach();
}
}

public function detach()
{
if (!isset($this->stream)) {
return null;
}
$result = $this->stream;
unset($this->stream);
$this->size = $this->uri = null;
return $result;
}

public function getSize()
{
if (null !== $this->size) {
return $this->size;
}
if (!isset($this->stream)) {
return null;
}
// Clear the stat cache if the stream has a URI
if ($this->uri) {
clearstatcache(true, $this->uri);
}
$stats = fstat($this->stream);
if (isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
return null;
}

public function isReadable()
{
return $this->readable;
}

public function isWritable()
{
return $this->writable;
}

public function isSeekable()
{
return $this->seekable;
}

public function eof()
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}

return feof($this->stream);
}

public function tell()
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}

$result = ftell($this->stream);

if (false === $result) {
throw new \RuntimeException('Unable to determine stream position');
}

return $result;
}

public function rewind()
{
$this->seek(0);
}

public function seek($offset, $whence = SEEK_SET)
{
$whence = (int)$whence;

if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->seekable) {
throw new \RuntimeException('Stream is not seekable');
}
if (-1 === fseek($this->stream, $offset, $whence)) {
throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . var_export($whence, true));
}
}

public function read($length)
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->readable) {
throw new \RuntimeException('Cannot read from non-readable stream');
}
if ($length < 0) {
throw new \RuntimeException('Length parameter cannot be negative');
}
if (0 === $length) {
return '';
}
$string = fread($this->stream, $length);
if (false === $string) {
throw new \RuntimeException('Unable to read from stream');
}
return $string;
}

public function write($string)
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->writable) {
throw new \RuntimeException('Cannot write to a non-writable stream');
}
// We can't know the size after writing anything
$this->size = null;
$result = fwrite($this->stream, $string);
if (false === $result) {
throw new \RuntimeException('Unable to write to stream');
}
return $result;
}

public function getMetadata($key = null)
{
if (!isset($this->stream)) {
return $key ? null : [];
}
$meta = stream_get_meta_data($this->stream);
return isset($meta[$key]) ? $meta[$key] : null;
}

private function next($endStr = '')
{
$this->streaming = false;
++$this->index;
$this->write($endStr);
$this->currStream = null;
return $endStr;
}
}

+ 124
- 0
src/Kernel/Helpers.php View File

@@ -0,0 +1,124 @@
<?php

namespace Alipay\Kernel;

use Alipay\Kernel\Contracts\Arrayable;
use Alipay\Kernel\Exceptions\RuntimeException;
use Alipay\Kernel\Support\Arr;
use Alipay\Kernel\Support\Collection;

/**
* @param $str
* @param $key
* @return string
*/
function encrypt($str, $key)
{
$key = base64_decode($key);
$str = trim($str);
$str = addPKCS7Padding($str);
$iv_size = openssl_cipher_iv_length('AES-128-CBC');
$iv = str_repeat("\0", $iv_size);
$encrypt_str = openssl_encrypt($str, "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypt_str);
}

/**
* @param $str
* @param $key
* @return false|string
*/
function decrypt($str, $key)
{
$str = base64_decode($str);
$key = base64_decode($key);
$iv_size = openssl_cipher_iv_length('AES-128-CBC');
$iv = str_repeat("\0", $iv_size);
$decrypt_str = openssl_decrypt($str, "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv);
return stripPKSC7Padding($decrypt_str);
}

/**
* 填充算法
* @param $source
* @return string
*/
function addPKCS7Padding($source)
{
$source = trim($source);
$block = 16;

$pad = $block - (strlen($source) % $block);
if ($pad <= $block) {
$char = chr($pad);
$source .= str_repeat($char, $pad);
}
return $source;
}

/**
* 移除填充
* @param $source
* @return false|string
*/
function stripPKSC7Padding($source)
{
$char = substr($source, -1);
$num = ord($char);
if ($num == 62) return $source;
$source = substr($source, 0, -$num);
return $source;
}

/**
* @param $data
* @param $key
* @param null $default
* @return mixed|null
* @throws RuntimeException
*/
function data_get($data, $key, $default = null)
{
switch (true) {
case is_array($data):
return Arr::get($data, $key, $default);
case $data instanceof Collection:
return $data->get($key, $default);
case $data instanceof Arrayable:
return Arr::get($data->toArray(), $key, $default);
case $data instanceof \ArrayIterator:
return $data->getArrayCopy()[$key] ?? $default;
case $data instanceof \ArrayAccess:
return $data[$key] ?? $default;
case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator:
return $data->getIterator()->getArrayCopy()[$key] ?? $default;
case is_object($data):
return $data->{$key} ?? $default;
default:
throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key));
}
}

/**
* @param $data
* @return array
* @throws RuntimeException
*/
function data_to_array($data)
{
switch (true) {
case is_array($data):
return $data;
case $data instanceof Collection:
return $data->all();
case $data instanceof Arrayable:
return $data->toArray();
case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator:
return $data->getIterator()->getArrayCopy();
case $data instanceof \ArrayIterator:
return $data->getArrayCopy();
default:
throw new RuntimeException(sprintf('Can\'t transform data to array'));
}
}


+ 114
- 0
src/Kernel/Http/Request.php View File

@@ -0,0 +1,114 @@
<?php

namespace Alipay\Kernel\Http;

use GuzzleHttp\Psr7\Request as PsrRequest;
use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;

/**
* Class Request.
*/
class Request extends PsrRequest
{
/**
* @var string
*/
public $protocol = 'https';

/**
* @var string
*/
public $pathname = '/';

/**
* @var array
*/
public $headers = [];

/**
* @var array
*/
public $query = [];

/**
* @var string
*/
public $body;

/**
* @var int
*/
public $port;

public $method;

public function __construct($method = 'GET', $uri = '', array $headers = [], $body = null, $version = '1.1')
{
parent::__construct($method, $uri, $headers, $body, $version);
$this->method = $method;
}

/**
* These fields are compatible if you define other fields.
* Mainly for compatibility situations where the code generator cannot generate set properties.
*
* @return PsrRequest
*/
public function getPsrRequest()
{
$this->assertQuery($this->query);

$request = clone $this;

$uri = $request->getUri();
if ($this->query) {
$uri = $uri->withQuery(http_build_query($this->query));
}

if ($this->port) {
$uri = $uri->withPort($this->port);
}

if ($this->protocol) {
$uri = $uri->withScheme($this->protocol);
}

if ($this->pathname) {
$uri = $uri->withPath($this->pathname);
}

if (isset($this->headers['host'])) {
$uri = $uri->withHost($this->headers['host']);
}

$request = $request->withUri($uri);
$request = $request->withMethod($this->method);

if ('' !== $this->body && null !== $this->body) {
if ($this->body instanceof StreamInterface) {
$request = $request->withBody($this->body);
} else {
$request = $request->withBody(\GuzzleHttp\Psr7\stream_for($this->body));
}
}

if ($this->headers) {
foreach ($this->headers as $key => $value) {
$request = $request->withHeader($key, $value);
}
}

return $request;
}

/**
* @param array $query
*/
private function assertQuery($query)
{
if (!\is_array($query)) {
throw new InvalidArgumentException('Query must be array.');
}
}
}

+ 134
- 0
src/Kernel/Http/Response.php View File

@@ -0,0 +1,134 @@
<?php

namespace Alipay\Kernel\Http;

use Alipay\Kernel\Exceptions\BadRequestException;
use Alipay\Kernel\Exceptions\InvalidArgumentException;
use Alipay\Kernel\Support\Collection;
use Alipay\Kernel\Support\XML;
use Alipay\Kernel\Traits\SingData;
use GuzzleHttp\Psr7\Response as GuzzleResponse;
use Psr\Http\Message\ResponseInterface;

/**
* Class Response.
*
* @author overtrue <i@overtrue.me>
*/
class Response extends GuzzleResponse
{
use SingData;

protected $config;

/**
* @return string
*/
public function getBodyContents()
{
$this->getBody()->rewind();
$contents = $this->getBody()->getContents();
$this->getBody()->rewind();

return $contents;
}

/**
* @param \Psr\Http\Message\ResponseInterface $response
*
* @return \Alipay\Kernel\Http\Response
*/
public static function buildFromPsrResponse(ResponseInterface $response, $config)
{
$res = new static(
$response->getStatusCode(),
$response->getHeaders(),
$response->getBody(),
$response->getProtocolVersion(),
$response->getReasonPhrase()
);
$res->config = $config;
return $res;
}

/**
* @return false|string
* @throws BadRequestException
* @throws InvalidArgumentException
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
*/
public function toJson()
{
return json_encode($this->toArray());
}

/**
* @return array|\SimpleXMLElement
* @throws BadRequestException
* @throws InvalidArgumentException
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
*/
public function toArray()
{
$content = $this->removeControlCharacters($this->getBodyContents());

if (false !== stripos($this->getHeaderLine('Content-Type'), 'xml') || 0 === stripos($content, '<xml')) {
return XML::parse($content);
}

$array = json_decode($content, true, 512, JSON_BIGINT_AS_STRING);
if (JSON_ERROR_NONE === json_last_error()) {
$method = str_replace(".", "_", $this->config['method']) . '_response';
$content = $array[$method];
if (!isset($array['sign']) || (isset($content['code']) && $content['code'] != '10000')) {
throw new BadRequestException($content['msg'] . ' ' . $content['sub_code'], $content['code'] ?? 10000);
}
if (!$this->verify($content, $array['sign'], $this->config['alipay_public_key'], $this->config['sign_type'])) {
throw new InvalidArgumentException('签名失败', 10000);
}
return (array)$content;
}

return [];
}

/**
* @return Collection
* @throws BadRequestException
* @throws InvalidArgumentException
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
*/
public function toCollection()
{
return new Collection($this->toArray());
}

/**
* @return mixed
* @throws BadRequestException
* @throws InvalidArgumentException
* @throws \Alipay\Kernel\Exceptions\InvalidConfigException
*/
public function toObject()
{
return json_decode($this->toJson());
}

/**
* @return bool|string
*/
public function __toString()
{
return $this->getBodyContents();
}

/**
* @param string $content
*
* @return string
*/
protected function removeControlCharacters(string $content)
{
return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', $content);
}
}

+ 86
- 0
src/Kernel/Http/StreamResponse.php View File

@@ -0,0 +1,86 @@
<?php

/*
* This file is part of the overtrue/wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace EasyAlipay\Kernel\Http;

use Alipay\Kernel\Exceptions\InvalidArgumentException;
use Alipay\Kernel\Exceptions\RuntimeException;
use Alipay\Kernel\Support\File;

/**