Functions.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. <?php
  2. declare(strict_types=1);
  3. namespace Yansongda\Pay;
  4. use JetBrains\PhpStorm\Deprecated;
  5. use Psr\Http\Message\ResponseInterface;
  6. use Psr\Http\Message\ServerRequestInterface;
  7. use Yansongda\Artful\Contract\ConfigInterface;
  8. use Yansongda\Artful\Exception\ContainerException;
  9. use Yansongda\Artful\Exception\InvalidConfigException;
  10. use Yansongda\Artful\Exception\InvalidParamsException;
  11. use Yansongda\Artful\Exception\ServiceNotFoundException;
  12. use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
  13. use Yansongda\Artful\Plugin\ParserPlugin;
  14. use Yansongda\Artful\Plugin\StartPlugin;
  15. use Yansongda\Pay\Exception\DecryptException;
  16. use Yansongda\Pay\Exception\Exception;
  17. use Yansongda\Pay\Exception\InvalidSignException;
  18. use Yansongda\Pay\Plugin\Wechat\AddRadarPlugin;
  19. use Yansongda\Pay\Plugin\Wechat\ResponsePlugin;
  20. use Yansongda\Pay\Plugin\Wechat\V3\AddPayloadSignaturePlugin;
  21. use Yansongda\Pay\Plugin\Wechat\V3\WechatPublicCertsPlugin;
  22. use Yansongda\Pay\Provider\Alipay;
  23. use Yansongda\Pay\Provider\Douyin;
  24. use Yansongda\Pay\Provider\Jsb;
  25. use Yansongda\Pay\Provider\Unipay;
  26. use Yansongda\Pay\Provider\Wechat;
  27. use Yansongda\Supports\Collection;
  28. use Yansongda\Supports\Str;
  29. use function Yansongda\Artful\get_radar_body;
  30. use function Yansongda\Artful\get_radar_method;
  31. function get_tenant(array $params = []): string
  32. {
  33. return strval($params['_config'] ?? 'default');
  34. }
  35. function get_public_cert(string $key): string
  36. {
  37. return is_file($key) ? file_get_contents($key) : $key;
  38. }
  39. function get_private_cert(string $key): string
  40. {
  41. if (is_file($key)) {
  42. return file_get_contents($key);
  43. }
  44. if (Str::startsWith($key, '-----BEGIN PRIVATE KEY-----')) {
  45. return $key;
  46. }
  47. return "-----BEGIN RSA PRIVATE KEY-----\n"
  48. .wordwrap($key, 64, "\n", true)
  49. ."\n-----END RSA PRIVATE KEY-----";
  50. }
  51. function get_radar_url(array $config, ?Collection $payload): ?string
  52. {
  53. return match ($config['mode'] ?? Pay::MODE_NORMAL) {
  54. Pay::MODE_SERVICE => $payload?->get('_service_url') ?? $payload?->get('_url') ?? null,
  55. Pay::MODE_SANDBOX => $payload?->get('_sandbox_url') ?? $payload?->get('_url') ?? null,
  56. default => $payload?->get('_url') ?? null,
  57. };
  58. }
  59. /**
  60. * @throws ContainerException
  61. * @throws ServiceNotFoundException
  62. */
  63. function get_provider_config(string $provider, array $params = []): array
  64. {
  65. /** @var ConfigInterface $config */
  66. $config = Pay::get(ConfigInterface::class);
  67. return $config->get($provider, [])[get_tenant($params)] ?? [];
  68. }
  69. /**
  70. * @throws ContainerException
  71. * @throws ServiceNotFoundException
  72. */
  73. #[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
  74. function get_alipay_config(array $params = []): array
  75. {
  76. $alipay = Pay::get(ConfigInterface::class)->get('alipay');
  77. return $alipay[get_tenant($params)] ?? [];
  78. }
  79. function get_alipay_url(array $config, ?Collection $payload): string
  80. {
  81. $url = get_radar_url($config, $payload) ?? '';
  82. if (str_starts_with($url, 'http')) {
  83. return $url;
  84. }
  85. return Alipay::URL[$config['mode'] ?? Pay::MODE_NORMAL];
  86. }
  87. /**
  88. * @throws InvalidConfigException
  89. * @throws InvalidSignException
  90. */
  91. function verify_alipay_sign(array $config, string $contents, string $sign): void
  92. {
  93. if (empty($sign)) {
  94. throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证支付宝签名失败-支付宝签名为空', func_get_args());
  95. }
  96. $public = $config['alipay_public_cert_path'] ?? null;
  97. if (empty($public)) {
  98. throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [alipay_public_cert_path]');
  99. }
  100. $result = 1 === openssl_verify(
  101. $contents,
  102. base64_decode($sign),
  103. get_public_cert($public),
  104. OPENSSL_ALGO_SHA256
  105. );
  106. if (!$result) {
  107. throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证支付宝签名失败', func_get_args());
  108. }
  109. }
  110. /**
  111. * @throws ContainerException
  112. * @throws ServiceNotFoundException
  113. */
  114. #[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
  115. function get_wechat_config(array $params = []): array
  116. {
  117. $wechat = Pay::get(ConfigInterface::class)->get('wechat');
  118. return $wechat[get_tenant($params)] ?? [];
  119. }
  120. function get_wechat_method(?Collection $payload): string
  121. {
  122. return get_radar_method($payload) ?? 'POST';
  123. }
  124. /**
  125. * @throws InvalidParamsException
  126. */
  127. function get_wechat_url(array $config, ?Collection $payload): string
  128. {
  129. $url = get_radar_url($config, $payload);
  130. if (empty($url)) {
  131. throw new InvalidParamsException(Exception::PARAMS_WECHAT_URL_MISSING, '参数异常: 微信 `_url` 或 `_service_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
  132. }
  133. if (str_starts_with($url, 'http')) {
  134. return $url;
  135. }
  136. return Wechat::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
  137. }
  138. /**
  139. * @throws InvalidParamsException
  140. */
  141. function get_wechat_body(?Collection $payload): mixed
  142. {
  143. $body = get_radar_body($payload);
  144. if (is_null($body)) {
  145. throw new InvalidParamsException(Exception::PARAMS_WECHAT_BODY_MISSING, '参数异常: 微信 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`');
  146. }
  147. return $body;
  148. }
  149. function get_wechat_type_key(array $params): string
  150. {
  151. $key = ($params['_type'] ?? 'mp').'_app_id';
  152. if ('app_app_id' === $key) {
  153. $key = 'app_id';
  154. }
  155. return $key;
  156. }
  157. /**
  158. * @throws InvalidConfigException
  159. */
  160. function get_wechat_sign(array $config, string $contents): string
  161. {
  162. $privateKey = $config['mch_secret_cert'] ?? null;
  163. if (empty($privateKey)) {
  164. throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_cert]');
  165. }
  166. $privateKey = get_private_cert($privateKey);
  167. openssl_sign($contents, $sign, $privateKey, 'sha256WithRSAEncryption');
  168. return base64_encode($sign);
  169. }
  170. /**
  171. * @throws InvalidConfigException
  172. */
  173. function get_wechat_sign_v2(array $config, array $payload, bool $upper = true): string
  174. {
  175. $key = $config['mch_secret_key_v2'] ?? null;
  176. if (empty($key)) {
  177. throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]');
  178. }
  179. ksort($payload);
  180. $buff = '';
  181. foreach ($payload as $k => $v) {
  182. $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
  183. }
  184. $sign = md5($buff.'key='.$key);
  185. return $upper ? strtoupper($sign) : $sign;
  186. }
  187. /**
  188. * @throws ContainerException
  189. * @throws DecryptException
  190. * @throws InvalidConfigException
  191. * @throws InvalidParamsException
  192. * @throws InvalidSignException
  193. * @throws ServiceNotFoundException
  194. */
  195. function verify_wechat_sign(ResponseInterface|ServerRequestInterface $message, array $params): void
  196. {
  197. if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {
  198. return;
  199. }
  200. $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
  201. $timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
  202. $random = $message->getHeaderLine('Wechatpay-Nonce');
  203. $sign = $message->getHeaderLine('Wechatpay-Signature');
  204. $body = (string) $message->getBody();
  205. $content = $timestamp."\n".$random."\n".$body."\n";
  206. $public = get_provider_config('wechat', $params)['wechat_public_cert_path'][$wechatSerial] ?? null;
  207. if (empty($sign)) {
  208. throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', ['headers' => $message->getHeaders(), 'body' => $body]);
  209. }
  210. $public = get_public_cert(
  211. empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public
  212. );
  213. $result = 1 === openssl_verify(
  214. $content,
  215. base64_decode($sign),
  216. $public,
  217. 'sha256WithRSAEncryption'
  218. );
  219. if (!$result) {
  220. throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', ['headers' => $message->getHeaders(), 'body' => $body]);
  221. }
  222. }
  223. /**
  224. * @throws InvalidConfigException
  225. * @throws InvalidSignException
  226. */
  227. function verify_wechat_sign_v2(array $config, array $destination): void
  228. {
  229. $sign = $destination['sign'] ?? null;
  230. if (empty($sign)) {
  231. throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', $destination);
  232. }
  233. $key = $config['mch_secret_key_v2'] ?? null;
  234. if (empty($key)) {
  235. throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]');
  236. }
  237. if (get_wechat_sign_v2($config, $destination) !== $sign) {
  238. throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', $destination);
  239. }
  240. }
  241. function encrypt_wechat_contents(string $contents, string $publicKey): ?string
  242. {
  243. if (openssl_public_encrypt($contents, $encrypted, get_public_cert($publicKey), OPENSSL_PKCS1_OAEP_PADDING)) {
  244. return base64_encode($encrypted);
  245. }
  246. return null;
  247. }
  248. function decrypt_wechat_contents(string $encrypted, array $config): ?string
  249. {
  250. if (openssl_private_decrypt(base64_decode($encrypted), $decrypted, get_private_cert($config['mch_secret_cert'] ?? ''), OPENSSL_PKCS1_OAEP_PADDING)) {
  251. return $decrypted;
  252. }
  253. return null;
  254. }
  255. /**
  256. * @throws ContainerException
  257. * @throws DecryptException
  258. * @throws InvalidConfigException
  259. * @throws InvalidParamsException
  260. * @throws ServiceNotFoundException
  261. */
  262. function reload_wechat_public_certs(array $params, ?string $serialNo = null): string
  263. {
  264. $data = Pay::wechat()->pay(
  265. [StartPlugin::class, WechatPublicCertsPlugin::class, AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class],
  266. $params
  267. )->get('data', []);
  268. $wechatConfig = get_provider_config('wechat', $params);
  269. foreach ($data as $item) {
  270. $certs[$item['serial_no']] = decrypt_wechat_resource($item['encrypt_certificate'], $wechatConfig)['ciphertext'] ?? '';
  271. }
  272. Pay::get(ConfigInterface::class)->set(
  273. 'wechat.'.get_tenant($params).'.wechat_public_cert_path',
  274. ((array) ($wechatConfig['wechat_public_cert_path'] ?? [])) + ($certs ?? []),
  275. );
  276. if (!is_null($serialNo) && empty($certs[$serialNo])) {
  277. throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 获取微信 wechat_public_cert_path 配置失败');
  278. }
  279. return $certs[$serialNo] ?? '';
  280. }
  281. /**
  282. * @throws ContainerException
  283. * @throws DecryptException
  284. * @throws InvalidConfigException
  285. * @throws InvalidParamsException
  286. * @throws ServiceNotFoundException
  287. */
  288. function get_wechat_public_certs(array $params = [], ?string $path = null): void
  289. {
  290. reload_wechat_public_certs($params);
  291. $config = get_provider_config('wechat', $params);
  292. if (empty($path)) {
  293. var_dump($config['wechat_public_cert_path']);
  294. return;
  295. }
  296. foreach ($config['wechat_public_cert_path'] as $serialNo => $cert) {
  297. file_put_contents($path.'/'.$serialNo.'.crt', $cert);
  298. }
  299. }
  300. /**
  301. * @throws InvalidConfigException
  302. * @throws DecryptException
  303. */
  304. function decrypt_wechat_resource(array $resource, array $config): array
  305. {
  306. $ciphertext = base64_decode($resource['ciphertext'] ?? '');
  307. $secret = $config['mch_secret_key'] ?? null;
  308. if (strlen($ciphertext) <= Wechat::AUTH_TAG_LENGTH_BYTE) {
  309. throw new DecryptException(Exception::DECRYPT_WECHAT_CIPHERTEXT_PARAMS_INVALID, '加解密异常: ciphertext 位数过短');
  310. }
  311. if (is_null($secret) || Wechat::MCH_SECRET_KEY_LENGTH_BYTE != strlen($secret)) {
  312. throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key]');
  313. }
  314. $resource['ciphertext'] = match ($resource['algorithm'] ?? '') {
  315. 'AEAD_AES_256_GCM' => decrypt_wechat_resource_aes_256_gcm($ciphertext, $secret, $resource['nonce'] ?? '', $resource['associated_data'] ?? ''),
  316. default => throw new DecryptException(Exception::DECRYPT_WECHAT_DECRYPTED_METHOD_INVALID, '加解密异常: algorithm 不支持'),
  317. };
  318. return $resource;
  319. }
  320. /**
  321. * @throws DecryptException
  322. */
  323. function decrypt_wechat_resource_aes_256_gcm(string $ciphertext, string $secret, string $nonce, string $associatedData): array|string
  324. {
  325. $decrypted = openssl_decrypt(
  326. substr($ciphertext, 0, -Wechat::AUTH_TAG_LENGTH_BYTE),
  327. 'aes-256-gcm',
  328. $secret,
  329. OPENSSL_RAW_DATA,
  330. $nonce,
  331. substr($ciphertext, -Wechat::AUTH_TAG_LENGTH_BYTE),
  332. $associatedData
  333. );
  334. if (false === $decrypted) {
  335. throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 解密失败,请检查微信 mch_secret_key 是否正确');
  336. }
  337. if ('certificate' !== $associatedData) {
  338. $decrypted = json_decode($decrypted, true);
  339. if (JSON_ERROR_NONE !== json_last_error()) {
  340. throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 待解密数据非正常数据');
  341. }
  342. }
  343. return $decrypted;
  344. }
  345. /**
  346. * @throws ContainerException
  347. * @throws DecryptException
  348. * @throws InvalidConfigException
  349. * @throws InvalidParamsException
  350. * @throws ServiceNotFoundException
  351. */
  352. function get_wechat_serial_no(array $params): string
  353. {
  354. if (!empty($params['_serial_no'])) {
  355. return $params['_serial_no'];
  356. }
  357. $config = get_provider_config('wechat', $params);
  358. if (empty($config['wechat_public_cert_path'])) {
  359. reload_wechat_public_certs($params);
  360. $config = get_provider_config('wechat', $params);
  361. }
  362. mt_srand();
  363. return strval(array_rand($config['wechat_public_cert_path']));
  364. }
  365. /**
  366. * @throws InvalidParamsException
  367. */
  368. function get_wechat_public_key(array $config, string $serialNo): string
  369. {
  370. $publicKey = $config['wechat_public_cert_path'][$serialNo] ?? null;
  371. if (empty($publicKey)) {
  372. throw new InvalidParamsException(Exception::PARAMS_WECHAT_SERIAL_NOT_FOUND, '参数异常: 微信公钥序列号未找到 - '.$serialNo);
  373. }
  374. return $publicKey;
  375. }
  376. /**
  377. * @throws InvalidConfigException
  378. */
  379. function get_wechat_miniprogram_pay_sign(array $config, string $url, string $payload): string
  380. {
  381. if (empty($config['mini_app_key_virtual_pay'])) {
  382. throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mini_app_key_virtual_pay]');
  383. }
  384. return hash_hmac('sha256', $url.'&'.$payload, $config['mini_app_key_virtual_pay']);
  385. }
  386. function get_wechat_miniprogram_user_sign(string $sessionKey, string $payload): string
  387. {
  388. return hash_hmac('sha256', $payload, $sessionKey);
  389. }
  390. /**
  391. * @throws ContainerException
  392. * @throws ServiceNotFoundException
  393. */
  394. #[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
  395. function get_unipay_config(array $params = []): array
  396. {
  397. $unipay = Pay::get(ConfigInterface::class)->get('unipay');
  398. return $unipay[get_tenant($params)] ?? [];
  399. }
  400. /**
  401. * @throws InvalidConfigException
  402. * @throws InvalidSignException
  403. */
  404. function verify_unipay_sign(array $config, string $contents, string $sign, ?string $signPublicKeyCert = null): void
  405. {
  406. if (empty($sign)) {
  407. throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', func_get_args());
  408. }
  409. if (empty($signPublicKeyCert) && empty($public = $config['unipay_public_cert_path'] ?? null)) {
  410. throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [unipay_public_cert_path]');
  411. }
  412. $result = 1 === openssl_verify(
  413. hash('sha256', $contents),
  414. base64_decode($sign),
  415. get_public_cert($signPublicKeyCert ?? $public ?? ''),
  416. 'sha256'
  417. );
  418. if (!$result) {
  419. throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', func_get_args());
  420. }
  421. }
  422. /**
  423. * @throws InvalidParamsException
  424. */
  425. function get_unipay_url(array $config, ?Collection $payload): string
  426. {
  427. $url = get_radar_url($config, $payload);
  428. if (empty($url)) {
  429. throw new InvalidParamsException(Exception::PARAMS_UNIPAY_URL_MISSING, '参数异常: 银联 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
  430. }
  431. if (str_starts_with($url, 'http')) {
  432. return $url;
  433. }
  434. return Unipay::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
  435. }
  436. /**
  437. * @throws InvalidParamsException
  438. */
  439. function get_unipay_body(?Collection $payload): string
  440. {
  441. $body = get_radar_body($payload);
  442. if (is_null($body)) {
  443. throw new InvalidParamsException(Exception::PARAMS_UNIPAY_BODY_MISSING, '参数异常: 银联 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`');
  444. }
  445. return $body;
  446. }
  447. /**
  448. * @throws InvalidConfigException
  449. */
  450. function get_unipay_sign_qra(array $config, array $payload): string
  451. {
  452. $key = $config['mch_secret_key'] ?? null;
  453. if (empty($key)) {
  454. throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]');
  455. }
  456. ksort($payload);
  457. $buff = '';
  458. foreach ($payload as $k => $v) {
  459. $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
  460. }
  461. return strtoupper(md5($buff.'key='.$key));
  462. }
  463. /**
  464. * @throws InvalidConfigException
  465. * @throws InvalidSignException
  466. */
  467. function verify_unipay_sign_qra(array $config, array $destination): void
  468. {
  469. $sign = $destination['sign'] ?? null;
  470. if (empty($sign)) {
  471. throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', $destination);
  472. }
  473. $key = $config['mch_secret_key'] ?? null;
  474. if (empty($key)) {
  475. throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]');
  476. }
  477. if (get_unipay_sign_qra($config, $destination) !== $sign) {
  478. throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', $destination);
  479. }
  480. }
  481. function get_jsb_url(array $config, ?Collection $payload): string
  482. {
  483. $url = get_radar_url($config, $payload) ?? '';
  484. if (str_starts_with($url, 'http')) {
  485. return $url;
  486. }
  487. return Jsb::URL[$config['mode'] ?? Pay::MODE_NORMAL];
  488. }
  489. /**
  490. * @throws InvalidConfigException
  491. * @throws InvalidSignException
  492. */
  493. function verify_jsb_sign(array $config, string $content, string $sign): void
  494. {
  495. if (empty($sign)) {
  496. throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 江苏银行签名为空', func_get_args());
  497. }
  498. $publicCert = $config['jsb_public_cert_path'] ?? null;
  499. if (empty($publicCert)) {
  500. throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [jsb_public_cert_path]');
  501. }
  502. $result = 1 === openssl_verify(
  503. $content,
  504. base64_decode($sign),
  505. get_public_cert($publicCert)
  506. );
  507. if (!$result) {
  508. throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证江苏银行签名失败', func_get_args());
  509. }
  510. }
  511. /**
  512. * @throws InvalidParamsException
  513. */
  514. function get_douyin_url(array $config, ?Collection $payload): string
  515. {
  516. $url = get_radar_url($config, $payload);
  517. if (empty($url)) {
  518. throw new InvalidParamsException(Exception::PARAMS_DOUYIN_URL_MISSING, '参数异常: 抖音 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
  519. }
  520. if (str_starts_with($url, 'http')) {
  521. return $url;
  522. }
  523. return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
  524. }