Issues (120)

src/Functions.php (2 issues)

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\Douyin;
27
use Yansongda\Pay\Provider\Jsb;
28
use Yansongda\Pay\Provider\Unipay;
29
use Yansongda\Pay\Provider\Wechat;
30
use Yansongda\Supports\Collection;
31
use Yansongda\Supports\Str;
32
33
use function Yansongda\Artful\get_radar_body;
34
use function Yansongda\Artful\get_radar_method;
35
36
function get_tenant(array $params = []): string
37
{
38
    return strval($params['_config'] ?? 'default');
39
}
40
41
function get_public_cert(string $key): string
42
{
43
    return is_file($key) ? file_get_contents($key) : $key;
44
}
45
46
function get_private_cert(string $key): string
47
{
48
    if (is_file($key)) {
49
        return file_get_contents($key);
50
    }
51
52
    if (Str::startsWith($key, '-----BEGIN PRIVATE KEY-----')) {
53
        return $key;
54
    }
55
56
    return "-----BEGIN RSA PRIVATE KEY-----\n"
57
        .wordwrap($key, 64, "\n", true)
58
        ."\n-----END RSA PRIVATE KEY-----";
59
}
60
61
function get_radar_url(array $config, ?Collection $payload): ?string
62
{
63
    return match ($config['mode'] ?? Pay::MODE_NORMAL) {
64
        Pay::MODE_SERVICE => $payload?->get('_service_url') ?? $payload?->get('_url') ?? null,
65
        Pay::MODE_SANDBOX => $payload?->get('_sandbox_url') ?? $payload?->get('_url') ?? null,
66
        default => $payload?->get('_url') ?? null,
67
    };
68
}
69
70
/**
71
 * @throws ContainerException
72
 * @throws ServiceNotFoundException
73
 */
74
function get_provider_config(string $provider, array $params = []): array
75
{
76
    /** @var ConfigInterface $config */
77
    $config = Pay::get(ConfigInterface::class);
78
79
    return $config->get($provider, [])[get_tenant($params)] ?? [];
80
}
81
82
/**
83
 * @throws ContainerException
84
 * @throws ServiceNotFoundException
85
 */
86
#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
87
function get_alipay_config(array $params = []): array
88
{
89
    $alipay = Pay::get(ConfigInterface::class)->get('alipay');
90
91
    return $alipay[get_tenant($params)] ?? [];
92
}
93
94
function get_alipay_url(array $config, ?Collection $payload): string
95
{
96
    $url = get_radar_url($config, $payload) ?? '';
97
98
    if (str_starts_with($url, 'http')) {
99
        return $url;
100
    }
101
102
    return Alipay::URL[$config['mode'] ?? Pay::MODE_NORMAL];
103
}
104
105
/**
106
 * @throws InvalidConfigException
107
 * @throws InvalidSignException
108
 */
109
function verify_alipay_sign(array $config, string $contents, string $sign): void
110
{
111
    if (empty($sign)) {
112
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证支付宝签名失败-支付宝签名为空', func_get_args());
113
    }
114
115
    $public = $config['alipay_public_cert_path'] ?? null;
116
117
    if (empty($public)) {
118
        throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [alipay_public_cert_path]');
119
    }
120
121
    $result = 1 === openssl_verify(
122
        $contents,
123
        base64_decode($sign),
124
        get_public_cert($public),
125
        OPENSSL_ALGO_SHA256
126
    );
127
128
    if (!$result) {
129
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证支付宝签名失败', func_get_args());
130
    }
131
}
132
133
/**
134
 * @throws ContainerException
135
 * @throws ServiceNotFoundException
136
 */
137
#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
138
function get_wechat_config(array $params = []): array
139
{
140
    $wechat = Pay::get(ConfigInterface::class)->get('wechat');
141
142
    return $wechat[get_tenant($params)] ?? [];
143
}
144
145
function get_wechat_method(?Collection $payload): string
146
{
147
    return get_radar_method($payload) ?? 'POST';
148
}
149
150
/**
151
 * @throws InvalidParamsException
152
 */
153
function get_wechat_url(array $config, ?Collection $payload): string
154
{
155
    $url = get_radar_url($config, $payload);
156
157
    if (empty($url)) {
158
        throw new InvalidParamsException(Exception::PARAMS_WECHAT_URL_MISSING, '参数异常: 微信 `_url` 或 `_service_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
159
    }
160
161
    if (str_starts_with($url, 'http')) {
162
        return $url;
163
    }
164
165
    return Wechat::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
166
}
167
168
/**
169
 * @throws InvalidParamsException
170
 */
171
function get_wechat_body(?Collection $payload): mixed
172
{
173
    $body = get_radar_body($payload);
174
175
    if (is_null($body)) {
176
        throw new InvalidParamsException(Exception::PARAMS_WECHAT_BODY_MISSING, '参数异常: 微信 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`');
177
    }
178
179
    return $body;
180
}
181
182
function get_wechat_type_key(array $params): string
183
{
184
    $key = ($params['_type'] ?? 'mp').'_app_id';
185
186
    if ('app_app_id' === $key) {
187
        $key = 'app_id';
188
    }
189
190
    return $key;
191
}
192
193
/**
194
 * @throws InvalidConfigException
195
 */
196
function get_wechat_sign(array $config, string $contents): string
197
{
198
    $privateKey = $config['mch_secret_cert'] ?? null;
199
200
    if (empty($privateKey)) {
201
        throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_cert]');
202
    }
203
204
    $privateKey = get_private_cert($privateKey);
205
206
    openssl_sign($contents, $sign, $privateKey, 'sha256WithRSAEncryption');
207
208
    return base64_encode($sign);
209
}
210
211
/**
212
 * @throws InvalidConfigException
213
 */
214
function get_wechat_sign_v2(array $config, array $payload, bool $upper = true): string
215
{
216
    $key = $config['mch_secret_key_v2'] ?? null;
217
218
    if (empty($key)) {
219
        throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]');
220
    }
221
222
    ksort($payload);
223
224
    $buff = '';
225
226
    foreach ($payload as $k => $v) {
227
        $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
228
    }
229
230
    $sign = md5($buff.'key='.$key);
231
232
    return $upper ? strtoupper($sign) : $sign;
233
}
234
235
/**
236
 * @throws ContainerException
237
 * @throws DecryptException
238
 * @throws InvalidConfigException
239
 * @throws InvalidParamsException
240
 * @throws InvalidSignException
241
 * @throws ServiceNotFoundException
242
 */
243
function verify_wechat_sign(ResponseInterface|ServerRequestInterface $message, array $params): void
244
{
245
    if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {
246
        return;
247
    }
248
249
    $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
250
    $timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
251
    $random = $message->getHeaderLine('Wechatpay-Nonce');
252
    $sign = $message->getHeaderLine('Wechatpay-Signature');
253
    $body = (string) $message->getBody();
254
255
    $content = $timestamp."\n".$random."\n".$body."\n";
256
    $public = get_provider_config('wechat', $params)['wechat_public_cert_path'][$wechatSerial] ?? null;
257
258
    if (empty($sign)) {
259
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', ['headers' => $message->getHeaders(), 'body' => $body]);
260
    }
261
262
    $public = get_public_cert(
263
        empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public
264
    );
265
266
    $result = 1 === openssl_verify(
267
        $content,
268
        base64_decode($sign),
269
        $public,
270
        'sha256WithRSAEncryption'
271
    );
272
273
    if (!$result) {
274
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', ['headers' => $message->getHeaders(), 'body' => $body]);
275
    }
276
}
277
278
/**
279
 * @throws InvalidConfigException
280
 * @throws InvalidSignException
281
 */
282
function verify_wechat_sign_v2(array $config, array $destination): void
283
{
284
    $sign = $destination['sign'] ?? null;
285
286
    if (empty($sign)) {
287
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', $destination);
288
    }
289
290
    $key = $config['mch_secret_key_v2'] ?? null;
291
292
    if (empty($key)) {
293
        throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]');
294
    }
295
296
    if (get_wechat_sign_v2($config, $destination) !== $sign) {
297
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', $destination);
298
    }
299
}
300
301
function encrypt_wechat_contents(string $contents, string $publicKey): ?string
302
{
303
    if (openssl_public_encrypt($contents, $encrypted, get_public_cert($publicKey), OPENSSL_PKCS1_OAEP_PADDING)) {
304
        return base64_encode($encrypted);
305
    }
306
307
    return null;
308
}
309
310
function decrypt_wechat_contents(string $encrypted, array $config): ?string
311
{
312
    if (openssl_private_decrypt(base64_decode($encrypted), $decrypted, get_private_cert($config['mch_secret_cert'] ?? ''), OPENSSL_PKCS1_OAEP_PADDING)) {
313
        return $decrypted;
314
    }
315
316
    return null;
317
}
318
319
/**
320
 * @throws ContainerException
321
 * @throws DecryptException
322
 * @throws InvalidConfigException
323
 * @throws InvalidParamsException
324
 * @throws ServiceNotFoundException
325
 */
326
function reload_wechat_public_certs(array $params, ?string $serialNo = null): string
327
{
328
    $data = Pay::wechat()->pay(
329
        [StartPlugin::class, WechatPublicCertsPlugin::class, AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class],
330
        $params
331
    )->get('data', []);
0 ignored issues
show
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

331
    )->/** @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...
332
333
    $wechatConfig = get_provider_config('wechat', $params);
334
335
    foreach ($data as $item) {
336
        $certs[$item['serial_no']] = decrypt_wechat_resource($item['encrypt_certificate'], $wechatConfig)['ciphertext'] ?? '';
337
    }
338
339
    Pay::get(ConfigInterface::class)->set(
340
        'wechat.'.get_tenant($params).'.wechat_public_cert_path',
341
        ((array) ($wechatConfig['wechat_public_cert_path'] ?? [])) + ($certs ?? []),
342
    );
343
344
    if (!is_null($serialNo) && empty($certs[$serialNo])) {
345
        throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 获取微信 wechat_public_cert_path 配置失败');
346
    }
347
348
    return $certs[$serialNo] ?? '';
349
}
350
351
/**
352
 * @throws ContainerException
353
 * @throws DecryptException
354
 * @throws InvalidConfigException
355
 * @throws InvalidParamsException
356
 * @throws ServiceNotFoundException
357
 */
358
function get_wechat_public_certs(array $params = [], ?string $path = null): void
359
{
360
    reload_wechat_public_certs($params);
361
362
    $config = get_provider_config('wechat', $params);
363
364
    if (empty($path)) {
365
        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...
366
367
        return;
368
    }
369
370
    foreach ($config['wechat_public_cert_path'] as $serialNo => $cert) {
371
        file_put_contents($path.'/'.$serialNo.'.crt', $cert);
372
    }
373
}
374
375
/**
376
 * @throws InvalidConfigException
377
 * @throws DecryptException
378
 */
379
function decrypt_wechat_resource(array $resource, array $config): array
380
{
381
    $ciphertext = base64_decode($resource['ciphertext'] ?? '');
382
    $secret = $config['mch_secret_key'] ?? null;
383
384
    if (strlen($ciphertext) <= Wechat::AUTH_TAG_LENGTH_BYTE) {
385
        throw new DecryptException(Exception::DECRYPT_WECHAT_CIPHERTEXT_PARAMS_INVALID, '加解密异常: ciphertext 位数过短');
386
    }
387
388
    if (is_null($secret) || Wechat::MCH_SECRET_KEY_LENGTH_BYTE != strlen($secret)) {
389
        throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key]');
390
    }
391
392
    $resource['ciphertext'] = match ($resource['algorithm'] ?? '') {
393
        'AEAD_AES_256_GCM' => decrypt_wechat_resource_aes_256_gcm($ciphertext, $secret, $resource['nonce'] ?? '', $resource['associated_data'] ?? ''),
394
        default => throw new DecryptException(Exception::DECRYPT_WECHAT_DECRYPTED_METHOD_INVALID, '加解密异常: algorithm 不支持'),
395
    };
396
397
    return $resource;
398
}
399
400
/**
401
 * @throws DecryptException
402
 */
403
function decrypt_wechat_resource_aes_256_gcm(string $ciphertext, string $secret, string $nonce, string $associatedData): array|string
404
{
405
    $decrypted = openssl_decrypt(
406
        substr($ciphertext, 0, -Wechat::AUTH_TAG_LENGTH_BYTE),
407
        'aes-256-gcm',
408
        $secret,
409
        OPENSSL_RAW_DATA,
410
        $nonce,
411
        substr($ciphertext, -Wechat::AUTH_TAG_LENGTH_BYTE),
412
        $associatedData
413
    );
414
415
    if (false === $decrypted) {
416
        throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 解密失败,请检查微信 mch_secret_key 是否正确');
417
    }
418
419
    if ('certificate' !== $associatedData) {
420
        $decrypted = json_decode($decrypted, true);
421
422
        if (JSON_ERROR_NONE !== json_last_error()) {
423
            throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 待解密数据非正常数据');
424
        }
425
    }
426
427
    return $decrypted;
428
}
429
430
/**
431
 * @throws ContainerException
432
 * @throws DecryptException
433
 * @throws InvalidConfigException
434
 * @throws InvalidParamsException
435
 * @throws ServiceNotFoundException
436
 */
437
function get_wechat_serial_no(array $params): string
438
{
439
    if (!empty($params['_serial_no'])) {
440
        return $params['_serial_no'];
441
    }
442
443
    $config = get_provider_config('wechat', $params);
444
445
    if (empty($config['wechat_public_cert_path'])) {
446
        reload_wechat_public_certs($params);
447
448
        $config = get_provider_config('wechat', $params);
449
    }
450
451
    mt_srand();
452
453
    return strval(array_rand($config['wechat_public_cert_path']));
454
}
455
456
/**
457
 * @throws InvalidParamsException
458
 */
459
function get_wechat_public_key(array $config, string $serialNo): string
460
{
461
    $publicKey = $config['wechat_public_cert_path'][$serialNo] ?? null;
462
463
    if (empty($publicKey)) {
464
        throw new InvalidParamsException(Exception::PARAMS_WECHAT_SERIAL_NOT_FOUND, '参数异常: 微信公钥序列号未找到 - '.$serialNo);
465
    }
466
467
    return $publicKey;
468
}
469
470
/**
471
 * @throws InvalidConfigException
472
 */
473
function get_wechat_miniprogram_pay_sign(array $config, string $url, string $payload): string
474
{
475
    if (empty($config['mini_app_key_virtual_pay'])) {
476
        throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mini_app_key_virtual_pay]');
477
    }
478
479
    return hash_hmac('sha256', $url.'&'.$payload, $config['mini_app_key_virtual_pay']);
480
}
481
482
function get_wechat_miniprogram_user_sign(string $sessionKey, string $payload): string
483
{
484
    return hash_hmac('sha256', $payload, $sessionKey);
485
}
486
487
/**
488
 * @throws ContainerException
489
 * @throws ServiceNotFoundException
490
 */
491
#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
492
function get_unipay_config(array $params = []): array
493
{
494
    $unipay = Pay::get(ConfigInterface::class)->get('unipay');
495
496
    return $unipay[get_tenant($params)] ?? [];
497
}
498
499
/**
500
 * @throws InvalidConfigException
501
 * @throws InvalidSignException
502
 */
503
function verify_unipay_sign(array $config, string $contents, string $sign, ?string $signPublicKeyCert = null): void
504
{
505
    if (empty($sign)) {
506
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', func_get_args());
507
    }
508
509
    if (empty($signPublicKeyCert) && empty($public = $config['unipay_public_cert_path'] ?? null)) {
510
        throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [unipay_public_cert_path]');
511
    }
512
513
    $result = 1 === openssl_verify(
514
        hash('sha256', $contents),
515
        base64_decode($sign),
516
        get_public_cert($signPublicKeyCert ?? $public ?? ''),
517
        'sha256'
518
    );
519
520
    if (!$result) {
521
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', func_get_args());
522
    }
523
}
524
525
/**
526
 * @throws InvalidParamsException
527
 */
528
function get_unipay_url(array $config, ?Collection $payload): string
529
{
530
    $url = get_radar_url($config, $payload);
531
532
    if (empty($url)) {
533
        throw new InvalidParamsException(Exception::PARAMS_UNIPAY_URL_MISSING, '参数异常: 银联 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
534
    }
535
536
    if (str_starts_with($url, 'http')) {
537
        return $url;
538
    }
539
540
    return Unipay::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
541
}
542
543
/**
544
 * @throws InvalidParamsException
545
 */
546
function get_unipay_body(?Collection $payload): string
547
{
548
    $body = get_radar_body($payload);
549
550
    if (is_null($body)) {
551
        throw new InvalidParamsException(Exception::PARAMS_UNIPAY_BODY_MISSING, '参数异常: 银联 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`');
552
    }
553
554
    return $body;
555
}
556
557
/**
558
 * @throws InvalidConfigException
559
 */
560
function get_unipay_sign_qra(array $config, array $payload): string
561
{
562
    $key = $config['mch_secret_key'] ?? null;
563
564
    if (empty($key)) {
565
        throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]');
566
    }
567
568
    ksort($payload);
569
570
    $buff = '';
571
572
    foreach ($payload as $k => $v) {
573
        $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
574
    }
575
576
    return strtoupper(md5($buff.'key='.$key));
577
}
578
579
/**
580
 * @throws InvalidConfigException
581
 * @throws InvalidSignException
582
 */
583
function verify_unipay_sign_qra(array $config, array $destination): void
584
{
585
    $sign = $destination['sign'] ?? null;
586
587
    if (empty($sign)) {
588
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', $destination);
589
    }
590
591
    $key = $config['mch_secret_key'] ?? null;
592
593
    if (empty($key)) {
594
        throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]');
595
    }
596
597
    if (get_unipay_sign_qra($config, $destination) !== $sign) {
598
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', $destination);
599
    }
600
}
601
602
function get_jsb_url(array $config, ?Collection $payload): string
603
{
604
    $url = get_radar_url($config, $payload) ?? '';
605
606
    if (str_starts_with($url, 'http')) {
607
        return $url;
608
    }
609
610
    return Jsb::URL[$config['mode'] ?? Pay::MODE_NORMAL];
611
}
612
613
/**
614
 * @throws InvalidConfigException
615
 * @throws InvalidSignException
616
 */
617
function verify_jsb_sign(array $config, string $content, string $sign): void
618
{
619
    if (empty($sign)) {
620
        throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 江苏银行签名为空', func_get_args());
621
    }
622
623
    $publicCert = $config['jsb_public_cert_path'] ?? null;
624
625
    if (empty($publicCert)) {
626
        throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [jsb_public_cert_path]');
627
    }
628
629
    $result = 1 === openssl_verify(
630
        $content,
631
        base64_decode($sign),
632
        get_public_cert($publicCert)
633
    );
634
635
    if (!$result) {
636
        throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证江苏银行签名失败', func_get_args());
637
    }
638
}
639
640
/**
641
 * @throws InvalidParamsException
642
 */
643
function get_douyin_url(array $config, ?Collection $payload): string
644
{
645
    $url = get_radar_url($config, $payload);
646
647
    if (empty($url)) {
648
        throw new InvalidParamsException(Exception::PARAMS_DOUYIN_URL_MISSING, '参数异常: 抖音 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
649
    }
650
651
    if (str_starts_with($url, 'http')) {
652
        return $url;
653
    }
654
655
    return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
656
}
657