Passed
Pull Request — master (#753)
by Songda
04:14 queued 02:24
created

RadarSignPlugin::v3PayloadToString()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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