Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "better Exception control & method type signature" #4

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ composer install

1. 请求 `pathname` 切分后的每个`segment`,可直接以对象获取形式串接,例如 `v3/pay/transactions/native` 即串成 `v3->pay->transactions->native`;
2. 每个 `pathname` 所支持的 `HTTP METHOD`,即作为被串接对象的末尾执行方法,例如: `v3->pay->transactions->native->post(['json' => []])`;
3. 每个 `pathname` 所支持的 `HTTP METHOD`,同时支持`Async`语法糖,例如: `v3->pay->transactions->native->postAsync(['json' => []])`;
3. 每个 `pathname` 所支持的 `HTTP METHOD`,同时支持`Async`语法糖,例如: `v3->pay->transactions->native->postAsycn(['json' => []])`;
4. 每个 `segment` 有中线(dash)分隔符的,可以使用驼峰`camelCase`风格书写,例如: `merchant-service`可写成 `merchantService`,或如 `{'merchant-service'}`;
5. 每个 `segment` 中,若有`uri_template`动态参数,例如 `business_code/{business_code}` 推荐以`business_code->{'{business_code}'}`形式书写,其格式语义与`pathname`基本一致,阅读起来比较自然;
6. SDK内置以 `v2` 特殊标识为 `APIv2` 的起始 `segmemt`,之后串接切分后的 `segments`,如源 `pay/micropay` 即串成 `v2->pay->micropay->post(['xml' => []])` 即以XML形式请求远端接口;
Expand Down
4 changes: 3 additions & 1 deletion src/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ public function __construct(array $input = [], ?ClientDecoratorInterface $instan
/**
* `$driver` setter
* @param ClientDecoratorInterface $instance - The `ClientDecorator` instance
*
* @return BuilderChainable
*/
public function setDriver(ClientDecoratorInterface &$instance): BuilderChainable
public function setDriver(ClientDecoratorInterface &$instance)
{
$this->driver = $instance;

Expand Down
27 changes: 25 additions & 2 deletions src/ClientDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use function sprintf;
use function php_uname;
use function implode;
use function preg_match;
use function strncasecmp;
use function strcasecmp;
use function substr;
Expand Down Expand Up @@ -122,14 +123,36 @@ public function select(?string $protocol = null): ClientInterface
: $this->{ClientDecoratorInterface::JSON_BASED};
}

/**
* Expands a URI template
*
* @param string $template URI template
* @param array<string|int,string|int> $variables Template variables
*
* @return string
*/
protected static function withUriTemplate(string $template, array $variables = []): string
{
if (0 === preg_match('#\{(?:[^/]+)\}#', $template)) {
return $template;
}

static $uriTemplate;
if (!$uriTemplate) {
$uriTemplate = new UriTemplate();
}

return $uriTemplate->expand($template, $variables);
}

/**
* @inheritDoc
*/
public function request(string $method, string $uri, array $options = []): ResponseInterface
{
list($protocol, $pathname) = static::prepare($uri);

return $this->select($protocol)->request($method, UriTemplate::expand($pathname, $options), $options);
return $this->select($protocol)->request($method, static::withUriTemplate($pathname, $options), $options);
}

/**
Expand All @@ -139,6 +162,6 @@ public function requestAsync(string $method, string $uri, array $options = []):
{
list($protocol, $pathname) = static::prepare($uri);

return $this->select($protocol)->requestAsync($method, UriTemplate::expand($pathname, $options), $options);
return $this->select($protocol)->requestAsync($method, static::withUriTemplate($pathname, $options), $options);
}
}
19 changes: 10 additions & 9 deletions src/ClientJsonTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
use function is_object;
use function is_array;
use function count;
use function sprintf;

use InvalidArgumentException;
use UnexpectedValueException;

use GuzzleHttp\Client;
Expand Down Expand Up @@ -62,6 +62,7 @@ abstract protected static function withDefaults(array $config = []): array;
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|string $privateKey - The merchant private key.
*
* @return callable(RequestInterface)
* @throws InvalidArgumentException
*/
public static function signer(string $mchid, string $serial, $privateKey): callable
{
Expand Down Expand Up @@ -91,7 +92,7 @@ public static function verifier(array &$certs): callable
return static function (ResponseInterface $response) use (&$certs): ResponseInterface {
if (!($response->hasHeader(WechatpayNonce) && $response->hasHeader(WechatpaySerial)
&& $response->hasHeader(WechatpaySignature) && $response->hasHeader(WechatpayTimestamp))) {
throw new UnexpectedValueException(sprintf(Exception\EV3_RES_HEADERS_INCOMPLATE, WechatpayNonce, WechatpaySerial, WechatpaySignature, WechatpayTimestamp));
throw new UnexpectedValueException('The response\'s Headers incomplete.');
}

list($nonce) = $response->getHeader(WechatpayNonce);
Expand All @@ -103,13 +104,13 @@ public static function verifier(array &$certs): callable

if (abs($localTimestamp - intval($timestamp)) > MAXIMUM_CLOCK_OFFSET) {
throw new UnexpectedValueException(
sprintf(Exception\EV3_RES_HEADER_TIMESTAMP_OFFSET, MAXIMUM_CLOCK_OFFSET, $timestamp, $localTimestamp)
"It's allowed time offset in ± 5 minutes, the response was on ${timestamp}, your's localtime on ${localTimestamp}."
);
}

if (!Crypto\Rsa::verify(Formatter::response($timestamp, $nonce, static::body($response)), $signature, $certs[$serial])) {
throw new UnexpectedValueException(
sprintf(Exception\EV3_RES_HEADER_SIGNATURE_DEGIST, $timestamp, $nonce, $signature, $serial)
"Verify the response's data with: timestamp=${timestamp}, nonce=${nonce}, signature=${signature}, cert=[${serial}: publicKey] failed."
);
}

Expand All @@ -127,25 +128,25 @@ public static function verifier(array &$certs): callable
* - certs: array{string, \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|string} - The wechatpay platform serial and certificate(s), `[$serial => $cert]` pair
*
* @param array<string,string|int|bool|array|mixed> $config - The configuration
* @throws \WeChatPay\Exception\InvalidArgumentException
* @throws InvalidArgumentException
*/
public static function jsonBased(array $config = []): Client
{
if (!(
isset($config['mchid']) && (is_string($config['mchid']) || is_numeric($config['mchid']))
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_MCHID_IS_MANDATORY); }
)) { throw new InvalidArgumentException('The merchant\' ID aka `mchid` is required, usually numerical.'); }

if (!(
isset($config['serial']) && is_string($config['serial'])
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_SERIAL_IS_MANDATORY); }
)) { throw new InvalidArgumentException('The serial number of the merchant\'s certificate aka `serial` is required, usually hexadecial.'); }

if (!(
isset($config['privateKey']) && (is_string($config['privateKey']) || is_resource($config['privateKey']) || is_object($config['privateKey']))
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_PRIVATEKEY_IS_MANDATORY); }
)) { throw new InvalidArgumentException('The merchant\'s private key aka `privateKey` is required, usual as pem format.'); }

if (!(
isset($config['certs']) && is_array($config['certs']) && count($config['certs'])
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_CERTS_IS_MANDATORY); }
)) { throw new InvalidArgumentException('The platform certificate(s) aka `certs` is required, paired as of `[serial => certificate]`.'); }

$handler = $config['handler'] ?? HandlerStack::create();
$handler->unshift(Middleware::mapRequest(static::signer($config['mchid'], $config['serial'], $config['privateKey'])), 'signer');
Expand Down
15 changes: 8 additions & 7 deletions src/ClientXmlTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
use function strlen;
use function array_replace_recursive;
use function trigger_error;
use function sprintf;

use const E_USER_DEPRECATED;

use InvalidArgumentException;

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Utils;
Expand Down Expand Up @@ -48,19 +49,19 @@ abstract protected static function withDefaults(array $config = []): array;
* @param ?string $secret - The secret key string (optional)
* @param array{cert?: ?string, key?: ?string} $merchant - The merchant private key and certificate array. (optional)
*
* @return callable(callable(RequestInterface, array))
* @throws \WeChatPay\Exception\InvalidArgumentException
* @return callable
* @throws InvalidArgumentException
*/
public static function transformRequest(?string $mchid = null, ?string $secret = null, ?array $merchant = null): callable
{
return static function (callable $handler) use ($mchid, $secret, $merchant): callable {
trigger_error('New features are all in `APIv3`, there\'s no reason to continue use this kind client since v2.0.', E_USER_DEPRECATED);

return static function (RequestInterface $request, array $options = []) use ($handler, $mchid, $secret, $merchant): P\PromiseInterface {
return static function (RequestInterface $request, array $options = []) use ($handler, $mchid, $secret, $merchant) {
$data = $options['xml'] ?? [];

if ($mchid && $mchid !== ($data['mch_id'] ?? null)) {
throw new Exception\InvalidArgumentException(sprintf(Exception\EV2_REQ_XML_NOTMATCHED_MCHID, $data['mch_id'] ?? '', $mchid));
throw new InvalidArgumentException("The xml's mch_id({$data['mch_id']}) doesn't matched the init one ({$mchid}).");
}

$type = $data['sign_type'] ?? Crypto\Hash::ALGO_MD5;
Expand Down Expand Up @@ -91,12 +92,12 @@ public static function transformRequest(?string $mchid = null, ?string $secret =
*
* @param ?string $secret - The secret key string (optional)
*
* @return callable(callable(RequestInterface, array))
* @return callable
*/
public static function transformResponse(?string $secret = null): callable
{
return static function (callable $handler) use ($secret): callable {
return static function (RequestInterface $request, array $options = []) use ($secret, $handler): P\PromiseInterface {
return static function (RequestInterface $request, array $options = []) use ($secret, $handler) {
$promise = $handler($request, $options);

return $promise->then(static function(ResponseInterface $response) use ($secret) {
Expand Down
4 changes: 2 additions & 2 deletions src/Crypto/AesEcb.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AesEcb implements AesInterface
/**
* @inheritDoc
*/
public static function encrypt(string $plaintext, string $key, string $iv = ''): string
static public function encrypt(string $plaintext, string $key, string $iv = ''): string
{
$ciphertext = openssl_encrypt($plaintext, static::ALGO_AES_256_ECB, $key, OPENSSL_RAW_DATA, $iv);

Expand All @@ -27,7 +27,7 @@ public static function encrypt(string $plaintext, string $key, string $iv = ''):
/**
* @inheritDoc
*/
public static function decrypt(string $ciphertext, string $key, string $iv = ''): string
static public function decrypt(string $ciphertext, string $key, string $iv = ''): string
{
return openssl_decrypt(base64_decode($ciphertext), static::ALGO_AES_256_ECB, $key, OPENSSL_RAW_DATA, $iv);
}
Expand Down
1 change: 1 addition & 0 deletions src/Crypto/AesGcm.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AesGcm implements AesInterface
/**
* Detect the ext-openssl whether or nor including the `aes-256-gcm` algorithm
*
* @return void
* @throws RuntimeException
*/
private static function preCondition(): void
Expand Down
7 changes: 0 additions & 7 deletions src/Exception/InvalidArgumentException.php

This file was deleted.

20 changes: 0 additions & 20 deletions src/Exception/WeChatPayException.php

This file was deleted.

12 changes: 6 additions & 6 deletions src/Formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace WeChatPay;

use function array_reduce;
use function mt_rand;
use function range;
use function preg_replace_callback;
use function rand;
use function str_repeat;
use function time;
use function sprintf;
use function implode;
Expand All @@ -15,6 +15,8 @@
use const SORT_FLAG_CASE;
use const SORT_NATURAL;

const BASE62_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

/**
* Provides easy used methods using in this project.
*/
Expand All @@ -29,9 +31,7 @@ class Formatter
*/
public static function nonce(int $size = 32): string
{
return array_reduce(range(1, $size), static function(string $char) {
return $char .= 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'[mt_rand(0, 61)];
}, '');
return preg_replace_callback('#0#', static function() { return BASE62_CHARS[rand(0, 61)]; }, str_repeat('0', $size));
}

/**
Expand Down
31 changes: 22 additions & 9 deletions src/Util/MediaUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,37 @@
class MediaUtil
{
/**
* @var string - local file path
* local file path
*
* @var string
*/
private $filepath;

/**
* @var string - file content stream to upload
* file content stream to upload
* @var string
*/
private $fileStream;

/**
* @var string - upload meta json string
* upload meta json string
*
* @var string
*/
private $meta;

/**
* @var MultipartStream - upload contents stream
* upload contents stream
*
* @var MultipartStream
*/
private $multipart;


/**
* @var StreamInterface - multipart stream wrapper
* multipart stream wrapper
*
* @var StreamInterface
*/
private $stream;

Expand Down Expand Up @@ -94,10 +103,14 @@ private function composeStream(): void
$this->multipart = $multipart;

$this->stream = FnStream::decorate($multipart, [
/** @var callable __toString - for signature */
'__toString' => static function () use ($json) { return $json; },
/** @var callable getSize - let the `CURL` to use `CURLOPT_UPLOAD` context */
'getSize' => static function () { return null; },
// for signature
'__toString' => function () use ($json) {
return $json;
},
// let the `CURL` to use `CURLOPT_UPLOAD` context
'getSize' => function () {
return null;
},
]);
}

Expand Down