之前在做开发时,意识到APIv3接口权限过大问题的一些建议。现在,你可以参考如下实现,来定制PHP版的APIv3开发包,使商户私钥
以及接口请求
安全管控可控。
定制
wechatpay/wechapay 开发包默认是使用的本地签名和验签方式来和官方接口交互,如对安全管控要求较高时,你可以通过实现signer
及/或verifier
中间件来定制签名服务或/及验签服务,来让你的系统把商户私钥集中存储,SDK从属的系统系统,仅需通过远程服务进行签名/验签。
以下示例用来演示如何替换SDK内置中间件,来实现远程请求签名
及结果验签
,供大家参考实现。
服务定制
假设集中管理服务器接入点为内网http://192.168.169.170:8080/
地址,并提供两个URI供签名及验签:
/wechatpay-merchant-request-signature
为请求签名/wechatpay-response-merchant-validation
为响应验签
SDK中间件定制
use GuzzleHttp\Client;
use GuzzleHttp\Middleware;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
$client = new Client(['base_uri' => 'http://192.168.169.170:8080/']);
// 请求参数签名,返回字符串形如`\WeChatPay\Formatter::authorization`返回的字符串
$remoteSigner = function (RequestInterface $request) use ($client, $merchantId): string {
return (string)$client->post('/wechatpay-merchant-request-signature', ['json' => [
'mchid' => $merchantId,
'verb' => $request->getMethod(),
'uri' => $request->getRequestTarget(),
'body' => (string)$request->getBody(),
]])->getBody();
};
// 返回结果验签,返回可以是4xx,5xx,与远程验签应用约定返回字符串'OK'为验签通过
$remoteVerifier = function (ResponseInterface $response) use ($client, $merchantId): string {
[$nonce] = $response->getHeader('Wechatpay-Nonce');
[$serial] = $response->getHeader('Wechatpay-Serial');
[$signature] = $response->getHeader('Wechatpay-Signature');
[$timestamp] = $response->getHeader('Wechatpay-Timestamp');
return (string)$client->post('/wechatpay-response-merchant-validation', ['json' => [
'mchid' => $merchantId,
'nonce' => $nonce,
'serial' => $serial,
'signature' => $signature,
'timestamp' => $timestamp,
'body' => (string)$response->getBody(),
]])->getBody();
};
正常初始化
仅需保证商户号为正确商户号即可,其他必填参数,可以任意值,例如:
use WeChatPay\Builder;
// 商户号,假定为`1000100`
$merchantId = '1000100';
// 工厂方法构造一个实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => 'nop',
'privateKey' => 'any',
'certs' => [ 'any' => null],
]);
注册及替换SDK默认中间件
$stack = $instance->getDriver()->select()->getConfig('handler');
// 卸载SDK内置签名中间件
$stack->remove('signer');
// 注册内网远程请求签名中间件
$stack->before('prepare_body', Middleware::mapRequest(
static function (RequestInterface $request) use ($remoteSigner): RequestInterface {
return $request->withHeader('Authorization', $remoteSigner($request));
}
), 'signer');
// 卸载SDK内置验签中间件
$stack->remove('verifier');
// 注册内网远程请求验签中间件
$stack->before('http_errors', static function (callable $handler) use ($remoteVerifier): callable {
return static function (RequestInterface $request, array $options = []) use ($remoteVerifier, $handler) {
return $handler($request, $options)->then(
static function(ResponseInterface $response) use ($remoteVerifier, $request): ResponseInterface {
$verified = '';
try {
$verified = $remoteVerifier($response);
} catch (\Throwable $exception) {}
if ($verified === 'OK') { //远程验签约定,返回字符串`OK`作为验签通过
throw new RequestException('签名验签失败', $request, $response, $exception ?? null);
}
return $response;
}
);
};
}, 'verifier');
正常 Chainable/PromiseA+ 使用SDK
异步模式请求平台证书接口
$instance
->V3->Certificates
->getAsync()
->then(static function($res) { return $res->getBody(); })
->wait();
退款
$res = $instance
->chain('v3/refund/domestic/refunds')
->postAsync([
'json' => [
'transaction_id' => '1217752501201407033233368018',
'out_refund_no' => '1217752501201407033233368018',
'amount' => [
'refund' => 888,
'total' => 888,
'currency' => 'CNY',
],
],
])
->then(static function($response) {
// 正常逻辑回调处理
// do something
})
->otherwise(static function($e) {
// 异常错误处理
// do something
})
->wait();
备注
远程请求签名,可以按需加入ACL,推荐对请求的 VERB
+URI
做权限控制,即可解开商户私钥分发
以及接口权限
过大等问题。