Passed
Pull Request — master (#772)
by Songda
01:55
created

RadarSignPlugin::v2PayloadToString()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 5
c 0
b 0
f 0
nc 7
nop 1
dl 0
loc 11
rs 9.6111
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yansongda\Pay\Plugin\Wechat;
6
7
use Closure;
8
use GuzzleHttp\Psr7\Utils;
9
use Psr\Http\Message\RequestInterface;
10
use Yansongda\Pay\Contract\PluginInterface;
11
use Yansongda\Pay\Exception\ContainerException;
12
use Yansongda\Pay\Exception\Exception;
13
use Yansongda\Pay\Exception\InvalidConfigException;
14
use Yansongda\Pay\Exception\InvalidParamsException;
15
use Yansongda\Pay\Exception\ServiceNotFoundException;
16
use Yansongda\Pay\Logger;
17
use Yansongda\Pay\Packer\JsonPacker;
18
use Yansongda\Pay\Packer\XmlPacker;
19
use Yansongda\Pay\Rocket;
20
use Yansongda\Supports\Collection;
21
use Yansongda\Supports\Str;
22
23
use function Yansongda\Pay\get_public_cert;
24
use function Yansongda\Pay\get_wechat_config;
25
use function Yansongda\Pay\get_wechat_sign;
26
use function Yansongda\Pay\get_wechat_sign_v2;
27
28
class RadarSignPlugin implements PluginInterface
29
{
30
    protected JsonPacker $jsonPacker;
31
32
    protected XmlPacker $xmlPacker;
33
34
    public function __construct(JsonPacker $jsonPacker, XmlPacker $xmlPacker)
35
    {
36
        $this->jsonPacker = $jsonPacker;
37
        $this->xmlPacker = $xmlPacker;
38
    }
39
40
    /**
41
     * @throws ContainerException
42
     * @throws InvalidConfigException
43
     * @throws InvalidParamsException
44
     * @throws ServiceNotFoundException
45
     */
46
    public function assembly(Rocket $rocket, Closure $next): Rocket
47
    {
48
        Logger::debug('[wechat][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
49
50
        switch ($rocket->getParams()['_version'] ?? 'default') {
51
            case 'v2':
52
                $radar = $this->v2($rocket);
53
54
                break;
55
56
            default:
57
                $radar = $this->v3($rocket);
58
59
                break;
60
        }
61
62
        $rocket->setRadar($radar);
63
64
        Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
65
66
        return $next($rocket);
67
    }
68
69
    /**
70
     * @throws ContainerException
71
     * @throws ServiceNotFoundException
72
     * @throws InvalidConfigException
73
     * @throws \Exception
74
     */
75
    protected function v2(Rocket $rocket): RequestInterface
76
    {
77
        $rocket->mergePayload(['nonce_str' => Str::random(32)]);
78
        $rocket->mergePayload([
79
            'sign' => get_wechat_sign_v2($rocket->getParams(), $rocket->getPayload()->all()),
80
        ]);
81
82
        return $rocket->getRadar()->withBody(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $rocket->getRadar...>getPayload()->all()))) could return the type null which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface. Consider adding an additional type-check to rule them out.
Loading history...
83
            Utils::streamFor($this->xmlPacker->pack($rocket->getPayload()->all()))
84
        );
85
    }
86
87
    /**
88
     * @throws ContainerException
89
     * @throws InvalidConfigException
90
     * @throws InvalidParamsException
91
     * @throws ServiceNotFoundException
92
     * @throws \Exception
93
     */
94
    protected function v3(Rocket $rocket): RequestInterface
95
    {
96
        $timestamp = time();
97
        $random = Str::random(32);
98
        $body = $this->v3PayloadToString($rocket->getPayload());
99
        $contents = $this->v3GetContents($rocket, $timestamp, $random);
100
        $authorization = $this->v3GetWechatAuthorization($rocket->getParams(), $timestamp, $random, $contents);
101
        $radar = $rocket->getRadar()->withHeader('Authorization', $authorization);
102
103
        if (!empty($rocket->getParams()['_serial_no'])) {
104
            $radar = $radar->withHeader('Wechatpay-Serial', $rocket->getParams()['_serial_no']);
105
        }
106
107
        if (!empty($body)) {
108
            $radar = $radar->withBody(Utils::streamFor($body));
109
        }
110
111
        return $radar;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $radar could return the type null which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface. Consider adding an additional type-check to rule them out.
Loading history...
112
    }
113
114
    /**
115
     * @throws ContainerException
116
     * @throws InvalidConfigException
117
     * @throws ServiceNotFoundException
118
     */
119
    protected function v3GetWechatAuthorization(array $params, int $timestamp, string $random, string $contents): string
120
    {
121
        $config = get_wechat_config($params);
122
        $mchPublicCertPath = $config['mch_public_cert_path'] ?? null;
123
124
        if (empty($mchPublicCertPath)) {
125
            throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_public_cert_path]');
126
        }
127
128
        $ssl = openssl_x509_parse(get_public_cert($mchPublicCertPath));
129
130
        if (empty($ssl['serialNumberHex'])) {
131
            throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Parse [mch_public_cert_path] Serial Number Error');
132
        }
133
134
        $auth = sprintf(
135
            'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
136
            $config['mch_id'] ?? '',
137
            $random,
138
            $timestamp,
139
            $ssl['serialNumberHex'],
140
            get_wechat_sign($params, $contents),
141
        );
142
143
        return 'WECHATPAY2-SHA256-RSA2048 '.$auth;
144
    }
145
146
    /**
147
     * @throws InvalidParamsException
148
     */
149
    protected function v3GetContents(Rocket $rocket, int $timestamp, string $random): string
150
    {
151
        $request = $rocket->getRadar();
152
153
        if (is_null($request)) {
154
            throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
155
        }
156
157
        $uri = $request->getUri();
158
159
        return $request->getMethod()."\n".
160
            $uri->getPath().(empty($uri->getQuery()) ? '' : '?'.$uri->getQuery())."\n".
161
            $timestamp."\n".
162
            $random."\n".
163
            $this->v3PayloadToString($rocket->getPayload())."\n";
164
    }
165
166
    protected function v3PayloadToString(?Collection $payload): string
167
    {
168
        return (is_null($payload) || 0 === $payload->count()) ? '' : $this->jsonPacker->pack($payload->all());
169
    }
170
}
171