Skip to content

Commit

Permalink
optim pay apiv2 specials, method signatures and phpstan@^2 (#2885)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheNorthMemory authored Feb 12, 2025
1 parent 5d29981 commit efedd21
Show file tree
Hide file tree
Showing 14 changed files with 81 additions and 40 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"require-dev": {
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.4.4",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan": "^1.0 | ^2",
"phpunit/phpunit": "^9.5",
"symfony/var-dumper": "^5.2",
"jetbrains/phpstorm-attributes": "^1.0",
Expand Down
28 changes: 20 additions & 8 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ parameters:
ignoreErrors:
-
identifier: missingType.iterableValue
-
message: '#Property EasyWeChat\\Kernel\\Config::\$items \(array<string, mixed>\) does not accept array#'
path: src/Kernel/Config.php
-
message: '#Method EasyWeChat\\Kernel\\Message::format\(\) should return array<string, string> but returns array#'
path: src/Kernel/Message.php
-
message: '#\$client .*? does not accept#'
path: src/Kernel/HttpClient/AccessTokenAwareClient.php
Expand All @@ -16,31 +22,34 @@ parameters:
message: '#Parameter \#1 \$object of function spl_object_hash expects object, callable given#'
path: src/Kernel/Traits/InteractWithHandlers.php
-
message: '#Match arm is unreachable because previous comparison is always true#'
message: '#Call to function is_callable\(\) with callable\(\): mixed will always evaluate to true#'
path: src/Kernel/Traits/InteractWithHandlers.php
-
message: '#Parameter \$stable of class EasyWeChat\\MiniApp\\AccessToken constructor expects bool\|null, mixed given#'
path: src/MiniApp/Application.php
-
message: '#Parameter \#1 \$options of static method EasyWeChat\\Kernel\\HttpClient\\RequestUtil::mergeDefaultRetryOptions\(\) expects array<string, mixed>, array given#'
message: '#Parameter \#1 \$options of static method EasyWeChat\\Kernel\\HttpClient\\RequestUtil::mergeDefaultRetryOptions\(\) expects array<string, mixed>#'
path: src/MiniApp/Application.php
-
message: '#Method EasyWeChat\\MiniApp\\Decryptor::decrypt\(\) should return array<string, mixed> but returns array#'
path: src/MiniApp/Decryptor.php
-
message: '#Parameter \$stable of class EasyWeChat\\OfficialAccount\\(AccessToken|JsApiTicket) constructor expects bool\|null, mixed given#'
path: src/OfficialAccount/Application.php
-
message: '#Parameter \#1 \$options of static method EasyWeChat\\Kernel\\HttpClient\\RequestUtil::mergeDefaultRetryOptions\(\) expects array<string, mixed>, array given#'
message: '#Parameter \#1 \$options of static method EasyWeChat\\Kernel\\HttpClient\\RequestUtil::mergeDefaultRetryOptions\(\) expects array<string, mixed>#'
path: src/OfficialAccount/Application.php
-
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>, array given#'
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>#'
path: src/OfficialAccount/Application.php
-
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>, array given#'
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>#'
path: src/OpenPlatform/Application.php
-
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>, array given#'
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>#'
path: src/OpenWork/Application.php
-
message: '#Parameter \#3 \$defaultOptions of class EasyWeChat\\Pay\\Client constructor expects array<string, mixed>, array given#'
message: '#Parameter \#3 \$defaultOptions of class EasyWeChat\\Pay\\Client constructor expects array<string, mixed>#'
path: src/Pay/Application.php
-
message: '#Property .*?\$client \(Symfony\\Contracts\\HttpClient\\HttpClientInterface\) does not accept Mockery\\Mock\|Symfony\\Contracts\\HttpClient\\HttpClientInterface#'
Expand All @@ -49,5 +58,8 @@ parameters:
message: '#Method EasyWeChat\\Pay\\Client::createMockClient\(\) should return Mockery\\Mock\|Symfony\\Contracts\\HttpClient\\HttpClientInterface but returns Mockery\\LegacyMockInterface#'
path: src/Pay/Client.php
-
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>, array given#'
message: '#Call to function is_string\(\) with string will always evaluate to true#'
path: src/Pay/Client.php
-
message: '#Parameter \#1 \$scopes of method Overtrue\\Socialite\\Providers\\Base::scopes\(\) expects array<string>#'
path: src/Work/Application.php
4 changes: 2 additions & 2 deletions src/Kernel/HttpClient/AccessTokenExpiredRetryStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ class AccessTokenExpiredRetryStrategy extends GenericRetryStrategy

protected ?Closure $decider = null;

public function withAccessToken(AccessTokenInterface $accessToken): self
public function withAccessToken(AccessTokenInterface $accessToken): static
{
$this->accessToken = $accessToken;

return $this;
}

public function decideUsing(Closure $decider): self
public function decideUsing(Closure $decider): static
{
$this->decider = $decider;

Expand Down
6 changes: 3 additions & 3 deletions src/Kernel/HttpClient/RequestUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ public static function formatOptions(array $options, string $method): array
}

/**
* @param array{headers?:array<string, string>, xml?:array|string, body?:array|string, json?:array|string} $options
* @param array{headers?:array<string, string>, xml?:mixed, body?:array|string, json?:mixed} $options
* @return array{headers?:array<string, string|array<string, string>|array<string>>, xml?:array|string, body?:array|string}
*/
public static function formatBody(array $options): array
{
$contentType = $options['headers']['Content-Type'] ?? $options['headers']['content-type'] ?? null;

if (isset($options['xml'])) {
if (array_key_exists('xml', $options)) {
if (is_array($options['xml'])) {
$options['xml'] = Xml::build($options['xml']);
}
Expand All @@ -128,7 +128,7 @@ public static function formatBody(array $options): array
unset($options['xml']);
}

if (isset($options['json'])) {
if (array_key_exists('json', $options)) {
if (is_array($options['json'])) {
/** XXX: 微信的 JSON 是比较奇葩的,比如菜单不能把中文 encode 为 unicode */
$options['json'] = json_encode(
Expand Down
3 changes: 3 additions & 0 deletions src/Kernel/HttpClient/RequestWithPresets.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ public function withFiles(array $files): static
return $this;
}

/**
* @return array{xml?:array<string,mixed>|string,json?:array<string,mixed>|string,body?:array<string,mixed>|string,query?:array<string,mixed>,headers?:array<string,string>}
*/
public function mergeThenResetPrepends(array $options, string $method = 'GET'): array
{
$name = in_array(strtoupper($method), ['GET', 'HEAD', 'DELETE']) ? 'query' : 'body';
Expand Down
2 changes: 1 addition & 1 deletion src/Kernel/Support/Arr.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static function exists(array $array, string|int $key): bool

/**
* @param array<string|int, mixed> $array
* @return array<string|int, mixed>
* @return array<string, mixed>
*/
public static function set(array &$array, string|int|null $key, mixed $value): array
{
Expand Down
2 changes: 1 addition & 1 deletion src/Kernel/Support/PublicKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function getSerialNo(): string
{
$info = openssl_x509_parse($this->certificate);

if ($info === false || ! isset($info['serialNumberHex'])) {
if ($info === false) {
throw new InvalidConfigException('Read the $certificate failed, please check it whether or nor correct');
}

Expand Down
3 changes: 2 additions & 1 deletion src/Kernel/Support/UserAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use function array_map;
use function array_unshift;
use function class_exists;
use function constant;
use function curl_version;
use function defined;
use function explode;
Expand All @@ -26,7 +27,7 @@ public static function create(array $appends = []): string
$value = array_map('strval', $appends);

if (defined('HHVM_VERSION')) {
array_unshift($value, 'HHVM/'.HHVM_VERSION);
array_unshift($value, 'HHVM/'.constant('HHVM_VERSION'));
}

$disabledFunctions = explode(',', ini_get('disable_functions') ?: '');
Expand Down
8 changes: 4 additions & 4 deletions src/Kernel/Traits/InteractWithHandlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ public function createHandlerItem(callable|string $handler): array
/**
* @throws InvalidArgumentException
*/
protected function getHandlerHash(callable|string $handler): string
protected function getHandlerHash(callable|array|string $handler): string
{
return match (true) {
is_string($handler) => $handler,
is_array($handler) => is_string($handler[0]) ? $handler[0].'::'.$handler[1] : get_class(
$handler[0]
).$handler[1],
is_array($handler) => is_string($handler[0])
? $handler[0].'::'.$handler[1]
: get_class($handler[0]).$handler[1],
$handler instanceof Closure => spl_object_hash($handler),
is_callable($handler) => spl_object_hash($handler),
default => throw new InvalidArgumentException('Invalid handler: '.gettype($handler)),
Expand Down
5 changes: 2 additions & 3 deletions src/Kernel/Traits/InteractWithHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
use EasyWeChat\Kernel\HttpClient\ScopingHttpClient;
use EasyWeChat\Kernel\Support\Arr;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;

use function is_array;
use function property_exists;

trait InteractWithHttpClient
{
Expand All @@ -32,8 +32,7 @@ public function setHttpClient(HttpClientInterface $httpClient): static
$this->httpClient = $httpClient;

if ($this instanceof LoggerAwareInterface && $httpClient instanceof LoggerAwareInterface
&& property_exists($this, 'logger')
&& $this->logger) {
&& $this->logger instanceof LoggerInterface) {
$httpClient->setLogger($this->logger);
}

Expand Down
2 changes: 1 addition & 1 deletion src/OfficialAccount/AccessToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function getToken(): string
}

/**
* @return array<string, string>
* @return array{access_token:string}
*
* @throws HttpException
* @throws InvalidArgumentException
Expand Down
45 changes: 35 additions & 10 deletions src/Pay/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use function is_array;
use function is_string;
use function str_starts_with;
use function strcasecmp;

/**
* @method ResponseInterface get(string $uri, array $options = [])
Expand All @@ -54,7 +55,7 @@ class Client implements HttpClientInterface
use RequestWithPresets;

/**
* @var array<string, mixed>
* @var array{base_uri:string,headers:array{'Content-Type':string,Accept:string}}
*/
protected array $defaultOptions = [
'base_uri' => 'https://api.mch.weixin.qq.com/',
Expand All @@ -64,13 +65,23 @@ class Client implements HttpClientInterface
],
];

public const V3_URI_PREFIXES = [
protected const V3_URI_PREFIXES = [
'/v3/',
'/sandbox/v3/',
'/hk/v3/',
'/global/v3/',
];

/**
* Special absolute path string over `GET` method
*/
protected const V2_URI_OVER_GETS = [
'/appauth/getaccesstoken', // secret API which's respond `JSON`, must keep in the first
'/papay/entrustweb',
'/papay/h5entrustweb',
'/papay/partner/entrustweb',
'/papay/partner/h5entrustweb',
];

protected bool $throw = true;

/**
Expand Down Expand Up @@ -101,7 +112,6 @@ public function __construct(
*/
public function request(string $method, string $url, array $options = []): ResponseInterface
{
/** @var array{headers?:array<string, string>, xml?:array|string, body?:array|string} $options */
if (empty($options['headers'])) {
$options['headers'] = [];
}
Expand All @@ -118,8 +128,7 @@ public function request(string $method, string $url, array $options = []): Respo
$options['headers']['Authorization'] = $this->createSignature($method, $url, $_options);
}
} else {
// v2 全部为 xml 请求
if (! empty($options['xml'])) {
if (! strcasecmp($method, 'POST') && ! empty($options['xml'])) {
if (is_array($options['xml'])) {
$options['xml'] = Xml::build($this->attachLegacySignature($options['xml']));
}
Expand All @@ -136,6 +145,10 @@ public function request(string $method, string $url, array $options = []): Respo
$options['body'] = Xml::build($this->attachLegacySignature($options['body']));
}

if (! strcasecmp($method, 'GET') && in_array($url, self::V2_URI_OVER_GETS) && is_array($options['query'] ?? null)) {
$options['query'] = $this->attachLegacySignature($options['query']);
}

if (! isset($options['headers']['Content-Type']) && ! isset($options['headers']['content-type'])) {
$options['headers']['Content-Type'] = 'text/xml';
}
Expand All @@ -148,25 +161,37 @@ public function request(string $method, string $url, array $options = []): Respo

return new Response(
$this->client->request($method, $url, $options),
failureJudge: $this->isV3Request($url) ? null : fn (Response $response) => $response->toArray()['return_code'] === 'FAIL' || $response->toArray()['result_code'] === 'FAIL',
failureJudge: $this->isV3Request($url) ? null : function (Response $response) use ($url): bool {
$arr = $response->toArray();

return ! (
$url === self::V2_URI_OVER_GETS[0] && array_key_exists('retcode', $arr) && $arr['retcode'] === 0
) || ! (
// protocol code, most similar to the HTTP status code in APIv3
array_key_exists('return_code', $arr) && $arr['return_code'] === 'SUCCESS'
) || (
// business code, most similar to the Response.JSON.code in APIv3
array_key_exists('result_code', $arr) && $arr['result_code'] !== 'SUCCESS'
);
},
throw: $this->throw
);
}

protected function isV3Request(string $url): bool
{
$uri = new Uri($url);
$uri = (new Uri($url))->getPath();

foreach (self::V3_URI_PREFIXES as $prefix) {
if (str_starts_with('/'.ltrim($uri->getPath(), '/'), $prefix)) {
if (str_starts_with($uri, $prefix)) {
return true;
}
}

return false;
}

public function withSerialHeader(?string $serial = null): self
public function withSerialHeader(?string $serial = null): static
{
$platformCerts = $this->merchant->getPlatformCerts();
if (empty($platformCerts)) {
Expand Down
2 changes: 1 addition & 1 deletion src/Pay/Merchant.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function getPlatformCerts(): array
}

/**
* @param array<array-key, string|PublicKey> $platformCerts
* @param array<array-key, mixed> $platformCerts
* @return array<string, PublicKey>
*
* @throws InvalidArgumentException
Expand Down
9 changes: 5 additions & 4 deletions src/Pay/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use function is_array;
use function json_decode;
use function json_encode;
use function str_contains;
use function strval;

/**
Expand Down Expand Up @@ -115,8 +116,8 @@ public function getRequestMessage(?ServerRequestInterface $request = null): \Eas
$originContent = (string) ($request ?? $this->getRequest())->getBody();

// 微信支付的回调数据回调,偶尔是 XML https://github.com/w7corp/easywechat/issues/2737
// PS: 这帮傻逼,真的是该死啊
$isXml = str_starts_with($originContent, '<xml');
$contentType = ($request ?? $this->getRequest())->getHeaderLine('content-type');
$isXml = (str_contains($contentType, 'text/xml') || str_contains($contentType, 'application/xml')) && str_starts_with($originContent, '<xml');
$attributes = $isXml ? $this->decodeXmlMessage($originContent) : $this->decodeJsonMessage($originContent);

return new Message($attributes, $originContent);
Expand Down Expand Up @@ -159,11 +160,11 @@ protected function decodeJsonMessage(string $contents): array
{
$attributes = json_decode($contents, true);

if (! is_array($attributes)) {
if (! (is_array($attributes) && is_array($attributes['resource']))) {
throw new RuntimeException('Invalid request body.');
}

if (empty($attributes['resource']['ciphertext'])) {
if (empty($attributes['resource']['ciphertext'] ?? null)) {
throw new RuntimeException('Invalid request.');
}

Expand Down

0 comments on commit efedd21

Please sign in to comment.