Passed
Pull Request — master (#997)
by
unknown
02:11
created

get_alipay_error_response_message()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 16
rs 9.9666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yansongda\Pay;
6
7
use JetBrains\PhpStorm\Deprecated;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Yansongda\Artful\Contract\ConfigInterface;
11
use Yansongda\Artful\Exception\ContainerException;
12
use Yansongda\Artful\Exception\InvalidConfigException;
13
use Yansongda\Artful\Exception\InvalidParamsException;
14
use Yansongda\Artful\Exception\ServiceNotFoundException;
15
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
16
use Yansongda\Artful\Plugin\ParserPlugin;
17
use Yansongda\Artful\Plugin\StartPlugin;
18
use Yansongda\Pay\Exception\DecryptException;
19
use Yansongda\Pay\Exception\Exception;
20
use Yansongda\Pay\Exception\InvalidSignException;
21
use Yansongda\Pay\Plugin\Wechat\AddRadarPlugin;
22
use Yansongda\Pay\Plugin\Wechat\ResponsePlugin;
23
use Yansongda\Pay\Plugin\Wechat\V3\AddPayloadSignaturePlugin;
24
use Yansongda\Pay\Plugin\Wechat\V3\WechatPublicCertsPlugin;
25
use Yansongda\Pay\Provider\Alipay;
26
use Yansongda\Pay\Provider\Unipay;
27
use Yansongda\Pay\Provider\Wechat;
28
use Yansongda\Supports\Collection;
29
30
use function Yansongda\Artful\get_radar_body;
31
use function Yansongda\Artful\get_radar_method;
32
33
function get_tenant(array $params = []): string
34
{
35
    return strval($params['_config'] ?? 'default');
36
}
37
38
function get_public_cert(string $key): string
39
{
40
    return is_file($key) ? file_get_contents($key) : $key;
41
}
42
43
function get_private_cert(string $key): string
44
{
45
    if (is_file($key)) {
46
        return file_get_contents($key);
47
    }
48
49
    return "-----BEGIN RSA PRIVATE KEY-----\n".
50
        wordwrap($key, 64, "\n", true).
51
        "\n-----END RSA PRIVATE KEY-----";
52
}
53
54
function get_radar_url(array $config, ?Collection $payload): ?string
55
{
56
    return match ($config['mode'] ?? Pay::MODE_NORMAL) {
57
        Pay::MODE_SERVICE => $payload?->get('_service_url') ?? $payload?->get('_url') ?? null,
58
        Pay::MODE_SANDBOX => $payload?->get('_sandbox_url') ?? $payload?->get('_url') ?? null,
59
        default => $payload?->get('_url') ?? null,
60
    };
61
}
62
63
/**
64
 * @throws ContainerException
65
 * @throws ServiceNotFoundException
66
 */
67
function get_provider_config(string $provider, array $params = []): array
68
{
69
    /** @var ConfigInterface $config */
70
    $config = Pay::get(ConfigInterface::class);
71
72
    return $config->get($provider, [])[get_tenant($params)] ?? [];
73
}
74
75
/**
76
 * @throws ContainerException
77
 * @throws ServiceNotFoundException
78
 */
79
#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
80
function get_alipay_config(array $params = []): array
81
{
82
    $alipay = Pay::get(ConfigInterface::class)->get('alipay');
83
84
    return $alipay[get_tenant($params)] ?? [];
85
}
86
87
function get_alipay_url(array $config, ?Collection $payload): string
88
{
89
    $url = get_radar_url($config, $payload) ?? '';
90
91
    if (str_starts_with($url, 'http')) {
92
        return $url;
93
    }
94
95
    return Alipay::URL[$config['mode'] ?? Pay::MODE_NORMAL];
96
}
97
98
/**
99
 * "{\"alipay_fund_trans_uni_transfer_response\":{\"code\":\"40002\",\"msg\":\"Invalid Arguments\",\"sub_code\":\"isv.invalid-app-id\",\"sub_msg\":\"无效的AppID参数\"}}".
100
 */
101
function get_alipay_error_response_message(array $contents): string
102
{
103
    // 格式化响应内容
104
    foreach (array_keys($contents) as $key) {
105
        if (str_ends_with($key, '_response')) {
106
            $contents = $contents[$key];
107
            break;
108
        }
109
    }
110
111
    $code = $contents['code'] ?? '未知code';
112
    $msg = $contents['msg'] ?? '未知异常';
113
    $subCode = $contents['sub_code'] ?? '未知sub_code';
114
    $subMsg = $contents['sub_msg'] ?? '未知sub_msg';
115
116
    return sprintf('alipay-%s: %s; 错误详情-%s: %s;', $code, $msg, $subCode, $subMsg);
117
}
118
119
/**
120
 * @throws InvalidConfigException
121
 * @throws InvalidSignException
122
 */
123
function verify_alipay_sign(array $config, string $contents, string $sign): void
124
{
125
    if (empty($sign)) {
126
        throw new InvalidSignException(
127
            Exception::SIGN_EMPTY,
128
            '签名异常: 验证支付宝签名失败-支付宝签名为空',
129
            func_get_args()
130
        );
131
    }
132
133
    $public = $config['alipay_public_cert_path'] ?? null;
134
135
    if (empty($public)) {
136
        throw new InvalidConfigException(
137
            Exception::CONFIG_ALIPAY_INVALID,
138
            '配置异常: 缺少支付宝配置 -- [alipay_public_cert_path]'
139
        );
140
    }
141
142
    $result = 1 === openssl_verify(
143
        $contents,
144
        base64_decode($sign),
145
        get_public_cert($public),
146
        OPENSSL_ALGO_SHA256
147
    );
148
149
    if (!$result) {
150
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证支付宝签名失败', func_get_args());
151
    }
152
}
153
154
/**
155
 * @throws ContainerException
156
 * @throws ServiceNotFoundException
157
 */
158
#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
159
function get_wechat_config(array $params = []): array
160
{
161
    $wechat = Pay::get(ConfigInterface::class)->get('wechat');
162
163
    return $wechat[get_tenant($params)] ?? [];
164
}
165
166
function get_wechat_method(?Collection $payload): string
167
{
168
    return get_radar_method($payload) ?? 'POST';
169
}
170
171
/**
172
 * @throws InvalidParamsException
173
 */
174
function get_wechat_url(array $config, ?Collection $payload): string
175
{
176
    $url = get_radar_url($config, $payload);
177
178
    if (empty($url)) {
179
        throw new InvalidParamsException(
180
            Exception::PARAMS_WECHAT_URL_MISSING,
181
            '参数异常: 微信 `_url` 或 `_service_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`'
182
        );
183
    }
184
185
    if (str_starts_with($url, 'http')) {
186
        return $url;
187
    }
188
189
    return Wechat::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
190
}
191
192
/**
193
 * @throws InvalidParamsException
194
 */
195
function get_wechat_body(?Collection $payload): mixed
196
{
197
    $body = get_radar_body($payload);
198
199
    if (is_null($body)) {
200
        throw new InvalidParamsException(
201
            Exception::PARAMS_WECHAT_BODY_MISSING,
202
            '参数异常: 微信 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`'
203
        );
204
    }
205
206
    return $body;
207
}
208
209
function get_wechat_type_key(array $params): string
210
{
211
    $key = ($params['_type'] ?? 'mp').'_app_id';
212
213
    if ('app_app_id' === $key) {
214
        $key = 'app_id';
215
    }
216
217
    return $key;
218
}
219
220
/**
221
 * @throws InvalidConfigException
222
 */
223
function get_wechat_sign(array $config, string $contents): string
224
{
225
    $privateKey = $config['mch_secret_cert'] ?? null;
226
227
    if (empty($privateKey)) {
228
        throw new InvalidConfigException(
229
            Exception::CONFIG_WECHAT_INVALID,
230
            '配置异常: 缺少微信配置 -- [mch_secret_cert]'
231
        );
232
    }
233
234
    $privateKey = get_private_cert($privateKey);
235
236
    openssl_sign($contents, $sign, $privateKey, 'sha256WithRSAEncryption');
237
238
    return base64_encode($sign);
239
}
240
241
/**
242
 * @throws InvalidConfigException
243
 */
244
function get_wechat_sign_v2(array $config, array $payload, bool $upper = true): string
245
{
246
    $key = $config['mch_secret_key_v2'] ?? null;
247
248
    if (empty($key)) {
249
        throw new InvalidConfigException(
250
            Exception::CONFIG_WECHAT_INVALID,
251
            '配置异常: 缺少微信配置 -- [mch_secret_key_v2]'
252
        );
253
    }
254
255
    ksort($payload);
256
257
    $buff = '';
258
259
    foreach ($payload as $k => $v) {
260
        $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
261
    }
262
263
    $sign = md5($buff.'key='.$key);
264
265
    return $upper ? strtoupper($sign) : $sign;
266
}
267
268
/**
269
 * @throws ContainerException
270
 * @throws DecryptException
271
 * @throws InvalidConfigException
272
 * @throws InvalidParamsException
273
 * @throws InvalidSignException
274
 * @throws ServiceNotFoundException
275
 */
276
function verify_wechat_sign(ResponseInterface|ServerRequestInterface $message, array $params): void
277
{
278
    if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {
279
        return;
280
    }
281
282
    $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
283
    $timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
284
    $random = $message->getHeaderLine('Wechatpay-Nonce');
285
    $sign = $message->getHeaderLine('Wechatpay-Signature');
286
    $body = (string) $message->getBody();
287
288
    $content = $timestamp."\n".$random."\n".$body."\n";
289
    $public = get_provider_config('wechat', $params)['wechat_public_cert_path'][$wechatSerial] ?? null;
290
291
    if (empty($sign)) {
292
        throw new InvalidSignException(
293
            Exception::SIGN_EMPTY,
294
            '签名异常: 微信签名为空',
295
            ['headers' => $message->getHeaders(), 'body' => $body]
296
        );
297
    }
298
299
    $public = get_public_cert(
300
        empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public
301
    );
302
303
    $result = 1 === openssl_verify(
304
        $content,
305
        base64_decode($sign),
306
        $public,
307
        'sha256WithRSAEncryption'
308
    );
309
310
    if (!$result) {
311
        throw new InvalidSignException(
312
            Exception::SIGN_ERROR,
313
            '签名异常: 验证微信签名失败',
314
            ['headers' => $message->getHeaders(), 'body' => $body]
315
        );
316
    }
317
}
318
319
/**
320
 * @throws InvalidConfigException
321
 * @throws InvalidSignException
322
 */
323
function verify_wechat_sign_v2(array $config, array $destination): void
324
{
325
    $sign = $destination['sign'] ?? null;
326
327
    if (empty($sign)) {
328
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', $destination);
329
    }
330
331
    $key = $config['mch_secret_key_v2'] ?? null;
332
333
    if (empty($key)) {
334
        throw new InvalidConfigException(
335
            Exception::CONFIG_WECHAT_INVALID,
336
            '配置异常: 缺少微信配置 -- [mch_secret_key_v2]'
337
        );
338
    }
339
340
    if (get_wechat_sign_v2($config, $destination) !== $sign) {
341
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', $destination);
342
    }
343
}
344
345
function encrypt_wechat_contents(string $contents, string $publicKey): ?string
346
{
347
    if (openssl_public_encrypt($contents, $encrypted, get_public_cert($publicKey), OPENSSL_PKCS1_OAEP_PADDING)) {
348
        return base64_encode($encrypted);
349
    }
350
351
    return null;
352
}
353
354
function decrypt_wechat_contents(string $encrypted, array $config): ?string
355
{
356
    if (openssl_private_decrypt(
357
        base64_decode($encrypted),
358
        $decrypted,
359
        get_private_cert($config['mch_secret_cert'] ?? ''),
360
        OPENSSL_PKCS1_OAEP_PADDING
361
    )) {
362
        return $decrypted;
363
    }
364
365
    return null;
366
}
367
368
/**
369
 * @throws ContainerException
370
 * @throws DecryptException
371
 * @throws InvalidConfigException
372
 * @throws InvalidParamsException
373
 * @throws ServiceNotFoundException
374
 */
375
function reload_wechat_public_certs(array $params, ?string $serialNo = null): string
376
{
377
    $data = Pay::wechat()->pay(
378
        [
379
            StartPlugin::class,
380
            WechatPublicCertsPlugin::class,
381
            AddPayloadBodyPlugin::class,
382
            AddPayloadSignaturePlugin::class,
383
            AddRadarPlugin::class,
384
            ResponsePlugin::class,
385
            ParserPlugin::class,
386
        ],
387
        $params
388
    )->get('data', []);
0 ignored issues
show
Bug introduced by
The method get() does not exist on Psr\Http\Message\MessageInterface. ( Ignorable by Annotation )

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

388
    )->/** @scrutinizer ignore-call */ get('data', []);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
389
390
    $wechatConfig = get_provider_config('wechat', $params);
391
392
    foreach ($data as $item) {
393
        $certs[$item['serial_no']] = decrypt_wechat_resource(
394
            $item['encrypt_certificate'],
395
            $wechatConfig
396
        )['ciphertext'] ?? '';
397
    }
398
399
    Pay::get(ConfigInterface::class)->set(
400
        'wechat.'.get_tenant($params).'.wechat_public_cert_path',
401
        ((array) ($wechatConfig['wechat_public_cert_path'] ?? [])) + ($certs ?? []),
402
    );
403
404
    if (!is_null($serialNo) && empty($certs[$serialNo])) {
405
        throw new InvalidConfigException(
406
            Exception::CONFIG_WECHAT_INVALID,
407
            '配置异常: 获取微信 wechat_public_cert_path 配置失败'
408
        );
409
    }
410
411
    return $certs[$serialNo] ?? '';
412
}
413
414
/**
415
 * @throws ContainerException
416
 * @throws DecryptException
417
 * @throws InvalidConfigException
418
 * @throws InvalidParamsException
419
 * @throws ServiceNotFoundException
420
 */
421
function get_wechat_public_certs(array $params = [], ?string $path = null): void
422
{
423
    reload_wechat_public_certs($params);
424
425
    $config = get_provider_config('wechat', $params);
426
427
    if (empty($path)) {
428
        var_dump($config['wechat_public_cert_path']);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($config['wechat_public_cert_path']) looks like debug code. Are you sure you do not want to remove it?
Loading history...
429
430
        return;
431
    }
432
433
    foreach ($config['wechat_public_cert_path'] as $serialNo => $cert) {
434
        file_put_contents($path.'/'.$serialNo.'.crt', $cert);
435
    }
436
}
437
438
/**
439
 * @throws InvalidConfigException
440
 * @throws DecryptException
441
 */
442
function decrypt_wechat_resource(array $resource, array $config): array
443
{
444
    $ciphertext = base64_decode($resource['ciphertext'] ?? '');
445
    $secret = $config['mch_secret_key'] ?? null;
446
447
    if (strlen($ciphertext) <= Wechat::AUTH_TAG_LENGTH_BYTE) {
448
        throw new DecryptException(
449
            Exception::DECRYPT_WECHAT_CIPHERTEXT_PARAMS_INVALID,
450
            '加解密异常: ciphertext 位数过短'
451
        );
452
    }
453
454
    if (is_null($secret) || Wechat::MCH_SECRET_KEY_LENGTH_BYTE != strlen($secret)) {
455
        throw new InvalidConfigException(
456
            Exception::CONFIG_WECHAT_INVALID,
457
            '配置异常: 缺少微信配置 -- [mch_secret_key]'
458
        );
459
    }
460
461
    $resource['ciphertext'] = match ($resource['algorithm'] ?? '') {
462
        'AEAD_AES_256_GCM' => decrypt_wechat_resource_aes_256_gcm(
463
            $ciphertext,
464
            $secret,
465
            $resource['nonce'] ?? '',
466
            $resource['associated_data'] ?? ''
467
        ),
468
        default => throw new DecryptException(
469
            Exception::DECRYPT_WECHAT_DECRYPTED_METHOD_INVALID,
470
            '加解密异常: algorithm 不支持'
471
        ),
472
    };
473
474
    return $resource;
475
}
476
477
/**
478
 * @throws DecryptException
479
 */
480
function decrypt_wechat_resource_aes_256_gcm(
481
    string $ciphertext,
482
    string $secret,
483
    string $nonce,
484
    string $associatedData
485
): array|string {
486
    $decrypted = openssl_decrypt(
487
        substr($ciphertext, 0, -Wechat::AUTH_TAG_LENGTH_BYTE),
488
        'aes-256-gcm',
489
        $secret,
490
        OPENSSL_RAW_DATA,
491
        $nonce,
492
        substr($ciphertext, -Wechat::AUTH_TAG_LENGTH_BYTE),
493
        $associatedData
494
    );
495
496
    if (false === $decrypted) {
497
        throw new DecryptException(
498
            Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID,
499
            '加解密异常: 解密失败,请检查微信 mch_secret_key 是否正确'
500
        );
501
    }
502
503
    if ('certificate' !== $associatedData) {
504
        $decrypted = json_decode($decrypted, true);
505
506
        if (JSON_ERROR_NONE !== json_last_error()) {
507
            throw new DecryptException(
508
                Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID,
509
                '加解密异常: 待解密数据非正常数据'
510
            );
511
        }
512
    }
513
514
    return $decrypted;
515
}
516
517
/**
518
 * @throws ContainerException
519
 * @throws DecryptException
520
 * @throws InvalidConfigException
521
 * @throws InvalidParamsException
522
 * @throws ServiceNotFoundException
523
 */
524
function get_wechat_serial_no(array $params): string
525
{
526
    if (!empty($params['_serial_no'])) {
527
        return $params['_serial_no'];
528
    }
529
530
    $config = get_provider_config('wechat', $params);
531
532
    if (empty($config['wechat_public_cert_path'])) {
533
        reload_wechat_public_certs($params);
534
535
        $config = get_provider_config('wechat', $params);
536
    }
537
538
    mt_srand();
539
540
    return strval(array_rand($config['wechat_public_cert_path']));
541
}
542
543
/**
544
 * @throws InvalidParamsException
545
 */
546
function get_wechat_public_key(array $config, string $serialNo): string
547
{
548
    $publicKey = $config['wechat_public_cert_path'][$serialNo] ?? null;
549
550
    if (empty($publicKey)) {
551
        throw new InvalidParamsException(
552
            Exception::PARAMS_WECHAT_SERIAL_NOT_FOUND,
553
            '参数异常: 微信公钥序列号为找到 -'.$serialNo
554
        );
555
    }
556
557
    return $publicKey;
558
}
559
560
/**
561
 * @throws InvalidConfigException
562
 */
563
function get_wechat_miniprogram_pay_sign(array $config, string $url, string $payload): string
564
{
565
    if (empty($config['mini_app_key_virtual_pay'])) {
566
        throw new InvalidConfigException(
567
            Exception::CONFIG_WECHAT_INVALID,
568
            '配置异常: 缺少微信配置 -- [mini_app_key_virtual_pay]'
569
        );
570
    }
571
572
    return hash_hmac('sha256', $url.'&'.$payload, $config['mini_app_key_virtual_pay']);
573
}
574
575
function get_wechat_miniprogram_user_sign(string $sessionKey, string $payload): string
576
{
577
    return hash_hmac('sha256', $payload, $sessionKey);
578
}
579
580
/**
581
 * @throws ContainerException
582
 * @throws ServiceNotFoundException
583
 */
584
#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
585
function get_unipay_config(array $params = []): array
586
{
587
    $unipay = Pay::get(ConfigInterface::class)->get('unipay');
588
589
    return $unipay[get_tenant($params)] ?? [];
590
}
591
592
/**
593
 * @throws InvalidConfigException
594
 * @throws InvalidSignException
595
 */
596
function verify_unipay_sign(array $config, string $contents, string $sign, ?string $signPublicKeyCert = null): void
597
{
598
    if (empty($sign)) {
599
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', func_get_args());
600
    }
601
602
    if (empty($signPublicKeyCert) && empty($public = $config['unipay_public_cert_path'] ?? null)) {
603
        throw new InvalidConfigException(
604
            Exception::CONFIG_UNIPAY_INVALID,
605
            '配置异常: 缺少银联配置 -- [unipay_public_cert_path]'
606
        );
607
    }
608
609
    $result = 1 === openssl_verify(
610
        hash('sha256', $contents),
611
        base64_decode($sign),
612
        get_public_cert($signPublicKeyCert ?? $public ?? ''),
613
        'sha256'
614
    );
615
616
    if (!$result) {
617
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', func_get_args());
618
    }
619
}
620
621
/**
622
 * @throws InvalidParamsException
623
 */
624
function get_unipay_url(array $config, ?Collection $payload): string
625
{
626
    $url = get_radar_url($config, $payload);
627
628
    if (empty($url)) {
629
        throw new InvalidParamsException(
630
            Exception::PARAMS_UNIPAY_URL_MISSING,
631
            '参数异常: 银联 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`'
632
        );
633
    }
634
635
    if (str_starts_with($url, 'http')) {
636
        return $url;
637
    }
638
639
    return Unipay::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
640
}
641
642
/**
643
 * @throws InvalidParamsException
644
 */
645
function get_unipay_body(?Collection $payload): string
646
{
647
    $body = get_radar_body($payload);
648
649
    if (is_null($body)) {
650
        throw new InvalidParamsException(
651
            Exception::PARAMS_UNIPAY_BODY_MISSING,
652
            '参数异常: 银联 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`'
653
        );
654
    }
655
656
    return $body;
657
}
658
659
/**
660
 * @throws InvalidConfigException
661
 */
662
function get_unipay_sign_qra(array $config, array $payload): string
663
{
664
    $key = $config['mch_secret_key'] ?? null;
665
666
    if (empty($key)) {
667
        throw new InvalidConfigException(
668
            Exception::CONFIG_UNIPAY_INVALID,
669
            '配置异常: 缺少银联配置 -- [mch_secret_key]'
670
        );
671
    }
672
673
    ksort($payload);
674
675
    $buff = '';
676
677
    foreach ($payload as $k => $v) {
678
        $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
679
    }
680
681
    return strtoupper(md5($buff.'key='.$key));
682
}
683
684
/**
685
 * @throws InvalidConfigException
686
 * @throws InvalidSignException
687
 */
688
function verify_unipay_sign_qra(array $config, array $destination): void
689
{
690
    $sign = $destination['sign'] ?? null;
691
692
    if (empty($sign)) {
693
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', $destination);
694
    }
695
696
    $key = $config['mch_secret_key'] ?? null;
697
698
    if (empty($key)) {
699
        throw new InvalidConfigException(
700
            Exception::CONFIG_UNIPAY_INVALID,
701
            '配置异常: 缺少银联配置 -- [mch_secret_key]'
702
        );
703
    }
704
705
    if (get_unipay_sign_qra($config, $destination) !== $sign) {
706
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', $destination);
707
    }
708
}
709