Passed
Pull Request — master (#753)
by Songda
01:53
created

RadarSignPlugin::v3GetWechatAuthorization()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 25
rs 9.7666
cc 3
nc 3
nop 4
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\Exception;
12
use Yansongda\Pay\Exception\InvalidConfigException;
13
use Yansongda\Pay\Exception\InvalidParamsException;
14
15
use function Yansongda\Pay\get_public_cert;
16
use function Yansongda\Pay\get_wechat_config;
17
use function Yansongda\Pay\get_wechat_sign;
18
19
use Yansongda\Pay\Logger;
20
use Yansongda\Pay\Packer\JsonPacker;
21
use Yansongda\Pay\Packer\XmlPacker;
22
use Yansongda\Pay\Rocket;
23
use Yansongda\Supports\Collection;
24
use Yansongda\Supports\Str;
25
26
class RadarSignPlugin implements PluginInterface
27
{
28
    protected JsonPacker $jsonPacker;
29
30
    protected XmlPacker $xmlPacker;
31
32
    public function __construct(JsonPacker $jsonPacker, XmlPacker $xmlPacker)
33
    {
34
        $this->jsonPacker = $jsonPacker;
35
        $this->xmlPacker = $xmlPacker;
36
    }
37
38
    /**
39
     * @throws \Yansongda\Pay\Exception\ContainerException
40
     * @throws \Yansongda\Pay\Exception\InvalidConfigException
41
     * @throws \Yansongda\Pay\Exception\InvalidParamsException
42
     * @throws \Yansongda\Pay\Exception\ServiceNotFoundException
43
     * @throws \Exception
44
     */
45
    public function assembly(Rocket $rocket, Closure $next): Rocket
46
    {
47
        Logger::info('[wechat][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
48
49
        switch ($rocket->getParams()['_version'] ?? 'default') {
50
            case 'v2':
51
                $radar = $this->v2($rocket);
52
                break;
53
            default:
54
                $radar = $this->v3($rocket);
55
                break;
56
        }
57
58
        $rocket->setRadar($radar);
59
60
        Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
61
62
        return $next($rocket);
63
    }
64
65
    /**
66
     * @throws \Yansongda\Pay\Exception\ContainerException
67
     * @throws \Yansongda\Pay\Exception\ServiceNotFoundException
68
     * @throws \Yansongda\Pay\Exception\InvalidConfigException
69
     */
70
    protected function v2(Rocket $rocket): RequestInterface
71
    {
72
        $config = get_wechat_config($rocket->getParams());
73
        $payload = $rocket->getPayload();
74
75
        $body = $payload->all();
76
        $body['sign'] = $this->v2GetSign($config['mch_secret_key_v2'] ?? '', $payload);
0 ignored issues
show
Bug introduced by
It seems like $payload can also be of type null; however, parameter $payload of Yansongda\Pay\Plugin\Wec...SignPlugin::v2GetSign() does only seem to accept Yansongda\Supports\Collection, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
        $body['sign'] = $this->v2GetSign($config['mch_secret_key_v2'] ?? '', /** @scrutinizer ignore-type */ $payload);
Loading history...
77
78
        return $rocket->getRadar()->withBody(Utils::streamFor($this->xmlPacker->pack($body)));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $rocket->getRadar...mlPacker->pack($body))) 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...
79
    }
80
81
    /**
82
     * @throws \Yansongda\Pay\Exception\InvalidConfigException
83
     */
84
    protected function v2GetSign(?string $secret, Collection $payload): string
85
    {
86
        if (empty($secret)) {
87
            throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_secret_key_v2]');
88
        }
89
90
        $string = md5($this->v2PayloadToString($payload).'&key='.$secret);
91
92
        return strtoupper($string);
93
    }
94
95
    protected function v2PayloadToString(Collection $payload): string
96
    {
97
        $data = $payload->all();
98
99
        ksort($data);
100
101
        $buff = '';
102
103
        foreach ($data as $k => $v) {
104
            $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
105
        }
106
107
        return trim($buff, '&');
108
    }
109
110
    /**
111
     * @throws \Yansongda\Pay\Exception\ContainerException
112
     * @throws \Yansongda\Pay\Exception\InvalidConfigException
113
     * @throws \Yansongda\Pay\Exception\InvalidParamsException
114
     * @throws \Yansongda\Pay\Exception\ServiceNotFoundException
115
     * @throws \Exception
116
     */
117
    protected function v3(Rocket $rocket): RequestInterface
118
    {
119
        $timestamp = time();
120
        $random = Str::random(32);
121
        $body = $this->v3PayloadToString($rocket->getPayload());
122
        $contents = $this->v3GetContents($rocket, $timestamp, $random);
123
        $authorization = $this->v3GetWechatAuthorization($rocket->getParams(), $timestamp, $random, $contents);
124
        $radar = $rocket->getRadar()->withHeader('Authorization', $authorization);
125
126
        if (!empty($rocket->getParams()['_serial_no'])) {
127
            $radar = $radar->withHeader('Wechatpay-Serial', $rocket->getParams()['_serial_no']);
128
        }
129
130
        if (!empty($body)) {
131
            $radar = $radar->withBody(Utils::streamFor($body));
132
        }
133
134
        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...
135
    }
136
137
    /**
138
     * @throws \Yansongda\Pay\Exception\ContainerException
139
     * @throws \Yansongda\Pay\Exception\InvalidConfigException
140
     * @throws \Yansongda\Pay\Exception\ServiceNotFoundException
141
     */
142
    protected function v3GetWechatAuthorization(array $params, int $timestamp, string $random, string $contents): string
143
    {
144
        $config = get_wechat_config($params);
145
        $mchPublicCertPath = $config['mch_public_cert_path'] ?? null;
146
147
        if (empty($mchPublicCertPath)) {
148
            throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_public_cert_path]');
149
        }
150
151
        $ssl = openssl_x509_parse(get_public_cert($mchPublicCertPath));
152
153
        if (empty($ssl['serialNumberHex'])) {
154
            throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Parse [mch_public_cert_path] Serial Number Error');
155
        }
156
157
        $auth = sprintf(
158
            'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
159
            $config['mch_id'] ?? '',
160
            $random,
161
            $timestamp,
162
            $ssl['serialNumberHex'],
163
            get_wechat_sign($params, $contents),
164
        );
165
166
        return 'WECHATPAY2-SHA256-RSA2048 '.$auth;
167
    }
168
169
    /**
170
     * @throws \Yansongda\Pay\Exception\InvalidParamsException
171
     */
172
    protected function v3GetContents(Rocket $rocket, int $timestamp, string $random): string
173
    {
174
        $request = $rocket->getRadar();
175
176
        if (is_null($request)) {
177
            throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
178
        }
179
180
        $uri = $request->getUri();
181
182
        return $request->getMethod()."\n".
183
            $uri->getPath().(empty($uri->getQuery()) ? '' : '?'.$uri->getQuery())."\n".
184
            $timestamp."\n".
185
            $random."\n".
186
            $this->v3PayloadToString($rocket->getPayload())."\n";
187
    }
188
189
    protected function v3PayloadToString(?Collection $payload): string
190
    {
191
        return (is_null($payload) || 0 === $payload->count()) ? '' : $this->jsonPacker->pack($payload->all());
192
    }
193
}
194