diff --git a/CHANGELOG.md b/CHANGELOG.md index e892edd..fdbbc55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v3.7.9 + +### added + +- feat: 新增抖音支付(#1014) + ## v3.7.8 ### added diff --git a/README.md b/README.md index 026c626..b50dd80 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,96 @@ 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 +382,7 @@ namespace App\Http\Controllers; use Yansongda\Pay\Pay; -class EpayController +class JsbController { protected $config = [ 'jsb' => [ @@ -331,33 +421,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/composer.json b/composer.json index 50d0bae..1aeb668 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 e180890..8aa3d6e 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; + /** * 关于响应. */ @@ -57,6 +59,8 @@ class Exception extends \Exception public const CONFIG_JSB_INVALID = 9404; + public const CONFIG_DOUYIN_INVALID = 9405; + /** * 关于签名. */ diff --git a/src/Functions.php b/src/Functions.php index 51801a8..dd80474 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -23,6 +23,7 @@ use Yansongda\Pay\Plugin\Wechat\ResponsePlugin; 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\Jsb; use Yansongda\Pay\Provider\Unipay; use Yansongda\Pay\Provider\Wechat; @@ -596,6 +597,7 @@ function verify_unipay_sign_qra(array $config, array $destination): void function get_jsb_url(array $config, ?Collection $payload): string { $url = get_radar_url($config, $payload) ?? ''; + if (str_starts_with($url, 'http')) { return $url; } @@ -629,3 +631,21 @@ function verify_jsb_sign(array $config, string $content, string $sign): void throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证江苏银行签名失败', func_get_args()); } } + +/** + * @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 b0d40b1..d10beba 100644 --- a/src/Pay.php +++ b/src/Pay.php @@ -10,10 +10,12 @@ use Yansongda\Artful\Artful; use Yansongda\Artful\Exception\ContainerException; use Yansongda\Artful\Exception\ServiceNotFoundException; use Yansongda\Pay\Provider\Alipay; +use Yansongda\Pay\Provider\Douyin; use Yansongda\Pay\Provider\Jsb; use Yansongda\Pay\Provider\Unipay; use Yansongda\Pay\Provider\Wechat; use Yansongda\Pay\Service\AlipayServiceProvider; +use Yansongda\Pay\Service\DouyinServiceProvider; use Yansongda\Pay\Service\JsbServiceProvider; use Yansongda\Pay\Service\UnipayServiceProvider; use Yansongda\Pay\Service\WechatServiceProvider; @@ -23,6 +25,7 @@ use Yansongda\Pay\Service\WechatServiceProvider; * @method static Wechat wechat(array $config = [], $container = null) * @method static Unipay unipay(array $config = [], $container = null) * @method static Jsb jsb(array $config = [], $container = null) + * @method static Douyin douyin(array $config = [], $container = null) */ class Pay { @@ -46,6 +49,7 @@ class Pay WechatServiceProvider::class, UnipayServiceProvider::class, JsbServiceProvider::class, + DouyinServiceProvider::class, ]; /** diff --git a/src/Plugin/Alipay/V2/AddRadarPlugin.php b/src/Plugin/Alipay/V2/AddRadarPlugin.php index 732dd49..cc99de1 100644 --- a/src/Plugin/Alipay/V2/AddRadarPlugin.php +++ b/src/Plugin/Alipay/V2/AddRadarPlugin.php @@ -11,7 +11,9 @@ use Yansongda\Artful\Exception\ContainerException; 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,8 @@ class AddRadarPlugin implements PluginInterface $payload = $rocket->getPayload(); $rocket->setRadar(new Request( - strtoupper($params['_method'] ?? '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/Alipay/V2/CallbackPlugin.php b/src/Plugin/Alipay/V2/CallbackPlugin.php index d0bbc7c..cb6c850 100644 --- a/src/Plugin/Alipay/V2/CallbackPlugin.php +++ b/src/Plugin/Alipay/V2/CallbackPlugin.php @@ -13,7 +13,6 @@ use Yansongda\Artful\Exception\ServiceNotFoundException; 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 @@ class CallbackPlugin implements PluginInterface $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/AddPayloadSignaturePlugin.php b/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php new file mode 100644 index 0000000..7a83f97 --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/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][Pay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(array $config, Collection $payload): string + { + $salt = $config['mch_secret_salt'] ?? null; + + if (empty($salt)) { + throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_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/AddRadarPlugin.php b/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php new file mode 100644 index 0000000..b7a020c --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/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][V1][Pay][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/V1/Pay/CallbackPlugin.php b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php new file mode 100644 index 0000000..c348f48 --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php @@ -0,0 +1,74 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + $value = filter_params($params, fn ($k, $v) => '' !== $v && 'msg_signature' != $k && 'type' != $k); + + $this->verifySign($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); + } + + /** + * @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/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php new file mode 100644 index 0000000..3926149 --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.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_order', + 'app_id' => $config['mini_app_id'] ?? '', + 'notify_url' => $payload->get('notify_url') ?? $this->getNotifyUrl($config), + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][PayPlugin] 插件装载完毕', ['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/Mini/QueryPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php new file mode 100644 index 0000000..3dbca6d --- /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/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php b/src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php new file mode 100644 index 0000000..8f15e3a --- /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 0000000..7186461 --- /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 new file mode 100644 index 0000000..df4939a --- /dev/null +++ b/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php @@ -0,0 +1,51 @@ + $rocket]); + + $this->validateResponse($rocket); + + Logger::info('[Douyin][V1][Pay][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidResponseException + */ + protected function validateResponse(Rocket $rocket): void + { + $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); + } + } +} diff --git a/src/Provider/Douyin.php b/src/Provider/Douyin.php new file mode 100644 index 0000000..dfe77c1 --- /dev/null +++ b/src/Provider/Douyin.php @@ -0,0 +1,154 @@ + '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|Rocket + { + $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 InvalidParamsException + */ + public function close(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 抖音不支持 close API'); + } + + /** + * @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', $request->all(), $params, null)); + + return $this->pay([CallbackPlugin::class], $request->merge($params)->all()); + } + + public function success(): ResponseInterface + { + return new Response( + 200, + ['Content-Type' => 'application/json'], + json_encode(['err_no' => 0, 'err_tips' => 'success']), + ); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class], + ); + } + + protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): Collection + { + if (is_array($contents)) { + return Collection::wrap($contents); + } + + if (!$contents instanceof ServerRequestInterface) { + $contents = ServerRequest::fromGlobals(); + } + + $body = Collection::wrap($contents->getParsedBody()); + + if ($body->isNotEmpty()) { + return $body; + } + + return Collection::wrapJson((string) $contents->getBody()); + } +} diff --git a/src/Provider/Wechat.php b/src/Provider/Wechat.php index f8975ec..fb88094 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/Service/DouyinServiceProvider.php b/src/Service/DouyinServiceProvider.php new file mode 100644 index 0000000..404c48a --- /dev/null +++ b/src/Service/DouyinServiceProvider.php @@ -0,0 +1,24 @@ +{$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 984ca96..016d849 100644 --- a/src/Shortcut/Alipay/CloseShortcut.php +++ b/src/Shortcut/Alipay/CloseShortcut.php @@ -37,7 +37,7 @@ class CloseShortcut implements ShortcutInterface 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 e58574b..27ecfcf 100644 --- a/src/Shortcut/Alipay/QueryShortcut.php +++ b/src/Shortcut/Alipay/QueryShortcut.php @@ -50,7 +50,7 @@ class QueryShortcut implements ShortcutInterface 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 cf3e70b..bf30461 100644 --- a/src/Shortcut/Alipay/RefundShortcut.php +++ b/src/Shortcut/Alipay/RefundShortcut.php @@ -38,7 +38,7 @@ class RefundShortcut implements ShortcutInterface 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/MiniShortcut.php b/src/Shortcut/Douyin/MiniShortcut.php new file mode 100644 index 0000000..d912164 --- /dev/null +++ b/src/Shortcut/Douyin/MiniShortcut.php @@ -0,0 +1,30 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->miniPlugins(); + } + + protected function refundPlugins(): array + { + return $this->refundMiniPlugins(); + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + 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 0000000..6bbac50 --- /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 40abc57..6cc9569 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 2337957..0ecbc64 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 1c1322d..7f0b29e 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 6852c5b..7539677 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 e678f30..74cbf9e 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 3d98da1..3b4a178 100644 --- a/src/Shortcut/Wechat/CloseShortcut.php +++ b/src/Shortcut/Wechat/CloseShortcut.php @@ -33,13 +33,13 @@ class CloseShortcut implements ShortcutInterface 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 eb0d34f..1e3eabe 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 7f632da..ff6f5a9 100644 --- a/src/Shortcut/Wechat/QueryShortcut.php +++ b/src/Shortcut/Wechat/QueryShortcut.php @@ -40,13 +40,13 @@ class QueryShortcut implements ShortcutInterface 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 34281df..2aa9bed 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 7229dfe..d694bc7 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -21,6 +21,7 @@ use function Yansongda\Pay\decrypt_wechat_contents; 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; @@ -42,6 +43,7 @@ use function Yansongda\Pay\get_wechat_type_key; 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; @@ -566,10 +568,10 @@ Q0C300Eo+XOoO4M1WvsRBAF13g9RPSw=\r 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 +695,16 @@ Q0C300Eo+XOoO4M1WvsRBAF13g9RPSw=\r 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 new file mode 100644 index 0000000..7c0957e --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php @@ -0,0 +1,81 @@ +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')); + } + + 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 new file mode 100644 index 0000000..f911339 --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/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::assertStringContainsString('api/apps/ecpay/v1/create_order', (string) $radar->getUri()); + } +} diff --git a/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php b/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php new file mode 100644 index 0000000..ff4aaad --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php @@ -0,0 +1,79 @@ +plugin = new CallbackPlugin(); + } + + 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"}'; + + $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()); + } + + 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/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php new file mode 100644 index 0000000..95f0c1c --- /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/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php new file mode 100644 index 0000000..f2fce07 --- /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/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php new file mode 100644 index 0000000..65cf7cd --- /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/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php b/tests/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php new file mode 100644 index 0000000..59e6adb --- /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 new file mode 100644 index 0000000..26ffd08 --- /dev/null +++ b/tests/Plugin/Douyin/V1/Pay/ResponsePluginTest.php @@ -0,0 +1,64 @@ +plugin = new ResponsePlugin(); + } + + public function testOriginalResponseDestination() + { + $destination = ['err_no' => 0, 'err_tips' => 'ok', 'data' => ['foo' => 'bar']]; + + $rocket = new Rocket(); + $rocket->setDestinationOrigin(new Response()); + $rocket->setDestination(new Collection($destination)); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertInstanceOf(Collection::class, $result->getDestination()); + self::assertEquals($destination, $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/Provider/DouyinTest.php b/tests/Provider/DouyinTest.php new file mode 100644 index 0000000..ed06e30 --- /dev/null +++ b/tests/Provider/DouyinTest.php @@ -0,0 +1,204 @@ +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('data.order_id')); + self::assertEquals('CgwIARDPKBjKMCABKAESTgpMTgGUG+Ms5klBoqYlsymcJWNMvgWCR8XH+9OO5vFPSl2zZcVKFX0sKRuG9zxMNlT43OJotxNNHaO4KLMbiqo6HYxMiRS5tkoeILFzexoA.W', $result->get('data.order_token')); + 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( + 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() + { + $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()); + } +} diff --git a/tests/Shortcut/Douyin/MiniShortcutTest.php b/tests/Shortcut/Douyin/MiniShortcutTest.php new file mode 100644 index 0000000..4cb9c2b --- /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/Shortcut/Douyin/QueryShortcutTest.php b/tests/Shortcut/Douyin/QueryShortcutTest.php new file mode 100644 index 0000000..96edaee --- /dev/null +++ b/tests/Shortcut/Douyin/QueryShortcutTest.php @@ -0,0 +1,90 @@ +plugin = new QueryShortcut(); + } + + 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, + MiniQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $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([ + StartPlugin::class, + MiniQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + 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 0000000..7db9bde --- /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 e2aafc8..38d003f 100644 --- a/tests/Shortcut/Unipay/CancelShortcutTest.php +++ b/tests/Shortcut/Unipay/CancelShortcutTest.php @@ -75,7 +75,6 @@ class CancelShortcutTest extends TestCase { 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 86f9585..d48306a 100644 --- a/tests/Shortcut/Unipay/PosShortcutTest.php +++ b/tests/Shortcut/Unipay/PosShortcutTest.php @@ -75,7 +75,6 @@ class PosShortcutTest extends TestCase { 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 6f8d4e9..b242046 100644 --- a/tests/Shortcut/Unipay/QueryShortcutTest.php +++ b/tests/Shortcut/Unipay/QueryShortcutTest.php @@ -89,7 +89,6 @@ class QueryShortcutTest extends TestCase { 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 e47ac80..adc5f28 100644 --- a/tests/Shortcut/Unipay/RefundShortcutTest.php +++ b/tests/Shortcut/Unipay/RefundShortcutTest.php @@ -7,6 +7,7 @@ namespace Yansongda\Pay\Tests\Shortcut\Unipay; 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 @@ class RefundShortcutTest extends TestCase 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 69721f7..8725ad5 100644 --- a/tests/Shortcut/Unipay/ScanShortcutTest.php +++ b/tests/Shortcut/Unipay/ScanShortcutTest.php @@ -86,7 +86,6 @@ class ScanShortcutTest extends TestCase { 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 809b74f..0ac0722 100644 --- a/tests/Shortcut/Wechat/CloseShortcutTest.php +++ b/tests/Shortcut/Wechat/CloseShortcutTest.php @@ -86,7 +86,6 @@ class CloseShortcutTest extends TestCase { 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 e551504..61a3958 100644 --- a/tests/Shortcut/Wechat/PapayShortcutTest.php +++ b/tests/Shortcut/Wechat/PapayShortcutTest.php @@ -123,7 +123,6 @@ class PapayShortcutTest extends TestCase { 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 0bb7870..0fcb05b 100644 --- a/tests/Shortcut/Wechat/QueryShortcutTest.php +++ b/tests/Shortcut/Wechat/QueryShortcutTest.php @@ -179,7 +179,6 @@ class QueryShortcutTest extends TestCase { 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 316e725..2233fdc 100644 --- a/tests/Shortcut/Wechat/RefundShortcutTest.php +++ b/tests/Shortcut/Wechat/RefundShortcutTest.php @@ -136,7 +136,6 @@ class RefundShortcutTest extends TestCase { 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 147e4bd..34bf6b3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -171,6 +171,63 @@ class TestCase extends \PHPUnit\Framework\TestCase 'mode' => Pay::MODE_SANDBOX, ], ], + 'douyin' => [ + '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', + 'mode' => Pay::MODE_SANDBOX, + ], + 'service_provider' => [ + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + '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' => 'service_provider', + // 选填-抖音支付回调地址 + 'notify_url' => 'https://yansongda.cn/douyin/notify', + 'mode' => Pay::MODE_SERVICE, + ], + 'empty_salt' => [ + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + 'mch_id' => '73744242495132490630', + // 必填-支付 Token,用于支付回调签名 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mch_secret_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, + ], + ], ]; // hyperf 单测时,未在 hyperf 框架内,所以 sdk 没有 container, 手动设置一个 diff --git a/web/.vitepress/sidebar/v3.js b/web/.vitepress/sidebar/v3.js index faefb64..16b9132 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/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' } + ] + }, + { + text: '银联', + collapsed: true, items: [ { text: '支付', link: '/docs/v3/unipay/pay' }, { text: '查询', link: '/docs/v3/unipay/query' }, @@ -80,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' }, @@ -96,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/docs/v3/douyin/all.md b/web/docs/v3/douyin/all.md new file mode 100644 index 0000000..dd1c780 --- /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/callback.md b/web/docs/v3/douyin/callback.md new file mode 100644 index 0000000..d5862ea --- /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` 这个租户的配置信息。 diff --git a/web/docs/v3/douyin/cancel.md b/web/docs/v3/douyin/cancel.md new file mode 100644 index 0000000..49b4112 --- /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 0000000..0f0f5f4 --- /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 new file mode 100644 index 0000000..6e85b77 --- /dev/null +++ b/web/docs/v3/douyin/pay.md @@ -0,0 +1,37 @@ +# 抖音支付 + +抖音支付目前直接内置支持以下快捷方式支付方法,对应的支付 method 如下: + +| method | 说明 | 参数 | 返回值 | +|:--------:|:------:|:------------:|:----------:| +| mini | 小程序支付 | array $order | Collection | + +## 小程序支付 + +### 例子 + +```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->data['order_id'], $result->['order_token'] 获取相关值。 +// 后续调用不在本文档讨论范围内,请自行参考官方文档。 +``` + +### 订单配置参数 + +**所有订单配置中,客观参数均不用配置,扩展包已经为大家自动处理了**,比如,`app_id`,`sign` 等参数,大家只需传入订单类主观参数即可。 + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考[这里](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/pay),查看「请求参数」一栏。 + +### 调用支付 + +后续调起支付不再本文档讨论范围内,请参考[官方文档](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/tt-pay) diff --git a/web/docs/v3/douyin/query.md b/web/docs/v3/douyin/query.md new file mode 100644 index 0000000..0bef1bb --- /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 0000000..d42725d --- /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 0000000..d2507c5 --- /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/overview/contribute.md b/web/docs/v3/overview/contribute.md index adbb1ea..8a0394c 100644 --- a/web/docs/v3/overview/contribute.md +++ b/web/docs/v3/overview/contribute.md @@ -1,6 +1,6 @@ # 参与开发 -由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「银联」、「江苏银行」的相关支付网关。 +由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「抖音支付」、「银联」、「江苏银行」的相关支付网关。 如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_** diff --git a/web/docs/v3/quick-start/douyin.md b/web/docs/v3/quick-start/douyin.md new file mode 100644 index 0000000..751c589 --- /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/quick-start/init.md b/web/docs/v3/quick-start/init.md index 77090ae..27e4e9d 100644 --- a/web/docs/v3/quick-start/init.md +++ b/web/docs/v3/quick-start/init.md @@ -96,6 +96,26 @@ $config = [ 'mode' => Pay::MODE_NORMAL, ], ], + 'douyin' => [ + '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', + ], + ], 'jsb' => [ 'default' => [ // 服务代码 @@ -114,7 +134,7 @@ $config = [ 'notify_url' => '', // 选填-默认为正常模式。可选为: MODE_NORMAL:正式环境, MODE_SANDBOX:测试环境 'mode' => Pay::MODE_NORMAL, - ] + ], ], 'logger' => [ 'enable' => false, diff --git a/web/docs/v3/wechat/pay.md b/web/docs/v3/wechat/pay.md index 45c7bbe..4b3958c 100644 --- a/web/docs/v3/wechat/pay.md +++ b/web/docs/v3/wechat/pay.md @@ -12,6 +12,12 @@ | scan | 扫码支付 | array $order | Collection | | transfer | 转账 | array $order | Collection | +:::tip +默认情况下,除 APP支付、小程序支付 外所使用的 appid 均是微信公众号的 appid,即配置文件中的 `mp_app_id` 参数 + +如果想使用其他类型的 appid,则只需要在调用参数中增加 `_type` 参数即可,例如,如果想使用小程序的 appid,则:`['_type' => 'mini']` +::: + ## 公众号支付 ### 例子 diff --git a/web/docs/v3/wechat/refund.md b/web/docs/v3/wechat/refund.md index b5898ae..45b4120 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 退款 ]; diff --git a/web/package.json b/web/package.json index 3136339..3f24fb1 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": "^22.1.0", "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 99e4489..7202eb3 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: ^22.1.0 + version: 22.1.0 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@22.1.0)(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@22.1.0)(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: @@ -55,76 +55,76 @@ packages: '@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 +140,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 +293,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.20.0': + resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} 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.20.0': + resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.17.2': - resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} + '@rollup/rollup-darwin-arm64@4.20.0': + resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.17.2': - resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} + '@rollup/rollup-darwin-x64@4.20.0': + resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} + '@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.17.2': - resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} + '@rollup/rollup-linux-arm64-gnu@4.20.0': + resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} 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.20.0': + resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} 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.20.0': + resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} + '@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.17.2': - resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} + '@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.17.2': - resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} + '@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.17.2': - resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} + '@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.17.2': - resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} + '@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.17.2': - resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} + '@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.17.2': - resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} + '@rollup/rollup-win32-x64-msvc@4.20.0': + resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} 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@22.1.0': + resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} + + '@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.2': + resolution: {integrity: sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==} 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==} + '@vue/devtools-kit@7.3.7': + resolution: {integrity: sha512-ktHhhjI4CoUrwdSUF5b/MFfjrtAtK8r4vhOkFyRN5Yp9kdXTwsRBYcwarHuP+wFPKf4/KM7DVBj2ELO8SBwdsw==} + + '@vue/devtools-shared@7.3.7': + resolution: {integrity: sha512-M9EU1/bWi5GNS/+IZrAhwGOVZmUTN4MH22Hvh35nUZZg9AZP2R2OhfCb+MG4EtAsrUEYlu3R43/SIj3G7EZYtQ==} + + '@vue/reactivity@3.4.35': + resolution: {integrity: sha512-Ggtz7ZZHakriKioveJtPlStYardwQH6VCs9V13/4qjHSQb/teE30LVJNrbBVs4+aoYGtTQKJbTe4CWGxVZrvEw==} + + '@vue/runtime-core@3.4.35': + resolution: {integrity: sha512-D+BAjFoWwT5wtITpSxwqfWZiBClhBbR+bm0VQlWYFOadUUXFo+5wbe9ErXhLvwguPiLZdEF13QAWi2vP3ZD5tA==} + + '@vue/runtime-dom@3.4.35': + resolution: {integrity: sha512-yGOlbos+MVhlS5NWBF2HDNgblG8e2MY3+GigHEyR/dREAluvI5tuUUgie3/9XeqhPE4LF0i2wjlduh5thnfOqw==} + + '@vue/server-renderer@3.4.35': + resolution: {integrity: sha512-iZ0e/u9mRE4T8tNhlo0tbA+gzVkgv8r5BX6s1kRbOZqfpq14qoIvCZ5gIgraOmYkMYrSEZgkkojFPr+Nyq/Mnw==} peerDependencies: - vue: ^3.0.0 + vue: 3.4.35 - '@vue/devtools-shared@7.2.0': - resolution: {integrity: sha512-gVr3IjKjU7axNvclRgICgy1gq/TDnF1hhBAEox+l5mMXZiTIFVIm1zpcIPssc0HxMDgzy+lXqOVsY4DGyZ+ZeA==} + '@vue/shared@3.4.35': + resolution: {integrity: sha512-hvuhBYYDe+b1G8KHxsQ0diDqDMA8D9laxWZhNAjE83VZb5UDaXl9Xnz7cGdDSyiHM90qqI/CyGMcpBpiDy6VVQ==} - '@vue/reactivity@3.4.27': - resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==} + '@vueuse/core@10.11.0': + resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==} - '@vue/runtime-core@3.4.27': - resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==} - - '@vue/runtime-dom@3.4.27': - resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==} - - '@vue/server-renderer@3.4.27': - resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==} + '@vueuse/integrations@10.11.0': + resolution: {integrity: sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==} peerDependencies: - vue: 3.4.27 - - '@vue/shared@3.4.27': - resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} - - '@vueuse/core@10.9.0': - resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==} - - '@vueuse/integrations@10.9.0': - resolution: {integrity: sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==} - 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 +492,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 +509,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 +531,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 +546,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 +565,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 +584,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 +598,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 +627,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 +645,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.20.0: + resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} 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 +675,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==} @@ -671,11 +690,11 @@ 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.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 +721,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 +733,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 +744,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 +754,132 @@ 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 + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + algoliasearch: 4.24.0 optionalDependencies: - '@algolia/client-search': 4.23.3 + '@algolia/client-search': 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 + algoliasearch: 4.24.0 optionalDependencies: - '@algolia/client-search': 4.23.3 + '@algolia/client-search': 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 +887,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 +981,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.20.0': optional: true - '@rollup/rollup-android-arm64@4.17.2': + '@rollup/rollup-android-arm64@4.20.0': optional: true - '@rollup/rollup-darwin-arm64@4.17.2': + '@rollup/rollup-darwin-arm64@4.20.0': optional: true - '@rollup/rollup-darwin-x64@4.17.2': + '@rollup/rollup-darwin-x64@4.20.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': + '@rollup/rollup-linux-arm-gnueabihf@4.20.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.17.2': + '@rollup/rollup-linux-arm-musleabihf@4.20.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.17.2': + '@rollup/rollup-linux-arm64-gnu@4.20.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.17.2': + '@rollup/rollup-linux-arm64-musl@4.20.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.17.2': + '@rollup/rollup-linux-riscv64-gnu@4.20.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.17.2': + '@rollup/rollup-linux-s390x-gnu@4.20.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.17.2': + '@rollup/rollup-linux-x64-gnu@4.20.0': optional: true - '@rollup/rollup-linux-x64-musl@4.17.2': + '@rollup/rollup-linux-x64-musl@4.20.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.17.2': + '@rollup/rollup-win32-arm64-msvc@4.20.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.17.2': + '@rollup/rollup-win32-ia32-msvc@4.20.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.17.2': + '@rollup/rollup-win32-x64-msvc@4.20.0': optional: true - '@shikijs/core@1.5.2': {} - - '@shikijs/transformers@1.5.2': + '@shikijs/core@1.12.1': dependencies: - shiki: 1.5.2 + '@types/hast': 3.0.4 + + '@shikijs/transformers@1.12.1': + dependencies: + 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@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.0.4(vite@5.2.11(@types/node@20.12.12)(sass@1.77.1))(vue@3.4.27)': + '@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.2.11(@types/node@20.12.12)(sass@1.77.1) - vue: 3.4.27 + vite: 5.3.5(@types/node@22.1.0)(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: + algoliasearch@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-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 + '@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 +1192,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 +1210,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 +1252,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 +1275,7 @@ snapshots: hookable@5.5.3: {} - immutable@4.3.6: {} + immutable@4.3.7: {} is-binary-path@2.1.0: dependencies: @@ -1256,20 +1289,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 +1318,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 +1334,55 @@ snapshots: reusify@1.0.4: {} - rfdc@1.3.1: {} + rfdc@1.4.1: {} - rollup@4.17.2: + rollup@4.20.0: 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.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: 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: {} @@ -1351,37 +1391,38 @@ snapshots: dependencies: is-number: 7.0.0 - undici-types@5.26.5: {} + undici-types@6.13.0: {} - vite@5.2.11(@types/node@20.12.12)(sass@1.77.1): + vite@5.3.5(@types/node@22.1.0)(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.20.0 optionalDependencies: - '@types/node': 20.12.12 + '@types/node': 22.1.0 fsevents: 2.3.3 - sass: 1.77.1 + sass: 1.77.8 - 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): + 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.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) + '@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.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) + '@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@22.1.0)(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 +1450,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