feat: 微信查询投诉单详情自动解密用户手机号 (#912)

This commit is contained in:
yansongda 2023-12-31 20:40:39 +08:00 committed by GitHub
parent 1ac094086f
commit 1dfbea1253
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 1 deletions

View File

@ -4,6 +4,8 @@
- feat: 新增 `InvalidSignException`(#903)
- feat: 新增 `DecryptException`(#906)
- feat: 新增 `decrypt_wechat_contents` 解密微信加密内容(#912)
- feat: `\Yansongda\Pay\Plugin\Wechat\Extend\Complaints\QueryDetailPlugin` 自动解密用户手机号(#912)
### changed

View File

@ -109,6 +109,8 @@ class Exception extends \Exception
public const DECRYPT_WECHAT_DECRYPTED_METHOD_INVALID = 7003;
public const DECRYPT_WECHAT_ENCRYPTED_CONTENTS_INVALID = 7004;
public mixed $extra;
public function __construct(string $message = '未知异常', int $code = self::UNKNOWN_ERROR, mixed $extra = null, ?Throwable $previous = null)

View File

@ -273,6 +273,15 @@ function encrypt_wechat_contents(string $contents, string $publicKey): ?string
return null;
}
function decrypt_wechat_contents(string $encrypted, array $config): ?string
{
if (openssl_private_decrypt(base64_decode($encrypted), $decrypted, get_private_cert($config['mch_secret_cert'] ?? ''), OPENSSL_PKCS1_OAEP_PADDING)) {
return $decrypted;
}
return null;
}
/**
* @throws ContainerException
* @throws DecryptException

View File

@ -6,10 +6,17 @@ namespace Yansongda\Pay\Plugin\Wechat\Extend\Complaints;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\decrypt_wechat_contents;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/docs/merchant/apis/consumer-complaint/complaints/query-complaint-v2.html
@ -19,6 +26,9 @@ class QueryDetailPlugin implements PluginInterface
{
/**
* @throws InvalidParamsException
* @throws InvalidConfigException
* @throws ContainerException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
@ -38,6 +48,25 @@ class QueryDetailPlugin implements PluginInterface
Logger::info('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
/** @var Rocket $rocket */
$rocket = $next($rocket);
Logger::debug('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件开始后置装载', ['rocket' => $rocket]);
$destination = $rocket->getDestination();
if ($destination instanceof Collection && !empty($payerPhone = $destination->get('payer_phone'))) {
$decryptPayerPhone = decrypt_wechat_contents($payerPhone, get_wechat_config($rocket->getParams()));
if (empty($decryptPayerPhone)) {
throw new InvalidConfigException(Exception::DECRYPT_WECHAT_ENCRYPTED_CONTENTS_INVALID, '参数异常: 查询投诉单详情,参数 `payer_phone` 解密失败');
}
$destination->set('payer_phone', $decryptPayerPhone);
}
Logger::debug('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件后置装载完毕', ['rocket' => $rocket]);
return $rocket;
}
}

View File

@ -23,6 +23,7 @@ use Yansongda\Pay\Provider\Wechat;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Str;
use function Yansongda\Pay\decrypt_wechat_contents;
use function Yansongda\Pay\decrypt_wechat_resource;
use function Yansongda\Pay\decrypt_wechat_resource_aes_256_gcm;
use function Yansongda\Pay\encrypt_wechat_contents;
@ -316,6 +317,16 @@ class FunctionTest extends TestCase
self::assertIsString($result);
}
public function testDecryptWechatContents()
{
$encrypted = 'WIesmK+dSJycwdhTTkNmv0Lk2wb9o7NGODovccjhyotNnRkEeh+sxRK1gNSRNMJJgkQ30m4HwcuweSO24mehFeXVNTVAKFVef/3FlHnYDZfE1c3mCLToEef7e8J/Z8TwFH1ecn3t+Jk9ZaBpQKNHdQ0Q8jcL7AnL48h0D9BcZxDekPqX6hNnKfISoKSv4TXFcgvBLFeAe4Q3KM0Snq0N5IvI86D9xZqVg6mY+Gfz0782ymQFxflau6Qxx3mJ+0etHMocNuCdgctVH390XYYMc0u+V2FCJ5cU5h/M/AxzP9ayrEO4l0ftaxL6lP0HjifNrkPcAAb+q9I67UepKO9iGw==';
$config = get_wechat_config();
self::assertEquals('yansongda', decrypt_wechat_contents($encrypted, $config));
self::assertNull(decrypt_wechat_contents('invalid', $config));
}
public function testReloadWechatPublicCerts()
{
$response = new Response(

View File

@ -3,6 +3,7 @@
namespace Yansongda\Pay\Tests\Plugin\Wechat\Extend\Complaints;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\Extend\Complaints\QueryDetailPlugin;
use Yansongda\Pay\Rocket;
@ -48,4 +49,50 @@ class QueryDetailPluginTest extends TestCase
'_service_url' => 'v3/merchant-service/complaints-v2/yansongda',
], $result->getPayload()->all());
}
public function testNormalWithEncryptedContents()
{
$payload = [
"complaint_id" => "yansongda",
];
$rocket = new Rocket();
$rocket->setPayload(new Collection($payload));
$result = $this->plugin->assembly($rocket, function ($rocket) {
$rocket->setDestination(new Collection([
'payer_phone' => 'WIesmK+dSJycwdhTTkNmv0Lk2wb9o7NGODovccjhyotNnRkEeh+sxRK1gNSRNMJJgkQ30m4HwcuweSO24mehFeXVNTVAKFVef/3FlHnYDZfE1c3mCLToEef7e8J/Z8TwFH1ecn3t+Jk9ZaBpQKNHdQ0Q8jcL7AnL48h0D9BcZxDekPqX6hNnKfISoKSv4TXFcgvBLFeAe4Q3KM0Snq0N5IvI86D9xZqVg6mY+Gfz0782ymQFxflau6Qxx3mJ+0etHMocNuCdgctVH390XYYMc0u+V2FCJ5cU5h/M/AxzP9ayrEO4l0ftaxL6lP0HjifNrkPcAAb+q9I67UepKO9iGw==',
]));
return $rocket;
});
self::assertEquals([
'_method' => 'GET',
'_url' => 'v3/merchant-service/complaints-v2/yansongda',
'_service_url' => 'v3/merchant-service/complaints-v2/yansongda',
], $result->getPayload()->all());
self::assertEquals('yansongda', $result->getDestination()->all()['payer_phone']);
}
public function testNormalWithEncryptedContentsWrong()
{
$payload = [
"complaint_id" => "yansongda",
];
$rocket = new Rocket();
$rocket->setPayload(new Collection($payload));
self::expectException(InvalidConfigException::class);
self::expectExceptionCode(Exception::DECRYPT_WECHAT_ENCRYPTED_CONTENTS_INVALID);
$this->plugin->assembly($rocket, function ($rocket) {
$rocket->setDestination(new Collection([
'payer_phone' => 'invalid',
]));
return $rocket;
});
}
}