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

RadarSignPlugin::v3GetContents()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 15
rs 9.9666
cc 3
nc 3
nop 3
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\Rocket;
21
22
use function Yansongda\Pay\to_xml;
0 ignored issues
show
introduced by
The function Yansongda\Pay\to_xml was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
23
24
use Yansongda\Supports\Collection;
25
use Yansongda\Supports\Str;
26
27
class RadarSignPlugin implements PluginInterface
28
{
29
    /**
30
     * @throws \Yansongda\Pay\Exception\ContainerException
31
     * @throws \Yansongda\Pay\Exception\InvalidConfigException
32
     * @throws \Yansongda\Pay\Exception\InvalidParamsException
33
     * @throws \Yansongda\Pay\Exception\ServiceNotFoundException
34
     * @throws \Exception
35
     */
36
    public function assembly(Rocket $rocket, Closure $next): Rocket
37
    {
38
        Logger::info('[wechat][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
39
40
        switch ($rocket->getParams()['_version'] ?? 'default') {
41
            case 'v2':
42
                $radar = $this->v2($rocket);
43
                break;
44
            default:
45
                $radar = $this->v3($rocket);
46
                break;
47
        }
48
49
        $rocket->setRadar($radar);
50
51
        Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
52
53
        return $next($rocket);
54
    }
55
56
    /**
57
     * @throws \Yansongda\Pay\Exception\ContainerException
58
     * @throws \Yansongda\Pay\Exception\ServiceNotFoundException
59
     * @throws \Yansongda\Pay\Exception\InvalidConfigException
60
     */
61
    protected function v2(Rocket $rocket): RequestInterface
62
    {
63
        $config = get_wechat_config($rocket->getParams());
64
        $payload = $rocket->getPayload();
65
66
        $body = $payload->all();
67
        $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

67
        $body['sign'] = $this->v2GetSign($config['mch_secret_key_v2'] ?? '', /** @scrutinizer ignore-type */ $payload);
Loading history...
68
69
        return $rocket->getRadar()->withBody(Utils::streamFor(to_xml($body)));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $rocket->getRadar...reamFor(to_xml($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...
Bug introduced by
The function to_xml was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

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