From cacd4be0b9c445a6131e80b5e61021546396e065 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sat, 8 Jun 2024 21:59:18 +0800 Subject: [PATCH 01/19] update --- src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php | 81 ++++++++++ src/Provider/Douyin.php | 166 ++++++++++++++++++++ src/Service/DouyinServiceProvider.php | 24 +++ src/Shortcut/Douyin/MiniShortcut.php | 34 ++++ web/docs/v3/douyin/pay.md | 42 +++++ web/docs/v3/quick-start/init.md | 10 ++ 6 files changed, 357 insertions(+) create mode 100644 src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php create mode 100644 src/Provider/Douyin.php create mode 100644 src/Service/DouyinServiceProvider.php create mode 100644 src/Shortcut/Douyin/MiniShortcut.php create mode 100644 web/docs/v3/douyin/pay.md diff --git a/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php new file mode 100644 index 000000000..3a7ada97f --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php @@ -0,0 +1,81 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_douyin_config($params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 下单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_order', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Douyin][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'appid' => $config['mini_app_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + $data = [ + 'sp_appid' => $config['mini_app_id'] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + + if ($payload->has('payer.sub_openid')) { + $data['sub_appid'] = $config['sub_mini_app_id'] ?? ''; + } + + return $data; + } +} diff --git a/src/Provider/Douyin.php b/src/Provider/Douyin.php new file mode 100644 index 000000000..5cc1a7c21 --- /dev/null +++ b/src/Provider/Douyin.php @@ -0,0 +1,166 @@ + 'https://developer.toutiao.com/', + Pay::MODE_SANDBOX => 'https://open-sandbox.douyin.com/', + Pay::MODE_SERVICE => 'https://developer.toutiao.com/', + ]; + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function __call(string $shortcut, array $params): null|Collection|MessageInterface + { + $plugin = '\\Yansongda\\Pay\\Shortcut\\Douyin\\'.Str::studly($shortcut).'Shortcut'; + + return Artful::shortcut($plugin, ...$params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function pay(array $plugins, array $params): null|Collection|MessageInterface|Rocket + { + return Artful::artful($plugins, $params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function query(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null)); + + return $this->__call('query', [$order]); + } + + /** + * @throws InvalidParamsException + */ + public function cancel(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 微信不支持 cancel API'); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function close(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null)); + + $this->__call('close', [$order]); + + return new Collection(); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null)); + + return $this->__call('refund', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection|Rocket + { + $request = $this->getCallbackParams($contents); + + Event::dispatch(new CallbackReceived('douyin', clone $request, $params, null)); + + return $this->pay( + [CallbackPlugin::class], + ['_request' => $request, '_params' => $params] + ); + } + + public function success(): ResponseInterface + { + return new Response( + 200, + ['Content-Type' => 'application/json'], + json_encode(['code' => 'SUCCESS', 'message' => '成功']), + ); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class], + ); + } + + protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): ServerRequestInterface + { + if (is_array($contents) && isset($contents['body'], $contents['headers'])) { + return new ServerRequest('POST', 'http://localhost', $contents['headers'], $contents['body']); + } + + if (is_array($contents)) { + return new ServerRequest('POST', 'http://localhost', [], json_encode($contents)); + } + + if ($contents instanceof ServerRequestInterface) { + return $contents; + } + + return ServerRequest::fromGlobals(); + } +} diff --git a/src/Service/DouyinServiceProvider.php b/src/Service/DouyinServiceProvider.php new file mode 100644 index 000000000..404c48af4 --- /dev/null +++ b/src/Service/DouyinServiceProvider.php @@ -0,0 +1,24 @@ + time().'', + 'description' => 'subject-测试', + 'amount' => [ + 'total' => 1, + 'currency' => 'CNY', + ], + 'payer' => [ + 'openid' => '123fsdf234', + ] +]; + +$result = Pay::wechat()->mini($order); +// 返回 Collection 实例。包含了调用 JSAPI 的所有参数,如appId,timeStamp,nonceStr,package,signType,paySign 等; +// 可直接通过 $result->appId, $result->timeStamp 获取相关值。 +// 后续调用不在本文档讨论范围内,请自行参考官方文档。 +``` + +### 订单配置参数 + +**所有订单配置中,客观参数均不用配置,扩展包已经为大家自动处理了**,比如,`trade_type`,`appid`,`sign` 等参数,大家只需传入订单类主观参数即可。 + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考[这里](https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/mini-prepay.html),查看「请求参数」一栏。 + +### 调用支付 + +后续调起支付不再本文档讨论范围内,请参考[官方文档](https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/mini-transfer-payment.html) diff --git a/web/docs/v3/quick-start/init.md b/web/docs/v3/quick-start/init.md index 6381290a0..54804eb4e 100644 --- a/web/docs/v3/quick-start/init.md +++ b/web/docs/v3/quick-start/init.md @@ -96,6 +96,16 @@ $config = [ 'mode' => Pay::MODE_NORMAL, ], ], + 'douyin' => [ + 'default' => [ + // 必填-抖音开放平台应用的 小程序 app_id + 'mini_app_id' => 'tt226e54d3bd581bf801', + // 必填-抖音开放平台应用的 小程序 app_secret + 'mini_app_secret' => 'da880aa23bb2864d381484577e2ce474fb2818f4', + // 选填-抖音开放平台服务商id + 'thirdparty_id' => '' + ], + ], 'logger' => [ 'enable' => false, 'file' => './logs/pay.log', From 6de830cef1bdda49e85aa3a0591aea75d170f7a6 Mon Sep 17 00:00:00 2001 From: yansongda Date: Mon, 10 Jun 2024 12:23:41 +0800 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=8A=96?= =?UTF-8?q?=E9=9F=B3=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 4 +- src/Exception/Exception.php | 6 ++ src/Functions.php | 19 ++++ src/Pay.php | 2 + src/Plugin/Alipay/V2/AddRadarPlugin.php | 4 +- src/Plugin/Douyin/AddRadarPlugin.php | 55 +++++++++++ src/Plugin/Douyin/ResponsePlugin.php | 54 +++++++++++ .../Douyin/V1/AddPayloadSignaturePlugin.php | 94 +++++++++++++++++++ src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php | 33 +++---- src/Provider/Douyin.php | 13 +-- src/Shortcut/Douyin/MiniShortcut.php | 14 +-- web/docs/v3/quick-start/init.md | 15 ++- web/docs/v3/wechat/pay.md | 5 + 13 files changed, 270 insertions(+), 48 deletions(-) create mode 100644 src/Plugin/Douyin/AddRadarPlugin.php create mode 100644 src/Plugin/Douyin/ResponsePlugin.php create mode 100644 src/Plugin/Douyin/V1/AddPayloadSignaturePlugin.php diff --git a/composer.json b/composer.json index 50d0bae25..1aeb66836 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,8 @@ "ext-libxml": "*", "ext-json": "*", "ext-bcmath": "*", - "yansongda/artful": "~1.1.0", - "yansongda/supports": "~4.0.9" + "yansongda/artful": "~1.1.1", + "yansongda/supports": "~4.0.10" }, "require-dev": { "phpunit/phpunit": "^9.0", diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php index 1085168ff..b2a07a3f0 100644 --- a/src/Exception/Exception.php +++ b/src/Exception/Exception.php @@ -37,6 +37,8 @@ class Exception extends \Exception public const PARAMS_CALLBACK_REQUEST_INVALID = 9221; + public const PARAMS_DOUYIN_URL_MISSING = 9222; + /** * 关于响应. */ @@ -44,6 +46,8 @@ class Exception extends \Exception public const RESPONSE_MISSING_NECESSARY_PARAMS = 9305; + public const RESPONSE_BUSINESS_CODE_WRONG = 9306; + /* * 关于配置. */ @@ -53,6 +57,8 @@ class Exception extends \Exception public const CONFIG_UNIPAY_INVALID = 9403; + public const CONFIG_DOUYIN_INVALID = 9404; + /** * 关于签名. */ diff --git a/src/Functions.php b/src/Functions.php index 5f7c30292..5d4f0dfa6 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -23,6 +23,7 @@ use Yansongda\Pay\Plugin\Wechat\V3\AddPayloadSignaturePlugin; use Yansongda\Pay\Plugin\Wechat\V3\WechatPublicCertsPlugin; use Yansongda\Pay\Provider\Alipay; +use Yansongda\Pay\Provider\Douyin; use Yansongda\Pay\Provider\Unipay; use Yansongda\Pay\Provider\Wechat; use Yansongda\Supports\Collection; @@ -591,3 +592,21 @@ function verify_unipay_sign_qra(array $config, array $destination): void throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', $destination); } } + +/** + * @throws InvalidParamsException + */ +function get_douyin_url(array $config, ?Collection $payload): string +{ + $url = get_radar_url($config, $payload); + + if (empty($url)) { + throw new InvalidParamsException(Exception::PARAMS_DOUYIN_URL_MISSING, '参数异常: 抖音 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`'); + } + + if (str_starts_with($url, 'http')) { + return $url; + } + + return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url; +} diff --git a/src/Pay.php b/src/Pay.php index a6d1dd8e9..4f7c3f293 100644 --- a/src/Pay.php +++ b/src/Pay.php @@ -13,6 +13,7 @@ use Yansongda\Pay\Provider\Unipay; use Yansongda\Pay\Provider\Wechat; use Yansongda\Pay\Service\AlipayServiceProvider; +use Yansongda\Pay\Service\DouyinServiceProvider; use Yansongda\Pay\Service\UnipayServiceProvider; use Yansongda\Pay\Service\WechatServiceProvider; @@ -42,6 +43,7 @@ class Pay AlipayServiceProvider::class, WechatServiceProvider::class, UnipayServiceProvider::class, + DouyinServiceProvider::class, ]; /** diff --git a/src/Plugin/Alipay/V2/AddRadarPlugin.php b/src/Plugin/Alipay/V2/AddRadarPlugin.php index 732dd495a..68045d675 100644 --- a/src/Plugin/Alipay/V2/AddRadarPlugin.php +++ b/src/Plugin/Alipay/V2/AddRadarPlugin.php @@ -11,7 +11,9 @@ use Yansongda\Artful\Exception\ServiceNotFoundException; use Yansongda\Artful\Logger; use Yansongda\Artful\Rocket; +use Yansongda\Supports\Collection; +use function Yansongda\Artful\get_radar_method; use function Yansongda\Pay\get_alipay_url; use function Yansongda\Pay\get_provider_config; @@ -30,7 +32,7 @@ public function assembly(Rocket $rocket, Closure $next): Rocket $payload = $rocket->getPayload(); $rocket->setRadar(new Request( - strtoupper($params['_method'] ?? 'POST'), + get_radar_method(new Collection($params) ?? 'POST'), get_alipay_url($config, $payload), $this->getHeaders(), // 不能用 packer,支付宝接收的是 x-www-form-urlencoded 返回的又是 json,packer 用的是返回. diff --git a/src/Plugin/Douyin/AddRadarPlugin.php b/src/Plugin/Douyin/AddRadarPlugin.php new file mode 100644 index 000000000..d377a6f30 --- /dev/null +++ b/src/Plugin/Douyin/AddRadarPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('douyin', $params); + + $rocket->setRadar(new Request( + get_radar_method($payload), + get_douyin_url($config, $payload), + $this->getHeaders(), + get_radar_body($payload), + )); + + Logger::info('[Douyin][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getHeaders(): array + { + return [ + 'User-Agent' => 'yansongda/pay-v3', + 'Content-Type' => 'application/json; charset=utf-8', + ]; + } +} diff --git a/src/Plugin/Douyin/ResponsePlugin.php b/src/Plugin/Douyin/ResponsePlugin.php new file mode 100644 index 000000000..ba916c449 --- /dev/null +++ b/src/Plugin/Douyin/ResponsePlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $rocket->setDestination(new Collection($this->validateResponse($rocket))); + + Logger::info('[Douyin][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidResponseException + */ + protected function validateResponse(Rocket $rocket): array + { + $destination = $rocket->getDestination(); + $response = $rocket->getDestinationOrigin(); + + if ($response instanceof ResponseInterface + && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300)) { + throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '抖音返回状态码异常,请检查参数是否错误', $destination); + } + + if (0 !== $destination->get('err_no')) { + throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, '抖音返回业务异常: '.$destination->get('err_tips'), $destination); + } + + return $destination->get('data', []); + } +} diff --git a/src/Plugin/Douyin/V1/AddPayloadSignaturePlugin.php b/src/Plugin/Douyin/V1/AddPayloadSignaturePlugin.php new file mode 100644 index 000000000..cb429eb22 --- /dev/null +++ b/src/Plugin/Douyin/V1/AddPayloadSignaturePlugin.php @@ -0,0 +1,94 @@ + $rocket]); + + $config = get_provider_config('douyin', $rocket->getParams()); + $payload = $rocket->getPayload(); + + $rocket->mergePayload(['sign' => $this->getSign($config, filter_params($payload))]); + + Logger::info('[Douyin][V1][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(array $config, Collection $payload): string + { + $salt = $config['mini_salt'] ?? null; + + if (empty($salt)) { + throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mini_salt]'); + } + + foreach ($payload as $key => $value) { + if (is_string($value)) { + $value = trim($value); + } + + if (in_array($key, ['other_settle_params', 'app_id', 'sign', 'thirdparty_id']) || empty($value) || 'null' === $value) { + continue; + } + + if (is_array($value)) { + $value = $this->arrayToString($value); + } + + $signData[] = $value; + } + + $signData[] = $salt; + + sort($signData, SORT_STRING); + + return md5(implode('&', $signData)); + } + + protected function arrayToString(array $value): string + { + $isJsonArray = isset($value[0]); + $keys = array_keys($value); + + if ($isJsonArray) { + sort($keys); + } + + foreach ($keys as $key) { + $val = $value[$key]; + + $result[] = is_array($val) ? $this->arrayToString($val) : (($isJsonArray ? '' : $key.':').trim(strval($val))); + } + + $result = '['.implode(' ', $result ?? []).']'; + + return ($isJsonArray ? '' : 'map').$result; + } +} diff --git a/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php index 3a7ada97f..2678b25bb 100644 --- a/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php +++ b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php @@ -15,7 +15,7 @@ use Yansongda\Pay\Pay; use Yansongda\Supports\Collection; -use function Yansongda\Pay\get_douyin_config; +use function Yansongda\Pay\get_provider_config; /** * @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/pay @@ -29,14 +29,14 @@ class PayPlugin implements PluginInterface */ public function assembly(Rocket $rocket, Closure $next): Rocket { - Logger::debug('[Douyin][Pay][Mini][PayPlugin] 插件开始装载', ['rocket' => $rocket]); + Logger::debug('[Douyin][V1][Pay][Mini][PayPlugin] 插件开始装载', ['rocket' => $rocket]); $payload = $rocket->getPayload(); $params = $rocket->getParams(); - $config = get_douyin_config($params); + $config = get_provider_config('douyin', $params); if (is_null($payload)) { - throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 下单,参数为空'); + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序下单,参数为空'); } if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { @@ -47,35 +47,26 @@ public function assembly(Rocket $rocket, Closure $next): Rocket [ '_method' => 'POST', '_url' => 'api/apps/ecpay/v1/create_order', - 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + 'app_id' => $config['mini_app_id'] ?? '', + 'notify_url' => $payload->get('notify_url') ?? $this->getNotifyUrl($config), ], - $data ?? $this->normal($config) + $data ?? [], )); - Logger::info('[Douyin][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + Logger::info('[Douyin][V1][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); return $next($rocket); } - protected function normal(array $config): array + protected function service(Collection $payload, array $config): array { return [ - 'appid' => $config['mini_app_id'] ?? '', + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), ]; } - protected function service(Collection $payload, array $config): array + protected function getNotifyUrl(array $config): ?string { - $data = [ - 'sp_appid' => $config['mini_app_id'] ?? '', - 'sp_mchid' => $config['mch_id'] ?? '', - 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), - ]; - - if ($payload->has('payer.sub_openid')) { - $data['sub_appid'] = $config['sub_mini_app_id'] ?? ''; - } - - return $data; + return empty($config['mini_notify_url']) ? null : $config['mini_notify_url']; } } diff --git a/src/Provider/Douyin.php b/src/Provider/Douyin.php index 5cc1a7c21..ac7cd1696 100644 --- a/src/Provider/Douyin.php +++ b/src/Provider/Douyin.php @@ -16,30 +16,21 @@ use Yansongda\Artful\Exception\ServiceNotFoundException; use Yansongda\Artful\Plugin\AddPayloadBodyPlugin; use Yansongda\Artful\Plugin\ParserPlugin; +use Yansongda\Artful\Plugin\StartPlugin; use Yansongda\Artful\Rocket; use Yansongda\Pay\Contract\ProviderInterface; use Yansongda\Pay\Event\CallbackReceived; use Yansongda\Pay\Event\MethodCalled; use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Pay; -use Yansongda\Pay\Plugin\Wechat\AddRadarPlugin; -use Yansongda\Pay\Plugin\Wechat\ResponsePlugin; -use Yansongda\Pay\Plugin\Wechat\StartPlugin; -use Yansongda\Pay\Plugin\Wechat\V3\AddPayloadSignaturePlugin; -use Yansongda\Pay\Plugin\Wechat\V3\CallbackPlugin; -use Yansongda\Pay\Plugin\Wechat\V3\VerifySignaturePlugin; use Yansongda\Supports\Collection; use Yansongda\Supports\Str; /** - * @method Collection|Rocket mini(array $order) 小程序支付 + * @method Collection|Rocket mini(array $order) 小程序支付 */ class Douyin implements ProviderInterface { - public const AUTH_TAG_LENGTH_BYTE = 16; - - public const MCH_SECRET_KEY_LENGTH_BYTE = 32; - public const URL = [ Pay::MODE_NORMAL => 'https://developer.toutiao.com/', Pay::MODE_SANDBOX => 'https://open-sandbox.douyin.com/', diff --git a/src/Shortcut/Douyin/MiniShortcut.php b/src/Shortcut/Douyin/MiniShortcut.php index e42239426..3bdab97f1 100644 --- a/src/Shortcut/Douyin/MiniShortcut.php +++ b/src/Shortcut/Douyin/MiniShortcut.php @@ -8,12 +8,10 @@ use Yansongda\Artful\Plugin\AddPayloadBodyPlugin; use Yansongda\Artful\Plugin\ParserPlugin; use Yansongda\Artful\Plugin\StartPlugin; -use Yansongda\Pay\Plugin\Wechat\AddRadarPlugin; -use Yansongda\Pay\Plugin\Wechat\ResponsePlugin; -use Yansongda\Pay\Plugin\Wechat\V3\AddPayloadSignaturePlugin; -use Yansongda\Pay\Plugin\Wechat\V3\Pay\Mini\InvokePlugin; -use Yansongda\Pay\Plugin\Wechat\V3\Pay\Mini\PayPlugin; -use Yansongda\Pay\Plugin\Wechat\V3\VerifySignaturePlugin; +use Yansongda\Pay\Plugin\Douyin\AddRadarPlugin; +use Yansongda\Pay\Plugin\Douyin\ResponsePlugin; +use Yansongda\Pay\Plugin\Douyin\V1\AddPayloadSignaturePlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin; class MiniShortcut implements ShortcutInterface { @@ -22,11 +20,9 @@ public function getPlugins(array $params): array return [ StartPlugin::class, PayPlugin::class, - AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, AddRadarPlugin::class, - InvokePlugin::class, - VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class, ]; diff --git a/web/docs/v3/quick-start/init.md b/web/docs/v3/quick-start/init.md index 54804eb4e..ce4aaba63 100644 --- a/web/docs/v3/quick-start/init.md +++ b/web/docs/v3/quick-start/init.md @@ -98,12 +98,19 @@ $config = [ ], 'douyin' => [ 'default' => [ - // 必填-抖音开放平台应用的 小程序 app_id + // 必填-小程序 app_id + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> 小程序appid 'mini_app_id' => 'tt226e54d3bd581bf801', - // 必填-抖音开放平台应用的 小程序 app_secret - 'mini_app_secret' => 'da880aa23bb2864d381484577e2ce474fb2818f4', + // 必填-支付 Token,用于支付回调签名 + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mini_token' => 'douyin_mini_token', + // 必填-支付 SALT,用于支付签名 + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> SALT + 'mini_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', // 选填-抖音开放平台服务商id - 'thirdparty_id' => '' + 'mini_thirdparty_id' => '', + // 选填-抖音支付回调地址 + 'mini_notify_url' => 'https://yansongda.cn/douyin/notify', ], ], 'logger' => [ diff --git a/web/docs/v3/wechat/pay.md b/web/docs/v3/wechat/pay.md index 45c7bbec3..205cd1695 100644 --- a/web/docs/v3/wechat/pay.md +++ b/web/docs/v3/wechat/pay.md @@ -12,6 +12,11 @@ | scan | 扫码支付 | array $order | Collection | | transfer | 转账 | array $order | Collection | + +默认情况下,除 APP支付、小程序支付 外所使用的 appid 均是微信公众号的 appid,即配置文件中的 `mp_app_id` 参数 + +如果想使用其他类型的 appid,则只需要在调用参数中增加 `_type` 参数即可,例如,如果想使用小程序的 appid,则:`['_type' => 'mini']` + ## 公众号支付 ### 例子 From b4b54e99a5ba8be5a96899ae5a07f16af4338f57 Mon Sep 17 00:00:00 2001 From: yansongda Date: Mon, 10 Jun 2024 12:24:21 +0800 Subject: [PATCH 03/19] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=8A=96?= =?UTF-8?q?=E9=9F=B3=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f287eb8f..f5d2c29b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## TBD - v3.7.5 +### added + +- feat: 支持抖音支付(#996) + ### deprecated - deprecate: 微信 `StartPlugin` 改为使用 `yansongda/artful` 中的插件(#993) From 38e08c115226c94f1094b942c632f86070332cc0 Mon Sep 17 00:00:00 2001 From: yansongda Date: Mon, 10 Jun 2024 21:12:21 +0800 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=8A=96?= =?UTF-8?q?=E9=9F=B3=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Pay.php | 2 + src/Plugin/Alipay/V2/AddRadarPlugin.php | 3 +- .../{ => Pay}/AddPayloadSignaturePlugin.php | 6 +- src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php | 2 +- src/Provider/Douyin.php | 7 +- src/Provider/Wechat.php | 2 +- src/Shortcut/Douyin/MiniShortcut.php | 2 +- tests/Plugin/Douyin/AddRadarPluginTest.php | 41 +++++++++ tests/Plugin/Douyin/ResponsePluginTest.php | 62 +++++++++++++ .../V1/Pay/AddPayloadSignaturePluginTest.php | 58 ++++++++++++ .../Douyin/V1/Pay/Mini/PayPluginTest.php | 90 +++++++++++++++++++ tests/Provider/DouyinTest.php | 82 +++++++++++++++++ tests/Shortcut/Douyin/MiniShortcutTest.php | 40 +++++++++ tests/TestCase.php | 36 +++++++- web/docs/v3/douyin/pay.md | 25 +++--- web/docs/v3/quick-start/douyin.md | 21 +++++ web/docs/v3/wechat/pay.md | 3 +- 17 files changed, 456 insertions(+), 26 deletions(-) rename src/Plugin/Douyin/V1/{ => Pay}/AddPayloadSignaturePlugin.php (90%) create mode 100644 tests/Plugin/Douyin/AddRadarPluginTest.php create mode 100644 tests/Plugin/Douyin/ResponsePluginTest.php create mode 100644 tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php create mode 100644 tests/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php create mode 100644 tests/Provider/DouyinTest.php create mode 100644 tests/Shortcut/Douyin/MiniShortcutTest.php create mode 100644 web/docs/v3/quick-start/douyin.md diff --git a/src/Pay.php b/src/Pay.php index 4f7c3f293..6a587919d 100644 --- a/src/Pay.php +++ b/src/Pay.php @@ -10,6 +10,7 @@ use Yansongda\Artful\Exception\ContainerException; use Yansongda\Artful\Exception\ServiceNotFoundException; use Yansongda\Pay\Provider\Alipay; +use Yansongda\Pay\Provider\Douyin; use Yansongda\Pay\Provider\Unipay; use Yansongda\Pay\Provider\Wechat; use Yansongda\Pay\Service\AlipayServiceProvider; @@ -21,6 +22,7 @@ * @method static Alipay alipay(array $config = [], $container = null) * @method static Wechat wechat(array $config = [], $container = null) * @method static Unipay unipay(array $config = [], $container = null) + * @method static Douyin douyin(array $config = [], $container = null) */ class Pay { diff --git a/src/Plugin/Alipay/V2/AddRadarPlugin.php b/src/Plugin/Alipay/V2/AddRadarPlugin.php index 68045d675..cc99de1c5 100644 --- a/src/Plugin/Alipay/V2/AddRadarPlugin.php +++ b/src/Plugin/Alipay/V2/AddRadarPlugin.php @@ -32,7 +32,8 @@ public function assembly(Rocket $rocket, Closure $next): Rocket $payload = $rocket->getPayload(); $rocket->setRadar(new Request( - get_radar_method(new Collection($params) ?? 'POST'), + // 这里因为支付宝的 payload 里不包含 _method,所以需要取 params 中的 + get_radar_method(new Collection($params)) ?? 'POST', get_alipay_url($config, $payload), $this->getHeaders(), // 不能用 packer,支付宝接收的是 x-www-form-urlencoded 返回的又是 json,packer 用的是返回. diff --git a/src/Plugin/Douyin/V1/AddPayloadSignaturePlugin.php b/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php similarity index 90% rename from src/Plugin/Douyin/V1/AddPayloadSignaturePlugin.php rename to src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php index cb429eb22..8bfb2bf1d 100644 --- a/src/Plugin/Douyin/V1/AddPayloadSignaturePlugin.php +++ b/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yansongda\Pay\Plugin\Douyin\V1; +namespace Yansongda\Pay\Plugin\Douyin\V1\Pay; use Closure; use Yansongda\Artful\Contract\PluginInterface; @@ -26,14 +26,14 @@ class AddPayloadSignaturePlugin implements PluginInterface */ public function assembly(Rocket $rocket, Closure $next): Rocket { - Logger::debug('[Douyin][V1][AddPayloadSignaturePlugin] 插件开始装载', ['rocket' => $rocket]); + Logger::debug('[Douyin][V1][Pay][AddPayloadSignaturePlugin] 插件开始装载', ['rocket' => $rocket]); $config = get_provider_config('douyin', $rocket->getParams()); $payload = $rocket->getPayload(); $rocket->mergePayload(['sign' => $this->getSign($config, filter_params($payload))]); - Logger::info('[Douyin][V1][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + Logger::info('[Douyin][V1][Pay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); return $next($rocket); } diff --git a/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php index 2678b25bb..10bf88872 100644 --- a/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php +++ b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php @@ -61,7 +61,7 @@ public function assembly(Rocket $rocket, Closure $next): Rocket protected function service(Collection $payload, array $config): array { return [ - 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + 'thirdparty_id' => $payload->get('thirdparty_id', $config['mini_thirdparty_id'] ?? ''), ]; } diff --git a/src/Provider/Douyin.php b/src/Provider/Douyin.php index ac7cd1696..76be876b4 100644 --- a/src/Provider/Douyin.php +++ b/src/Provider/Douyin.php @@ -15,6 +15,7 @@ use Yansongda\Artful\Exception\InvalidParamsException; use Yansongda\Artful\Exception\ServiceNotFoundException; use Yansongda\Artful\Plugin\AddPayloadBodyPlugin; +use Yansongda\Artful\Plugin\AddRadarPlugin; use Yansongda\Artful\Plugin\ParserPlugin; use Yansongda\Artful\Plugin\StartPlugin; use Yansongda\Artful\Rocket; @@ -23,6 +24,8 @@ use Yansongda\Pay\Event\MethodCalled; use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Pay; +use Yansongda\Pay\Plugin\Douyin\ResponsePlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; use Yansongda\Supports\Collection; use Yansongda\Supports\Str; @@ -42,7 +45,7 @@ class Douyin implements ProviderInterface * @throws InvalidParamsException * @throws ServiceNotFoundException */ - public function __call(string $shortcut, array $params): null|Collection|MessageInterface + public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket { $plugin = '\\Yansongda\\Pay\\Shortcut\\Douyin\\'.Str::studly($shortcut).'Shortcut'; @@ -134,7 +137,7 @@ public function mergeCommonPlugins(array $plugins): array return array_merge( [StartPlugin::class], $plugins, - [AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class], + [AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class], ); } diff --git a/src/Provider/Wechat.php b/src/Provider/Wechat.php index cbf265426..65578d6b5 100644 --- a/src/Provider/Wechat.php +++ b/src/Provider/Wechat.php @@ -56,7 +56,7 @@ class Wechat implements ProviderInterface * @throws InvalidParamsException * @throws ServiceNotFoundException */ - public function __call(string $shortcut, array $params): null|Collection|MessageInterface + public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket { $plugin = '\\Yansongda\\Pay\\Shortcut\\Wechat\\'.Str::studly($shortcut).'Shortcut'; diff --git a/src/Shortcut/Douyin/MiniShortcut.php b/src/Shortcut/Douyin/MiniShortcut.php index 3bdab97f1..9be7663f0 100644 --- a/src/Shortcut/Douyin/MiniShortcut.php +++ b/src/Shortcut/Douyin/MiniShortcut.php @@ -10,7 +10,7 @@ use Yansongda\Artful\Plugin\StartPlugin; use Yansongda\Pay\Plugin\Douyin\AddRadarPlugin; use Yansongda\Pay\Plugin\Douyin\ResponsePlugin; -use Yansongda\Pay\Plugin\Douyin\V1\AddPayloadSignaturePlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin; class MiniShortcut implements ShortcutInterface diff --git a/tests/Plugin/Douyin/AddRadarPluginTest.php b/tests/Plugin/Douyin/AddRadarPluginTest.php new file mode 100644 index 000000000..6cd21957c --- /dev/null +++ b/tests/Plugin/Douyin/AddRadarPluginTest.php @@ -0,0 +1,41 @@ +plugin = new AddRadarPlugin(); + } + + public function testNormal() + { + $params = []; + $payload = new Collection([ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_order', + '_body' => '123', + ]); + + $rocket = (new Rocket())->setParams($params)->setPayload($payload); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + $radar = $result->getRadar(); + + self::assertEquals('yansongda/pay-v3', $radar->getHeaderLine('User-Agent')); + self::assertEquals('application/json; charset=utf-8', $radar->getHeaderLine('Content-Type')); + self::assertEquals('123', (string) $radar->getBody()); + self::assertEquals('POST', $radar->getMethod()); + self::assertEquals('https://open-sandbox.douyin.com/api/apps/ecpay/v1/create_order', (string) $radar->getUri()); + } +} diff --git a/tests/Plugin/Douyin/ResponsePluginTest.php b/tests/Plugin/Douyin/ResponsePluginTest.php new file mode 100644 index 000000000..be05c8b22 --- /dev/null +++ b/tests/Plugin/Douyin/ResponsePluginTest.php @@ -0,0 +1,62 @@ +plugin = new ResponsePlugin(); + } + + public function testOriginalResponseDestination() + { + $rocket = new Rocket(); + $rocket->setDestinationOrigin(new Response()); + $rocket->setDestination(new Collection(['err_no' => 0, 'err_tips' => 'ok', 'data' => ['foo' => 'bar']])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertInstanceOf(Collection::class, $result->getDestination()); + self::assertEquals(['foo' => 'bar'], $result->getDestination()->all()); + } + + public function testOriginalResponseCodeErrorDestination() + { + $destination = new Response(500); + + $rocket = new Rocket(); + $rocket->setDestinationOrigin($destination); + + self::expectException(InvalidResponseException::class); + self::expectExceptionCode(Exception::RESPONSE_CODE_WRONG); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + + public function testDestinationErrorCode() + { + $destination = new Response(200); + + $rocket = new Rocket(); + $rocket->setDestinationOrigin($destination); + $rocket->setDestination(new Collection(['err_no' => 1, 'err_tips' => 'error'])); + + self::expectException(InvalidResponseException::class); + self::expectExceptionCode(Exception::RESPONSE_BUSINESS_CODE_WRONG); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } +} diff --git a/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php b/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php new file mode 100644 index 000000000..756753c60 --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php @@ -0,0 +1,58 @@ +plugin = new AddPayloadSignaturePlugin(); + } + + public function testSignNormal() + { + $rocket = new Rocket(); + + $rocket->setPayload([ + '_foo' => 'bar', + 'out_order_no' => '202406100423024876', + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ]); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals('771c1952ffb5e0744fc0ad1337aafa6a', $result->getPayload()->get('sign')); + } + + public function testSignContainsJsonString() + { + $rocket = new Rocket(); + + $rocket->setPayload([ + '_foo' => 'bar', + 'out_order_no' => '202406101307142575', + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, + 'notify_url' => 'https://yansongda.cn/douyin/notify', + 'expand_order_info' => '{"original_delivery_fee":15,"actual_delivery_fee":10}', + ]); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals('259702d0e950991b0bd494c9357f3ca4', $result->getPayload()->get('sign')); + } +} diff --git a/tests/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php new file mode 100644 index 000000000..95f0c1c6f --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php @@ -0,0 +1,90 @@ +plugin = new PayPlugin(); + } + + public function testEmptyPayload() + { + $rocket = new Rocket(); + + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); + self::expectExceptionMessage('参数异常: 抖音小程序下单,参数为空'); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + + public function testNormal() + { + $rocket = new Rocket(); + $rocket->setPayload(new Collection( [ + "name" => "yansongda", + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + "name" => "yansongda", + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_order', + 'app_id' => 'tt226e54d3bd581bf801', + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ], $result->getPayload()->all()); + } + + public function testServiceParams() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'name' => 'yansongda', + 'thirdparty_id' => 'service_provider111', + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'name' => 'yansongda', + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_order', + 'app_id' => 'tt226e54d3bd581bf801', + 'notify_url' => 'https://yansongda.cn/douyin/notify', + 'thirdparty_id' => 'service_provider111' + ], $result->getPayload()->all()); + } + + public function testService() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'name' => 'yansongda', + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'name' => 'yansongda', + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_order', + 'app_id' => 'tt226e54d3bd581bf801', + 'notify_url' => 'https://yansongda.cn/douyin/notify', + 'thirdparty_id' => 'service_provider' + ], $result->getPayload()->all()); + } +} diff --git a/tests/Provider/DouyinTest.php b/tests/Provider/DouyinTest.php new file mode 100644 index 000000000..bc1db44fe --- /dev/null +++ b/tests/Provider/DouyinTest.php @@ -0,0 +1,82 @@ +foo(); + } + + public function testShortcutIncompatible() + { + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_SHORTCUT_INVALID); + + Pay::douyin()->foo(); + } + + public function testMergeCommonPlugins() + { + Pay::config([]); + $plugins = [FooPluginStub::class]; + + self::assertEquals(array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class], + ), Pay::douyin()->mergeCommonPlugins($plugins)); + } + + public function testCallMini() + { + $response = new Response( + 200, + [], + '{"err_no":0,"err_tips":"","data":{"order_id":"7376826336364513572","order_token":"CgwIARDPKBjKMCABKAESTgpMTgGUG+Ms5klBoqYlsymcJWNMvgWCR8XH+9OO5vFPSl2zZcVKFX0sKRuG9zxMNlT43OJotxNNHaO4KLMbiqo6HYxMiRS5tkoeILFzexoA.W"}}', + ); + + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn($response); + Pay::set(HttpClientInterface::class, $http); + + $response = Pay::douyin()->mini([ + 'out_order_no' => '202406100423024876', + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, + // 'notify_url' => 'https://yansongda.cn/unipay/notify', + '_return_rocket' => true, + ]); + + $result = $response->getDestination(); + $payload = $response->getPayload(); + + self::assertInstanceOf(Collection::class, $result); + self::assertEquals('7376826336364513572', $result->get('order_id')); + self::assertEquals('CgwIARDPKBjKMCABKAESTgpMTgGUG+Ms5klBoqYlsymcJWNMvgWCR8XH+9OO5vFPSl2zZcVKFX0sKRuG9zxMNlT43OJotxNNHaO4KLMbiqo6HYxMiRS5tkoeILFzexoA.W', $result->get('order_token')); + self::assertEquals('771c1952ffb5e0744fc0ad1337aafa6a', $payload->get('sign')); + } +} diff --git a/tests/Shortcut/Douyin/MiniShortcutTest.php b/tests/Shortcut/Douyin/MiniShortcutTest.php new file mode 100644 index 000000000..b9ab9f1bc --- /dev/null +++ b/tests/Shortcut/Douyin/MiniShortcutTest.php @@ -0,0 +1,40 @@ +plugin = new MiniShortcut(); + } + + public function testDefault() + { + self::assertEquals([ + StartPlugin::class, + PayPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins([])); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index cd9be3c15..e0c3dae40 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -144,7 +144,41 @@ protected function setUp(): void 'mch_secret_key' => '979da4cfccbae7923641daa5dd7047c2', 'mode' => Pay::MODE_SANDBOX, ], - ] + ], + 'douyin' => [ + 'default' => [ + // 必填-小程序 app_id + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', + // 必填-支付 Token,用于支付回调签名 + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mini_token' => 'douyin_mini_token', + // 必填-支付 SALT,用于支付签名 + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> SALT + 'mini_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 选填-抖音开放平台服务商id + 'mini_thirdparty_id' => '', + // 选填-抖音支付回调地址 + 'mini_notify_url' => 'https://yansongda.cn/douyin/notify', + 'mode' => Pay::MODE_SANDBOX, + ], + 'service_provider' => [ + // 必填-小程序 app_id + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', + // 必填-支付 Token,用于支付回调签名 + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mini_token' => 'douyin_mini_token', + // 必填-支付 SALT,用于支付签名 + // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> SALT + 'mini_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 选填-抖音开放平台服务商id + 'mini_thirdparty_id' => 'service_provider', + // 选填-抖音支付回调地址 + 'mini_notify_url' => 'https://yansongda.cn/douyin/notify', + 'mode' => Pay::MODE_SERVICE, + ], + ], ]; // hyperf 单测时,未在 hyperf 框架内,所以 sdk 没有 container, 手动设置一个 diff --git a/web/docs/v3/douyin/pay.md b/web/docs/v3/douyin/pay.md index 40a02b3b0..0552a6321 100644 --- a/web/docs/v3/douyin/pay.md +++ b/web/docs/v3/douyin/pay.md @@ -14,29 +14,24 @@ Pay::config($config); $order = [ - 'out_trade_no' => time().'', - 'description' => 'subject-测试', - 'amount' => [ - 'total' => 1, - 'currency' => 'CNY', - ], - 'payer' => [ - 'openid' => '123fsdf234', - ] + 'out_order_no' => date('YmdHis') . rand(1000, 9999), + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, ]; -$result = Pay::wechat()->mini($order); -// 返回 Collection 实例。包含了调用 JSAPI 的所有参数,如appId,timeStamp,nonceStr,package,signType,paySign 等; -// 可直接通过 $result->appId, $result->timeStamp 获取相关值。 +$result = Pay::douyin()->mini($order); +// 可直接通过 $result->order_id, $result->order_token 获取相关值。 // 后续调用不在本文档讨论范围内,请自行参考官方文档。 ``` ### 订单配置参数 -**所有订单配置中,客观参数均不用配置,扩展包已经为大家自动处理了**,比如,`trade_type`,`appid`,`sign` 等参数,大家只需传入订单类主观参数即可。 +**所有订单配置中,客观参数均不用配置,扩展包已经为大家自动处理了**,比如,`app_id`,`sign` 等参数,大家只需传入订单类主观参数即可。 -所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考[这里](https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/mini-prepay.html),查看「请求参数」一栏。 +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考[这里](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/pay),查看「请求参数」一栏。 ### 调用支付 -后续调起支付不再本文档讨论范围内,请参考[官方文档](https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/mini-transfer-payment.html) +后续调起支付不再本文档讨论范围内,请参考[官方文档](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/tt-pay) diff --git a/web/docs/v3/quick-start/douyin.md b/web/docs/v3/quick-start/douyin.md new file mode 100644 index 000000000..751c5895c --- /dev/null +++ b/web/docs/v3/quick-start/douyin.md @@ -0,0 +1,21 @@ +# 抖音快速入门 + +在初始化完毕后,就可以直接方便的享受 `yansongda/pay` 带来的便利了。 + +## 小程序支付 + +```php +Pay::config($config); + +$order = [ + 'out_order_no' => date('YmdHis') . rand(1000, 9999), + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, +]; + +$result = Pay::douyin()->mini($order); +// 可直接通过 $result->order_id, $result->order_token 获取相关值。 +// 后续调用不在本文档讨论范围内,请自行参考官方文档。 +``` diff --git a/web/docs/v3/wechat/pay.md b/web/docs/v3/wechat/pay.md index 205cd1695..4b3958cca 100644 --- a/web/docs/v3/wechat/pay.md +++ b/web/docs/v3/wechat/pay.md @@ -12,10 +12,11 @@ | scan | 扫码支付 | array $order | Collection | | transfer | 转账 | array $order | Collection | - +:::tip 默认情况下,除 APP支付、小程序支付 外所使用的 appid 均是微信公众号的 appid,即配置文件中的 `mp_app_id` 参数 如果想使用其他类型的 appid,则只需要在调用参数中增加 `_type` 参数即可,例如,如果想使用小程序的 appid,则:`['_type' => 'mini']` +::: ## 公众号支付 From 3de9390fbb78d26bd10ba874a307ee74be02e212 Mon Sep 17 00:00:00 2001 From: yansongda Date: Mon, 10 Jun 2024 21:14:27 +0800 Subject: [PATCH 05/19] update --- src/Plugin/Douyin/{ => V1/Pay}/AddRadarPlugin.php | 2 +- src/Plugin/Douyin/{ => V1/Pay}/ResponsePlugin.php | 2 +- src/Provider/Douyin.php | 2 +- src/Shortcut/Douyin/MiniShortcut.php | 4 ++-- tests/Plugin/Douyin/{ => V1/Pay}/AddRadarPluginTest.php | 4 ++-- tests/Plugin/Douyin/{ => V1/Pay}/ResponsePluginTest.php | 4 ++-- tests/Provider/DouyinTest.php | 2 +- tests/Shortcut/Douyin/MiniShortcutTest.php | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) rename src/Plugin/Douyin/{ => V1/Pay}/AddRadarPlugin.php (97%) rename src/Plugin/Douyin/{ => V1/Pay}/ResponsePlugin.php (97%) rename tests/Plugin/Douyin/{ => V1/Pay}/AddRadarPluginTest.php (91%) rename tests/Plugin/Douyin/{ => V1/Pay}/ResponsePluginTest.php (94%) diff --git a/src/Plugin/Douyin/AddRadarPlugin.php b/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php similarity index 97% rename from src/Plugin/Douyin/AddRadarPlugin.php rename to src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php index d377a6f30..4be463a56 100644 --- a/src/Plugin/Douyin/AddRadarPlugin.php +++ b/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yansongda\Pay\Plugin\Douyin; +namespace Yansongda\Pay\Plugin\Douyin\V1\Pay; use Closure; use GuzzleHttp\Psr7\Request; diff --git a/src/Plugin/Douyin/ResponsePlugin.php b/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php similarity index 97% rename from src/Plugin/Douyin/ResponsePlugin.php rename to src/Plugin/Douyin/V1/Pay/ResponsePlugin.php index ba916c449..bdbcf28fe 100644 --- a/src/Plugin/Douyin/ResponsePlugin.php +++ b/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yansongda\Pay\Plugin\Douyin; +namespace Yansongda\Pay\Plugin\Douyin\V1\Pay; use Closure; use Psr\Http\Message\ResponseInterface; diff --git a/src/Provider/Douyin.php b/src/Provider/Douyin.php index 76be876b4..486e3d90f 100644 --- a/src/Provider/Douyin.php +++ b/src/Provider/Douyin.php @@ -24,8 +24,8 @@ use Yansongda\Pay\Event\MethodCalled; use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Pay; -use Yansongda\Pay\Plugin\Douyin\ResponsePlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin; use Yansongda\Supports\Collection; use Yansongda\Supports\Str; diff --git a/src/Shortcut/Douyin/MiniShortcut.php b/src/Shortcut/Douyin/MiniShortcut.php index 9be7663f0..d91216419 100644 --- a/src/Shortcut/Douyin/MiniShortcut.php +++ b/src/Shortcut/Douyin/MiniShortcut.php @@ -8,10 +8,10 @@ use Yansongda\Artful\Plugin\AddPayloadBodyPlugin; use Yansongda\Artful\Plugin\ParserPlugin; use Yansongda\Artful\Plugin\StartPlugin; -use Yansongda\Pay\Plugin\Douyin\AddRadarPlugin; -use Yansongda\Pay\Plugin\Douyin\ResponsePlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin; class MiniShortcut implements ShortcutInterface { diff --git a/tests/Plugin/Douyin/AddRadarPluginTest.php b/tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php similarity index 91% rename from tests/Plugin/Douyin/AddRadarPluginTest.php rename to tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php index 6cd21957c..b320be105 100644 --- a/tests/Plugin/Douyin/AddRadarPluginTest.php +++ b/tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php @@ -1,9 +1,9 @@ Date: Mon, 10 Jun 2024 22:34:36 +0800 Subject: [PATCH 06/19] update --- .../V1/Pay/AddPayloadSignaturePlugin.php | 4 +- src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php | 4 +- tests/TestCase.php | 41 +++++++++++-------- web/docs/v3/quick-start/init.md | 21 ++++++---- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php b/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php index 8bfb2bf1d..7a83f97c9 100644 --- a/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php +++ b/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php @@ -43,10 +43,10 @@ public function assembly(Rocket $rocket, Closure $next): Rocket */ protected function getSign(array $config, Collection $payload): string { - $salt = $config['mini_salt'] ?? null; + $salt = $config['mch_secret_salt'] ?? null; if (empty($salt)) { - throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mini_salt]'); + throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_salt]'); } foreach ($payload as $key => $value) { diff --git a/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php index 10bf88872..392614966 100644 --- a/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php +++ b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php @@ -61,12 +61,12 @@ public function assembly(Rocket $rocket, Closure $next): Rocket protected function service(Collection $payload, array $config): array { return [ - 'thirdparty_id' => $payload->get('thirdparty_id', $config['mini_thirdparty_id'] ?? ''), + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), ]; } protected function getNotifyUrl(array $config): ?string { - return empty($config['mini_notify_url']) ? null : $config['mini_notify_url']; + return empty($config['notify_url']) ? null : $config['notify_url']; } } diff --git a/tests/TestCase.php b/tests/TestCase.php index e0c3dae40..db5c10c74 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -147,35 +147,40 @@ protected function setUp(): void ], 'douyin' => [ 'default' => [ - // 必填-小程序 app_id - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> 小程序appid - 'mini_app_id' => 'tt226e54d3bd581bf801', + // 选填-商户号 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mini_token' => 'douyin_mini_token', + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mch_secret_key' => 'douyin_mini_token', // 必填-支付 SALT,用于支付签名 - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> SALT - 'mini_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT + 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 必填-小程序 app_id + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', // 选填-抖音开放平台服务商id - 'mini_thirdparty_id' => '', + 'thirdparty_id' => '', // 选填-抖音支付回调地址 - 'mini_notify_url' => 'https://yansongda.cn/douyin/notify', + 'notify_url' => 'https://yansongda.cn/douyin/notify', 'mode' => Pay::MODE_SANDBOX, ], 'service_provider' => [ - // 必填-小程序 app_id - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> 小程序appid - 'mini_app_id' => 'tt226e54d3bd581bf801', + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mini_token' => 'douyin_mini_token', + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mch_secret_key' => 'douyin_mini_token', // 必填-支付 SALT,用于支付签名 - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> SALT - 'mini_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT + 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 必填-小程序 app_id + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', // 选填-抖音开放平台服务商id - 'mini_thirdparty_id' => 'service_provider', + 'thirdparty_id' => 'service_provider', // 选填-抖音支付回调地址 - 'mini_notify_url' => 'https://yansongda.cn/douyin/notify', + 'notify_url' => 'https://yansongda.cn/douyin/notify', 'mode' => Pay::MODE_SERVICE, ], ], diff --git a/web/docs/v3/quick-start/init.md b/web/docs/v3/quick-start/init.md index ce4aaba63..4fff2baed 100644 --- a/web/docs/v3/quick-start/init.md +++ b/web/docs/v3/quick-start/init.md @@ -98,19 +98,22 @@ $config = [ ], 'douyin' => [ 'default' => [ - // 必填-小程序 app_id - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> 小程序appid - 'mini_app_id' => 'tt226e54d3bd581bf801', + // 选填-商户号 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mini_token' => 'douyin_mini_token', + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mch_secret_key' => 'douyin_mini_token', // 必填-支付 SALT,用于支付签名 - // 抖音开放平台 --> 小程序详情 --> 支付信息 --> 支付设置 --> SALT - 'mini_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT + 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 必填-小程序 app_id + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', // 选填-抖音开放平台服务商id - 'mini_thirdparty_id' => '', + 'thirdparty_id' => '', // 选填-抖音支付回调地址 - 'mini_notify_url' => 'https://yansongda.cn/douyin/notify', + 'notify_url' => 'https://yansongda.cn/douyin/notify', ], ], 'logger' => [ From 9d168bc426380625082b62809d1343c40becac2e Mon Sep 17 00:00:00 2001 From: yansongda Date: Mon, 10 Jun 2024 23:01:17 +0800 Subject: [PATCH 07/19] update --- src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php | 66 ++++++++++++++ src/Shortcut/Douyin/QueryShortcut.php | 52 +++++++++++ .../Douyin/V1/Pay/Mini/QueryPluginTest.php | 87 +++++++++++++++++++ tests/Shortcut/Douyin/QueryShortcutTest.php | 64 ++++++++++++++ 4 files changed, 269 insertions(+) create mode 100644 src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php create mode 100644 src/Shortcut/Douyin/QueryShortcut.php create mode 100644 tests/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php create mode 100644 tests/Shortcut/Douyin/QueryShortcutTest.php diff --git a/src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php new file mode 100644 index 000000000..3dbca6db2 --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php @@ -0,0 +1,66 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序查询订单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_order', + 'app_id' => $config['mini_app_id'] ?? '', + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + ]; + } +} diff --git a/src/Shortcut/Douyin/QueryShortcut.php b/src/Shortcut/Douyin/QueryShortcut.php new file mode 100644 index 000000000..744fc6104 --- /dev/null +++ b/src/Shortcut/Douyin/QueryShortcut.php @@ -0,0 +1,52 @@ +{$action}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$action}] not supported"); + } + + protected function defaultPlugins(): array + { + return $this->miniPlugins(); + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/tests/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php new file mode 100644 index 000000000..f2fce0724 --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php @@ -0,0 +1,87 @@ +plugin = new QueryPlugin(); + } + + public function testEmptyPayload() + { + $rocket = new Rocket(); + + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); + self::expectExceptionMessage('参数异常: 抖音小程序查询订单,参数为空'); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + + public function testNormal() + { + $rocket = new Rocket(); + $rocket->setPayload(new Collection( [ + "out_order_no" => "yansongda", + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + "out_order_no" => "yansongda", + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_order', + 'app_id' => 'tt226e54d3bd581bf801', + ], $result->getPayload()->all()); + } + + public function testServiceParams() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'out_order_no' => 'yansongda', + 'thirdparty_id' => 'service_provider111', + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'out_order_no' => 'yansongda', + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_order', + 'app_id' => 'tt226e54d3bd581bf801', + 'thirdparty_id' => 'service_provider111' + ], $result->getPayload()->all()); + } + + public function testService() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'out_order_no' => 'yansongda', + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'out_order_no' => 'yansongda', + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_order', + 'app_id' => 'tt226e54d3bd581bf801', + 'thirdparty_id' => 'service_provider' + ], $result->getPayload()->all()); + } +} diff --git a/tests/Shortcut/Douyin/QueryShortcutTest.php b/tests/Shortcut/Douyin/QueryShortcutTest.php new file mode 100644 index 000000000..3d582c443 --- /dev/null +++ b/tests/Shortcut/Douyin/QueryShortcutTest.php @@ -0,0 +1,64 @@ +plugin = new QueryShortcut(); + } + + public function testFoo() + { + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); + self::expectExceptionMessage('Query action [fooPlugins] not supported'); + + $this->plugin->getPlugins(['_action' => 'foo']); + } + + public function testDefault() + { + self::assertEquals([ + StartPlugin::class, + MiniQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins([])); + } + + public function testMini() + { + self::assertEquals([ + StartPlugin::class, + MiniQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins(['_action' => 'mini'])); + } +} From 63f6d66aae22618532c13a99e42d59327b6ebe47 Mon Sep 17 00:00:00 2001 From: yansongda Date: Fri, 21 Jun 2024 21:34:09 +0800 Subject: [PATCH 08/19] update --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c4dbe80..a068e9ecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,6 @@ - fix: 支付宝响应空签名时签名验证逻辑错误的问题(#998) -### added - -- feat: 支持抖音支付(#996) - ### optimized - optimize: 优化微信 `ResponsePlugin` 插件去除不必要的返回参数(#996) From 9aaf43402f792505d7f82be65ddbc3470b274081 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 28 Jul 2024 22:17:24 +0800 Subject: [PATCH 09/19] update --- web/.vitepress/sidebar/v3.js | 17 ++++++++++++++++- web/docs/v3/quick-start/init.md | 6 ++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/web/.vitepress/sidebar/v3.js b/web/.vitepress/sidebar/v3.js index faefb64fe..10e91e503 100644 --- a/web/.vitepress/sidebar/v3.js +++ b/web/.vitepress/sidebar/v3.js @@ -19,6 +19,7 @@ export default [ { text: '初始化', link: '/docs/v3/quick-start/init' }, { text: '支付宝', link: '/docs/v3/quick-start/alipay' }, { text: '微信', link: '/docs/v3/quick-start/wechat' }, + { text: '支付宝', link: '/docs/v3/quick-start/douyin' }, { text: '银联', link: '/docs/v3/quick-start/unipay' }, { text: '江苏银行', link: '/docs/v3/quick-start/jsb' }, { text: '返回格式', link: '/docs/v3/quick-start/return-format' } @@ -53,8 +54,22 @@ export default [ ] }, { - text: '银联', + text: '抖音', collapsed: false, + items: [ + { text: '支付', link: '/docs/v3/douyin/pay' }, + { text: '查询', link: '/docs/v3/douyin/query' }, + { text: '退款', link: '/docs/v3/wechat/refund' }, + { text: '关闭', link: '/docs/v3/wechat/close' }, + { text: '取消', link: '/docs/v3/wechat/cancel' }, + { text: '接收回调', link: '/docs/v3/wechat/callback' }, + { text: '确认回调', link: '/docs/v3/wechat/response' }, + { text: '所有内置插件', link: '/docs/v3/wechat/all' } + ] + }, + { + text: '银联', + collapsed: true, items: [ { text: '支付', link: '/docs/v3/unipay/pay' }, { text: '查询', link: '/docs/v3/unipay/query' }, diff --git a/web/docs/v3/quick-start/init.md b/web/docs/v3/quick-start/init.md index a20399314..b7bbdc961 100644 --- a/web/docs/v3/quick-start/init.md +++ b/web/docs/v3/quick-start/init.md @@ -96,7 +96,6 @@ $config = [ 'mode' => Pay::MODE_NORMAL, ], ], -<<<<<<< HEAD 'douyin' => [ 'default' => [ // 选填-商户号 @@ -116,7 +115,7 @@ $config = [ // 选填-抖音支付回调地址 'notify_url' => 'https://yansongda.cn/douyin/notify', ], -======= + ], 'jsb' => [ 'default' => [ // 服务代码 @@ -135,8 +134,7 @@ $config = [ 'notify_url' => '', // 选填-默认为正常模式。可选为: MODE_NORMAL:正式环境, MODE_SANDBOX:测试环境 'mode' => Pay::MODE_NORMAL, - ] ->>>>>>> master + ], ], 'logger' => [ 'enable' => false, From cbd56b9f529a10ebadca88f04d422ef6bb81e0f3 Mon Sep 17 00:00:00 2001 From: yansongda Date: Thu, 1 Aug 2024 21:14:49 +0800 Subject: [PATCH 10/19] update --- web/.vitepress/sidebar/v3.js | 20 +- web/package.json | 10 +- web/pnpm-lock.yaml | 1067 ++++++++++++++++++---------------- 3 files changed, 565 insertions(+), 532 deletions(-) diff --git a/web/.vitepress/sidebar/v3.js b/web/.vitepress/sidebar/v3.js index 10e91e503..c8080f58b 100644 --- a/web/.vitepress/sidebar/v3.js +++ b/web/.vitepress/sidebar/v3.js @@ -19,7 +19,7 @@ export default [ { text: '初始化', link: '/docs/v3/quick-start/init' }, { text: '支付宝', link: '/docs/v3/quick-start/alipay' }, { text: '微信', link: '/docs/v3/quick-start/wechat' }, - { text: '支付宝', link: '/docs/v3/quick-start/douyin' }, + { text: '抖音', link: '/docs/v3/quick-start/douyin' }, { text: '银联', link: '/docs/v3/quick-start/unipay' }, { text: '江苏银行', link: '/docs/v3/quick-start/jsb' }, { text: '返回格式', link: '/docs/v3/quick-start/return-format' } @@ -59,12 +59,12 @@ export default [ items: [ { text: '支付', link: '/docs/v3/douyin/pay' }, { text: '查询', link: '/docs/v3/douyin/query' }, - { text: '退款', link: '/docs/v3/wechat/refund' }, - { text: '关闭', link: '/docs/v3/wechat/close' }, - { text: '取消', link: '/docs/v3/wechat/cancel' }, - { text: '接收回调', link: '/docs/v3/wechat/callback' }, - { text: '确认回调', link: '/docs/v3/wechat/response' }, - { text: '所有内置插件', link: '/docs/v3/wechat/all' } + // { text: '退款', link: '/docs/v3/wechat/refund' }, + // { text: '关闭', link: '/docs/v3/wechat/close' }, + // { text: '取消', link: '/docs/v3/wechat/cancel' }, + // { text: '接收回调', link: '/docs/v3/wechat/callback' }, + // { text: '确认回调', link: '/docs/v3/wechat/response' }, + // { text: '所有内置插件', link: '/docs/v3/wechat/all' } ] }, { @@ -95,14 +95,14 @@ export default [ }, { text: '核心架构', - collapsed: false, + collapsed: true, items: [ { text: '核心思想', link: '/docs/v3/kernel/kernel' }, ] }, { text: '其它', - collapsed: false, + collapsed: true, items: [ { text: '事件', link: '/docs/v3/others/event' }, { text: '日志', link: '/docs/v3/others/logger' }, @@ -111,7 +111,7 @@ export default [ }, { text: '升级指南', - collapsed: false, + collapsed: true, items: [ { text: 'v3.7 升级指南', link: '/docs/v3/upgrade/v3.7' }, { text: 'v3.6 升级指南', link: '/docs/v3/upgrade/v3.6' }, diff --git a/web/package.json b/web/package.json index 313633995..12606acd5 100644 --- a/web/package.json +++ b/web/package.json @@ -6,12 +6,12 @@ "web:serve": "vitepress serve" }, "devDependencies": { - "@types/node": "^20.12.12", + "@types/node": "^20.14.13", "fast-glob": "^3.3.2", - "sass": "^1.77.1", - "vite": "^5.2.11", - "vitepress": "^1.1.4", - "vue": "^3.4.27" + "sass": "^1.77.8", + "vite": "^5.3.5", + "vitepress": "^1.3.1", + "vue": "^3.4.35" }, "pnpm": { "peerDependencyRules": { diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 99e4489c8..311f55979 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -9,23 +9,23 @@ importers: .: devDependencies: '@types/node': - specifier: ^20.12.12 - version: 20.12.12 + specifier: ^20.14.13 + version: 20.14.13 fast-glob: specifier: ^3.3.2 version: 3.3.2 sass: - specifier: ^1.77.1 - version: 1.77.1 + specifier: ^1.77.8 + version: 1.77.8 vite: - specifier: ^5.2.11 - version: 5.2.11(@types/node@20.12.12)(sass@1.77.1) + specifier: ^5.3.5 + version: 5.3.5(@types/node@20.14.13)(sass@1.77.8) vitepress: - specifier: ^1.1.4 - version: 1.1.4(@algolia/client-search@4.23.3)(@types/node@20.12.12)(postcss@8.4.38)(sass@1.77.1)(search-insights@2.13.0) + specifier: ^1.3.1 + version: 1.3.1(@algolia/client-search@4.24.0)(@types/node@20.14.13)(postcss@8.4.40)(sass@1.77.8)(search-insights@2.13.0) vue: - specifier: ^3.4.27 - version: 3.4.27 + specifier: ^3.4.35 + version: 3.4.35 packages: @@ -42,89 +42,83 @@ packages: peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - peerDependenciesMeta: - '@algolia/client-search': - optional: true '@algolia/autocomplete-shared@1.9.3': resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - peerDependenciesMeta: - '@algolia/client-search': - optional: true - '@algolia/cache-browser-local-storage@4.23.3': - resolution: {integrity: sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==} + '@algolia/cache-browser-local-storage@4.24.0': + resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} - '@algolia/cache-common@4.23.3': - resolution: {integrity: sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==} + '@algolia/cache-common@4.24.0': + resolution: {integrity: sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==} - '@algolia/cache-in-memory@4.23.3': - resolution: {integrity: sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==} + '@algolia/cache-in-memory@4.24.0': + resolution: {integrity: sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==} - '@algolia/client-account@4.23.3': - resolution: {integrity: sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==} + '@algolia/client-account@4.24.0': + resolution: {integrity: sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==} - '@algolia/client-analytics@4.23.3': - resolution: {integrity: sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==} + '@algolia/client-analytics@4.24.0': + resolution: {integrity: sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==} - '@algolia/client-common@4.23.3': - resolution: {integrity: sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==} + '@algolia/client-common@4.24.0': + resolution: {integrity: sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==} - '@algolia/client-personalization@4.23.3': - resolution: {integrity: sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==} + '@algolia/client-personalization@4.24.0': + resolution: {integrity: sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==} - '@algolia/client-search@4.23.3': - resolution: {integrity: sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==} + '@algolia/client-search@4.24.0': + resolution: {integrity: sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==} - '@algolia/logger-common@4.23.3': - resolution: {integrity: sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==} + '@algolia/logger-common@4.24.0': + resolution: {integrity: sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==} - '@algolia/logger-console@4.23.3': - resolution: {integrity: sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==} + '@algolia/logger-console@4.24.0': + resolution: {integrity: sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==} - '@algolia/recommend@4.23.3': - resolution: {integrity: sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==} + '@algolia/recommend@4.24.0': + resolution: {integrity: sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==} - '@algolia/requester-browser-xhr@4.23.3': - resolution: {integrity: sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==} + '@algolia/requester-browser-xhr@4.24.0': + resolution: {integrity: sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==} - '@algolia/requester-common@4.23.3': - resolution: {integrity: sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==} + '@algolia/requester-common@4.24.0': + resolution: {integrity: sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==} - '@algolia/requester-node-http@4.23.3': - resolution: {integrity: sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==} + '@algolia/requester-node-http@4.24.0': + resolution: {integrity: sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==} - '@algolia/transporter@4.23.3': - resolution: {integrity: sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==} + '@algolia/transporter@4.24.0': + resolution: {integrity: sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==} - '@babel/helper-string-parser@7.24.1': - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.5': - resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.24.5': - resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} + '@babel/parser@7.25.3': + resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.24.5': - resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} + '@babel/types@7.25.2': + resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} engines: {node: '>=6.9.0'} - '@docsearch/css@3.6.0': - resolution: {integrity: sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==} + '@docsearch/css@3.6.1': + resolution: {integrity: sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==} - '@docsearch/js@3.6.0': - resolution: {integrity: sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==} + '@docsearch/js@3.6.1': + resolution: {integrity: sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==} - '@docsearch/react@3.6.0': - resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==} + '@docsearch/react@3.6.1': + resolution: {integrity: sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' @@ -140,146 +134,146 @@ packages: search-insights: optional: true - '@esbuild/aix-ppc64@0.20.2': - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.20.2': - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.20.2': - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.20.2': - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.20.2': - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.20.2': - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.20.2': - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.20.2': - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.20.2': - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.20.2': - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.20.2': - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.20.2': - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.20.2': - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.20.2': - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.20.2': - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.20.2': - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.20.2': - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.20.2': - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.20.2': - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.20.2': - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.20.2': - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.20.2': - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.20.2': - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -293,175 +287,179 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@rollup/rollup-android-arm-eabi@4.17.2': - resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} + '@rollup/rollup-android-arm-eabi@4.19.2': + resolution: {integrity: sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.17.2': - resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} + '@rollup/rollup-android-arm64@4.19.2': + resolution: {integrity: sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.17.2': - resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} + '@rollup/rollup-darwin-arm64@4.19.2': + resolution: {integrity: sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.17.2': - resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} + '@rollup/rollup-darwin-x64@4.19.2': + resolution: {integrity: sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} + '@rollup/rollup-linux-arm-gnueabihf@4.19.2': + resolution: {integrity: sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} + '@rollup/rollup-linux-arm-musleabihf@4.19.2': + resolution: {integrity: sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.17.2': - resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} + '@rollup/rollup-linux-arm64-gnu@4.19.2': + resolution: {integrity: sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.17.2': - resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} + '@rollup/rollup-linux-arm64-musl@4.19.2': + resolution: {integrity: sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': - resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.19.2': + resolution: {integrity: sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} + '@rollup/rollup-linux-riscv64-gnu@4.19.2': + resolution: {integrity: sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.17.2': - resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} + '@rollup/rollup-linux-s390x-gnu@4.19.2': + resolution: {integrity: sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.17.2': - resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} + '@rollup/rollup-linux-x64-gnu@4.19.2': + resolution: {integrity: sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.17.2': - resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} + '@rollup/rollup-linux-x64-musl@4.19.2': + resolution: {integrity: sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.17.2': - resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} + '@rollup/rollup-win32-arm64-msvc@4.19.2': + resolution: {integrity: sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.17.2': - resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} + '@rollup/rollup-win32-ia32-msvc@4.19.2': + resolution: {integrity: sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.17.2': - resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} + '@rollup/rollup-win32-x64-msvc@4.19.2': + resolution: {integrity: sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==} cpu: [x64] os: [win32] - '@shikijs/core@1.5.2': - resolution: {integrity: sha512-wSAOgaz48GmhILFElMCeQypSZmj6Ru6DttOOtl3KNkdJ17ApQuGNCfzpk4cClasVrnIu45++2DBwG4LNMQAfaA==} + '@shikijs/core@1.12.1': + resolution: {integrity: sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==} - '@shikijs/transformers@1.5.2': - resolution: {integrity: sha512-/Sh64rKOFGMQLCvtHeL1Y7EExdq8LLxcdVkvoGx2aMHsYMOn8DckYl2gYKMHRBu/YUt1C38/Amd1Jdh48tWHgw==} + '@shikijs/transformers@1.12.1': + resolution: {integrity: sha512-zOpj/S2thBvnJV4Ty3EE8aRs/VqCbV+lgtEYeBRkPxTW22uLADEIZq0qjt5W2Rfy2KSu29e73nRyzp4PefjUTg==} '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - '@types/markdown-it@14.1.1': - resolution: {integrity: sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==} + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - '@types/node@20.12.12': - resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} + '@types/node@20.14.13': + resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} + + '@types/unist@3.0.2': + resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} - '@vitejs/plugin-vue@5.0.4': - resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} + '@vitejs/plugin-vue@5.1.1': + resolution: {integrity: sha512-sDckXxlHpMsjRQbAH9WanangrfrblsOd3pNifePs+FOHjJg1jfWq5L/P0PsBRndEt3nmdUnmvieP8ULDeX5AvA==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 vue: ^3.2.25 - '@vue/compiler-core@3.4.27': - resolution: {integrity: sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==} + '@vue/compiler-core@3.4.35': + resolution: {integrity: sha512-gKp0zGoLnMYtw4uS/SJRRO7rsVggLjvot3mcctlMXunYNsX+aRJDqqw/lV5/gHK91nvaAAlWFgdVl020AW1Prg==} - '@vue/compiler-dom@3.4.27': - resolution: {integrity: sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==} + '@vue/compiler-dom@3.4.35': + resolution: {integrity: sha512-pWIZRL76/oE/VMhdv/ovZfmuooEni6JPG1BFe7oLk5DZRo/ImydXijoZl/4kh2406boRQ7lxTYzbZEEXEhj9NQ==} - '@vue/compiler-sfc@3.4.27': - resolution: {integrity: sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==} + '@vue/compiler-sfc@3.4.35': + resolution: {integrity: sha512-xacnRS/h/FCsjsMfxBkzjoNxyxEyKyZfBch/P4vkLRvYJwe5ChXmZZrj8Dsed/752H2Q3JE8kYu9Uyha9J6PgA==} - '@vue/compiler-ssr@3.4.27': - resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==} + '@vue/compiler-ssr@3.4.35': + resolution: {integrity: sha512-7iynB+0KB1AAJKk/biENTV5cRGHRdbdaD7Mx3nWcm1W8bVD6QmnH3B4AHhQQ1qZHhqFwzEzMwiytXm3PX1e60A==} - '@vue/devtools-api@7.2.0': - resolution: {integrity: sha512-92RsjyH9WKNFO6U/dECUMakq4dm2CeqEDJYLJ8wZ81AnCifpXE7d4jPIjK34ENsPaapA6BSfIZdH/qzLOHiepA==} + '@vue/devtools-api@7.3.7': + resolution: {integrity: sha512-kvjQ6nmsqTp7SrmpwI2G0MgbC4ys0bPsgQirHXJM8y1m7siQ5RnWQUHJVfyUrHNguCySW1cevAdIw87zrPTl9g==} - '@vue/devtools-kit@7.2.0': - resolution: {integrity: sha512-Kx+U0QiQg/g714euYKfnCdhTcOycSlH1oyTE57D0sAmisdsRCNLfXcnnIwcFY2jdCpuz9DNbuE0VWQuYF5zAZQ==} - peerDependencies: - vue: ^3.0.0 + '@vue/devtools-kit@7.3.7': + resolution: {integrity: sha512-ktHhhjI4CoUrwdSUF5b/MFfjrtAtK8r4vhOkFyRN5Yp9kdXTwsRBYcwarHuP+wFPKf4/KM7DVBj2ELO8SBwdsw==} - '@vue/devtools-shared@7.2.0': - resolution: {integrity: sha512-gVr3IjKjU7axNvclRgICgy1gq/TDnF1hhBAEox+l5mMXZiTIFVIm1zpcIPssc0HxMDgzy+lXqOVsY4DGyZ+ZeA==} + '@vue/devtools-shared@7.3.7': + resolution: {integrity: sha512-M9EU1/bWi5GNS/+IZrAhwGOVZmUTN4MH22Hvh35nUZZg9AZP2R2OhfCb+MG4EtAsrUEYlu3R43/SIj3G7EZYtQ==} - '@vue/reactivity@3.4.27': - resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==} + '@vue/reactivity@3.4.35': + resolution: {integrity: sha512-Ggtz7ZZHakriKioveJtPlStYardwQH6VCs9V13/4qjHSQb/teE30LVJNrbBVs4+aoYGtTQKJbTe4CWGxVZrvEw==} - '@vue/runtime-core@3.4.27': - resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==} + '@vue/runtime-core@3.4.35': + resolution: {integrity: sha512-D+BAjFoWwT5wtITpSxwqfWZiBClhBbR+bm0VQlWYFOadUUXFo+5wbe9ErXhLvwguPiLZdEF13QAWi2vP3ZD5tA==} - '@vue/runtime-dom@3.4.27': - resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==} + '@vue/runtime-dom@3.4.35': + resolution: {integrity: sha512-yGOlbos+MVhlS5NWBF2HDNgblG8e2MY3+GigHEyR/dREAluvI5tuUUgie3/9XeqhPE4LF0i2wjlduh5thnfOqw==} - '@vue/server-renderer@3.4.27': - resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==} + '@vue/server-renderer@3.4.35': + resolution: {integrity: sha512-iZ0e/u9mRE4T8tNhlo0tbA+gzVkgv8r5BX6s1kRbOZqfpq14qoIvCZ5gIgraOmYkMYrSEZgkkojFPr+Nyq/Mnw==} peerDependencies: - vue: 3.4.27 + vue: 3.4.35 - '@vue/shared@3.4.27': - resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} + '@vue/shared@3.4.35': + resolution: {integrity: sha512-hvuhBYYDe+b1G8KHxsQ0diDqDMA8D9laxWZhNAjE83VZb5UDaXl9Xnz7cGdDSyiHM90qqI/CyGMcpBpiDy6VVQ==} - '@vueuse/core@10.9.0': - resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==} + '@vueuse/core@10.11.0': + resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==} - '@vueuse/integrations@10.9.0': - resolution: {integrity: sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==} + '@vueuse/integrations@10.11.0': + resolution: {integrity: sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==} peerDependencies: - async-validator: '*' - axios: '*' - change-case: '*' - drauu: '*' - focus-trap: '*' - fuse.js: '*' - idb-keyval: '*' - jwt-decode: '*' - nprogress: '*' - qrcode: '*' - sortablejs: '*' - universal-cookie: '*' + async-validator: ^4 + axios: ^1 + change-case: ^4 + drauu: ^0.3 + focus-trap: ^7 + fuse.js: ^6 + idb-keyval: ^6 + jwt-decode: ^3 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^6 peerDependenciesMeta: async-validator: optional: true @@ -488,14 +486,14 @@ packages: universal-cookie: optional: true - '@vueuse/metadata@10.9.0': - resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==} + '@vueuse/metadata@10.11.0': + resolution: {integrity: sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==} - '@vueuse/shared@10.9.0': - resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} + '@vueuse/shared@10.11.0': + resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} - algoliasearch@4.23.3: - resolution: {integrity: sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==} + algoliasearch@4.24.0: + resolution: {integrity: sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==} anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -505,14 +503,21 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + birpc@0.2.17: + resolution: {integrity: sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -520,8 +525,8 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true @@ -535,8 +540,8 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} focus-trap@7.5.4: @@ -554,8 +559,8 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - immutable@4.3.6: - resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} + immutable@4.3.7: + resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} @@ -573,8 +578,12 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} mark.js@8.11.1: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} @@ -583,12 +592,12 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} - minisearch@6.3.0: - resolution: {integrity: sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==} + minisearch@7.1.0: + resolution: {integrity: sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==} mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} @@ -612,12 +621,12 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + postcss@8.4.40: + resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} engines: {node: ^10 || ^12 || >=14} - preact@10.22.0: - resolution: {integrity: sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==} + preact@10.23.1: + resolution: {integrity: sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -630,27 +639,27 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.3.1: - resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.17.2: - resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} + rollup@4.19.2: + resolution: {integrity: sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - sass@1.77.1: - resolution: {integrity: sha512-OMEyfirt9XEfyvocduUIOlUSkWOXS/LAt6oblR/ISXCTukyavjex+zQNm51pPCOiFKY1QpWvEH1EeCkgyV3I6w==} + sass@1.77.8: + resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} engines: {node: '>=14.0.0'} hasBin: true search-insights@2.13.0: resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==} - shiki@1.5.2: - resolution: {integrity: sha512-fpPbuSaatinmdGijE7VYUD3hxLozR3ZZ+iAx8Iy2X6REmJGyF5hQl94SgmiUNTospq346nXUVZx0035dyGvIVw==} + shiki@1.12.1: + resolution: {integrity: sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==} source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} @@ -660,6 +669,10 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} + superjson@2.2.1: + resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + engines: {node: '>=16'} + tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} @@ -674,8 +687,8 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - vite@5.2.11: - resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} + vite@5.3.5: + resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -702,8 +715,8 @@ packages: terser: optional: true - vitepress@1.1.4: - resolution: {integrity: sha512-bWIzFZXpPB6NIDBuWnS20aMADH+FcFKDfQNYFvbOWij03PR29eImTceQHIzCKordjXYBhM/TjE5VKFTUJ3EheA==} + vitepress@1.3.1: + resolution: {integrity: sha512-soZDpg2rRVJNIM/IYMNDPPr+zTHDA5RbLDHAxacRu+Q9iZ2GwSR0QSUlLs+aEZTkG0SOX1dc8RmUYwyuxK8dfQ==} hasBin: true peerDependencies: markdown-it-mathjax3: ^4 @@ -714,8 +727,8 @@ packages: postcss: optional: true - vue-demi@0.14.7: - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} hasBin: true peerDependencies: @@ -725,8 +738,8 @@ packages: '@vue/composition-api': optional: true - vue@3.4.27: - resolution: {integrity: sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==} + vue@3.4.35: + resolution: {integrity: sha512-+fl/GLmI4GPileHftVlCdB7fUL4aziPcqTudpTGXCT8s+iZWuOCeNEB5haX6Uz2IpRrbEXOgIFbe+XciCuGbNQ==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -735,132 +748,130 @@ packages: snapshots: - '@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0)': + '@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.13.0)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0) - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) + '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.13.0) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0)': + '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.13.0)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) search-insights: 2.13.0 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)': + '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) - algoliasearch: 4.23.3 - optionalDependencies: - '@algolia/client-search': 4.23.3 + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + '@algolia/client-search': 4.24.0 + algoliasearch: 4.24.0 - '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)': + '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': dependencies: - algoliasearch: 4.23.3 - optionalDependencies: - '@algolia/client-search': 4.23.3 + '@algolia/client-search': 4.24.0 + algoliasearch: 4.24.0 - '@algolia/cache-browser-local-storage@4.23.3': + '@algolia/cache-browser-local-storage@4.24.0': dependencies: - '@algolia/cache-common': 4.23.3 + '@algolia/cache-common': 4.24.0 - '@algolia/cache-common@4.23.3': {} + '@algolia/cache-common@4.24.0': {} - '@algolia/cache-in-memory@4.23.3': + '@algolia/cache-in-memory@4.24.0': dependencies: - '@algolia/cache-common': 4.23.3 + '@algolia/cache-common': 4.24.0 - '@algolia/client-account@4.23.3': + '@algolia/client-account@4.24.0': dependencies: - '@algolia/client-common': 4.23.3 - '@algolia/client-search': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/transporter': 4.24.0 - '@algolia/client-analytics@4.23.3': + '@algolia/client-analytics@4.24.0': dependencies: - '@algolia/client-common': 4.23.3 - '@algolia/client-search': 4.23.3 - '@algolia/requester-common': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 - '@algolia/client-common@4.23.3': + '@algolia/client-common@4.24.0': dependencies: - '@algolia/requester-common': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 - '@algolia/client-personalization@4.23.3': + '@algolia/client-personalization@4.24.0': dependencies: - '@algolia/client-common': 4.23.3 - '@algolia/requester-common': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 - '@algolia/client-search@4.23.3': + '@algolia/client-search@4.24.0': dependencies: - '@algolia/client-common': 4.23.3 - '@algolia/requester-common': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 - '@algolia/logger-common@4.23.3': {} + '@algolia/logger-common@4.24.0': {} - '@algolia/logger-console@4.23.3': + '@algolia/logger-console@4.24.0': dependencies: - '@algolia/logger-common': 4.23.3 + '@algolia/logger-common': 4.24.0 - '@algolia/recommend@4.23.3': + '@algolia/recommend@4.24.0': dependencies: - '@algolia/cache-browser-local-storage': 4.23.3 - '@algolia/cache-common': 4.23.3 - '@algolia/cache-in-memory': 4.23.3 - '@algolia/client-common': 4.23.3 - '@algolia/client-search': 4.23.3 - '@algolia/logger-common': 4.23.3 - '@algolia/logger-console': 4.23.3 - '@algolia/requester-browser-xhr': 4.23.3 - '@algolia/requester-common': 4.23.3 - '@algolia/requester-node-http': 4.23.3 - '@algolia/transporter': 4.23.3 + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 - '@algolia/requester-browser-xhr@4.23.3': + '@algolia/requester-browser-xhr@4.24.0': dependencies: - '@algolia/requester-common': 4.23.3 + '@algolia/requester-common': 4.24.0 - '@algolia/requester-common@4.23.3': {} + '@algolia/requester-common@4.24.0': {} - '@algolia/requester-node-http@4.23.3': + '@algolia/requester-node-http@4.24.0': dependencies: - '@algolia/requester-common': 4.23.3 + '@algolia/requester-common': 4.24.0 - '@algolia/transporter@4.23.3': + '@algolia/transporter@4.24.0': dependencies: - '@algolia/cache-common': 4.23.3 - '@algolia/logger-common': 4.23.3 - '@algolia/requester-common': 4.23.3 + '@algolia/cache-common': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/requester-common': 4.24.0 - '@babel/helper-string-parser@7.24.1': {} + '@babel/helper-string-parser@7.24.8': {} - '@babel/helper-validator-identifier@7.24.5': {} + '@babel/helper-validator-identifier@7.24.7': {} - '@babel/parser@7.24.5': + '@babel/parser@7.25.3': dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.25.2 - '@babel/types@7.24.5': + '@babel/types@7.25.2': dependencies: - '@babel/helper-string-parser': 7.24.1 - '@babel/helper-validator-identifier': 7.24.5 + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@docsearch/css@3.6.0': {} + '@docsearch/css@3.6.1': {} - '@docsearch/js@3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0)': + '@docsearch/js@3.6.1(@algolia/client-search@4.24.0)(search-insights@2.13.0)': dependencies: - '@docsearch/react': 3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0) - preact: 10.22.0 + '@docsearch/react': 3.6.1(@algolia/client-search@4.24.0)(search-insights@2.13.0) + preact: 10.23.1 transitivePeerDependencies: - '@algolia/client-search' - '@types/react' @@ -868,87 +879,87 @@ snapshots: - react-dom - search-insights - '@docsearch/react@3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0)': + '@docsearch/react@3.6.1(@algolia/client-search@4.24.0)(search-insights@2.13.0)': dependencies: - '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0) - '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) - '@docsearch/css': 3.6.0 - algoliasearch: 4.23.3 + '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.13.0) + '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + '@docsearch/css': 3.6.1 + algoliasearch: 4.24.0 optionalDependencies: search-insights: 2.13.0 transitivePeerDependencies: - '@algolia/client-search' - '@esbuild/aix-ppc64@0.20.2': + '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/android-arm64@0.20.2': + '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm@0.20.2': + '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-x64@0.20.2': + '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.20.2': + '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-x64@0.20.2': + '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.20.2': + '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.20.2': + '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/linux-arm64@0.20.2': + '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm@0.20.2': + '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-ia32@0.20.2': + '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-loong64@0.20.2': + '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-mips64el@0.20.2': + '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-ppc64@0.20.2': + '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.20.2': + '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-s390x@0.20.2': + '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-x64@0.20.2': + '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.20.2': + '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.20.2': + '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.20.2': + '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/win32-arm64@0.20.2': + '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-ia32@0.20.2': + '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-x64@0.20.2': + '@esbuild/win32-x64@0.21.5': optional: true - '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -962,201 +973,209 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@rollup/rollup-android-arm-eabi@4.17.2': + '@rollup/rollup-android-arm-eabi@4.19.2': optional: true - '@rollup/rollup-android-arm64@4.17.2': + '@rollup/rollup-android-arm64@4.19.2': optional: true - '@rollup/rollup-darwin-arm64@4.17.2': + '@rollup/rollup-darwin-arm64@4.19.2': optional: true - '@rollup/rollup-darwin-x64@4.17.2': + '@rollup/rollup-darwin-x64@4.19.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': + '@rollup/rollup-linux-arm-gnueabihf@4.19.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.17.2': + '@rollup/rollup-linux-arm-musleabihf@4.19.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.17.2': + '@rollup/rollup-linux-arm64-gnu@4.19.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.17.2': + '@rollup/rollup-linux-arm64-musl@4.19.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.19.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.17.2': + '@rollup/rollup-linux-riscv64-gnu@4.19.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.17.2': + '@rollup/rollup-linux-s390x-gnu@4.19.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.17.2': + '@rollup/rollup-linux-x64-gnu@4.19.2': optional: true - '@rollup/rollup-linux-x64-musl@4.17.2': + '@rollup/rollup-linux-x64-musl@4.19.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.17.2': + '@rollup/rollup-win32-arm64-msvc@4.19.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.17.2': + '@rollup/rollup-win32-ia32-msvc@4.19.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.17.2': + '@rollup/rollup-win32-x64-msvc@4.19.2': optional: true - '@shikijs/core@1.5.2': {} + '@shikijs/core@1.12.1': + dependencies: + '@types/hast': 3.0.4 - '@shikijs/transformers@1.5.2': + '@shikijs/transformers@1.12.1': dependencies: - shiki: 1.5.2 + shiki: 1.12.1 '@types/estree@1.0.5': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.2 + '@types/linkify-it@5.0.0': {} - '@types/markdown-it@14.1.1': + '@types/markdown-it@14.1.2': dependencies: '@types/linkify-it': 5.0.0 '@types/mdurl': 2.0.0 '@types/mdurl@2.0.0': {} - '@types/node@20.12.12': + '@types/node@20.14.13': dependencies: undici-types: 5.26.5 + '@types/unist@3.0.2': {} + '@types/web-bluetooth@0.0.20': {} - '@vitejs/plugin-vue@5.0.4(vite@5.2.11(@types/node@20.12.12)(sass@1.77.1))(vue@3.4.27)': + '@vitejs/plugin-vue@5.1.1(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))(vue@3.4.35)': dependencies: - vite: 5.2.11(@types/node@20.12.12)(sass@1.77.1) - vue: 3.4.27 + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vue: 3.4.35 - '@vue/compiler-core@3.4.27': + '@vue/compiler-core@3.4.35': dependencies: - '@babel/parser': 7.24.5 - '@vue/shared': 3.4.27 + '@babel/parser': 7.25.3 + '@vue/shared': 3.4.35 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.0 - '@vue/compiler-dom@3.4.27': + '@vue/compiler-dom@3.4.35': dependencies: - '@vue/compiler-core': 3.4.27 - '@vue/shared': 3.4.27 + '@vue/compiler-core': 3.4.35 + '@vue/shared': 3.4.35 - '@vue/compiler-sfc@3.4.27': + '@vue/compiler-sfc@3.4.35': dependencies: - '@babel/parser': 7.24.5 - '@vue/compiler-core': 3.4.27 - '@vue/compiler-dom': 3.4.27 - '@vue/compiler-ssr': 3.4.27 - '@vue/shared': 3.4.27 + '@babel/parser': 7.25.3 + '@vue/compiler-core': 3.4.35 + '@vue/compiler-dom': 3.4.35 + '@vue/compiler-ssr': 3.4.35 + '@vue/shared': 3.4.35 estree-walker: 2.0.2 - magic-string: 0.30.10 - postcss: 8.4.38 + magic-string: 0.30.11 + postcss: 8.4.40 source-map-js: 1.2.0 - '@vue/compiler-ssr@3.4.27': + '@vue/compiler-ssr@3.4.35': dependencies: - '@vue/compiler-dom': 3.4.27 - '@vue/shared': 3.4.27 + '@vue/compiler-dom': 3.4.35 + '@vue/shared': 3.4.35 - '@vue/devtools-api@7.2.0(vue@3.4.27)': + '@vue/devtools-api@7.3.7': dependencies: - '@vue/devtools-kit': 7.2.0(vue@3.4.27) - transitivePeerDependencies: - - vue + '@vue/devtools-kit': 7.3.7 - '@vue/devtools-kit@7.2.0(vue@3.4.27)': + '@vue/devtools-kit@7.3.7': dependencies: - '@vue/devtools-shared': 7.2.0 + '@vue/devtools-shared': 7.3.7 + birpc: 0.2.17 hookable: 5.5.3 mitt: 3.0.1 perfect-debounce: 1.0.0 speakingurl: 14.0.1 - vue: 3.4.27 + superjson: 2.2.1 - '@vue/devtools-shared@7.2.0': + '@vue/devtools-shared@7.3.7': dependencies: - rfdc: 1.3.1 + rfdc: 1.4.1 - '@vue/reactivity@3.4.27': + '@vue/reactivity@3.4.35': dependencies: - '@vue/shared': 3.4.27 + '@vue/shared': 3.4.35 - '@vue/runtime-core@3.4.27': + '@vue/runtime-core@3.4.35': dependencies: - '@vue/reactivity': 3.4.27 - '@vue/shared': 3.4.27 + '@vue/reactivity': 3.4.35 + '@vue/shared': 3.4.35 - '@vue/runtime-dom@3.4.27': + '@vue/runtime-dom@3.4.35': dependencies: - '@vue/runtime-core': 3.4.27 - '@vue/shared': 3.4.27 + '@vue/reactivity': 3.4.35 + '@vue/runtime-core': 3.4.35 + '@vue/shared': 3.4.35 csstype: 3.1.3 - '@vue/server-renderer@3.4.27(vue@3.4.27)': + '@vue/server-renderer@3.4.35(vue@3.4.35)': dependencies: - '@vue/compiler-ssr': 3.4.27 - '@vue/shared': 3.4.27 - vue: 3.4.27 + '@vue/compiler-ssr': 3.4.35 + '@vue/shared': 3.4.35 + vue: 3.4.35 - '@vue/shared@3.4.27': {} + '@vue/shared@3.4.35': {} - '@vueuse/core@10.9.0(vue@3.4.27)': + '@vueuse/core@10.11.0(vue@3.4.35)': dependencies: '@types/web-bluetooth': 0.0.20 - '@vueuse/metadata': 10.9.0 - '@vueuse/shared': 10.9.0(vue@3.4.27) - vue-demi: 0.14.7(vue@3.4.27) + '@vueuse/metadata': 10.11.0 + '@vueuse/shared': 10.11.0(vue@3.4.35) + vue-demi: 0.14.10(vue@3.4.35) transitivePeerDependencies: - '@vue/composition-api' - vue - '@vueuse/integrations@10.9.0(focus-trap@7.5.4)(vue@3.4.27)': + '@vueuse/integrations@10.11.0(focus-trap@7.5.4)(vue@3.4.35)': dependencies: - '@vueuse/core': 10.9.0(vue@3.4.27) - '@vueuse/shared': 10.9.0(vue@3.4.27) - vue-demi: 0.14.7(vue@3.4.27) + '@vueuse/core': 10.11.0(vue@3.4.35) + '@vueuse/shared': 10.11.0(vue@3.4.35) + vue-demi: 0.14.10(vue@3.4.35) optionalDependencies: focus-trap: 7.5.4 transitivePeerDependencies: - '@vue/composition-api' - vue - '@vueuse/metadata@10.9.0': {} + '@vueuse/metadata@10.11.0': {} - '@vueuse/shared@10.9.0(vue@3.4.27)': + '@vueuse/shared@10.11.0(vue@3.4.35)': dependencies: - vue-demi: 0.14.7(vue@3.4.27) + vue-demi: 0.14.10(vue@3.4.35) transitivePeerDependencies: - '@vue/composition-api' - vue - algoliasearch@4.23.3: - dependencies: - '@algolia/cache-browser-local-storage': 4.23.3 - '@algolia/cache-common': 4.23.3 - '@algolia/cache-in-memory': 4.23.3 - '@algolia/client-account': 4.23.3 - '@algolia/client-analytics': 4.23.3 - '@algolia/client-common': 4.23.3 - '@algolia/client-personalization': 4.23.3 - '@algolia/client-search': 4.23.3 - '@algolia/logger-common': 4.23.3 - '@algolia/logger-console': 4.23.3 - '@algolia/recommend': 4.23.3 - '@algolia/requester-browser-xhr': 4.23.3 - '@algolia/requester-common': 4.23.3 - '@algolia/requester-node-http': 4.23.3 - '@algolia/transporter': 4.23.3 + algoliasearch@4.24.0: + dependencies: + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-account': 4.24.0 + '@algolia/client-analytics': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-personalization': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/recommend': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 anymatch@3.1.3: dependencies: @@ -1165,14 +1184,16 @@ snapshots: binary-extensions@2.3.0: {} - braces@3.0.2: + birpc@0.2.17: {} + + braces@3.0.3: dependencies: - fill-range: 7.0.1 + fill-range: 7.1.1 chokidar@3.6.0: dependencies: anymatch: 3.1.3 - braces: 3.0.2 + braces: 3.0.3 glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.3 @@ -1181,35 +1202,39 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + csstype@3.1.3: {} entities@4.5.0: {} - esbuild@0.20.2: + esbuild@0.21.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 estree-walker@2.0.2: {} @@ -1219,13 +1244,13 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.5 + micromatch: 4.0.7 fastq@1.17.1: dependencies: reusify: 1.0.4 - fill-range@7.0.1: + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -1242,7 +1267,7 @@ snapshots: hookable@5.5.3: {} - immutable@4.3.6: {} + immutable@4.3.7: {} is-binary-path@2.1.0: dependencies: @@ -1256,20 +1281,22 @@ snapshots: is-number@7.0.0: {} - magic-string@0.30.10: + is-what@4.1.16: {} + + magic-string@0.30.11: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 mark.js@8.11.1: {} merge2@1.4.1: {} - micromatch@4.0.5: + micromatch@4.0.7: dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 - minisearch@6.3.0: {} + minisearch@7.1.0: {} mitt@3.0.1: {} @@ -1283,13 +1310,13 @@ snapshots: picomatch@2.3.1: {} - postcss@8.4.38: + postcss@8.4.40: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 source-map-js: 1.2.0 - preact@10.22.0: {} + preact@10.23.1: {} queue-microtask@1.2.3: {} @@ -1299,50 +1326,55 @@ snapshots: reusify@1.0.4: {} - rfdc@1.3.1: {} + rfdc@1.4.1: {} - rollup@4.17.2: + rollup@4.19.2: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.17.2 - '@rollup/rollup-android-arm64': 4.17.2 - '@rollup/rollup-darwin-arm64': 4.17.2 - '@rollup/rollup-darwin-x64': 4.17.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.17.2 - '@rollup/rollup-linux-arm-musleabihf': 4.17.2 - '@rollup/rollup-linux-arm64-gnu': 4.17.2 - '@rollup/rollup-linux-arm64-musl': 4.17.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2 - '@rollup/rollup-linux-riscv64-gnu': 4.17.2 - '@rollup/rollup-linux-s390x-gnu': 4.17.2 - '@rollup/rollup-linux-x64-gnu': 4.17.2 - '@rollup/rollup-linux-x64-musl': 4.17.2 - '@rollup/rollup-win32-arm64-msvc': 4.17.2 - '@rollup/rollup-win32-ia32-msvc': 4.17.2 - '@rollup/rollup-win32-x64-msvc': 4.17.2 + '@rollup/rollup-android-arm-eabi': 4.19.2 + '@rollup/rollup-android-arm64': 4.19.2 + '@rollup/rollup-darwin-arm64': 4.19.2 + '@rollup/rollup-darwin-x64': 4.19.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.19.2 + '@rollup/rollup-linux-arm-musleabihf': 4.19.2 + '@rollup/rollup-linux-arm64-gnu': 4.19.2 + '@rollup/rollup-linux-arm64-musl': 4.19.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.19.2 + '@rollup/rollup-linux-riscv64-gnu': 4.19.2 + '@rollup/rollup-linux-s390x-gnu': 4.19.2 + '@rollup/rollup-linux-x64-gnu': 4.19.2 + '@rollup/rollup-linux-x64-musl': 4.19.2 + '@rollup/rollup-win32-arm64-msvc': 4.19.2 + '@rollup/rollup-win32-ia32-msvc': 4.19.2 + '@rollup/rollup-win32-x64-msvc': 4.19.2 fsevents: 2.3.3 run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - sass@1.77.1: + sass@1.77.8: dependencies: chokidar: 3.6.0 - immutable: 4.3.6 + immutable: 4.3.7 source-map-js: 1.2.0 search-insights@2.13.0: {} - shiki@1.5.2: + shiki@1.12.1: dependencies: - '@shikijs/core': 1.5.2 + '@shikijs/core': 1.12.1 + '@types/hast': 3.0.4 source-map-js@1.2.0: {} speakingurl@14.0.1: {} + superjson@2.2.1: + dependencies: + copy-anything: 3.0.5 + tabbable@6.2.0: {} to-fast-properties@2.0.0: {} @@ -1353,35 +1385,36 @@ snapshots: undici-types@5.26.5: {} - vite@5.2.11(@types/node@20.12.12)(sass@1.77.1): + vite@5.3.5(@types/node@20.14.13)(sass@1.77.8): dependencies: - esbuild: 0.20.2 - postcss: 8.4.38 - rollup: 4.17.2 + esbuild: 0.21.5 + postcss: 8.4.40 + rollup: 4.19.2 optionalDependencies: - '@types/node': 20.12.12 + '@types/node': 20.14.13 fsevents: 2.3.3 - sass: 1.77.1 - - vitepress@1.1.4(@algolia/client-search@4.23.3)(@types/node@20.12.12)(postcss@8.4.38)(sass@1.77.1)(search-insights@2.13.0): - dependencies: - '@docsearch/css': 3.6.0 - '@docsearch/js': 3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0) - '@shikijs/core': 1.5.2 - '@shikijs/transformers': 1.5.2 - '@types/markdown-it': 14.1.1 - '@vitejs/plugin-vue': 5.0.4(vite@5.2.11(@types/node@20.12.12)(sass@1.77.1))(vue@3.4.27) - '@vue/devtools-api': 7.2.0(vue@3.4.27) - '@vueuse/core': 10.9.0(vue@3.4.27) - '@vueuse/integrations': 10.9.0(focus-trap@7.5.4)(vue@3.4.27) + sass: 1.77.8 + + vitepress@1.3.1(@algolia/client-search@4.24.0)(@types/node@20.14.13)(postcss@8.4.40)(sass@1.77.8)(search-insights@2.13.0): + dependencies: + '@docsearch/css': 3.6.1 + '@docsearch/js': 3.6.1(@algolia/client-search@4.24.0)(search-insights@2.13.0) + '@shikijs/core': 1.12.1 + '@shikijs/transformers': 1.12.1 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.1.1(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))(vue@3.4.35) + '@vue/devtools-api': 7.3.7 + '@vue/shared': 3.4.35 + '@vueuse/core': 10.11.0(vue@3.4.35) + '@vueuse/integrations': 10.11.0(focus-trap@7.5.4)(vue@3.4.35) focus-trap: 7.5.4 mark.js: 8.11.1 - minisearch: 6.3.0 - shiki: 1.5.2 - vite: 5.2.11(@types/node@20.12.12)(sass@1.77.1) - vue: 3.4.27 + minisearch: 7.1.0 + shiki: 1.12.1 + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vue: 3.4.35 optionalDependencies: - postcss: 8.4.38 + postcss: 8.4.40 transitivePeerDependencies: - '@algolia/client-search' - '@types/node' @@ -1409,14 +1442,14 @@ snapshots: - typescript - universal-cookie - vue-demi@0.14.7(vue@3.4.27): + vue-demi@0.14.10(vue@3.4.35): dependencies: - vue: 3.4.27 + vue: 3.4.35 - vue@3.4.27: + vue@3.4.35: dependencies: - '@vue/compiler-dom': 3.4.27 - '@vue/compiler-sfc': 3.4.27 - '@vue/runtime-dom': 3.4.27 - '@vue/server-renderer': 3.4.27(vue@3.4.27) - '@vue/shared': 3.4.27 + '@vue/compiler-dom': 3.4.35 + '@vue/compiler-sfc': 3.4.35 + '@vue/runtime-dom': 3.4.35 + '@vue/server-renderer': 3.4.35(vue@3.4.35) + '@vue/shared': 3.4.35 From 1f24c18e007e09660aec97794d0c61e5dad49ba7 Mon Sep 17 00:00:00 2001 From: yansongda Date: Thu, 1 Aug 2024 21:40:13 +0800 Subject: [PATCH 11/19] update --- src/Functions.php | 4 ++++ tests/FunctionTest.php | 21 +++++++++++++---- .../V1/Pay/AddPayloadSignaturePluginTest.php | 23 +++++++++++++++++++ .../Douyin/V1/Pay/AddRadarPluginTest.php | 2 +- tests/TestCase.php | 18 +++++++++++++++ 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/Functions.php b/src/Functions.php index 436417a05..dd804744e 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -643,5 +643,9 @@ function get_douyin_url(array $config, ?Collection $payload): string throw new InvalidParamsException(Exception::PARAMS_DOUYIN_URL_MISSING, '参数异常: 抖音 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`'); } + if (str_starts_with($url, 'http')) { + return $url; + } + return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url; } diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php index 7229dfe24..5fe38df43 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -21,6 +21,7 @@ use function Yansongda\Pay\decrypt_wechat_resource; use function Yansongda\Pay\decrypt_wechat_resource_aes_256_gcm; use function Yansongda\Pay\encrypt_wechat_contents; +use function Yansongda\Pay\get_douyin_url; use function Yansongda\Pay\get_private_cert; use function Yansongda\Pay\get_provider_config; use function Yansongda\Pay\get_public_cert; @@ -566,10 +567,10 @@ public function testVerifyUnipaySignEmpty() public function testGetUnipayUrl() { - self::assertEquals('https://yansongda.cn', get_wechat_url([], new Collection(['_url' => 'https://yansongda.cn']))); - self::assertEquals('https://api.mch.weixin.qq.com/api/v1/yansongda', get_wechat_url([], new Collection(['_url' => 'api/v1/yansongda']))); - self::assertEquals('https://api.mch.weixin.qq.com/api/v1/service/yansongda', get_wechat_url(['mode' => Pay::MODE_SERVICE], new Collection(['_service_url' => 'api/v1/service/yansongda']))); - self::assertEquals('https://api.mch.weixin.qq.com/api/v1/service/yansongda', get_wechat_url(['mode' => Pay::MODE_SERVICE], new Collection(['_url' => 'foo', '_service_url' => 'api/v1/service/yansongda']))); + self::assertEquals('https://yansongda.cn', get_unipay_url([], new Collection(['_url' => 'https://yansongda.cn']))); + self::assertEquals('https://gateway.95516.com/api/v1/yansongda', get_unipay_url([], new Collection(['_url' => 'api/v1/yansongda']))); + self::assertEquals('https://gateway.95516.com/api/v1/service/yansongda', get_unipay_url(['mode' => Pay::MODE_SERVICE], new Collection(['_service_url' => 'api/v1/service/yansongda']))); + self::assertEquals('https://gateway.95516.com/api/v1/service/yansongda', get_unipay_url(['mode' => Pay::MODE_SERVICE], new Collection(['_url' => 'foo', '_service_url' => 'api/v1/service/yansongda']))); self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_UNIPAY_URL_MISSING); @@ -693,4 +694,16 @@ public function testGetEpayUrl() self::assertEquals('https://mybank.jsbchina.cn:577/eis/merchant/merchantServices.htm', get_jsb_url(['mode' => Pay::MODE_NORMAL], new Collection())); self::assertEquals('https://epaytest.jsbchina.cn:9999/eis/merchant/merchantServices.htm', get_jsb_url(['mode' => Pay::MODE_SANDBOX], new Collection())); } + + public function testGetDouyinUrl() + { + self::assertEquals('https://yansongda.cn', get_douyin_url([], new Collection(['_url' => 'https://yansongda.cn']))); + self::assertEquals('https://developer.toutiao.com/api/v1/yansongda', get_douyin_url([], new Collection(['_url' => 'api/v1/yansongda']))); + self::assertEquals('https://developer.toutiao.com/api/v1/service/yansongda', get_douyin_url(['mode' => Pay::MODE_SERVICE], new Collection(['_service_url' => 'api/v1/service/yansongda']))); + self::assertEquals('https://developer.toutiao.com/api/v1/service/yansongda', get_douyin_url(['mode' => Pay::MODE_SERVICE], new Collection(['_url' => 'foo', '_service_url' => 'api/v1/service/yansongda']))); + + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_DOUYIN_URL_MISSING); + get_douyin_url([], new Collection([])); + } } diff --git a/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php b/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php index 756753c60..7c0957e23 100644 --- a/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php +++ b/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php @@ -2,7 +2,9 @@ namespace Yansongda\Pay\Tests\Plugin\Douyin\V1\Pay; +use Yansongda\Artful\Exception\InvalidConfigException; use Yansongda\Artful\Rocket; +use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; use Yansongda\Pay\Tests\TestCase; @@ -55,4 +57,25 @@ public function testSignContainsJsonString() self::assertEquals('259702d0e950991b0bd494c9357f3ca4', $result->getPayload()->get('sign')); } + + public function testEmptySalt() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'empty_salt']); + + $rocket->setPayload([ + '_foo' => 'bar', + 'out_order_no' => '202406100423024876', + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ]); + + self::expectException(InvalidConfigException::class); + self::expectExceptionCode(Exception::CONFIG_DOUYIN_INVALID); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } } diff --git a/tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php b/tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php index b320be105..f9113396b 100644 --- a/tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php +++ b/tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php @@ -36,6 +36,6 @@ public function testNormal() self::assertEquals('application/json; charset=utf-8', $radar->getHeaderLine('Content-Type')); self::assertEquals('123', (string) $radar->getBody()); self::assertEquals('POST', $radar->getMethod()); - self::assertEquals('https://open-sandbox.douyin.com/api/apps/ecpay/v1/create_order', (string) $radar->getUri()); + self::assertStringContainsString('api/apps/ecpay/v1/create_order', (string) $radar->getUri()); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 8bde00070..9b1e27961 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -209,6 +209,24 @@ protected function setUp(): void 'notify_url' => 'https://yansongda.cn/douyin/notify', 'mode' => Pay::MODE_SERVICE, ], + 'empty_salt' => [ + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + 'mch_id' => '73744242495132490630', + // 必填-支付 Token,用于支付回调签名 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mch_secret_key' => 'douyin_mini_token', + // 必填-支付 SALT,用于支付签名 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT + 'mch_secret_salt' => '', + // 必填-小程序 app_id + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', + // 选填-抖音开放平台服务商id + 'thirdparty_id' => 'service_provider', + // 选填-抖音支付回调地址 + 'notify_url' => 'https://yansongda.cn/douyin/notify', + 'mode' => Pay::MODE_SANDBOX, + ], ], ]; From 27fa0e9cfdedb017a29a478bb88eb17f7e938642 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 21:56:02 +0800 Subject: [PATCH 12/19] update --- src/Functions.php | 26 +++++ src/Plugin/Alipay/V2/CallbackPlugin.php | 3 +- src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php | 4 +- src/Plugin/Douyin/V1/Pay/CallbackPlugin.php | 48 ++++++++ .../Douyin/V1/Pay/Mini/QueryRefundPlugin.php | 66 +++++++++++ .../Douyin/V1/Pay/Mini/RefundPlugin.php | 72 ++++++++++++ src/Plugin/Douyin/V1/Pay/ResponsePlugin.php | 11 +- src/Provider/Douyin.php | 30 ++--- src/Shortcut/Alipay/CancelShortcut.php | 2 +- src/Shortcut/Alipay/CloseShortcut.php | 2 +- src/Shortcut/Alipay/QueryShortcut.php | 2 +- src/Shortcut/Alipay/RefundShortcut.php | 2 +- src/Shortcut/Douyin/QueryShortcut.php | 27 ++++- src/Shortcut/Douyin/RefundShortcut.php | 52 +++++++++ src/Shortcut/Unipay/CancelShortcut.php | 8 +- src/Shortcut/Unipay/PosShortcut.php | 8 +- src/Shortcut/Unipay/QueryShortcut.php | 8 +- src/Shortcut/Unipay/RefundShortcut.php | 8 +- src/Shortcut/Unipay/ScanShortcut.php | 8 +- src/Shortcut/Wechat/CloseShortcut.php | 8 +- src/Shortcut/Wechat/PapayShortcut.php | 8 +- src/Shortcut/Wechat/QueryShortcut.php | 8 +- src/Shortcut/Wechat/RefundShortcut.php | 8 +- tests/FunctionTest.php | 15 +++ .../Douyin/V1/Pay/CallbackPluginTest.php | 34 ++++++ .../Douyin/V1/Pay/Mini/RefundPluginTest.php | 108 ++++++++++++++++++ .../Douyin/V1/Pay/ResponsePluginTest.php | 6 +- tests/Provider/DouyinTest.php | 96 +++++++++++++++- tests/Shortcut/Douyin/QueryShortcutTest.php | 28 ++++- tests/Shortcut/Douyin/RefundShortcutTest.php | 63 ++++++++++ tests/Shortcut/Unipay/CancelShortcutTest.php | 1 - tests/Shortcut/Unipay/PosShortcutTest.php | 1 - tests/Shortcut/Unipay/QueryShortcutTest.php | 1 - tests/Shortcut/Unipay/RefundShortcutTest.php | 5 +- tests/Shortcut/Unipay/ScanShortcutTest.php | 1 - tests/Shortcut/Wechat/CloseShortcutTest.php | 1 - tests/Shortcut/Wechat/PapayShortcutTest.php | 1 - tests/Shortcut/Wechat/QueryShortcutTest.php | 1 - tests/Shortcut/Wechat/RefundShortcutTest.php | 1 - tests/TestCase.php | 6 +- web/.vitepress/sidebar/v3.js | 12 +- web/docs/v3/douyin/all.md | 43 +++++++ web/docs/v3/douyin/cancel.md | 9 ++ web/docs/v3/douyin/close.md | 9 ++ web/docs/v3/douyin/pay.md | 4 +- web/docs/v3/douyin/query.md | 44 +++++++ web/docs/v3/douyin/refund.md | 28 +++++ web/docs/v3/douyin/response.md | 19 +++ web/docs/v3/quick-start/init.md | 2 +- web/docs/v3/wechat/refund.md | 2 +- 50 files changed, 862 insertions(+), 98 deletions(-) create mode 100644 src/Plugin/Douyin/V1/Pay/CallbackPlugin.php create mode 100644 src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php create mode 100644 src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php create mode 100644 src/Shortcut/Douyin/RefundShortcut.php create mode 100644 tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php create mode 100644 tests/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php create mode 100644 tests/Shortcut/Douyin/RefundShortcutTest.php create mode 100644 web/docs/v3/douyin/all.md create mode 100644 web/docs/v3/douyin/cancel.md create mode 100644 web/docs/v3/douyin/close.md create mode 100644 web/docs/v3/douyin/query.md create mode 100644 web/docs/v3/douyin/refund.md create mode 100644 web/docs/v3/douyin/response.md diff --git a/src/Functions.php b/src/Functions.php index dd804744e..7611eccd2 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -649,3 +649,29 @@ function get_douyin_url(array $config, ?Collection $payload): string return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url; } + +/** + * @throws InvalidConfigException + * @throws InvalidSignException + */ +function verify_douyin_sign(array $config, array $contents, string $sign): void +{ + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证抖音签名失败-抖音签名为空', func_get_args()); + } + + $contents['token'] = $config['mch_secret_token'] ?? null; + + if (empty($contents['token'])) { + throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_token]'); + } + + sort($contents, SORT_STRING); + $data = trim(implode('', $contents)); + + $result = $sign === sha1($data); + + if (!$result) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证抖音签名失败', func_get_args()); + } +} diff --git a/src/Plugin/Alipay/V2/CallbackPlugin.php b/src/Plugin/Alipay/V2/CallbackPlugin.php index d0bbc7c4d..cb6c8504a 100644 --- a/src/Plugin/Alipay/V2/CallbackPlugin.php +++ b/src/Plugin/Alipay/V2/CallbackPlugin.php @@ -13,7 +13,6 @@ use Yansongda\Artful\Logger; use Yansongda\Artful\Rocket; use Yansongda\Pay\Exception\InvalidSignException; -use Yansongda\Supports\Collection; use function Yansongda\Artful\filter_params; use function Yansongda\Pay\get_provider_config; @@ -36,7 +35,7 @@ public function assembly(Rocket $rocket, Closure $next): Rocket $value = filter_params($params, fn ($k, $v) => '' !== $v && 'sign' != $k && 'sign_type' != $k); - verify_alipay_sign($config, Collection::wrap($value)->sortKeys()->toString(), $params['sign'] ?? ''); + verify_alipay_sign($config, $value->sortKeys()->toString(), $params['sign'] ?? ''); $rocket->setPayload($params) ->setDirection(NoHttpRequestDirection::class) diff --git a/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php b/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php index 4be463a56..b7a020c9a 100644 --- a/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php +++ b/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php @@ -27,7 +27,7 @@ class AddRadarPlugin implements PluginInterface */ public function assembly(Rocket $rocket, Closure $next): Rocket { - Logger::debug('[Douyin][AddRadarPlugin] 插件开始装载', ['rocket' => $rocket]); + Logger::debug('[Douyin][V1][Pay][AddRadarPlugin] 插件开始装载', ['rocket' => $rocket]); $params = $rocket->getParams(); $payload = $rocket->getPayload(); @@ -40,7 +40,7 @@ public function assembly(Rocket $rocket, Closure $next): Rocket get_radar_body($payload), )); - Logger::info('[Douyin][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + Logger::info('[Douyin][V1][Pay][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); return $next($rocket); } diff --git a/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php new file mode 100644 index 000000000..6e36a6b00 --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php @@ -0,0 +1,48 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + $value = filter_params($params, fn ($k, $v) => '' !== $v && 'msg_signature' != $k && 'type' != $k); + + verify_douyin_sign($config, $value->all(), $params['msg_signature'] ?? ''); + + $rocket->setPayload($params) + ->setDirection(NoHttpRequestDirection::class) + ->setDestination($rocket->getPayload()); + + Logger::info('[Douyin][V1][Pay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php new file mode 100644 index 000000000..8f15e3a9a --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php @@ -0,0 +1,66 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序查询退款订单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_refund', + 'app_id' => $config['mini_app_id'] ?? '', + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + ]; + } +} diff --git a/src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php new file mode 100644 index 000000000..718646168 --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php @@ -0,0 +1,72 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序退款订单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_refund', + 'app_id' => $config['mini_app_id'] ?? '', + 'notify_url' => $payload->get('notify_url') ?? $this->getNotifyUrl($config), + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + ]; + } + + protected function getNotifyUrl(array $config): ?string + { + return empty($config['notify_url']) ? null : $config['notify_url']; + } +} diff --git a/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php b/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php index bdbcf28fe..df4939a6a 100644 --- a/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php +++ b/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php @@ -11,7 +11,6 @@ use Yansongda\Artful\Logger; use Yansongda\Artful\Rocket; use Yansongda\Pay\Exception\Exception; -use Yansongda\Supports\Collection; class ResponsePlugin implements PluginInterface { @@ -23,11 +22,11 @@ public function assembly(Rocket $rocket, Closure $next): Rocket /* @var Rocket $rocket */ $rocket = $next($rocket); - Logger::debug('[Douyin][ResponsePlugin] 插件开始装载', ['rocket' => $rocket]); + Logger::debug('[Douyin][V1][Pay][ResponsePlugin] 插件开始装载', ['rocket' => $rocket]); - $rocket->setDestination(new Collection($this->validateResponse($rocket))); + $this->validateResponse($rocket); - Logger::info('[Douyin][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + Logger::info('[Douyin][V1][Pay][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); return $rocket; } @@ -35,7 +34,7 @@ public function assembly(Rocket $rocket, Closure $next): Rocket /** * @throws InvalidResponseException */ - protected function validateResponse(Rocket $rocket): array + protected function validateResponse(Rocket $rocket): void { $destination = $rocket->getDestination(); $response = $rocket->getDestinationOrigin(); @@ -48,7 +47,5 @@ protected function validateResponse(Rocket $rocket): array if (0 !== $destination->get('err_no')) { throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, '抖音返回业务异常: '.$destination->get('err_tips'), $destination); } - - return $destination->get('data', []); } } diff --git a/src/Provider/Douyin.php b/src/Provider/Douyin.php index edd7dd810..1d166d213 100644 --- a/src/Provider/Douyin.php +++ b/src/Provider/Douyin.php @@ -25,6 +25,7 @@ use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Pay; use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\CallbackPlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin; use Yansongda\Supports\Collection; use Yansongda\Supports\Str; @@ -78,7 +79,7 @@ public function query(array $order): Collection|Rocket */ public function cancel(array $order): Collection|Rocket { - throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 微信不支持 cancel API'); + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 抖音不支持 cancel API'); } /** @@ -115,12 +116,9 @@ public function callback(null|array|ServerRequestInterface $contents = null, ?ar { $request = $this->getCallbackParams($contents); - Event::dispatch(new CallbackReceived('douyin', clone $request, $params, null)); + Event::dispatch(new CallbackReceived('douyin', $request->all(), $params, null)); - return $this->pay( - [CallbackPlugin::class], - ['_request' => $request, '_params' => $params] - ); + return $this->pay([CallbackPlugin::class], $request->merge($params)->all()); } public function success(): ResponseInterface @@ -128,7 +126,7 @@ public function success(): ResponseInterface return new Response( 200, ['Content-Type' => 'application/json'], - json_encode(['code' => 'SUCCESS', 'message' => '成功']), + json_encode(['err_no' => 0, 'err_tips' => 'success']), ); } @@ -141,20 +139,22 @@ public function mergeCommonPlugins(array $plugins): array ); } - protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): ServerRequestInterface + protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): Collection { - if (is_array($contents) && isset($contents['body'], $contents['headers'])) { - return new ServerRequest('POST', 'http://localhost', $contents['headers'], $contents['body']); + if (is_array($contents)) { + return Collection::wrap($contents); } - if (is_array($contents)) { - return new ServerRequest('POST', 'http://localhost', [], json_encode($contents)); + if (!$contents instanceof ServerRequestInterface) { + $contents = ServerRequest::fromGlobals(); } - if ($contents instanceof ServerRequestInterface) { - return $contents; + $body = Collection::wrap($contents->getParsedBody()); + + if ($body->isNotEmpty()) { + return $body; } - return ServerRequest::fromGlobals(); + return Collection::wrapJson((string) $contents->getBody()); } } diff --git a/src/Shortcut/Alipay/CancelShortcut.php b/src/Shortcut/Alipay/CancelShortcut.php index 3a68e289e..acec946cc 100644 --- a/src/Shortcut/Alipay/CancelShortcut.php +++ b/src/Shortcut/Alipay/CancelShortcut.php @@ -34,7 +34,7 @@ public function getPlugins(array $params): array return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Cancel action [{$method}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Alipay/CloseShortcut.php b/src/Shortcut/Alipay/CloseShortcut.php index 984ca9669..016d8495a 100644 --- a/src/Shortcut/Alipay/CloseShortcut.php +++ b/src/Shortcut/Alipay/CloseShortcut.php @@ -37,7 +37,7 @@ public function getPlugins(array $params): array return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Close action [{$method}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Alipay/QueryShortcut.php b/src/Shortcut/Alipay/QueryShortcut.php index e58574b82..27ecfcfe5 100644 --- a/src/Shortcut/Alipay/QueryShortcut.php +++ b/src/Shortcut/Alipay/QueryShortcut.php @@ -50,7 +50,7 @@ public function getPlugins(array $params): array return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$method}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Alipay/RefundShortcut.php b/src/Shortcut/Alipay/RefundShortcut.php index cf3e70b5d..bf3046122 100644 --- a/src/Shortcut/Alipay/RefundShortcut.php +++ b/src/Shortcut/Alipay/RefundShortcut.php @@ -38,7 +38,7 @@ public function getPlugins(array $params): array return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Refund action [{$method}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Douyin/QueryShortcut.php b/src/Shortcut/Douyin/QueryShortcut.php index 744fc6104..c023e2f0a 100644 --- a/src/Shortcut/Douyin/QueryShortcut.php +++ b/src/Shortcut/Douyin/QueryShortcut.php @@ -13,6 +13,7 @@ use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryPlugin as MiniQueryPlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryRefundPlugin as MiniQueryRefundPlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin; use Yansongda\Supports\Str; @@ -23,13 +24,13 @@ class QueryShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $action = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $action)) { - return $this->{$action}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$action}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array @@ -37,6 +38,11 @@ protected function defaultPlugins(): array return $this->miniPlugins(); } + protected function refundPlugins(): array + { + return $this->refundMiniPlugins(); + } + protected function miniPlugins(): array { return [ @@ -49,4 +55,17 @@ protected function miniPlugins(): array ParserPlugin::class, ]; } + + protected function refundMiniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } } diff --git a/src/Shortcut/Douyin/RefundShortcut.php b/src/Shortcut/Douyin/RefundShortcut.php new file mode 100644 index 000000000..6bbac50d5 --- /dev/null +++ b/src/Shortcut/Douyin/RefundShortcut.php @@ -0,0 +1,52 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->miniPlugins(); + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/src/Shortcut/Unipay/CancelShortcut.php b/src/Shortcut/Unipay/CancelShortcut.php index 40abc57db..6cc956979 100644 --- a/src/Shortcut/Unipay/CancelShortcut.php +++ b/src/Shortcut/Unipay/CancelShortcut.php @@ -28,13 +28,13 @@ class CancelShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $typeMethod)) { - return $this->{$typeMethod}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Cancel action [{$typeMethod}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Unipay/PosShortcut.php b/src/Shortcut/Unipay/PosShortcut.php index 2337957ad..0ecbc6486 100644 --- a/src/Shortcut/Unipay/PosShortcut.php +++ b/src/Shortcut/Unipay/PosShortcut.php @@ -28,13 +28,13 @@ class PosShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $typeMethod)) { - return $this->{$typeMethod}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Pos action [{$typeMethod}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Unipay/QueryShortcut.php b/src/Shortcut/Unipay/QueryShortcut.php index 1c1322d79..7f0b29e9c 100644 --- a/src/Shortcut/Unipay/QueryShortcut.php +++ b/src/Shortcut/Unipay/QueryShortcut.php @@ -29,13 +29,13 @@ class QueryShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $typeMethod)) { - return $this->{$typeMethod}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$typeMethod}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Unipay/RefundShortcut.php b/src/Shortcut/Unipay/RefundShortcut.php index 6852c5b6d..75396772e 100644 --- a/src/Shortcut/Unipay/RefundShortcut.php +++ b/src/Shortcut/Unipay/RefundShortcut.php @@ -28,13 +28,13 @@ class RefundShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $typeMethod)) { - return $this->{$typeMethod}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Refund action [{$typeMethod}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Unipay/ScanShortcut.php b/src/Shortcut/Unipay/ScanShortcut.php index e678f30d6..74cbf9eb4 100644 --- a/src/Shortcut/Unipay/ScanShortcut.php +++ b/src/Shortcut/Unipay/ScanShortcut.php @@ -26,13 +26,13 @@ class ScanShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $typeMethod)) { - return $this->{$typeMethod}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Scan action [{$typeMethod}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Wechat/CloseShortcut.php b/src/Shortcut/Wechat/CloseShortcut.php index 3d98da120..3b4a17881 100644 --- a/src/Shortcut/Wechat/CloseShortcut.php +++ b/src/Shortcut/Wechat/CloseShortcut.php @@ -33,13 +33,13 @@ public function getPlugins(array $params): array return $this->combinePlugins(); } - $action = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $action)) { - return $this->{$action}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Close action [{$action}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Wechat/PapayShortcut.php b/src/Shortcut/Wechat/PapayShortcut.php index eb0d34fe5..1e3eabe6a 100644 --- a/src/Shortcut/Wechat/PapayShortcut.php +++ b/src/Shortcut/Wechat/PapayShortcut.php @@ -28,13 +28,13 @@ class PapayShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $action = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $action)) { - return $this->{$action}($params); + if (method_exists($this, $method)) { + return $this->{$method}($params); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Papay action [{$action}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } /** diff --git a/src/Shortcut/Wechat/QueryShortcut.php b/src/Shortcut/Wechat/QueryShortcut.php index 7f632da2d..ff6f5a9ff 100644 --- a/src/Shortcut/Wechat/QueryShortcut.php +++ b/src/Shortcut/Wechat/QueryShortcut.php @@ -40,13 +40,13 @@ public function getPlugins(array $params): array return $this->combinePlugins(); } - $action = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $action)) { - return $this->{$action}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$action}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/src/Shortcut/Wechat/RefundShortcut.php b/src/Shortcut/Wechat/RefundShortcut.php index 34281df04..2aa9bed62 100644 --- a/src/Shortcut/Wechat/RefundShortcut.php +++ b/src/Shortcut/Wechat/RefundShortcut.php @@ -29,13 +29,13 @@ class RefundShortcut implements ShortcutInterface */ public function getPlugins(array $params): array { - $action = Str::camel($params['_action'] ?? 'default').'Plugins'; + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; - if (method_exists($this, $action)) { - return $this->{$action}(); + if (method_exists($this, $method)) { + return $this->{$method}(); } - throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Refund action [{$action}] not supported"); + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); } protected function defaultPlugins(): array diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php index 5fe38df43..e2d0d5f74 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -43,6 +43,7 @@ use function Yansongda\Pay\get_wechat_url; use function Yansongda\Pay\reload_wechat_public_certs; use function Yansongda\Pay\verify_alipay_sign; +use function Yansongda\Pay\verify_douyin_sign; use function Yansongda\Pay\verify_unipay_sign; use function Yansongda\Pay\verify_unipay_sign_qra; use function Yansongda\Pay\verify_wechat_sign; @@ -706,4 +707,18 @@ public function testGetDouyinUrl() self::expectExceptionCode(Exception::PARAMS_DOUYIN_URL_MISSING); get_douyin_url([], new Collection([])); } + + public function testVerifyDouyinSign() + { + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $body = json_decode($post, true); + + $contents = $body; + unset($contents['msg_signature'], $contents['type']); + + verify_douyin_sign(get_provider_config('douyin'), $contents, $body['msg_signature']); + + self::assertTrue(true); + } } diff --git a/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php b/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php new file mode 100644 index 000000000..b6778c269 --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php @@ -0,0 +1,34 @@ +plugin = new CallbackPlugin(); + } + + public function testNotifyCallbackIncludePlus() + { + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $rocket = new Rocket(); + $rocket->setParams(json_decode($post, true)); + + $result = $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + + self::assertNotEmpty($result->getPayload()->all()); + self::assertNotEmpty($result->getDestination()->all()); + } +} diff --git a/tests/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php new file mode 100644 index 000000000..59e6adb48 --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php @@ -0,0 +1,108 @@ +plugin = new RefundPlugin(); + } + + public function testEmptyPayload() + { + $rocket = new Rocket(); + + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); + self::expectExceptionMessage('参数异常: 抖音小程序退款订单,参数为空'); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + + public function testNormal() + { + $rocket = new Rocket(); + $rocket->setPayload(new Collection( [ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_refund', + 'app_id' => 'tt226e54d3bd581bf801', + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ], $result->getPayload()->all()); + } + + public function testServiceParams() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + 'thirdparty_id' => 'service_provider111', + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_refund', + 'app_id' => 'tt226e54d3bd581bf801', + 'thirdparty_id' => 'service_provider111', + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ], $result->getPayload()->all()); + } + + public function testService() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_refund', + 'app_id' => 'tt226e54d3bd581bf801', + 'thirdparty_id' => 'service_provider', + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ], $result->getPayload()->all()); + } +} diff --git a/tests/Plugin/Douyin/V1/Pay/ResponsePluginTest.php b/tests/Plugin/Douyin/V1/Pay/ResponsePluginTest.php index 9428ac034..26ffd0879 100644 --- a/tests/Plugin/Douyin/V1/Pay/ResponsePluginTest.php +++ b/tests/Plugin/Douyin/V1/Pay/ResponsePluginTest.php @@ -23,14 +23,16 @@ protected function setUp(): void public function testOriginalResponseDestination() { + $destination = ['err_no' => 0, 'err_tips' => 'ok', 'data' => ['foo' => 'bar']]; + $rocket = new Rocket(); $rocket->setDestinationOrigin(new Response()); - $rocket->setDestination(new Collection(['err_no' => 0, 'err_tips' => 'ok', 'data' => ['foo' => 'bar']])); + $rocket->setDestination(new Collection($destination)); $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); self::assertInstanceOf(Collection::class, $result->getDestination()); - self::assertEquals(['foo' => 'bar'], $result->getDestination()->all()); + self::assertEquals($destination, $result->getDestination()->all()); } public function testOriginalResponseCodeErrorDestination() diff --git a/tests/Provider/DouyinTest.php b/tests/Provider/DouyinTest.php index ad44e10f5..bb9e6b84b 100644 --- a/tests/Provider/DouyinTest.php +++ b/tests/Provider/DouyinTest.php @@ -75,8 +75,100 @@ public function testCallMini() $payload = $response->getPayload(); self::assertInstanceOf(Collection::class, $result); - self::assertEquals('7376826336364513572', $result->get('order_id')); - self::assertEquals('CgwIARDPKBjKMCABKAESTgpMTgGUG+Ms5klBoqYlsymcJWNMvgWCR8XH+9OO5vFPSl2zZcVKFX0sKRuG9zxMNlT43OJotxNNHaO4KLMbiqo6HYxMiRS5tkoeILFzexoA.W', $result->get('order_token')); + self::assertEquals('7376826336364513572', $result->get('data.order_id')); + self::assertEquals('CgwIARDPKBjKMCABKAESTgpMTgGUG+Ms5klBoqYlsymcJWNMvgWCR8XH+9OO5vFPSl2zZcVKFX0sKRuG9zxMNlT43OJotxNNHaO4KLMbiqo6HYxMiRS5tkoeILFzexoA.W', $result->get('data.order_token')); self::assertEquals('771c1952ffb5e0744fc0ad1337aafa6a', $payload->get('sign')); } + + public function testQuery() + { + $response = new Response( + 200, + [], + '{"err_no":0,"err_tips":"","out_order_no":"202408040747147327","order_id":"7398075047971440922","payment_info":{"total_fee":1,"order_status":"SUCCESS","pay_time":"2024-08-04 15:49:48","way":2,"channel_no":"","channel_gateway_no":"","seller_uid":"73744242495132490630","item_id":"","cp_extra":""}}', + ); + + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn($response); + Pay::set(HttpClientInterface::class, $http); + + $response = Pay::douyin()->query([ + 'out_order_no' => '202406100423024876', + '_return_rocket' => true, + ]); + + $result = $response->getDestination(); + $payload = $response->getPayload(); + + self::assertInstanceOf(Collection::class, $result); + self::assertEquals('7517fb55db55327c396e5b7c9cb1be31', $payload->get('sign')); + self::assertEquals('202408040747147327', $result->get('out_order_no')); + self::assertEquals('7398075047971440922', $result->get('order_id')); + self::assertEquals('SUCCESS', $result->get('payment_info.order_status')); + } + + public function testQueryRefund() + { + $response = new Response( + 200, + [], + '{"err_no":0,"err_tips":"success","refundInfo":{"refund_no":"7398108028894988571","refund_amount":1,"refund_status":"SUCCESS","refunded_at":1722762159,"is_all_settled":true,"cp_extra":""}}', + ); + + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn($response); + Pay::set(HttpClientInterface::class, $http); + + $response = Pay::douyin()->query([ + 'out_refund_no' => '202408040747147327', + '_action' => 'refund', + '_return_rocket' => true, + ]); + + $result = $response->getDestination(); + $payload = $response->getPayload(); + + self::assertInstanceOf(Collection::class, $result); + self::assertEquals('fa6511979b1185cf98df2538f63ee1a3', $payload->get('sign')); + self::assertEquals('7398108028894988571', $result->get('refundInfo.refund_no')); + } + + public function testRefund() + { + $response = new Response( + 200, + [], + '{"err_no":0,"err_tips":"受理成功","refund_no":"7398108028894988571"}', + ); + + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn($response); + Pay::set(HttpClientInterface::class, $http); + + $response = Pay::douyin()->refund([ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + '_return_rocket' => true, + ]); + + $result = $response->getDestination(); + $payload = $response->getPayload(); + + self::assertInstanceOf(Collection::class, $result); + self::assertEquals('32f9c840085091f5c84a346d87bd2b4e', $payload->get('sign')); + self::assertEquals('7398108028894988571', $result->get('refund_no')); + } + + public function testCallback() + { + $callback = Pay::douyin()->callback(json_decode( + '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}', + true + )); + + self::assertInstanceOf(Collection::class, $callback); + self::assertNotEmpty($callback->all()); + } } diff --git a/tests/Shortcut/Douyin/QueryShortcutTest.php b/tests/Shortcut/Douyin/QueryShortcutTest.php index 3d582c443..96edaeec1 100644 --- a/tests/Shortcut/Douyin/QueryShortcutTest.php +++ b/tests/Shortcut/Douyin/QueryShortcutTest.php @@ -12,6 +12,7 @@ use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryPlugin as MiniQueryPlugin; +use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryRefundPlugin as MiniQueryRefundPlugin; use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin; use Yansongda\Pay\Shortcut\Douyin\QueryShortcut; use Yansongda\Pay\Tests\TestCase; @@ -31,7 +32,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Query action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } @@ -49,6 +49,19 @@ public function testDefault() ], $this->plugin->getPlugins([])); } + public function testRefund() + { + self::assertEquals([ + StartPlugin::class, + MiniQueryRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins(['_action' => 'refund'])); + } + public function testMini() { self::assertEquals([ @@ -61,4 +74,17 @@ public function testMini() ParserPlugin::class, ], $this->plugin->getPlugins(['_action' => 'mini'])); } + + public function testRefundMini() + { + self::assertEquals([ + StartPlugin::class, + MiniQueryRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins(['_action' => 'refund_mini'])); + } } diff --git a/tests/Shortcut/Douyin/RefundShortcutTest.php b/tests/Shortcut/Douyin/RefundShortcutTest.php new file mode 100644 index 000000000..7db9bde7b --- /dev/null +++ b/tests/Shortcut/Douyin/RefundShortcutTest.php @@ -0,0 +1,63 @@ +plugin = new RefundShortcut(); + } + + public function testFoo() + { + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); + + $this->plugin->getPlugins(['_action' => 'foo']); + } + + public function testDefault() + { + self::assertEquals([ + StartPlugin::class, + MiniRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins([])); + } + + public function testMini() + { + self::assertEquals([ + StartPlugin::class, + MiniRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins(['_action' => 'mini'])); + } +} diff --git a/tests/Shortcut/Unipay/CancelShortcutTest.php b/tests/Shortcut/Unipay/CancelShortcutTest.php index e2aafc81e..38d003f56 100644 --- a/tests/Shortcut/Unipay/CancelShortcutTest.php +++ b/tests/Shortcut/Unipay/CancelShortcutTest.php @@ -75,7 +75,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Cancel action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Unipay/PosShortcutTest.php b/tests/Shortcut/Unipay/PosShortcutTest.php index 86f9585b7..d48306a57 100644 --- a/tests/Shortcut/Unipay/PosShortcutTest.php +++ b/tests/Shortcut/Unipay/PosShortcutTest.php @@ -75,7 +75,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Pos action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Unipay/QueryShortcutTest.php b/tests/Shortcut/Unipay/QueryShortcutTest.php index 6f8d4e95d..b242046d6 100644 --- a/tests/Shortcut/Unipay/QueryShortcutTest.php +++ b/tests/Shortcut/Unipay/QueryShortcutTest.php @@ -89,7 +89,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Query action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Unipay/RefundShortcutTest.php b/tests/Shortcut/Unipay/RefundShortcutTest.php index e47ac806b..adc5f28fc 100644 --- a/tests/Shortcut/Unipay/RefundShortcutTest.php +++ b/tests/Shortcut/Unipay/RefundShortcutTest.php @@ -7,6 +7,7 @@ use Yansongda\Artful\Exception\InvalidParamsException; use Yansongda\Artful\Plugin\AddPayloadBodyPlugin; use Yansongda\Artful\Plugin\ParserPlugin; +use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Plugin\Unipay\AddRadarPlugin; use Yansongda\Pay\Plugin\Unipay\Open\AddPayloadSignaturePlugin; use Yansongda\Pay\Plugin\Unipay\Open\Pay\QrCode\RefundPlugin as QrCodeRefundPlugin; @@ -72,8 +73,8 @@ public function testQraPos() public function testFoo() { - $this->expectException(InvalidParamsException::class); - $this->expectExceptionMessage('Refund action [fooPlugins] not supported'); + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Unipay/ScanShortcutTest.php b/tests/Shortcut/Unipay/ScanShortcutTest.php index 69721f736..8725ad5f1 100644 --- a/tests/Shortcut/Unipay/ScanShortcutTest.php +++ b/tests/Shortcut/Unipay/ScanShortcutTest.php @@ -86,7 +86,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Scan action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Wechat/CloseShortcutTest.php b/tests/Shortcut/Wechat/CloseShortcutTest.php index 809b74f06..0ac07228e 100644 --- a/tests/Shortcut/Wechat/CloseShortcutTest.php +++ b/tests/Shortcut/Wechat/CloseShortcutTest.php @@ -86,7 +86,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Close action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Wechat/PapayShortcutTest.php b/tests/Shortcut/Wechat/PapayShortcutTest.php index e5515046c..61a395859 100644 --- a/tests/Shortcut/Wechat/PapayShortcutTest.php +++ b/tests/Shortcut/Wechat/PapayShortcutTest.php @@ -123,7 +123,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Papay action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Wechat/QueryShortcutTest.php b/tests/Shortcut/Wechat/QueryShortcutTest.php index 0bb78709b..0fcb05b24 100644 --- a/tests/Shortcut/Wechat/QueryShortcutTest.php +++ b/tests/Shortcut/Wechat/QueryShortcutTest.php @@ -179,7 +179,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Query action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/Shortcut/Wechat/RefundShortcutTest.php b/tests/Shortcut/Wechat/RefundShortcutTest.php index 316e725c6..2233fdcb6 100644 --- a/tests/Shortcut/Wechat/RefundShortcutTest.php +++ b/tests/Shortcut/Wechat/RefundShortcutTest.php @@ -136,7 +136,6 @@ public function testFoo() { self::expectException(InvalidParamsException::class); self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID); - self::expectExceptionMessage('Refund action [fooPlugins] not supported'); $this->plugin->getPlugins(['_action' => 'foo']); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 9b1e27961..91ca59558 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -178,7 +178,7 @@ protected function setUp(): void 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mch_secret_key' => 'douyin_mini_token', + 'mch_secret_token' => 'douyin_mini_token', // 必填-支付 SALT,用于支付签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', @@ -196,7 +196,7 @@ protected function setUp(): void 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mch_secret_key' => 'douyin_mini_token', + 'mch_secret_token' => 'douyin_mini_token', // 必填-支付 SALT,用于支付签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', @@ -214,7 +214,7 @@ protected function setUp(): void 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mch_secret_key' => 'douyin_mini_token', + 'mch_secret_token' => 'douyin_mini_token', // 必填-支付 SALT,用于支付签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT 'mch_secret_salt' => '', diff --git a/web/.vitepress/sidebar/v3.js b/web/.vitepress/sidebar/v3.js index c8080f58b..476e72f77 100644 --- a/web/.vitepress/sidebar/v3.js +++ b/web/.vitepress/sidebar/v3.js @@ -59,12 +59,12 @@ export default [ items: [ { text: '支付', link: '/docs/v3/douyin/pay' }, { text: '查询', link: '/docs/v3/douyin/query' }, - // { text: '退款', link: '/docs/v3/wechat/refund' }, - // { text: '关闭', link: '/docs/v3/wechat/close' }, - // { text: '取消', link: '/docs/v3/wechat/cancel' }, - // { text: '接收回调', link: '/docs/v3/wechat/callback' }, - // { text: '确认回调', link: '/docs/v3/wechat/response' }, - // { text: '所有内置插件', link: '/docs/v3/wechat/all' } + { text: '退款', link: '/docs/v3/douyin/refund' }, + { text: '关闭', link: '/docs/v3/douyin/close' }, + { text: '取消', link: '/docs/v3/douyin/cancel' }, + // { text: '接收回调', link: '/docs/v3/douyin/callback' }, + { text: '确认回调', link: '/docs/v3/douyin/response' }, + { text: '所有内置插件', link: '/docs/v3/douyin/all' } ] }, { diff --git a/web/docs/v3/douyin/all.md b/web/docs/v3/douyin/all.md new file mode 100644 index 000000000..dd1c78074 --- /dev/null +++ b/web/docs/v3/douyin/all.md @@ -0,0 +1,43 @@ +# 抖音更多方便的插件 + +得益于 yansongda/pay 的基础架构和良好的插件机制, +您可以自由的使用任何内置插件和自定义插件调用微信的任何 API。 + +诸如签名、API调用、解密、验签、解包等基础插件已经内置在 Pay 中, +您可以使用 `Pay::douyin()->mergeCommonPlugins(array $plugins)` 来获取调用 API 所必须的常用插件 + +首先,查找你想使用的插件,然后 + +```php +Pay::config($config); + +$params = [ + 'out_trade_no' => '202408040747147327', +]; + +$allPlugins = Pay::douyin()->mergeCommonPlugins([QueryPlugin::class]); + +$result = Pay::douyin()->pay($allPlugins, $params); +``` + +关于插件的详细介绍,如果您感兴趣,可以参考 [yansongda/artful](https://artful.yansongda.cn/) + +## 支付产品 + +### 小程序支付 + +- 小程序下单 + + `\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin` + +- 商户订单号查询订单 + + `\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryPlugin` + +- 退款申请 + + `\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\RefundPlugin` + +- 查询单笔退款(通过商户退款单号) + + `\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryRefundPlugin` diff --git a/web/docs/v3/douyin/cancel.md b/web/docs/v3/douyin/cancel.md new file mode 100644 index 000000000..49b411252 --- /dev/null +++ b/web/docs/v3/douyin/cancel.md @@ -0,0 +1,9 @@ +# 抖音取消订单 + +:::danger +抖音官方无此 API,如有退款需求,可使用 `refund` 方法。 +::: + +## 异常 + +Yansongda\Pay\Exceptions\InvalidParamsException diff --git a/web/docs/v3/douyin/close.md b/web/docs/v3/douyin/close.md new file mode 100644 index 000000000..0f0f5f454 --- /dev/null +++ b/web/docs/v3/douyin/close.md @@ -0,0 +1,9 @@ +# 抖音关闭订单 + +:::danger +抖音官方无此 API,如有退款需求,可使用 `refund` 方法。 +::: + +## 异常 + +Yansongda\Pay\Exceptions\InvalidParamsException diff --git a/web/docs/v3/douyin/pay.md b/web/docs/v3/douyin/pay.md index 0552a6321..6e85b77c1 100644 --- a/web/docs/v3/douyin/pay.md +++ b/web/docs/v3/douyin/pay.md @@ -14,7 +14,7 @@ Pay::config($config); $order = [ - 'out_order_no' => date('YmdHis') . rand(1000, 9999), + 'out_order_no' => date('YmdHis').rand(1000, 9999), 'total_amount' => 1, 'subject' => '闫嵩达 - test - subject - 01', 'body' => '闫嵩达 - test - body - 01', @@ -22,7 +22,7 @@ $order = [ ]; $result = Pay::douyin()->mini($order); -// 可直接通过 $result->order_id, $result->order_token 获取相关值。 +// 可直接通过 $result->data['order_id'], $result->['order_token'] 获取相关值。 // 后续调用不在本文档讨论范围内,请自行参考官方文档。 ``` diff --git a/web/docs/v3/douyin/query.md b/web/docs/v3/douyin/query.md new file mode 100644 index 000000000..0bef1bb6d --- /dev/null +++ b/web/docs/v3/douyin/query.md @@ -0,0 +1,44 @@ +# 抖音查询订单 + +| 方法名 | 参数 | 返回值 | +|:-----:|:------------:|:----------:| +| query | array $order | Collection | + +## 查询支付订单 + +```php +Pay::config($config); + +$order = [ + 'out_trade_no' => '202408040747147327', + // '_action' => 'mini', // 查询小程序支付,默认 +]; + +$result = Pay::douyin()->query($order); +``` + +### 订单配置参数 + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考以下 API 查看「请求参数」一栏。 + +- [小程序订单](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/query) + +## 查询退款订单 + +```php +Pay::config($config); + +$order = [ + 'transaction_id' => '1217752501201407033233368018', + '_action' => 'refund', + // '_action' => 'refund_mini', // 查询小程序退款订单,refund action 默认 +]; + +$result = Pay::douyin()->query($order); +``` + +### 订单配置参数 + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考以下 API 查看「请求参数」一栏。 + +- [小程序订单](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/refund-list/query) diff --git a/web/docs/v3/douyin/refund.md b/web/docs/v3/douyin/refund.md new file mode 100644 index 000000000..d42725de5 --- /dev/null +++ b/web/docs/v3/douyin/refund.md @@ -0,0 +1,28 @@ +# 抖音退款 + +| 方法名 | 参数 | 返回值 | +|:------:|:------------:|:----------:| +| refund | array $order | Collection | + +## 退款操作 + +```php +Pay::config($config); + +$order = [ + 'out_order_no' => '202408040747147327', + 'out_refund_no' => '202408040747147327', + 'reason' => '测试', + 'refund_amount' => 1, + // '_action' => 'mini', // 小程序退款,默认 + +]; + +$result = Pay::douyin()->refund($order); +``` + +### 订单配置参数 + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考以下 API 查看「请求参数」一栏。 + +- [小程序订单](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/refund-list/refund) diff --git a/web/docs/v3/douyin/response.md b/web/docs/v3/douyin/response.md new file mode 100644 index 000000000..d2507c5e0 --- /dev/null +++ b/web/docs/v3/douyin/response.md @@ -0,0 +1,19 @@ +# 抖音确认回调 + +| 方法名 | 参数 | 返回值 | +|:-------:|:---:|:--------:| +| success | 无 | Response | + +## 例子 + +```php +Pay::config($config); + +// $result = Pay::douyin()->callback(); + +return Pay::douyin()->success(); +``` + +## 订单配置参数 + +无 diff --git a/web/docs/v3/quick-start/init.md b/web/docs/v3/quick-start/init.md index b7bbdc961..27e4e9d4b 100644 --- a/web/docs/v3/quick-start/init.md +++ b/web/docs/v3/quick-start/init.md @@ -103,7 +103,7 @@ $config = [ 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mch_secret_key' => 'douyin_mini_token', + 'mch_secret_token' => 'douyin_mini_token', // 必填-支付 SALT,用于支付签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', diff --git a/web/docs/v3/wechat/refund.md b/web/docs/v3/wechat/refund.md index b5898aedb..45b4120df 100644 --- a/web/docs/v3/wechat/refund.md +++ b/web/docs/v3/wechat/refund.md @@ -21,7 +21,7 @@ $order = [ // '_action' => 'app', // app 退款 // '_action' => 'combine', // 合单退款 // '_action' => 'h5', // h5 退款 - // '_action' => 'miniapp', // 小程序退款 + // '_action' => 'mini', // 小程序退款 // '_action' => 'native', // native 退款 ]; From 0c3e90c20b6241c0ba7cac42bb969ea611c470bd Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 21:57:21 +0800 Subject: [PATCH 13/19] update --- web/.vitepress/sidebar/v3.js | 2 +- web/docs/v3/douyin/callback.md | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 web/docs/v3/douyin/callback.md diff --git a/web/.vitepress/sidebar/v3.js b/web/.vitepress/sidebar/v3.js index 476e72f77..16b9132e0 100644 --- a/web/.vitepress/sidebar/v3.js +++ b/web/.vitepress/sidebar/v3.js @@ -62,7 +62,7 @@ export default [ { text: '退款', link: '/docs/v3/douyin/refund' }, { text: '关闭', link: '/docs/v3/douyin/close' }, { text: '取消', link: '/docs/v3/douyin/cancel' }, - // { text: '接收回调', link: '/docs/v3/douyin/callback' }, + { text: '接收回调', link: '/docs/v3/douyin/callback' }, { text: '确认回调', link: '/docs/v3/douyin/response' }, { text: '所有内置插件', link: '/docs/v3/douyin/all' } ] diff --git a/web/docs/v3/douyin/callback.md b/web/docs/v3/douyin/callback.md new file mode 100644 index 000000000..d5862eae3 --- /dev/null +++ b/web/docs/v3/douyin/callback.md @@ -0,0 +1,40 @@ +# 接收抖音回调 + +| 方法名 | 参数 | 返回值 | +|:--------:|:------------------------------:|:----------:| +| callback | 无/array/ServerRequestInterface | Collection | + +## 例子 + +```php +Pay::config($this->config); + +// 是的,你没有看错,就是这么简单! +$result = Pay::douyin()->callback(); +``` + +## 参数 + +### 第一个参数 + +#### `null` + +如果您没有传参,或传 `null` 则 `yansongda/pay` 会自动识别抖音的回调请求并处理,通过 `Collection` 实例返回抖音的处理参数 + +:::warning +建议仅在 php-fpm 下使用,swoole 方式请使用 `ServerRequestInterface` 参数传递方式 +::: + +#### `ServerRequestInterface` + +推荐在 swoole 环境下传递此参数,传递此参数后, yansongda/pay 会自动进行后续处理 + +#### `array` + +也可以自行解析请求参数,传递一个 array 会自动进行后续处理 + +### 第二个参数 + +第二个参数主要是传递相关自定义变量的,类似于 `web()` 中的 `_config` / `_method` 等参数。 + +例如,如果你想在回调的时候使用非默认配置,则可以 `Pay::douyin()->callback(null, ['_config' => 'yansongda'])` 切换为 `yansongda` 这个租户的配置信息。 From 875440f83024826083ce4ead093cd0957673bf29 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 22:00:08 +0800 Subject: [PATCH 14/19] update --- web/package.json | 2 +- web/pnpm-lock.yaml | 188 +++++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 91 deletions(-) diff --git a/web/package.json b/web/package.json index 12606acd5..3f24fb1b4 100644 --- a/web/package.json +++ b/web/package.json @@ -6,7 +6,7 @@ "web:serve": "vitepress serve" }, "devDependencies": { - "@types/node": "^20.14.13", + "@types/node": "^22.1.0", "fast-glob": "^3.3.2", "sass": "^1.77.8", "vite": "^5.3.5", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 311f55979..7202eb331 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@types/node': - specifier: ^20.14.13 - version: 20.14.13 + specifier: ^22.1.0 + version: 22.1.0 fast-glob: specifier: ^3.3.2 version: 3.3.2 @@ -19,10 +19,10 @@ importers: version: 1.77.8 vite: specifier: ^5.3.5 - version: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + version: 5.3.5(@types/node@22.1.0)(sass@1.77.8) vitepress: specifier: ^1.3.1 - version: 1.3.1(@algolia/client-search@4.24.0)(@types/node@20.14.13)(postcss@8.4.40)(sass@1.77.8)(search-insights@2.13.0) + version: 1.3.1(@algolia/client-search@4.24.0)(@types/node@22.1.0)(postcss@8.4.40)(sass@1.77.8)(search-insights@2.13.0) vue: specifier: ^3.4.35 version: 3.4.35 @@ -42,12 +42,18 @@ packages: peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' + peerDependenciesMeta: + '@algolia/client-search': + optional: true '@algolia/autocomplete-shared@1.9.3': resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' + peerDependenciesMeta: + '@algolia/client-search': + optional: true '@algolia/cache-browser-local-storage@4.24.0': resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} @@ -287,83 +293,83 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@rollup/rollup-android-arm-eabi@4.19.2': - resolution: {integrity: sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==} + '@rollup/rollup-android-arm-eabi@4.20.0': + resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.19.2': - resolution: {integrity: sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==} + '@rollup/rollup-android-arm64@4.20.0': + resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.19.2': - resolution: {integrity: sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==} + '@rollup/rollup-darwin-arm64@4.20.0': + resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.19.2': - resolution: {integrity: sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==} + '@rollup/rollup-darwin-x64@4.20.0': + resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.19.2': - resolution: {integrity: sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==} + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.19.2': - resolution: {integrity: sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==} + '@rollup/rollup-linux-arm-musleabihf@4.20.0': + resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.19.2': - resolution: {integrity: sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==} + '@rollup/rollup-linux-arm64-gnu@4.20.0': + resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.19.2': - resolution: {integrity: sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==} + '@rollup/rollup-linux-arm64-musl@4.20.0': + resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.19.2': - resolution: {integrity: sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==} + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.19.2': - resolution: {integrity: sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==} + '@rollup/rollup-linux-riscv64-gnu@4.20.0': + resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.19.2': - resolution: {integrity: sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==} + '@rollup/rollup-linux-s390x-gnu@4.20.0': + resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.19.2': - resolution: {integrity: sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==} + '@rollup/rollup-linux-x64-gnu@4.20.0': + resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.19.2': - resolution: {integrity: sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==} + '@rollup/rollup-linux-x64-musl@4.20.0': + resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.19.2': - resolution: {integrity: sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==} + '@rollup/rollup-win32-arm64-msvc@4.20.0': + resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.19.2': - resolution: {integrity: sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==} + '@rollup/rollup-win32-ia32-msvc@4.20.0': + resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.19.2': - resolution: {integrity: sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==} + '@rollup/rollup-win32-x64-msvc@4.20.0': + resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} cpu: [x64] os: [win32] @@ -388,8 +394,8 @@ packages: '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - '@types/node@20.14.13': - resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} + '@types/node@22.1.0': + resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} '@types/unist@3.0.2': resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} @@ -397,8 +403,8 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} - '@vitejs/plugin-vue@5.1.1': - resolution: {integrity: sha512-sDckXxlHpMsjRQbAH9WanangrfrblsOd3pNifePs+FOHjJg1jfWq5L/P0PsBRndEt3nmdUnmvieP8ULDeX5AvA==} + '@vitejs/plugin-vue@5.1.2': + resolution: {integrity: sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 @@ -642,8 +648,8 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.19.2: - resolution: {integrity: sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==} + rollup@4.20.0: + resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -684,8 +690,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.13.0: + resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} vite@5.3.5: resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} @@ -768,13 +774,15 @@ snapshots: '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': dependencies: '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) - '@algolia/client-search': 4.24.0 algoliasearch: 4.24.0 + optionalDependencies: + '@algolia/client-search': 4.24.0 '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': dependencies: - '@algolia/client-search': 4.24.0 algoliasearch: 4.24.0 + optionalDependencies: + '@algolia/client-search': 4.24.0 '@algolia/cache-browser-local-storage@4.24.0': dependencies: @@ -973,52 +981,52 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@rollup/rollup-android-arm-eabi@4.19.2': + '@rollup/rollup-android-arm-eabi@4.20.0': optional: true - '@rollup/rollup-android-arm64@4.19.2': + '@rollup/rollup-android-arm64@4.20.0': optional: true - '@rollup/rollup-darwin-arm64@4.19.2': + '@rollup/rollup-darwin-arm64@4.20.0': optional: true - '@rollup/rollup-darwin-x64@4.19.2': + '@rollup/rollup-darwin-x64@4.20.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.19.2': + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.19.2': + '@rollup/rollup-linux-arm-musleabihf@4.20.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.19.2': + '@rollup/rollup-linux-arm64-gnu@4.20.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.19.2': + '@rollup/rollup-linux-arm64-musl@4.20.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.19.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.19.2': + '@rollup/rollup-linux-riscv64-gnu@4.20.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.19.2': + '@rollup/rollup-linux-s390x-gnu@4.20.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.19.2': + '@rollup/rollup-linux-x64-gnu@4.20.0': optional: true - '@rollup/rollup-linux-x64-musl@4.19.2': + '@rollup/rollup-linux-x64-musl@4.20.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.19.2': + '@rollup/rollup-win32-arm64-msvc@4.20.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.19.2': + '@rollup/rollup-win32-ia32-msvc@4.20.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.19.2': + '@rollup/rollup-win32-x64-msvc@4.20.0': optional: true '@shikijs/core@1.12.1': @@ -1044,17 +1052,17 @@ snapshots: '@types/mdurl@2.0.0': {} - '@types/node@20.14.13': + '@types/node@22.1.0': dependencies: - undici-types: 5.26.5 + undici-types: 6.13.0 '@types/unist@3.0.2': {} '@types/web-bluetooth@0.0.20': {} - '@vitejs/plugin-vue@5.1.1(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))(vue@3.4.35)': + '@vitejs/plugin-vue@5.1.2(vite@5.3.5(@types/node@22.1.0)(sass@1.77.8))(vue@3.4.35)': dependencies: - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@22.1.0)(sass@1.77.8) vue: 3.4.35 '@vue/compiler-core@3.4.35': @@ -1328,26 +1336,26 @@ snapshots: rfdc@1.4.1: {} - rollup@4.19.2: + rollup@4.20.0: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.19.2 - '@rollup/rollup-android-arm64': 4.19.2 - '@rollup/rollup-darwin-arm64': 4.19.2 - '@rollup/rollup-darwin-x64': 4.19.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.19.2 - '@rollup/rollup-linux-arm-musleabihf': 4.19.2 - '@rollup/rollup-linux-arm64-gnu': 4.19.2 - '@rollup/rollup-linux-arm64-musl': 4.19.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.19.2 - '@rollup/rollup-linux-riscv64-gnu': 4.19.2 - '@rollup/rollup-linux-s390x-gnu': 4.19.2 - '@rollup/rollup-linux-x64-gnu': 4.19.2 - '@rollup/rollup-linux-x64-musl': 4.19.2 - '@rollup/rollup-win32-arm64-msvc': 4.19.2 - '@rollup/rollup-win32-ia32-msvc': 4.19.2 - '@rollup/rollup-win32-x64-msvc': 4.19.2 + '@rollup/rollup-android-arm-eabi': 4.20.0 + '@rollup/rollup-android-arm64': 4.20.0 + '@rollup/rollup-darwin-arm64': 4.20.0 + '@rollup/rollup-darwin-x64': 4.20.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 + '@rollup/rollup-linux-arm-musleabihf': 4.20.0 + '@rollup/rollup-linux-arm64-gnu': 4.20.0 + '@rollup/rollup-linux-arm64-musl': 4.20.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 + '@rollup/rollup-linux-riscv64-gnu': 4.20.0 + '@rollup/rollup-linux-s390x-gnu': 4.20.0 + '@rollup/rollup-linux-x64-gnu': 4.20.0 + '@rollup/rollup-linux-x64-musl': 4.20.0 + '@rollup/rollup-win32-arm64-msvc': 4.20.0 + '@rollup/rollup-win32-ia32-msvc': 4.20.0 + '@rollup/rollup-win32-x64-msvc': 4.20.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -1383,26 +1391,26 @@ snapshots: dependencies: is-number: 7.0.0 - undici-types@5.26.5: {} + undici-types@6.13.0: {} - vite@5.3.5(@types/node@20.14.13)(sass@1.77.8): + vite@5.3.5(@types/node@22.1.0)(sass@1.77.8): dependencies: esbuild: 0.21.5 postcss: 8.4.40 - rollup: 4.19.2 + rollup: 4.20.0 optionalDependencies: - '@types/node': 20.14.13 + '@types/node': 22.1.0 fsevents: 2.3.3 sass: 1.77.8 - vitepress@1.3.1(@algolia/client-search@4.24.0)(@types/node@20.14.13)(postcss@8.4.40)(sass@1.77.8)(search-insights@2.13.0): + vitepress@1.3.1(@algolia/client-search@4.24.0)(@types/node@22.1.0)(postcss@8.4.40)(sass@1.77.8)(search-insights@2.13.0): dependencies: '@docsearch/css': 3.6.1 '@docsearch/js': 3.6.1(@algolia/client-search@4.24.0)(search-insights@2.13.0) '@shikijs/core': 1.12.1 '@shikijs/transformers': 1.12.1 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.1.1(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))(vue@3.4.35) + '@vitejs/plugin-vue': 5.1.2(vite@5.3.5(@types/node@22.1.0)(sass@1.77.8))(vue@3.4.35) '@vue/devtools-api': 7.3.7 '@vue/shared': 3.4.35 '@vueuse/core': 10.11.0(vue@3.4.35) @@ -1411,7 +1419,7 @@ snapshots: mark.js: 8.11.1 minisearch: 7.1.0 shiki: 1.12.1 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@22.1.0)(sass@1.77.8) vue: 3.4.35 optionalDependencies: postcss: 8.4.40 From 9e1d494faacb147c1d29983a18d92ab318bb5b52 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 22:15:44 +0800 Subject: [PATCH 15/19] update --- src/Provider/Douyin.php | 8 +- tests/FunctionTest.php | 32 +++++++ .../V1/Pay/Mini/QueryRefundPluginTest.php | 87 +++++++++++++++++++ tests/Provider/DouyinTest.php | 38 +++++++- 4 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 tests/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php diff --git a/src/Provider/Douyin.php b/src/Provider/Douyin.php index 1d166d213..dfe77c1c0 100644 --- a/src/Provider/Douyin.php +++ b/src/Provider/Douyin.php @@ -83,17 +83,11 @@ public function cancel(array $order): Collection|Rocket } /** - * @throws ContainerException * @throws InvalidParamsException - * @throws ServiceNotFoundException */ public function close(array $order): Collection|Rocket { - Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null)); - - $this->__call('close', [$order]); - - return new Collection(); + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 抖音不支持 close API'); } /** diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php index e2d0d5f74..47840fccf 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -720,5 +720,37 @@ public function testVerifyDouyinSign() verify_douyin_sign(get_provider_config('douyin'), $contents, $body['msg_signature']); self::assertTrue(true); + + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_EMPTY); + verify_douyin_sign(get_provider_config('douyin'), [], ''); + } + + public function testVerifyDouyinSignError() + { + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $body = json_decode($post, true); + + $contents = $body; + unset($contents['msg_signature'], $contents['type']); + + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_ERROR); + verify_douyin_sign(get_provider_config('douyin'), $contents, 'foo'); + } + + public function testVerifyDouyinSignConfigError() + { + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $body = json_decode($post, true); + + $contents = $body; + unset($contents['msg_signature'], $contents['type']); + + self::expectException(InvalidConfigException::class); + self::expectExceptionCode(Exception::CONFIG_DOUYIN_INVALID); + verify_douyin_sign([], $contents, 'foo'); } } diff --git a/tests/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php new file mode 100644 index 000000000..65cf7cd2a --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php @@ -0,0 +1,87 @@ +plugin = new QueryRefundPlugin(); + } + + public function testEmptyPayload() + { + $rocket = new Rocket(); + + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); + self::expectExceptionMessage('参数异常: 抖音小程序查询退款订单,参数为空'); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + + public function testNormal() + { + $rocket = new Rocket(); + $rocket->setPayload(new Collection( [ + "out_order_no" => "yansongda", + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + "out_order_no" => "yansongda", + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_refund', + 'app_id' => 'tt226e54d3bd581bf801', + ], $result->getPayload()->all()); + } + + public function testServiceParams() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'out_order_no' => 'yansongda', + 'thirdparty_id' => 'service_provider111', + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'out_order_no' => 'yansongda', + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_refund', + 'app_id' => 'tt226e54d3bd581bf801', + 'thirdparty_id' => 'service_provider111' + ], $result->getPayload()->all()); + } + + public function testService() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'out_order_no' => 'yansongda', + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals([ + 'out_order_no' => 'yansongda', + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_refund', + 'app_id' => 'tt226e54d3bd581bf801', + 'thirdparty_id' => 'service_provider' + ], $result->getPayload()->all()); + } +} diff --git a/tests/Provider/DouyinTest.php b/tests/Provider/DouyinTest.php index bb9e6b84b..ed06e30c7 100644 --- a/tests/Provider/DouyinTest.php +++ b/tests/Provider/DouyinTest.php @@ -4,7 +4,9 @@ use GuzzleHttp\Client; use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; use Mockery; +use Psr\Http\Message\ResponseInterface; use Yansongda\Artful\Contract\HttpClientInterface; use Yansongda\Artful\Exception\Exception; use Yansongda\Artful\Exception\InvalidParamsException; @@ -80,6 +82,22 @@ public function testCallMini() self::assertEquals('771c1952ffb5e0744fc0ad1337aafa6a', $payload->get('sign')); } + public function testClose() + { + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(\Yansongda\Pay\Exception\Exception::PARAMS_METHOD_NOT_SUPPORTED); + + Pay::douyin()->close([]); + } + + public function testCancel() + { + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(\Yansongda\Pay\Exception\Exception::PARAMS_METHOD_NOT_SUPPORTED); + + Pay::douyin()->cancel([]); + } + public function testQuery() { $response = new Response( @@ -163,12 +181,24 @@ public function testRefund() public function testCallback() { - $callback = Pay::douyin()->callback(json_decode( - '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}', - true - )); + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $callback = Pay::douyin()->callback(json_decode($post, true)); + self::assertInstanceOf(Collection::class, $callback); + self::assertNotEmpty($callback->all()); + + $request = new ServerRequest('POST', 'https://yansongda.cn/unipay/notify', [], $post); + $callback = Pay::douyin()->callback($request); self::assertInstanceOf(Collection::class, $callback); self::assertNotEmpty($callback->all()); } + + public function testSuccess() + { + $result = Pay::douyin()->success(); + + self::assertInstanceOf(ResponseInterface::class, $result); + self::assertStringContainsString('success', (string) $result->getBody()); + } } From 4232b643848360530a2eef76758c59721017c6d1 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 22:57:20 +0800 Subject: [PATCH 16/19] update --- CHANGELOG.md | 6 +++ src/Functions.php | 26 ---------- src/Plugin/Douyin/V1/Pay/CallbackPlugin.php | 29 +++++++++++- tests/FunctionTest.php | 46 ------------------ .../Douyin/V1/Pay/CallbackPluginTest.php | 47 ++++++++++++++++++- tests/TestCase.php | 2 +- 6 files changed, 81 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e892edd2f..fdbbc55d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v3.7.9 + +### added + +- feat: 新增抖音支付(#1014) + ## v3.7.8 ### added diff --git a/src/Functions.php b/src/Functions.php index 7611eccd2..dd804744e 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -649,29 +649,3 @@ function get_douyin_url(array $config, ?Collection $payload): string return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url; } - -/** - * @throws InvalidConfigException - * @throws InvalidSignException - */ -function verify_douyin_sign(array $config, array $contents, string $sign): void -{ - if (empty($sign)) { - throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证抖音签名失败-抖音签名为空', func_get_args()); - } - - $contents['token'] = $config['mch_secret_token'] ?? null; - - if (empty($contents['token'])) { - throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_token]'); - } - - sort($contents, SORT_STRING); - $data = trim(implode('', $contents)); - - $result = $sign === sha1($data); - - if (!$result) { - throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证抖音签名失败', func_get_args()); - } -} diff --git a/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php index 6e36a6b00..2bb0d1372 100644 --- a/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php +++ b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php @@ -12,6 +12,7 @@ use Yansongda\Artful\Exception\ServiceNotFoundException; use Yansongda\Artful\Logger; use Yansongda\Artful\Rocket; +use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Exception\InvalidSignException; use function Yansongda\Artful\filter_params; @@ -35,7 +36,7 @@ public function assembly(Rocket $rocket, Closure $next): Rocket $value = filter_params($params, fn ($k, $v) => '' !== $v && 'msg_signature' != $k && 'type' != $k); - verify_douyin_sign($config, $value->all(), $params['msg_signature'] ?? ''); + $this->verifySign($config, $value->all(), $params['msg_signature'] ?? ''); $rocket->setPayload($params) ->setDirection(NoHttpRequestDirection::class) @@ -45,4 +46,30 @@ public function assembly(Rocket $rocket, Closure $next): Rocket return $next($rocket); } + + /** + * @throws InvalidConfigException + * @throws InvalidSignException + */ + protected function verifySign(array $config, array $contents, string $sign): void + { + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证抖音签名失败-抖音签名为空', func_get_args()); + } + + $contents['token'] = $config['mch_secret_token'] ?? null; + + if (empty($contents['token'])) { + throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_token]'); + } + + sort($contents, SORT_STRING); + $data = trim(implode('', $contents)); + + $result = $sign === sha1($data); + + if (!$result) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证抖音签名失败', func_get_args()); + } + } } diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php index 47840fccf..d694bc788 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -707,50 +707,4 @@ public function testGetDouyinUrl() self::expectExceptionCode(Exception::PARAMS_DOUYIN_URL_MISSING); get_douyin_url([], new Collection([])); } - - public function testVerifyDouyinSign() - { - $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; - - $body = json_decode($post, true); - - $contents = $body; - unset($contents['msg_signature'], $contents['type']); - - verify_douyin_sign(get_provider_config('douyin'), $contents, $body['msg_signature']); - - self::assertTrue(true); - - self::expectException(InvalidSignException::class); - self::expectExceptionCode(Exception::SIGN_EMPTY); - verify_douyin_sign(get_provider_config('douyin'), [], ''); - } - - public function testVerifyDouyinSignError() - { - $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; - - $body = json_decode($post, true); - - $contents = $body; - unset($contents['msg_signature'], $contents['type']); - - self::expectException(InvalidSignException::class); - self::expectExceptionCode(Exception::SIGN_ERROR); - verify_douyin_sign(get_provider_config('douyin'), $contents, 'foo'); - } - - public function testVerifyDouyinSignConfigError() - { - $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; - - $body = json_decode($post, true); - - $contents = $body; - unset($contents['msg_signature'], $contents['type']); - - self::expectException(InvalidConfigException::class); - self::expectExceptionCode(Exception::CONFIG_DOUYIN_INVALID); - verify_douyin_sign([], $contents, 'foo'); - } } diff --git a/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php b/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php index b6778c269..ff4aaad4b 100644 --- a/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php +++ b/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php @@ -4,7 +4,10 @@ namespace Plugin\Douyin\V1\Pay; +use Yansongda\Artful\Exception\InvalidConfigException; use Yansongda\Artful\Rocket; +use Yansongda\Pay\Exception\Exception; +use Yansongda\Pay\Exception\InvalidSignException; use Yansongda\Pay\Plugin\Douyin\V1\Pay\CallbackPlugin; use Yansongda\Pay\Tests\TestCase; @@ -19,7 +22,7 @@ protected function setUp(): void $this->plugin = new CallbackPlugin(); } - public function testNotifyCallbackIncludePlus() + public function testCallback() { $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; @@ -31,4 +34,46 @@ public function testNotifyCallbackIncludePlus() self::assertNotEmpty($result->getPayload()->all()); self::assertNotEmpty($result->getDestination()->all()); } + + public function testVerifyDouyinSignEmpty() + { + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $rocket = new Rocket(); + $rocket->setParams(json_decode($post, true)); + + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_EMPTY); + + $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + } + + public function testVerifyDouyinSignError() + { + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"foo","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $rocket = new Rocket(); + $rocket->setParams(json_decode($post, true)); + + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_ERROR); + + $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + } + + public function testVerifyDouyinSignConfigError() + { + $post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}'; + + $params = json_decode($post, true); + $params['_config'] = 'empty_salt'; + + $rocket = new Rocket(); + $rocket->setParams($params); + + self::expectException(InvalidConfigException::class); + self::expectExceptionCode(Exception::CONFIG_DOUYIN_INVALID); + + $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 91ca59558..34bf6b3cf 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -214,7 +214,7 @@ protected function setUp(): void 'mch_id' => '73744242495132490630', // 必填-支付 Token,用于支付回调签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) - 'mch_secret_token' => 'douyin_mini_token', + 'mch_secret_token' => '', // 必填-支付 SALT,用于支付签名 // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT 'mch_secret_salt' => '', From 95b7ed5b5b5631f783c61aa478c96a5d5e3b0469 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 22:59:04 +0800 Subject: [PATCH 17/19] update --- src/Plugin/Douyin/V1/Pay/CallbackPlugin.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php index 2bb0d1372..c348f4882 100644 --- a/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php +++ b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php @@ -17,7 +17,6 @@ use function Yansongda\Artful\filter_params; use function Yansongda\Pay\get_provider_config; -use function Yansongda\Pay\verify_douyin_sign; class CallbackPlugin implements PluginInterface { From 5a40b314fbb93c8fed2423972b3fcd135bb9a152 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 23:11:58 +0800 Subject: [PATCH 18/19] update --- README.md | 141 ++++++++++++++++++++++++----- web/docs/v3/overview/contribute.md | 2 +- 2 files changed, 118 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 026c62666..449a33741 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,11 @@ yansongda/pay 100% 兼容 支付宝/微信/银联 所有功能(包括服务商 - 刷卡支付 - ... +### 抖音 + +- 小程序支付 +- ... + ### 银联 - 手机网站支付 @@ -146,7 +151,9 @@ class AlipayController public function web() { - $result = Pay::alipay($this->config)->web([ + Pay::config($this->config); + + $result = Pay::alipay()->web([ 'out_trade_no' => ''.time(), 'total_amount' => '0.01', 'subject' => 'yansongda 测试 - 1', @@ -157,7 +164,9 @@ class AlipayController public function returnCallback() { - $data = Pay::alipay($this->config)->callback(); // 是的,验签就这么简单! + Pay::config($this->config); + + $data = Pay::alipay()->callback(); // 是的,验签就这么简单! // 订单号:$data->out_trade_no // 支付宝交易号:$data->trade_no @@ -166,10 +175,10 @@ class AlipayController public function notifyCallback() { - $alipay = Pay::alipay($this->config); - + Pay::config($this->config); + try{ - $data = $alipay->callback(); // 是的,验签就这么简单! + $data = Pay::alipay()->callback(); // 是的,验签就这么简单! // 请自行对 trade_status 进行判断及其它逻辑进行判断,在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。 // 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号; @@ -177,11 +186,11 @@ class AlipayController // 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email); // 4、验证app_id是否为该商户本身。 // 5、其它业务逻辑情况 - } catch (\Exception $e) { - // $e->getMessage(); + } catch (\Throwable $e) { + dd($e); } - return $alipay->success(); + return Pay::alipay()->success(); } } ``` @@ -249,6 +258,8 @@ class WechatController public function index() { + Pay::config($this->config); + $order = [ 'out_trade_no' => time().'', 'description' => 'subject-测试', @@ -260,7 +271,7 @@ class WechatController ], ]; - $pay = Pay::wechat($this->config)->mp($order); + $pay = Pay::wechat()->mp($order); // $pay->appId // $pay->timeStamp @@ -269,17 +280,97 @@ class WechatController // $pay->signType } - public function notifyCallback() + public function callback() { - $pay = Pay::wechat($this->config); - + Pay::config($this->config); + try{ - $data = $pay->callback(); // 是的,验签就这么简单! - } catch (\Exception $e) { - // $e->getMessage(); + $data = Pay::wechat()->callback(); // 是的,验签就这么简单! + } catch (\Throwable $e) { + dd($e); } - return $pay->success(); + return Pay::wechat()->success(); + } +} +``` + + +### 抖音 +```php + [ + 'default' => [ + // 选填-商户号 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + 'mch_id' => '73744242495132490630', + // 必填-支付 Token,用于支付回调签名 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mch_secret_token' => 'douyin_mini_token', + // 必填-支付 SALT,用于支付签名 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT + 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 必填-小程序 app_id + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', + // 选填-抖音开放平台服务商id + 'thirdparty_id' => '', + // 选填-抖音支付回调地址 + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ], + ], + 'logger' => [ // optional + 'enable' => false, + 'file' => './logs/alipay.log', + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + ]; + + public function pay() + { + Pay::config($this->config); + + $result = Pay::douyin()->mini([ + 'out_order_no' => date('YmdHis').mt_rand(1000, 9999), + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, + 'expand_order_info' => json_encode([ + 'original_delivery_fee' => 15, + 'actual_delivery_fee' => 10 + ]) + ]); + + return $result; + } + + public function callback() + { + Pay::config($this->config); + + try{ + $data = Pay::douyin()->callback(); // 是的,验签就这么简单! + } catch (\Throwable $e) { + dd($e) + } + + return Pay::douyin()->success(); } } ``` @@ -292,7 +383,7 @@ namespace App\Http\Controllers; use Yansongda\Pay\Pay; -class EpayController +class JsbController { protected $config = [ 'jsb' => [ @@ -331,33 +422,35 @@ class EpayController public function index() { + Pay::config($this->config); + $order = [ 'outTradeNo' => time().'', 'proInfo' => 'subject-测试', 'totalFee'=> 1, ]; - $pay = Pay::jsb($this->config)->scan($order); + $pay = Pay::jsb()->scan($order); } public function notifyCallback() { - $pay = Pay::jsb($this->config); + Pay::config($this->config); try{ - $data = $pay->callback(); // 是的,验签就这么简单! - } catch (\Exception $e) { - // $e->getMessage(); + $data = Pay::jsb()->callback(); // 是的,验签就这么简单! + } catch (\Throwable $e) { + dd($e); } - return $pay->success(); + return Pay::jsb()->success(); } } ``` ## 代码贡献 -由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「银联」、「江苏银行」的相关支付网关。 +由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「抖音支付」、「银联」、「江苏银行」的相关支付网关。 如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_** diff --git a/web/docs/v3/overview/contribute.md b/web/docs/v3/overview/contribute.md index adbb1ea29..8a0394c61 100644 --- a/web/docs/v3/overview/contribute.md +++ b/web/docs/v3/overview/contribute.md @@ -1,6 +1,6 @@ # 参与开发 -由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「银联」、「江苏银行」的相关支付网关。 +由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「抖音支付」、「银联」、「江苏银行」的相关支付网关。 如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_** From cfdd8d16471fb2dce6a17d0d1159a0fccedcd947 Mon Sep 17 00:00:00 2001 From: yansongda Date: Sun, 4 Aug 2024 23:13:15 +0800 Subject: [PATCH 19/19] update --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 449a33741..b50dd8048 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,6 @@ class WechatController } ``` - ### 抖音 ```php