From 63fdfe87d7a17c8f5ff36f7c0a4856c46c4c5b28 Mon Sep 17 00:00:00 2001
From: MiAn
Date: Mon, 19 Oct 2026 10:03:13 +0800
Subject: [PATCH] =?UTF-8?q?feat(v1.1.0):=20=E6=94=AF=E4=BB=98=E6=94=B9?=
=?UTF-8?q?=E7=94=B1=20epay=20=E6=8F=92=E4=BB=B6=E6=89=BF=E8=BD=BD?=
=?UTF-8?q?=E3=80=81=E6=8F=92=E4=BB=B6=E7=BA=B3=E5=85=A5=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E5=BA=93=EF=BC=8C=E8=A1=A5=E9=BD=90=E5=AA=92=E4=BD=93=E4=B8=8A?=
=?UTF-8?q?=E4=BC=A0/LBS/=E4=BA=92=E5=8A=A8/=E4=BC=98=E6=83=A0=E5=88=B8/?=
=?UTF-8?q?=E5=90=8E=E5=8F=B0=E7=9C=8B=E6=9D=BF=EF=BC=8C=E5=B9=B6=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8D=E4=BD=99=E9=A2=9D=E5=85=85=E5=80=BC=E8=B5=84=E6=8D=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## 一、支付:改由已安装的 epay 插件承载(不再自研签名)
- 下单:\addons\epay\library\Service::submitOrder($amount 元, $orderNo, 'wechat', $body, $notifyUrl, null, 'miniapp', $openid)
- 验签:Service::checkNotify('wechat')(内部 $pay->verify());应答:$pay->success()(v2 = XML)
- 业务编排仍在 application/api/controller/Pay.php,不改插件源码:
POST /pay/prepay(取调起参数,下发前剔除 appId);POST /pay/notify(公开路由:插件验签 → 金额核对 → 幂等分派)
- 配置真源 = addons/epay/config.php(后台「插件管理 → 微信支付宝整合插件 → 配置」);
fc_config 的 wx.mchId / wx.apiKey / wx.payNotifyUrl 废弃;后台「系统设置」支付表单改只读并跳插件配置页
- 插件 API 保持 version=v2(PHP 7.4 可跑);certs/* 为 0 字节占位,不含任何真实密钥
- 修复 BUG-015:Pay::notify() 与基类站内信同名且签名不兼容导致 /pay/* 全 500,动作改名 notifyCallback()
## 二、插件纳入 Git 版本管理
- .gitignore 移除 /addons/* 与 /public/assets/addons/*(保留 .gitkeep 占位)
- 入库 addons/epay、addons/wechat、public/assets/addons/*、addons/.htaccess(deny from all)
- config.php 入库为空白模板;服务器请在填好密钥后执行
git update-index --skip-worktree addons/epay/config.php,避免真实密钥回传仓库
## 三、安全修复:余额充值资损(高危,已实测复现)
- 现象:POST /order/recharge {payType:'balance'} 只加余额不扣款(余额 134.50 → 1022.50),等于凭空增发余额,
可再去买会员 / 付费发布 / 解锁联系方式
- 修复:/order/recharge 仅接受 payType=wxpay,balance → 「余额充值仅支持微信支付」,其它 → 「支付方式不支持」;
订单恒为 status=pending、pay_type=wxpay、paytime=null;入账只发生在 /pay/notify 回调(Order::creditRecharge)
或后台「会员管理 → 余额充值」(fc/userext/recharge)
- 小程序充值页同步为「仅微信支付」+ 防重复提交 + onShow 刷新资产;mock / 用例 / 文档同步
## 四、其他 v1.1.0 能力
- 媒体上传:POST /post/upload(图片 ≤10MB / 视频 ≤50MB,真落盘),发布页 uni.uploadFile 真上传
- LBS:fc_post.lat/lng、首页 sort=nearby(距离升序、无坐标排最后)、卡片 distance、详情页地图
- 互动:举报 POST /report(原因字典)、收藏 / 关注(fc_favorite / fc_follow、/favorite/*、/follow/toggle)
- 交易:余额充值 POST /order/recharge、优惠券 /coupon/list|receive(fc_coupon / fc_user_coupon、我的资产券)
- 发布计费:会员发布折扣(discountRate/discountText)+ 免费发布条数(freeQuota*)
- 后台新增:数据总览 fc/dashboard、系统设置 fc/setting、优惠券管理 fc/coupon(含菜单与权限节点)
## 五、本批审核修复
- 详情页:游客不再请求登录态接口(避免 401 被清 token 踢去登录);微信付费查看改为支付后轮询解锁,
不再读取 wxpay 分支未返回的 contact;加防重复提交
- 发布页:freeLeftToday → freeQuotaLeft / freeQuotaCycle / freeUnlimited(对齐后端出参);
上传图预览拼后端域名(新增 format.js#assetUrl,后端返回相对路径)
- 收藏空态「去首页」:switchTab → reLaunch(项目用自绘 fc-tabbar,无 tabBar,switchTab 必然失败)
- 会员券 days=0 显示「永久有效」;manifest.json 补 requiredPrivateInfos(getLocation / chooseLocation)
- 后端:publishConfig 补 discountRate/discountText 与 memberPostDiscountText();优惠券总开关 coupon.enabled
真生效(关闭后 list/receive 拒绝);后台 fc/Coupon 的 add/edit 补 strip_tags 过滤(防存储型 XSS)、
补 searchFields='name',列表 name 列再做一次 HTML 转义
- FcInstall:补清理后台菜单缓存 __menu__(否则新菜单要等缓存过期才显示),并同步 README 说明
- 后台语言包 fc/setting.php 按「支付只读 + 跳插件配置」的新页面重写
- 文档同步:uniapp/docs/{01,03,04}、uniapp/README.md、docs/05-后端实现规范.md、后台 README 上线步骤
## 六、校验结果(全部通过)
- 后端端到端:tools/verify-api.mjs 226/226(含新增「拒绝余额充值(资损防护)」用例)
- 小程序:uniapp/tests/run.mjs 167/167;validate.mjs 0 错 0 警;check-scss 19/19
- 后台:check-admin-i18n 0 错 0 警;check-theme 0 错 0 警;9 个页面 HTTP 200 全中文
- php -l 改动/新增 PHP 文件 0 语法错误;探针复测:余额不再增长、epay 插件下单/验签链路通
---
.gitignore | 2 -
addons/.htaccess | 1 +
addons/epay/.addonrc | 1 +
addons/epay/Epay.php | 112 +
addons/epay/certs/alipayCertPublicKey.crt | 0
addons/epay/certs/alipayRootCert.crt | 0
addons/epay/certs/apiclient_cert.pem | 0
addons/epay/certs/apiclient_key.pem | 0
addons/epay/certs/appCertPublicKey.crt | 0
addons/epay/certs/public_key.pem | 0
addons/epay/config.html | 529 +++++
addons/epay/config.php | 72 +
addons/epay/controller/Api.php | 234 +++
addons/epay/controller/Index.php | 131 ++
addons/epay/info.ini | 10 +
addons/epay/library/Collection.php | 18 +
addons/epay/library/OrderException.php | 16 +
addons/epay/library/RedirectResponse.php | 63 +
addons/epay/library/Response.php | 46 +
addons/epay/library/Service.php | 494 +++++
addons/epay/library/Wechat.php | 110 +
.../library/hyperf/context/.gitattributes | 2 +
addons/epay/library/hyperf/context/LICENSE | 21 +
.../epay/library/hyperf/context/composer.json | 40 +
.../library/hyperf/context/src/Context.php | 112 +
.../library/hyperf/contract/.gitattributes | 2 +
addons/epay/library/hyperf/contract/LICENSE | 21 +
.../library/hyperf/contract/composer.json | 33 +
.../contract/src/ApplicationInterface.php | 16 +
.../library/hyperf/contract/src/Castable.php | 22 +
.../hyperf/contract/src/CastsAttributes.php | 33 +
.../contract/src/CastsInboundAttributes.php | 24 +
.../hyperf/contract/src/CompressInterface.php | 17 +
.../hyperf/contract/src/ConfigInterface.php | 41 +
.../contract/src/ConnectionInterface.php | 40 +
.../contract/src/ContainerInterface.php | 53 +
.../contract/src/DispatcherInterface.php | 17 +
.../contract/src/FrequencyInterface.php | 25 +
.../contract/src/IdGeneratorInterface.php | 17 +
.../src/LengthAwarePaginatorInterface.php | 30 +
.../src/MiddlewareInitializerInterface.php | 17 +
.../contract/src/NormalizerInterface.php | 32 +
.../hyperf/contract/src/OnCloseInterface.php | 23 +
.../contract/src/OnHandShakeInterface.php | 20 +
.../contract/src/OnMessageInterface.php | 24 +
.../hyperf/contract/src/OnOpenInterface.php | 24 +
.../hyperf/contract/src/OnPacketInterface.php | 24 +
.../contract/src/OnReceiveInterface.php | 23 +
.../contract/src/OnRequestInterface.php | 21 +
.../hyperf/contract/src/PackerInterface.php | 19 +
.../contract/src/PaginatorInterface.php | 95 +
.../hyperf/contract/src/PoolInterface.php | 30 +
.../contract/src/PoolOptionInterface.php | 27 +
.../hyperf/contract/src/ProcessInterface.php | 36 +
.../contract/src/ResponseEmitterInterface.php | 22 +
.../hyperf/contract/src/SessionInterface.php | 158 ++
.../contract/src/StdoutLoggerInterface.php | 18 +
.../hyperf/contract/src/Synchronized.php | 20 +
.../contract/src/TranslatorInterface.php | 37 +
.../src/TranslatorLoaderInterface.php | 35 +
.../contract/src/UnCompressInterface.php | 17 +
.../contract/src/ValidatorInterface.php | 60 +
.../epay/library/hyperf/engine/.gitattributes | 4 +
addons/epay/library/hyperf/engine/.gitignore | 4 +
.../library/hyperf/engine/.php-cs-fixer.php | 89 +
.../library/hyperf/engine/.phpstorm.meta.php | 6 +
addons/epay/library/hyperf/engine/Dockerfile | 46 +
addons/epay/library/hyperf/engine/README.md | 7 +
.../epay/library/hyperf/engine/composer.json | 49 +
addons/epay/library/hyperf/engine/phpunit.xml | 15 +
.../library/hyperf/engine/src/Channel.php | 142 ++
.../library/hyperf/engine/src/Constant.php | 25 +
.../engine/src/Contract/ChannelInterface.php | 134 ++
.../src/Contract/CoroutineInterface.php | 70 +
.../src/Contract/Http/ClientInterface.php | 24 +
.../Contract/WebSocket/WebSocketInterface.php | 23 +
.../library/hyperf/engine/src/Coroutine.php | 100 +
.../Exception/CoroutineDestroyedException.php | 16 +
.../src/Exception/HttpClientException.php | 16 +
.../RunningInNonCoroutineException.php | 16 +
.../engine/src/Exception/RuntimeException.php | 16 +
.../library/hyperf/engine/src/Extension.php | 20 +
.../library/hyperf/engine/src/Http/Client.php | 76 +
.../hyperf/engine/src/Http/FdGetter.php | 22 +
.../hyperf/engine/src/Http/RawResponse.php | 47 +
.../epay/library/hyperf/engine/src/Socket.php | 16 +
.../library/hyperf/engine/src/WaitGroup.php | 16 +
.../hyperf/engine/src/WebSocket/Frame.php | 19 +
.../hyperf/engine/src/WebSocket/Opcode.php | 27 +
.../hyperf/engine/src/WebSocket/WebSocket.php | 59 +
.../library/hyperf/macroable/.gitattributes | 2 +
addons/epay/library/hyperf/macroable/LICENSE | 22 +
.../library/hyperf/macroable/composer.json | 39 +
.../hyperf/macroable/src/Macroable.php | 129 ++
.../epay/library/hyperf/pimple/.gitattributes | 2 +
addons/epay/library/hyperf/pimple/.gitignore | 4 +
.../library/hyperf/pimple/.php-cs-fixer.php | 92 +
.../library/hyperf/pimple/.phpstorm.meta.php | 6 +
addons/epay/library/hyperf/pimple/.travis.yml | 42 +
addons/epay/library/hyperf/pimple/README.md | 195 ++
.../epay/library/hyperf/pimple/composer.json | 53 +
addons/epay/library/hyperf/pimple/phpunit.xml | 15 +
.../hyperf/pimple/src/ConfigProvider.php | 32 +
.../library/hyperf/pimple/src/Container.php | 139 ++
.../hyperf/pimple/src/ContainerFactory.php | 37 +
.../Exception/InvalidDefinitionException.php | 18 +
.../src/Exception/NotFoundException.php | 18 +
.../src/Exception/NotSupportException.php | 18 +
.../hyperf/pimple/src/ProviderInterface.php | 19 +
.../epay/library/hyperf/utils/.gitattributes | 2 +
addons/epay/library/hyperf/utils/LICENSE | 22 +
.../epay/library/hyperf/utils/composer.json | 58 +
.../hyperf/utils/src/ApplicationContext.php | 41 +
addons/epay/library/hyperf/utils/src/Arr.php | 558 +++++
.../epay/library/hyperf/utils/src/Backoff.php | 84 +
.../hyperf/utils/src/Channel/Caller.php | 79 +
.../utils/src/Channel/ChannelManager.php | 72 +
.../library/hyperf/utils/src/ChannelPool.php | 38 +
.../hyperf/utils/src/ClearStatCache.php | 62 +
.../hyperf/utils/src/CodeGen/Package.php | 26 +
.../hyperf/utils/src/CodeGen/PhpDocReader.php | 247 +++
.../utils/src/CodeGen/PhpDocReaderManager.php | 30 +
.../hyperf/utils/src/CodeGen/PhpParser.php | 156 ++
.../hyperf/utils/src/CodeGen/Project.php | 69 +
.../library/hyperf/utils/src/Codec/Base62.php | 39 +
.../library/hyperf/utils/src/Codec/Json.php | 56 +
.../library/hyperf/utils/src/Codec/Xml.php | 70 +
.../library/hyperf/utils/src/Collection.php | 1835 +++++++++++++++++
.../library/hyperf/utils/src/Composer.php | 159 ++
.../hyperf/utils/src/ConfigProvider.php | 37 +
.../epay/library/hyperf/utils/src/Context.php | 107 +
.../hyperf/utils/src/Contracts/Arrayable.php | 24 +
.../hyperf/utils/src/Contracts/Jsonable.php | 17 +
.../hyperf/utils/src/Contracts/MessageBag.php | 87 +
.../utils/src/Contracts/MessageProvider.php | 20 +
.../hyperf/utils/src/Contracts/Xmlable.php | 17 +
.../utils/src/Coordinator/Constants.php | 25 +
.../utils/src/Coordinator/Coordinator.php | 48 +
.../src/Coordinator/CoordinatorManager.php | 53 +
.../library/hyperf/utils/src/Coroutine.php | 92 +
.../hyperf/utils/src/Coroutine/Concurrent.php | 95 +
.../hyperf/utils/src/Coroutine/Locker.php | 60 +
.../src/Exception/ChannelClosedException.php | 16 +
.../utils/src/Exception/ExceptionThrower.php | 32 +
.../Exception/InvalidArgumentException.php | 16 +
.../Exception/ParallelExecutionException.php | 45 +
.../utils/src/Exception/TimeoutException.php | 16 +
.../src/Exception/WaitTimeoutException.php | 16 +
.../src/Filesystem/FileNotFoundException.php | 16 +
.../utils/src/Filesystem/Filesystem.php | 554 +++++
.../epay/library/hyperf/utils/src/Fluent.php | 200 ++
.../library/hyperf/utils/src/Functions.php | 486 +++++
.../utils/src/HigherOrderCollectionProxy.php | 63 +
.../hyperf/utils/src/HigherOrderTapProxy.php | 46 +
.../hyperf/utils/src/InteractsWithTime.php | 70 +
.../library/hyperf/utils/src/MessageBag.php | 351 ++++
.../utils/src/MimeTypeExtensionGuesser.php | 814 ++++++++
.../epay/library/hyperf/utils/src/Network.php | 46 +
.../library/hyperf/utils/src/Optional.php | 134 ++
.../hyperf/utils/src/Packer/JsonPacker.php | 27 +
.../utils/src/Packer/PhpSerializerPacker.php | 27 +
.../library/hyperf/utils/src/Parallel.php | 111 +
.../library/hyperf/utils/src/Pipeline.php | 171 ++
.../library/hyperf/utils/src/Pluralizer.php | 161 ++
.../utils/src/Reflection/ClassInvoker.php | 51 +
.../library/hyperf/utils/src/Resource.php | 37 +
.../hyperf/utils/src/ResourceGenerator.php | 34 +
.../src/Serializer/ExceptionNormalizer.php | 104 +
.../utils/src/Serializer/ScalarNormalizer.php | 64 +
.../utils/src/Serializer/Serializer.php | 312 +++
.../src/Serializer/SerializerFactory.php | 39 +
.../utils/src/Serializer/SimpleNormalizer.php | 40 +
.../src/Serializer/SymfonyNormalizer.php | 38 +
addons/epay/library/hyperf/utils/src/Str.php | 1320 ++++++++++++
.../library/hyperf/utils/src/Stringable.php | 803 ++++++++
.../hyperf/utils/src/Traits/Conditionable.php | 57 +
.../hyperf/utils/src/Traits/Container.php | 64 +
.../utils/src/Traits/CoroutineProxy.php | 43 +
.../hyperf/utils/src/Traits/ForwardsCalls.php | 53 +
.../hyperf/utils/src/Traits/Macroable.php | 20 +
.../utils/src/Traits/StaticInstance.php | 40 +
.../hyperf/utils/src/Traits/Tappable.php | 26 +
.../library/hyperf/utils/src/WaitGroup.php | 18 +
.../epay/library/hyperf/utils/src/Waiter.php | 67 +
.../Contracts/GatewayApplicationInterface.php | 83 +
.../Pay/Contracts/GatewayInterface.php | 20 +
.../epay/library/v2/Yansongda/Pay/Events.php | 98 +
.../v2/Yansongda/Pay/Events/ApiRequested.php | 31 +
.../v2/Yansongda/Pay/Events/ApiRequesting.php | 31 +
.../library/v2/Yansongda/Pay/Events/Event.php | 40 +
.../v2/Yansongda/Pay/Events/MethodCalled.php | 33 +
.../v2/Yansongda/Pay/Events/PayStarted.php | 31 +
.../v2/Yansongda/Pay/Events/PayStarting.php | 23 +
.../Yansongda/Pay/Events/RequestReceived.php | 25 +
.../v2/Yansongda/Pay/Events/SignFailed.php | 25 +
.../Pay/Exceptions/BusinessException.php | 19 +
.../v2/Yansongda/Pay/Exceptions/Exception.php | 44 +
.../Pay/Exceptions/GatewayException.php | 20 +
.../Exceptions/InvalidArgumentException.php | 19 +
.../Pay/Exceptions/InvalidConfigException.php | 19 +
.../Exceptions/InvalidGatewayException.php | 19 +
.../Pay/Exceptions/InvalidSignException.php | 19 +
.../v2/Yansongda/Pay/Gateways/Alipay.php | 423 ++++
.../Pay/Gateways/Alipay/AppGateway.php | 38 +
.../Yansongda/Pay/Gateways/Alipay/Gateway.php | 40 +
.../Pay/Gateways/Alipay/MiniGateway.php | 46 +
.../Pay/Gateways/Alipay/PosGateway.php | 47 +
.../Pay/Gateways/Alipay/RefundGateway.php | 21 +
.../Pay/Gateways/Alipay/ScanGateway.php | 41 +
.../Yansongda/Pay/Gateways/Alipay/Support.php | 452 ++++
.../Pay/Gateways/Alipay/TransferGateway.php | 49 +
.../Pay/Gateways/Alipay/WapGateway.php | 26 +
.../Pay/Gateways/Alipay/WebGateway.php | 104 +
.../v2/Yansongda/Pay/Gateways/Wechat.php | 366 ++++
.../Pay/Gateways/Wechat/AppGateway.php | 62 +
.../Yansongda/Pay/Gateways/Wechat/Gateway.php | 88 +
.../Gateways/Wechat/GroupRedpackGateway.php | 57 +
.../Pay/Gateways/Wechat/MiniappGateway.php | 35 +
.../Pay/Gateways/Wechat/MpGateway.php | 59 +
.../Pay/Gateways/Wechat/PosGateway.php | 44 +
.../Pay/Gateways/Wechat/RedpackGateway.php | 61 +
.../Pay/Gateways/Wechat/RefundGateway.php | 50 +
.../Pay/Gateways/Wechat/ScanGateway.php | 44 +
.../Yansongda/Pay/Gateways/Wechat/Support.php | 460 +++++
.../Pay/Gateways/Wechat/TransferGateway.php | 80 +
.../Pay/Gateways/Wechat/WapGateway.php | 47 +
.../Pay/Gateways/Wechat/WebGateway.php | 86 +
addons/epay/library/v2/Yansongda/Pay/LICENSE | 20 +
.../Pay/Listeners/KernelLogSubscriber.php | 114 +
addons/epay/library/v2/Yansongda/Pay/Log.php | 49 +
addons/epay/library/v2/Yansongda/Pay/Pay.php | 131 ++
.../library/v2/Yansongda/Supports/Arr.php | 605 ++++++
.../v2/Yansongda/Supports/Collection.php | 386 ++++
.../library/v2/Yansongda/Supports/Config.php | 7 +
.../library/v2/Yansongda/Supports/LICENSE | 20 +
.../library/v2/Yansongda/Supports/Log.php | 91 +
.../library/v2/Yansongda/Supports/Logger.php | 240 +++
.../Supports/Logger/StdoutHandler.php | 36 +
.../library/v2/Yansongda/Supports/Str.php | 570 +++++
.../Yansongda/Supports/Traits/Accessable.php | 142 ++
.../Yansongda/Supports/Traits/Arrayable.php | 32 +
.../Supports/Traits/HasHttpRequest.php | 229 ++
.../Supports/Traits/Serializable.php | 85 +
.../Supports/Traits/ShouldThrottle.php | 147 ++
addons/epay/library/v2/version.txt | 1 +
.../Pay/Contract/ConfigInterface.php | 22 +
.../Pay/Contract/ContainerInterface.php | 22 +
.../Pay/Contract/DirectionInterface.php | 15 +
.../Pay/Contract/EventDispatcherInterface.php | 9 +
.../Pay/Contract/HttpClientInterface.php | 11 +
.../Pay/Contract/LoggerInterface.php | 9 +
.../Pay/Contract/PackerInterface.php | 12 +
.../Pay/Contract/PluginInterface.php | 13 +
.../Pay/Contract/ProviderInterface.php | 58 +
.../Pay/Contract/ServiceProviderInterface.php | 17 +
.../Pay/Contract/ShortcutInterface.php | 13 +
.../Pay/Direction/ArrayDirection.php | 32 +
.../Pay/Direction/CollectionDirection.php | 27 +
.../Pay/Direction/NoHttpRequestDirection.php | 17 +
.../Pay/Direction/OriginResponseDirection.php | 26 +
.../Pay/Direction/ResponseDirection.php | 9 +
.../epay/library/v3/Yansongda/Pay/Event.php | 38 +
.../v3/Yansongda/Pay/Event/ApiRequested.php | 9 +
.../v3/Yansongda/Pay/Event/ApiRequesting.php | 9 +
.../Yansongda/Pay/Event/CallbackReceived.php | 32 +
.../library/v3/Yansongda/Pay/Event/Event.php | 17 +
.../v3/Yansongda/Pay/Event/MethodCalled.php | 25 +
.../v3/Yansongda/Pay/Event/PayFinish.php | 9 +
.../v3/Yansongda/Pay/Event/PayStarted.php | 26 +
.../Pay/Exception/ContainerException.php | 19 +
.../Exception/ContainerNotFoundException.php | 18 +
.../v3/Yansongda/Pay/Exception/Exception.php | 114 +
.../Pay/Exception/InvalidConfigException.php | 18 +
.../Pay/Exception/InvalidParamsException.php | 18 +
.../Exception/InvalidResponseException.php | 33 +
.../Pay/Exception/ServiceException.php | 18 +
.../Exception/ServiceNotFoundException.php | 19 +
.../library/v3/Yansongda/Pay/Functions.php | 403 ++++
.../epay/library/v3/Yansongda/Pay/Logger.php | 48 +
.../v3/Yansongda/Pay/Packer/JsonPacker.php | 26 +
.../v3/Yansongda/Pay/Packer/QueryPacker.php | 33 +
.../v3/Yansongda/Pay/Packer/XmlPacker.php | 46 +
addons/epay/library/v3/Yansongda/Pay/Pay.php | 267 +++
.../Pay/Plugin/Alipay/CallbackPlugin.php | 65 +
.../Data/BillDownloadUrlQueryPlugin.php | 18 +
.../Alipay/Data/BillEreceiptApplyPlugin.php | 35 +
.../Alipay/Data/BillEreceiptQueryPlugin.php | 18 +
.../Alipay/Ebpp/PdeductBillStatusPlugin.php | 18 +
.../Plugin/Alipay/Ebpp/PdeductPayPlugin.php | 35 +
.../Alipay/Ebpp/PdeductSignAddPlugin.php | 37 +
.../Alipay/Ebpp/PdeductSignCancelPlugin.php | 35 +
.../Plugin/Alipay/Fund/AccountQueryPlugin.php | 35 +
.../Alipay/Fund/AuthOrderFreezePlugin.php | 35 +
.../Alipay/Fund/AuthOrderUnfreezePlugin.php | 18 +
.../Alipay/Fund/TransCommonQueryPlugin.php | 36 +
.../Plugin/Alipay/Fund/TransPagePayPlugin.php | 33 +
.../Alipay/Fund/TransTobankTransferPlugin.php | 15 +
.../Alipay/Fund/TransUniTransferPlugin.php | 36 +
.../Pay/Plugin/Alipay/GeneralPlugin.php | 35 +
.../Pay/Plugin/Alipay/HtmlResponsePlugin.php | 71 +
.../Pay/Plugin/Alipay/LaunchPlugin.php | 73 +
.../Pay/Plugin/Alipay/PreparePlugin.php | 214 ++
.../Pay/Plugin/Alipay/RadarSignPlugin.php | 144 ++
.../Plugin/Alipay/Shortcut/AppShortcut.php | 47 +
.../Plugin/Alipay/Shortcut/CancelShortcut.php | 18 +
.../Plugin/Alipay/Shortcut/CloseShortcut.php | 18 +
.../Plugin/Alipay/Shortcut/MiniShortcut.php | 18 +
.../Plugin/Alipay/Shortcut/PosShortcut.php | 18 +
.../Plugin/Alipay/Shortcut/QueryShortcut.php | 55 +
.../Plugin/Alipay/Shortcut/RefundShortcut.php | 18 +
.../Plugin/Alipay/Shortcut/ScanShortcut.php | 18 +
.../Alipay/Shortcut/TransferShortcut.php | 18 +
.../Plugin/Alipay/Shortcut/WapShortcut.php | 20 +
.../Plugin/Alipay/Shortcut/WebShortcut.php | 20 +
.../Alipay/Tools/OpenAuthTokenAppPlugin.php | 18 +
.../Tools/OpenAuthTokenAppQueryPlugin.php | 18 +
.../Alipay/Tools/SystemOauthTokenPlugin.php | 24 +
.../Pay/Plugin/Alipay/Trade/AppPayPlugin.php | 47 +
.../Pay/Plugin/Alipay/Trade/CancelPlugin.php | 18 +
.../Pay/Plugin/Alipay/Trade/ClosePlugin.php | 18 +
.../Pay/Plugin/Alipay/Trade/CreatePlugin.php | 18 +
.../Alipay/Trade/FastRefundQueryPlugin.php | 18 +
.../Pay/Plugin/Alipay/Trade/OrderPlugin.php | 15 +
.../Plugin/Alipay/Trade/OrderSettlePlugin.php | 18 +
.../Pay/Plugin/Alipay/Trade/PagePayPlugin.php | 47 +
.../Plugin/Alipay/Trade/PageRefundPlugin.php | 30 +
.../Pay/Plugin/Alipay/Trade/PayPlugin.php | 47 +
.../Plugin/Alipay/Trade/PreCreatePlugin.php | 33 +
.../Pay/Plugin/Alipay/Trade/QueryPlugin.php | 18 +
.../Pay/Plugin/Alipay/Trade/RefundPlugin.php | 18 +
.../Pay/Plugin/Alipay/Trade/WapPayPlugin.php | 49 +
.../AgreementExecutionPlanModifyPlugin.php | 18 +
.../Alipay/User/AgreementPageSignPlugin.php | 36 +
.../Alipay/User/AgreementQueryPlugin.php | 33 +
.../Alipay/User/AgreementTransferPlugin.php | 33 +
.../Alipay/User/AgreementUnsignPlugin.php | 33 +
.../Plugin/Alipay/User/InfoSharePlugin.php | 30 +
.../v3/Yansongda/Pay/Plugin/ParserPlugin.php | 74 +
.../Pay/Plugin/Unipay/CallbackPlugin.php | 62 +
.../Pay/Plugin/Unipay/GeneralPlugin.php | 84 +
.../Pay/Plugin/Unipay/HtmlResponsePlugin.php | 45 +
.../Pay/Plugin/Unipay/LaunchPlugin.php | 53 +
.../Unipay/OnlineGateway/CancelPlugin.php | 29 +
.../Unipay/OnlineGateway/PagePayPlugin.php | 32 +
.../Unipay/OnlineGateway/QueryPlugin.php | 28 +
.../Unipay/OnlineGateway/RefundPlugin.php | 29 +
.../Unipay/OnlineGateway/WapPayPlugin.php | 32 +
.../Pay/Plugin/Unipay/PreparePlugin.php | 68 +
.../Pay/Plugin/Unipay/QrCode/CancelPlugin.php | 29 +
.../Plugin/Unipay/QrCode/PosNormalPlugin.php | 29 +
.../Plugin/Unipay/QrCode/PosPreAuthPlugin.php | 29 +
.../Pay/Plugin/Unipay/QrCode/QueryPlugin.php | 33 +
.../Pay/Plugin/Unipay/QrCode/RefundPlugin.php | 29 +
.../Plugin/Unipay/QrCode/ScanFeePlugin.php | 29 +
.../Plugin/Unipay/QrCode/ScanNormalPlugin.php | 29 +
.../Unipay/QrCode/ScanPreAuthPlugin.php | 29 +
.../Unipay/QrCode/ScanPreOrderPlugin.php | 29 +
.../Pay/Plugin/Unipay/RadarSignPlugin.php | 100 +
.../Plugin/Unipay/Shortcut/CancelShortcut.php | 42 +
.../Plugin/Unipay/Shortcut/PosShortcut.php | 43 +
.../Plugin/Unipay/Shortcut/QueryShortcut.php | 42 +
.../Plugin/Unipay/Shortcut/RefundShortcut.php | 42 +
.../Plugin/Unipay/Shortcut/ScanShortcut.php | 59 +
.../Plugin/Unipay/Shortcut/WapShortcut.php | 20 +
.../Plugin/Unipay/Shortcut/WebShortcut.php | 20 +
.../Pay/Plugin/Wechat/CallbackPlugin.php | 71 +
.../Wechat/Ecommerce/Refund/ApplyPlugin.php | 55 +
.../Wechat/Ecommerce/Refund/QueryPlugin.php | 60 +
.../Refund/QueryReturnAdvancePlugin.php | 56 +
.../Ecommerce/Refund/ReturnAdvancePlugin.php | 56 +
.../Wechat/Fund/Balance/QueryDayEndPlugin.php | 38 +
.../Wechat/Fund/Balance/QueryPlugin.php | 37 +
.../Fund/Profitsharing/AddReceiverPlugin.php | 83 +
.../Fund/Profitsharing/CreatePlugin.php | 89 +
.../Profitsharing/DeleteReceiverPlugin.php | 45 +
.../Fund/Profitsharing/DownloadBillPlugin.php | 43 +
.../Fund/Profitsharing/QueryAmountsPlugin.php | 42 +
.../QueryMerchantConfigsPlugin.php | 49 +
.../Wechat/Fund/Profitsharing/QueryPlugin.php | 56 +
.../Fund/Profitsharing/QueryReturnPlugin.php | 56 +
.../Fund/Profitsharing/ReturnPlugin.php | 40 +
.../Fund/Profitsharing/UnfreezePlugin.php | 40 +
.../Fund/Transfer/CreateBillReceiptPlugin.php | 33 +
.../Transfer/CreateDetailReceiptPlugin.php | 33 +
.../Wechat/Fund/Transfer/CreatePlugin.php | 99 +
.../Fund/Transfer/DownloadReceiptPlugin.php | 43 +
.../Transfer/QueryBatchDetailIdPlugin.php | 60 +
.../Fund/Transfer/QueryBatchIdPlugin.php | 64 +
.../Fund/Transfer/QueryBillReceiptPlugin.php | 40 +
.../Transfer/QueryDetailReceiptPlugin.php | 40 +
.../Transfer/QueryOutBatchDetailNoPlugin.php | 60 +
.../Fund/Transfer/QueryOutBatchNoPlugin.php | 64 +
.../Pay/Plugin/Wechat/GeneralPlugin.php | 97 +
.../Pay/Plugin/Wechat/GeneralV2Plugin.php | 42 +
.../Pay/Plugin/Wechat/LaunchPlugin.php | 67 +
.../Wechat/Marketing/Coupon/CreatePlugin.php | 37 +
.../Wechat/Marketing/Coupon/PausePlugin.php | 50 +
.../Coupon/QueryCouponDetailPlugin.php | 51 +
.../Coupon/QueryStockDetailPlugin.php | 50 +
.../Coupon/QueryStockItemsPlugin.php | 58 +
.../Coupon/QueryStockMerchantsPlugin.php | 58 +
.../Coupon/QueryStockRefundFlowPlugin.php | 42 +
.../Coupon/QueryStockUseFlowPlugin.php | 42 +
.../Marketing/Coupon/QueryStocksPlugin.php | 44 +
.../Coupon/QueryUserCouponsPlugin.php | 62 +
.../Wechat/Marketing/Coupon/RestartPlugin.php | 51 +
.../Wechat/Marketing/Coupon/SendPlugin.php | 54 +
.../Marketing/Coupon/SetCallbackPlugin.php | 36 +
.../Wechat/Marketing/Coupon/StartPlugin.php | 50 +
.../Pay/Plugin/Wechat/Papay/ApplyPlugin.php | 21 +
.../Wechat/Papay/ContractOrderPlugin.php | 21 +
.../Wechat/Papay/OnlyContractPlugin.php | 70 +
.../Pay/Plugin/Wechat/Pay/App/ClosePlugin.php | 12 +
.../Wechat/Pay/App/DownloadBillPlugin.php | 12 +
.../Wechat/Pay/App/GetFlowBillPlugin.php | 12 +
.../Wechat/Pay/App/GetTradeBillPlugin.php | 12 +
.../Wechat/Pay/App/InvokePrepayPlugin.php | 64 +
.../Wechat/Pay/App/InvokePrepayV2Plugin.php | 49 +
.../Plugin/Wechat/Pay/App/PrepayPlugin.php | 28 +
.../Pay/Plugin/Wechat/Pay/App/QueryPlugin.php | 12 +
.../Wechat/Pay/App/QueryRefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/App/RefundPlugin.php | 12 +
.../Wechat/Pay/Combine/AppPrepayPlugin.php | 19 +
.../Plugin/Wechat/Pay/Combine/ClosePlugin.php | 47 +
.../Wechat/Pay/Combine/DownloadBillPlugin.php | 12 +
.../Wechat/Pay/Combine/GetFlowBillPlugin.php | 12 +
.../Wechat/Pay/Combine/GetTradeBillPlugin.php | 12 +
.../Wechat/Pay/Combine/H5PrepayPlugin.php | 19 +
.../Pay/Combine/InvokeAppPrepayPlugin.php | 14 +
.../Pay/Combine/InvokeJsapiPrepayPlugin.php | 14 +
.../Pay/Combine/InvokeMiniPrepayPlugin.php | 14 +
.../Wechat/Pay/Combine/JsapiPrepayPlugin.php | 14 +
.../Wechat/Pay/Combine/MiniPrepayPlugin.php | 14 +
.../Wechat/Pay/Combine/NativePrepayPlugin.php | 19 +
.../Plugin/Wechat/Pay/Combine/QueryPlugin.php | 27 +
.../Wechat/Pay/Combine/QueryRefundPlugin.php | 12 +
.../Wechat/Pay/Combine/RefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/Common/ClosePlugin.php | 76 +
.../Wechat/Pay/Common/CombinePrepayPlugin.php | 50 +
.../Wechat/Pay/Common/DownloadBillPlugin.php | 40 +
.../Wechat/Pay/Common/GetFlowBillPlugin.php | 26 +
.../Wechat/Pay/Common/GetTradeBillPlugin.php | 26 +
.../Wechat/Pay/Common/InvokePrepayPlugin.php | 110 +
.../Pay/Common/InvokePrepayV2Plugin.php | 40 +
.../Plugin/Wechat/Pay/Common/PrepayPlugin.php | 70 +
.../Plugin/Wechat/Pay/Common/QueryPlugin.php | 79 +
.../Wechat/Pay/Common/QueryRefundPlugin.php | 53 +
.../Plugin/Wechat/Pay/Common/RefundPlugin.php | 41 +
.../Pay/Plugin/Wechat/Pay/H5/ClosePlugin.php | 12 +
.../Wechat/Pay/H5/DownloadBillPlugin.php | 12 +
.../Wechat/Pay/H5/GetFlowBillPlugin.php | 12 +
.../Wechat/Pay/H5/GetTradeBillPlugin.php | 12 +
.../Pay/Plugin/Wechat/Pay/H5/PrepayPlugin.php | 23 +
.../Pay/Plugin/Wechat/Pay/H5/QueryPlugin.php | 12 +
.../Wechat/Pay/H5/QueryRefundPlugin.php | 12 +
.../Pay/Plugin/Wechat/Pay/H5/RefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/Jsapi/ClosePlugin.php | 12 +
.../Wechat/Pay/Jsapi/DownloadBillPlugin.php | 12 +
.../Wechat/Pay/Jsapi/GetFlowBillPlugin.php | 12 +
.../Wechat/Pay/Jsapi/GetTradeBillPlugin.php | 12 +
.../Wechat/Pay/Jsapi/InvokePrepayPlugin.php | 12 +
.../Wechat/Pay/Jsapi/InvokePrepayV2Plugin.php | 12 +
.../Plugin/Wechat/Pay/Jsapi/PrepayPlugin.php | 12 +
.../Plugin/Wechat/Pay/Jsapi/QueryPlugin.php | 12 +
.../Wechat/Pay/Jsapi/QueryRefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/Jsapi/RefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/Mini/ClosePlugin.php | 12 +
.../Wechat/Pay/Mini/DownloadBillPlugin.php | 12 +
.../Wechat/Pay/Mini/GetFlowBillPlugin.php | 12 +
.../Wechat/Pay/Mini/GetTradeBillPlugin.php | 12 +
.../Wechat/Pay/Mini/InvokePrepayPlugin.php | 16 +
.../Wechat/Pay/Mini/InvokePrepayV2Plugin.php | 16 +
.../Plugin/Wechat/Pay/Mini/PrepayPlugin.php | 31 +
.../Plugin/Wechat/Pay/Mini/QueryPlugin.php | 12 +
.../Wechat/Pay/Mini/QueryRefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/Mini/RefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/Native/ClosePlugin.php | 12 +
.../Wechat/Pay/Native/DownloadBillPlugin.php | 12 +
.../Wechat/Pay/Native/GetFlowBillPlugin.php | 12 +
.../Wechat/Pay/Native/GetTradeBillPlugin.php | 12 +
.../Plugin/Wechat/Pay/Native/PrepayPlugin.php | 23 +
.../Plugin/Wechat/Pay/Native/QueryPlugin.php | 12 +
.../Wechat/Pay/Native/QueryRefundPlugin.php | 12 +
.../Plugin/Wechat/Pay/Native/RefundPlugin.php | 12 +
.../Pay/Plugin/Wechat/Pay/Pos/PayPlugin.php | 19 +
.../Pay/Plugin/Wechat/Pay/Pos/QueryPlugin.php | 19 +
.../Wechat/Pay/Pos/QueryRefundPlugin.php | 19 +
.../Pay/Plugin/Wechat/PreparePlugin.php | 30 +
.../Pay/Plugin/Wechat/RadarSignPlugin.php | 170 ++
.../Complaints/CompleteComplaintPlugin.php | 54 +
.../Risk/Complaints/DeleteCallbackPlugin.php | 32 +
.../Risk/Complaints/DownloadMediaPlugin.php | 43 +
.../Risk/Complaints/QueryCallbackPlugin.php | 29 +
.../Complaints/QueryComplaintDetailPlugin.php | 40 +
.../QueryComplaintNegotiationPlugin.php | 45 +
.../Risk/Complaints/QueryComplaintsPlugin.php | 29 +
.../Complaints/ResponseComplaintPlugin.php | 57 +
.../Risk/Complaints/SetCallbackPlugin.php | 23 +
.../Risk/Complaints/UpdateCallbackPlugin.php | 28 +
.../Risk/Complaints/UpdateRefundPlugin.php | 37 +
.../Plugin/Wechat/Shortcut/AppShortcut.php | 20 +
.../Plugin/Wechat/Shortcut/CloseShortcut.php | 46 +
.../Plugin/Wechat/Shortcut/MiniShortcut.php | 20 +
.../Pay/Plugin/Wechat/Shortcut/MpShortcut.php | 20 +
.../Plugin/Wechat/Shortcut/PapayShortcut.php | 92 +
.../Plugin/Wechat/Shortcut/PosShortcut.php | 18 +
.../Plugin/Wechat/Shortcut/QueryShortcut.php | 54 +
.../Plugin/Wechat/Shortcut/RefundShortcut.php | 18 +
.../Plugin/Wechat/Shortcut/ScanShortcut.php | 18 +
.../Wechat/Shortcut/TransferShortcut.php | 18 +
.../Plugin/Wechat/Shortcut/WapShortcut.php | 18 +
.../Plugin/Wechat/WechatPublicCertsPlugin.php | 25 +
.../Pay/Provider/AbstractProvider.php | 159 ++
.../v3/Yansongda/Pay/Provider/Alipay.php | 178 ++
.../v3/Yansongda/Pay/Provider/Unipay.php | 168 ++
.../v3/Yansongda/Pay/Provider/Wechat.php | 178 ++
.../epay/library/v3/Yansongda/Pay/Request.php | 27 +
.../epay/library/v3/Yansongda/Pay/Rocket.php | 148 ++
.../Pay/Service/AlipayServiceProvider.php | 26 +
.../Pay/Service/ConfigServiceProvider.php | 45 +
.../Pay/Service/ContainerServiceProvider.php | 107 +
.../Pay/Service/EventServiceProvider.php | 26 +
.../Pay/Service/HttpServiceProvider.php | 35 +
.../Pay/Service/LoggerServiceProvider.php | 37 +
.../Pay/Service/UnipayServiceProvider.php | 26 +
.../Pay/Service/WechatServiceProvider.php | 26 +
.../Yansongda/Pay/Traits/GetUnipayCerts.php | 61 +
.../Pay/Traits/HasWechatEncryption.php | 61 +
.../Traits/SupportServiceProviderTrait.php | 35 +
.../library/v3/Yansongda/Supports/Arr.php | 624 ++++++
.../v3/Yansongda/Supports/Collection.php | 531 +++++
.../library/v3/Yansongda/Supports/Config.php | 9 +
.../v3/Yansongda/Supports/Functions.php | 73 +
.../library/v3/Yansongda/Supports/Logger.php | 174 ++
.../Supports/Logger/StdoutHandler.php | 33 +
.../v3/Yansongda/Supports/Pipeline.php | 158 ++
.../library/v3/Yansongda/Supports/Str.php | 587 ++++++
.../Yansongda/Supports/Traits/Accessable.php | 146 ++
.../Yansongda/Supports/Traits/Arrayable.php | 28 +
.../Supports/Traits/Serializable.php | 65 +
addons/epay/library/v3/version.txt | 1 +
.../library/v3new/Yansongda/Artful/Artful.php | 342 +++
.../Artful/Contract/ConfigInterface.php | 14 +
.../Artful/Contract/ContainerInterface.php | 12 +
.../Artful/Contract/DirectionInterface.php | 12 +
.../Contract/EventDispatcherInterface.php | 7 +
.../Contract/HttpClientFactoryInterface.php | 12 +
.../Artful/Contract/HttpClientInterface.php | 9 +
.../Artful/Contract/LoggerInterface.php | 7 +
.../Artful/Contract/PackerInterface.php | 14 +
.../Artful/Contract/PluginInterface.php | 13 +
.../Contract/ServiceProviderInterface.php | 15 +
.../Artful/Contract/ShortcutInterface.php | 13 +
.../Artful/Direction/CollectionDirection.php | 33 +
.../Direction/NoHttpRequestDirection.php | 17 +
.../Direction/OriginResponseDirection.php | 26 +
.../Artful/Direction/ResponseDirection.php | 7 +
.../library/v3new/Yansongda/Artful/Event.php | 39 +
.../Yansongda/Artful/Event/ArtfulEnd.php | 7 +
.../Yansongda/Artful/Event/ArtfulStart.php | 10 +
.../v3new/Yansongda/Artful/Event/Event.php | 12 +
.../v3new/Yansongda/Artful/Event/HttpEnd.php | 7 +
.../Yansongda/Artful/Event/HttpStart.php | 7 +
.../Artful/Exception/ContainerException.php | 16 +
.../Exception/ContainerNotFoundException.php | 15 +
.../Yansongda/Artful/Exception/Exception.php | 69 +
.../Exception/InvalidConfigException.php | 18 +
.../Exception/InvalidParamsException.php | 15 +
.../Exception/InvalidResponseException.php | 23 +
.../Exception/ServiceNotFoundException.php | 16 +
.../v3new/Yansongda/Artful/Functions.php | 93 +
.../Yansongda/Artful/HttpClientFactory.php | 39 +
.../library/v3new/Yansongda/Artful/Logger.php | 49 +
.../Yansongda/Artful/Packer/JsonPacker.php | 26 +
.../Yansongda/Artful/Packer/QueryPacker.php | 22 +
.../Yansongda/Artful/Packer/XmlPacker.php | 22 +
.../Artful/Plugin/AddPayloadBodyPlugin.php | 33 +
.../Artful/Plugin/AddRadarPlugin.php | 45 +
.../Yansongda/Artful/Plugin/ParserPlugin.php | 45 +
.../Yansongda/Artful/Plugin/StartPlugin.php | 24 +
.../library/v3new/Yansongda/Artful/Rocket.php | 180 ++
.../Artful/Service/ConfigServiceProvider.php | 42 +
.../Service/ContainerServiceProvider.php | 108 +
.../Artful/Service/EventServiceProvider.php | 24 +
.../Artful/Service/HttpServiceProvider.php | 24 +
.../Artful/Service/LoggerServiceProvider.php | 71 +
.../Pay/Contract/ProviderInterface.php | 28 +
.../Yansongda/Pay/Event/CallbackReceived.php | 27 +
.../Yansongda/Pay/Event/MethodCalled.php | 26 +
.../v3new/Yansongda/Pay/Event/PayEnd.php | 9 +
.../v3new/Yansongda/Pay/Event/PayStart.php | 27 +
.../Pay/Exception/DecryptException.php | 19 +
.../Yansongda/Pay/Exception/Exception.php | 92 +
.../Pay/Exception/InvalidSignException.php | 23 +
.../library/v3new/Yansongda/Pay/Functions.php | 656 ++++++
.../epay/library/v3new/Yansongda/Pay/Pay.php | 108 +
.../Alipay/V2/AddPayloadSignaturePlugin.php | 68 +
.../Pay/Plugin/Alipay/V2/AddRadarPlugin.php | 55 +
.../Plugin/Alipay/V2/AppCallbackPlugin.php | 55 +
.../Pay/Plugin/Alipay/V2/CallbackPlugin.php | 48 +
.../V2/FormatPayloadBizContentPlugin.php | 33 +
.../PCreditPayInstallment/AppPayPlugin.php | 32 +
.../PCreditPayInstallment/H5PayPlugin.php | 48 +
.../PCreditPayInstallment/PosPayPlugin.php | 47 +
.../PCreditPayInstallment/ScanPayPlugin.php | 41 +
.../V2/Fund/Royalty/Query/OnsettlePlugin.php | 30 +
.../V2/Fund/Royalty/Query/RatePlugin.php | 30 +
.../V2/Fund/Royalty/Query/SettlePlugin.php | 30 +
.../V2/Fund/Royalty/Relation/BindPlugin.php | 30 +
.../V2/Fund/Royalty/Relation/QueryPlugin.php | 30 +
.../V2/Fund/Royalty/Relation/UnbindPlugin.php | 30 +
.../Royalty/Request/SettleOrderPlugin.php | 30 +
.../V2/Fund/Transfer/ApplyReceiptPlugin.php | 30 +
.../V2/Fund/Transfer/Bill/QueryUrlPlugin.php | 30 +
.../V2/Fund/Transfer/Fund/QueryPlugin.php | 30 +
.../V2/Fund/Transfer/Fund/TransferPlugin.php | 37 +
.../V2/Fund/Transfer/QueryAccountPlugin.php | 30 +
.../V2/Fund/Transfer/QueryReceiptPlugin.php | 30 +
.../Alipay/V2/Fund/Transfer/RefundPlugin.php | 30 +
.../V2/Marketing/Redpack/AppPayPlugin.php | 38 +
.../V2/Marketing/Redpack/WebPayPlugin.php | 37 +
.../V2/Member/Authorization/AuthPlugin.php | 30 +
.../V2/Member/Authorization/QueryPlugin.php | 30 +
.../V2/Member/Authorization/TokenPlugin.php | 32 +
.../V2/Member/Certification/CertifyPlugin.php | 30 +
.../V2/Member/Certification/InitPlugin.php | 35 +
.../V2/Member/Certification/QueryPlugin.php | 30 +
.../Plugin/Alipay/V2/Member/DetailPlugin.php | 33 +
.../V2/Member/FaceCheck/AppInitPlugin.php | 35 +
.../V2/Member/FaceCheck/AppQueryPlugin.php | 30 +
.../Member/FaceVerification/AppInitPlugin.php | 36 +
.../FaceVerification/AppQueryPlugin.php | 30 +
.../Member/FaceVerification/H5InitPlugin.php | 35 +
.../Member/FaceVerification/H5QueryPlugin.php | 30 +
.../FaceVerification/H5VerifyPlugin.php | 30 +
.../FaceVerification/ServerVerifyPlugin.php | 35 +
.../Alipay/V2/Member/Ocr/AppInitPlugin.php | 35 +
.../Alipay/V2/Member/Ocr/DetectPlugin.php | 30 +
.../V2/Member/Ocr/ServerDetectPlugin.php | 30 +
.../V2/Pay/Agreement/Bill/QueryUrlPlugin.php | 30 +
.../V2/Pay/Agreement/Pay/AppPayPlugin.php | 32 +
.../V2/Pay/Agreement/Pay/CancelPlugin.php | 30 +
.../V2/Pay/Agreement/Pay/ClosePlugin.php | 30 +
.../Alipay/V2/Pay/Agreement/Pay/PayPlugin.php | 46 +
.../V2/Pay/Agreement/Pay/QueryPlugin.php | 30 +
.../V2/Pay/Agreement/Pay/RefundPlugin.php | 30 +
.../V2/Pay/Agreement/Sign/ModifyPlugin.php | 30 +
.../V2/Pay/Agreement/Sign/QueryPlugin.php | 30 +
.../V2/Pay/Agreement/Sign/SignPlugin.php | 32 +
.../V2/Pay/Agreement/Sign/UnsignPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/App/ClosePlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/App/PayPlugin.php | 43 +
.../Alipay/V2/Pay/App/QueryBillUrlPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/App/QueryPlugin.php | 30 +
.../Alipay/V2/Pay/App/QueryRefundPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/App/RefundPlugin.php | 30 +
.../Authorization/Auth/AppFreezePlugin.php | 37 +
.../Pay/Authorization/Auth/CancelPlugin.php | 30 +
.../Authorization/Auth/PosFreezePlugin.php | 36 +
.../V2/Pay/Authorization/Auth/QueryPlugin.php | 30 +
.../Authorization/Auth/ScanFreezePlugin.php | 35 +
.../Pay/Authorization/Auth/UnfreezePlugin.php | 30 +
.../Pay/Authorization/Bill/QueryUrlPlugin.php | 30 +
.../V2/Pay/Authorization/Pay/ClosePlugin.php | 30 +
.../V2/Pay/Authorization/Pay/PayPlugin.php | 46 +
.../V2/Pay/Authorization/Pay/QueryPlugin.php | 30 +
.../Authorization/Pay/QueryRefundPlugin.php | 30 +
.../V2/Pay/Authorization/Pay/RefundPlugin.php | 30 +
.../V2/Pay/Authorization/Pay/SyncPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Face/InitPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Face/QueryPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/H5/ClosePlugin.php | 30 +
.../Pay/Plugin/Alipay/V2/Pay/H5/PayPlugin.php | 43 +
.../Alipay/V2/Pay/H5/QueryBillUrlPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/H5/QueryPlugin.php | 30 +
.../Alipay/V2/Pay/H5/QueryRefundPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/H5/RefundPlugin.php | 30 +
.../Alipay/V2/Pay/Mini/CancelPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Mini/PayPlugin.php | 46 +
.../Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php | 30 +
.../Alipay/V2/Pay/Mini/QueryRefundPlugin.php | 30 +
.../Alipay/V2/Pay/Mini/RefundPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Pos/PayPlugin.php | 46 +
.../Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php | 30 +
.../Alipay/V2/Pay/Pos/QueryRefundPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php | 30 +
.../Alipay/V2/Pay/Scan/CancelPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php | 30 +
.../Alipay/V2/Pay/Scan/CreatePlugin.php | 41 +
.../Plugin/Alipay/V2/Pay/Scan/PayPlugin.php | 41 +
.../Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php | 30 +
.../Alipay/V2/Pay/Scan/QueryRefundPlugin.php | 30 +
.../Alipay/V2/Pay/Scan/RefundPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Web/ClosePlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Web/PayPlugin.php | 48 +
.../Alipay/V2/Pay/Web/QueryBillUrlPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Web/QueryPlugin.php | 30 +
.../Alipay/V2/Pay/Web/QueryRefundPlugin.php | 30 +
.../Plugin/Alipay/V2/Pay/Web/RefundPlugin.php | 30 +
.../Plugin/Alipay/V2/ResponseHtmlPlugin.php | 71 +
.../Alipay/V2/ResponseInvokeStringPlugin.php | 31 +
.../Pay/Plugin/Alipay/V2/ResponsePlugin.php | 51 +
.../Pay/Plugin/Alipay/V2/StartPlugin.php | 210 ++
.../Alipay/V2/VerifySignaturePlugin.php | 57 +
.../V1/Pay/AddPayloadSignaturePlugin.php | 94 +
.../Plugin/Douyin/V1/Pay/AddRadarPlugin.php | 55 +
.../Plugin/Douyin/V1/Pay/CallbackPlugin.php | 74 +
.../Plugin/Douyin/V1/Pay/Mini/PayPlugin.php | 72 +
.../Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php | 66 +
.../Douyin/V1/Pay/Mini/QueryRefundPlugin.php | 66 +
.../Douyin/V1/Pay/Mini/RefundPlugin.php | 72 +
.../Plugin/Douyin/V1/Pay/ResponsePlugin.php | 51 +
.../Pay/Plugin/Jsb/AddPayloadSignPlugin.php | 65 +
.../Pay/Plugin/Jsb/AddRadarPlugin.php | 68 +
.../Pay/Plugin/Jsb/CallbackPlugin.php | 70 +
.../Pay/Plugin/Jsb/Pay/Scan/PayPlugin.php | 49 +
.../Pay/Plugin/Jsb/Pay/Scan/QueryPlugin.php | 30 +
.../Pay/Plugin/Jsb/Pay/Scan/RefundPlugin.php | 30 +
.../Pay/Plugin/Jsb/ResponsePlugin.php | 52 +
.../Yansongda/Pay/Plugin/Jsb/StartPlugin.php | 49 +
.../Pay/Plugin/Jsb/VerifySignaturePlugin.php | 76 +
.../Pay/Plugin/Unipay/AddRadarPlugin.php | 55 +
.../Unipay/Open/AddPayloadSignaturePlugin.php | 63 +
.../Pay/Plugin/Unipay/Open/CallbackPlugin.php | 49 +
.../Plugin/Unipay/Open/Pay/H5/PayPlugin.php | 56 +
.../Unipay/Open/Pay/QrCode/CancelPlugin.php | 54 +
.../Unipay/Open/Pay/QrCode/PosPlugin.php | 55 +
.../Open/Pay/QrCode/PosPreAuthPlugin.php | 55 +
.../Unipay/Open/Pay/QrCode/QueryPlugin.php | 53 +
.../Unipay/Open/Pay/QrCode/RefundPlugin.php | 54 +
.../Unipay/Open/Pay/QrCode/ScanFeePlugin.php | 55 +
.../Unipay/Open/Pay/QrCode/ScanPlugin.php | 55 +
.../Open/Pay/QrCode/ScanPreAuthPlugin.php | 55 +
.../Open/Pay/QrCode/ScanPreOrderPlugin.php | 56 +
.../Unipay/Open/Pay/Web/CancelPlugin.php | 54 +
.../Plugin/Unipay/Open/Pay/Web/PayPlugin.php | 58 +
.../Unipay/Open/Pay/Web/QueryPlugin.php | 52 +
.../Unipay/Open/Pay/Web/RefundPlugin.php | 54 +
.../Plugin/Unipay/Open/ResponseHtmlPlugin.php | 48 +
.../Pay/Plugin/Unipay/Open/StartPlugin.php | 91 +
.../Unipay/Open/VerifySignaturePlugin.php | 60 +
.../Unipay/Qra/AddPayloadSignaturePlugin.php | 47 +
.../Pay/Plugin/Unipay/Qra/CallbackPlugin.php | 49 +
.../Plugin/Unipay/Qra/Pos/CancelPlugin.php | 50 +
.../Pay/Plugin/Unipay/Qra/Pos/PayPlugin.php | 50 +
.../Unipay/Qra/Pos/QueryOpenIdPlugin.php | 50 +
.../Pay/Plugin/Unipay/Qra/Pos/QueryPlugin.php | 50 +
.../Unipay/Qra/Pos/QueryRefundPlugin.php | 50 +
.../Plugin/Unipay/Qra/Pos/RefundPlugin.php | 50 +
.../Plugin/Unipay/Qra/Scan/ClosePlugin.php | 50 +
.../Pay/Plugin/Unipay/Qra/Scan/PayPlugin.php | 52 +
.../Plugin/Unipay/Qra/Scan/QueryPlugin.php | 50 +
.../Unipay/Qra/Scan/QueryRefundPlugin.php | 50 +
.../Plugin/Unipay/Qra/Scan/RefundPlugin.php | 50 +
.../Pay/Plugin/Unipay/Qra/StartPlugin.php | 26 +
.../Unipay/Qra/VerifySignaturePlugin.php | 47 +
.../Pay/Plugin/Wechat/AddRadarPlugin.php | 76 +
.../Pay/Plugin/Wechat/ResponsePlugin.php | 46 +
.../Pay/Plugin/Wechat/StartPlugin.php | 26 +
.../Wechat/V2/AddPayloadSignaturePlugin.php | 40 +
.../Wechat/V2/Papay/Direct/ApplyPlugin.php | 53 +
.../V2/Papay/Direct/ContractOrderPlugin.php | 56 +
.../Papay/Direct/MiniOnlyContractPlugin.php | 52 +
.../Plugin/Wechat/V2/Pay/App/InvokePlugin.php | 97 +
.../Wechat/V2/Pay/Mini/InvokePlugin.php | 91 +
.../Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php | 51 +
.../Plugin/Wechat/V2/Pay/Pos/PayPlugin.php | 51 +
.../Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php | 51 +
.../Wechat/V2/Pay/Redpack/SendPlugin.php | 81 +
.../Wechat/V2/VerifySignaturePlugin.php | 47 +
.../Wechat/V3/AddPayloadSignaturePlugin.php | 99 +
.../Pay/Plugin/Wechat/V3/CallbackPlugin.php | 76 +
.../V3/Extend/Complaints/CompletePlugin.php | 52 +
.../Complaints/DeleteCallbackPlugin.php | 32 +
.../Extend/Complaints/QueryCallbackPlugin.php | 32 +
.../Extend/Complaints/QueryDetailPlugin.php | 72 +
.../V3/Extend/Complaints/QueryImagePlugin.php | 43 +
.../Complaints/QueryNegotiationPlugin.php | 56 +
.../V3/Extend/Complaints/QueryPlugin.php | 47 +
.../V3/Extend/Complaints/ResponsePlugin.php | 52 +
.../Extend/Complaints/SetCallbackPlugin.php | 35 +
.../Complaints/UpdateCallbackPlugin.php | 35 +
.../Extend/Complaints/UpdateRefundPlugin.php | 43 +
.../ProfitSharing/AddReceiverPlugin.php | 134 ++
.../V3/Extend/ProfitSharing/CreatePlugin.php | 136 ++
.../ProfitSharing/DeleteReceiverPlugin.php | 84 +
.../ProfitSharing/DownloadBillPlugin.php | 45 +
.../V3/Extend/ProfitSharing/GetBillPlugin.php | 47 +
.../ProfitSharing/QueryAmountsPlugin.php | 43 +
.../QueryMerchantConfigsPlugin.php | 50 +
.../V3/Extend/ProfitSharing/QueryPlugin.php | 53 +
.../ProfitSharing/QueryReturnPlugin.php | 53 +
.../V3/Extend/ProfitSharing/ReturnPlugin.php | 75 +
.../Extend/ProfitSharing/UnfreezePlugin.php | 73 +
.../Marketing/Coupon/Callback/QueryPlugin.php | 44 +
.../Marketing/Coupon/Callback/SetPlugin.php | 48 +
.../Marketing/Coupon/Coupons/DetailPlugin.php | 55 +
.../Coupon/Coupons/QueryUserPlugin.php | 66 +
.../Marketing/Coupon/Coupons/SendPlugin.php | 70 +
.../Marketing/Coupon/Stock/CreatePlugin.php | 45 +
.../V3/Marketing/Coupon/Stock/PausePlugin.php | 54 +
.../Coupon/Stock/QueryDetailPlugin.php | 53 +
.../Coupon/Stock/QueryItemsPlugin.php | 65 +
.../Coupon/Stock/QueryMerchantsPlugin.php | 65 +
.../V3/Marketing/Coupon/Stock/QueryPlugin.php | 64 +
.../Coupon/Stock/QueryRefundFlowPlugin.php | 43 +
.../Coupon/Stock/QueryUseFlowPlugin.php | 43 +
.../Marketing/Coupon/Stock/RestartPlugin.php | 54 +
.../V3/Marketing/Coupon/Stock/StartPlugin.php | 53 +
.../ECommerceBalance/QueryDayEndPlugin.php | 67 +
.../ECommerceBalance/QueryPlugin.php | 53 +
.../Marketing/ECommerceRefund/ApplyPlugin.php | 60 +
.../ECommerceRefund/QueryByWxPlugin.php | 55 +
.../Marketing/ECommerceRefund/QueryPlugin.php | 55 +
.../QueryReturnAdvancePlugin.php | 55 +
.../ECommerceRefund/ReturnAdvancePlugin.php | 56 +
.../Fapiao/Blockchain/CreatePlugin.php | 80 +
.../Fapiao/Blockchain/DownloadPlugin.php | 46 +
.../Blockchain/GetBaseInformationPlugin.php | 30 +
.../Blockchain/GetDownloadInfoPlugin.php | 43 +
.../Fapiao/Blockchain/GetTaxCodePlugin.php | 43 +
.../Fapiao/Blockchain/ReversePlugin.php | 47 +
.../Fapiao/CreateCardTemplatePlugin.php | 43 +
.../V3/Marketing/Fapiao/GetTitleUrlPlugin.php | 64 +
.../V3/Marketing/Fapiao/QueryConfigPlugin.php | 30 +
.../V3/Marketing/Fapiao/QueryPlugin.php | 43 +
.../Marketing/Fapiao/QueryUserTitlePlugin.php | 43 +
.../Marketing/Fapiao/UpdateConfigPlugin.php | 30 +
.../V3/Marketing/MchTransfer/CancelPlugin.php | 54 +
.../V3/Marketing/MchTransfer/CreatePlugin.php | 102 +
.../MchTransfer/InvokeAndroidPlugin.php | 77 +
.../Marketing/MchTransfer/InvokeIosPlugin.php | 77 +
.../MchTransfer/InvokeJsapiPlugin.php | 74 +
.../Marketing/MchTransfer/QueryByWxPlugin.php | 54 +
.../V3/Marketing/MchTransfer/QueryPlugin.php | 54 +
.../Transfer/Batch/QueryByWxPlugin.php | 57 +
.../Marketing/Transfer/Batch/QueryPlugin.php | 57 +
.../V3/Marketing/Transfer/CreatePlugin.php | 109 +
.../Transfer/Detail/QueryByWxPlugin.php | 57 +
.../Marketing/Transfer/Detail/QueryPlugin.php | 57 +
.../Transfer/DownloadReceiptPlugin.php | 57 +
.../Transfer/Receipt/CreatePlugin.php | 50 +
.../Transfer/Receipt/QueryPlugin.php | 55 +
.../Transfer/ReceiptDetail/CreatePlugin.php | 50 +
.../Transfer/ReceiptDetail/QueryPlugin.php | 56 +
.../Plugin/Wechat/V3/Pay/App/ClosePlugin.php | 78 +
.../Wechat/V3/Pay/App/DownloadBillPlugin.php | 45 +
.../Wechat/V3/Pay/App/GetFundBillPlugin.php | 47 +
.../Wechat/V3/Pay/App/GetTradeBillPlugin.php | 47 +
.../Plugin/Wechat/V3/Pay/App/InvokePlugin.php | 111 +
.../Plugin/Wechat/V3/Pay/App/PayPlugin.php | 78 +
.../Wechat/V3/Pay/App/QueryByWxPlugin.php | 68 +
.../Plugin/Wechat/V3/Pay/App/QueryPlugin.php | 68 +
.../Wechat/V3/Pay/App/QueryRefundPlugin.php | 54 +
.../Plugin/Wechat/V3/Pay/App/RefundPlugin.php | 73 +
.../Wechat/V3/Pay/Bill/DownloadPlugin.php | 45 +
.../Wechat/V3/Pay/Bill/GetFundPlugin.php | 47 +
.../Wechat/V3/Pay/Bill/GetTradePlugin.php | 47 +
.../Wechat/V3/Pay/Combine/AppInvokePlugin.php | 111 +
.../Wechat/V3/Pay/Combine/AppPayPlugin.php | 54 +
.../Wechat/V3/Pay/Combine/ClosePlugin.php | 57 +
.../V3/Pay/Combine/DownloadBillPlugin.php | 45 +
.../V3/Pay/Combine/GetFundBillPlugin.php | 47 +
.../V3/Pay/Combine/GetTradeBillPlugin.php | 47 +
.../Wechat/V3/Pay/Combine/H5PayPlugin.php | 55 +
.../V3/Pay/Combine/JsapiInvokePlugin.php | 106 +
.../Wechat/V3/Pay/Combine/JsapiPayPlugin.php | 55 +
.../V3/Pay/Combine/MiniInvokePlugin.php | 105 +
.../Wechat/V3/Pay/Combine/MiniPayPlugin.php | 54 +
.../Wechat/V3/Pay/Combine/NativePayPlugin.php | 55 +
.../Wechat/V3/Pay/Combine/QueryPlugin.php | 43 +
.../V3/Pay/Combine/QueryRefundPlugin.php | 54 +
.../Wechat/V3/Pay/Combine/RefundPlugin.php | 73 +
.../Plugin/Wechat/V3/Pay/H5/ClosePlugin.php | 78 +
.../Wechat/V3/Pay/H5/DownloadBillPlugin.php | 45 +
.../Wechat/V3/Pay/H5/GetFundBillPlugin.php | 47 +
.../Wechat/V3/Pay/H5/GetTradeBillPlugin.php | 47 +
.../Pay/Plugin/Wechat/V3/Pay/H5/PayPlugin.php | 81 +
.../Wechat/V3/Pay/H5/QueryByWxPlugin.php | 68 +
.../Plugin/Wechat/V3/Pay/H5/QueryPlugin.php | 68 +
.../Wechat/V3/Pay/H5/QueryRefundPlugin.php | 54 +
.../Plugin/Wechat/V3/Pay/H5/RefundPlugin.php | 73 +
.../Wechat/V3/Pay/Jsapi/ClosePlugin.php | 78 +
.../V3/Pay/Jsapi/DownloadBillPlugin.php | 45 +
.../Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php | 47 +
.../V3/Pay/Jsapi/GetTradeBillPlugin.php | 47 +
.../Wechat/V3/Pay/Jsapi/InvokePlugin.php | 106 +
.../Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php | 87 +
.../Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php | 68 +
.../Wechat/V3/Pay/Jsapi/QueryPlugin.php | 68 +
.../Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php | 54 +
.../Wechat/V3/Pay/Jsapi/RefundPlugin.php | 73 +
.../Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php | 78 +
.../Wechat/V3/Pay/Mini/DownloadBillPlugin.php | 45 +
.../Wechat/V3/Pay/Mini/GetFundBillPlugin.php | 47 +
.../Wechat/V3/Pay/Mini/GetTradeBillPlugin.php | 47 +
.../Wechat/V3/Pay/Mini/InvokePlugin.php | 105 +
.../Plugin/Wechat/V3/Pay/Mini/PayPlugin.php | 84 +
.../Wechat/V3/Pay/Mini/QueryByWxPlugin.php | 68 +
.../Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php | 68 +
.../Wechat/V3/Pay/Mini/QueryRefundPlugin.php | 54 +
.../Wechat/V3/Pay/Mini/RefundPlugin.php | 73 +
.../Wechat/V3/Pay/Native/ClosePlugin.php | 78 +
.../V3/Pay/Native/DownloadBillPlugin.php | 45 +
.../V3/Pay/Native/GetFundBillPlugin.php | 47 +
.../V3/Pay/Native/GetTradeBillPlugin.php | 47 +
.../Plugin/Wechat/V3/Pay/Native/PayPlugin.php | 81 +
.../Wechat/V3/Pay/Native/QueryByWxPlugin.php | 68 +
.../Wechat/V3/Pay/Native/QueryPlugin.php | 68 +
.../V3/Pay/Native/QueryRefundPlugin.php | 54 +
.../Wechat/V3/Pay/Native/RefundPlugin.php | 73 +
.../Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php | 83 +
.../Plugin/Wechat/V3/Pay/Pos/PayPlugin.php | 81 +
.../Wechat/V3/Pay/Refund/QueryPlugin.php | 54 +
.../V3/Pay/Refund/RefundAbnormalPlugin.php | 118 ++
.../Wechat/V3/Pay/Refund/RefundPlugin.php | 73 +
.../Wechat/V3/VerifySignaturePlugin.php | 48 +
.../Wechat/V3/WechatPublicCertsPlugin.php | 27 +
.../v3new/Yansongda/Pay/Provider/Alipay.php | 175 ++
.../v3new/Yansongda/Pay/Provider/Douyin.php | 154 ++
.../v3new/Yansongda/Pay/Provider/Jsb.php | 155 ++
.../v3new/Yansongda/Pay/Provider/Unipay.php | 156 ++
.../v3new/Yansongda/Pay/Provider/Wechat.php | 171 ++
.../Pay/Service/AlipayServiceProvider.php | 24 +
.../Pay/Service/DouyinServiceProvider.php | 24 +
.../Pay/Service/JsbServiceProvider.php | 24 +
.../Pay/Service/UnipayServiceProvider.php | 24 +
.../Pay/Service/WechatServiceProvider.php | 24 +
.../Pay/Shortcut/Alipay/AppShortcut.php | 28 +
.../Pay/Shortcut/Alipay/CancelShortcut.php | 114 +
.../Pay/Shortcut/Alipay/CloseShortcut.php | 159 ++
.../Pay/Shortcut/Alipay/H5Shortcut.php | 30 +
.../Pay/Shortcut/Alipay/MiniShortcut.php | 32 +
.../Pay/Shortcut/Alipay/PosShortcut.php | 32 +
.../Pay/Shortcut/Alipay/QueryShortcut.php | 303 +++
.../Pay/Shortcut/Alipay/RefundShortcut.php | 174 ++
.../Pay/Shortcut/Alipay/ScanShortcut.php | 32 +
.../Pay/Shortcut/Alipay/TransferShortcut.php | 32 +
.../Pay/Shortcut/Alipay/WebShortcut.php | 30 +
.../Pay/Shortcut/Douyin/MiniShortcut.php | 30 +
.../Pay/Shortcut/Douyin/QueryShortcut.php | 71 +
.../Pay/Shortcut/Douyin/RefundShortcut.php | 52 +
.../Pay/Shortcut/Jsb/QueryShortcut.php | 30 +
.../Pay/Shortcut/Jsb/RefundShortcut.php | 30 +
.../Pay/Shortcut/Jsb/ScanShortcut.php | 30 +
.../Pay/Shortcut/Unipay/CancelShortcut.php | 83 +
.../Pay/Shortcut/Unipay/H5Shortcut.php | 30 +
.../Pay/Shortcut/Unipay/PosShortcut.php | 78 +
.../Pay/Shortcut/Unipay/QueryShortcut.php | 97 +
.../Pay/Shortcut/Unipay/RefundShortcut.php | 83 +
.../Pay/Shortcut/Unipay/ScanShortcut.php | 89 +
.../Pay/Shortcut/Unipay/WebShortcut.php | 30 +
.../Pay/Shortcut/Wechat/AppShortcut.php | 34 +
.../Pay/Shortcut/Wechat/CloseShortcut.php | 133 ++
.../Pay/Shortcut/Wechat/H5Shortcut.php | 32 +
.../Pay/Shortcut/Wechat/MiniShortcut.php | 34 +
.../Pay/Shortcut/Wechat/MpShortcut.php | 34 +
.../Pay/Shortcut/Wechat/PapayShortcut.php | 102 +
.../Pay/Shortcut/Wechat/PosShortcut.php | 32 +
.../Pay/Shortcut/Wechat/QueryShortcut.php | 243 +++
.../Pay/Shortcut/Wechat/RedpackShortcut.php | 32 +
.../Pay/Shortcut/Wechat/RefundShortcut.php | 129 ++
.../Pay/Shortcut/Wechat/ScanShortcut.php | 32 +
.../Pay/Shortcut/Wechat/TransferShortcut.php | 68 +
.../Traits/SupportServiceProviderTrait.php | 35 +
.../library/v3new/Yansongda/Supports/Arr.php | 550 +++++
.../v3new/Yansongda/Supports/Collection.php | 361 ++++
.../v3new/Yansongda/Supports/Config.php | 7 +
.../v3new/Yansongda/Supports/Functions.php | 57 +
.../v3new/Yansongda/Supports/Pipeline.php | 115 ++
.../library/v3new/Yansongda/Supports/Str.php | 455 ++++
.../Yansongda/Supports/Traits/Accessable.php | 73 +
.../Yansongda/Supports/Traits/Arrayable.php | 25 +
.../Supports/Traits/Serializable.php | 58 +
addons/epay/library/v3new/version.txt | 1 +
addons/epay/licenses/yansongda-pay.txt | 20 +
addons/epay/view/api/alipay.html | 47 +
addons/epay/view/api/wechat.html | 90 +
addons/epay/view/index/index.html | 107 +
addons/epay/view/layout/default.html | 103 +
addons/wechat/.addonrc | 1 +
addons/wechat/Wechat.php | 110 +
addons/wechat/config.php | 100 +
addons/wechat/controller/Captcha.php | 74 +
addons/wechat/controller/Index.php | 166 ++
addons/wechat/info.ini | 10 +
addons/wechat/install.sql | 81 +
addons/wechat/library/Config.php | 82 +
addons/wechat/library/Response.php | 27 +
addons/wechat/library/Wechat.php | 129 ++
addons/wechat/library/response/Ask.php | 94 +
addons/wechat/library/response/Blog.php | 71 +
addons/wechat/library/response/Cms.php | 119 ++
addons/wechat/library/response/Signin.php | 70 +
addons/wechat/library/response/Vote.php | 96 +
addons/wechat/model/WechatAutoreply.php | 16 +
addons/wechat/model/WechatCaptcha.php | 77 +
addons/wechat/model/WechatConfig.php | 32 +
addons/wechat/model/WechatContext.php | 16 +
addons/wechat/model/WechatResponse.php | 16 +
application/admin/command/FcInstall.php | 224 +-
application/admin/controller/fc/Coupon.php | 231 +++
application/admin/controller/fc/Dashboard.php | 122 ++
application/admin/controller/fc/Setting.php | 168 ++
application/admin/lang/zh-cn/fc/coupon.php | 27 +
application/admin/lang/zh-cn/fc/dashboard.php | 26 +
application/admin/lang/zh-cn/fc/setting.php | 67 +
application/admin/view/fc/coupon/add.html | 67 +
application/admin/view/fc/coupon/edit.html | 74 +
application/admin/view/fc/coupon/index.html | 24 +
.../admin/view/fc/dashboard/index.html | 158 ++
application/admin/view/fc/merchant/add.html | 30 +
application/admin/view/fc/merchant/edit.html | 30 +
application/admin/view/fc/setting/index.html | 124 ++
application/api/controller/AdminSetting.php | 24 +-
application/api/controller/Coupon.php | 144 ++
application/api/controller/Favorite.php | 204 ++
application/api/controller/Home.php | 96 +-
application/api/controller/Member.php | 155 +-
application/api/controller/Order.php | 110 +
application/api/controller/Pay.php | 353 ++++
application/api/controller/Post.php | 353 +++-
application/api/controller/Report.php | 75 +
application/config.php | 2 +-
application/route.php | 24 +
docs/05-后端实现规范.md | 75 +-
public/assets/addons/epay/css/common.css | 178 ++
public/assets/addons/epay/images/alipay.png | Bin 0 -> 3651 bytes
public/assets/addons/epay/images/expired.png | Bin 0 -> 4731 bytes
.../assets/addons/epay/images/logo-alipay.png | Bin 0 -> 1682 bytes
.../assets/addons/epay/images/logo-wechat.png | Bin 0 -> 1773 bytes
public/assets/addons/epay/images/paid.png | Bin 0 -> 2294 bytes
public/assets/addons/epay/images/scan.png | Bin 0 -> 922 bytes
.../addons/epay/images/screenshot-alipay.png | Bin 0 -> 66111 bytes
.../addons/epay/images/screenshot-wechat.png | Bin 0 -> 21909 bytes
public/assets/addons/epay/images/wechat.png | Bin 0 -> 22720 bytes
public/assets/addons/epay/js/common.js | 63 +
.../addons/epay/js/jquery.qrcode.min.js | 34 +
public/assets/addons/epay/less/common.less | 224 ++
public/assets/addons/wechat/css/menu.css | 383 ++++
.../addons/wechat/images/mobile_footer_bg.png | Bin 0 -> 1348 bytes
.../addons/wechat/images/mobile_header_bg.png | Bin 0 -> 12673 bytes
.../addons/wechat/images/mobile_index.png | Bin 0 -> 154 bytes
.../addons/wechat/images/weixin_icon.png | Bin 0 -> 1828 bytes
public/assets/js/backend/fc/coupon.js | 101 +
public/assets/js/backend/fc/dashboard.js | 67 +
public/assets/js/backend/fc/setting.js | 11 +
tools/verify-api.mjs | 191 +-
uniapp/README.md | 82 +-
uniapp/common/api/coupon.js | 16 +
uniapp/common/api/favorite.js | 22 +
uniapp/common/api/home.js | 7 +-
uniapp/common/api/index.js | 6 +
uniapp/common/api/order.js | 17 +
uniapp/common/api/pay.js | 15 +
uniapp/common/api/post.js | 41 +-
uniapp/common/api/report.js | 15 +
uniapp/common/constants/report.js | 13 +
uniapp/common/mock/data.js | 55 +-
uniapp/common/mock/index.js | 289 ++-
uniapp/common/utils/format.js | 40 +-
uniapp/common/utils/pay.js | 68 +-
.../components/fc-post-card/fc-post-card.vue | 12 +-
uniapp/docs/01-产品功能模块清单.md | 83 +-
uniapp/docs/03-接口文档.md | 98 +-
uniapp/docs/04-全链路测试用例与报告.md | 82 +-
uniapp/manifest.json | 9 +-
uniapp/pages.json | 16 +
uniapp/pages/detail/detail.vue | 155 +-
uniapp/pages/favorite/index.vue | 130 ++
uniapp/pages/index/index.vue | 29 +-
uniapp/pages/member/index.vue | 153 +-
uniapp/pages/member/recharge.vue | 263 +++
uniapp/pages/mine/index.vue | 13 +-
uniapp/pages/post/create.vue | 193 +-
uniapp/tests/api.user.contract.test.mjs | 5 +-
uniapp/tests/publish.test.mjs | 24 +-
uniapp/tests/run.mjs | 1 +
uniapp/tests/v110.test.mjs | 325 +++
uniapp/tools/check-scss.cjs | 82 +-
1086 files changed, 68849 insertions(+), 276 deletions(-)
create mode 100644 addons/.htaccess
create mode 100644 addons/epay/.addonrc
create mode 100644 addons/epay/Epay.php
create mode 100644 addons/epay/certs/alipayCertPublicKey.crt
create mode 100644 addons/epay/certs/alipayRootCert.crt
create mode 100644 addons/epay/certs/apiclient_cert.pem
create mode 100644 addons/epay/certs/apiclient_key.pem
create mode 100644 addons/epay/certs/appCertPublicKey.crt
create mode 100644 addons/epay/certs/public_key.pem
create mode 100644 addons/epay/config.html
create mode 100644 addons/epay/config.php
create mode 100644 addons/epay/controller/Api.php
create mode 100644 addons/epay/controller/Index.php
create mode 100644 addons/epay/info.ini
create mode 100644 addons/epay/library/Collection.php
create mode 100644 addons/epay/library/OrderException.php
create mode 100644 addons/epay/library/RedirectResponse.php
create mode 100644 addons/epay/library/Response.php
create mode 100644 addons/epay/library/Service.php
create mode 100644 addons/epay/library/Wechat.php
create mode 100644 addons/epay/library/hyperf/context/.gitattributes
create mode 100644 addons/epay/library/hyperf/context/LICENSE
create mode 100644 addons/epay/library/hyperf/context/composer.json
create mode 100644 addons/epay/library/hyperf/context/src/Context.php
create mode 100644 addons/epay/library/hyperf/contract/.gitattributes
create mode 100644 addons/epay/library/hyperf/contract/LICENSE
create mode 100644 addons/epay/library/hyperf/contract/composer.json
create mode 100644 addons/epay/library/hyperf/contract/src/ApplicationInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/Castable.php
create mode 100644 addons/epay/library/hyperf/contract/src/CastsAttributes.php
create mode 100644 addons/epay/library/hyperf/contract/src/CastsInboundAttributes.php
create mode 100644 addons/epay/library/hyperf/contract/src/CompressInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/ConfigInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/ConnectionInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/ContainerInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/DispatcherInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/FrequencyInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/IdGeneratorInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/LengthAwarePaginatorInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/MiddlewareInitializerInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/NormalizerInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/OnCloseInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/OnHandShakeInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/OnMessageInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/OnOpenInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/OnPacketInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/OnReceiveInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/OnRequestInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/PackerInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/PaginatorInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/PoolInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/PoolOptionInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/ProcessInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/ResponseEmitterInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/SessionInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/StdoutLoggerInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/Synchronized.php
create mode 100644 addons/epay/library/hyperf/contract/src/TranslatorInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/TranslatorLoaderInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/UnCompressInterface.php
create mode 100644 addons/epay/library/hyperf/contract/src/ValidatorInterface.php
create mode 100644 addons/epay/library/hyperf/engine/.gitattributes
create mode 100644 addons/epay/library/hyperf/engine/.gitignore
create mode 100644 addons/epay/library/hyperf/engine/.php-cs-fixer.php
create mode 100644 addons/epay/library/hyperf/engine/.phpstorm.meta.php
create mode 100644 addons/epay/library/hyperf/engine/Dockerfile
create mode 100644 addons/epay/library/hyperf/engine/README.md
create mode 100644 addons/epay/library/hyperf/engine/composer.json
create mode 100644 addons/epay/library/hyperf/engine/phpunit.xml
create mode 100644 addons/epay/library/hyperf/engine/src/Channel.php
create mode 100644 addons/epay/library/hyperf/engine/src/Constant.php
create mode 100644 addons/epay/library/hyperf/engine/src/Contract/ChannelInterface.php
create mode 100644 addons/epay/library/hyperf/engine/src/Contract/CoroutineInterface.php
create mode 100644 addons/epay/library/hyperf/engine/src/Contract/Http/ClientInterface.php
create mode 100644 addons/epay/library/hyperf/engine/src/Contract/WebSocket/WebSocketInterface.php
create mode 100644 addons/epay/library/hyperf/engine/src/Coroutine.php
create mode 100644 addons/epay/library/hyperf/engine/src/Exception/CoroutineDestroyedException.php
create mode 100644 addons/epay/library/hyperf/engine/src/Exception/HttpClientException.php
create mode 100644 addons/epay/library/hyperf/engine/src/Exception/RunningInNonCoroutineException.php
create mode 100644 addons/epay/library/hyperf/engine/src/Exception/RuntimeException.php
create mode 100644 addons/epay/library/hyperf/engine/src/Extension.php
create mode 100644 addons/epay/library/hyperf/engine/src/Http/Client.php
create mode 100644 addons/epay/library/hyperf/engine/src/Http/FdGetter.php
create mode 100644 addons/epay/library/hyperf/engine/src/Http/RawResponse.php
create mode 100644 addons/epay/library/hyperf/engine/src/Socket.php
create mode 100644 addons/epay/library/hyperf/engine/src/WaitGroup.php
create mode 100644 addons/epay/library/hyperf/engine/src/WebSocket/Frame.php
create mode 100644 addons/epay/library/hyperf/engine/src/WebSocket/Opcode.php
create mode 100644 addons/epay/library/hyperf/engine/src/WebSocket/WebSocket.php
create mode 100644 addons/epay/library/hyperf/macroable/.gitattributes
create mode 100644 addons/epay/library/hyperf/macroable/LICENSE
create mode 100644 addons/epay/library/hyperf/macroable/composer.json
create mode 100644 addons/epay/library/hyperf/macroable/src/Macroable.php
create mode 100644 addons/epay/library/hyperf/pimple/.gitattributes
create mode 100644 addons/epay/library/hyperf/pimple/.gitignore
create mode 100644 addons/epay/library/hyperf/pimple/.php-cs-fixer.php
create mode 100644 addons/epay/library/hyperf/pimple/.phpstorm.meta.php
create mode 100644 addons/epay/library/hyperf/pimple/.travis.yml
create mode 100644 addons/epay/library/hyperf/pimple/README.md
create mode 100644 addons/epay/library/hyperf/pimple/composer.json
create mode 100644 addons/epay/library/hyperf/pimple/phpunit.xml
create mode 100644 addons/epay/library/hyperf/pimple/src/ConfigProvider.php
create mode 100644 addons/epay/library/hyperf/pimple/src/Container.php
create mode 100644 addons/epay/library/hyperf/pimple/src/ContainerFactory.php
create mode 100644 addons/epay/library/hyperf/pimple/src/Exception/InvalidDefinitionException.php
create mode 100644 addons/epay/library/hyperf/pimple/src/Exception/NotFoundException.php
create mode 100644 addons/epay/library/hyperf/pimple/src/Exception/NotSupportException.php
create mode 100644 addons/epay/library/hyperf/pimple/src/ProviderInterface.php
create mode 100644 addons/epay/library/hyperf/utils/.gitattributes
create mode 100644 addons/epay/library/hyperf/utils/LICENSE
create mode 100644 addons/epay/library/hyperf/utils/composer.json
create mode 100644 addons/epay/library/hyperf/utils/src/ApplicationContext.php
create mode 100644 addons/epay/library/hyperf/utils/src/Arr.php
create mode 100644 addons/epay/library/hyperf/utils/src/Backoff.php
create mode 100644 addons/epay/library/hyperf/utils/src/Channel/Caller.php
create mode 100644 addons/epay/library/hyperf/utils/src/Channel/ChannelManager.php
create mode 100644 addons/epay/library/hyperf/utils/src/ChannelPool.php
create mode 100644 addons/epay/library/hyperf/utils/src/ClearStatCache.php
create mode 100644 addons/epay/library/hyperf/utils/src/CodeGen/Package.php
create mode 100644 addons/epay/library/hyperf/utils/src/CodeGen/PhpDocReader.php
create mode 100644 addons/epay/library/hyperf/utils/src/CodeGen/PhpDocReaderManager.php
create mode 100644 addons/epay/library/hyperf/utils/src/CodeGen/PhpParser.php
create mode 100644 addons/epay/library/hyperf/utils/src/CodeGen/Project.php
create mode 100644 addons/epay/library/hyperf/utils/src/Codec/Base62.php
create mode 100644 addons/epay/library/hyperf/utils/src/Codec/Json.php
create mode 100644 addons/epay/library/hyperf/utils/src/Codec/Xml.php
create mode 100644 addons/epay/library/hyperf/utils/src/Collection.php
create mode 100644 addons/epay/library/hyperf/utils/src/Composer.php
create mode 100644 addons/epay/library/hyperf/utils/src/ConfigProvider.php
create mode 100644 addons/epay/library/hyperf/utils/src/Context.php
create mode 100644 addons/epay/library/hyperf/utils/src/Contracts/Arrayable.php
create mode 100644 addons/epay/library/hyperf/utils/src/Contracts/Jsonable.php
create mode 100644 addons/epay/library/hyperf/utils/src/Contracts/MessageBag.php
create mode 100644 addons/epay/library/hyperf/utils/src/Contracts/MessageProvider.php
create mode 100644 addons/epay/library/hyperf/utils/src/Contracts/Xmlable.php
create mode 100644 addons/epay/library/hyperf/utils/src/Coordinator/Constants.php
create mode 100644 addons/epay/library/hyperf/utils/src/Coordinator/Coordinator.php
create mode 100644 addons/epay/library/hyperf/utils/src/Coordinator/CoordinatorManager.php
create mode 100644 addons/epay/library/hyperf/utils/src/Coroutine.php
create mode 100644 addons/epay/library/hyperf/utils/src/Coroutine/Concurrent.php
create mode 100644 addons/epay/library/hyperf/utils/src/Coroutine/Locker.php
create mode 100644 addons/epay/library/hyperf/utils/src/Exception/ChannelClosedException.php
create mode 100644 addons/epay/library/hyperf/utils/src/Exception/ExceptionThrower.php
create mode 100644 addons/epay/library/hyperf/utils/src/Exception/InvalidArgumentException.php
create mode 100644 addons/epay/library/hyperf/utils/src/Exception/ParallelExecutionException.php
create mode 100644 addons/epay/library/hyperf/utils/src/Exception/TimeoutException.php
create mode 100644 addons/epay/library/hyperf/utils/src/Exception/WaitTimeoutException.php
create mode 100644 addons/epay/library/hyperf/utils/src/Filesystem/FileNotFoundException.php
create mode 100644 addons/epay/library/hyperf/utils/src/Filesystem/Filesystem.php
create mode 100644 addons/epay/library/hyperf/utils/src/Fluent.php
create mode 100644 addons/epay/library/hyperf/utils/src/Functions.php
create mode 100644 addons/epay/library/hyperf/utils/src/HigherOrderCollectionProxy.php
create mode 100644 addons/epay/library/hyperf/utils/src/HigherOrderTapProxy.php
create mode 100644 addons/epay/library/hyperf/utils/src/InteractsWithTime.php
create mode 100644 addons/epay/library/hyperf/utils/src/MessageBag.php
create mode 100644 addons/epay/library/hyperf/utils/src/MimeTypeExtensionGuesser.php
create mode 100644 addons/epay/library/hyperf/utils/src/Network.php
create mode 100644 addons/epay/library/hyperf/utils/src/Optional.php
create mode 100644 addons/epay/library/hyperf/utils/src/Packer/JsonPacker.php
create mode 100644 addons/epay/library/hyperf/utils/src/Packer/PhpSerializerPacker.php
create mode 100644 addons/epay/library/hyperf/utils/src/Parallel.php
create mode 100644 addons/epay/library/hyperf/utils/src/Pipeline.php
create mode 100644 addons/epay/library/hyperf/utils/src/Pluralizer.php
create mode 100644 addons/epay/library/hyperf/utils/src/Reflection/ClassInvoker.php
create mode 100644 addons/epay/library/hyperf/utils/src/Resource.php
create mode 100644 addons/epay/library/hyperf/utils/src/ResourceGenerator.php
create mode 100644 addons/epay/library/hyperf/utils/src/Serializer/ExceptionNormalizer.php
create mode 100644 addons/epay/library/hyperf/utils/src/Serializer/ScalarNormalizer.php
create mode 100644 addons/epay/library/hyperf/utils/src/Serializer/Serializer.php
create mode 100644 addons/epay/library/hyperf/utils/src/Serializer/SerializerFactory.php
create mode 100644 addons/epay/library/hyperf/utils/src/Serializer/SimpleNormalizer.php
create mode 100644 addons/epay/library/hyperf/utils/src/Serializer/SymfonyNormalizer.php
create mode 100644 addons/epay/library/hyperf/utils/src/Str.php
create mode 100644 addons/epay/library/hyperf/utils/src/Stringable.php
create mode 100644 addons/epay/library/hyperf/utils/src/Traits/Conditionable.php
create mode 100644 addons/epay/library/hyperf/utils/src/Traits/Container.php
create mode 100644 addons/epay/library/hyperf/utils/src/Traits/CoroutineProxy.php
create mode 100644 addons/epay/library/hyperf/utils/src/Traits/ForwardsCalls.php
create mode 100644 addons/epay/library/hyperf/utils/src/Traits/Macroable.php
create mode 100644 addons/epay/library/hyperf/utils/src/Traits/StaticInstance.php
create mode 100644 addons/epay/library/hyperf/utils/src/Traits/Tappable.php
create mode 100644 addons/epay/library/hyperf/utils/src/WaitGroup.php
create mode 100644 addons/epay/library/hyperf/utils/src/Waiter.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayApplicationInterface.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayInterface.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/ApiRequested.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/ApiRequesting.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/Event.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/MethodCalled.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/PayStarted.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/PayStarting.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/RequestReceived.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Events/SignFailed.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Exceptions/BusinessException.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Exceptions/Exception.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Exceptions/GatewayException.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidArgumentException.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidConfigException.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidGatewayException.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidSignException.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/AppGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Gateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/MiniGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/PosGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/RefundGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/ScanGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Support.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/TransferGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WapGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WebGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/AppGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Gateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/GroupRedpackGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MpGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/PosGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RefundGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/ScanGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Support.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/TransferGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WapGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WebGateway.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/LICENSE
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Listeners/KernelLogSubscriber.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Log.php
create mode 100644 addons/epay/library/v2/Yansongda/Pay/Pay.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Arr.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Collection.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Config.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/LICENSE
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Log.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Logger.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Logger/StdoutHandler.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Str.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Traits/Accessable.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Traits/Arrayable.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Traits/HasHttpRequest.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Traits/Serializable.php
create mode 100644 addons/epay/library/v2/Yansongda/Supports/Traits/ShouldThrottle.php
create mode 100644 addons/epay/library/v2/version.txt
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/ConfigInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/ContainerInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/DirectionInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/EventDispatcherInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/HttpClientInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/LoggerInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/PackerInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/PluginInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/ProviderInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/ServiceProviderInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Contract/ShortcutInterface.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Direction/ArrayDirection.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Direction/CollectionDirection.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Direction/NoHttpRequestDirection.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Direction/OriginResponseDirection.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Direction/ResponseDirection.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event/ApiRequested.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event/ApiRequesting.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event/CallbackReceived.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event/Event.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event/MethodCalled.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event/PayFinish.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Event/PayStarted.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/ContainerException.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/ContainerNotFoundException.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/Exception.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/InvalidConfigException.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/InvalidParamsException.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/InvalidResponseException.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/ServiceException.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Exception/ServiceNotFoundException.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Functions.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Logger.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Packer/JsonPacker.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Packer/QueryPacker.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Packer/XmlPacker.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Pay.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/CallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillDownloadUrlQueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillEreceiptApplyPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillEreceiptQueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductBillStatusPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductPayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignAddPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignCancelPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AccountQueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderFreezePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderUnfreezePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransCommonQueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransPagePayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransTobankTransferPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransUniTransferPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/GeneralPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/HtmlResponsePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/LaunchPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/PreparePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/RadarSignPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/AppShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/CancelShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/CloseShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/MiniShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/PosShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/QueryShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/RefundShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/ScanShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/TransferShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/WapShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/WebShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Tools/OpenAuthTokenAppPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Tools/OpenAuthTokenAppQueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Tools/SystemOauthTokenPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/AppPayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/CancelPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/CreatePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/FastRefundQueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/OrderPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/OrderSettlePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PagePayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PageRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PreCreatePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/WapPayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementExecutionPlanModifyPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementPageSignPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementQueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementTransferPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementUnsignPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/InfoSharePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/ParserPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/CallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/GeneralPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/HtmlResponsePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/LaunchPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/CancelPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/PagePayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/WapPayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/PreparePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/CancelPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosNormalPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosPreAuthPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanFeePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanNormalPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreAuthPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreOrderPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/RadarSignPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/CancelShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/PosShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/QueryShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/RefundShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/ScanShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/WapShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/WebShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/CallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ApplyPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryReturnAdvancePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ReturnAdvancePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryDayEndPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/AddReceiverPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/CreatePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DeleteReceiverPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryAmountsPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryMerchantConfigsPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryReturnPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/ReturnPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/UnfreezePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateBillReceiptPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateDetailReceiptPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreatePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/DownloadReceiptPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchDetailIdPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchIdPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBillReceiptPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryDetailReceiptPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchDetailNoPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchNoPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralV2Plugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/LaunchPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/CreatePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/PausePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryCouponDetailPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockDetailPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockItemsPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockMerchantsPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockRefundFlowPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockUseFlowPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStocksPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryUserCouponsPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/RestartPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SendPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SetCallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/StartPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Papay/ApplyPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Papay/ContractOrderPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Papay/OnlyContractPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/GetFlowBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/InvokePrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/InvokePrepayV2Plugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/PrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/AppPrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/GetFlowBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/H5PrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/InvokeAppPrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/InvokeJsapiPrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/InvokeMiniPrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/JsapiPrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/MiniPrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/NativePrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/CombinePrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetFlowBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayV2Plugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/PrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/GetFlowBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/PrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/GetFlowBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/InvokePrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/InvokePrepayV2Plugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/PrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Jsapi/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/GetFlowBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/InvokePrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/InvokePrepayV2Plugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/PrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Mini/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/ClosePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/GetFlowBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/PrepayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Native/RefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Pos/PayPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Pos/QueryPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Pos/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/PreparePlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/RadarSignPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/CompleteComplaintPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DeleteCallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DownloadMediaPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryCallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintDetailPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintNegotiationPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintsPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/ResponseComplaintPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/SetCallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/UpdateCallbackPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/UpdateRefundPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/AppShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/CloseShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/MiniShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/MpShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/PapayShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/PosShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/QueryShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/RefundShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/ScanShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/TransferShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/WapShortcut.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/WechatPublicCertsPlugin.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Provider/AbstractProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Provider/Alipay.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Provider/Unipay.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Provider/Wechat.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Request.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Rocket.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/AlipayServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/ConfigServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/ContainerServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/EventServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/HttpServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/LoggerServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/UnipayServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Service/WechatServiceProvider.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Traits/GetUnipayCerts.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Traits/HasWechatEncryption.php
create mode 100644 addons/epay/library/v3/Yansongda/Pay/Traits/SupportServiceProviderTrait.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Arr.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Collection.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Config.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Functions.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Logger.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Logger/StdoutHandler.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Pipeline.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Str.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Traits/Accessable.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Traits/Arrayable.php
create mode 100644 addons/epay/library/v3/Yansongda/Supports/Traits/Serializable.php
create mode 100644 addons/epay/library/v3/version.txt
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Artful.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/ConfigInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/ContainerInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/DirectionInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/EventDispatcherInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/HttpClientFactoryInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/HttpClientInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/LoggerInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/PackerInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/PluginInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/ServiceProviderInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Contract/ShortcutInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Direction/CollectionDirection.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Direction/NoHttpRequestDirection.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Direction/OriginResponseDirection.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Direction/ResponseDirection.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Event.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Event/ArtfulEnd.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Event/ArtfulStart.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Event/Event.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Event/HttpEnd.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Event/HttpStart.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Exception/ContainerException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Exception/ContainerNotFoundException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Exception/Exception.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Exception/InvalidConfigException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Exception/InvalidParamsException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Exception/InvalidResponseException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Exception/ServiceNotFoundException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Functions.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/HttpClientFactory.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Logger.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Packer/JsonPacker.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Packer/QueryPacker.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Packer/XmlPacker.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Plugin/AddPayloadBodyPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Plugin/AddRadarPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Plugin/ParserPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Plugin/StartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Rocket.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Service/ConfigServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Service/ContainerServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Service/EventServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Service/HttpServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Artful/Service/LoggerServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Contract/ProviderInterface.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Event/CallbackReceived.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Event/MethodCalled.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Event/PayEnd.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Event/PayStart.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Exception/DecryptException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Exception/Exception.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Exception/InvalidSignException.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Functions.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Pay.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AddPayloadSignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AddRadarPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AppCallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/CallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/FormatPayloadBizContentPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/AppPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/H5PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/PosPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/ScanPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/OnsettlePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/RatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/SettlePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/BindPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/UnbindPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Request/SettleOrderPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/ApplyReceiptPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Bill/QueryUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/TransferPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryAccountPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryReceiptPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/AppPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/WebPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/AuthPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/TokenPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/CertifyPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/InitPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/DetailPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppInitPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppQueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppInitPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppQueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5InitPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5VerifyPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/ServerVerifyPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/AppInitPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/DetectPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/ServerDetectPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Bill/QueryUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/AppPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/ModifyPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/SignPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/UnsignPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryBillUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/AppFreezePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/PosFreezePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/ScanFreezePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/UnfreezePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Bill/QueryUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/SyncPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/InitPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryBillUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryBillUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseHtmlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseInvokeStringPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponsePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/StartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/VerifySignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/AddRadarPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/CallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/ResponsePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddPayloadSignPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddRadarPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/CallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/ResponsePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/StartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/VerifySignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/AddRadarPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/AddPayloadSignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/CallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/H5/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPreAuthPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanFeePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreAuthPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreOrderPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/ResponseHtmlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/StartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/VerifySignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/AddPayloadSignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/CallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryOpenIdPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/StartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/VerifySignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/AddRadarPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/ResponsePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/StartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/AddPayloadSignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ApplyPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ContractOrderPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/MiniOnlyContractPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/App/InvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Mini/InvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Redpack/SendPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/VerifySignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/AddPayloadSignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/CallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/CompletePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/DeleteCallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryCallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryDetailPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryImagePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryNegotiationPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/ResponsePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/SetCallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateCallbackPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/AddReceiverPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DeleteReceiverPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/GetBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryAmountsPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryMerchantConfigsPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryReturnPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/ReturnPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/UnfreezePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/SetPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/DetailPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/QueryUserPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/SendPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/PausePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryDetailPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryItemsPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryMerchantsPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryRefundFlowPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryUseFlowPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/RestartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/StartPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryDayEndPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ApplyPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryReturnAdvancePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ReturnAdvancePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/DownloadPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetBaseInformationPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetDownloadInfoPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetTaxCodePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/ReversePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/CreateCardTemplatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/GetTitleUrlPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryConfigPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryUserTitlePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/UpdateConfigPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeJsapiPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/DownloadReceiptPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/CreatePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetFundBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/InvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/DownloadPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetFundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetTradePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppInvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetFundBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/H5PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiInvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniInvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniPayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/NativePayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetFundBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/InvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetFundBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/InvokePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/DownloadBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetFundBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetTradeBillPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryByWxPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryRefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/QueryPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundAbnormalPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/VerifySignaturePlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/WechatPublicCertsPlugin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Provider/Alipay.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Provider/Douyin.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Provider/Jsb.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Provider/Unipay.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Provider/Wechat.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Service/AlipayServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Service/DouyinServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Service/JsbServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Service/UnipayServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Service/WechatServiceProvider.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/AppShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/CancelShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/CloseShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/H5Shortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/MiniShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/PosShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/QueryShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/RefundShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/ScanShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/TransferShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/WebShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Douyin/MiniShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Douyin/QueryShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Douyin/RefundShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Jsb/QueryShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Jsb/RefundShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Jsb/ScanShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/CancelShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/H5Shortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/PosShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/QueryShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/RefundShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/ScanShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/WebShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/AppShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/CloseShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/H5Shortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/MiniShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/MpShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/PapayShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/PosShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/QueryShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/RedpackShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/RefundShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/ScanShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/TransferShortcut.php
create mode 100644 addons/epay/library/v3new/Yansongda/Pay/Traits/SupportServiceProviderTrait.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Arr.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Collection.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Config.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Functions.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Pipeline.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Str.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Traits/Accessable.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Traits/Arrayable.php
create mode 100644 addons/epay/library/v3new/Yansongda/Supports/Traits/Serializable.php
create mode 100644 addons/epay/library/v3new/version.txt
create mode 100644 addons/epay/licenses/yansongda-pay.txt
create mode 100644 addons/epay/view/api/alipay.html
create mode 100644 addons/epay/view/api/wechat.html
create mode 100644 addons/epay/view/index/index.html
create mode 100644 addons/epay/view/layout/default.html
create mode 100644 addons/wechat/.addonrc
create mode 100644 addons/wechat/Wechat.php
create mode 100644 addons/wechat/config.php
create mode 100644 addons/wechat/controller/Captcha.php
create mode 100644 addons/wechat/controller/Index.php
create mode 100644 addons/wechat/info.ini
create mode 100644 addons/wechat/install.sql
create mode 100644 addons/wechat/library/Config.php
create mode 100644 addons/wechat/library/Response.php
create mode 100644 addons/wechat/library/Wechat.php
create mode 100644 addons/wechat/library/response/Ask.php
create mode 100644 addons/wechat/library/response/Blog.php
create mode 100644 addons/wechat/library/response/Cms.php
create mode 100644 addons/wechat/library/response/Signin.php
create mode 100644 addons/wechat/library/response/Vote.php
create mode 100644 addons/wechat/model/WechatAutoreply.php
create mode 100644 addons/wechat/model/WechatCaptcha.php
create mode 100644 addons/wechat/model/WechatConfig.php
create mode 100644 addons/wechat/model/WechatContext.php
create mode 100644 addons/wechat/model/WechatResponse.php
create mode 100644 application/admin/controller/fc/Coupon.php
create mode 100644 application/admin/controller/fc/Dashboard.php
create mode 100644 application/admin/controller/fc/Setting.php
create mode 100644 application/admin/lang/zh-cn/fc/coupon.php
create mode 100644 application/admin/lang/zh-cn/fc/dashboard.php
create mode 100644 application/admin/lang/zh-cn/fc/setting.php
create mode 100644 application/admin/view/fc/coupon/add.html
create mode 100644 application/admin/view/fc/coupon/edit.html
create mode 100644 application/admin/view/fc/coupon/index.html
create mode 100644 application/admin/view/fc/dashboard/index.html
create mode 100644 application/admin/view/fc/setting/index.html
create mode 100644 application/api/controller/Coupon.php
create mode 100644 application/api/controller/Favorite.php
create mode 100644 application/api/controller/Order.php
create mode 100644 application/api/controller/Pay.php
create mode 100644 application/api/controller/Report.php
create mode 100644 public/assets/addons/epay/css/common.css
create mode 100644 public/assets/addons/epay/images/alipay.png
create mode 100644 public/assets/addons/epay/images/expired.png
create mode 100644 public/assets/addons/epay/images/logo-alipay.png
create mode 100644 public/assets/addons/epay/images/logo-wechat.png
create mode 100644 public/assets/addons/epay/images/paid.png
create mode 100644 public/assets/addons/epay/images/scan.png
create mode 100644 public/assets/addons/epay/images/screenshot-alipay.png
create mode 100644 public/assets/addons/epay/images/screenshot-wechat.png
create mode 100644 public/assets/addons/epay/images/wechat.png
create mode 100644 public/assets/addons/epay/js/common.js
create mode 100644 public/assets/addons/epay/js/jquery.qrcode.min.js
create mode 100644 public/assets/addons/epay/less/common.less
create mode 100644 public/assets/addons/wechat/css/menu.css
create mode 100644 public/assets/addons/wechat/images/mobile_footer_bg.png
create mode 100644 public/assets/addons/wechat/images/mobile_header_bg.png
create mode 100644 public/assets/addons/wechat/images/mobile_index.png
create mode 100644 public/assets/addons/wechat/images/weixin_icon.png
create mode 100644 public/assets/js/backend/fc/coupon.js
create mode 100644 public/assets/js/backend/fc/dashboard.js
create mode 100644 public/assets/js/backend/fc/setting.js
create mode 100644 uniapp/common/api/coupon.js
create mode 100644 uniapp/common/api/favorite.js
create mode 100644 uniapp/common/api/order.js
create mode 100644 uniapp/common/api/pay.js
create mode 100644 uniapp/common/api/report.js
create mode 100644 uniapp/common/constants/report.js
create mode 100644 uniapp/pages/favorite/index.vue
create mode 100644 uniapp/pages/member/recharge.vue
create mode 100644 uniapp/tests/v110.test.mjs
diff --git a/.gitignore b/.gitignore
index 6785f82..08ac1df 100755
--- a/.gitignore
+++ b/.gitignore
@@ -2,9 +2,7 @@
/thinkphp/
/vendor/
/runtime/*
-/addons/*
/public/assets/libs/
-/public/assets/addons/*
/public/uploads/*
.DS_Store
.idea
diff --git a/addons/.htaccess b/addons/.htaccess
new file mode 100644
index 0000000..3418e55
--- /dev/null
+++ b/addons/.htaccess
@@ -0,0 +1 @@
+deny from all
\ No newline at end of file
diff --git a/addons/epay/.addonrc b/addons/epay/.addonrc
new file mode 100644
index 0000000..87dd713
--- /dev/null
+++ b/addons/epay/.addonrc
@@ -0,0 +1 @@
+{"files":["application\/admin\/controller\/Epay.php","public\/assets\/addons\/epay\/js\/common.js","public\/assets\/addons\/epay\/js\/jquery.qrcode.min.js","public\/assets\/addons\/epay\/less\/common.less","public\/assets\/addons\/epay\/images\/wechat.png","public\/assets\/addons\/epay\/images\/alipay.png","public\/assets\/addons\/epay\/images\/logo-wechat.png","public\/assets\/addons\/epay\/images\/screenshot-wechat.png","public\/assets\/addons\/epay\/images\/paid.png","public\/assets\/addons\/epay\/images\/expired.png","public\/assets\/addons\/epay\/images\/scan.png","public\/assets\/addons\/epay\/images\/logo-alipay.png","public\/assets\/addons\/epay\/images\/screenshot-alipay.png","public\/assets\/addons\/epay\/css\/common.css"],"license":"regular","licenseto":"26425","licensekey":"W8TNHEKYuUG2P0jJ nFnAiAd3bF2UiAjXnZAWNw==","domains":["gynhc.com"],"licensecodes":[],"validations":["baca5b0e114fc9a55d47e7adb2332062"]}
\ No newline at end of file
diff --git a/addons/epay/Epay.php b/addons/epay/Epay.php
new file mode 100644
index 0000000..952b5bd
--- /dev/null
+++ b/addons/epay/Epay.php
@@ -0,0 +1,112 @@
+actionBegin();
+ }
+
+ // 插件方法加载开始
+ public function addonActionBegin()
+ {
+ $this->actionBegin();
+ }
+
+ // 模块控制器方法加载开始
+ public function actionBegin()
+ {
+ //添加命名空间
+ if (!class_exists('\Yansongda\Pay\Pay')) {
+
+ //SDK版本
+ $version = Service::getSdkVersion();
+
+ $libraryDir = ADDON_PATH . 'epay' . DS . 'library' . DS;
+
+ if (PHP_VERSION_ID >= 80000 && $version == Service::SDK_VERSION_V3) {
+ $yansongdaDir = $libraryDir . $version . 'new' . DS . 'Yansongda' . DS;
+
+ // PHP 8.0+ 版本且为V3接口,需要加载 Artful 的命名空间
+ Loader::addNamespace('Yansongda\Artful', $yansongdaDir . 'Artful' . DS);
+ require_once $yansongdaDir . 'Artful' . DS . 'Functions.php';
+ } else {
+ $yansongdaDir = $libraryDir . $version . DS . 'Yansongda' . DS;
+ }
+
+ // 注册Pay命名空间
+ Loader::addNamespace('Yansongda\Pay', $yansongdaDir . 'Pay' . DS);
+
+ $checkArr = [
+ '\Hyperf\Context\Context' => 'context',
+ '\Hyperf\Contract\Castable' => 'contract',
+ '\Hyperf\Engine\Constant' => 'engine',
+ '\Hyperf\Macroable\Macroable' => 'macroable',
+ '\Hyperf\Pimple\Container' => 'pimple',
+ '\Hyperf\Utils\Arr' => 'utils',
+ ];
+ foreach ($checkArr as $index => $item) {
+ if (!class_exists($index)) {
+ Loader::addNamespace(substr($index, 1, strrpos($index, '\\') - 1), $libraryDir . 'hyperf' . DS . $item . DS . 'src' . DS);
+ }
+ }
+
+ if (!class_exists('\Yansongda\Supports\Logger')) {
+ Loader::addNamespace('Yansongda\Supports', $yansongdaDir . 'Supports' . DS);
+ }
+
+ // V3需载入辅助函数
+ if ($version == Service::SDK_VERSION_V3) {
+ require_once $yansongdaDir . 'Pay' . DS . 'Functions.php';
+ }
+ }
+ }
+}
diff --git a/addons/epay/certs/alipayCertPublicKey.crt b/addons/epay/certs/alipayCertPublicKey.crt
new file mode 100644
index 0000000..e69de29
diff --git a/addons/epay/certs/alipayRootCert.crt b/addons/epay/certs/alipayRootCert.crt
new file mode 100644
index 0000000..e69de29
diff --git a/addons/epay/certs/apiclient_cert.pem b/addons/epay/certs/apiclient_cert.pem
new file mode 100644
index 0000000..e69de29
diff --git a/addons/epay/certs/apiclient_key.pem b/addons/epay/certs/apiclient_key.pem
new file mode 100644
index 0000000..e69de29
diff --git a/addons/epay/certs/appCertPublicKey.crt b/addons/epay/certs/appCertPublicKey.crt
new file mode 100644
index 0000000..e69de29
diff --git a/addons/epay/certs/public_key.pem b/addons/epay/certs/public_key.pem
new file mode 100644
index 0000000..e69de29
diff --git a/addons/epay/config.html b/addons/epay/config.html
new file mode 100644
index 0000000..325f89b
--- /dev/null
+++ b/addons/epay/config.html
@@ -0,0 +1,529 @@
+
+
diff --git a/addons/epay/config.php b/addons/epay/config.php
new file mode 100644
index 0000000..92607f2
--- /dev/null
+++ b/addons/epay/config.php
@@ -0,0 +1,72 @@
+ 'version',
+ 'title' => 'API版本(请勿修改该值)',
+ 'type' => 'radio',
+ 'content' => [],
+ 'value' => 'v2',
+ 'rule' => '',
+ 'msg' => '',
+ 'tip' => 'V2版本只支持微信支付V2密钥,V3版本只支持微信支付V3密钥,请勿修改该值!!!',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'wechat',
+ 'title' => '微信',
+ 'type' => 'array',
+ 'content' => [],
+ 'value' => [
+ 'appid' => '',
+ 'app_id' => '',
+ 'app_secret' => '',
+ 'miniapp_id' => '',
+ 'mch_id' => '',
+ 'key' => '',
+ 'key_v3' => '',
+ 'public_key_id' => '',
+ 'public_key' => '/addons/epay/certs/public_key.pem',
+ 'cert_client' => '/addons/epay/certs/apiclient_cert.pem',
+ 'cert_key' => '/addons/epay/certs/apiclient_key.pem',
+ 'mode' => 'normal',
+ 'sub_mch_id' => '',
+ 'sub_appid' => '',
+ 'sub_app_id' => '',
+ 'sub_miniapp_id' => '',
+ 'notify_url' => '',
+ 'log' => '1',
+ ],
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '微信参数配置',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'alipay',
+ 'title' => '支付宝',
+ 'type' => 'array',
+ 'content' => [],
+ 'value' => [
+ 'app_id' => '',
+ 'mode' => 'normal',
+ 'notify_url' => '/addons/epay/api/notifyx/type/alipay',
+ 'return_url' => '/addons/epay/api/returnx/type/alipay',
+ 'private_key' => '',
+ 'signtype' => 'cert',
+ 'pid' => '',
+ 'ali_public_key' => '',
+ 'app_cert_public_key' => '',
+ 'alipay_root_cert' => '',
+ 'log' => '1',
+ 'scanpay' => '0',
+ ],
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '支付宝参数配置',
+ 'ok' => '',
+ 'extend' => '',
+ ]
+];
diff --git a/addons/epay/controller/Api.php b/addons/epay/controller/Api.php
new file mode 100644
index 0000000..27340e4
--- /dev/null
+++ b/addons/epay/controller/Api.php
@@ -0,0 +1,234 @@
+request->filter('trim');
+ $out_trade_no = $this->request->request("out_trade_no");
+ $title = $this->request->request("title");
+ $amount = $this->request->request('amount');
+ $type = $this->request->request('type', $this->request->request('paytype'));
+ $method = $this->request->request('method', 'web');
+ $openid = $this->request->request('openid', '');
+ $auth_code = $this->request->request('auth_code', '');
+ $notifyurl = $this->request->request('notifyurl', '');
+ $returnurl = $this->request->request('returnurl', '');
+
+ if (!$amount || $amount < 0) {
+ $this->error("支付金额必须大于0");
+ }
+
+ if (!$type || !in_array($type, ['alipay', 'wechat'])) {
+ $this->error("支付类型错误");
+ }
+
+ $params = [
+ 'type' => $type,
+ 'out_trade_no' => $out_trade_no,
+ 'title' => $title,
+ 'amount' => $amount,
+ 'method' => $method,
+ 'openid' => $openid,
+ 'auth_code' => $auth_code,
+ 'notifyurl' => $notifyurl,
+ 'returnurl' => $returnurl,
+ ];
+ return Service::submitOrder($params);
+ }
+
+ /**
+ * 微信支付(公众号支付&PC扫码支付)
+ */
+ public function wechat()
+ {
+ $config = Service::getConfig('wechat');
+
+ $isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
+ $isMobile = $this->request->isMobile();
+ $this->view->assign("isWechat", $isWechat);
+ $this->view->assign("isMobile", $isMobile);
+
+ //发起PC支付(Scan支付)(PC扫码模式)
+ if ($this->request->isAjax()) {
+ $pay = Pay::wechat($config);
+ $orderid = $this->request->post("orderid");
+ try {
+ $result = Service::isVersionV3() ? $pay->find(['out_trade_no' => $orderid]) : $pay->find($orderid, 'scan');
+ $this->success("", "", ['status' => $result['trade_state'] ?? 'NOTPAY']);
+ } catch (GatewayException $e) {
+ $this->error("查询失败(1001)");
+ }
+ }
+
+ $orderData = Session::get("wechatorderdata");
+ if (!$orderData) {
+ $this->error("请求参数错误");
+ }
+ if ($isWechat && $isMobile) {
+ //发起公众号(jsapi支付),openid必须
+
+ //如果没有openid,则自动去获取openid
+ if (!isset($orderData['openid']) || !$orderData['openid']) {
+ $orderData['openid'] = Service::getOpenid();
+ }
+
+ $orderData['method'] = 'mp';
+ $type = 'jsapi';
+ $payData = Service::submitOrder($orderData);
+ if (!isset($payData['paySign'])) {
+ $this->error("创建订单失败,请返回重试", "");
+ }
+ } else {
+ $orderData['method'] = 'scan';
+ $type = 'pc';
+ $payData = Service::submitOrder($orderData);
+ if (!isset($payData['code_url'])) {
+ $this->error("创建订单失败,请返回重试", "");
+ }
+ }
+ $this->view->assign("orderData", $orderData);
+ $this->view->assign("payData", $payData);
+ $this->view->assign("type", $type);
+
+ $this->view->assign("title", "微信支付");
+ return $this->view->fetch();
+ }
+
+ /**
+ * 支付宝支付(PC扫码支付)
+ */
+ public function alipay()
+ {
+ $config = Service::getConfig('alipay');
+
+ $isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
+ $isMobile = $this->request->isMobile();
+ $this->view->assign("isWechat", $isWechat);
+ $this->view->assign("isMobile", $isMobile);
+
+ if ($this->request->isAjax()) {
+ $orderid = $this->request->post("orderid");
+ $pay = Pay::alipay($config);
+ try {
+ $result = $pay->find(['out_trade_no' => $orderid]);
+ if ($result['code'] == '10000' && $result['trade_status'] == 'TRADE_SUCCESS') {
+ $this->success("", "", ['status' => $result['trade_status']]);
+ } else {
+ $this->error("查询失败");
+ }
+ } catch (GatewayException $e) {
+ $this->error("查询失败(1001)");
+ }
+ }
+
+ //发起PC支付(Scan支付)(PC扫码模式)
+ $orderData = Session::get("alipayorderdata");
+ if (!$orderData) {
+ $this->error("请求参数错误");
+ }
+
+ $orderData['method'] = 'scan';
+ $payData = Service::submitOrder($orderData);
+ if (!isset($payData['qr_code'])) {
+ $this->error("创建订单失败,请返回重试");
+ }
+
+ $type = 'pc';
+ $this->view->assign("orderData", $orderData);
+ $this->view->assign("payData", $payData);
+ $this->view->assign("type", $type);
+ $this->view->assign("title", "支付宝支付");
+ return $this->view->fetch();
+ }
+
+ /**
+ * 支付成功回调
+ */
+ public function notifyx()
+ {
+ $paytype = $this->request->param('paytype');
+ $pay = Service::checkNotify($paytype);
+ if (!$pay) {
+ return json(['code' => 'FAIL', 'message' => '失败'], 500, ['Content-Type' => 'application/json']);
+ }
+
+ // 获取回调数据,V3和V2的回调接收不同
+ $data = Service::isVersionV3() ? $pay->callback() : $pay->verify();
+
+ try {
+ //微信支付V3返回和V2不同
+ if (Service::isVersionV3() && $paytype === 'wechat') {
+ $data = $data['resource']['ciphertext'];
+ $data['total_fee'] = $data['amount']['total'];
+ }
+
+ \think\Log::record($data);
+ //获取支付金额、订单号
+ $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
+ $out_trade_no = $data['out_trade_no'];
+
+ \think\Log::record("回调成功,订单号:{$out_trade_no},金额:{$payamount}");
+
+ //你可以在此编写订单逻辑
+ } catch (Exception $e) {
+ \think\Log::record("回调逻辑处理错误:" . $e->getMessage(), "error");
+ }
+
+ //下面这句必须要执行,且在此之前不能有任何输出
+ if (Service::isVersionV3()) {
+ return $pay->success()->getBody()->getContents();
+ } else {
+ return $pay->success()->send();
+ }
+ }
+
+ /**
+ * 支付成功返回
+ */
+ public function returnx()
+ {
+ $paytype = $this->request->param('paytype');
+ if (Service::checkReturn($paytype)) {
+ echo '签名错误';
+ return;
+ }
+
+ //你可以在这里定义你的提示信息,但切记不可在此编写逻辑
+ $this->success("恭喜你!支付成功!", addon_url("epay/index/index"));
+ }
+
+}
diff --git a/addons/epay/controller/Index.php b/addons/epay/controller/Index.php
new file mode 100644
index 0000000..ea5a25a
--- /dev/null
+++ b/addons/epay/controller/Index.php
@@ -0,0 +1,131 @@
+error("仅在开发环境下查看");
+ }
+ }
+
+ public function index()
+ {
+ $this->view->assign("title", "微信支付宝整合");
+ return $this->view->fetch();
+ }
+
+ /**
+ * 体验,仅供开发测试
+ */
+ public function experience()
+ {
+ $amount = $this->request->post('amount');
+ $type = $this->request->post('type');
+ $method = $this->request->post('method');
+ $openid = $this->request->post('openid', "");
+
+ if (!$amount || $amount < 0) {
+ $this->error("支付金额必须大于0");
+ }
+
+ if (!$type || !in_array($type, ['alipay', 'wechat'])) {
+ $this->error("支付类型不能为空");
+ }
+
+ if (in_array($method, ['miniapp', 'mp']) && !$openid) {
+ $this->error("openid不能为空");
+ }
+
+ //订单号
+ $out_trade_no = date("YmdHis") . mt_rand(100000, 999999);
+
+ //订单标题
+ $title = '测试订单';
+
+ //回调链接
+ $notifyurl = $this->request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;
+ $returnurl = $this->request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $out_trade_no;
+
+ $response = Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method, $openid);
+
+ return $response;
+ }
+
+ /**
+ * 支付成功,仅供开发测试
+ */
+ public function notifyx()
+ {
+ $paytype = $this->request->param('paytype');
+ $pay = Service::checkNotify($paytype);
+ if (!$pay) {
+ return json(['code' => 'FAIL', 'message' => '失败'], 500, ['Content-Type' => 'application/json']);
+ }
+
+ // 获取回调数据,V3和V2的回调接收不同
+ $data = Service::isVersionV3() ? $pay->callback() : $pay->verify();
+
+ try {
+ //微信支付V3返回和V2不同
+ if (Service::isVersionV3() && $paytype === 'wechat') {
+ $data = $data['resource']['ciphertext'];
+ $data['total_fee'] = $data['amount']['total'];
+ }
+
+ \think\Log::record($data);
+ //获取支付金额、订单号
+ $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
+ $out_trade_no = $data['out_trade_no'];
+
+ \think\Log::record("回调成功,订单号:{$out_trade_no},金额:{$payamount}");
+
+ //你可以在此编写订单逻辑
+ } catch (Exception $e) {
+ \think\Log::record("回调逻辑处理错误:" . $e->getMessage(), "error");
+ }
+
+ //下面这句必须要执行,且在此之前不能有任何输出
+ if (Service::isVersionV3()) {
+ return $pay->success()->getBody()->getContents();
+ } else {
+ return $pay->success()->send();
+ }
+ }
+
+ /**
+ * 支付返回,仅供开发测试
+ */
+ public function returnx()
+ {
+ $paytype = $this->request->param('paytype');
+ $out_trade_no = $this->request->param('out_trade_no');
+ $pay = Service::checkReturn($paytype);
+ if (!$pay) {
+ $this->error('签名错误', '');
+ }
+
+ //你可以在这里定义你的提示信息,但切记不可在此编写逻辑
+ $this->success("请返回网站查看支付结果", addon_url("epay/index/index"));
+ }
+}
diff --git a/addons/epay/info.ini b/addons/epay/info.ini
new file mode 100644
index 0000000..c130145
--- /dev/null
+++ b/addons/epay/info.ini
@@ -0,0 +1,10 @@
+name = epay
+title = 微信支付宝整合
+intro = 可用于快速整合微信支付、支付宝支付功能
+author = FastAdmin
+website =
+version = 1.3.15
+state = 1
+url = /addons/epay
+license = regular
+licenseto = 26425
diff --git a/addons/epay/library/Collection.php b/addons/epay/library/Collection.php
new file mode 100644
index 0000000..8b5743e
--- /dev/null
+++ b/addons/epay/library/Collection.php
@@ -0,0 +1,18 @@
+message = $message;
+ $this->code = $code;
+ $this->data = $data;
+ }
+
+}
diff --git a/addons/epay/library/RedirectResponse.php b/addons/epay/library/RedirectResponse.php
new file mode 100644
index 0000000..aa91e46
--- /dev/null
+++ b/addons/epay/library/RedirectResponse.php
@@ -0,0 +1,63 @@
+getContent();
+ }
+
+ public function setTargetUrl($url)
+ {
+ if ('' === ($url ?? '')) {
+ throw new \InvalidArgumentException('无法跳转到空页面');
+ }
+
+ $this->targetUrl = $url;
+
+ $this->setContent(
+ sprintf('
+
+
+
+
+
+ 正在跳转支付 %1$s
+
+
+
+
+
+', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
+
+ $this->headers->set('Location', $url);
+
+ return $this;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return $this->getContent();
+ }
+
+ // 使用 PHP 8 兼容的新序列化方式
+ public function __serialize(): array
+ {
+ return ['content' => $this->content];
+ }
+
+ public function __unserialize(array $data): void
+ {
+ $this->content = $data['content'] ?? '';
+ }
+}
diff --git a/addons/epay/library/Response.php b/addons/epay/library/Response.php
new file mode 100644
index 0000000..c6c8613
--- /dev/null
+++ b/addons/epay/library/Response.php
@@ -0,0 +1,46 @@
+getContent();
+ }
+
+ public function jsonSerialize(): mixed
+ {
+ return $this->getContent();
+ }
+
+ public function serialize()
+ {
+ return serialize($this->content);
+ }
+
+ public function unserialize($data)
+ {
+ return $this->content = unserialize($data);
+ }
+
+ /**
+ * (PHP 8.1+) Magic method for serialization.
+ *
+ * @return mixed
+ */
+ public function __serialize()
+ {
+ return $this->content;
+ }
+
+ /**
+ * (PHP 8.1+) Magic method for unserialization.
+ *
+ * @param array $data
+ */
+ public function __unserialize(array $data): void
+ {
+ $this->content = $data;
+ }
+}
diff --git a/addons/epay/library/Service.php b/addons/epay/library/Service.php
new file mode 100644
index 0000000..92eec5e
--- /dev/null
+++ b/addons/epay/library/Service.php
@@ -0,0 +1,494 @@
+ $amount,
+ 'orderid' => $orderid,
+ 'type' => $type,
+ 'title' => $title,
+ 'notifyurl' => $notifyurl,
+ 'returnurl' => $returnurl,
+ 'method' => $method,
+ 'openid' => $openid,
+ 'custom' => $custom,
+ ];
+ } else {
+ $params = $amount;
+ }
+ $type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';
+ $method = $params['method'] ?? 'web';
+ $orderid = $params['orderid'] ?? date("YmdHis") . mt_rand(100000, 999999);
+ $amount = $params['amount'] ?? 1;
+ $title = $params['title'] ?? "支付";
+ $auth_code = $params['auth_code'] ?? '';
+ $openid = $params['openid'] ?? '';
+
+ //自定义微信支付宝相关配置
+ $custom = $params['custom'] ?? [];
+
+ //未定义则使用默认回调和跳转
+ $notifyurl = !empty($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;
+ $returnurl = !empty($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $orderid;
+
+ $html = '';
+ $config = Service::getConfig($type, array_merge($custom, ['notify_url' => $notifyurl, 'return_url' => $returnurl]));
+
+ //判断是否移动端或微信内浏览器
+ $isMobile = $request->isMobile();
+ $isWechat = strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
+
+ $result = null;
+ if ($type == 'alipay') {
+ //如果是PC支付,判断当前环境,进行跳转
+ if ($method == 'web') {
+ //如果是微信环境或后台配置PC使用扫码支付
+ if ($isWechat || $addonConfig['alipay']['scanpay']) {
+ Session::set("alipayorderdata", $params);
+ $url = addon_url('epay/api/alipay', [], true, true);
+ return new RedirectResponse($url);
+ } elseif ($isMobile) {
+ $method = 'wap';
+ }
+ }
+
+ //创建支付对象
+ $pay = Pay::alipay($config);
+ $params = [
+ 'out_trade_no' => $orderid,//你的订单号
+ 'total_amount' => $amount,//单位元
+ 'subject' => $title,
+ ];
+
+ switch ($method) {
+ case 'web':
+ //电脑支付
+ $result = $pay->web($params);
+ break;
+ case 'wap':
+ //手机网页支付
+ $result = $pay->wap($params);
+ break;
+ case 'app':
+ //APP支付
+ $result = $pay->app($params);
+ break;
+ case 'scan':
+ //扫码支付
+ $result = $pay->scan($params);
+ break;
+ case 'pos':
+ //刷卡支付必须要有auth_code
+ $params['auth_code'] = $auth_code;
+ $result = $pay->pos($params);
+ break;
+ case 'mini':
+ case 'miniapp':
+ //小程序支付,直接返回字符串
+ //小程序支付必须要有buyer_id或buyer_open_id
+ if (is_numeric($openid) && strlen($openid) === 16) {
+ $params['buyer_id'] = $openid;
+ } else {
+ $params['buyer_open_id'] = $openid;
+ }
+ $result = $pay->mini($params);
+ break;
+ default:
+ }
+ } else {
+ //如果是PC支付,判断当前环境,进行跳转
+ if ($method == 'web') {
+ //如果是移动端,但不是微信环境
+ if ($isMobile && !$isWechat) {
+ $method = 'wap';
+ } else {
+ Session::set("wechatorderdata", $params);
+ $url = addon_url('epay/api/wechat', [], true, true);
+ return new RedirectResponse($url);
+ }
+ }
+
+ //单位分
+ $total_fee = function_exists('bcmul') ? bcmul($amount, 100) : $amount * 100;
+ $total_fee = (int)$total_fee;
+ $ip = $request->ip();
+ //微信服务商模式时需传递sub_openid参数
+ $openidName = $addonConfig['wechat']['mode'] == 'service' ? 'sub_openid' : 'openid';
+
+ //创建支付对象
+ $pay = Pay::wechat($config);
+
+ if (self::isVersionV3()) {
+ //V3支付
+ $params = [
+ 'out_trade_no' => $orderid,
+ 'description' => $title,
+ 'amount' => [
+ 'total' => $total_fee,
+ ]
+ ];
+ switch ($method) {
+ case 'mp':
+ //公众号支付
+ //公众号支付必须有openid
+ $params['payer'] = [$openidName => $openid];
+ $result = $pay->mp($params);
+ break;
+ case 'wap':
+ //手机网页支付,跳转
+ $params['scene_info'] = [
+ 'payer_client_ip' => $ip,
+ 'h5_info' => [
+ 'type' => 'Wap',
+ ]
+ ];
+ $result = $pay->wap($params);
+ break;
+ case 'app':
+ //APP支付,直接返回字符串
+ $result = $pay->app($params);
+ break;
+ case 'scan':
+ //扫码支付,直接返回字符串
+ $result = $pay->scan($params);
+ break;
+ case 'pos':
+ //刷卡支付,直接返回字符串
+ //刷卡支付必须要有auth_code
+ $params['auth_code'] = $auth_code;
+ $result = $pay->pos($params);
+ break;
+ case 'mini':
+ case 'miniapp':
+ //小程序支付,直接返回字符串
+ //小程序支付必须要有openid
+ $params['payer'] = [$openidName => $openid];
+ $result = $pay->mini($params);
+ break;
+ default:
+ }
+ } else {
+ //V2支付
+ $params = [
+ 'out_trade_no' => $orderid,
+ 'body' => $title,
+ 'total_fee' => $total_fee,
+ ];
+ switch ($method) {
+ case 'mp':
+ //公众号支付
+ //公众号支付必须有openid
+ $params[$openidName] = $openid;
+ $result = $pay->mp($params);
+ break;
+ case 'wap':
+ //手机网页支付,跳转
+ $params['spbill_create_ip'] = $ip;
+ $result = $pay->wap($params);
+ break;
+ case 'app':
+ //APP支付,直接返回字符串
+ $result = $pay->app($params);
+ break;
+ case 'scan':
+ //扫码支付,直接返回字符串
+ $result = $pay->scan($params);
+ break;
+ case 'pos':
+ //刷卡支付,直接返回字符串
+ //刷卡支付必须要有auth_code
+ $params['auth_code'] = $auth_code;
+ $result = $pay->pos($params);
+ break;
+ case 'mini':
+ case 'miniapp':
+ //小程序支付,直接返回字符串
+ //小程序支付必须要有openid
+ $params[$openidName] = $openid;
+ $result = $pay->miniapp($params);
+ break;
+ default:
+ }
+ }
+ }
+
+ //使用重写的Response类、RedirectResponse、Collection类
+ if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) {
+ $result = new RedirectResponse($result->getTargetUrl());
+ } elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) {
+ $result = new Response($result->getContent());
+ } elseif ($result instanceof \Yansongda\Supports\Collection) {
+ $result = Collection::make($result->all());
+ } elseif ($result instanceof \GuzzleHttp\Psr7\Response) {
+ $result = new Response($result->getBody());
+ }
+
+ return $result;
+ }
+
+ /**
+ * 验证回调是否成功
+ * @param string $type 支付类型
+ * @param array $custom 自定义配置信息
+ * @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat|\Yansongda\Pay\Provider\Wechat|\Yansongda\Pay\Provider\Alipay
+ */
+ public static function checkNotify($type, $custom = [])
+ {
+ $type = strtolower($type);
+ if (!in_array($type, ['wechat', 'alipay'])) {
+ return false;
+ }
+
+ $version = self::getSdkVersion();
+
+ try {
+ $config = self::getConfig($type, $custom);
+ $pay = $type == 'wechat' ? Pay::wechat($config) : Pay::alipay($config);
+
+ $data = Service::isVersionV3() ? $pay->callback() : $pay->verify();
+ if ($type == 'alipay') {
+ if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
+ return $pay;
+ }
+ } else {
+ return $pay;
+ }
+ } catch (Exception $e) {
+ \think\Log::record("回调请求参数解析错误", "error");
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * 验证返回是否成功,请勿用于判断是否支付成功的逻辑验证
+ * 已弃用
+ *
+ * @param string $type 支付类型
+ * @param array $custom 自定义配置信息
+ * @return bool
+ * @deprecated 已弃用,请勿用于逻辑验证
+ */
+ public static function checkReturn($type, $custom = [])
+ {
+ //由于PC及移动端无法获取请求的参数信息,取消return验证,均返回true
+ return true;
+ }
+
+ /**
+ * 处理证书路径
+ * @param array $config 配置
+ * @param string $field 字段
+ * @return void
+ */
+ private static function processAddonsPath(&$config, $field)
+ {
+ if (isset($config[$field]) && substr($config[$field], 0, 8) == '/addons/') {
+ $config[$field] = ROOT_PATH . str_replace('/', DS, substr($config[$field], 1));
+ }
+ }
+
+ /**
+ * 获取配置
+ * @param string $type 支付类型
+ * @param array $custom 自定义配置,用于覆盖插件默认配置
+ * @return array
+ */
+ public static function getConfig($type = 'wechat', $custom = [])
+ {
+ $addonConfig = get_addon_config('epay');
+ $config = $addonConfig[$type] ?? $addonConfig['wechat'];
+
+ // SDK版本
+ $version = self::getSdkVersion();
+
+ // 处理微信证书路径
+ if ($type === 'wechat') {
+ $certFields = ['cert_client', 'cert_key', 'public_key'];
+ foreach ($certFields as $field) {
+ self::processAddonsPath($config, $field);
+ }
+ }
+
+ // 处理支付宝证书路径
+ if ($type === 'alipay') {
+ $config['signtype'] = $config['signtype'] ?? 'publickey';
+ if ($config['signtype'] == 'cert') {
+ $certFields = ['app_cert_public_key', 'alipay_root_cert', 'ali_public_key'];
+ foreach ($certFields as $field) {
+ self::processAddonsPath($config, $field);
+ }
+ } else {
+ // 如果是普通公钥需要将app_cert_public_key和alipay_root_cert设置为空,不然会导致错误
+ $config['app_cert_public_key'] = '';
+ $config['alipay_root_cert'] = '';
+ }
+ }
+
+ // V3支付
+ if (self::isVersionV3()) {
+ if ($type == 'wechat') {
+ $config['mp_app_id'] = $config['app_id'] ?? '';
+ $config['app_id'] = $config['appid'] ?? '';
+ $config['mini_app_id'] = $config['miniapp_id'] ?? '';
+ $config['combine_mch_id'] = $config['combine_mch_id'] ?? '';
+ $config['mch_secret_key'] = $config['key_v3'] ?? '';
+ $config['mch_secret_cert'] = $config['cert_key'];
+ $config['mch_public_cert_path'] = $config['cert_client'];
+
+ // 配置微信支付公钥ID及证书路径
+ $config['wechat_public_cert_path'] = [
+ ($config['public_key_id'] ?? '') => ($config['public_key'] ?? '')
+ ];
+
+ $config['sub_mp_app_id'] = $config['sub_appid'] ?? '';
+ $config['sub_app_id'] = $config['sub_app_id'] ?? '';
+ $config['sub_mini_app_id'] = $config['sub_miniapp_id'] ?? '';
+ $config['sub_mch_id'] = $config['sub_mch_id'] ?? '';
+ } elseif ($type == 'alipay') {
+ $config['app_secret_cert'] = $config['private_key'] ?? '';
+ $config['app_public_cert_path'] = $config['app_cert_public_key'] ?? '';
+ $config['alipay_public_cert_path'] = $config['ali_public_key'] ?? '';
+ $config['alipay_root_cert_path'] = $config['alipay_root_cert'] ?? '';
+ $config['service_provider_id'] = $config['pid'] ?? '';
+ }
+ $modeArr = ['normal' => 0, 'dev' => 1, 'service' => 2];
+ $config['mode'] = $modeArr[$config['mode']] ?? 0;
+ }
+
+ // 日志
+ if ($config['log']) {
+ $config['log'] = [
+ 'enable' => true,
+ 'file' => LOG_PATH . 'epaylogs' . DS . $type . '-' . date("Y-m-d") . '.log',
+ 'level' => 'debug'
+ ];
+ } else {
+ $config['log'] = [
+ 'enable' => false,
+ ];
+ }
+
+ // GuzzleHttp配置,可选
+ $config['http'] = [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
+ ];
+
+ $config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notifyx', [], false) . '/type/' . $type : $config['notify_url'];
+ $config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url'];
+ $config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url'];
+ $config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url'];
+
+ //合并自定义配置
+ $config = array_merge($config, $custom);
+
+ //v3版本时返回的结构不同
+ if (self::isVersionV3()) {
+ $config = [$type => ['default' => $config], 'logger' => $config['log'], 'http' => $config['http'], '_force' => true];
+
+ }
+ return $config;
+ }
+
+ /**
+ * 获取微信Openid
+ *
+ * @param array $custom 自定义配置信息
+ * @return mixed|string
+ */
+ public static function getOpenid($custom = [])
+ {
+ $openid = '';
+ $auth = Auth::instance();
+ if ($auth->isLogin()) {
+ $third = get_addon_info('third');
+ if ($third && $third['state']) {
+ $thirdInfo = Third::where('user_id', $auth->id)->where('platform', 'wechat')->where('apptype', 'mp')->find();
+ $openid = $thirdInfo ? $thirdInfo['openid'] : '';
+ }
+ }
+ if (!$openid) {
+ $openid = Session::get("openid");
+
+ //如果未传openid,则去读取openid
+ if (!$openid) {
+ $addonConfig = get_addon_config('epay');
+ $wechat = new Wechat($custom['app_id'] ?? $addonConfig['wechat']['app_id'], $custom['app_secret'] ?? $addonConfig['wechat']['app_secret']);
+ $openid = $wechat->getOpenid();
+ }
+ }
+ return $openid;
+ }
+
+ /**
+ * 获取SDK版本
+ * @return mixed|string
+ */
+ public static function getSdkVersion()
+ {
+ $addonConfig = get_addon_config('epay');
+ return $addonConfig['version'] ?? self::SDK_VERSION_V2;
+ }
+
+ /**
+ * 判断是否V2支付
+ * @return bool
+ */
+ public static function isVersionV2()
+ {
+ return self::getSdkVersion() === self::SDK_VERSION_V2;
+ }
+
+ /**
+ * 判断是否V3支付
+ * @return bool
+ */
+ public static function isVersionV3()
+ {
+ return self::getSdkVersion() === self::SDK_VERSION_V3;
+ }
+}
diff --git a/addons/epay/library/Wechat.php b/addons/epay/library/Wechat.php
new file mode 100644
index 0000000..5877265
--- /dev/null
+++ b/addons/epay/library/Wechat.php
@@ -0,0 +1,110 @@
+app_id = $app_id;
+ $this->app_secret = $app_secret;
+ }
+
+ /**
+ * 获取微信授权链接
+ *
+ * @return string
+ */
+ public function getAuthorizeUrl()
+ {
+ $redirect_uri = addon_url('epay/api/wechat', [], true, true);
+ $redirect_uri = urlencode($redirect_uri);
+ $state = \fast\Random::alnum();
+ Session::set('state', $state);
+ return "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->app_id}&redirect_uri={$redirect_uri}&response_type=code&scope={$this->scope}&state={$state}#wechat_redirect";
+ }
+
+ /**
+ * 获取微信openid
+ *
+ * @return mixed|string
+ */
+ public function getOpenid()
+ {
+ $openid = Session::get('openid');
+ if (!$openid) {
+ if (!isset($_GET['code'])) {
+ $url = $this->getAuthorizeUrl();
+
+ Header("Location: $url");
+ exit();
+ } else {
+ $state = Session::get('state');
+ if ($state == $_GET['state']) {
+ $code = $_GET['code'];
+ $token = $this->getAccessToken($code);
+ if (!isset($token['openid']) && isset($token['errmsg'])) {
+ exception($token['errmsg']);
+ }
+ $openid = $token['openid'] ?? '';
+ if ($openid) {
+ Session::set("openid", $openid);
+ }
+ }
+ }
+ }
+ return $openid;
+ }
+
+ /**
+ * 获取授权token网页授权
+ *
+ * @param string $code
+ * @return mixed|string
+ */
+ public function getAccessToken($code = '')
+ {
+ $params = [
+ 'appid' => $this->app_id,
+ 'secret' => $this->app_secret,
+ 'code' => $code,
+ 'grant_type' => 'authorization_code'
+ ];
+ $ret = Http::sendRequest('https://api.weixin.qq.com/sns/oauth2/access_token', $params, 'GET');
+ if ($ret['ret']) {
+ $ar = json_decode($ret['msg'], true);
+ return $ar;
+ }
+ return [];
+ }
+
+ public function getJsticket($code = '')
+ {
+ $jsticket = Session::get('jsticket');
+ if (!$jsticket) {
+ $token = $this->getAccessToken($code);
+ $params = [
+ 'access_token' => 'token',
+ 'type' => 'jsapi',
+ ];
+ $ret = Http::sendRequest('https://api.weixin.qq.com/cgi-bin/ticket/getticket', $params, 'GET');
+ if ($ret['ret']) {
+ $ar = json_decode($ret['msg'], true);
+ return $ar;
+ }
+ }
+ return $jsticket;
+ }
+}
diff --git a/addons/epay/library/hyperf/context/.gitattributes b/addons/epay/library/hyperf/context/.gitattributes
new file mode 100644
index 0000000..27b765f
--- /dev/null
+++ b/addons/epay/library/hyperf/context/.gitattributes
@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore
diff --git a/addons/epay/library/hyperf/context/LICENSE b/addons/epay/library/hyperf/context/LICENSE
new file mode 100644
index 0000000..c35d3f5
--- /dev/null
+++ b/addons/epay/library/hyperf/context/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Hyperf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/addons/epay/library/hyperf/context/composer.json b/addons/epay/library/hyperf/context/composer.json
new file mode 100644
index 0000000..04147af
--- /dev/null
+++ b/addons/epay/library/hyperf/context/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "hyperf/context",
+ "description": "A coroutine context library.",
+ "license": "MIT",
+ "keywords": [
+ "php",
+ "swoole",
+ "hyperf",
+ "context"
+ ],
+ "homepage": "https://hyperf.io",
+ "support": {
+ "docs": "https://hyperf.wiki",
+ "issues": "https://github.com/hyperf/hyperf/issues",
+ "pull-request": "https://github.com/hyperf/hyperf/pulls",
+ "source": "https://github.com/hyperf/hyperf"
+ },
+ "require": {
+ "php": ">=7.2",
+ "hyperf/engine": "^1.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "Hyperf\\Context\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "HyperfTest\\Context\\": "tests/"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/context/src/Context.php b/addons/epay/library/hyperf/context/src/Context.php
new file mode 100644
index 0000000..c8baa4c
--- /dev/null
+++ b/addons/epay/library/hyperf/context/src/Context.php
@@ -0,0 +1,112 @@
+ 0) {
+ Coroutine::getContextFor()[$id] = $value;
+ } else {
+ static::$nonCoContext[$id] = $value;
+ }
+ return $value;
+ }
+
+ public static function get(string $id, $default = null, $coroutineId = null)
+ {
+ if (Coroutine::id() > 0) {
+ return Coroutine::getContextFor($coroutineId)[$id] ?? $default;
+ }
+
+ return static::$nonCoContext[$id] ?? $default;
+ }
+
+ public static function has(string $id, $coroutineId = null)
+ {
+ if (Coroutine::id() > 0) {
+ return isset(Coroutine::getContextFor($coroutineId)[$id]);
+ }
+
+ return isset(static::$nonCoContext[$id]);
+ }
+
+ /**
+ * Release the context when you are not in coroutine environment.
+ */
+ public static function destroy(string $id)
+ {
+ unset(static::$nonCoContext[$id]);
+ }
+
+ /**
+ * Copy the context from a coroutine to current coroutine.
+ * This method will delete the origin values in current coroutine.
+ */
+ public static function copy(int $fromCoroutineId, array $keys = []): void
+ {
+ $from = Coroutine::getContextFor($fromCoroutineId);
+ if ($from === null) {
+ return;
+ }
+
+ $current = Coroutine::getContextFor();
+
+ if ($keys) {
+ $map = array_intersect_key($from->getArrayCopy(), array_flip($keys));
+ } else {
+ $map = $from->getArrayCopy();
+ }
+
+ $current->exchangeArray($map);
+ }
+
+ /**
+ * Retrieve the value and override it by closure.
+ */
+ public static function override(string $id, \Closure $closure)
+ {
+ $value = null;
+ if (self::has($id)) {
+ $value = self::get($id);
+ }
+ $value = $closure($value);
+ self::set($id, $value);
+ return $value;
+ }
+
+ /**
+ * Retrieve the value and store it if not exists.
+ * @param mixed $value
+ */
+ public static function getOrSet(string $id, $value)
+ {
+ if (! self::has($id)) {
+ return self::set($id, value($value));
+ }
+ return self::get($id);
+ }
+
+ public static function getContainer()
+ {
+ if (Coroutine::id() > 0) {
+ return Coroutine::getContextFor();
+ }
+
+ return static::$nonCoContext;
+ }
+}
diff --git a/addons/epay/library/hyperf/contract/.gitattributes b/addons/epay/library/hyperf/contract/.gitattributes
new file mode 100644
index 0000000..27b765f
--- /dev/null
+++ b/addons/epay/library/hyperf/contract/.gitattributes
@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore
diff --git a/addons/epay/library/hyperf/contract/LICENSE b/addons/epay/library/hyperf/contract/LICENSE
new file mode 100644
index 0000000..c35d3f5
--- /dev/null
+++ b/addons/epay/library/hyperf/contract/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Hyperf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/addons/epay/library/hyperf/contract/composer.json b/addons/epay/library/hyperf/contract/composer.json
new file mode 100644
index 0000000..eda94ef
--- /dev/null
+++ b/addons/epay/library/hyperf/contract/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "hyperf/contract",
+ "description": "The contracts of Hyperf.",
+ "license": "MIT",
+ "keywords": [
+ "php",
+ "swoole",
+ "hyperf"
+ ],
+ "homepage": "https://hyperf.io",
+ "support": {
+ "docs": "https://hyperf.wiki",
+ "issues": "https://github.com/hyperf/hyperf/issues",
+ "pull-request": "https://github.com/hyperf/hyperf/pulls",
+ "source": "https://github.com/hyperf/hyperf"
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "Hyperf\\Contract\\": "src/"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/contract/src/ApplicationInterface.php b/addons/epay/library/hyperf/contract/src/ApplicationInterface.php
new file mode 100644
index 0000000..24743ed
--- /dev/null
+++ b/addons/epay/library/hyperf/contract/src/ApplicationInterface.php
@@ -0,0 +1,16 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ '@Symfony' => true,
+ '@DoctrineAnnotation' => true,
+ '@PhpCsFixer' => true,
+ 'header_comment' => [
+ 'comment_type' => 'PHPDoc',
+ 'header' => $header,
+ 'separate' => 'none',
+ 'location' => 'after_declare_strict',
+ ],
+ 'array_syntax' => [
+ 'syntax' => 'short'
+ ],
+ 'list_syntax' => [
+ 'syntax' => 'short'
+ ],
+ 'concat_space' => [
+ 'spacing' => 'one'
+ ],
+ 'blank_line_before_statement' => [
+ 'statements' => [
+ 'declare',
+ ],
+ ],
+ 'general_phpdoc_annotation_remove' => [
+ 'annotations' => [
+ 'author'
+ ],
+ ],
+ 'ordered_imports' => [
+ 'imports_order' => [
+ 'class', 'function', 'const',
+ ],
+ 'sort_algorithm' => 'alpha',
+ ],
+ 'single_line_comment_style' => [
+ 'comment_types' => [
+ ],
+ ],
+ 'yoda_style' => [
+ 'always_move_variable' => false,
+ 'equal' => false,
+ 'identical' => false,
+ ],
+ 'phpdoc_align' => [
+ 'align' => 'left',
+ ],
+ 'multiline_whitespace_before_semicolons' => [
+ 'strategy' => 'no_multi_line',
+ ],
+ 'constant_case' => [
+ 'case' => 'lower',
+ ],
+ 'class_attributes_separation' => true,
+ 'combine_consecutive_unsets' => true,
+ 'declare_strict_types' => true,
+ 'linebreak_after_opening_tag' => true,
+ 'lowercase_static_reference' => true,
+ 'no_useless_else' => true,
+ 'no_unused_imports' => true,
+ 'not_operator_with_successor_space' => true,
+ 'not_operator_with_space' => false,
+ 'ordered_class_elements' => true,
+ 'php_unit_strict' => false,
+ 'phpdoc_separation' => false,
+ 'single_quote' => true,
+ 'standardize_not_equals' => true,
+ 'multiline_comment_opening_closing' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->exclude('vendor')
+ ->in(__DIR__)
+ )
+ ->setUsingCache(false);
diff --git a/addons/epay/library/hyperf/engine/.phpstorm.meta.php b/addons/epay/library/hyperf/engine/.phpstorm.meta.php
new file mode 100644
index 0000000..1014069
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/.phpstorm.meta.php
@@ -0,0 +1,6 @@
+" version="1.0" license="MIT" app.name="Hyperf"
+
+ARG timezone
+ARG PHP_VERSION
+
+ENV TIMEZONE=${timezone:-"Asia/Shanghai"}
+ENV COMPOSER_ROOT_VERSION="v1.2.0"
+
+# update
+RUN set -ex \
+ # show php version and extensions
+ && php -v \
+ && php -m \
+ && php --ri swoole \
+ # ---------- some config ----------
+ && cd "/etc/php${PHP_VERSION%\.*}" \
+ # - config PHP
+ && { \
+ echo "upload_max_filesize=128M"; \
+ echo "post_max_size=128M"; \
+ echo "memory_limit=1G"; \
+ echo "date.timezone=${TIMEZONE}"; \
+ } | tee conf.d/99_overrides.ini \
+ # - config timezone
+ && ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
+ && echo "${TIMEZONE}" > /etc/timezone \
+ # ---------- clear works ----------
+ && rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
+ && echo -e "\033[42;37m Build Completed :).\033[0m\n"
+
+WORKDIR /opt/www
+
+COPY . /opt/www
+
+RUN composer install -o
diff --git a/addons/epay/library/hyperf/engine/README.md b/addons/epay/library/hyperf/engine/README.md
new file mode 100644
index 0000000..c2888db
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/README.md
@@ -0,0 +1,7 @@
+# Swoole Engine
+
+
+
+```
+composer require hyperf/engine
+```
diff --git a/addons/epay/library/hyperf/engine/composer.json b/addons/epay/library/hyperf/engine/composer.json
new file mode 100644
index 0000000..04b49ee
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/composer.json
@@ -0,0 +1,49 @@
+{
+ "name": "hyperf/engine",
+ "type": "library",
+ "license": "MIT",
+ "keywords": [
+ "php",
+ "hyperf"
+ ],
+ "description": "",
+ "autoload": {
+ "psr-4": {
+ "Hyperf\\Engine\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "HyperfTest\\": "tests"
+ }
+ },
+ "require": {
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "hyperf/guzzle": "^2.2",
+ "phpstan/phpstan": "^1.0",
+ "phpunit/phpunit": "^9.4",
+ "swoole/ide-helper": "dev-master"
+ },
+ "suggest": {
+ "ext-swoole": ">=4.5"
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "config": {
+ "optimize-autoloader": true,
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "scripts": {
+ "test": "phpunit -c phpunit.xml --colors=always",
+ "analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
+ "cs-fix": "php-cs-fixer fix $1"
+ }
+}
diff --git a/addons/epay/library/hyperf/engine/phpunit.xml b/addons/epay/library/hyperf/engine/phpunit.xml
new file mode 100644
index 0000000..d2c615a
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/phpunit.xml
@@ -0,0 +1,15 @@
+
+
+
+ ./tests/
+
+
\ No newline at end of file
diff --git a/addons/epay/library/hyperf/engine/src/Channel.php b/addons/epay/library/hyperf/engine/src/Channel.php
new file mode 100644
index 0000000..9683a45
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/src/Channel.php
@@ -0,0 +1,142 @@
+ 80000 && SWOOLE_VERSION_ID >= 50000) {
+ class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
+ {
+ protected bool $closed = false;
+
+ public function push(mixed $data, float $timeout = -1): bool
+ {
+ return parent::push($data, $timeout);
+ }
+
+ public function pop(float $timeout = -1): mixed
+ {
+ return parent::pop($timeout);
+ }
+
+ public function getCapacity(): int
+ {
+ return $this->capacity;
+ }
+
+ public function getLength(): int
+ {
+ return $this->length();
+ }
+
+ public function isAvailable(): bool
+ {
+ return ! $this->isClosing();
+ }
+
+ public function close(): bool
+ {
+ $this->closed = true;
+ return parent::close();
+ }
+
+ public function hasProducers(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function hasConsumers(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function isReadable(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function isWritable(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function isClosing(): bool
+ {
+ return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
+ }
+
+ public function isTimeout(): bool
+ {
+ return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
+ }
+ }
+} else {
+ class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
+ {
+ /**
+ * @var bool
+ */
+ protected $closed = false;
+
+ public function getCapacity(): int
+ {
+ return $this->capacity;
+ }
+
+ public function getLength(): int
+ {
+ return $this->length();
+ }
+
+ public function isAvailable(): bool
+ {
+ return ! $this->isClosing();
+ }
+
+ public function close(): bool
+ {
+ $this->closed = true;
+ return parent::close();
+ }
+
+ public function hasProducers(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function hasConsumers(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function isReadable(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function isWritable(): bool
+ {
+ throw new RuntimeException('Not supported.');
+ }
+
+ public function isClosing(): bool
+ {
+ return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
+ }
+
+ public function isTimeout(): bool
+ {
+ return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/engine/src/Constant.php b/addons/epay/library/hyperf/engine/src/Constant.php
new file mode 100644
index 0000000..48a347b
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/src/Constant.php
@@ -0,0 +1,25 @@
+ 80000 && SWOOLE_VERSION_ID >= 50000) {
+ interface ChannelInterface
+ {
+ /**
+ * @param float|int $timeout [optional] = -1
+ */
+ public function push(mixed $data, float $timeout = -1): bool;
+
+ /**
+ * @param float $timeout seconds [optional] = -1
+ * @return mixed when pop failed, return false
+ */
+ public function pop(float $timeout = -1): mixed;
+
+ /**
+ * Swow: When the channel is closed, all the data in it will be destroyed.
+ * Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
+ */
+ public function close(): bool;
+
+ public function getCapacity(): int;
+
+ public function getLength(): int;
+
+ public function isAvailable(): bool;
+
+ public function hasProducers(): bool;
+
+ public function hasConsumers(): bool;
+
+ public function isEmpty(): bool;
+
+ public function isFull(): bool;
+
+ public function isReadable(): bool;
+
+ public function isWritable(): bool;
+
+ public function isClosing(): bool;
+
+ public function isTimeout(): bool;
+ }
+} else {
+ interface ChannelInterface
+ {
+ /**
+ * @param mixed $data [required]
+ * @param float|int $timeout [optional] = -1
+ * @return bool
+ */
+ public function push($data, $timeout = -1);
+
+ /**
+ * @param float $timeout seconds [optional] = -1
+ * @return mixed when pop failed, return false
+ */
+ public function pop($timeout = -1);
+
+ /**
+ * Swow: When the channel is closed, all the data in it will be destroyed.
+ * Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
+ * @return mixed
+ */
+ public function close(): bool;
+
+ /**
+ * @return int
+ */
+ public function getCapacity();
+
+ /**
+ * @return int
+ */
+ public function getLength();
+
+ /**
+ * @return bool
+ */
+ public function isAvailable();
+
+ /**
+ * @return bool
+ */
+ public function hasProducers();
+
+ /**
+ * @return bool
+ */
+ public function hasConsumers();
+
+ /**
+ * @return bool
+ */
+ public function isEmpty();
+
+ /**
+ * @return bool
+ */
+ public function isFull();
+
+ /**
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * @return bool
+ */
+ public function isClosing();
+
+ /**
+ * @return bool
+ */
+ public function isTimeout();
+ }
+}
diff --git a/addons/epay/library/hyperf/engine/src/Contract/CoroutineInterface.php b/addons/epay/library/hyperf/engine/src/Contract/CoroutineInterface.php
new file mode 100644
index 0000000..439e80e
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/src/Contract/CoroutineInterface.php
@@ -0,0 +1,70 @@
+callable = $callable;
+ }
+
+ public static function create(callable $callable, ...$data)
+ {
+ $coroutine = new static($callable);
+ $coroutine->execute(...$data);
+ return $coroutine;
+ }
+
+ public function execute(...$data)
+ {
+ $this->id = SwooleCo::create($this->callable, ...$data);
+ return $this;
+ }
+
+ public function getId()
+ {
+ if (is_null($this->id)) {
+ throw new RuntimeException('Coroutine was not be executed.');
+ }
+ return $this->id;
+ }
+
+ public static function id()
+ {
+ return SwooleCo::getCid();
+ }
+
+ public static function pid(?int $id = null)
+ {
+ if ($id) {
+ $cid = SwooleCo::getPcid($id);
+ if ($cid === false) {
+ throw new CoroutineDestroyedException(sprintf('Coroutine #%d has been destroyed.', $id));
+ }
+ } else {
+ $cid = SwooleCo::getPcid();
+ }
+ if ($cid === false) {
+ throw new RunningInNonCoroutineException('Non-Coroutine environment don\'t has parent coroutine id.');
+ }
+ return max(0, $cid);
+ }
+
+ public static function set(array $config)
+ {
+ SwooleCo::set($config);
+ }
+
+ /**
+ * @return null|\ArrayObject
+ */
+ public static function getContextFor(?int $id = null)
+ {
+ if ($id === null) {
+ return SwooleCo::getContext();
+ }
+
+ return SwooleCo::getContext($id);
+ }
+
+ public static function defer(callable $callable)
+ {
+ SwooleCo::defer($callable);
+ }
+}
diff --git a/addons/epay/library/hyperf/engine/src/Exception/CoroutineDestroyedException.php b/addons/epay/library/hyperf/engine/src/Exception/CoroutineDestroyedException.php
new file mode 100644
index 0000000..1218249
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/src/Exception/CoroutineDestroyedException.php
@@ -0,0 +1,16 @@
+setMethod($method);
+ $this->setData($contents);
+ $this->setHeaders($this->encodeHeaders($headers));
+ $this->execute($path);
+ if ($this->errCode !== 0) {
+ throw new HttpClientException($this->errMsg, $this->errCode);
+ }
+ return new RawResponse(
+ $this->statusCode,
+ $this->decodeHeaders($this->headers ?? []),
+ $this->body,
+ $version
+ );
+ }
+
+ /**
+ * @param string[] $headers
+ * @return string[][]
+ */
+ private function decodeHeaders(array $headers): array
+ {
+ $result = [];
+ foreach ($headers as $name => $header) {
+ // The key of header is lower case.
+ $result[$name][] = $header;
+ }
+ if ($this->set_cookie_headers) {
+ $result['set-cookie'] = $this->set_cookie_headers;
+ }
+ return $result;
+ }
+
+ /**
+ * Swoole engine not support two dimensional array.
+ * @param string[][] $headers
+ * @return string[]
+ */
+ private function encodeHeaders(array $headers): array
+ {
+ $result = [];
+ foreach ($headers as $name => $value) {
+ $result[$name] = is_array($value) ? implode(',', $value) : $value;
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/hyperf/engine/src/Http/FdGetter.php b/addons/epay/library/hyperf/engine/src/Http/FdGetter.php
new file mode 100644
index 0000000..51ca079
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/src/Http/FdGetter.php
@@ -0,0 +1,22 @@
+fd;
+ }
+}
diff --git a/addons/epay/library/hyperf/engine/src/Http/RawResponse.php b/addons/epay/library/hyperf/engine/src/Http/RawResponse.php
new file mode 100644
index 0000000..8a8fb20
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/src/Http/RawResponse.php
@@ -0,0 +1,47 @@
+statusCode = $statusCode;
+ $this->headers = $headers;
+ $this->body = $body;
+ $this->version = $version;
+ }
+}
diff --git a/addons/epay/library/hyperf/engine/src/Socket.php b/addons/epay/library/hyperf/engine/src/Socket.php
new file mode 100644
index 0000000..17a8a1b
--- /dev/null
+++ b/addons/epay/library/hyperf/engine/src/Socket.php
@@ -0,0 +1,16 @@
+
+ */
+ protected $events = [];
+
+ public function __construct(Response $connection, Request $request)
+ {
+ $this->connection = $connection;
+ $this->connection->upgrade();
+ }
+
+ public function on(string $event, callable $callback): void
+ {
+ $this->events[$event] = $callback;
+ }
+
+ public function start(): void
+ {
+ while (true) {
+ $frame = $this->connection->recv();
+ if ($frame === false || $frame instanceof CloseFrame || $frame === '') {
+ $callback = $this->events[static::ON_CLOSE];
+ $callback($this->connection, $this->connection->fd);
+ break;
+ }
+
+ $callback = $this->events[static::ON_MESSAGE];
+ $callback($this->connection, $frame);
+ }
+
+ $this->connection = null;
+ $this->events = [];
+ }
+}
diff --git a/addons/epay/library/hyperf/macroable/.gitattributes b/addons/epay/library/hyperf/macroable/.gitattributes
new file mode 100644
index 0000000..27b765f
--- /dev/null
+++ b/addons/epay/library/hyperf/macroable/.gitattributes
@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore
diff --git a/addons/epay/library/hyperf/macroable/LICENSE b/addons/epay/library/hyperf/macroable/LICENSE
new file mode 100644
index 0000000..10b0738
--- /dev/null
+++ b/addons/epay/library/hyperf/macroable/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) Taylor Otwell
+Copyright (c) Hyperf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/addons/epay/library/hyperf/macroable/composer.json b/addons/epay/library/hyperf/macroable/composer.json
new file mode 100644
index 0000000..c828d10
--- /dev/null
+++ b/addons/epay/library/hyperf/macroable/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "hyperf/macroable",
+ "description": "Hyperf Macroable package which come from illuminate/macroable",
+ "license": "MIT",
+ "keywords": [
+ "php",
+ "swoole",
+ "hyperf",
+ "macroable"
+ ],
+ "homepage": "https://hyperf.io",
+ "support": {
+ "docs": "https://hyperf.wiki",
+ "issues": "https://github.com/hyperf/hyperf/issues",
+ "pull-request": "https://github.com/hyperf/hyperf/pulls",
+ "source": "https://github.com/hyperf/hyperf"
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "Hyperf\\Macroable\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "HyperfTest\\Macroable\\": "tests/"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/macroable/src/Macroable.php b/addons/epay/library/hyperf/macroable/src/Macroable.php
new file mode 100644
index 0000000..b58417a
--- /dev/null
+++ b/addons/epay/library/hyperf/macroable/src/Macroable.php
@@ -0,0 +1,129 @@
+bindTo(null, static::class);
+ }
+
+ return $macro(...$parameters);
+ }
+
+ /**
+ * Dynamically handle calls to the class.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @throws \BadMethodCallException
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ if (! static::hasMacro($method)) {
+ throw new BadMethodCallException(sprintf(
+ 'Method %s::%s does not exist.',
+ static::class,
+ $method
+ ));
+ }
+
+ $macro = static::$macros[$method];
+
+ if ($macro instanceof Closure) {
+ $macro = $macro->bindTo($this, static::class);
+ }
+
+ return $macro(...$parameters);
+ }
+
+ /**
+ * Register a custom macro.
+ *
+ * @param string $name
+ * @param callable|object $macro
+ */
+ public static function macro($name, $macro)
+ {
+ static::$macros[$name] = $macro;
+ }
+
+ /**
+ * Mix another object into the class.
+ *
+ * @param object $mixin
+ * @param bool $replace
+ *
+ * @throws \ReflectionException
+ */
+ public static function mixin($mixin, $replace = true)
+ {
+ $methods = (new ReflectionClass($mixin))->getMethods(
+ ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
+ );
+
+ foreach ($methods as $method) {
+ if ($replace || ! static::hasMacro($method->name)) {
+ $method->setAccessible(true);
+ static::macro($method->name, $method->invoke($mixin));
+ }
+ }
+ }
+
+ /**
+ * Checks if macro is registered.
+ *
+ * @param string $name
+ * @return bool
+ */
+ public static function hasMacro($name)
+ {
+ return isset(static::$macros[$name]);
+ }
+}
diff --git a/addons/epay/library/hyperf/pimple/.gitattributes b/addons/epay/library/hyperf/pimple/.gitattributes
new file mode 100644
index 0000000..27b765f
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/.gitattributes
@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore
diff --git a/addons/epay/library/hyperf/pimple/.gitignore b/addons/epay/library/hyperf/pimple/.gitignore
new file mode 100644
index 0000000..7e11e88
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/.gitignore
@@ -0,0 +1,4 @@
+/vendor/
+composer.lock
+*.cache
+*.log
\ No newline at end of file
diff --git a/addons/epay/library/hyperf/pimple/.php-cs-fixer.php b/addons/epay/library/hyperf/pimple/.php-cs-fixer.php
new file mode 100644
index 0000000..4e06aab
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/.php-cs-fixer.php
@@ -0,0 +1,92 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ '@Symfony' => true,
+ '@DoctrineAnnotation' => true,
+ '@PhpCsFixer' => true,
+ 'header_comment' => [
+ 'comment_type' => 'PHPDoc',
+ 'header' => $header,
+ 'separate' => 'none',
+ 'location' => 'after_declare_strict',
+ ],
+ 'array_syntax' => [
+ 'syntax' => 'short'
+ ],
+ 'list_syntax' => [
+ 'syntax' => 'short'
+ ],
+ 'concat_space' => [
+ 'spacing' => 'one'
+ ],
+ 'blank_line_before_statement' => [
+ 'statements' => [
+ 'declare',
+ ],
+ ],
+ 'general_phpdoc_annotation_remove' => [
+ 'annotations' => [
+ 'author'
+ ],
+ ],
+ 'ordered_imports' => [
+ 'imports_order' => [
+ 'class', 'function', 'const',
+ ],
+ 'sort_algorithm' => 'alpha',
+ ],
+ 'single_line_comment_style' => [
+ 'comment_types' => [
+ ],
+ ],
+ 'yoda_style' => [
+ 'always_move_variable' => false,
+ 'equal' => false,
+ 'identical' => false,
+ ],
+ 'phpdoc_align' => [
+ 'align' => 'left',
+ ],
+ 'multiline_whitespace_before_semicolons' => [
+ 'strategy' => 'no_multi_line',
+ ],
+ 'constant_case' => [
+ 'case' => 'lower',
+ ],
+ 'class_attributes_separation' => true,
+ 'combine_consecutive_unsets' => true,
+ 'declare_strict_types' => true,
+ 'linebreak_after_opening_tag' => true,
+ 'lowercase_static_reference' => true,
+ 'no_useless_else' => true,
+ 'no_unused_imports' => true,
+ 'not_operator_with_successor_space' => true,
+ 'not_operator_with_space' => false,
+ 'ordered_class_elements' => true,
+ 'php_unit_strict' => false,
+ 'phpdoc_separation' => false,
+ 'single_quote' => true,
+ 'standardize_not_equals' => true,
+ 'multiline_comment_opening_closing' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->exclude('bin')
+ ->exclude('public')
+ ->exclude('runtime')
+ ->exclude('vendor')
+ ->in(__DIR__)
+ )
+ ->setUsingCache(false);
diff --git a/addons/epay/library/hyperf/pimple/.phpstorm.meta.php b/addons/epay/library/hyperf/pimple/.phpstorm.meta.php
new file mode 100644
index 0000000..1014069
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/.phpstorm.meta.php
@@ -0,0 +1,6 @@
+set(TranslatorLoaderInterface::class, function () use ($container) {
+ $config = $container->get(ConfigInterface::class);
+ $files = $container->get(Filesystem::class);
+ $path = $config->get('translation.path');
+
+ return make(FileLoader::class, compact('files', 'path'));
+ });
+ }
+}
+```
+
+```php
+set(TranslatorInterface::class, function () use ($container) {
+ $config = $container->get(ConfigInterface::class);
+ $locale = $config->get('translation.locale');
+ $fallbackLocale = $config->get('translation.fallback_locale');
+
+ $loader = $container->get(TranslatorLoaderInterface::class);
+
+ $translator = make(Translator::class, compact('loader', 'locale'));
+ $translator->setFallback((string) $fallbackLocale);
+
+ return $translator;
+ });
+ }
+}
+
+```
+
+3. `EasySwoole` 事件注册器在 `EasySwooleEvent.php` 中,所以我们需要在 `initialize()` 中初始化我们的容器和国际化组件。
+
+> 以下 Config 组件,可以自行封装,这里方便起见直接配置。
+
+```php
+set(ConfigInterface::class, new Config([
+ 'translation' => [
+ 'locale' => 'zh_CN',
+ 'fallback_locale' => 'en',
+ 'path' => EASYSWOOLE_ROOT . '/storage/languages',
+ ],
+ ]));
+ }
+}
+```
+
+4. 修改控制器,使用国际化组件
+
+```php
+get(TranslatorInterface::class);
+
+ $data = [
+ 'message' => $translator->trans('message.hello', ['name' => 'Hyperf']),
+ ];
+
+ $this->response()->write(Json::encode($data));
+ }
+}
+
+```
+
+5. 添加国际化配置
+
+```php
+// storage/languages/en/message.php
+return [
+ 'hello' => 'Hello :name',
+];
+
+// storage/languages/zh_CN/message.php
+return [
+ 'hello' => '你好 :name',
+];
+```
+
+6. 测试
+
+```
+$ curl http://127.0.0.1:9501/
+{"message":"你好 Hyperf"}
+```
+
+
+
diff --git a/addons/epay/library/hyperf/pimple/composer.json b/addons/epay/library/hyperf/pimple/composer.json
new file mode 100644
index 0000000..b878c0a
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/composer.json
@@ -0,0 +1,53 @@
+{
+ "name": "hyperf/pimple",
+ "type": "library",
+ "license": "MIT",
+ "keywords": [
+ "php",
+ "hyperf",
+ "container",
+ "psr11"
+ ],
+ "description": "Pimple Container",
+ "autoload": {
+ "psr-4": {
+ "Hyperf\\Pimple\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "HyperfTest\\": "tests"
+ }
+ },
+ "require": {
+ "php": ">=7.4",
+ "doctrine/instantiator": "^1.0",
+ "hyperf/utils": "^2.2|^3.0",
+ "pimple/pimple": "^3.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "mockery/mockery": "^1.3",
+ "phpstan/phpstan": "^1.0",
+ "phpunit/phpunit": ">=7.0"
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "config": {
+ "optimize-autoloader": true,
+ "sort-packages": true
+ },
+ "scripts": {
+ "test": "phpunit -c phpunit.xml --colors=always",
+ "analyse": "phpstan analyse --memory-limit 300M -l 0 ./src",
+ "cs-fix": "php-cs-fixer fix $1"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ },
+ "hyperf": {
+ "config": "Hyperf\\Pimple\\ConfigProvider"
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/pimple/phpunit.xml b/addons/epay/library/hyperf/pimple/phpunit.xml
new file mode 100644
index 0000000..d2c615a
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/phpunit.xml
@@ -0,0 +1,15 @@
+
+
+
+ ./tests/
+
+
\ No newline at end of file
diff --git a/addons/epay/library/hyperf/pimple/src/ConfigProvider.php b/addons/epay/library/hyperf/pimple/src/ConfigProvider.php
new file mode 100644
index 0000000..be00f3f
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/src/ConfigProvider.php
@@ -0,0 +1,32 @@
+ [
+ ],
+ 'commands' => [
+ ],
+ 'annotations' => [
+ 'scan' => [
+ 'paths' => [
+ __DIR__,
+ ],
+ ],
+ ],
+ ];
+ }
+}
diff --git a/addons/epay/library/hyperf/pimple/src/Container.php b/addons/epay/library/hyperf/pimple/src/Container.php
new file mode 100644
index 0000000..aab71dc
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/src/Container.php
@@ -0,0 +1,139 @@
+pimple = $pimple;
+ $this->pimple[ContainerInterface::class] = $this;
+ $this->pimple[PsrContainerInterface::class] = $this;
+ }
+
+ public function get($id)
+ {
+ if ($this->has($id)) {
+ return $this->pimple[$id];
+ }
+
+ return $this->pimple[$id] = $this->make($id);
+ }
+
+ public function has($id): bool
+ {
+ return isset($this->pimple[$id]);
+ }
+
+ public function make(string $name, array $parameters = [])
+ {
+ if (! class_exists($name)) {
+ throw new NotFoundException("Entry {$name} is not found.");
+ }
+
+ $ref = $this->reflection[$name] ?? new ReflectionClass($name);
+ $constructor = $ref->getConstructor();
+ $args = [];
+ if ($constructor && $constructor->isPublic()) {
+ $args = $this->resolveParameters($constructor, $parameters);
+ }
+
+ $instance = new $name(...$args);
+
+ $this->reflection[$name] = $ref;
+
+ return $instance;
+ }
+
+ public function set(string $name, $entry): void
+ {
+ $this->pimple[$name] = $entry;
+ }
+
+ public function define(string $name, $definition): void
+ {
+ throw new NotSupportException('Method define is not support.');
+ }
+
+ public function unbind(string $name): void
+ {
+ $this->pimple[$name] = null;
+ }
+
+ protected function resolveParameters(ReflectionMethod $method, $parameters = [])
+ {
+ $args = [];
+ foreach ($method->getParameters() as $index => $parameter) {
+ if (array_key_exists($parameter->getName(), $parameters)) {
+ $value = $parameters[$parameter->getName()];
+ } elseif (array_key_exists($index, $parameters)) {
+ $value = $parameters[$index];
+ } elseif ($parameter->getType() && $this->has($parameter->getType()->getName())) {
+ $value = $this->get($parameter->getType()->getName());
+ } else {
+ if ($parameter->isDefaultValueAvailable() || $parameter->isOptional()) {
+ $value = $this->getParameterDefaultValue($parameter, $method);
+ } else {
+ throw new InvalidDefinitionException(sprintf(
+ 'Parameter $%s of %s has no value defined or guessable',
+ $parameter->getName(),
+ $this->getFunctionName($method)
+ ));
+ }
+ }
+
+ $args[] = $value;
+ }
+
+ return $args;
+ }
+
+ protected function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function)
+ {
+ try {
+ return $parameter->getDefaultValue();
+ } catch (ReflectionException $e) {
+ throw new InvalidDefinitionException(sprintf(
+ 'The parameter "%s" of %s has no type defined or guessable. It has a default value, '
+ . 'but the default value can\'t be read through Reflection because it is a PHP internal class.',
+ $parameter->getName(),
+ $this->getFunctionName($function)
+ ));
+ }
+ }
+
+ private function getFunctionName(ReflectionMethod $method): string
+ {
+ return $method->getName() . '()';
+ }
+}
diff --git a/addons/epay/library/hyperf/pimple/src/ContainerFactory.php b/addons/epay/library/hyperf/pimple/src/ContainerFactory.php
new file mode 100644
index 0000000..fed4e3d
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/src/ContainerFactory.php
@@ -0,0 +1,37 @@
+providers = $providers;
+ }
+
+ public function __invoke()
+ {
+ $container = new Container(new Pimple\Container());
+ foreach ($this->providers as $provider) {
+ /** @var ProviderInterface $instance */
+ $instance = new $provider();
+ $instance->register($container);
+ }
+
+ return ApplicationContext::setContainer($container);
+ }
+}
diff --git a/addons/epay/library/hyperf/pimple/src/Exception/InvalidDefinitionException.php b/addons/epay/library/hyperf/pimple/src/Exception/InvalidDefinitionException.php
new file mode 100644
index 0000000..8b9032b
--- /dev/null
+++ b/addons/epay/library/hyperf/pimple/src/Exception/InvalidDefinitionException.php
@@ -0,0 +1,18 @@
+=7.2",
+ "doctrine/inflector": "^2.0",
+ "hyperf/context": "~2.2.0",
+ "hyperf/contract": "~2.2.0",
+ "hyperf/engine": "^1.1",
+ "hyperf/macroable": "~2.2.0"
+ },
+ "suggest": {
+ "ext-swoole": "Required to use methods related to swoole (>=4.5).",
+ "symfony/var-dumper": "Required to use the dd function (^5.0).",
+ "symfony/serializer": "Required to use SymfonyNormalizer (^5.0)",
+ "symfony/property-access": "Required to use SymfonyNormalizer (^5.0)",
+ "hyperf/di": "Required to use ExceptionNormalizer",
+ "nikic/php-parser": "Required to use PhpParser. (^4.0)"
+ },
+ "autoload": {
+ "files": [
+ "src/Functions.php"
+ ],
+ "psr-4": {
+ "Hyperf\\Utils\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "HyperfTest\\Utils\\": "tests/"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "hyperf": {
+ "config": "Hyperf\\Utils\\ConfigProvider"
+ },
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/ApplicationContext.php b/addons/epay/library/hyperf/utils/src/ApplicationContext.php
new file mode 100644
index 0000000..dc16b7b
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/ApplicationContext.php
@@ -0,0 +1,41 @@
+all();
+ } elseif (! is_array($values)) {
+ continue;
+ }
+ $results[] = $values;
+ }
+ return array_merge([], ...$results);
+ }
+
+ /**
+ * Cross join the given arrays, returning all possible permutations.
+ *
+ * @param array ...$arrays
+ */
+ public static function crossJoin(...$arrays): array
+ {
+ $results = [[]];
+ foreach ($arrays as $index => $array) {
+ $append = [];
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+ $append[] = $product;
+ }
+ }
+ $results = $append;
+ }
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function divide($array)
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ */
+ public static function dot(array $array, string $prepend = ''): array
+ {
+ $results = [];
+ foreach ($array as $key => $value) {
+ if (is_array($value) && ! empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend . $key . '.'));
+ } else {
+ $results[$prepend . $key] = $value;
+ }
+ }
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of keys.
+ *
+ * @param array|string $keys
+ */
+ public static function except(array $array, $keys): array
+ {
+ static::forget($array, $keys);
+ return $array;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param array|\ArrayAccess $array
+ * @param int|string $key
+ */
+ public static function exists($array, $key): bool
+ {
+ if ($array instanceof ArrayAccess) {
+ return $array->offsetExists($key);
+ }
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param null|mixed $default
+ */
+ public static function first(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return value($default);
+ }
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $value, $key)) {
+ return $value;
+ }
+ }
+ return value($default);
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param null|mixed $default
+ */
+ public static function last(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ return empty($array) ? value($default) : end($array);
+ }
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ * @param float|int $depth
+ */
+ public static function flatten(array $array, $depth = INF): array
+ {
+ $result = [];
+ foreach ($array as $item) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+ if (! is_array($item)) {
+ $result[] = $item;
+ } elseif ($depth === 1) {
+ $result = array_merge($result, array_values($item));
+ } else {
+ $result = array_merge($result, static::flatten($item, $depth - 1));
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array|string $keys
+ */
+ public static function forget(array &$array, $keys): void
+ {
+ $original = &$array;
+ $keys = (array) $keys;
+ if (count($keys) === 0) {
+ return;
+ }
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+ continue;
+ }
+ $parts = explode('.', (string) $key);
+ // clean up before each pass
+ $array = &$original;
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param array|\ArrayAccess $array
+ * @param null|int|string $key
+ * @param mixed $default
+ */
+ public static function get($array, $key = null, $default = null)
+ {
+ if (! static::accessible($array)) {
+ return value($default);
+ }
+ if (is_null($key)) {
+ return $array;
+ }
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+ if (! is_string($key) || strpos($key, '.') === false) {
+ return $array[$key] ?? value($default);
+ }
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($array) && static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return value($default);
+ }
+ }
+ return $array;
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param array|\ArrayAccess $array
+ * @param null|array|string $keys
+ */
+ public static function has($array, $keys): bool
+ {
+ if (is_null($keys)) {
+ return false;
+ }
+ $keys = (array) $keys;
+ if (! $array) {
+ return false;
+ }
+ if ($keys === []) {
+ return false;
+ }
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+ if (static::exists($array, $key)) {
+ continue;
+ }
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Determines if an array is associative.
+ * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ */
+ public static function isAssoc(array $array): bool
+ {
+ $keys = array_keys($array);
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array|string $keys
+ */
+ public static function only(array $array, $keys): array
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Pluck an array of values from an array.
+ *
+ * @param array|string $value
+ * @param null|array|string $key
+ */
+ public static function pluck(array $array, $value, $key = null): array
+ {
+ $results = [];
+ [$value, $key] = static::explodePluckParameters($value, $key);
+ foreach ($array as $item) {
+ $itemValue = data_get($item, $value);
+ // If the key is "null", we will just append the value to the array and keep
+ // looping. Otherwise we will key the array using the value of the key we
+ // received from the developer. Then we'll return the final array form.
+ if (is_null($key)) {
+ $results[] = $itemValue;
+ } else {
+ $itemKey = data_get($item, $key);
+ if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
+ $itemKey = (string) $itemKey;
+ }
+ $results[$itemKey] = $itemValue;
+ }
+ }
+ return $results;
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param array $array
+ * @param null|TKey $key
+ * @param TValue $value
+ * @return array
+ */
+ public static function prepend(array $array, $value, $key = null): array
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param null|mixed $default
+ */
+ public static function pull(array &$array, string $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+ static::forget($array, $key);
+ return $value;
+ }
+
+ /**
+ * Get one or a specified number of random values from an array.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random(array $array, int $number = null)
+ {
+ $requested = is_null($number) ? 1 : $number;
+ $count = count($array);
+ if ($requested > $count) {
+ throw new InvalidArgumentException("You requested {$requested} items, but there are only {$count} items available.");
+ }
+ if (is_null($number)) {
+ return $array[array_rand($array)];
+ }
+ if ((int) $number === 0) {
+ return [];
+ }
+ $keys = array_rand($array, $number);
+ $results = [];
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param null|int|string $key
+ * @param mixed $value
+ */
+ public static function set(array &$array, $key, $value): array
+ {
+ if (is_null($key)) {
+ return $array = $value;
+ }
+ if (! is_string($key)) {
+ $array[$key] = $value;
+ return $array;
+ }
+ $keys = explode('.', $key);
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (! isset($array[$key]) || ! is_array($array[$key])) {
+ $array[$key] = [];
+ }
+ $array = &$array[$key];
+ }
+ $array[array_shift($keys)] = $value;
+ return $array;
+ }
+
+ /**
+ * Shuffle the given array and return the result.
+ */
+ public static function shuffle(array $array, int $seed = null): array
+ {
+ if (is_null($seed)) {
+ shuffle($array);
+ } else {
+ srand($seed);
+ usort($array, function () {
+ return rand(-1, 1);
+ });
+ }
+ return $array;
+ }
+
+ /**
+ * Sort the array using the given callback or "dot" notation.
+ *
+ * @param null|callable|string $callback
+ */
+ public static function sort(array $array, $callback = null): array
+ {
+ return Collection::make($array)->sortBy($callback)->all();
+ }
+
+ /**
+ * Recursively sort an array by keys and values.
+ */
+ public static function sortRecursive(array $array): array
+ {
+ foreach ($array as &$value) {
+ if (is_array($value)) {
+ $value = static::sortRecursive($value);
+ }
+ }
+ if (static::isAssoc($array)) {
+ ksort($array);
+ } else {
+ sort($array);
+ }
+ return $array;
+ }
+
+ /**
+ * Convert the array into a query string.
+ */
+ public static function query(array $array): string
+ {
+ return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
+ }
+
+ /**
+ * Filter the array using the given callback.
+ */
+ public static function where(array $array, callable $callback): array
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * If the given value is not an array and not null, wrap it in one.
+ * @param mixed $value
+ */
+ public static function wrap($value): array
+ {
+ if (is_null($value)) {
+ return [];
+ }
+ return ! is_array($value) ? [$value] : $value;
+ }
+
+ /**
+ * Make array elements unique.
+ */
+ public static function unique(array $array): array
+ {
+ $result = [];
+ foreach ($array ?? [] as $key => $item) {
+ if (is_array($item)) {
+ $result[$key] = self::unique($item);
+ } else {
+ $result[$key] = $item;
+ }
+ }
+
+ if (! self::isAssoc($result)) {
+ return array_unique($result);
+ }
+
+ return $result;
+ }
+
+ public static function merge(array $array1, array $array2, bool $unique = true): array
+ {
+ $isAssoc = static::isAssoc($array1 ?: $array2);
+ if ($isAssoc) {
+ foreach ($array2 as $key => $value) {
+ if (is_array($value)) {
+ $array1[$key] = static::merge($array1[$key] ?? [], $value, $unique);
+ } else {
+ $array1[$key] = $value;
+ }
+ }
+ } else {
+ foreach ($array2 as $key => $value) {
+ if ($unique && in_array($value, $array1, true)) {
+ continue;
+ }
+ $array1[] = $value;
+ }
+
+ $array1 = array_values($array1);
+ }
+ return $array1;
+ }
+
+ /**
+ * Explode the "value" and "key" arguments passed to "pluck".
+ *
+ * @param array|string $value
+ * @param null|array|string $key
+ */
+ protected static function explodePluckParameters($value, $key): array
+ {
+ $value = is_string($value) ? explode('.', $value) : $value;
+ $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
+ return [$value, $key];
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Backoff.php b/addons/epay/library/hyperf/utils/src/Backoff.php
new file mode 100644
index 0000000..6e486b7
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Backoff.php
@@ -0,0 +1,84 @@
+ Backoff::CAP) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'first backoff interval must be less or equal than %d milliseconds',
+ self::CAP
+ )
+ );
+ }
+
+ $this->firstMs = $firstMs;
+ $this->currentMs = $firstMs;
+ }
+
+ /**
+ * Sleep until the next execution.
+ */
+ public function sleep(): void
+ {
+ if ($this->currentMs === 0) {
+ return;
+ }
+
+ usleep($this->currentMs * 1000);
+
+ // update backoff using Decorrelated Jitter
+ // see: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
+ $this->currentMs = rand($this->firstMs, $this->currentMs * 3);
+
+ if ($this->currentMs > self::CAP) {
+ $this->currentMs = self::CAP;
+ }
+ }
+
+ /**
+ * Get the next backoff for logging, etc.
+ * @return int next backoff
+ */
+ public function nextBackoff(): int
+ {
+ return $this->currentMs;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Channel/Caller.php b/addons/epay/library/hyperf/utils/src/Channel/Caller.php
new file mode 100644
index 0000000..634135e
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Channel/Caller.php
@@ -0,0 +1,79 @@
+closure = $closure;
+ $this->waitTimeout = $waitTimeout;
+ $this->initInstance();
+ }
+
+ public function call(Closure $closure)
+ {
+ $release = true;
+ $channel = $this->channel;
+ try {
+ $instance = $channel->pop($this->waitTimeout);
+ if ($instance === false) {
+ if ($channel->isClosing()) {
+ throw new ChannelClosedException('The channel was closed.');
+ }
+
+ if ($channel->isTimeout()) {
+ throw new WaitTimeoutException('The instance pop from channel timeout.');
+ }
+ }
+
+ $result = $closure($instance);
+ } catch (ChannelClosedException|WaitTimeoutException $exception) {
+ $release = false;
+ throw $exception;
+ } finally {
+ $release && $channel->push($instance ?? null);
+ }
+
+ return $result;
+ }
+
+ public function initInstance(): void
+ {
+ if ($this->channel) {
+ $this->channel->close();
+ }
+
+ $this->channel = new Channel(1);
+ $this->channel->push($this->closure->__invoke());
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Channel/ChannelManager.php b/addons/epay/library/hyperf/utils/src/Channel/ChannelManager.php
new file mode 100644
index 0000000..db86cf4
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Channel/ChannelManager.php
@@ -0,0 +1,72 @@
+size = $size;
+ }
+
+ public function get(int $id, bool $initialize = false): ?Channel
+ {
+ if (isset($this->channels[$id])) {
+ return $this->channels[$id];
+ }
+
+ if ($initialize) {
+ return $this->channels[$id] = $this->make($this->size);
+ }
+
+ return null;
+ }
+
+ public function make(int $limit): Channel
+ {
+ return new Channel($limit);
+ }
+
+ public function close(int $id): void
+ {
+ if ($channel = $this->channels[$id] ?? null) {
+ $channel->close();
+ }
+
+ unset($this->channels[$id]);
+ }
+
+ public function getChannels(): array
+ {
+ return $this->channels;
+ }
+
+ public function flush(): void
+ {
+ $channels = $this->getChannels();
+ foreach ($channels as $id => $channel) {
+ $this->close($id);
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/ChannelPool.php b/addons/epay/library/hyperf/utils/src/ChannelPool.php
new file mode 100644
index 0000000..adc6f8e
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/ChannelPool.php
@@ -0,0 +1,38 @@
+isEmpty() ? new Channel(1) : $this->pop();
+ }
+
+ public function release(Channel $channel)
+ {
+ $channel->errCode = 0;
+ $this->push($channel);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/ClearStatCache.php b/addons/epay/library/hyperf/utils/src/ClearStatCache.php
new file mode 100644
index 0000000..ca97023
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/ClearStatCache.php
@@ -0,0 +1,62 @@
+ self::$interval
+ || self::$lastCleared
+ || (self::$lastCleared + self::$interval < $now)
+ ) {
+ self::forceClear($filename);
+ self::$lastCleared = $now;
+ }
+ }
+
+ public static function forceClear(?string $filename = null): void
+ {
+ if ($filename !== null) {
+ clearstatcache(true, $filename);
+ } else {
+ clearstatcache();
+ }
+ }
+
+ public static function getInterval(): int
+ {
+ return self::$interval;
+ }
+
+ public static function setInterval(int $interval)
+ {
+ self::$interval = $interval;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/CodeGen/Package.php b/addons/epay/library/hyperf/utils/src/CodeGen/Package.php
new file mode 100644
index 0000000..50d0ef4
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/CodeGen/Package.php
@@ -0,0 +1,26 @@
+ 'bool',
+ 'boolean' => 'bool',
+ 'string' => 'string',
+ 'int' => 'int',
+ 'integer' => 'int',
+ 'float' => 'float',
+ 'double' => 'float',
+ 'array' => 'array',
+ 'object' => 'object',
+ 'callable' => 'callable',
+ 'resource' => 'resource',
+ 'mixed' => 'mixed',
+ 'iterable' => 'iterable',
+ ];
+
+ /**
+ * @var null|PhpDocReader
+ */
+ protected static $instance;
+
+ /** @var UseStatementParser */
+ private $parser;
+
+ /** @var bool */
+ private $ignorePhpDocErrors;
+
+ /**
+ * @param bool $ignorePhpDocErrors enable or disable throwing errors when PhpDoc errors occur (when parsing annotations)
+ */
+ public function __construct(bool $ignorePhpDocErrors = false)
+ {
+ $this->parser = new UseStatementParser();
+ $this->ignorePhpDocErrors = $ignorePhpDocErrors;
+ }
+
+ public static function getInstance(): PhpDocReader
+ {
+ if (static::$instance) {
+ return static::$instance;
+ }
+ return static::$instance = new static();
+ }
+
+ /**
+ * Parse the docblock of the property to get the type (class or primitive type) of the param annotation.
+ *
+ * @throws AnnotationException
+ */
+ public function getReturnType(ReflectionMethod $method, bool $withoutNamespace = false): array
+ {
+ return $this->readReturnClass($method, true, $withoutNamespace);
+ }
+
+ /**
+ * Parse the docblock of the property to get the class of the param annotation.
+ *
+ * @throws AnnotationException
+ */
+ public function getReturnClass(ReflectionMethod $method, bool $withoutNamespace = false): array
+ {
+ return $this->readReturnClass($method, false, $withoutNamespace);
+ }
+
+ protected function readReturnClass(ReflectionMethod $method, bool $allowPrimitiveTypes, bool $withoutNamespace = false): array
+ {
+ // Use reflection
+ $returnType = $method->getReturnType();
+ if ($returnType instanceof \ReflectionNamedType) {
+ if (! $returnType->isBuiltin() || $allowPrimitiveTypes) {
+ return [($returnType->allowsNull() ? '?' : '') . $returnType->getName()];
+ }
+ }
+
+ $docComment = $method->getDocComment();
+ if (! $docComment) {
+ return ['mixed'];
+ }
+ if (preg_match('/@return\s+([^\s]+)\s+/', $docComment, $matches)) {
+ [, $type] = $matches;
+ } else {
+ return ['mixed'];
+ }
+
+ $result = [];
+ $class = $method->getDeclaringClass();
+ $types = explode('|', $type);
+ foreach ($types as $type) {
+ // Ignore primitive types
+ if (isset(self::PRIMITIVE_TYPES[$type])) {
+ if ($allowPrimitiveTypes) {
+ $result[] = self::PRIMITIVE_TYPES[$type];
+ }
+ continue;
+ }
+
+ // Ignore types containing special characters ([], <> ...)
+ if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
+ continue;
+ }
+
+ // If the class name is not fully qualified (i.e. doesn't start with a \)
+ if ($type[0] !== '\\' && ! $withoutNamespace) {
+ // Try to resolve the FQN using the class context
+ $resolvedType = $this->tryResolveFqn($type, $class, $method);
+
+ if (! $resolvedType && ! $this->ignorePhpDocErrors) {
+ throw new AnnotationException(sprintf(
+ 'The @return annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
+ . 'Did you maybe forget to add a "use" statement for this annotation?',
+ $method,
+ $class->name,
+ $method->name,
+ $type
+ ));
+ }
+
+ $type = $resolvedType;
+ }
+
+ if (! $this->ignorePhpDocErrors && ! $withoutNamespace && ! $this->classExists($type)) {
+ throw new AnnotationException(sprintf(
+ 'The @return annotation for parameter "%s" of %s::%s contains a non existent class "%s"',
+ $method,
+ $class->name,
+ $method->name,
+ $type
+ ));
+ }
+
+ // Remove the leading \ (FQN shouldn't contain it)
+ $result[] = is_string($type) ? ltrim($type, '\\') : null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Attempts to resolve the FQN of the provided $type based on the $class and $member context.
+ *
+ * @return null|string Fully qualified name of the type, or null if it could not be resolved
+ */
+ protected function tryResolveFqn(string $type, ReflectionClass $class, Reflector $member): ?string
+ {
+ $alias = ($pos = strpos($type, '\\')) === false ? $type : substr($type, 0, $pos);
+ $loweredAlias = strtolower($alias);
+
+ // Retrieve "use" statements
+ $uses = $this->parser->parseUseStatements($class);
+
+ if (isset($uses[$loweredAlias])) {
+ // Imported classes
+ if ($pos !== false) {
+ return $uses[$loweredAlias] . substr($type, $pos);
+ }
+ return $uses[$loweredAlias];
+ }
+
+ if ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
+ return $class->getNamespaceName() . '\\' . $type;
+ }
+
+ if (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
+ // Class namespace
+ return $uses['__NAMESPACE__'] . '\\' . $type;
+ }
+
+ if ($this->classExists($type)) {
+ // No namespace
+ return $type;
+ }
+
+ // If all fail, try resolving through related traits
+ return $this->tryResolveFqnInTraits($type, $class, $member);
+ }
+
+ /**
+ * Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
+ * through the traits that are used by the provided $class.
+ *
+ * @return null|string Fully qualified name of the type, or null if it could not be resolved
+ */
+ protected function tryResolveFqnInTraits(string $type, ReflectionClass $class, Reflector $member): ?string
+ {
+ /** @var ReflectionClass[] $traits */
+ $traits = [];
+
+ // Get traits for the class and its parents
+ while ($class) {
+ $traits = array_merge($traits, $class->getTraits());
+ $class = $class->getParentClass();
+ }
+
+ foreach ($traits as $trait) {
+ // Eliminate traits that don't have the property/method/parameter
+ if ($member instanceof ReflectionProperty && ! $trait->hasProperty($member->name)) {
+ continue;
+ }
+ if ($member instanceof ReflectionMethod && ! $trait->hasMethod($member->name)) {
+ continue;
+ }
+ if ($member instanceof ReflectionParameter && ! $trait->hasMethod($member->getDeclaringFunction()->name)) {
+ continue;
+ }
+
+ // Run the resolver again with the ReflectionClass instance for the trait
+ $resolvedType = $this->tryResolveFqn($type, $trait, $member);
+
+ if ($resolvedType) {
+ return $resolvedType;
+ }
+ }
+
+ return null;
+ }
+
+ protected function classExists(string $class): bool
+ {
+ return class_exists($class) || interface_exists($class);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/CodeGen/PhpDocReaderManager.php b/addons/epay/library/hyperf/utils/src/CodeGen/PhpDocReaderManager.php
new file mode 100644
index 0000000..3b6971f
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/CodeGen/PhpDocReaderManager.php
@@ -0,0 +1,30 @@
+parser = $parserFactory->create(ParserFactory::ONLY_PHP7);
+ }
+
+ public static function getInstance(): PhpParser
+ {
+ if (static::$instance) {
+ return static::$instance;
+ }
+ return static::$instance = new static();
+ }
+
+ /**
+ * @return null|Node\Stmt[]
+ */
+ public function getNodesFromReflectionClass(ReflectionClass $reflectionClass): ?array
+ {
+ $code = file_get_contents($reflectionClass->getFileName());
+ return $this->parser->parse($code);
+ }
+
+ public function getNodeFromReflectionParameter(ReflectionParameter $parameter): Node\Param
+ {
+ $result = new Node\Param(
+ new Node\Expr\Variable($parameter->getName())
+ );
+
+ if ($parameter->isDefaultValueAvailable()) {
+ $result->default = $this->getExprFromValue($parameter->getDefaultValue());
+ }
+
+ if ($parameter->hasType()) {
+ /** @var \ReflectionNamedType|\ReflectionUnionType $reflection */
+ $reflection = $parameter->getType();
+ if ($reflection instanceof \ReflectionUnionType) {
+ $unionType = [];
+ foreach ($reflection->getTypes() as $objType) {
+ $type = $objType->getName();
+ if (! in_array($type, static::TYPES)) {
+ $unionType[] = new Node\Name('\\' . $type);
+ } else {
+ $unionType[] = new Node\Identifier($type);
+ }
+ }
+ $result->type = new Node\UnionType($unionType);
+ } else {
+ $type = $reflection->getName();
+ if (! in_array($type, static::TYPES)) {
+ $result->type = new Node\Name('\\' . $type);
+ } else {
+ $result->type = new Node\Identifier($type);
+ }
+ }
+ }
+
+ if ($parameter->isPassedByReference()) {
+ $result->byRef = true;
+ }
+
+ if ($parameter->isVariadic()) {
+ $result->variadic = true;
+ }
+
+ return $result;
+ }
+
+ public function getExprFromValue($value): Node\Expr
+ {
+ switch (gettype($value)) {
+ case 'array':
+ return new Node\Expr\Array_($value);
+ case 'string':
+ return new Node\Scalar\String_($value);
+ case 'integer':
+ return new Node\Scalar\LNumber($value);
+ case 'double':
+ return new Node\Scalar\DNumber($value);
+ case 'NULL':
+ return new Node\Expr\ConstFetch(new Node\Name('null'));
+ case 'boolean':
+ return new Node\Expr\ConstFetch(new Node\Name($value ? 'true' : 'false'));
+ default:
+ throw new InvalidArgumentException($value . ' is invalid');
+ }
+ }
+
+ /**
+ * @return Node\Stmt\ClassMethod[]
+ */
+ public function getAllMethodsFromStmts(array $stmts): array
+ {
+ $methods = [];
+ foreach ($stmts as $namespace) {
+ if (! $namespace instanceof Node\Stmt\Namespace_) {
+ continue;
+ }
+
+ foreach ($namespace->stmts as $class) {
+ if (! $class instanceof Node\Stmt\Class_ && ! $class instanceof Node\Stmt\Interface_) {
+ continue;
+ }
+
+ foreach ($class->getMethods() as $method) {
+ $methods[] = $method;
+ }
+ }
+ }
+
+ return $methods;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/CodeGen/Project.php b/addons/epay/library/hyperf/utils/src/CodeGen/Project.php
new file mode 100644
index 0000000..4aa5937
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/CodeGen/Project.php
@@ -0,0 +1,69 @@
+getAutoloadRules() as $prefix => $prefixPath) {
+ if ($this->isRootNamespace($prefix) || strpos($path, $prefixPath) === 0) {
+ return $prefix . str_replace('/', '\\', substr($path, strlen($prefixPath)));
+ }
+ }
+ throw new \RuntimeException("Invalid project path: {$path}");
+ }
+
+ public function className(string $path): string
+ {
+ return $this->namespace($path);
+ }
+
+ public function path(string $name, $extension = '.php'): string
+ {
+ if (Str::endsWith($name, '\\')) {
+ $extension = '';
+ }
+
+ foreach ($this->getAutoloadRules() as $prefix => $prefixPath) {
+ if ($this->isRootNamespace($prefix) || strpos($name, $prefix) === 0) {
+ return $prefixPath . str_replace('\\', '/', substr($name, strlen($prefix))) . $extension;
+ }
+ }
+
+ throw new \RuntimeException("Invalid class name: {$name}");
+ }
+
+ protected function isRootNamespace(string $namespace): bool
+ {
+ return $namespace === '';
+ }
+
+ protected function getAutoloadRules(): array
+ {
+ return data_get(Composer::getJsonContent(), 'autoload.psr-4', []);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Codec/Base62.php b/addons/epay/library/hyperf/utils/src/Codec/Base62.php
new file mode 100644
index 0000000..8007238
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Codec/Base62.php
@@ -0,0 +1,39 @@
+ 0) {
+ $remain = $number % self::BASE;
+ $chars[] = self::CHARS[$remain];
+ $number = ($number - $remain) / self::BASE;
+ }
+ return implode('', array_reverse($chars));
+ }
+
+ public static function decode(string $data): int
+ {
+ return array_reduce(array_map(function ($character) {
+ return strpos(self::CHARS, $character);
+ }, str_split($data)), function ($result, $remain) {
+ return $result * self::BASE + $remain;
+ });
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Codec/Json.php b/addons/epay/library/hyperf/utils/src/Codec/Json.php
new file mode 100644
index 0000000..0e3d8d5
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Codec/Json.php
@@ -0,0 +1,56 @@
+toArray();
+ }
+
+ try {
+ $json = json_encode($data, $flags | JSON_THROW_ON_ERROR, $depth);
+ } catch (\Throwable $exception) {
+ throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
+ }
+
+ return $json;
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ public static function decode(string $json, bool $assoc = true, int $depth = 512, int $flags = 0)
+ {
+ try {
+ $decode = json_decode($json, $assoc, $depth, $flags | JSON_THROW_ON_ERROR);
+ } catch (\Throwable $exception) {
+ throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
+ }
+
+ return $decode;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Codec/Xml.php b/addons/epay/library/hyperf/utils/src/Codec/Xml.php
new file mode 100644
index 0000000..4a26fbf
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Codec/Xml.php
@@ -0,0 +1,70 @@
+toArray();
+ } else {
+ $data = (array) $data;
+ }
+ if ($parentNode === null) {
+ $xml = new SimpleXMLElement('' . "<{$root}>{$root}>");
+ } else {
+ $xml = $parentNode;
+ }
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ self::toXml($value, $xml->addChild($key));
+ } else {
+ if (is_numeric($key)) {
+ $xml->addChild('item' . $key, (string) $value);
+ } else {
+ $xml->addChild($key, (string) $value);
+ }
+ }
+ }
+ return trim($xml->asXML());
+ }
+
+ public static function toArray($xml)
+ {
+ // For PHP 8.0, libxml_disable_entity_loader() has been deprecated.
+ // As libxml 2.9.0 is now required, external entity loading is guaranteed to be disabled by default.
+ // And this function is no longer needed to protect against XXE attacks, unless the (still vulnerable). LIBXML_NOENT is used.
+ // In that case, it is recommended to refactor the code using libxml_set_external_entity_loader() to suppress loading of external entities.
+ if (\PHP_VERSION_ID < 80000) {
+ $disableLibxmlEntityLoader = libxml_disable_entity_loader(true);
+ $respObject = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOERROR);
+ libxml_disable_entity_loader($disableLibxmlEntityLoader);
+ } else {
+ $respObject = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOERROR);
+ }
+
+ if ($respObject === false) {
+ throw new InvalidArgumentException('Syntax error.');
+ }
+
+ return json_decode(json_encode($respObject), true);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Collection.php b/addons/epay/library/hyperf/utils/src/Collection.php
new file mode 100644
index 0000000..e696e11
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Collection.php
@@ -0,0 +1,1835 @@
+
+ * @implements Arrayable
+ * @implements IteratorAggregate
+ *
+ * @property HigherOrderCollectionProxy $average
+ * @property HigherOrderCollectionProxy $avg
+ * @property HigherOrderCollectionProxy $contains
+ * @property HigherOrderCollectionProxy $each
+ * @property HigherOrderCollectionProxy $every
+ * @property HigherOrderCollectionProxy $filter
+ * @property HigherOrderCollectionProxy $first
+ * @property HigherOrderCollectionProxy $flatMap
+ * @property HigherOrderCollectionProxy $groupBy
+ * @property HigherOrderCollectionProxy $keyBy
+ * @property HigherOrderCollectionProxy $map
+ * @property HigherOrderCollectionProxy $max
+ * @property HigherOrderCollectionProxy $min
+ * @property HigherOrderCollectionProxy $partition
+ * @property HigherOrderCollectionProxy $reject
+ * @property HigherOrderCollectionProxy $sortBy
+ * @property HigherOrderCollectionProxy $sortByDesc
+ * @property HigherOrderCollectionProxy $sum
+ * @property HigherOrderCollectionProxy $unique
+ */
+class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
+{
+ use Macroable;
+
+ /**
+ * The items contained in the collection.
+ *
+ * @var array
+ */
+ protected $items = [];
+
+ /**
+ * The methods that can be proxied.
+ *
+ * @var string[]
+ */
+ protected static $proxies
+ = [
+ 'average',
+ 'avg',
+ 'contains',
+ 'each',
+ 'every',
+ 'filter',
+ 'first',
+ 'flatMap',
+ 'groupBy',
+ 'keyBy',
+ 'map',
+ 'max',
+ 'min',
+ 'partition',
+ 'reject',
+ 'sortBy',
+ 'sortByDesc',
+ 'sum',
+ 'unique',
+ ];
+
+ /**
+ * Create a new collection.
+ * @param null|iterable|Jsonable|JsonSerializable $items
+ */
+ public function __construct($items = [])
+ {
+ $this->items = $this->getArrayableItems($items);
+ }
+
+ /**
+ * Convert the collection to its string representation.
+ */
+ public function __toString(): string
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * Dynamically access collection proxies.
+ *
+ * @throws \Exception
+ */
+ public function __get(string $key)
+ {
+ if (! in_array($key, static::$proxies)) {
+ throw new Exception("Property [{$key}] does not exist on this collection instance.");
+ }
+ return new HigherOrderCollectionProxy($this, $key);
+ }
+
+ /**
+ * @param null|iterable|Jsonable|JsonSerializable $items
+ * @return static
+ */
+ public function fill($items = [])
+ {
+ $this->items = $this->getArrayableItems($items);
+ return $this;
+ }
+
+ /**
+ * Create a new collection instance if the value isn't one already.
+ *
+ * @template TMakeKey of array-key
+ * @template TMakeValue
+ *
+ * @param null|Arrayable|iterable|Jsonable|JsonSerializable $items
+ * @return static
+ */
+ public static function make($items = []): self
+ {
+ return new static($items);
+ }
+
+ /**
+ * Wrap the given value in a collection if applicable.
+ *
+ * @template TWrapKey of array-key
+ * @template TWrapValue
+ *
+ * @param iterable $value
+ * @return static
+ */
+ public static function wrap($value): self
+ {
+ return $value instanceof self ? new static($value) : new static(Arr::wrap($value));
+ }
+
+ /**
+ * Get the underlying items from the given collection if applicable.
+ *
+ * @template TUnwrapKey of array-key
+ * @template TUnwrapValue
+ *
+ * @param array|static $value
+ * @return array
+ */
+ public static function unwrap($value): array
+ {
+ return $value instanceof self ? $value->all() : $value;
+ }
+
+ /**
+ * Create a new collection by invoking the callback a given amount of times.
+ *
+ * @template TTimesValue
+ *
+ * @param (callable(int): TTimesValue)|null $callback
+ * @return static
+ */
+ public static function times(int $number, callable $callback = null): self
+ {
+ if ($number < 1) {
+ return new static();
+ }
+ if (is_null($callback)) {
+ return new static(range(1, $number));
+ }
+ return (new static(range(1, $number)))->map($callback);
+ }
+
+ /**
+ * Get all of the items in the collection.
+ *
+ * @return array
+ */
+ public function all(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * Get the average value of a given key.
+ *
+ * @param (callable(TValue): float|int)|string|null $callback
+ */
+ public function avg($callback = null)
+ {
+ $callback = $this->valueRetriever($callback);
+ $items = $this->map(function ($value) use ($callback) {
+ return $callback($value);
+ })->filter(function ($value) {
+ return ! is_null($value);
+ });
+ if ($count = $items->count()) {
+ return $items->sum() / $count;
+ }
+ }
+
+ /**
+ * Alias for the "avg" method.
+ *
+ * @param (callable(TValue): float|int)|string|null $callback
+ * @return null|float|int
+ */
+ public function average($callback = null)
+ {
+ return $this->avg($callback);
+ }
+
+ /**
+ * Get the median of a given key.
+ *
+ * @param null|array|string $key
+ */
+ public function median($key = null)
+ {
+ $values = (isset($key) ? $this->pluck($key) : $this)->filter(function ($item) {
+ return ! is_null($item);
+ })->sort()->values();
+ $count = $values->count();
+ if ($count == 0) {
+ return;
+ }
+ $middle = (int) ($count / 2);
+ if ($count % 2) {
+ return $values->get($middle);
+ }
+ return (new static([
+ $values->get($middle - 1),
+ $values->get($middle),
+ ]))->average();
+ }
+
+ /**
+ * Get the mode of a given key.
+ *
+ * @param null|array|string $key
+ * @return null|array
+ */
+ public function mode($key = null)
+ {
+ if ($this->count() == 0) {
+ return null;
+ }
+ $collection = isset($key) ? $this->pluck($key) : $this;
+
+ /**
+ * @template TValue of array-key
+ * @var static $counts
+ */
+ $counts = new self();
+ $collection->each(function ($value) use ($counts) {
+ $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1;
+ });
+ $sorted = $counts->sort();
+ $highestValue = $sorted->last();
+ return $sorted->filter(function ($value) use ($highestValue) {
+ return $value == $highestValue;
+ })->sort()->keys()->all();
+ }
+
+ /**
+ * Collapse the collection of items into a single array.
+ *
+ * @return static
+ */
+ public function collapse(): self
+ {
+ return new static(Arr::collapse($this->items));
+ }
+
+ /**
+ * Determine if an item exists in the collection.
+ *
+ * @param null|mixed $operator
+ * @param null|mixed $value
+ * @param (callable(TValue): bool)|TValue|string $key
+ */
+ public function contains($key, $operator = null, $value = null): bool
+ {
+ if (func_num_args() === 1) {
+ if ($this->useAsCallable($key)) {
+ $placeholder = new stdClass();
+ return $this->first($key, $placeholder) !== $placeholder;
+ }
+ return in_array($key, $this->items);
+ }
+ return $this->contains($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Determine if an item exists in the collection using strict comparison.
+ *
+ * @param null|TValue $value
+ * @param callable|TKey|TValue $key
+ */
+ public function containsStrict($key, $value = null): bool
+ {
+ if (func_num_args() === 2) {
+ return $this->contains(function ($item) use ($key, $value) {
+ return data_get($item, $key) === $value;
+ });
+ }
+ if ($this->useAsCallable($key)) {
+ return ! is_null($this->first($key));
+ }
+ return in_array($key, $this->items, true);
+ }
+
+ /**
+ * Cross join with the given lists, returning all possible permutations.
+ */
+ public function crossJoin(...$lists): self
+ {
+ return new static(Arr::crossJoin($this->items, ...array_map([$this, 'getArrayableItems'], $lists)));
+ }
+
+ /**
+ * Dump the collection and end the script.
+ */
+ public function dd(...$args): void
+ {
+ call_user_func_array([$this, 'dump'], $args);
+ exit(1);
+ }
+
+ /**
+ * Dump the collection.
+ */
+ public function dump(): self
+ {
+ $params = (new static(func_get_args()));
+ $params->push($this)->each(function ($item) {
+ if (! class_exists(VarDumper::class)) {
+ throw new \RuntimeException('symfony/var-dumper package required, please require the package via "composer require symfony/var-dumper"');
+ }
+ VarDumper::dump($item);
+ });
+ return $this;
+ }
+
+ /**
+ * Get the items in the collection that are not present in the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @return static
+ */
+ public function diff($items): self
+ {
+ return new static(array_diff($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Get the items in the collection that are not present in the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @param callable(TValue): int $callback
+ * @return static
+ */
+ public function diffUsing($items, callable $callback): self
+ {
+ return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback));
+ }
+
+ /**
+ * Get the items in the collection whose keys and values are not present in the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @return static
+ */
+ public function diffAssoc($items): self
+ {
+ return new static(array_diff_assoc($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Get the items in the collection whose keys and values are not present in the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @param callable(TKey): int $callback
+ * @return static
+ */
+ public function diffAssocUsing($items, callable $callback): self
+ {
+ return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback));
+ }
+
+ /**
+ * Get the items in the collection whose keys are not present in the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @return static
+ */
+ public function diffKeys($items): self
+ {
+ return new static(array_diff_key($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Get the items in the collection whose keys are not present in the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @param callable(TKey): int $callback
+ * @return static
+ */
+ public function diffKeysUsing($items, callable $callback): self
+ {
+ return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback));
+ }
+
+ /**
+ * Execute a callback over each item.
+ * @param callable(TValue,TKey): mixed $callback
+ */
+ public function each(callable $callback): self
+ {
+ foreach ($this->items as $key => $item) {
+ if ($callback($item, $key) === false) {
+ break;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Execute a callback over each nested chunk of items.
+ * @param callable(...mixed): mixed $callback
+ * @return static
+ */
+ public function eachSpread(callable $callback): self
+ {
+ return $this->each(function ($chunk, $key) use ($callback) {
+ $chunk[] = $key;
+ return $callback(...$chunk);
+ });
+ }
+
+ /**
+ * Determine if all items in the collection pass the given test.
+ *
+ * @param (callable(TValue, TKey): bool)|TValue|string $key
+ * @param mixed $operator
+ * @param mixed $value
+ */
+ public function every($key, $operator = null, $value = null): bool
+ {
+ if (func_num_args() === 1) {
+ $callback = $this->valueRetriever($key);
+ foreach ($this->items as $k => $v) {
+ if (! $callback($v, $k)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return $this->every($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Get all items except for those with the specified keys.
+ *
+ * @param array|static $keys
+ * @return static
+ */
+ public function except($keys): self
+ {
+ if ($keys instanceof self) {
+ $keys = $keys->all();
+ } elseif (! is_array($keys)) {
+ $keys = func_get_args();
+ }
+ return new static(Arr::except($this->items, $keys));
+ }
+
+ /**
+ * Run a filter over each of the items.
+ *
+ * @param callable(TValue, TKey): bool|null $callback
+ * @return static
+ */
+ public function filter(callable $callback = null): self
+ {
+ if ($callback) {
+ return new static(Arr::where($this->items, $callback));
+ }
+ return new static(array_filter($this->items));
+ }
+
+ /**
+ * Apply the callback if the value is truthy.
+ *
+ * @param callable($this): $this $callback
+ * @param callable($this): $this $default
+ * @return $this
+ */
+ public function when(bool $value, callable $callback, callable $default = null): self
+ {
+ if ($value) {
+ return $callback($this, $value);
+ }
+ if ($default) {
+ return $default($this, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Apply the callback if the value is falsy.
+ *
+ * @param callable($this): $this $callback
+ * @param callable($this): $this|null $default
+ * @return $this
+ */
+ public function unless(bool $value, callable $callback, callable $default = null): self
+ {
+ return $this->when(! $value, $callback, $default);
+ }
+
+ /**
+ * Filter items by the given key value pair.
+ *
+ * @param mixed $operator
+ * @param mixed $value
+ * @return static
+ */
+ public function where(string $key, $operator = null, $value = null): self
+ {
+ return $this->filter($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Filter items by the given key value pair using strict comparison.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function whereStrict(string $key, $value): self
+ {
+ return $this->where($key, '===', $value);
+ }
+
+ /**
+ * Filter items by the given key value pair.
+ *
+ * @param Arrayable|iterable $values
+ * @return static
+ */
+ public function whereIn(string $key, $values, bool $strict = false): self
+ {
+ $values = $this->getArrayableItems($values);
+ return $this->filter(function ($item) use ($key, $values, $strict) {
+ return in_array(data_get($item, $key), $values, $strict);
+ });
+ }
+
+ /**
+ * Filter items by the given key value pair using strict comparison.
+ *
+ * @param Arrayable|iterable $values
+ * @return static
+ */
+ public function whereInStrict(string $key, $values): self
+ {
+ return $this->whereIn($key, $values, true);
+ }
+
+ /**
+ * Filter items by the given key value pair.
+ *
+ * @param Arrayable|iterable $values
+ * @return static
+ */
+ public function whereNotIn(string $key, $values, bool $strict = false): self
+ {
+ $values = $this->getArrayableItems($values);
+ return $this->reject(function ($item) use ($key, $values, $strict) {
+ return in_array(data_get($item, $key), $values, $strict);
+ });
+ }
+
+ /**
+ * Filter items by the given key value pair using strict comparison.
+ *
+ * @param Arrayable|iterable $values
+ * @return static
+ */
+ public function whereNotInStrict(string $key, $values): self
+ {
+ return $this->whereNotIn($key, $values, true);
+ }
+
+ /**
+ * Filter the items, removing any items that don't match the given type.
+ *
+ * @param class-string $type
+ * @return static
+ */
+ public function whereInstanceOf(string $type): self
+ {
+ return $this->filter(function ($value) use ($type) {
+ return $value instanceof $type;
+ });
+ }
+
+ /**
+ * Get the first item from the collection.
+ *
+ * @template TFirstDefault
+ *
+ * @param callable(TValue, TKey): bool|null $callback
+ * @param TFirstDefault|callable(): TFirstDefault $default
+ * @return TFirstDefault|TValue
+ */
+ public function first(callable $callback = null, $default = null)
+ {
+ return Arr::first($this->items, $callback, $default);
+ }
+
+ /**
+ * Get the first item by the given key value pair.
+ *
+ * @param mixed $operator
+ * @param mixed $value
+ * @return null|TValue
+ */
+ public function firstWhere(string $key, $operator, $value = null)
+ {
+ return $this->first($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Get a flattened array of the items in the collection.
+ *
+ * @param float|int $depth
+ * @return static
+ */
+ public function flatten($depth = INF): self
+ {
+ return new static(Arr::flatten($this->items, $depth));
+ }
+
+ /**
+ * Flip the items in the collection.
+ *
+ * @return static
+ */
+ public function flip(): self
+ {
+ return new static(array_flip($this->items));
+ }
+
+ /**
+ * Remove an item from the collection by key.
+ *
+ * @param array|TKey $keys
+ * @return $this
+ */
+ public function forget($keys): self
+ {
+ foreach ((array) $keys as $key) {
+ $this->offsetUnset($key);
+ }
+ return $this;
+ }
+
+ /**
+ * Get an item from the collection by key.
+ *
+ * @template TGetDefault
+ *
+ * @param TKey $key
+ * @param TGetDefault|(\Closure(): TGetDefault) $default
+ * @return TGetDefault|TValue
+ */
+ public function get($key, $default = null)
+ {
+ if ($this->offsetExists($key)) {
+ return $this->items[$key];
+ }
+ return value($default);
+ }
+
+ /**
+ * Group an associative array by a field or using a callback.
+ * @param mixed $groupBy
+ */
+ public function groupBy($groupBy, bool $preserveKeys = false): self
+ {
+ if (is_array($groupBy)) {
+ $nextGroups = $groupBy;
+ $groupBy = array_shift($nextGroups);
+ }
+ $groupBy = $this->valueRetriever($groupBy);
+ $results = [];
+ foreach ($this->items as $key => $value) {
+ $groupKeys = $groupBy($value, $key);
+ if (! is_array($groupKeys)) {
+ $groupKeys = [$groupKeys];
+ }
+ foreach ($groupKeys as $groupKey) {
+ $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey;
+ if (! array_key_exists($groupKey, $results)) {
+ $results[$groupKey] = new static();
+ }
+ $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value);
+ }
+ }
+ $result = new static($results);
+ if (! empty($nextGroups)) {
+ return $result->map->groupBy($nextGroups, $preserveKeys);
+ }
+ return $result;
+ }
+
+ /**
+ * Key an associative array by a field or using a callback.
+ *
+ * @param (callable(TValue, TKey): array-key)|array|string $keyBy
+ * @return static>
+ */
+ public function keyBy($keyBy): self
+ {
+ $keyBy = $this->valueRetriever($keyBy);
+ $results = [];
+ foreach ($this->items as $key => $item) {
+ $resolvedKey = $keyBy($item, $key);
+ if (is_object($resolvedKey)) {
+ $resolvedKey = (string) $resolvedKey;
+ }
+ $results[$resolvedKey] = $item;
+ }
+ return new static($results);
+ }
+
+ /**
+ * Determine if an item exists in the collection by key.
+ * @param array|TKey $key
+ */
+ public function has($key): bool
+ {
+ $keys = is_array($key) ? $key : func_get_args();
+ foreach ($keys as $value) {
+ if (! $this->offsetExists($value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Concatenate values of a given key as a string.
+ */
+ public function implode(string $value, string $glue = null): string
+ {
+ $first = $this->first();
+ if (is_array($first) || is_object($first)) {
+ return implode($glue, $this->pluck($value)->all());
+ }
+ return implode($value, $this->items);
+ }
+
+ /**
+ * Intersect the collection with the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @return static
+ */
+ public function intersect($items): self
+ {
+ return new static(array_intersect($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Intersect the collection with the given items by key.
+ * @param Arrayable|iterable $items
+ * @return static
+ */
+ public function intersectByKeys($items): self
+ {
+ return new static(array_intersect_key($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Determine if the collection is empty or not.
+ */
+ public function isEmpty(): bool
+ {
+ return empty($this->items);
+ }
+
+ /**
+ * Determine if the collection is not empty.
+ */
+ public function isNotEmpty(): bool
+ {
+ return ! $this->isEmpty();
+ }
+
+ /**
+ * Get the keys of the collection items.
+ * @return static
+ */
+ public function keys(): self
+ {
+ return new static(array_keys($this->items));
+ }
+
+ /**
+ * Get the last item from the collection.
+ *
+ * @template TLastDefault
+ *
+ * @param (callable(TValue, TKey): bool)|null $callback
+ * @param TLastDefault|(\Closure(): TLastDefault) $default
+ * @return TLastDefault|TValue
+ */
+ public function last(callable $callback = null, $default = null)
+ {
+ return Arr::last($this->items, $callback, $default);
+ }
+
+ /**
+ * Get the values of a given key.
+ *
+ * @param array|string $value
+ * @return static
+ */
+ public function pluck($value, ?string $key = null): self
+ {
+ return new static(Arr::pluck($this->items, $value, $key));
+ }
+
+ /**
+ * Run a map over each of the items.
+ *
+ * @template TMapValue
+ *
+ * @param callable(TValue, TKey): TMapValue $callback
+ * @return static
+ */
+ public function map(callable $callback): self
+ {
+ $keys = array_keys($this->items);
+ $items = array_map($callback, $this->items, $keys);
+ return new static(array_combine($keys, $items));
+ }
+
+ /**
+ * Run a map over each nested chunk of items.
+ *
+ * @template TMapSpreadValue
+ *
+ * @param callable(mixed): TMapSpreadValue $callback
+ * @return static
+ */
+ public function mapSpread(callable $callback): self
+ {
+ return $this->map(function ($chunk, $key) use ($callback) {
+ $chunk[] = $key;
+ return $callback(...$chunk);
+ });
+ }
+
+ /**
+ * Run a dictionary map over the items.
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @template TMapToDictionaryKey of array-key
+ * @template TMapToDictionaryValue
+ *
+ * @param callable(TValue, TKey): array $callback
+ * @return static>
+ */
+ public function mapToDictionary(callable $callback): self
+ {
+ $dictionary = [];
+ foreach ($this->items as $key => $item) {
+ $pair = $callback($item, $key);
+ $key = key($pair);
+ $value = reset($pair);
+ if (! isset($dictionary[$key])) {
+ $dictionary[$key] = [];
+ }
+ $dictionary[$key][] = $value;
+ }
+ return new static($dictionary);
+ }
+
+ /**
+ * Run a grouping map over the items.
+ * The callback should return an associative array with a single key/value pair.
+ */
+ public function mapToGroups(callable $callback): self
+ {
+ $groups = $this->mapToDictionary($callback);
+ return $groups->map([$this, 'make']);
+ }
+
+ /**
+ * Run an associative map over each of the items.
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @template TMapWithKeysKey of array-key
+ * @template TMapWithKeysValue
+ *
+ * @param callable(TValue, TKey): array $callback
+ * @return static
+ */
+ public function mapWithKeys(callable $callback): self
+ {
+ $result = [];
+ foreach ($this->items as $key => $value) {
+ $assoc = $callback($value, $key);
+ foreach ($assoc as $mapKey => $mapValue) {
+ $result[$mapKey] = $mapValue;
+ }
+ }
+ return new static($result);
+ }
+
+ /**
+ * Map a collection and flatten the result by a single level.
+ *
+ * @param callable(TValue, TKey): mixed $callback
+ * @return static
+ */
+ public function flatMap(callable $callback): self
+ {
+ return $this->map($callback)->collapse();
+ }
+
+ /**
+ * Map the values into a new class.
+ *
+ * @param class-string $class
+ * @return static
+ */
+ public function mapInto(string $class): self
+ {
+ return $this->map(function ($value, $key) use ($class) {
+ return new $class($value, $key);
+ });
+ }
+
+ /**
+ * Get the max value of a given key.
+ *
+ * @param (callable(TValue):mixed)|string|null $callback
+ * @return TValue
+ */
+ public function max($callback = null)
+ {
+ $callback = $this->valueRetriever($callback);
+ return $this->filter(function ($value) {
+ return ! is_null($value);
+ })->reduce(function ($result, $item) use ($callback) {
+ $value = $callback($item);
+ return is_null($result) || $value > $result ? $value : $result;
+ });
+ }
+
+ /**
+ * Merge the collection with the given items.
+ * @param Arrayable|iterable $items
+ * @return static
+ */
+ public function merge($items): self
+ {
+ return new static(array_merge($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Create a collection by using this collection for keys and another for its values.
+ *
+ * @template TCombineValue
+ *
+ * @param Arrayable|iterable $values
+ * @return static
+ */
+ public function combine($values): self
+ {
+ return new static(array_combine($this->all(), $this->getArrayableItems($values)));
+ }
+
+ /**
+ * Union the collection with the given items.
+ *
+ * @param Arrayable|iterable $items
+ * @return static
+ */
+ public function union($items): self
+ {
+ return new static($this->items + $this->getArrayableItems($items));
+ }
+
+ /**
+ * Get the min value of a given key.
+ *
+ * @param (callable(TValue):mixed)|string|null $callback
+ * @return TValue
+ */
+ public function min($callback = null)
+ {
+ $callback = $this->valueRetriever($callback);
+ return $this->map(function ($value) use ($callback) {
+ return $callback($value);
+ })->filter(function ($value) {
+ return ! is_null($value);
+ })->reduce(function ($result, $value) {
+ return is_null($result) || $value < $result ? $value : $result;
+ });
+ }
+
+ /**
+ * Create a new collection consisting of every n-th element.
+ *
+ * @return static
+ */
+ public function nth(int $step, int $offset = 0): self
+ {
+ $new = [];
+ $position = 0;
+ foreach ($this->items as $item) {
+ if ($position % $step === $offset) {
+ $new[] = $item;
+ }
+ ++$position;
+ }
+ return new static($new);
+ }
+
+ /**
+ * Get the items with the specified keys.
+ *
+ * @param null|array|static|string $keys
+ * @return static
+ */
+ public function only($keys): self
+ {
+ if (is_null($keys)) {
+ return new static($this->items);
+ }
+ if ($keys instanceof self) {
+ $keys = $keys->all();
+ }
+ $keys = is_array($keys) ? $keys : func_get_args();
+ return new static(Arr::only($this->items, $keys));
+ }
+
+ /**
+ * "Paginate" the collection by slicing it into a smaller collection.
+ */
+ public function forPage(int $page, int $perPage): self
+ {
+ $offset = max(0, ($page - 1) * $perPage);
+ return $this->slice($offset, $perPage);
+ }
+
+ /**
+ * Partition the collection into two arrays using the given callback or key.
+ *
+ * @param callable(TValue, TKey) bool)|TValue|string $key
+ * @param null|string|TValue $operator
+ * @param null|TValue $value
+ * @return static>
+ */
+ public function partition($key, $operator = null, $value = null): self
+ {
+ $partitions = [new static(), new static()];
+ $callback = func_num_args() === 1 ? $this->valueRetriever($key) : $this->operatorForWhere(...func_get_args());
+ foreach ($this->items as $key => $item) {
+ $partitions[(int) ! $callback($item, $key)][$key] = $item;
+ }
+ return new static($partitions);
+ }
+
+ /**
+ * Pass the collection to the given callback and return the result.
+ *
+ * @template TPipeReturnType
+ *
+ * @param callable($this): TPipeReturnType $callback
+ * @return TPipeReturnType
+ */
+ public function pipe(callable $callback)
+ {
+ return $callback($this);
+ }
+
+ /**
+ * Get and remove the last item from the collection.
+ */
+ public function pop()
+ {
+ return array_pop($this->items);
+ }
+
+ /**
+ * Push an item onto the beginning of the collection.
+ *
+ * @param TValue $value
+ * @param null|TKey $key
+ * @return $this
+ */
+ public function prepend($value, $key = null): self
+ {
+ $this->items = Arr::prepend($this->items, $value, $key);
+ return $this;
+ }
+
+ /**
+ * Push an item onto the end of the collection.
+ *
+ * @param TValue $value
+ * @return $this
+ */
+ public function push($value): self
+ {
+ $this->offsetSet(null, $value);
+ return $this;
+ }
+
+ /**
+ * Push all of the given items onto the collection.
+ *
+ * @param iterable $source
+ * @return static
+ */
+ public function concat($source): self
+ {
+ $result = new static($this);
+ foreach ($source as $item) {
+ $result->push($item);
+ }
+ return $result;
+ }
+
+ /**
+ * Get and remove an item from the collection.
+ *
+ * @template TPullDefault
+ *
+ * @param TKey $key
+ * @param TPullDefault|(\Closure(): TPullDefault) $default
+ * @return TPullDefault|TValue
+ */
+ public function pull($key, $default = null)
+ {
+ return Arr::pull($this->items, $key, $default);
+ }
+
+ /**
+ * Put an item in the collection by key.
+ *
+ * @param TKey $key
+ * @param TValue $value
+ * @return $this
+ */
+ public function put($key, $value): self
+ {
+ $this->offsetSet($key, $value);
+ return $this;
+ }
+
+ /**
+ * Get one or a specified number of items randomly from the collection.
+ *
+ * @throws \InvalidArgumentException
+ * @return static|TValue
+ */
+ public function random(int $number = null)
+ {
+ if (is_null($number)) {
+ return Arr::random($this->items);
+ }
+ return new static(Arr::random($this->items, $number));
+ }
+
+ /**
+ * Reduce the collection to a single value.
+ *
+ * @template TReduceInitial
+ * @template TReduceReturnType
+ *
+ * @param callable(TReduceInitial|TReduceReturnType, TValue): TReduceReturnType $callback
+ * @param TReduceInitial $initial
+ * @return TReduceInitial|TReduceReturnType
+ */
+ public function reduce(callable $callback, $initial = null)
+ {
+ return array_reduce($this->items, $callback, $initial);
+ }
+
+ /**
+ * Create a collection of all elements that do not pass a given truth test.
+ *
+ * @param callable(TValue, TKey): bool|bool $callback
+ * @return static
+ */
+ public function reject($callback): self
+ {
+ if ($this->useAsCallable($callback)) {
+ return $this->filter(function ($value, $key) use ($callback) {
+ return ! $callback($value, $key);
+ });
+ }
+ return $this->filter(function ($item) use ($callback) {
+ return $item != $callback;
+ });
+ }
+
+ /**
+ * Reverse items order.
+ *
+ * @return static
+ */
+ public function reverse(): self
+ {
+ return new static(array_reverse($this->items, true));
+ }
+
+ /**
+ * Search the collection for a given value and return the corresponding key if successful.
+ *
+ * @param TValue|(callable(TValue,TKey): bool) $value
+ * @return bool|TKey
+ */
+ public function search($value, bool $strict = false)
+ {
+ if (! $this->useAsCallable($value)) {
+ return array_search($value, $this->items, $strict);
+ }
+ foreach ($this->items as $key => $item) {
+ if (call_user_func($value, $item, $key)) {
+ return $key;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get and remove the first item from the collection.
+ *
+ * @return null|TValue
+ */
+ public function shift()
+ {
+ return array_shift($this->items);
+ }
+
+ /**
+ * Shuffle the items in the collection.
+ *
+ * @return static
+ */
+ public function shuffle(int $seed = null): self
+ {
+ return new static(Arr::shuffle($this->items, $seed));
+ }
+
+ /**
+ * Slice the underlying collection array.
+ *
+ * @return static
+ */
+ public function slice(int $offset, int $length = null): self
+ {
+ return new static(array_slice($this->items, $offset, $length, true));
+ }
+
+ /**
+ * Split a collection into a certain number of groups.
+ *
+ * @return static>
+ */
+ public function split(int $numberOfGroups): self
+ {
+ if ($this->isEmpty()) {
+ return new static();
+ }
+ $groups = new static();
+ $groupSize = (int) floor($this->count() / $numberOfGroups);
+ $remain = $this->count() % $numberOfGroups;
+ $start = 0;
+ for ($i = 0; $i < $numberOfGroups; ++$i) {
+ $size = $groupSize;
+ if ($i < $remain) {
+ ++$size;
+ }
+ if ($size) {
+ $groups->push(new static(array_slice($this->items, $start, $size)));
+ $start += $size;
+ }
+ }
+ return $groups;
+ }
+
+ /**
+ * Chunk the underlying collection array.
+ *
+ * @return static>
+ */
+ public function chunk(int $size): self
+ {
+ if ($size <= 0) {
+ return new static();
+ }
+ $chunks = [];
+ foreach (array_chunk($this->items, $size, true) as $chunk) {
+ $chunks[] = new static($chunk);
+ }
+ return new static($chunks);
+ }
+
+ /**
+ * Sort through each item with a callback.
+ *
+ * @param callable(TValue, TValue): int $callback
+ * @return static
+ */
+ public function sort(callable $callback = null): self
+ {
+ $items = $this->items;
+ $callback ? uasort($items, $callback) : asort($items);
+ return new static($items);
+ }
+
+ /**
+ * Sort the collection using the given callback.
+ *
+ * @param (callable(TValue, TKey): mixed)|string|array $callback
+ * @return static
+ */
+ public function sortBy($callback, int $options = SORT_REGULAR, bool $descending = false): self
+ {
+ if (is_array($callback) && ! is_callable($callback)) {
+ return $this->sortByMany($callback);
+ }
+
+ $results = [];
+ $callback = $this->valueRetriever($callback);
+ // First we will loop through the items and get the comparator from a callback
+ // function which we were given. Then, we will sort the returned values and
+ // and grab the corresponding values for the sorted keys from this array.
+ foreach ($this->items as $key => $value) {
+ $results[$key] = $callback($value, $key);
+ }
+ $descending ? arsort($results, $options) : asort($results, $options);
+ // Once we have sorted all of the keys in the array, we will loop through them
+ // and grab the corresponding model so we can set the underlying items list
+ // to the sorted version. Then we'll just return the collection instance.
+ foreach (array_keys($results) as $key) {
+ $results[$key] = $this->items[$key];
+ }
+ return new static($results);
+ }
+
+ /**
+ * Sort the collection in descending order using the given callback.
+ *
+ * @param (callable(TValue, TKey): mixed)|string $callback
+ * @return static
+ */
+ public function sortByDesc($callback, int $options = SORT_REGULAR): self
+ {
+ return $this->sortBy($callback, $options, true);
+ }
+
+ /**
+ * Sort the collection keys.
+ *
+ * @return static
+ */
+ public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): self
+ {
+ $items = $this->items;
+ $descending ? krsort($items, $options) : ksort($items, $options);
+ return new static($items);
+ }
+
+ /**
+ * Sort the collection keys in descending order.
+ *
+ * @return static
+ */
+ public function sortKeysDesc(int $options = SORT_REGULAR): self
+ {
+ return $this->sortKeys($options, true);
+ }
+
+ /**
+ * Splice a portion of the underlying collection array.
+ *
+ * @param array $replacement
+ * @return static
+ */
+ public function splice(int $offset, int $length = null, $replacement = []): self
+ {
+ if (func_num_args() === 1) {
+ return new static(array_splice($this->items, $offset));
+ }
+ return new static(array_splice($this->items, $offset, $length, $replacement));
+ }
+
+ /**
+ * Get the sum of the given values.
+ *
+ * @param (callable(TValue): mixed)|string|null $callback
+ * @return mixed
+ */
+ public function sum($callback = null)
+ {
+ if (is_null($callback)) {
+ return array_sum($this->items);
+ }
+ $callback = $this->valueRetriever($callback);
+ return $this->reduce(function ($result, $item) use ($callback) {
+ return $result + $callback($item);
+ }, 0);
+ }
+
+ /**
+ * Take the first or last {$limit} items.
+ *
+ * @return static
+ */
+ public function take(int $limit): self
+ {
+ if ($limit < 0) {
+ return $this->slice($limit, abs($limit));
+ }
+ return $this->slice(0, $limit);
+ }
+
+ /**
+ * Pass the collection to the given callback and then return it.
+ *
+ * @param callable(static): mixed $callback
+ * @return $this
+ */
+ public function tap(callable $callback): self
+ {
+ $callback(new static($this->items));
+ return $this;
+ }
+
+ /**
+ * Transform each item in the collection using a callback.
+ *
+ * @param callable(TValue, TKey): TValue $callback
+ * @return $this
+ */
+ public function transform(callable $callback): self
+ {
+ $this->items = $this->map($callback)->all();
+ return $this;
+ }
+
+ /**
+ * Return only unique items from the collection array.
+ *
+ * @param (callable(TValue, TKey): bool)|string|null $key
+ * @return static
+ */
+ public function unique($key = null, bool $strict = false): self
+ {
+ $callback = $this->valueRetriever($key);
+ $exists = [];
+ return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
+ if (in_array($id = $callback($item, $key), $exists, $strict)) {
+ return true;
+ }
+ $exists[] = $id;
+ });
+ }
+
+ /**
+ * Return only unique items from the collection array using strict comparison.
+ *
+ * @param (callable(TValue, TKey): bool)|string|null $key
+ * @return static
+ */
+ public function uniqueStrict($key = null): self
+ {
+ return $this->unique($key, true);
+ }
+
+ /**
+ * Reset the keys on the underlying array.
+ *
+ * @return static
+ */
+ public function values(): self
+ {
+ return new static(array_values($this->items));
+ }
+
+ /**
+ * Zip the collection together with one or more arrays.
+ * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
+ * => [[1, 4], [2, 5], [3, 6]].
+ *
+ * @template TZipValue
+ *
+ * @param Arrayable|iterable ...$items
+ * @return static>
+ */
+ public function zip($items): self
+ {
+ $arrayableItems = array_map(function ($items) {
+ return $this->getArrayableItems($items);
+ }, func_get_args());
+ $params = array_merge([
+ function () {
+ return new static(func_get_args());
+ },
+ $this->items,
+ ], $arrayableItems);
+ return new static(call_user_func_array('array_map', $params));
+ }
+
+ /**
+ * Pad collection to the specified length with a value.
+ *
+ * @template TPadValue
+ *
+ * @param TPadValue $value
+ * @return static
+ */
+ public function pad(int $size, $value): self
+ {
+ return new static(array_pad($this->items, $size, $value));
+ }
+
+ /**
+ * Get the collection of items as a plain array.
+ *
+ * @return array
+ */
+ public function toArray(): array
+ {
+ return array_map(function ($value) {
+ return $value instanceof Arrayable ? $value->toArray() : $value;
+ }, $this->items);
+ }
+
+ /**
+ * Convert the object into something JSON serializable.
+ *
+ * @return array
+ */
+ public function jsonSerialize(): array
+ {
+ return array_map(function ($value) {
+ if ($value instanceof JsonSerializable) {
+ return $value->jsonSerialize();
+ }
+ if ($value instanceof Jsonable) {
+ return json_decode($value->__toString(), true);
+ }
+ if ($value instanceof Arrayable) {
+ return $value->toArray();
+ }
+ return $value;
+ }, $this->items);
+ }
+
+ /**
+ * Get the collection of items as JSON.
+ */
+ public function toJson(int $options = 0): string
+ {
+ return json_encode($this->jsonSerialize(), $options);
+ }
+
+ /**
+ * Get an iterator for the items.
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator(): ArrayIterator
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * Get a CachingIterator instance.
+ */
+ public function getCachingIterator(int $flags = CachingIterator::CALL_TOSTRING): CachingIterator
+ {
+ return new CachingIterator($this->getIterator(), $flags);
+ }
+
+ /**
+ * Count the number of items in the collection.
+ */
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ /**
+ * Get a base Support collection instance from this collection.
+ *
+ * @return Collection
+ */
+ public function toBase()
+ {
+ return new self($this);
+ }
+
+ /**
+ * Determine if an item exists at an offset.
+ *
+ * @param TKey $key
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return array_key_exists($key, $this->items);
+ }
+
+ /**
+ * Get an item at a given offset.
+ *
+ * @param TKey $key
+ * @return TValue
+ */
+ public function offsetGet($key)
+ {
+ return $this->items[$key];
+ }
+
+ /**
+ * Set the item at a given offset.
+ *
+ * @param null|TKey $key
+ * @param TValue $value
+ */
+ public function offsetSet($key, $value)
+ {
+ if (is_null($key)) {
+ $this->items[] = $value;
+ } else {
+ $this->items[$key] = $value;
+ }
+ }
+
+ /**
+ * Unset the item at a given offset.
+ *
+ * @param TKey $key
+ */
+ public function offsetUnset($key)
+ {
+ unset($this->items[$key]);
+ }
+
+ /**
+ * Add a method to the list of proxied methods.
+ */
+ public static function proxy(string $method): void
+ {
+ static::$proxies[] = $method;
+ }
+
+ /**
+ * Sort the collection using multiple comparisons.
+ *
+ * @return static
+ */
+ protected function sortByMany(array $comparisons = [])
+ {
+ $items = $this->items;
+
+ usort($items, function ($a, $b) use ($comparisons) {
+ foreach ($comparisons as $comparison) {
+ $comparison = Arr::wrap($comparison);
+
+ $prop = $comparison[0];
+
+ $ascending = Arr::get($comparison, 1, true) === true
+ || Arr::get($comparison, 1, true) === 'asc';
+
+ $result = 0;
+
+ if (! is_string($prop) && is_callable($prop)) {
+ $result = $prop($a, $b);
+ } else {
+ $values = [data_get($a, $prop), data_get($b, $prop)];
+
+ if (! $ascending) {
+ $values = array_reverse($values);
+ }
+
+ $result = $values[0] <=> $values[1];
+ }
+
+ if ($result === 0) {
+ continue;
+ }
+
+ return $result;
+ }
+ });
+
+ return new static($items);
+ }
+
+ /**
+ * Get an operator checker callback.
+ * @param mixed|string $operator
+ * @param null|TValue $value
+ */
+ protected function operatorForWhere(string $key, $operator = null, $value = null): \Closure
+ {
+ if (func_num_args() === 1) {
+ $value = true;
+ $operator = '=';
+ }
+ if (func_num_args() === 2) {
+ $value = $operator;
+ $operator = '=';
+ }
+ return function ($item) use ($key, $operator, $value) {
+ $retrieved = data_get($item, $key);
+ $strings = array_filter([$retrieved, $value], function ($value) {
+ return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
+ });
+ if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) {
+ return in_array($operator, ['!=', '<>', '!==']);
+ }
+ switch ($operator) {
+ default:
+ case '=':
+ case '==':
+ return $retrieved == $value;
+ case '!=':
+ case '<>':
+ return $retrieved != $value;
+ case '<':
+ return $retrieved < $value;
+ case '>':
+ return $retrieved > $value;
+ case '<=':
+ return $retrieved <= $value;
+ case '>=':
+ return $retrieved >= $value;
+ case '===':
+ return $retrieved === $value;
+ case '!==':
+ return $retrieved !== $value;
+ }
+ };
+ }
+
+ /**
+ * Determine if the given value is callable, but not a string.
+ * @param mixed $value
+ */
+ protected function useAsCallable($value): bool
+ {
+ return ! is_string($value) && is_callable($value);
+ }
+
+ /**
+ * Get a value retrieving callback.
+ * @param mixed $value
+ */
+ protected function valueRetriever($value): callable
+ {
+ if ($this->useAsCallable($value)) {
+ return $value;
+ }
+ return function ($item) use ($value) {
+ return data_get($item, $value);
+ };
+ }
+
+ /**
+ * Results array of items from Collection or Arrayable.
+ * @param null|Arrayable|iterable|Jsonable|JsonSerializable|static $items
+ * @return array
+ */
+ protected function getArrayableItems($items): array
+ {
+ if (is_array($items)) {
+ return $items;
+ }
+ if ($items instanceof self) {
+ return $items->all();
+ }
+ if ($items instanceof Arrayable) {
+ return $items->toArray();
+ }
+ if ($items instanceof Jsonable) {
+ return json_decode($items->__toString(), true);
+ }
+ if ($items instanceof JsonSerializable) {
+ return $items->jsonSerialize();
+ }
+ if ($items instanceof Traversable) {
+ return iterator_to_array($items);
+ }
+ return (array) $items;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Composer.php b/addons/epay/library/hyperf/utils/src/Composer.php
new file mode 100644
index 0000000..8d6f0c4
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Composer.php
@@ -0,0 +1,159 @@
+offsetGet('packages') ?? [];
+ $packagesDev = self::$content->offsetGet('packages-dev') ?? [];
+ foreach (array_merge($packages, $packagesDev) as $package) {
+ $packageName = '';
+ foreach ($package ?? [] as $key => $value) {
+ if ($key === 'name') {
+ $packageName = $value;
+ continue;
+ }
+ switch ($key) {
+ case 'extra':
+ $packageName && self::$extra[$packageName] = $value;
+ break;
+ case 'scripts':
+ $packageName && self::$scripts[$packageName] = $value;
+ break;
+ case 'version':
+ $packageName && self::$versions[$packageName] = $value;
+ break;
+ }
+ }
+ }
+ }
+ return self::$content;
+ }
+
+ public static function getJsonContent(): Collection
+ {
+ if (! self::$json) {
+ $path = BASE_PATH . '/composer.json';
+ if (! is_readable($path)) {
+ throw new \RuntimeException('composer.json is not readable.');
+ }
+ self::$json = collect(json_decode(file_get_contents($path), true));
+ }
+ return self::$json;
+ }
+
+ public static function discoverLockFile(): string
+ {
+ $path = '';
+ if (is_readable(BASE_PATH . '/composer.lock')) {
+ $path = BASE_PATH . '/composer.lock';
+ }
+ return $path;
+ }
+
+ public static function getMergedExtra(string $key = null)
+ {
+ if (! self::$extra) {
+ self::getLockContent();
+ }
+ if ($key === null) {
+ return self::$extra;
+ }
+ $extra = [];
+ foreach (self::$extra ?? [] as $project => $config) {
+ foreach ($config ?? [] as $configKey => $item) {
+ if ($key === $configKey && $item) {
+ foreach ($item ?? [] as $k => $v) {
+ if (is_array($v)) {
+ $extra[$k] = array_merge($extra[$k] ?? [], $v);
+ } else {
+ $extra[$k][] = $v;
+ }
+ }
+ }
+ }
+ }
+ return $extra;
+ }
+
+ public static function getLoader(): ClassLoader
+ {
+ if (! self::$classLoader) {
+ self::$classLoader = self::findLoader();
+ }
+ return self::$classLoader;
+ }
+
+ public static function setLoader(ClassLoader $classLoader): ClassLoader
+ {
+ self::$classLoader = $classLoader;
+ return $classLoader;
+ }
+
+ private static function findLoader(): ClassLoader
+ {
+ $composerClass = '';
+ foreach (get_declared_classes() as $declaredClass) {
+ if (strpos($declaredClass, 'ComposerAutoloaderInit') === 0 && method_exists($declaredClass, 'getLoader')) {
+ $composerClass = $declaredClass;
+ break;
+ }
+ }
+ if (! $composerClass) {
+ throw new \RuntimeException('Composer loader not found.');
+ }
+ return $composerClass::getLoader();
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/ConfigProvider.php b/addons/epay/library/hyperf/utils/src/ConfigProvider.php
new file mode 100644
index 0000000..c2e9abf
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/ConfigProvider.php
@@ -0,0 +1,37 @@
+ [
+ Serializer::class => SerializerFactory::class,
+ NormalizerInterface::class => SimpleNormalizer::class,
+ ],
+ 'annotations' => [
+ 'scan' => [
+ 'paths' => [
+ __DIR__,
+ ],
+ ],
+ ],
+ ];
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Context.php b/addons/epay/library/hyperf/utils/src/Context.php
new file mode 100644
index 0000000..0db022e
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Context.php
@@ -0,0 +1,107 @@
+exchangeArray($keys ? Arr::only($from->getArrayCopy(), $keys) : $from->getArrayCopy());
+ }
+
+ /**
+ * Retrieve the value and override it by closure.
+ */
+ public static function override(string $id, \Closure $closure)
+ {
+ $value = null;
+ if (self::has($id)) {
+ $value = self::get($id);
+ }
+ $value = $closure($value);
+ self::set($id, $value);
+ return $value;
+ }
+
+ /**
+ * Retrieve the value and store it if not exists.
+ * @param mixed $value
+ */
+ public static function getOrSet(string $id, $value)
+ {
+ if (! self::has($id)) {
+ return self::set($id, value($value));
+ }
+ return self::get($id);
+ }
+
+ public static function getContainer()
+ {
+ if (Coroutine::inCoroutine()) {
+ return Co::getContextFor();
+ }
+
+ return static::$nonCoContext;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Contracts/Arrayable.php b/addons/epay/library/hyperf/utils/src/Contracts/Arrayable.php
new file mode 100644
index 0000000..d588c44
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Contracts/Arrayable.php
@@ -0,0 +1,24 @@
+
+ */
+ public function toArray(): array;
+}
diff --git a/addons/epay/library/hyperf/utils/src/Contracts/Jsonable.php b/addons/epay/library/hyperf/utils/src/Contracts/Jsonable.php
new file mode 100644
index 0000000..01d3668
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Contracts/Jsonable.php
@@ -0,0 +1,17 @@
+channel = new Channel(1);
+ }
+
+ /**
+ * Yield the current coroutine for a given timeout,
+ * unless the coordinator is woke up from outside.
+ *
+ * @param float|int $timeout
+ * @return bool returns true if the coordinator has been woken up
+ */
+ public function yield($timeout = -1): bool
+ {
+ $this->channel->pop((float) $timeout);
+ return $this->channel->isClosing();
+ }
+
+ /**
+ * Wakeup all coroutines yielding for this coordinator.
+ */
+ public function resume(): void
+ {
+ $this->channel->close();
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Coordinator/CoordinatorManager.php b/addons/epay/library/hyperf/utils/src/Coordinator/CoordinatorManager.php
new file mode 100644
index 0000000..e82e433
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Coordinator/CoordinatorManager.php
@@ -0,0 +1,53 @@
+has(StdoutLoggerInterface::class)) {
+ /* @var LoggerInterface $logger */
+ $logger = $container->get(StdoutLoggerInterface::class);
+ /* @var FormatterInterface $formatter */
+ if ($container->has(FormatterInterface::class)) {
+ $formatter = $container->get(FormatterInterface::class);
+ $logger->warning($formatter->format($throwable));
+ } else {
+ $logger->warning(sprintf('Uncaptured exception[%s] detected in %s::%d.', get_class($throwable), $throwable->getFile(), $throwable->getLine()));
+ }
+ }
+ }
+ }
+ });
+
+ try {
+ return $coroutine->getId();
+ } catch (\Throwable $exception) {
+ return -1;
+ }
+ }
+
+ public static function inCoroutine(): bool
+ {
+ return Co::id() > 0;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Coroutine/Concurrent.php b/addons/epay/library/hyperf/utils/src/Coroutine/Concurrent.php
new file mode 100644
index 0000000..c029c6a
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Coroutine/Concurrent.php
@@ -0,0 +1,95 @@
+limit = $limit;
+ $this->channel = new Channel($limit);
+ }
+
+ public function __call($name, $arguments)
+ {
+ if (in_array($name, ['isFull', 'isEmpty'])) {
+ return $this->channel->{$name}(...$arguments);
+ }
+ }
+
+ public function getLimit(): int
+ {
+ return $this->limit;
+ }
+
+ public function length(): int
+ {
+ return $this->channel->getLength();
+ }
+
+ public function getLength(): int
+ {
+ return $this->channel->getLength();
+ }
+
+ public function getRunningCoroutineCount(): int
+ {
+ return $this->getLength();
+ }
+
+ public function getChannel(): Channel
+ {
+ return $this->channel;
+ }
+
+ public function create(callable $callable): void
+ {
+ $this->channel->push(true);
+
+ Coroutine::create(function () use ($callable) {
+ try {
+ $callable();
+ } catch (\Throwable $exception) {
+ if (ApplicationContext::hasContainer()) {
+ $container = ApplicationContext::getContainer();
+ if ($container->has(StdoutLoggerInterface::class) && $container->has(FormatterInterface::class)) {
+ $logger = $container->get(StdoutLoggerInterface::class);
+ $formatter = $container->get(FormatterInterface::class);
+ $logger->error($formatter->format($exception));
+ }
+ }
+ } finally {
+ $this->channel->pop();
+ }
+ });
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Coroutine/Locker.php b/addons/epay/library/hyperf/utils/src/Coroutine/Locker.php
new file mode 100644
index 0000000..31c155c
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Coroutine/Locker.php
@@ -0,0 +1,60 @@
+ 0) {
+ SwooleCoroutine::resume($id);
+ }
+ }
+ self::clear($key);
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Exception/ChannelClosedException.php b/addons/epay/library/hyperf/utils/src/Exception/ChannelClosedException.php
new file mode 100644
index 0000000..52c7a42
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Exception/ChannelClosedException.php
@@ -0,0 +1,16 @@
+throwable = $throwable;
+ }
+
+ public function getThrowable(): Throwable
+ {
+ return $this->throwable;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Exception/InvalidArgumentException.php b/addons/epay/library/hyperf/utils/src/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..f4155ec
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Exception/InvalidArgumentException.php
@@ -0,0 +1,16 @@
+results;
+ }
+
+ public function setResults(array $results)
+ {
+ $this->results = $results;
+ }
+
+ public function getThrowables()
+ {
+ return $this->throwables;
+ }
+
+ public function setThrowables(array $throwables)
+ {
+ return $this->throwables = $throwables;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Exception/TimeoutException.php b/addons/epay/library/hyperf/utils/src/Exception/TimeoutException.php
new file mode 100644
index 0000000..7ff9e02
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Exception/TimeoutException.php
@@ -0,0 +1,16 @@
+isFile($path)) {
+ return $lock ? $this->sharedGet($path) : file_get_contents($path);
+ }
+
+ throw new FileNotFoundException("File does not exist at path {$path}");
+ }
+
+ /**
+ * Get contents of a file with shared access.
+ */
+ public function sharedGet(string $path): string
+ {
+ return $this->atomic($path, function ($path) {
+ $contents = '';
+ $handle = fopen($path, 'rb');
+ if ($handle) {
+ $wouldBlock = false;
+ flock($handle, LOCK_SH | LOCK_NB, $wouldBlock);
+ while ($wouldBlock) {
+ usleep(1000);
+ flock($handle, LOCK_SH | LOCK_NB, $wouldBlock);
+ }
+ try {
+ clearstatcache(true, $path);
+ $contents = fread($handle, $this->size($path) ?: 1);
+ } finally {
+ flock($handle, LOCK_UN);
+ fclose($handle);
+ }
+ }
+ return $contents;
+ });
+ }
+
+ /**
+ * Get the returned value of a file.
+ *
+ * @throws \Hyperf\Utils\Filesystem\FileNotFoundException
+ */
+ public function getRequire(string $path)
+ {
+ if ($this->isFile($path)) {
+ return require $path;
+ }
+
+ throw new FileNotFoundException("File does not exist at path {$path}");
+ }
+
+ /**
+ * Require the given file once.
+ *
+ * @return mixed
+ */
+ public function requireOnce(string $file)
+ {
+ require_once $file;
+ }
+
+ /**
+ * Get the MD5 hash of the file at the given path.
+ */
+ public function hash(string $path): string
+ {
+ return md5_file($path);
+ }
+
+ /**
+ * Clears file status cache.
+ */
+ public function clearStatCache(string $path): void
+ {
+ clearstatcache(true, $path);
+ }
+
+ /**
+ * Write the contents of a file.
+ *
+ * @param resource|string $contents
+ * @return bool|int
+ */
+ public function put(string $path, $contents, bool $lock = false)
+ {
+ if ($lock) {
+ return $this->atomic($path, function ($path) use ($contents) {
+ $handle = fopen($path, 'w+');
+ if ($handle) {
+ $wouldBlock = false;
+ flock($handle, LOCK_EX | LOCK_NB, $wouldBlock);
+ while ($wouldBlock) {
+ usleep(1000);
+ flock($handle, LOCK_EX | LOCK_NB, $wouldBlock);
+ }
+ try {
+ fwrite($handle, $contents);
+ } finally {
+ flock($handle, LOCK_UN);
+ fclose($handle);
+ }
+ }
+ });
+ }
+ return file_put_contents($path, $contents);
+ }
+
+ /**
+ * Write the contents of a file, replacing it atomically if it already exists.
+ */
+ public function replace(string $path, string $content)
+ {
+ // If the path already exists and is a symlink, get the real path...
+ clearstatcache(true, $path);
+
+ $path = realpath($path) ?: $path;
+
+ $tempPath = tempnam(dirname($path), basename($path));
+
+ // Fix permissions of tempPath because `tempnam()` creates it with permissions set to 0600...
+ chmod($tempPath, 0777 - umask());
+
+ file_put_contents($tempPath, $content);
+
+ rename($tempPath, $path);
+ }
+
+ /**
+ * Prepend to a file.
+ */
+ public function prepend(string $path, string $data): int
+ {
+ if ($this->exists($path)) {
+ return $this->put($path, $data . $this->get($path));
+ }
+
+ return $this->put($path, $data);
+ }
+
+ /**
+ * Append to a file.
+ */
+ public function append(string $path, string $data): int
+ {
+ return file_put_contents($path, $data, FILE_APPEND);
+ }
+
+ /**
+ * Get or set UNIX mode of a file or directory.
+ */
+ public function chmod(string $path, ?int $mode = null)
+ {
+ if ($mode) {
+ return chmod($path, $mode);
+ }
+
+ return substr(sprintf('%o', fileperms($path)), -4);
+ }
+
+ /**
+ * Delete the file at a given path.
+ *
+ * @param array|string $paths
+ */
+ public function delete($paths): bool
+ {
+ $paths = is_array($paths) ? $paths : func_get_args();
+
+ $success = true;
+
+ foreach ($paths as $path) {
+ try {
+ if (! @unlink($path)) {
+ $success = false;
+ }
+ } catch (ErrorException $e) {
+ $success = false;
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Move a file to a new location.
+ */
+ public function move(string $path, string $target): bool
+ {
+ return rename($path, $target);
+ }
+
+ /**
+ * Copy a file to a new location.
+ */
+ public function copy(string $path, string $target): bool
+ {
+ return copy($path, $target);
+ }
+
+ /**
+ * Create a hard link to the target file or directory.
+ */
+ public function link(string $target, string $link)
+ {
+ if (! $this->windowsOs()) {
+ return symlink($target, $link);
+ }
+
+ $mode = $this->isDirectory($target) ? 'J' : 'H';
+
+ exec("mklink /{$mode} \"{$link}\" \"{$target}\"");
+ }
+
+ /**
+ * Extract the file name from a file path.
+ */
+ public function name(string $path): string
+ {
+ return pathinfo($path, PATHINFO_FILENAME);
+ }
+
+ /**
+ * Extract the trailing name component from a file path.
+ */
+ public function basename(string $path): string
+ {
+ return pathinfo($path, PATHINFO_BASENAME);
+ }
+
+ /**
+ * Extract the parent directory from a file path.
+ */
+ public function dirname(string $path): string
+ {
+ return pathinfo($path, PATHINFO_DIRNAME);
+ }
+
+ /**
+ * Extract the file extension from a file path.
+ */
+ public function extension(string $path): string
+ {
+ return pathinfo($path, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * Get the file type of a given file.
+ */
+ public function type(string $path): string
+ {
+ return filetype($path);
+ }
+
+ /**
+ * Get the mime-type of a given file.
+ *
+ * @return false|string
+ */
+ public function mimeType(string $path)
+ {
+ return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path);
+ }
+
+ /**
+ * Get the file size of a given file.
+ */
+ public function size(string $path): int
+ {
+ return filesize($path);
+ }
+
+ /**
+ * Get the file's last modification time.
+ */
+ public function lastModified(string $path): int
+ {
+ return filemtime($path);
+ }
+
+ /**
+ * Determine if the given path is a directory.
+ */
+ public function isDirectory(string $directory): bool
+ {
+ return is_dir($directory);
+ }
+
+ /**
+ * Determine if the given path is readable.
+ */
+ public function isReadable(string $path): bool
+ {
+ return is_readable($path);
+ }
+
+ /**
+ * Determine if the given path is writable.
+ */
+ public function isWritable(string $path): bool
+ {
+ return is_writable($path);
+ }
+
+ /**
+ * Determine if the given path is a file.
+ */
+ public function isFile(string $file): bool
+ {
+ return is_file($file);
+ }
+
+ /**
+ * Find path names matching a given pattern.
+ */
+ public function glob(string $pattern, int $flags = 0): array
+ {
+ return glob($pattern, $flags);
+ }
+
+ /**
+ * Get an array of all files in a directory.
+ *
+ * @return \Symfony\Component\Finder\SplFileInfo[]
+ */
+ public function files(string $directory, bool $hidden = false): array
+ {
+ return iterator_to_array(
+ Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->depth(0)->sortByName(),
+ false
+ );
+ }
+
+ /**
+ * Get all of the files from the given directory (recursive).
+ * @return \Symfony\Component\Finder\SplFileInfo[]
+ */
+ public function allFiles(string $directory, bool $hidden = false): array
+ {
+ return iterator_to_array(
+ Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->sortByName(),
+ false
+ );
+ }
+
+ /**
+ * Get all of the directories within a given directory.
+ */
+ public function directories(string $directory): array
+ {
+ $directories = [];
+
+ foreach (Finder::create()->in($directory)->directories()->depth(0)->sortByName() as $dir) {
+ $directories[] = $dir->getPathname();
+ }
+
+ return $directories;
+ }
+
+ /**
+ * Create a directory.
+ */
+ public function makeDirectory(string $path, int $mode = 0755, bool $recursive = false, bool $force = false): bool
+ {
+ if ($force) {
+ return @mkdir($path, $mode, $recursive);
+ }
+
+ return mkdir($path, $mode, $recursive);
+ }
+
+ /**
+ * Move a directory.
+ */
+ public function moveDirectory(string $from, string $to, bool $overwrite = false): bool
+ {
+ if ($overwrite && $this->isDirectory($to) && ! $this->deleteDirectory($to)) {
+ return false;
+ }
+
+ return @rename($from, $to) === true;
+ }
+
+ /**
+ * Copy a directory from one location to another.
+ */
+ public function copyDirectory(string $directory, string $destination, int $options = null): bool
+ {
+ if (! $this->isDirectory($directory)) {
+ return false;
+ }
+
+ $options = $options ?: FilesystemIterator::SKIP_DOTS;
+
+ // If the destination directory does not actually exist, we will go ahead and
+ // create it recursively, which just gets the destination prepared to copy
+ // the files over. Once we make the directory we'll proceed the copying.
+ if (! $this->isDirectory($destination)) {
+ $this->makeDirectory($destination, 0777, true);
+ }
+
+ $items = new FilesystemIterator($directory, $options);
+
+ foreach ($items as $item) {
+ // As we spin through items, we will check to see if the current file is actually
+ // a directory or a file. When it is actually a directory we will need to call
+ // back into this function recursively to keep copying these nested folders.
+ $target = $destination . DIRECTORY_SEPARATOR . $item->getBasename();
+
+ if ($item->isDir()) {
+ $path = $item->getPathname();
+
+ if (! $this->copyDirectory($path, $target, $options)) {
+ return false;
+ }
+ }
+
+ // If the current items is just a regular file, we will just copy this to the new
+ // location and keep looping. If for some reason the copy fails we'll bail out
+ // and return false, so the developer is aware that the copy process failed.
+ else {
+ if (! $this->copy($item->getPathname(), $target)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Recursively delete a directory.
+ *
+ * The directory itself may be optionally preserved.
+ */
+ public function deleteDirectory(string $directory, bool $preserve = false): bool
+ {
+ if (! $this->isDirectory($directory)) {
+ return false;
+ }
+
+ $items = new FilesystemIterator($directory);
+
+ foreach ($items as $item) {
+ // If the item is a directory, we can just recurse into the function and
+ // delete that sub-directory otherwise we'll just delete the file and
+ // keep iterating through each file until the directory is cleaned.
+ if ($item->isDir() && ! $item->isLink()) {
+ $this->deleteDirectory($item->getPathname());
+ }
+
+ // If the item is just a file, we can go ahead and delete it since we're
+ // just looping through and waxing all of the files in this directory
+ // and calling directories recursively, so we delete the real path.
+ else {
+ $this->delete($item->getPathname());
+ }
+ }
+
+ if (! $preserve) {
+ @rmdir($directory);
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove all of the directories within a given directory.
+ */
+ public function deleteDirectories(string $directory): bool
+ {
+ $allDirectories = $this->directories($directory);
+
+ if (! empty($allDirectories)) {
+ foreach ($allDirectories as $directoryName) {
+ $this->deleteDirectory($directoryName);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Empty the specified directory of all files and folders.
+ */
+ public function cleanDirectory(string $directory): bool
+ {
+ return $this->deleteDirectory($directory, true);
+ }
+
+ /**
+ * Detect whether it's Windows.
+ */
+ public function windowsOs(): bool
+ {
+ return stripos(PHP_OS, 'win') === 0;
+ }
+
+ protected function atomic($path, $callback)
+ {
+ if (Coroutine::inCoroutine()) {
+ try {
+ while (! Coroutine\Locker::lock($path)) {
+ usleep(1000);
+ }
+ return $callback($path);
+ } finally {
+ Coroutine\Locker::unlock($path);
+ }
+ } else {
+ return $callback($path);
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Fluent.php b/addons/epay/library/hyperf/utils/src/Fluent.php
new file mode 100644
index 0000000..0f1cf84
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Fluent.php
@@ -0,0 +1,200 @@
+ $value) {
+ $this->attributes[$key] = $value;
+ }
+ }
+
+ /**
+ * Handle dynamic calls to the fluent instance to set attributes.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return $this
+ */
+ public function __call($method, $parameters)
+ {
+ $this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true;
+
+ return $this;
+ }
+
+ /**
+ * Dynamically retrieve the value of an attribute.
+ *
+ * @param string $key
+ */
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Dynamically set the value of an attribute.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function __set($key, $value)
+ {
+ $this->offsetSet($key, $value);
+ }
+
+ /**
+ * Dynamically check if an attribute is set.
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return $this->offsetExists($key);
+ }
+
+ /**
+ * Dynamically unset an attribute.
+ *
+ * @param string $key
+ */
+ public function __unset($key)
+ {
+ $this->offsetUnset($key);
+ }
+
+ public function __toString(): string
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * Get an attribute from the fluent instance.
+ *
+ * @param string $key
+ * @param null|mixed $default
+ */
+ public function get($key, $default = null)
+ {
+ if (array_key_exists($key, $this->attributes)) {
+ return $this->attributes[$key];
+ }
+
+ return value($default);
+ }
+
+ /**
+ * Get the attributes from the fluent instance.
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Convert the fluent instance to an array.
+ */
+ public function toArray(): array
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Convert the object into something JSON serializable.
+ *
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * Convert the fluent instance to JSON.
+ *
+ * @param int $options
+ * @return string
+ */
+ public function toJson($options = 0)
+ {
+ return json_encode($this->jsonSerialize(), $options);
+ }
+
+ /**
+ * Determine if the given offset exists.
+ *
+ * @param string $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->attributes[$offset]);
+ }
+
+ /**
+ * Get the value for a given offset.
+ *
+ * @param string $offset
+ */
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * Set the value at the given offset.
+ *
+ * @param string $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->attributes[$offset] = $value;
+ }
+
+ /**
+ * Unset the value at the given offset.
+ *
+ * @param string $offset
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->attributes[$offset]);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Functions.php b/addons/epay/library/hyperf/utils/src/Functions.php
new file mode 100644
index 0000000..02b20f0
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Functions.php
@@ -0,0 +1,486 @@
+ 1 && $value[0] === '"' && $value[$valueLength - 1] === '"') {
+ return substr($value, 1, -1);
+ }
+ return $value;
+ }
+}
+if (! function_exists('retry')) {
+ /**
+ * Retry an operation a given number of times.
+ *
+ * @param float|int $times
+ * @param int $sleep millisecond
+ * @throws \Throwable
+ */
+ function retry($times, callable $callback, int $sleep = 0)
+ {
+ $attempts = 0;
+ $backoff = new Backoff($sleep);
+
+ beginning:
+ try {
+ return $callback(++$attempts);
+ } catch (\Throwable $e) {
+ if (--$times < 0) {
+ throw $e;
+ }
+
+ $backoff->sleep();
+ goto beginning;
+ }
+ }
+}
+if (! function_exists('with')) {
+ /**
+ * Return the given value, optionally passed through the given callback.
+ *
+ * @param mixed $value
+ */
+ function with($value, callable $callback = null)
+ {
+ return is_null($callback) ? $value : $callback($value);
+ }
+}
+
+if (! function_exists('collect')) {
+ /**
+ * Create a collection from the given value.
+ *
+ * @param null|mixed $value
+ * @return Collection
+ */
+ function collect($value = null)
+ {
+ return new Collection($value);
+ }
+}
+if (! function_exists('data_fill')) {
+ /**
+ * Fill in data where it's missing.
+ *
+ * @param mixed $target
+ * @param array|string $key
+ * @param mixed $value
+ */
+ function data_fill(&$target, $key, $value)
+ {
+ return data_set($target, $key, $value, false);
+ }
+}
+if (! function_exists('data_get')) {
+ /**
+ * Get an item from an array or object using "dot" notation.
+ *
+ * @param null|array|int|string $key
+ * @param null|mixed $default
+ * @param mixed $target
+ */
+ function data_get($target, $key, $default = null)
+ {
+ if (is_null($key)) {
+ return $target;
+ }
+
+ $key = is_array($key) ? $key : explode('.', is_int($key) ? (string) $key : $key);
+ while (! is_null($segment = array_shift($key))) {
+ if ($segment === '*') {
+ if ($target instanceof Collection) {
+ $target = $target->all();
+ } elseif (! is_array($target)) {
+ return value($default);
+ }
+ $result = [];
+ foreach ($target as $item) {
+ $result[] = data_get($item, $key);
+ }
+ return in_array('*', $key) ? Arr::collapse($result) : $result;
+ }
+ if (Arr::accessible($target) && Arr::exists($target, $segment)) {
+ $target = $target[$segment];
+ } elseif (is_object($target) && isset($target->{$segment})) {
+ $target = $target->{$segment};
+ } else {
+ return value($default);
+ }
+ }
+ return $target;
+ }
+}
+if (! function_exists('data_set')) {
+ /**
+ * Set an item on an array or object using dot notation.
+ *
+ * @param mixed $target
+ * @param array|string $key
+ * @param bool $overwrite
+ * @param mixed $value
+ */
+ function data_set(&$target, $key, $value, $overwrite = true)
+ {
+ $segments = is_array($key) ? $key : explode('.', $key);
+ if (($segment = array_shift($segments)) === '*') {
+ if (! Arr::accessible($target)) {
+ $target = [];
+ }
+ if ($segments) {
+ foreach ($target as &$inner) {
+ data_set($inner, $segments, $value, $overwrite);
+ }
+ } elseif ($overwrite) {
+ foreach ($target as &$inner) {
+ $inner = $value;
+ }
+ }
+ } elseif (Arr::accessible($target)) {
+ if ($segments) {
+ if (! Arr::exists($target, $segment)) {
+ $target[$segment] = [];
+ }
+ data_set($target[$segment], $segments, $value, $overwrite);
+ } elseif ($overwrite || ! Arr::exists($target, $segment)) {
+ $target[$segment] = $value;
+ }
+ } elseif (is_object($target)) {
+ if ($segments) {
+ if (! isset($target->{$segment})) {
+ $target->{$segment} = [];
+ }
+ data_set($target->{$segment}, $segments, $value, $overwrite);
+ } elseif ($overwrite || ! isset($target->{$segment})) {
+ $target->{$segment} = $value;
+ }
+ } else {
+ $target = [];
+ if ($segments) {
+ $target[$segment] = [];
+ data_set($target[$segment], $segments, $value, $overwrite);
+ } elseif ($overwrite) {
+ $target[$segment] = $value;
+ }
+ }
+ return $target;
+ }
+}
+if (! function_exists('head')) {
+ /**
+ * Get the first element of an array. Useful for method chaining.
+ *
+ * @param array $array
+ */
+ function head($array)
+ {
+ return reset($array);
+ }
+}
+if (! function_exists('last')) {
+ /**
+ * Get the last element from an array.
+ *
+ * @param array $array
+ */
+ function last($array)
+ {
+ return end($array);
+ }
+}
+if (! function_exists('tap')) {
+ /**
+ * Call the given Closure with the given value then return the value.
+ *
+ * @param null|callable $callback
+ * @param mixed $value
+ */
+ function tap($value, $callback = null)
+ {
+ if (is_null($callback)) {
+ return new HigherOrderTapProxy($value);
+ }
+ $callback($value);
+ return $value;
+ }
+}
+
+if (! function_exists('call')) {
+ /**
+ * Call a callback with the arguments.
+ *
+ * @param mixed $callback
+ * @return null|mixed
+ */
+ function call($callback, array $args = [])
+ {
+ $result = null;
+ if ($callback instanceof \Closure) {
+ $result = $callback(...$args);
+ } elseif (is_object($callback) || (is_string($callback) && function_exists($callback))) {
+ $result = $callback(...$args);
+ } elseif (is_array($callback)) {
+ [$object, $method] = $callback;
+ $result = is_object($object) ? $object->{$method}(...$args) : $object::$method(...$args);
+ } else {
+ $result = call_user_func_array($callback, $args);
+ }
+ return $result;
+ }
+}
+
+if (! function_exists('go')) {
+ /**
+ * @return bool|int
+ */
+ function go(callable $callable)
+ {
+ $id = Coroutine::create($callable);
+ return $id > 0 ? $id : false;
+ }
+}
+
+if (! function_exists('co')) {
+ /**
+ * @return bool|int
+ */
+ function co(callable $callable)
+ {
+ $id = Coroutine::create($callable);
+ return $id > 0 ? $id : false;
+ }
+}
+
+if (! function_exists('defer')) {
+ function defer(callable $callable): void
+ {
+ Coroutine::defer($callable);
+ }
+}
+
+if (! function_exists('class_basename')) {
+ /**
+ * Get the class "basename" of the given object / class.
+ *
+ * @param object|string $class
+ * @return string
+ */
+ function class_basename($class)
+ {
+ $class = is_object($class) ? get_class($class) : $class;
+
+ return basename(str_replace('\\', '/', $class));
+ }
+}
+
+if (! function_exists('trait_uses_recursive')) {
+ /**
+ * Returns all traits used by a trait and its traits.
+ *
+ * @param string $trait
+ * @return array
+ */
+ function trait_uses_recursive($trait)
+ {
+ $traits = class_uses($trait);
+
+ foreach ($traits as $trait) {
+ $traits += trait_uses_recursive($trait);
+ }
+
+ return $traits;
+ }
+}
+
+if (! function_exists('class_uses_recursive')) {
+ /**
+ * Returns all traits used by a class, its parent classes and trait of their traits.
+ *
+ * @param object|string $class
+ * @return array
+ */
+ function class_uses_recursive($class)
+ {
+ if (is_object($class)) {
+ $class = get_class($class);
+ }
+
+ $results = [];
+
+ /* @phpstan-ignore-next-line */
+ foreach (array_reverse(class_parents($class)) + [$class => $class] as $class) {
+ $results += trait_uses_recursive($class);
+ }
+
+ return array_unique($results);
+ }
+}
+
+if (! function_exists('setter')) {
+ /**
+ * Create a setter string.
+ */
+ function setter(string $property): string
+ {
+ return 'set' . Str::studly($property);
+ }
+}
+
+if (! function_exists('getter')) {
+ /**
+ * Create a getter string.
+ */
+ function getter(string $property): string
+ {
+ return 'get' . Str::studly($property);
+ }
+}
+
+if (! function_exists('parallel')) {
+ /**
+ * @param callable[] $callables
+ * @param int $concurrent if $concurrent is equal to 0, that means unlimit
+ */
+ function parallel(array $callables, int $concurrent = 0)
+ {
+ $parallel = new Parallel($concurrent);
+ foreach ($callables as $key => $callable) {
+ $parallel->add($callable, $key);
+ }
+ return $parallel->wait();
+ }
+}
+
+if (! function_exists('make')) {
+ /**
+ * Create an object instance, if the DI container exist in ApplicationContext,
+ * then the object will be created by DI container via `make()` method, if not,
+ * the object will create by `new` keyword.
+ */
+ function make(string $name, array $parameters = [])
+ {
+ if (ApplicationContext::hasContainer()) {
+ $container = ApplicationContext::getContainer();
+ if (method_exists($container, 'make')) {
+ return $container->make($name, $parameters);
+ }
+ }
+ $parameters = array_values($parameters);
+ return new $name(...$parameters);
+ }
+}
+
+if (! function_exists('run')) {
+ /**
+ * Run callable in non-coroutine environment, all hook functions by Swoole only available in the callable.
+ *
+ * @param array|callable $callbacks
+ */
+ function run($callbacks, int $flags = SWOOLE_HOOK_ALL): bool
+ {
+ if (Coroutine::inCoroutine()) {
+ throw new RuntimeException('Function \'run\' only execute in non-coroutine environment.');
+ }
+
+ \Swoole\Runtime::enableCoroutine($flags);
+
+ $result = \Swoole\Coroutine\Run(...(array) $callbacks);
+
+ \Swoole\Runtime::enableCoroutine(false);
+ return $result;
+ }
+}
+
+if (! function_exists('swoole_hook_flags')) {
+ /**
+ * Return the default swoole hook flags, you can rewrite it by defining `SWOOLE_HOOK_FLAGS`.
+ */
+ function swoole_hook_flags(): int
+ {
+ return defined('SWOOLE_HOOK_FLAGS') ? SWOOLE_HOOK_FLAGS : SWOOLE_HOOK_ALL;
+ }
+}
+
+if (! function_exists('optional')) {
+ /**
+ * Provide access to optional objects.
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ function optional($value = null, callable $callback = null)
+ {
+ if (is_null($callback)) {
+ return new Optional($value);
+ }
+ if (! is_null($value)) {
+ return $callback($value);
+ }
+ }
+}
+
+if (! function_exists('wait')) {
+ function wait(Closure $closure, ?float $timeout = null)
+ {
+ if (ApplicationContext::hasContainer()) {
+ $waiter = ApplicationContext::getContainer()->get(Waiter::class);
+ return $waiter->wait($closure, $timeout);
+ }
+ return (new Waiter())->wait($closure, $timeout);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/HigherOrderCollectionProxy.php b/addons/epay/library/hyperf/utils/src/HigherOrderCollectionProxy.php
new file mode 100644
index 0000000..21d9b04
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/HigherOrderCollectionProxy.php
@@ -0,0 +1,63 @@
+method = $method;
+ $this->collection = $collection;
+ }
+
+ /**
+ * Proxy accessing an attribute onto the collection items.
+ */
+ public function __get(string $key)
+ {
+ return $this->collection->{$this->method}(function ($value) use ($key) {
+ return is_array($value) ? $value[$key] : $value->{$key};
+ });
+ }
+
+ /**
+ * Proxy a method call onto the collection items.
+ */
+ public function __call(string $method, array $parameters)
+ {
+ return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
+ return $value->{$method}(...$parameters);
+ });
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/HigherOrderTapProxy.php b/addons/epay/library/hyperf/utils/src/HigherOrderTapProxy.php
new file mode 100644
index 0000000..ae07961
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/HigherOrderTapProxy.php
@@ -0,0 +1,46 @@
+target = $target;
+ }
+
+ /**
+ * Dynamically pass method calls to the target.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ $this->target->{$method}(...$parameters);
+
+ return $this->target;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/InteractsWithTime.php b/addons/epay/library/hyperf/utils/src/InteractsWithTime.php
new file mode 100644
index 0000000..09db79b
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/InteractsWithTime.php
@@ -0,0 +1,70 @@
+parseDateInterval($delay);
+
+ return $delay instanceof DateTimeInterface
+ ? max(0, $delay->getTimestamp() - $this->currentTime())
+ : (int) $delay;
+ }
+
+ /**
+ * Get the "available at" UNIX timestamp.
+ *
+ * @param \DateInterval|\DateTimeInterface|int $delay
+ */
+ protected function availableAt($delay = 0): int
+ {
+ $delay = $this->parseDateInterval($delay);
+
+ return $delay instanceof DateTimeInterface
+ ? $delay->getTimestamp()
+ : Carbon::now()->addSeconds($delay)->getTimestamp();
+ }
+
+ /**
+ * If the given value is an interval, convert it to a DateTime instance.
+ *
+ * @param \DateInterval|\DateTimeInterface|int $delay
+ * @return \DateTimeInterface|int
+ */
+ protected function parseDateInterval($delay)
+ {
+ if ($delay instanceof DateInterval) {
+ $delay = Carbon::now()->add($delay);
+ }
+
+ return $delay;
+ }
+
+ /**
+ * Get the current system time as a UNIX timestamp.
+ */
+ protected function currentTime(): int
+ {
+ return Carbon::now()->getTimestamp();
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/MessageBag.php b/addons/epay/library/hyperf/utils/src/MessageBag.php
new file mode 100644
index 0000000..3b3e029
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/MessageBag.php
@@ -0,0 +1,351 @@
+ $value) {
+ $value = $value instanceof Arrayable ? $value->toArray() : (array) $value;
+
+ $this->messages[$key] = array_unique($value);
+ }
+ }
+
+ /**
+ * Convert the message bag to its string representation.
+ */
+ public function __toString(): string
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * Get the keys present in the message bag.
+ */
+ public function keys(): array
+ {
+ return array_keys($this->messages);
+ }
+
+ /**
+ * Add a message to the message bag.
+ */
+ public function add(string $key, string $message): MessageBagContract
+ {
+ if ($this->isUnique($key, $message)) {
+ $this->messages[$key][] = $message;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Merge a new array of messages into the message bag.
+ *
+ * @param array|MessageProvider $messages
+ * @return $this
+ */
+ public function merge($messages)
+ {
+ if ($messages instanceof MessageProvider) {
+ $messages = $messages->getMessageBag()->getMessages();
+ }
+
+ $this->messages = array_merge_recursive($this->messages, $messages);
+
+ return $this;
+ }
+
+ /**
+ * Determine if messages exist for all of the given keys.
+ *
+ * @param null|array|string $key
+ */
+ public function has($key): bool
+ {
+ if ($this->isEmpty()) {
+ return false;
+ }
+
+ if (is_null($key)) {
+ return $this->any();
+ }
+
+ $keys = is_array($key) ? $key : func_get_args();
+
+ foreach ($keys as $key) {
+ if ($this->first($key) === '') {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine if messages exist for any of the given keys.
+ *
+ * @param array|string $keys
+ */
+ public function hasAny($keys = []): bool
+ {
+ if ($this->isEmpty()) {
+ return false;
+ }
+
+ $keys = is_array($keys) ? $keys : func_get_args();
+
+ foreach ($keys as $key) {
+ if ($this->has($key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the first message from the message bag for a given key.
+ *
+ * @param string $key
+ * @param string $format
+ */
+ public function first($key = null, $format = null): string
+ {
+ $messages = is_null($key) ? $this->all($format) : $this->get($key, $format);
+
+ $firstMessage = Arr::first($messages, null, '');
+
+ return is_array($firstMessage) ? Arr::first($firstMessage) : $firstMessage;
+ }
+
+ /**
+ * Get all of the messages from the message bag for a given key.
+ */
+ public function get(string $key, ?string $format = null): array
+ {
+ // If the message exists in the message bag, we will transform it and return
+ // the message. Otherwise, we will check if the key is implicit & collect
+ // all the messages that match the given key and output it as an array.
+ if (array_key_exists($key, $this->messages)) {
+ return $this->transform(
+ $this->messages[$key],
+ $this->checkFormat($format),
+ $key
+ );
+ }
+
+ if (Str::contains($key, '*')) {
+ return $this->getMessagesForWildcardKey($key, $format);
+ }
+
+ return [];
+ }
+
+ /**
+ * Get all of the messages for every key in the message bag.
+ */
+ public function all(?string $format = null): array
+ {
+ $format = $this->checkFormat($format);
+
+ $all = [];
+
+ foreach ($this->messages as $key => $messages) {
+ $all = array_merge($all, $this->transform($messages, $format, $key));
+ }
+
+ return $all;
+ }
+
+ /**
+ * Get all of the unique messages for every key in the message bag.
+ */
+ public function unique(?string $format = null): array
+ {
+ return array_unique($this->all($format));
+ }
+
+ /**
+ * Get the raw messages in the message bag.
+ */
+ public function messages(): array
+ {
+ return $this->messages;
+ }
+
+ /**
+ * Get the raw messages in the message bag.
+ */
+ public function getMessages(): array
+ {
+ return $this->messages();
+ }
+
+ /**
+ * Get the messages for the instance.
+ */
+ public function getMessageBag(): MessageBagContract
+ {
+ return $this;
+ }
+
+ /**
+ * Get the default message format.
+ */
+ public function getFormat(): string
+ {
+ return $this->format;
+ }
+
+ /**
+ * Set the default message format.
+ */
+ public function setFormat(string $format = ':message'): self
+ {
+ $this->format = $format;
+
+ return $this;
+ }
+
+ /**
+ * Determine if the message bag has any messages.
+ */
+ public function isEmpty(): bool
+ {
+ return ! $this->any();
+ }
+
+ /**
+ * Determine if the message bag has any messages.
+ */
+ public function isNotEmpty(): bool
+ {
+ return $this->any();
+ }
+
+ /**
+ * Determine if the message bag has any messages.
+ */
+ public function any(): bool
+ {
+ return $this->count() > 0;
+ }
+
+ /**
+ * Get the number of messages in the message bag.
+ */
+ public function count(): int
+ {
+ return count($this->messages, COUNT_RECURSIVE) - count($this->messages);
+ }
+
+ /**
+ * Get the instance as an array.
+ */
+ public function toArray(): array
+ {
+ return $this->getMessages();
+ }
+
+ /**
+ * Convert the object into something JSON serializable.
+ */
+ public function jsonSerialize(): array
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * Convert the object to its JSON representation.
+ */
+ public function toJson(int $options = 0): string
+ {
+ return json_encode($this->jsonSerialize(), $options);
+ }
+
+ /**
+ * Determine if a key and message combination already exists.
+ */
+ protected function isUnique(string $key, string $message): bool
+ {
+ $messages = (array) $this->messages;
+
+ return ! isset($messages[$key]) || ! in_array($message, $messages[$key]);
+ }
+
+ /**
+ * Get the messages for a wildcard key.
+ */
+ protected function getMessagesForWildcardKey(string $key, ?string $format): array
+ {
+ return collect($this->messages)
+ ->filter(function ($messages, $messageKey) use ($key) {
+ return Str::is($key, $messageKey);
+ })
+ ->map(function ($messages, $messageKey) use ($format) {
+ return $this->transform(
+ $messages,
+ $this->checkFormat($format),
+ $messageKey
+ );
+ })->all();
+ }
+
+ /**
+ * Format an array of messages.
+ */
+ protected function transform(array $messages, string $format, string $messageKey): array
+ {
+ return collect($messages)
+ ->map(function ($message) use ($format, $messageKey) {
+ // We will simply spin through the given messages and transform each one
+ // replacing the :message place holder with the real message allowing
+ // the messages to be easily formatted to each developer's desires.
+ return str_replace([':message', ':key'], [$message, $messageKey], $format);
+ })->all();
+ }
+
+ /**
+ * Get the appropriate format based on the given format.
+ */
+ protected function checkFormat(?string $format): string
+ {
+ return $format ?: $this->format;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/MimeTypeExtensionGuesser.php b/addons/epay/library/hyperf/utils/src/MimeTypeExtensionGuesser.php
new file mode 100644
index 0000000..978c48e
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/MimeTypeExtensionGuesser.php
@@ -0,0 +1,814 @@
+ 'ez',
+ 'application/applixware' => 'aw',
+ 'application/atom+xml' => 'atom',
+ 'application/atomcat+xml' => 'atomcat',
+ 'application/atomsvc+xml' => 'atomsvc',
+ 'application/ccxml+xml' => 'ccxml',
+ 'application/cdmi-capability' => 'cdmia',
+ 'application/cdmi-container' => 'cdmic',
+ 'application/cdmi-domain' => 'cdmid',
+ 'application/cdmi-object' => 'cdmio',
+ 'application/cdmi-queue' => 'cdmiq',
+ 'application/cu-seeme' => 'cu',
+ 'application/davmount+xml' => 'davmount',
+ 'application/docbook+xml' => 'dbk',
+ 'application/dssc+der' => 'dssc',
+ 'application/dssc+xml' => 'xdssc',
+ 'application/ecmascript' => 'ecma',
+ 'application/emma+xml' => 'emma',
+ 'application/epub+zip' => 'epub',
+ 'application/exi' => 'exi',
+ 'application/font-tdpfr' => 'pfr',
+ 'application/gml+xml' => 'gml',
+ 'application/gpx+xml' => 'gpx',
+ 'application/gxf' => 'gxf',
+ 'application/hyperstudio' => 'stk',
+ 'application/inkml+xml' => 'ink',
+ 'application/ipfix' => 'ipfix',
+ 'application/java-archive' => 'jar',
+ 'application/java-serialized-object' => 'ser',
+ 'application/java-vm' => 'class',
+ 'application/javascript' => 'js',
+ 'application/json' => 'json',
+ 'application/jsonml+json' => 'jsonml',
+ 'application/lost+xml' => 'lostxml',
+ 'application/mac-binhex40' => 'hqx',
+ 'application/mac-compactpro' => 'cpt',
+ 'application/mads+xml' => 'mads',
+ 'application/marc' => 'mrc',
+ 'application/marcxml+xml' => 'mrcx',
+ 'application/mathematica' => 'ma',
+ 'application/mathml+xml' => 'mathml',
+ 'application/mbox' => 'mbox',
+ 'application/mediaservercontrol+xml' => 'mscml',
+ 'application/metalink+xml' => 'metalink',
+ 'application/metalink4+xml' => 'meta4',
+ 'application/mets+xml' => 'mets',
+ 'application/mods+xml' => 'mods',
+ 'application/mp21' => 'm21',
+ 'application/mp4' => 'mp4s',
+ 'application/msword' => 'doc',
+ 'application/mxf' => 'mxf',
+ 'application/octet-stream' => 'bin',
+ 'application/oda' => 'oda',
+ 'application/oebps-package+xml' => 'opf',
+ 'application/ogg' => 'ogx',
+ 'application/omdoc+xml' => 'omdoc',
+ 'application/onenote' => 'onetoc',
+ 'application/oxps' => 'oxps',
+ 'application/patch-ops-error+xml' => 'xer',
+ 'application/pdf' => 'pdf',
+ 'application/pgp-encrypted' => 'pgp',
+ 'application/pgp-signature' => 'asc',
+ 'application/pics-rules' => 'prf',
+ 'application/pkcs10' => 'p10',
+ 'application/pkcs7-mime' => 'p7m',
+ 'application/pkcs7-signature' => 'p7s',
+ 'application/pkcs8' => 'p8',
+ 'application/pkix-attr-cert' => 'ac',
+ 'application/pkix-cert' => 'cer',
+ 'application/pkix-crl' => 'crl',
+ 'application/pkix-pkipath' => 'pkipath',
+ 'application/pkixcmp' => 'pki',
+ 'application/pls+xml' => 'pls',
+ 'application/postscript' => 'ai',
+ 'application/prs.cww' => 'cww',
+ 'application/pskc+xml' => 'pskcxml',
+ 'application/rdf+xml' => 'rdf',
+ 'application/reginfo+xml' => 'rif',
+ 'application/relax-ng-compact-syntax' => 'rnc',
+ 'application/resource-lists+xml' => 'rl',
+ 'application/resource-lists-diff+xml' => 'rld',
+ 'application/rls-services+xml' => 'rs',
+ 'application/rpki-ghostbusters' => 'gbr',
+ 'application/rpki-manifest' => 'mft',
+ 'application/rpki-roa' => 'roa',
+ 'application/rsd+xml' => 'rsd',
+ 'application/rss+xml' => 'rss',
+ 'application/rtf' => 'rtf',
+ 'application/sbml+xml' => 'sbml',
+ 'application/scvp-cv-request' => 'scq',
+ 'application/scvp-cv-response' => 'scs',
+ 'application/scvp-vp-request' => 'spq',
+ 'application/scvp-vp-response' => 'spp',
+ 'application/sdp' => 'sdp',
+ 'application/set-payment-initiation' => 'setpay',
+ 'application/set-registration-initiation' => 'setreg',
+ 'application/shf+xml' => 'shf',
+ 'application/smil+xml' => 'smi',
+ 'application/sparql-query' => 'rq',
+ 'application/sparql-results+xml' => 'srx',
+ 'application/srgs' => 'gram',
+ 'application/srgs+xml' => 'grxml',
+ 'application/sru+xml' => 'sru',
+ 'application/ssdl+xml' => 'ssdl',
+ 'application/ssml+xml' => 'ssml',
+ 'application/tei+xml' => 'tei',
+ 'application/thraud+xml' => 'tfi',
+ 'application/timestamped-data' => 'tsd',
+ 'application/vnd.3gpp.pic-bw-large' => 'plb',
+ 'application/vnd.3gpp.pic-bw-small' => 'psb',
+ 'application/vnd.3gpp.pic-bw-var' => 'pvb',
+ 'application/vnd.3gpp2.tcap' => 'tcap',
+ 'application/vnd.3m.post-it-notes' => 'pwn',
+ 'application/vnd.accpac.simply.aso' => 'aso',
+ 'application/vnd.accpac.simply.imp' => 'imp',
+ 'application/vnd.acucobol' => 'acu',
+ 'application/vnd.acucorp' => 'atc',
+ 'application/vnd.adobe.air-application-installer-package+zip' => 'air',
+ 'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
+ 'application/vnd.adobe.fxp' => 'fxp',
+ 'application/vnd.adobe.xdp+xml' => 'xdp',
+ 'application/vnd.adobe.xfdf' => 'xfdf',
+ 'application/vnd.ahead.space' => 'ahead',
+ 'application/vnd.airzip.filesecure.azf' => 'azf',
+ 'application/vnd.airzip.filesecure.azs' => 'azs',
+ 'application/vnd.amazon.ebook' => 'azw',
+ 'application/vnd.americandynamics.acc' => 'acc',
+ 'application/vnd.amiga.ami' => 'ami',
+ 'application/vnd.android.package-archive' => 'apk',
+ 'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
+ 'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
+ 'application/vnd.antix.game-component' => 'atx',
+ 'application/vnd.apple.installer+xml' => 'mpkg',
+ 'application/vnd.apple.mpegurl' => 'm3u8',
+ 'application/vnd.aristanetworks.swi' => 'swi',
+ 'application/vnd.astraea-software.iota' => 'iota',
+ 'application/vnd.audiograph' => 'aep',
+ 'application/vnd.blueice.multipass' => 'mpm',
+ 'application/vnd.bmi' => 'bmi',
+ 'application/vnd.businessobjects' => 'rep',
+ 'application/vnd.chemdraw+xml' => 'cdxml',
+ 'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
+ 'application/vnd.cinderella' => 'cdy',
+ 'application/vnd.claymore' => 'cla',
+ 'application/vnd.cloanto.rp9' => 'rp9',
+ 'application/vnd.clonk.c4group' => 'c4g',
+ 'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
+ 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
+ 'application/vnd.commonspace' => 'csp',
+ 'application/vnd.contact.cmsg' => 'cdbcmsg',
+ 'application/vnd.cosmocaller' => 'cmc',
+ 'application/vnd.crick.clicker' => 'clkx',
+ 'application/vnd.crick.clicker.keyboard' => 'clkk',
+ 'application/vnd.crick.clicker.palette' => 'clkp',
+ 'application/vnd.crick.clicker.template' => 'clkt',
+ 'application/vnd.crick.clicker.wordbank' => 'clkw',
+ 'application/vnd.criticaltools.wbs+xml' => 'wbs',
+ 'application/vnd.ctc-posml' => 'pml',
+ 'application/vnd.cups-ppd' => 'ppd',
+ 'application/vnd.curl.car' => 'car',
+ 'application/vnd.curl.pcurl' => 'pcurl',
+ 'application/vnd.dart' => 'dart',
+ 'application/vnd.data-vision.rdz' => 'rdz',
+ 'application/vnd.dece.data' => 'uvf',
+ 'application/vnd.dece.ttml+xml' => 'uvt',
+ 'application/vnd.dece.unspecified' => 'uvx',
+ 'application/vnd.dece.zip' => 'uvz',
+ 'application/vnd.denovo.fcselayout-link' => 'fe_launch',
+ 'application/vnd.dna' => 'dna',
+ 'application/vnd.dolby.mlp' => 'mlp',
+ 'application/vnd.dpgraph' => 'dpg',
+ 'application/vnd.dreamfactory' => 'dfac',
+ 'application/vnd.ds-keypoint' => 'kpxx',
+ 'application/vnd.dvb.ait' => 'ait',
+ 'application/vnd.dvb.service' => 'svc',
+ 'application/vnd.dynageo' => 'geo',
+ 'application/vnd.ecowin.chart' => 'mag',
+ 'application/vnd.enliven' => 'nml',
+ 'application/vnd.epson.esf' => 'esf',
+ 'application/vnd.epson.msf' => 'msf',
+ 'application/vnd.epson.quickanime' => 'qam',
+ 'application/vnd.epson.salt' => 'slt',
+ 'application/vnd.epson.ssf' => 'ssf',
+ 'application/vnd.eszigno3+xml' => 'es3',
+ 'application/vnd.ezpix-album' => 'ez2',
+ 'application/vnd.ezpix-package' => 'ez3',
+ 'application/vnd.fdf' => 'fdf',
+ 'application/vnd.fdsn.mseed' => 'mseed',
+ 'application/vnd.fdsn.seed' => 'seed',
+ 'application/vnd.flographit' => 'gph',
+ 'application/vnd.fluxtime.clip' => 'ftc',
+ 'application/vnd.framemaker' => 'fm',
+ 'application/vnd.frogans.fnc' => 'fnc',
+ 'application/vnd.frogans.ltf' => 'ltf',
+ 'application/vnd.fsc.weblaunch' => 'fsc',
+ 'application/vnd.fujitsu.oasys' => 'oas',
+ 'application/vnd.fujitsu.oasys2' => 'oa2',
+ 'application/vnd.fujitsu.oasys3' => 'oa3',
+ 'application/vnd.fujitsu.oasysgp' => 'fg5',
+ 'application/vnd.fujitsu.oasysprs' => 'bh2',
+ 'application/vnd.fujixerox.ddd' => 'ddd',
+ 'application/vnd.fujixerox.docuworks' => 'xdw',
+ 'application/vnd.fujixerox.docuworks.binder' => 'xbd',
+ 'application/vnd.fuzzysheet' => 'fzs',
+ 'application/vnd.genomatix.tuxedo' => 'txd',
+ 'application/vnd.geogebra.file' => 'ggb',
+ 'application/vnd.geogebra.tool' => 'ggt',
+ 'application/vnd.geometry-explorer' => 'gex',
+ 'application/vnd.geonext' => 'gxt',
+ 'application/vnd.geoplan' => 'g2w',
+ 'application/vnd.geospace' => 'g3w',
+ 'application/vnd.gmx' => 'gmx',
+ 'application/vnd.google-earth.kml+xml' => 'kml',
+ 'application/vnd.google-earth.kmz' => 'kmz',
+ 'application/vnd.grafeq' => 'gqf',
+ 'application/vnd.groove-account' => 'gac',
+ 'application/vnd.groove-help' => 'ghf',
+ 'application/vnd.groove-identity-message' => 'gim',
+ 'application/vnd.groove-injector' => 'grv',
+ 'application/vnd.groove-tool-message' => 'gtm',
+ 'application/vnd.groove-tool-template' => 'tpl',
+ 'application/vnd.groove-vcard' => 'vcg',
+ 'application/vnd.hal+xml' => 'hal',
+ 'application/vnd.handheld-entertainment+xml' => 'zmm',
+ 'application/vnd.hbci' => 'hbci',
+ 'application/vnd.hhe.lesson-player' => 'les',
+ 'application/vnd.hp-hpgl' => 'hpgl',
+ 'application/vnd.hp-hpid' => 'hpid',
+ 'application/vnd.hp-hps' => 'hps',
+ 'application/vnd.hp-jlyt' => 'jlt',
+ 'application/vnd.hp-pcl' => 'pcl',
+ 'application/vnd.hp-pclxl' => 'pclxl',
+ 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
+ 'application/vnd.ibm.minipay' => 'mpy',
+ 'application/vnd.ibm.modcap' => 'afp',
+ 'application/vnd.ibm.rights-management' => 'irm',
+ 'application/vnd.ibm.secure-container' => 'sc',
+ 'application/vnd.iccprofile' => 'icc',
+ 'application/vnd.igloader' => 'igl',
+ 'application/vnd.immervision-ivp' => 'ivp',
+ 'application/vnd.immervision-ivu' => 'ivu',
+ 'application/vnd.insors.igm' => 'igm',
+ 'application/vnd.intercon.formnet' => 'xpw',
+ 'application/vnd.intergeo' => 'i2g',
+ 'application/vnd.intu.qbo' => 'qbo',
+ 'application/vnd.intu.qfx' => 'qfx',
+ 'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
+ 'application/vnd.irepository.package+xml' => 'irp',
+ 'application/vnd.is-xpr' => 'xpr',
+ 'application/vnd.isac.fcs' => 'fcs',
+ 'application/vnd.jam' => 'jam',
+ 'application/vnd.jcp.javame.midlet-rms' => 'rms',
+ 'application/vnd.jisp' => 'jisp',
+ 'application/vnd.joost.joda-archive' => 'joda',
+ 'application/vnd.kahootz' => 'ktz',
+ 'application/vnd.kde.karbon' => 'karbon',
+ 'application/vnd.kde.kchart' => 'chrt',
+ 'application/vnd.kde.kformula' => 'kfo',
+ 'application/vnd.kde.kivio' => 'flw',
+ 'application/vnd.kde.kontour' => 'kon',
+ 'application/vnd.kde.kpresenter' => 'kpr',
+ 'application/vnd.kde.kspread' => 'ksp',
+ 'application/vnd.kde.kword' => 'kwd',
+ 'application/vnd.kenameaapp' => 'htke',
+ 'application/vnd.kidspiration' => 'kia',
+ 'application/vnd.kinar' => 'kne',
+ 'application/vnd.koan' => 'skp',
+ 'application/vnd.kodak-descriptor' => 'sse',
+ 'application/vnd.las.las+xml' => 'lasxml',
+ 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
+ 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
+ 'application/vnd.lotus-1-2-3' => '123',
+ 'application/vnd.lotus-approach' => 'apr',
+ 'application/vnd.lotus-freelance' => 'pre',
+ 'application/vnd.lotus-notes' => 'nsf',
+ 'application/vnd.lotus-organizer' => 'org',
+ 'application/vnd.lotus-screencam' => 'scm',
+ 'application/vnd.lotus-wordpro' => 'lwp',
+ 'application/vnd.macports.portpkg' => 'portpkg',
+ 'application/vnd.mcd' => 'mcd',
+ 'application/vnd.medcalcdata' => 'mc1',
+ 'application/vnd.mediastation.cdkey' => 'cdkey',
+ 'application/vnd.mfer' => 'mwf',
+ 'application/vnd.mfmp' => 'mfm',
+ 'application/vnd.micrografx.flo' => 'flo',
+ 'application/vnd.micrografx.igx' => 'igx',
+ 'application/vnd.mif' => 'mif',
+ 'application/vnd.mobius.daf' => 'daf',
+ 'application/vnd.mobius.dis' => 'dis',
+ 'application/vnd.mobius.mbk' => 'mbk',
+ 'application/vnd.mobius.mqy' => 'mqy',
+ 'application/vnd.mobius.msl' => 'msl',
+ 'application/vnd.mobius.plc' => 'plc',
+ 'application/vnd.mobius.txf' => 'txf',
+ 'application/vnd.mophun.application' => 'mpn',
+ 'application/vnd.mophun.certificate' => 'mpc',
+ 'application/vnd.mozilla.xul+xml' => 'xul',
+ 'application/vnd.ms-artgalry' => 'cil',
+ 'application/vnd.ms-cab-compressed' => 'cab',
+ 'application/vnd.ms-excel' => 'xls',
+ 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
+ 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
+ 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
+ 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
+ 'application/vnd.ms-fontobject' => 'eot',
+ 'application/vnd.ms-htmlhelp' => 'chm',
+ 'application/vnd.ms-ims' => 'ims',
+ 'application/vnd.ms-lrm' => 'lrm',
+ 'application/vnd.ms-officetheme' => 'thmx',
+ 'application/vnd.ms-pki.seccat' => 'cat',
+ 'application/vnd.ms-pki.stl' => 'stl',
+ 'application/vnd.ms-powerpoint' => 'ppt',
+ 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
+ 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
+ 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
+ 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
+ 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
+ 'application/vnd.ms-project' => 'mpp',
+ 'application/vnd.ms-word.document.macroenabled.12' => 'docm',
+ 'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
+ 'application/vnd.ms-works' => 'wps',
+ 'application/vnd.ms-wpl' => 'wpl',
+ 'application/vnd.ms-xpsdocument' => 'xps',
+ 'application/vnd.mseq' => 'mseq',
+ 'application/vnd.musician' => 'mus',
+ 'application/vnd.muvee.style' => 'msty',
+ 'application/vnd.mynfc' => 'taglet',
+ 'application/vnd.neurolanguage.nlu' => 'nlu',
+ 'application/vnd.nitf' => 'ntf',
+ 'application/vnd.noblenet-directory' => 'nnd',
+ 'application/vnd.noblenet-sealer' => 'nns',
+ 'application/vnd.noblenet-web' => 'nnw',
+ 'application/vnd.nokia.n-gage.data' => 'ngdat',
+ 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
+ 'application/vnd.nokia.radio-preset' => 'rpst',
+ 'application/vnd.nokia.radio-presets' => 'rpss',
+ 'application/vnd.novadigm.edm' => 'edm',
+ 'application/vnd.novadigm.edx' => 'edx',
+ 'application/vnd.novadigm.ext' => 'ext',
+ 'application/vnd.oasis.opendocument.chart' => 'odc',
+ 'application/vnd.oasis.opendocument.chart-template' => 'otc',
+ 'application/vnd.oasis.opendocument.database' => 'odb',
+ 'application/vnd.oasis.opendocument.formula' => 'odf',
+ 'application/vnd.oasis.opendocument.formula-template' => 'odft',
+ 'application/vnd.oasis.opendocument.graphics' => 'odg',
+ 'application/vnd.oasis.opendocument.graphics-template' => 'otg',
+ 'application/vnd.oasis.opendocument.image' => 'odi',
+ 'application/vnd.oasis.opendocument.image-template' => 'oti',
+ 'application/vnd.oasis.opendocument.presentation' => 'odp',
+ 'application/vnd.oasis.opendocument.presentation-template' => 'otp',
+ 'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
+ 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
+ 'application/vnd.oasis.opendocument.text' => 'odt',
+ 'application/vnd.oasis.opendocument.text-master' => 'odm',
+ 'application/vnd.oasis.opendocument.text-template' => 'ott',
+ 'application/vnd.oasis.opendocument.text-web' => 'oth',
+ 'application/vnd.olpc-sugar' => 'xo',
+ 'application/vnd.oma.dd2+xml' => 'dd2',
+ 'application/vnd.openofficeorg.extension' => 'oxt',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
+ 'application/vnd.osgeo.mapguide.package' => 'mgp',
+ 'application/vnd.osgi.dp' => 'dp',
+ 'application/vnd.osgi.subsystem' => 'esa',
+ 'application/vnd.palm' => 'pdb',
+ 'application/vnd.pawaafile' => 'paw',
+ 'application/vnd.pg.format' => 'str',
+ 'application/vnd.pg.osasli' => 'ei6',
+ 'application/vnd.picsel' => 'efif',
+ 'application/vnd.pmi.widget' => 'wg',
+ 'application/vnd.pocketlearn' => 'plf',
+ 'application/vnd.powerbuilder6' => 'pbd',
+ 'application/vnd.previewsystems.box' => 'box',
+ 'application/vnd.proteus.magazine' => 'mgz',
+ 'application/vnd.publishare-delta-tree' => 'qps',
+ 'application/vnd.pvi.ptid1' => 'ptid',
+ 'application/vnd.quark.quarkxpress' => 'qxd',
+ 'application/vnd.realvnc.bed' => 'bed',
+ 'application/vnd.recordare.musicxml' => 'mxl',
+ 'application/vnd.recordare.musicxml+xml' => 'musicxml',
+ 'application/vnd.rig.cryptonote' => 'cryptonote',
+ 'application/vnd.rim.cod' => 'cod',
+ 'application/vnd.rn-realmedia' => 'rm',
+ 'application/vnd.rn-realmedia-vbr' => 'rmvb',
+ 'application/vnd.route66.link66+xml' => 'link66',
+ 'application/vnd.sailingtracker.track' => 'st',
+ 'application/vnd.seemail' => 'see',
+ 'application/vnd.sema' => 'sema',
+ 'application/vnd.semd' => 'semd',
+ 'application/vnd.semf' => 'semf',
+ 'application/vnd.shana.informed.formdata' => 'ifm',
+ 'application/vnd.shana.informed.formtemplate' => 'itp',
+ 'application/vnd.shana.informed.interchange' => 'iif',
+ 'application/vnd.shana.informed.package' => 'ipk',
+ 'application/vnd.simtech-mindmapper' => 'twd',
+ 'application/vnd.smaf' => 'mmf',
+ 'application/vnd.smart.teacher' => 'teacher',
+ 'application/vnd.solent.sdkm+xml' => 'sdkm',
+ 'application/vnd.spotfire.dxp' => 'dxp',
+ 'application/vnd.spotfire.sfs' => 'sfs',
+ 'application/vnd.stardivision.calc' => 'sdc',
+ 'application/vnd.stardivision.draw' => 'sda',
+ 'application/vnd.stardivision.impress' => 'sdd',
+ 'application/vnd.stardivision.math' => 'smf',
+ 'application/vnd.stardivision.writer' => 'sdw',
+ 'application/vnd.stardivision.writer-global' => 'sgl',
+ 'application/vnd.stepmania.package' => 'smzip',
+ 'application/vnd.stepmania.stepchart' => 'sm',
+ 'application/vnd.sun.xml.calc' => 'sxc',
+ 'application/vnd.sun.xml.calc.template' => 'stc',
+ 'application/vnd.sun.xml.draw' => 'sxd',
+ 'application/vnd.sun.xml.draw.template' => 'std',
+ 'application/vnd.sun.xml.impress' => 'sxi',
+ 'application/vnd.sun.xml.impress.template' => 'sti',
+ 'application/vnd.sun.xml.math' => 'sxm',
+ 'application/vnd.sun.xml.writer' => 'sxw',
+ 'application/vnd.sun.xml.writer.global' => 'sxg',
+ 'application/vnd.sun.xml.writer.template' => 'stw',
+ 'application/vnd.sus-calendar' => 'sus',
+ 'application/vnd.svd' => 'svd',
+ 'application/vnd.symbian.install' => 'sis',
+ 'application/vnd.syncml+xml' => 'xsm',
+ 'application/vnd.syncml.dm+wbxml' => 'bdm',
+ 'application/vnd.syncml.dm+xml' => 'xdm',
+ 'application/vnd.tao.intent-module-archive' => 'tao',
+ 'application/vnd.tcpdump.pcap' => 'pcap',
+ 'application/vnd.tmobile-livetv' => 'tmo',
+ 'application/vnd.trid.tpt' => 'tpt',
+ 'application/vnd.triscape.mxs' => 'mxs',
+ 'application/vnd.trueapp' => 'tra',
+ 'application/vnd.ufdl' => 'ufd',
+ 'application/vnd.uiq.theme' => 'utz',
+ 'application/vnd.umajin' => 'umj',
+ 'application/vnd.unity' => 'unityweb',
+ 'application/vnd.uoml+xml' => 'uoml',
+ 'application/vnd.vcx' => 'vcx',
+ 'application/vnd.visio' => 'vsd',
+ 'application/vnd.visionary' => 'vis',
+ 'application/vnd.vsf' => 'vsf',
+ 'application/vnd.wap.wbxml' => 'wbxml',
+ 'application/vnd.wap.wmlc' => 'wmlc',
+ 'application/vnd.wap.wmlscriptc' => 'wmlsc',
+ 'application/vnd.webturbo' => 'wtb',
+ 'application/vnd.wolfram.player' => 'nbp',
+ 'application/vnd.wordperfect' => 'wpd',
+ 'application/vnd.wqd' => 'wqd',
+ 'application/vnd.wt.stf' => 'stf',
+ 'application/vnd.xara' => 'xar',
+ 'application/vnd.xfdl' => 'xfdl',
+ 'application/vnd.yamaha.hv-dic' => 'hvd',
+ 'application/vnd.yamaha.hv-script' => 'hvs',
+ 'application/vnd.yamaha.hv-voice' => 'hvp',
+ 'application/vnd.yamaha.openscoreformat' => 'osf',
+ 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
+ 'application/vnd.yamaha.smaf-audio' => 'saf',
+ 'application/vnd.yamaha.smaf-phrase' => 'spf',
+ 'application/vnd.yellowriver-custom-menu' => 'cmp',
+ 'application/vnd.zul' => 'zir',
+ 'application/vnd.zzazz.deck+xml' => 'zaz',
+ 'application/voicexml+xml' => 'vxml',
+ 'application/widget' => 'wgt',
+ 'application/winhlp' => 'hlp',
+ 'application/wsdl+xml' => 'wsdl',
+ 'application/wspolicy+xml' => 'wspolicy',
+ 'application/x-7z-compressed' => '7z',
+ 'application/x-abiword' => 'abw',
+ 'application/x-ace-compressed' => 'ace',
+ 'application/x-apple-diskimage' => 'dmg',
+ 'application/x-authorware-bin' => 'aab',
+ 'application/x-authorware-map' => 'aam',
+ 'application/x-authorware-seg' => 'aas',
+ 'application/x-bcpio' => 'bcpio',
+ 'application/x-bittorrent' => 'torrent',
+ 'application/x-blorb' => 'blb',
+ 'application/x-bzip' => 'bz',
+ 'application/x-bzip2' => 'bz2',
+ 'application/x-cbr' => 'cbr',
+ 'application/x-cdlink' => 'vcd',
+ 'application/x-cfs-compressed' => 'cfs',
+ 'application/x-chat' => 'chat',
+ 'application/x-chess-pgn' => 'pgn',
+ 'application/x-conference' => 'nsc',
+ 'application/x-cpio' => 'cpio',
+ 'application/x-csh' => 'csh',
+ 'application/x-debian-package' => 'deb',
+ 'application/x-dgc-compressed' => 'dgc',
+ 'application/x-director' => 'dir',
+ 'application/x-doom' => 'wad',
+ 'application/x-dtbncx+xml' => 'ncx',
+ 'application/x-dtbook+xml' => 'dtb',
+ 'application/x-dtbresource+xml' => 'res',
+ 'application/x-dvi' => 'dvi',
+ 'application/x-envoy' => 'evy',
+ 'application/x-eva' => 'eva',
+ 'application/x-font-bdf' => 'bdf',
+ 'application/x-font-ghostscript' => 'gsf',
+ 'application/x-font-linux-psf' => 'psf',
+ 'application/x-font-otf' => 'otf',
+ 'application/x-font-pcf' => 'pcf',
+ 'application/x-font-snf' => 'snf',
+ 'application/x-font-ttf' => 'ttf',
+ 'application/x-font-type1' => 'pfa',
+ 'application/x-font-woff' => 'woff',
+ 'application/x-freearc' => 'arc',
+ 'application/x-futuresplash' => 'spl',
+ 'application/x-gca-compressed' => 'gca',
+ 'application/x-glulx' => 'ulx',
+ 'application/x-gnumeric' => 'gnumeric',
+ 'application/x-gramps-xml' => 'gramps',
+ 'application/x-gtar' => 'gtar',
+ 'application/x-hdf' => 'hdf',
+ 'application/x-install-instructions' => 'install',
+ 'application/x-iso9660-image' => 'iso',
+ 'application/x-java-jnlp-file' => 'jnlp',
+ 'application/x-latex' => 'latex',
+ 'application/x-lzh-compressed' => 'lzh',
+ 'application/x-mie' => 'mie',
+ 'application/x-mobipocket-ebook' => 'prc',
+ 'application/x-ms-application' => 'application',
+ 'application/x-ms-shortcut' => 'lnk',
+ 'application/x-ms-wmd' => 'wmd',
+ 'application/x-ms-wmz' => 'wmz',
+ 'application/x-ms-xbap' => 'xbap',
+ 'application/x-msaccess' => 'mdb',
+ 'application/x-msbinder' => 'obd',
+ 'application/x-mscardfile' => 'crd',
+ 'application/x-msclip' => 'clp',
+ 'application/x-msdownload' => 'exe',
+ 'application/x-msmediaview' => 'mvb',
+ 'application/x-msmetafile' => 'wmf',
+ 'application/x-msmoney' => 'mny',
+ 'application/x-mspublisher' => 'pub',
+ 'application/x-msschedule' => 'scd',
+ 'application/x-msterminal' => 'trm',
+ 'application/x-mswrite' => 'wri',
+ 'application/x-netcdf' => 'nc',
+ 'application/x-nzb' => 'nzb',
+ 'application/x-pkcs12' => 'p12',
+ 'application/x-pkcs7-certificates' => 'p7b',
+ 'application/x-pkcs7-certreqresp' => 'p7r',
+ 'application/x-rar-compressed' => 'rar',
+ 'application/x-rar' => 'rar',
+ 'application/x-research-info-systems' => 'ris',
+ 'application/x-sh' => 'sh',
+ 'application/x-shar' => 'shar',
+ 'application/x-shockwave-flash' => 'swf',
+ 'application/x-silverlight-app' => 'xap',
+ 'application/x-sql' => 'sql',
+ 'application/x-stuffit' => 'sit',
+ 'application/x-stuffitx' => 'sitx',
+ 'application/x-subrip' => 'srt',
+ 'application/x-sv4cpio' => 'sv4cpio',
+ 'application/x-sv4crc' => 'sv4crc',
+ 'application/x-t3vm-image' => 't3',
+ 'application/x-tads' => 'gam',
+ 'application/x-tar' => 'tar',
+ 'application/x-tcl' => 'tcl',
+ 'application/x-tex' => 'tex',
+ 'application/x-tex-tfm' => 'tfm',
+ 'application/x-texinfo' => 'texinfo',
+ 'application/x-tgif' => 'obj',
+ 'application/x-ustar' => 'ustar',
+ 'application/x-wais-source' => 'src',
+ 'application/x-x509-ca-cert' => 'der',
+ 'application/x-xfig' => 'fig',
+ 'application/x-xliff+xml' => 'xlf',
+ 'application/x-xpinstall' => 'xpi',
+ 'application/x-xz' => 'xz',
+ 'application/x-zip-compressed' => 'zip',
+ 'application/x-zmachine' => 'z1',
+ 'application/xaml+xml' => 'xaml',
+ 'application/xcap-diff+xml' => 'xdf',
+ 'application/xenc+xml' => 'xenc',
+ 'application/xhtml+xml' => 'xhtml',
+ 'application/xml' => 'xml',
+ 'application/xml-dtd' => 'dtd',
+ 'application/xop+xml' => 'xop',
+ 'application/xproc+xml' => 'xpl',
+ 'application/xslt+xml' => 'xslt',
+ 'application/xspf+xml' => 'xspf',
+ 'application/xv+xml' => 'mxml',
+ 'application/yang' => 'yang',
+ 'application/yin+xml' => 'yin',
+ 'application/zip' => 'zip',
+ 'audio/adpcm' => 'adp',
+ 'audio/basic' => 'au',
+ 'audio/midi' => 'mid',
+ 'audio/mp4' => 'mp4a',
+ 'audio/mpeg' => 'mpga',
+ 'audio/ogg' => 'oga',
+ 'audio/s3m' => 's3m',
+ 'audio/silk' => 'sil',
+ 'audio/vnd.dece.audio' => 'uva',
+ 'audio/vnd.digital-winds' => 'eol',
+ 'audio/vnd.dra' => 'dra',
+ 'audio/vnd.dts' => 'dts',
+ 'audio/vnd.dts.hd' => 'dtshd',
+ 'audio/vnd.lucent.voice' => 'lvp',
+ 'audio/vnd.ms-playready.media.pya' => 'pya',
+ 'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
+ 'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
+ 'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
+ 'audio/vnd.rip' => 'rip',
+ 'audio/webm' => 'weba',
+ 'audio/x-aac' => 'aac',
+ 'audio/x-aiff' => 'aif',
+ 'audio/x-caf' => 'caf',
+ 'audio/x-flac' => 'flac',
+ 'audio/x-matroska' => 'mka',
+ 'audio/x-mpegurl' => 'm3u',
+ 'audio/x-ms-wax' => 'wax',
+ 'audio/x-ms-wma' => 'wma',
+ 'audio/x-pn-realaudio' => 'ram',
+ 'audio/x-pn-realaudio-plugin' => 'rmp',
+ 'audio/x-wav' => 'wav',
+ 'audio/xm' => 'xm',
+ 'chemical/x-cdx' => 'cdx',
+ 'chemical/x-cif' => 'cif',
+ 'chemical/x-cmdf' => 'cmdf',
+ 'chemical/x-cml' => 'cml',
+ 'chemical/x-csml' => 'csml',
+ 'chemical/x-xyz' => 'xyz',
+ 'image/bmp' => 'bmp',
+ 'image/x-ms-bmp' => 'bmp',
+ 'image/cgm' => 'cgm',
+ 'image/g3fax' => 'g3',
+ 'image/gif' => 'gif',
+ 'image/ief' => 'ief',
+ 'image/jpeg' => 'jpg',
+ 'image/pjpeg' => 'jpeg',
+ 'image/ktx' => 'ktx',
+ 'image/png' => 'png',
+ 'image/prs.btif' => 'btif',
+ 'image/sgi' => 'sgi',
+ 'image/svg+xml' => 'svg',
+ 'image/tiff' => 'tiff',
+ 'image/vnd.adobe.photoshop' => 'psd',
+ 'image/vnd.dece.graphic' => 'uvi',
+ 'image/vnd.dvb.subtitle' => 'sub',
+ 'image/vnd.djvu' => 'djvu',
+ 'image/vnd.dwg' => 'dwg',
+ 'image/vnd.dxf' => 'dxf',
+ 'image/vnd.fastbidsheet' => 'fbs',
+ 'image/vnd.fpx' => 'fpx',
+ 'image/vnd.fst' => 'fst',
+ 'image/vnd.fujixerox.edmics-mmr' => 'mmr',
+ 'image/vnd.fujixerox.edmics-rlc' => 'rlc',
+ 'image/vnd.ms-modi' => 'mdi',
+ 'image/vnd.ms-photo' => 'wdp',
+ 'image/vnd.net-fpx' => 'npx',
+ 'image/vnd.wap.wbmp' => 'wbmp',
+ 'image/vnd.xiff' => 'xif',
+ 'image/webp' => 'webp',
+ 'image/x-3ds' => '3ds',
+ 'image/x-cmu-raster' => 'ras',
+ 'image/x-cmx' => 'cmx',
+ 'image/x-freehand' => 'fh',
+ 'image/x-icon' => 'ico',
+ 'image/x-mrsid-image' => 'sid',
+ 'image/x-pcx' => 'pcx',
+ 'image/x-pict' => 'pic',
+ 'image/x-portable-anymap' => 'pnm',
+ 'image/x-portable-bitmap' => 'pbm',
+ 'image/x-portable-graymap' => 'pgm',
+ 'image/x-portable-pixmap' => 'ppm',
+ 'image/x-rgb' => 'rgb',
+ 'image/x-tga' => 'tga',
+ 'image/x-xbitmap' => 'xbm',
+ 'image/x-xpixmap' => 'xpm',
+ 'image/x-xwindowdump' => 'xwd',
+ 'message/rfc822' => 'eml',
+ 'model/iges' => 'igs',
+ 'model/mesh' => 'msh',
+ 'model/vnd.collada+xml' => 'dae',
+ 'model/vnd.dwf' => 'dwf',
+ 'model/vnd.gdl' => 'gdl',
+ 'model/vnd.gtw' => 'gtw',
+ 'model/vnd.mts' => 'mts',
+ 'model/vnd.vtu' => 'vtu',
+ 'model/vrml' => 'wrl',
+ 'model/x3d+binary' => 'x3db',
+ 'model/x3d+vrml' => 'x3dv',
+ 'model/x3d+xml' => 'x3d',
+ 'text/cache-manifest' => 'appcache',
+ 'text/calendar' => 'ics',
+ 'text/css' => 'css',
+ 'text/csv' => 'csv',
+ 'text/html' => 'html',
+ 'text/n3' => 'n3',
+ 'text/plain' => 'txt',
+ 'text/prs.lines.tag' => 'dsc',
+ 'text/richtext' => 'rtx',
+ 'text/rtf' => 'rtf',
+ 'text/sgml' => 'sgml',
+ 'text/tab-separated-values' => 'tsv',
+ 'text/troff' => 't',
+ 'text/turtle' => 'ttl',
+ 'text/uri-list' => 'uri',
+ 'text/vcard' => 'vcard',
+ 'text/vnd.curl' => 'curl',
+ 'text/vnd.curl.dcurl' => 'dcurl',
+ 'text/vnd.curl.scurl' => 'scurl',
+ 'text/vnd.curl.mcurl' => 'mcurl',
+ 'text/vnd.dvb.subtitle' => 'sub',
+ 'text/vnd.fly' => 'fly',
+ 'text/vnd.fmi.flexstor' => 'flx',
+ 'text/vnd.graphviz' => 'gv',
+ 'text/vnd.in3d.3dml' => '3dml',
+ 'text/vnd.in3d.spot' => 'spot',
+ 'text/vnd.sun.j2me.app-descriptor' => 'jad',
+ 'text/vnd.wap.wml' => 'wml',
+ 'text/vnd.wap.wmlscript' => 'wmls',
+ 'text/vtt' => 'vtt',
+ 'text/x-asm' => 's',
+ 'text/x-c' => 'c',
+ 'text/x-fortran' => 'f',
+ 'text/x-pascal' => 'p',
+ 'text/x-java-source' => 'java',
+ 'text/x-opml' => 'opml',
+ 'text/x-nfo' => 'nfo',
+ 'text/x-setext' => 'etx',
+ 'text/x-sfv' => 'sfv',
+ 'text/x-uuencode' => 'uu',
+ 'text/x-vcalendar' => 'vcs',
+ 'text/x-vcard' => 'vcf',
+ 'video/3gpp' => '3gp',
+ 'video/3gpp2' => '3g2',
+ 'video/h261' => 'h261',
+ 'video/h263' => 'h263',
+ 'video/h264' => 'h264',
+ 'video/jpeg' => 'jpgv',
+ 'video/jpm' => 'jpm',
+ 'video/mj2' => 'mj2',
+ 'video/mp4' => 'mp4',
+ 'video/mpeg' => 'mpeg',
+ 'video/ogg' => 'ogv',
+ 'video/quicktime' => 'qt',
+ 'video/vnd.dece.hd' => 'uvh',
+ 'video/vnd.dece.mobile' => 'uvm',
+ 'video/vnd.dece.pd' => 'uvp',
+ 'video/vnd.dece.sd' => 'uvs',
+ 'video/vnd.dece.video' => 'uvv',
+ 'video/vnd.dvb.file' => 'dvb',
+ 'video/vnd.fvt' => 'fvt',
+ 'video/vnd.mpegurl' => 'mxu',
+ 'video/vnd.ms-playready.media.pyv' => 'pyv',
+ 'video/vnd.uvvu.mp4' => 'uvu',
+ 'video/vnd.vivo' => 'viv',
+ 'video/webm' => 'webm',
+ 'video/x-f4v' => 'f4v',
+ 'video/x-fli' => 'fli',
+ 'video/x-flv' => 'flv',
+ 'video/x-m4v' => 'm4v',
+ 'video/x-matroska' => 'mkv',
+ 'video/x-mng' => 'mng',
+ 'video/x-ms-asf' => 'asf',
+ 'video/x-ms-vob' => 'vob',
+ 'video/x-ms-wm' => 'wm',
+ 'video/x-ms-wmv' => 'wmv',
+ 'video/x-ms-wmx' => 'wmx',
+ 'video/x-ms-wvx' => 'wvx',
+ 'video/x-msvideo' => 'avi',
+ 'video/x-sgi-movie' => 'movie',
+ 'video/x-smv' => 'smv',
+ 'x-conference/x-cooltalk' => 'ice',
+ ];
+
+ /**
+ * Reversed from $defaultExtensions property in constructor.
+ */
+ private $defaultMineTypes = [];
+
+ public function __construct()
+ {
+ $this->defaultMineTypes = array_flip($this->defaultExtensions);
+ }
+
+ public function guessExtension(string $mimeType): ?string
+ {
+ return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null;
+ }
+
+ public function guessMimeType(string $extension): ?string
+ {
+ return isset($this->defaultMineTypes[$extension]) ? $this->defaultMineTypes[$extension] : null;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Network.php b/addons/epay/library/hyperf/utils/src/Network.php
new file mode 100644
index 0000000..16ed0d8
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Network.php
@@ -0,0 +1,46 @@
+ $value) {
+ foreach ($value['unicast'] as $item) {
+ if (! isset($item['address'])) {
+ continue;
+ }
+ if (! Str::contains($item['address'], '::') && $item['address'] !== '127.0.0.1') {
+ $ips[$name] = $item['address'];
+ }
+ }
+ }
+ }
+ if (is_array($ips) && ! empty($ips)) {
+ return current($ips);
+ }
+ /** @var mixed|string $ip */
+ $ip = gethostbyname(gethostname());
+ if (is_string($ip)) {
+ return $ip;
+ }
+ throw new RuntimeException('Can not get the internal IP.');
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Optional.php b/addons/epay/library/hyperf/utils/src/Optional.php
new file mode 100644
index 0000000..e990700
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Optional.php
@@ -0,0 +1,134 @@
+value = $value;
+ }
+
+ /**
+ * Dynamically access a property on the underlying object.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (is_object($this->value)) {
+ return $this->value->{$key} ?? null;
+ }
+
+ return null;
+ }
+
+ /**
+ * Dynamically check a property exists on the underlying object.
+ *
+ * @param mixed $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ if (is_object($this->value)) {
+ return isset($this->value->{$name});
+ }
+
+ return false;
+ }
+
+ /**
+ * Dynamically pass a method to the underlying object.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ if (static::hasMacro($method)) {
+ return $this->macroCall($method, $parameters);
+ }
+
+ if (is_object($this->value)) {
+ return $this->value->{$method}(...$parameters);
+ }
+ }
+
+ /**
+ * Determine if an item exists at an offset.
+ *
+ * @param mixed $key
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return Arr::accessible($this->value) && Arr::exists($this->value, $key);
+ }
+
+ /**
+ * Get an item at a given offset.
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ return Arr::get($this->value, $key);
+ }
+
+ /**
+ * Set the item at a given offset.
+ *
+ * @param mixed $key
+ * @param mixed $value
+ */
+ public function offsetSet($key, $value)
+ {
+ if (Arr::accessible($this->value)) {
+ $this->value[$key] = $value;
+ }
+ }
+
+ /**
+ * Unset the item at a given offset.
+ *
+ * @param string $key
+ */
+ public function offsetUnset($key)
+ {
+ if (Arr::accessible($this->value)) {
+ unset($this->value[$key]);
+ }
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Packer/JsonPacker.php b/addons/epay/library/hyperf/utils/src/Packer/JsonPacker.php
new file mode 100644
index 0000000..5fa8c98
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Packer/JsonPacker.php
@@ -0,0 +1,27 @@
+ 0) {
+ $this->concurrentChannel = new Channel($concurrent);
+ }
+ }
+
+ public function add(callable $callable, $key = null)
+ {
+ if (is_null($key)) {
+ $this->callbacks[] = $callable;
+ } else {
+ $this->callbacks[$key] = $callable;
+ }
+ }
+
+ public function wait(bool $throw = true): array
+ {
+ $wg = new WaitGroup();
+ $wg->add(count($this->callbacks));
+ foreach ($this->callbacks as $key => $callback) {
+ $this->concurrentChannel && $this->concurrentChannel->push(true);
+ $this->results[$key] = null;
+ Coroutine::create(function () use ($callback, $key, $wg) {
+ try {
+ $this->results[$key] = $callback();
+ } catch (\Throwable $throwable) {
+ $this->throwables[$key] = $throwable;
+ unset($this->results[$key]);
+ } finally {
+ $this->concurrentChannel && $this->concurrentChannel->pop();
+ $wg->done();
+ }
+ });
+ }
+ $wg->wait();
+ if ($throw && ($throwableCount = count($this->throwables)) > 0) {
+ $message = 'Detecting ' . $throwableCount . ' throwable occurred during parallel execution:' . PHP_EOL . $this->formatThrowables($this->throwables);
+ $executionException = new ParallelExecutionException($message);
+ $executionException->setResults($this->results);
+ $executionException->setThrowables($this->throwables);
+ unset($this->results, $this->throwables);
+ throw $executionException;
+ }
+ return $this->results;
+ }
+
+ public function count(): int
+ {
+ return count($this->callbacks);
+ }
+
+ public function clear(): void
+ {
+ $this->callbacks = [];
+ $this->results = [];
+ $this->throwables = [];
+ }
+
+ /**
+ * Format throwables into a nice list.
+ *
+ * @param \Throwable[] $throwables
+ */
+ private function formatThrowables(array $throwables): string
+ {
+ $output = '';
+ foreach ($throwables as $key => $value) {
+ $output .= \sprintf('(%s) %s: %s' . PHP_EOL . '%s' . PHP_EOL, $key, get_class($value), $value->getMessage(), $value->getTraceAsString());
+ }
+ return $output;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Pipeline.php b/addons/epay/library/hyperf/utils/src/Pipeline.php
new file mode 100644
index 0000000..217c19d
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Pipeline.php
@@ -0,0 +1,171 @@
+container = $container;
+ }
+
+ /**
+ * Set the object being sent through the pipeline.
+ * @param mixed $passable
+ */
+ public function send($passable): self
+ {
+ $this->passable = $passable;
+
+ return $this;
+ }
+
+ /**
+ * Set the array of pipes.
+ *
+ * @param array|mixed $pipes
+ */
+ public function through($pipes): self
+ {
+ $this->pipes = is_array($pipes) ? $pipes : func_get_args();
+ return $this;
+ }
+
+ /**
+ * Set the method to call on the pipes.
+ */
+ public function via(string $method): self
+ {
+ $this->method = $method;
+
+ return $this;
+ }
+
+ /**
+ * Run the pipeline with a final destination callback.
+ */
+ public function then(Closure $destination)
+ {
+ $pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination));
+
+ return $pipeline($this->passable);
+ }
+
+ /**
+ * Get the final piece of the Closure onion.
+ */
+ protected function prepareDestination(Closure $destination): Closure
+ {
+ return static function ($passable) use ($destination) {
+ return $destination($passable);
+ };
+ }
+
+ /**
+ * Get a Closure that represents a slice of the application onion.
+ */
+ protected function carry(): Closure
+ {
+ return function ($stack, $pipe) {
+ return function ($passable) use ($stack, $pipe) {
+ if (is_callable($pipe)) {
+ // If the pipe is an instance of a Closure, we will just call it directly but
+ // otherwise we'll resolve the pipes out of the container and call it with
+ // the appropriate method and arguments, returning the results back out.
+ return $pipe($passable, $stack);
+ }
+ if (! is_object($pipe)) {
+ [$name, $parameters] = $this->parsePipeString($pipe);
+
+ // If the pipe is a string we will parse the string and resolve the class out
+ // of the dependency injection container. We can then build a callable and
+ // execute the pipe function giving in the parameters that are required.
+ $pipe = $this->container->get($name);
+
+ $parameters = array_merge([$passable, $stack], $parameters);
+ } else {
+ // If the pipe is already an object we'll just make a callable and pass it to
+ // the pipe as-is. There is no need to do any extra parsing and formatting
+ // since the object we're given was already a fully instantiated object.
+ $parameters = [$passable, $stack];
+ }
+
+ $carry = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters);
+
+ return $this->handleCarry($carry);
+ };
+ };
+ }
+
+ /**
+ * Parse full pipe string to get name and parameters.
+ *
+ * @param string $pipe
+ * @return array
+ */
+ protected function parsePipeString($pipe)
+ {
+ [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);
+
+ if (is_string($parameters)) {
+ $parameters = explode(',', $parameters);
+ }
+
+ return [$name, $parameters];
+ }
+
+ /**
+ * Handle the value returned from each pipe before passing it to the next.
+ *
+ * @param mixed $carry
+ * @return mixed
+ */
+ protected function handleCarry($carry)
+ {
+ return $carry;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Pluralizer.php b/addons/epay/library/hyperf/utils/src/Pluralizer.php
new file mode 100644
index 0000000..543291c
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Pluralizer.php
@@ -0,0 +1,161 @@
+pluralize($value);
+
+ return static::matchCase($plural, $value);
+ }
+
+ /**
+ * Get the singular form of an English word.
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function singular($value)
+ {
+ $singular = static::getInflector()->singularize($value);
+
+ return static::matchCase($singular, $value);
+ }
+
+ public static function setInflector(?Inflector $inflector): void
+ {
+ static::$inflector = $inflector;
+ }
+
+ /**
+ * Get the inflector instance.
+ */
+ public static function getInflector(): Inflector
+ {
+ if (is_null(static::$inflector)) {
+ static::$inflector = new Inflector(
+ new CachedWordInflector(new RulesetInflector(
+ English\Rules::getSingularRuleset()
+ )),
+ new CachedWordInflector(new RulesetInflector(
+ English\Rules::getPluralRuleset()
+ ))
+ );
+ }
+
+ return static::$inflector;
+ }
+
+ /**
+ * Determine if the given value is uncountable.
+ *
+ * @param string $value
+ * @return bool
+ */
+ protected static function uncountable($value)
+ {
+ return in_array(strtolower($value), static::$uncountable);
+ }
+
+ /**
+ * Attempt to match the case on two strings.
+ *
+ * @param string $value
+ * @param string $comparison
+ * @return string
+ */
+ protected static function matchCase($value, $comparison)
+ {
+ $functions = ['mb_strtolower', 'mb_strtoupper', 'ucfirst', 'ucwords'];
+
+ foreach ($functions as $function) {
+ if (call_user_func($function, $comparison) === $comparison) {
+ return call_user_func($function, $value);
+ }
+ }
+
+ return $value;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Reflection/ClassInvoker.php b/addons/epay/library/hyperf/utils/src/Reflection/ClassInvoker.php
new file mode 100644
index 0000000..4b413f8
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Reflection/ClassInvoker.php
@@ -0,0 +1,51 @@
+instance = $instance;
+ $this->reflection = new ReflectionClass($instance);
+ }
+
+ public function __get($name)
+ {
+ $property = $this->reflection->getProperty($name);
+
+ $property->setAccessible(true);
+
+ return $property->getValue($this->instance);
+ }
+
+ public function __call($name, $arguments)
+ {
+ $method = $this->reflection->getMethod($name);
+
+ $method->setAccessible(true);
+
+ return $method->invokeArgs($this->instance, $arguments);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Resource.php b/addons/epay/library/hyperf/utils/src/Resource.php
new file mode 100644
index 0000000..92aa822
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Resource.php
@@ -0,0 +1,37 @@
+getInstantiator()->instantiate($class);
+ foreach (['code', 'message', 'file', 'line'] as $attribute) {
+ if (isset($data[$attribute])) {
+ $property = ReflectionManager::reflectProperty($class, $attribute);
+ $property->setAccessible(true);
+ $property->setValue($exception, $data[$attribute]);
+ }
+ }
+ return $exception;
+ } catch (\ReflectionException $e) {
+ return new \RuntimeException(sprintf(
+ 'Bad data %s: %s',
+ $data['class'],
+ $data['message']
+ ), $data['code']);
+ } catch (\TypeError $e) {
+ return new \RuntimeException(sprintf(
+ 'Uncaught data %s: %s',
+ $data['class'],
+ $data['message']
+ ), $data['code']);
+ }
+ }
+
+ return new \RuntimeException('Bad data data: ' . json_encode($data));
+ }
+
+ public function supportsDenormalization($data, $type, $format = null)
+ {
+ return class_exists($type) && is_a($type, \Throwable::class, true);
+ }
+
+ public function normalize($object, string $format = null, array $context = [])
+ {
+ if ($object instanceof \Serializable) {
+ return serialize($object);
+ }
+ /* @var \Throwable $object */
+ return [
+ 'message' => $object->getMessage(),
+ 'code' => $object->getCode(),
+ 'file' => $object->getFile(),
+ 'line' => $object->getLine(),
+ ];
+ }
+
+ public function supportsNormalization($data, string $format = null)
+ {
+ return $data instanceof \Throwable;
+ }
+
+ public function hasCacheableSupportsMethod(): bool
+ {
+ return \get_class($this) === __CLASS__;
+ }
+
+ protected function getInstantiator(): Instantiator
+ {
+ if ($this->instantiator instanceof Instantiator) {
+ return $this->instantiator;
+ }
+
+ return $this->instantiator = new Instantiator();
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Serializer/ScalarNormalizer.php b/addons/epay/library/hyperf/utils/src/Serializer/ScalarNormalizer.php
new file mode 100644
index 0000000..5966cc9
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Serializer/ScalarNormalizer.php
@@ -0,0 +1,64 @@
+serialize($obj, 'xml')
+ * $serializer->decode($data, 'xml')
+ * $serializer->denormalize($data, 'Class', 'xml')
+ */
+class Serializer implements Normalizer, SerializerInterface, ContextAwareNormalizerInterface, ContextAwareDenormalizerInterface, ContextAwareEncoderInterface, ContextAwareDecoderInterface
+{
+ private const SCALAR_TYPES = [
+ 'int' => true,
+ 'bool' => true,
+ 'float' => true,
+ 'string' => true,
+ ];
+
+ /**
+ * @var Encoder\ChainEncoder
+ */
+ protected $encoder;
+
+ /**
+ * @var Encoder\ChainDecoder
+ */
+ protected $decoder;
+
+ private $normalizers = [];
+
+ private $denormalizerCache = [];
+
+ private $normalizerCache = [];
+
+ /**
+ * @param (NormalizerInterface|DenormalizerInterface|mixed)[] $normalizers
+ * @param (EncoderInterface|DecoderInterface|mixed)[] $encoders
+ */
+ public function __construct(array $normalizers = [], array $encoders = [])
+ {
+ foreach ($normalizers as $normalizer) {
+ if ($normalizer instanceof SerializerAwareInterface) {
+ $normalizer->setSerializer($this);
+ }
+
+ if ($normalizer instanceof DenormalizerAwareInterface) {
+ $normalizer->setDenormalizer($this);
+ }
+
+ if ($normalizer instanceof NormalizerAwareInterface) {
+ $normalizer->setNormalizer($this);
+ }
+
+ if (! ($normalizer instanceof NormalizerInterface || $normalizer instanceof DenormalizerInterface)) {
+ throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class));
+ }
+ }
+ $this->normalizers = $normalizers;
+
+ $decoders = [];
+ $realEncoders = [];
+ foreach ($encoders as $encoder) {
+ if ($encoder instanceof SerializerAwareInterface) {
+ $encoder->setSerializer($this);
+ }
+ if ($encoder instanceof DecoderInterface) {
+ $decoders[] = $encoder;
+ }
+ if ($encoder instanceof EncoderInterface) {
+ $realEncoders[] = $encoder;
+ }
+
+ if (! ($encoder instanceof EncoderInterface || $encoder instanceof DecoderInterface)) {
+ throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($encoder), EncoderInterface::class, DecoderInterface::class));
+ }
+ }
+ $this->encoder = new ChainEncoder($realEncoders);
+ $this->decoder = new ChainDecoder($decoders);
+ }
+
+ final public function serialize($data, string $format, array $context = []): string
+ {
+ if (! $this->supportsEncoding($format, $context)) {
+ throw new NotEncodableValueException(sprintf('Serialization for the format "%s" is not supported.', $format));
+ }
+
+ if ($this->encoder->needsNormalization($format, $context)) {
+ $data = $this->normalize($data, $format, $context);
+ }
+
+ return $this->encode($data, $format, $context);
+ }
+
+ final public function deserialize($data, string $type, string $format, array $context = [])
+ {
+ if (! $this->supportsDecoding($format, $context)) {
+ throw new NotEncodableValueException(sprintf('Deserialization for the format "%s" is not supported.', $format));
+ }
+
+ $data = $this->decode($data, $format, $context);
+
+ return $this->denormalize($data, $type, $format, $context);
+ }
+
+ public function normalize($data, string $format = null, array $context = [])
+ {
+ // If a normalizer supports the given data, use it
+ if ($normalizer = $this->getNormalizer($data, $format, $context)) {
+ return $normalizer->normalize($data, $format, $context);
+ }
+
+ if ($data === null || is_scalar($data)) {
+ return $data;
+ }
+
+ if (\is_array($data) || $data instanceof \Traversable) {
+ if ($data instanceof \Countable && $data->count() === 0) {
+ return $data;
+ }
+
+ $normalized = [];
+ foreach ($data as $key => $val) {
+ $normalized[$key] = $this->normalize($val, $format, $context);
+ }
+
+ return $normalized;
+ }
+
+ if (\is_object($data)) {
+ if (! $this->normalizers) {
+ throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
+ }
+
+ throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($data)));
+ }
+
+ throw new NotNormalizableValueException('An unexpected value could not be normalized: ' . (! \is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data))));
+ }
+
+ /**
+ * @param mixed $data
+ * @throws NotNormalizableValueException
+ */
+ public function denormalize($data, string $type, string $format = null, array $context = [])
+ {
+ if (isset(self::SCALAR_TYPES[$type])) {
+ if (is_scalar($data)) {
+ switch ($type) {
+ case 'int':
+ return (int) $data;
+ case 'bool':
+ return (bool) $data;
+ case 'float':
+ return (float) $data;
+ case 'string':
+ return (string) $data;
+ }
+ }
+ }
+
+ if (! $this->normalizers) {
+ throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');
+ }
+
+ if ($normalizer = $this->getDenormalizer($data, $type, $format, $context)) {
+ return $normalizer->denormalize($data, $type, $format, $context);
+ }
+
+ throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type));
+ }
+
+ public function supportsNormalization($data, string $format = null, array $context = [])
+ {
+ return $this->getNormalizer($data, $format, $context) !== null;
+ }
+
+ public function supportsDenormalization($data, string $type, string $format = null, array $context = [])
+ {
+ return isset(self::SCALAR_TYPES[$type]) || $this->getDenormalizer($data, $type, $format, $context) !== null;
+ }
+
+ final public function encode($data, string $format, array $context = [])
+ {
+ return $this->encoder->encode($data, $format, $context);
+ }
+
+ final public function decode(string $data, string $format, array $context = [])
+ {
+ return $this->decoder->decode($data, $format, $context);
+ }
+
+ public function supportsEncoding(string $format, array $context = [])
+ {
+ return $this->encoder->supportsEncoding($format, $context);
+ }
+
+ public function supportsDecoding(string $format, array $context = [])
+ {
+ return $this->decoder->supportsDecoding($format, $context);
+ }
+
+ /**
+ * Returns a matching normalizer.
+ *
+ * @param mixed $data Data to get the serializer for
+ * @param string $format Format name, present to give the option to normalizers to act differently based on formats
+ * @param array $context Options available to the normalizer
+ */
+ private function getNormalizer($data, ?string $format, array $context): ?NormalizerInterface
+ {
+ $type = \is_object($data) ? \get_class($data) : 'native-' . \gettype($data);
+
+ if (! isset($this->normalizerCache[$format][$type])) {
+ $this->normalizerCache[$format][$type] = [];
+
+ foreach ($this->normalizers as $k => $normalizer) {
+ if (! $normalizer instanceof NormalizerInterface) {
+ continue;
+ }
+
+ if (! $normalizer instanceof CacheableSupportsMethodInterface || ! $normalizer->hasCacheableSupportsMethod()) {
+ $this->normalizerCache[$format][$type][$k] = false;
+ } elseif ($normalizer->supportsNormalization($data, $format)) {
+ $this->normalizerCache[$format][$type][$k] = true;
+ break;
+ }
+ }
+ }
+
+ foreach ($this->normalizerCache[$format][$type] as $k => $cached) {
+ $normalizer = $this->normalizers[$k];
+ if ($cached || $normalizer->supportsNormalization($data, $format, $context)) {
+ return $normalizer;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a matching denormalizer.
+ *
+ * @param mixed $data Data to restore
+ * @param string $class The expected class to instantiate
+ * @param string $format Format name, present to give the option to normalizers to act differently based on formats
+ * @param array $context Options available to the denormalizer
+ */
+ private function getDenormalizer($data, string $class, ?string $format, array $context): ?DenormalizerInterface
+ {
+ if (! isset($this->denormalizerCache[$format][$class])) {
+ $this->denormalizerCache[$format][$class] = [];
+
+ foreach ($this->normalizers as $k => $normalizer) {
+ if (! $normalizer instanceof DenormalizerInterface) {
+ continue;
+ }
+
+ if (! $normalizer instanceof CacheableSupportsMethodInterface || ! $normalizer->hasCacheableSupportsMethod()) {
+ $this->denormalizerCache[$format][$class][$k] = false;
+ } elseif ($normalizer->supportsDenormalization(null, $class, $format)) {
+ $this->denormalizerCache[$format][$class][$k] = true;
+ break;
+ }
+ }
+ }
+
+ foreach ($this->denormalizerCache[$format][$class] as $k => $cached) {
+ $normalizer = $this->normalizers[$k];
+ if ($cached || $normalizer->supportsDenormalization($data, $class, $format, $context)) {
+ return $normalizer;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Serializer/SerializerFactory.php b/addons/epay/library/hyperf/utils/src/Serializer/SerializerFactory.php
new file mode 100644
index 0000000..0ec9709
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Serializer/SerializerFactory.php
@@ -0,0 +1,39 @@
+serializer = $serializer;
+ }
+
+ public function __invoke()
+ {
+ return new $this->serializer([
+ new ExceptionNormalizer(),
+ new ObjectNormalizer(),
+ new ArrayDenormalizer(),
+ new ScalarNormalizer(),
+ ]);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Serializer/SimpleNormalizer.php b/addons/epay/library/hyperf/utils/src/Serializer/SimpleNormalizer.php
new file mode 100644
index 0000000..6b31266
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Serializer/SimpleNormalizer.php
@@ -0,0 +1,40 @@
+serializer = $serializer;
+ }
+
+ public function normalize($object)
+ {
+ return $this->serializer->normalize($object);
+ }
+
+ public function denormalize($data, string $class)
+ {
+ return $this->serializer->denormalize($data, $class);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Str.php b/addons/epay/library/hyperf/utils/src/Str.php
new file mode 100644
index 0000000..d23d9a9
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Str.php
@@ -0,0 +1,1320 @@
+ $val) {
+ $value = str_replace($val, (string) $key, $value);
+ }
+
+ return preg_replace('/[^\x20-\x7E]/u', '', $value);
+ }
+
+ /**
+ * Get the portion of a string before a given value.
+ *
+ * @param string $subject
+ * @param string $search
+ * @return string
+ */
+ public static function before($subject, $search)
+ {
+ return $search === '' ? $subject : explode($search, $subject)[0];
+ }
+
+ /**
+ * Get the portion of a string before the last occurrence of a given value.
+ *
+ * @param string $subject
+ * @param string $search
+ * @return string
+ */
+ public static function beforeLast($subject, $search)
+ {
+ if ($search === '') {
+ return $subject;
+ }
+
+ $pos = mb_strrpos($subject, $search);
+
+ if ($pos === false) {
+ return $subject;
+ }
+
+ return static::substr($subject, 0, $pos);
+ }
+
+ /**
+ * Get the portion of a string between two given values.
+ *
+ * @param string $subject
+ * @param string $from
+ * @param string $to
+ * @return string
+ */
+ public static function between($subject, $from, $to)
+ {
+ if ($from === '' || $to === '') {
+ return $subject;
+ }
+
+ return static::beforeLast(static::after($subject, $from), $to);
+ }
+
+ /**
+ * Convert a value to camel case.
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function camel($value)
+ {
+ if (isset(static::$camelCache[$value])) {
+ return static::$camelCache[$value];
+ }
+
+ return static::$camelCache[$value] = lcfirst(static::studly($value));
+ }
+
+ /**
+ * Determine if a given string contains a given substring.
+ *
+ * @param string $haystack
+ * @param array|string $needles
+ * @return bool
+ */
+ public static function contains($haystack, $needles)
+ {
+ foreach ((array) $needles as $needle) {
+ if ($needle !== '' && mb_strpos($haystack, $needle) !== false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if a given string contains all array values.
+ *
+ * @param string $haystack
+ * @param string[] $needles
+ * @return bool
+ */
+ public static function containsAll($haystack, array $needles)
+ {
+ foreach ($needles as $needle) {
+ if (! static::contains($haystack, $needle)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine if a given string ends with a given substring.
+ *
+ * @param string $haystack
+ * @param array|string $needles
+ * @return bool
+ */
+ public static function endsWith($haystack, $needles)
+ {
+ foreach ((array) $needles as $needle) {
+ if (substr($haystack, -strlen($needle)) === (string) $needle) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Cap a string with a single instance of a given value.
+ *
+ * @param string $value
+ * @param string $cap
+ * @return string
+ */
+ public static function finish($value, $cap)
+ {
+ $quoted = preg_quote($cap, '/');
+
+ return preg_replace('/(?:' . $quoted . ')+$/u', '', $value) . $cap;
+ }
+
+ /**
+ * Determine if a given string matches a given pattern.
+ *
+ * @param array|string $pattern
+ * @param string $value
+ * @return bool
+ */
+ public static function is($pattern, $value)
+ {
+ $patterns = Arr::wrap($pattern);
+
+ if (empty($patterns)) {
+ return false;
+ }
+
+ foreach ($patterns as $pattern) {
+ // If the given value is an exact match we can of course return true right
+ // from the beginning. Otherwise, we will translate asterisks and do an
+ // actual pattern match against the two strings to see if they match.
+ if ($pattern == $value) {
+ return true;
+ }
+
+ $pattern = preg_quote($pattern, '#');
+
+ // Asterisks are translated into zero-or-more regular expression wildcards
+ // to make it convenient to check if the strings starts with the given
+ // pattern such as "library/*", making any string check convenient.
+ $pattern = str_replace('\*', '.*', $pattern);
+
+ if (preg_match('#^' . $pattern . '\z#u', $value) === 1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Convert a string to kebab case.
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function kebab($value)
+ {
+ return static::snake($value, '-');
+ }
+
+ /**
+ * Return the length of the given string.
+ *
+ * @param string $value
+ * @param string $encoding
+ * @return int
+ */
+ public static function length($value, $encoding = null)
+ {
+ if ($encoding) {
+ return mb_strlen($value, $encoding);
+ }
+
+ return mb_strlen($value);
+ }
+
+ /**
+ * Limit the number of characters in a string.
+ *
+ * @param string $value
+ * @param int $limit
+ * @param string $end
+ * @return string
+ */
+ public static function limit($value, $limit = 100, $end = '...')
+ {
+ if (mb_strwidth($value, 'UTF-8') <= $limit) {
+ return $value;
+ }
+
+ return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')) . $end;
+ }
+
+ /**
+ * Convert the given string to lower-case.
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function lower($value)
+ {
+ return mb_strtolower($value, 'UTF-8');
+ }
+
+ /**
+ * Limit the number of words in a string.
+ */
+ public static function words(string $value, int $words = 100, string $end = '...'): string
+ {
+ preg_match('/^\s*+(?:\S++\s*+){1,' . $words . '}/u', $value, $matches);
+
+ if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) {
+ return $value;
+ }
+
+ return rtrim($matches[0]) . $end;
+ }
+
+ /**
+ * Get the string matching the given pattern.
+ *
+ * @param string $pattern
+ * @param string $subject
+ * @return string
+ */
+ public static function match($pattern, $subject)
+ {
+ preg_match($pattern, $subject, $matches);
+
+ if (! $matches) {
+ return '';
+ }
+
+ return $matches[1] ?? $matches[0];
+ }
+
+ /**
+ * Get the string matching the given pattern.
+ *
+ * @param string $pattern
+ * @param string $subject
+ * @return Collection
+ */
+ public static function matchAll($pattern, $subject)
+ {
+ preg_match_all($pattern, $subject, $matches);
+
+ if (empty($matches[0])) {
+ return collect();
+ }
+
+ return collect($matches[1] ?? $matches[0]);
+ }
+
+ /**
+ * Pad both sides of a string with another.
+ *
+ * @param string $value
+ * @param int $length
+ * @param string $pad
+ * @return string
+ */
+ public static function padBoth($value, $length, $pad = ' ')
+ {
+ return str_pad($value, $length, $pad, STR_PAD_BOTH);
+ }
+
+ /**
+ * Pad the left side of a string with another.
+ *
+ * @param string $value
+ * @param int $length
+ * @param string $pad
+ * @return string
+ */
+ public static function padLeft($value, $length, $pad = ' ')
+ {
+ return str_pad($value, $length, $pad, STR_PAD_LEFT);
+ }
+
+ /**
+ * Pad the right side of a string with another.
+ *
+ * @param string $value
+ * @param int $length
+ * @param string $pad
+ * @return string
+ */
+ public static function padRight($value, $length, $pad = ' ')
+ {
+ return str_pad($value, $length, $pad, STR_PAD_RIGHT);
+ }
+
+ /**
+ * Parse a Class@method style callback into class and method.
+ *
+ * @param null|string $default
+ */
+ public static function parseCallback(string $callback, $default = null): array
+ {
+ return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
+ }
+
+ /**
+ * Pluralize the last word of an English, studly caps case string.
+ */
+ public static function pluralStudly(string $value, int $count = 2): string
+ {
+ $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
+
+ $lastWord = array_pop($parts);
+
+ return implode('', $parts) . self::plural($lastWord, $count);
+ }
+
+ /**
+ * Get the plural form of an English word.
+ */
+ public static function plural(string $value, int $count = 2): string
+ {
+ return Pluralizer::plural($value, $count);
+ }
+
+ /**
+ * Generate a more truly "random" alpha-numeric string.
+ */
+ public static function random(int $length = 16): string
+ {
+ $string = '';
+
+ while (($len = strlen($string)) < $length) {
+ $size = $length - $len;
+
+ $bytes = random_bytes($size);
+
+ $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Repeat the given string.
+ *
+ * @return string
+ */
+ public static function repeat(string $string, int $times)
+ {
+ return str_repeat($string, $times);
+ }
+
+ /**
+ * Replace a given value in the string sequentially with an array.
+ */
+ public static function replaceArray(string $search, array $replace, string $subject): string
+ {
+ foreach ($replace as $value) {
+ $subject = static::replaceFirst($search, (string) $value, $subject);
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Replace the given value in the given string.
+ *
+ * @param string|string[] $search
+ * @param string|string[] $replace
+ * @param string|string[] $subject
+ * @return string
+ */
+ public static function replace($search, $replace, $subject)
+ {
+ return str_replace($search, $replace, $subject);
+ }
+
+ /**
+ * Replace the first occurrence of a given value in the string.
+ */
+ public static function replaceFirst(string $search, string $replace, string $subject): string
+ {
+ if ($search == '') {
+ return $subject;
+ }
+
+ $position = strpos($subject, $search);
+
+ if ($position !== false) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Replace the last occurrence of a given value in the string.
+ */
+ public static function replaceLast(string $search, string $replace, string $subject): string
+ {
+ $position = strrpos($subject, $search);
+
+ if ($position !== false) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Remove any occurrence of the given string in the subject.
+ *
+ * @param array|string $search
+ * @param string $subject
+ * @param bool $caseSensitive
+ * @return string
+ */
+ public static function remove($search, $subject, $caseSensitive = true)
+ {
+ return $caseSensitive
+ ? str_replace($search, '', $subject)
+ : str_ireplace($search, '', $subject);
+ }
+
+ /**
+ * Begin a string with a single instance of a given value.
+ */
+ public static function start(string $value, string $prefix): string
+ {
+ $quoted = preg_quote($prefix, '/');
+
+ return $prefix . preg_replace('/^(?:' . $quoted . ')+/u', '', $value);
+ }
+
+ /**
+ * Strip HTML and PHP tags from the given string.
+ *
+ * @param null|string|string[] $allowedTags
+ */
+ public static function stripTags(string $value, $allowedTags = null): string
+ {
+ return strip_tags($value, $allowedTags);
+ }
+
+ /**
+ * Convert the given string to upper-case.
+ */
+ public static function upper(string $value): string
+ {
+ return mb_strtoupper($value, 'UTF-8');
+ }
+
+ /**
+ * Convert the given string to title case.
+ */
+ public static function title(string $value): string
+ {
+ return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+ }
+
+ /**
+ * Get the singular form of an English word.
+ */
+ public static function singular(string $value): string
+ {
+ return Pluralizer::singular($value);
+ }
+
+ /**
+ * Generate a URL friendly "slug" from a given string.
+ */
+ public static function slug(string $title, string $separator = '-', string $language = 'en'): string
+ {
+ $title = $language ? static::ascii($title, $language) : $title;
+
+ // Convert all dashes/underscores into separator
+ $flip = $separator === '-' ? '_' : '-';
+
+ $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title);
+
+ // Replace @ with the word 'at'
+ $title = str_replace('@', $separator . 'at' . $separator, $title);
+
+ // Remove all characters that are not the separator, letters, numbers, or whitespace.
+ $title = preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', mb_strtolower($title));
+
+ // Replace all separator characters and whitespace by a single separator
+ $title = preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title);
+
+ return trim($title, $separator);
+ }
+
+ /**
+ * Convert a string to snake case.
+ */
+ public static function snake(string $value, string $delimiter = '_'): string
+ {
+ $key = $value;
+
+ if (isset(static::$snakeCache[$key][$delimiter])) {
+ return static::$snakeCache[$key][$delimiter];
+ }
+
+ if (! ctype_lower($value)) {
+ $value = preg_replace('/\s+/u', '', ucwords($value));
+
+ $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
+ }
+
+ return static::$snakeCache[$key][$delimiter] = $value;
+ }
+
+ /**
+ * Determine if a given string starts with a given substring.
+ *
+ * @param array|string $needles
+ */
+ public static function startsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ($needle !== '' && substr($haystack, 0, strlen($needle)) === (string) $needle) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Convert a value to studly caps case.
+ */
+ public static function studly(string $value, string $gap = ''): string
+ {
+ $key = $value;
+
+ if (isset(static::$studlyCache[$key])) {
+ return static::$studlyCache[$key];
+ }
+
+ $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+ return static::$studlyCache[$key] = str_replace(' ', $gap, $value);
+ }
+
+ /**
+ * Returns the portion of string specified by the start and length parameters.
+ *
+ * @param string $string
+ * @param int $start
+ * @param null|int $length
+ * @return string
+ */
+ public static function substr($string, $start, $length = null)
+ {
+ return mb_substr($string, $start, $length, 'UTF-8');
+ }
+
+ /**
+ * Returns the number of substring occurrences.
+ *
+ * @param string $haystack
+ * @param string $needle
+ * @param int $offset
+ * @param null|int $length
+ * @return int
+ */
+ public static function substrCount($haystack, $needle, $offset = 0, $length = null)
+ {
+ if (! is_null($length)) {
+ return substr_count($haystack, $needle, $offset, $length);
+ }
+ return substr_count($haystack, $needle, $offset);
+ }
+
+ /**
+ * Make a string's first character uppercase.
+ */
+ public static function ucfirst(string $string): string
+ {
+ return static::upper(static::substr($string, 0, 1)) . static::substr($string, 1);
+ }
+
+ /**
+ * Replaces the first or the last ones chars from a string by a given char.
+ *
+ * @param int $offset if is negative it starts from the end
+ * @param string $replacement default is *
+ */
+ public static function mask(string $string, int $offset = 0, int $length = 0, string $replacement = '*')
+ {
+ if ($length < 0) {
+ throw new InvalidArgumentException('The length must equal or greater than zero.');
+ }
+
+ $stringLength = mb_strlen($string);
+ $absOffset = abs($offset);
+ if ($absOffset >= $stringLength) {
+ return $string;
+ }
+
+ $hiddenLength = $length ?: $stringLength - $absOffset;
+
+ if ($offset >= 0) {
+ return mb_substr($string, 0, $offset) . str_repeat($replacement, $hiddenLength) . mb_substr($string, $offset + $hiddenLength);
+ }
+
+ return mb_substr($string, 0, max($stringLength - $hiddenLength - $absOffset, 0)) . str_repeat($replacement, $hiddenLength) . mb_substr($string, $offset);
+ }
+
+ /**
+ * Returns the replacements for the ascii method.
+ * Note: Adapted from Stringy\Stringy.
+ *
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ */
+ protected static function charsArray(): array
+ {
+ static $charsArray;
+
+ if (isset($charsArray)) {
+ return $charsArray;
+ }
+
+ return $charsArray = [
+ '0' => ['°', '₀', '۰', '0'],
+ '1' => ['¹', '₁', '۱', '1'],
+ '2' => ['²', '₂', '۲', '2'],
+ '3' => ['³', '₃', '۳', '3'],
+ '4' => ['⁴', '₄', '۴', '٤', '4'],
+ '5' => ['⁵', '₅', '۵', '٥', '5'],
+ '6' => ['⁶', '₆', '۶', '٦', '6'],
+ '7' => ['⁷', '₇', '۷', '7'],
+ '8' => ['⁸', '₈', '۸', '8'],
+ '9' => ['⁹', '₉', '۹', '9'],
+ 'a' => [
+ 'à',
+ 'á',
+ 'ả',
+ 'ã',
+ 'ạ',
+ 'ă',
+ 'ắ',
+ 'ằ',
+ 'ẳ',
+ 'ẵ',
+ 'ặ',
+ 'â',
+ 'ấ',
+ 'ầ',
+ 'ẩ',
+ 'ẫ',
+ 'ậ',
+ 'ā',
+ 'ą',
+ 'å',
+ 'α',
+ 'ά',
+ 'ἀ',
+ 'ἁ',
+ 'ἂ',
+ 'ἃ',
+ 'ἄ',
+ 'ἅ',
+ 'ἆ',
+ 'ἇ',
+ 'ᾀ',
+ 'ᾁ',
+ 'ᾂ',
+ 'ᾃ',
+ 'ᾄ',
+ 'ᾅ',
+ 'ᾆ',
+ 'ᾇ',
+ 'ὰ',
+ 'ά',
+ 'ᾰ',
+ 'ᾱ',
+ 'ᾲ',
+ 'ᾳ',
+ 'ᾴ',
+ 'ᾶ',
+ 'ᾷ',
+ 'а',
+ 'أ',
+ 'အ',
+ 'ာ',
+ 'ါ',
+ 'ǻ',
+ 'ǎ',
+ 'ª',
+ 'ა',
+ 'अ',
+ 'ا',
+ 'a',
+ 'ä',
+ ],
+ 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'],
+ 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'],
+ 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'],
+ 'e' => [
+ 'é',
+ 'è',
+ 'ẻ',
+ 'ẽ',
+ 'ẹ',
+ 'ê',
+ 'ế',
+ 'ề',
+ 'ể',
+ 'ễ',
+ 'ệ',
+ 'ë',
+ 'ē',
+ 'ę',
+ 'ě',
+ 'ĕ',
+ 'ė',
+ 'ε',
+ 'έ',
+ 'ἐ',
+ 'ἑ',
+ 'ἒ',
+ 'ἓ',
+ 'ἔ',
+ 'ἕ',
+ 'ὲ',
+ 'έ',
+ 'е',
+ 'ё',
+ 'э',
+ 'є',
+ 'ə',
+ 'ဧ',
+ 'ေ',
+ 'ဲ',
+ 'ე',
+ 'ए',
+ 'إ',
+ 'ئ',
+ 'e',
+ ],
+ 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'],
+ 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'],
+ 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'],
+ 'i' => [
+ 'í',
+ 'ì',
+ 'ỉ',
+ 'ĩ',
+ 'ị',
+ 'î',
+ 'ï',
+ 'ī',
+ 'ĭ',
+ 'į',
+ 'ı',
+ 'ι',
+ 'ί',
+ 'ϊ',
+ 'ΐ',
+ 'ἰ',
+ 'ἱ',
+ 'ἲ',
+ 'ἳ',
+ 'ἴ',
+ 'ἵ',
+ 'ἶ',
+ 'ἷ',
+ 'ὶ',
+ 'ί',
+ 'ῐ',
+ 'ῑ',
+ 'ῒ',
+ 'ΐ',
+ 'ῖ',
+ 'ῗ',
+ 'і',
+ 'ї',
+ 'и',
+ 'ဣ',
+ 'ိ',
+ 'ီ',
+ 'ည်',
+ 'ǐ',
+ 'ი',
+ 'इ',
+ 'ی',
+ 'i',
+ ],
+ 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'],
+ 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'],
+ 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'],
+ 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'],
+ 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'],
+ 'o' => [
+ 'ó',
+ 'ò',
+ 'ỏ',
+ 'õ',
+ 'ọ',
+ 'ô',
+ 'ố',
+ 'ồ',
+ 'ổ',
+ 'ỗ',
+ 'ộ',
+ 'ơ',
+ 'ớ',
+ 'ờ',
+ 'ở',
+ 'ỡ',
+ 'ợ',
+ 'ø',
+ 'ō',
+ 'ő',
+ 'ŏ',
+ 'ο',
+ 'ὀ',
+ 'ὁ',
+ 'ὂ',
+ 'ὃ',
+ 'ὄ',
+ 'ὅ',
+ 'ὸ',
+ 'ό',
+ 'о',
+ 'و',
+ 'θ',
+ 'ို',
+ 'ǒ',
+ 'ǿ',
+ 'º',
+ 'ო',
+ 'ओ',
+ 'o',
+ 'ö',
+ ],
+ 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'],
+ 'q' => ['ყ', 'q'],
+ 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'],
+ 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'],
+ 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'],
+ 'u' => [
+ 'ú',
+ 'ù',
+ 'ủ',
+ 'ũ',
+ 'ụ',
+ 'ư',
+ 'ứ',
+ 'ừ',
+ 'ử',
+ 'ữ',
+ 'ự',
+ 'û',
+ 'ū',
+ 'ů',
+ 'ű',
+ 'ŭ',
+ 'ų',
+ 'µ',
+ 'у',
+ 'ဉ',
+ 'ု',
+ 'ူ',
+ 'ǔ',
+ 'ǖ',
+ 'ǘ',
+ 'ǚ',
+ 'ǜ',
+ 'უ',
+ 'उ',
+ 'u',
+ 'ў',
+ 'ü',
+ ],
+ 'v' => ['в', 'ვ', 'ϐ', 'v'],
+ 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'],
+ 'x' => ['χ', 'ξ', 'x'],
+ 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'],
+ 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'],
+ 'aa' => ['ع', 'आ', 'آ'],
+ 'ae' => ['æ', 'ǽ'],
+ 'ai' => ['ऐ'],
+ 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'],
+ 'dj' => ['ђ', 'đ'],
+ 'dz' => ['џ', 'ძ'],
+ 'ei' => ['ऍ'],
+ 'gh' => ['غ', 'ღ'],
+ 'ii' => ['ई'],
+ 'ij' => ['ij'],
+ 'kh' => ['х', 'خ', 'ხ'],
+ 'lj' => ['љ'],
+ 'nj' => ['њ'],
+ 'oe' => ['ö', 'œ', 'ؤ'],
+ 'oi' => ['ऑ'],
+ 'oii' => ['ऒ'],
+ 'ps' => ['ψ'],
+ 'sh' => ['ш', 'შ', 'ش'],
+ 'shch' => ['щ'],
+ 'ss' => ['ß'],
+ 'sx' => ['ŝ'],
+ 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'],
+ 'ts' => ['ц', 'ც', 'წ'],
+ 'ue' => ['ü'],
+ 'uu' => ['ऊ'],
+ 'ya' => ['я'],
+ 'yu' => ['ю'],
+ 'zh' => ['ж', 'ჟ', 'ژ'],
+ '(c)' => ['©'],
+ 'A' => [
+ 'Á',
+ 'À',
+ 'Ả',
+ 'Ã',
+ 'Ạ',
+ 'Ă',
+ 'Ắ',
+ 'Ằ',
+ 'Ẳ',
+ 'Ẵ',
+ 'Ặ',
+ 'Â',
+ 'Ấ',
+ 'Ầ',
+ 'Ẩ',
+ 'Ẫ',
+ 'Ậ',
+ 'Å',
+ 'Ā',
+ 'Ą',
+ 'Α',
+ 'Ά',
+ 'Ἀ',
+ 'Ἁ',
+ 'Ἂ',
+ 'Ἃ',
+ 'Ἄ',
+ 'Ἅ',
+ 'Ἆ',
+ 'Ἇ',
+ 'ᾈ',
+ 'ᾉ',
+ 'ᾊ',
+ 'ᾋ',
+ 'ᾌ',
+ 'ᾍ',
+ 'ᾎ',
+ 'ᾏ',
+ 'Ᾰ',
+ 'Ᾱ',
+ 'Ὰ',
+ 'Ά',
+ 'ᾼ',
+ 'А',
+ 'Ǻ',
+ 'Ǎ',
+ 'A',
+ 'Ä',
+ ],
+ 'B' => ['Б', 'Β', 'ब', 'B'],
+ 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'],
+ 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'],
+ 'E' => [
+ 'É',
+ 'È',
+ 'Ẻ',
+ 'Ẽ',
+ 'Ẹ',
+ 'Ê',
+ 'Ế',
+ 'Ề',
+ 'Ể',
+ 'Ễ',
+ 'Ệ',
+ 'Ë',
+ 'Ē',
+ 'Ę',
+ 'Ě',
+ 'Ĕ',
+ 'Ė',
+ 'Ε',
+ 'Έ',
+ 'Ἐ',
+ 'Ἑ',
+ 'Ἒ',
+ 'Ἓ',
+ 'Ἔ',
+ 'Ἕ',
+ 'Έ',
+ 'Ὲ',
+ 'Е',
+ 'Ё',
+ 'Э',
+ 'Є',
+ 'Ə',
+ 'E',
+ ],
+ 'F' => ['Ф', 'Φ', 'F'],
+ 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'],
+ 'H' => ['Η', 'Ή', 'Ħ', 'H'],
+ 'I' => [
+ 'Í',
+ 'Ì',
+ 'Ỉ',
+ 'Ĩ',
+ 'Ị',
+ 'Î',
+ 'Ï',
+ 'Ī',
+ 'Ĭ',
+ 'Į',
+ 'İ',
+ 'Ι',
+ 'Ί',
+ 'Ϊ',
+ 'Ἰ',
+ 'Ἱ',
+ 'Ἳ',
+ 'Ἴ',
+ 'Ἵ',
+ 'Ἶ',
+ 'Ἷ',
+ 'Ῐ',
+ 'Ῑ',
+ 'Ὶ',
+ 'Ί',
+ 'И',
+ 'І',
+ 'Ї',
+ 'Ǐ',
+ 'ϒ',
+ 'I',
+ ],
+ 'J' => ['J'],
+ 'K' => ['К', 'Κ', 'K'],
+ 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'],
+ 'M' => ['М', 'Μ', 'M'],
+ 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'],
+ 'O' => [
+ 'Ó',
+ 'Ò',
+ 'Ỏ',
+ 'Õ',
+ 'Ọ',
+ 'Ô',
+ 'Ố',
+ 'Ồ',
+ 'Ổ',
+ 'Ỗ',
+ 'Ộ',
+ 'Ơ',
+ 'Ớ',
+ 'Ờ',
+ 'Ở',
+ 'Ỡ',
+ 'Ợ',
+ 'Ø',
+ 'Ō',
+ 'Ő',
+ 'Ŏ',
+ 'Ο',
+ 'Ό',
+ 'Ὀ',
+ 'Ὁ',
+ 'Ὂ',
+ 'Ὃ',
+ 'Ὄ',
+ 'Ὅ',
+ 'Ὸ',
+ 'Ό',
+ 'О',
+ 'Θ',
+ 'Ө',
+ 'Ǒ',
+ 'Ǿ',
+ 'O',
+ 'Ö',
+ ],
+ 'P' => ['П', 'Π', 'P'],
+ 'Q' => ['Q'],
+ 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'],
+ 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'],
+ 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'],
+ 'U' => [
+ 'Ú',
+ 'Ù',
+ 'Ủ',
+ 'Ũ',
+ 'Ụ',
+ 'Ư',
+ 'Ứ',
+ 'Ừ',
+ 'Ử',
+ 'Ữ',
+ 'Ự',
+ 'Û',
+ 'Ū',
+ 'Ů',
+ 'Ű',
+ 'Ŭ',
+ 'Ų',
+ 'У',
+ 'Ǔ',
+ 'Ǖ',
+ 'Ǘ',
+ 'Ǚ',
+ 'Ǜ',
+ 'U',
+ 'Ў',
+ 'Ü',
+ ],
+ 'V' => ['В', 'V'],
+ 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'],
+ 'X' => ['Χ', 'Ξ', 'X'],
+ 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'],
+ 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'],
+ 'AE' => ['Æ', 'Ǽ'],
+ 'Ch' => ['Ч'],
+ 'Dj' => ['Ђ'],
+ 'Dz' => ['Џ'],
+ 'Gx' => ['Ĝ'],
+ 'Hx' => ['Ĥ'],
+ 'Ij' => ['IJ'],
+ 'Jx' => ['Ĵ'],
+ 'Kh' => ['Х'],
+ 'Lj' => ['Љ'],
+ 'Nj' => ['Њ'],
+ 'Oe' => ['Œ'],
+ 'Ps' => ['Ψ'],
+ 'Sh' => ['Ш'],
+ 'Shch' => ['Щ'],
+ 'Ss' => ['ẞ'],
+ 'Th' => ['Þ'],
+ 'Ts' => ['Ц'],
+ 'Ya' => ['Я'],
+ 'Yu' => ['Ю'],
+ 'Zh' => ['Ж'],
+ ' ' => [
+ "\xC2\xA0",
+ "\xE2\x80\x80",
+ "\xE2\x80\x81",
+ "\xE2\x80\x82",
+ "\xE2\x80\x83",
+ "\xE2\x80\x84",
+ "\xE2\x80\x85",
+ "\xE2\x80\x86",
+ "\xE2\x80\x87",
+ "\xE2\x80\x88",
+ "\xE2\x80\x89",
+ "\xE2\x80\x8A",
+ "\xE2\x80\xAF",
+ "\xE2\x81\x9F",
+ "\xE3\x80\x80",
+ "\xEF\xBE\xA0",
+ ],
+ ];
+ }
+
+ /**
+ * Returns the language specific replacements for the ascii method.
+ * Note: Adapted from Stringy\Stringy.
+ *
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ * @return null|array
+ */
+ protected static function languageSpecificCharsArray(string $language)
+ {
+ static $languageSpecific;
+
+ if (! isset($languageSpecific)) {
+ $languageSpecific = [
+ 'bg' => [
+ ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'],
+ ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'],
+ ],
+ 'de' => [
+ ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'],
+ ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'],
+ ],
+ ];
+ }
+
+ return $languageSpecific[$language] ?? null;
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Stringable.php b/addons/epay/library/hyperf/utils/src/Stringable.php
new file mode 100644
index 0000000..e0f7428
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Stringable.php
@@ -0,0 +1,803 @@
+value = (string) $value;
+ }
+
+ /**
+ * Proxy dynamic properties onto methods.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ return $this->{$key}();
+ }
+
+ /**
+ * Get the raw string value.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->value;
+ }
+
+ /**
+ * Return the remainder of a string after the first occurrence of a given value.
+ *
+ * @param string $search
+ * @return static
+ */
+ public function after($search)
+ {
+ return new static(Str::after($this->value, $search));
+ }
+
+ /**
+ * Return the remainder of a string after the last occurrence of a given value.
+ *
+ * @param string $search
+ * @return static
+ */
+ public function afterLast($search)
+ {
+ return new static(Str::afterLast($this->value, $search));
+ }
+
+ /**
+ * Append the given values to the string.
+ *
+ * @param array $values
+ * @return static
+ */
+ public function append(...$values)
+ {
+ return new static($this->value . implode('', $values));
+ }
+
+ /**
+ * Transliterate a UTF-8 value to ASCII.
+ *
+ * @param string $language
+ * @return static
+ */
+ public function ascii($language = 'en')
+ {
+ return new static(Str::ascii($this->value, $language));
+ }
+
+ /**
+ * Get the trailing name component of the path.
+ *
+ * @param string $suffix
+ * @return static
+ */
+ public function basename($suffix = '')
+ {
+ return new static(basename($this->value, $suffix));
+ }
+
+ /**
+ * Get the basename of the class path.
+ *
+ * @return static
+ */
+ public function classBasename()
+ {
+ return new static(class_basename($this->value));
+ }
+
+ /**
+ * Get the portion of a string before the first occurrence of a given value.
+ *
+ * @param string $search
+ * @return static
+ */
+ public function before($search)
+ {
+ return new static(Str::before($this->value, $search));
+ }
+
+ /**
+ * Get the portion of a string before the last occurrence of a given value.
+ *
+ * @param string $search
+ * @return static
+ */
+ public function beforeLast($search)
+ {
+ return new static(Str::beforeLast($this->value, $search));
+ }
+
+ /**
+ * Get the portion of a string between two given values.
+ *
+ * @param string $from
+ * @param string $to
+ * @return static
+ */
+ public function between($from, $to)
+ {
+ return new static(Str::between($this->value, $from, $to));
+ }
+
+ /**
+ * Convert a value to camel case.
+ *
+ * @return static
+ */
+ public function camel()
+ {
+ return new static(Str::camel($this->value));
+ }
+
+ /**
+ * Determine if a given string contains a given substring.
+ *
+ * @param array|string $needles
+ * @return bool
+ */
+ public function contains($needles)
+ {
+ return Str::contains($this->value, $needles);
+ }
+
+ /**
+ * Determine if a given string contains all array values.
+ *
+ * @return bool
+ */
+ public function containsAll(array $needles)
+ {
+ return Str::containsAll($this->value, $needles);
+ }
+
+ /**
+ * Get the parent directory's path.
+ *
+ * @param int $levels
+ * @return static
+ */
+ public function dirname($levels = 1)
+ {
+ return new static(dirname($this->value, $levels));
+ }
+
+ /**
+ * Determine if a given string ends with a given substring.
+ *
+ * @param array|string $needles
+ * @return bool
+ */
+ public function endsWith($needles)
+ {
+ return Str::endsWith($this->value, $needles);
+ }
+
+ /**
+ * Determine if the string is an exact match with the given value.
+ *
+ * @param string $value
+ * @return bool
+ */
+ public function exactly($value)
+ {
+ return $this->value === $value;
+ }
+
+ /**
+ * Explode the string into an array.
+ *
+ * @param string $delimiter
+ * @param int $limit
+ * @return \Hyperf\Utils\Collection
+ */
+ public function explode($delimiter, $limit = PHP_INT_MAX)
+ {
+ return collect(explode($delimiter, $this->value, $limit));
+ }
+
+ /**
+ * Split a string using a regular expression or by length.
+ *
+ * @param int|string $pattern
+ * @param int $limit
+ * @param int $flags
+ * @return \Hyperf\Utils\Collection
+ */
+ public function split($pattern, $limit = -1, $flags = 0)
+ {
+ if (filter_var($pattern, FILTER_VALIDATE_INT) !== false) {
+ return collect(mb_str_split($this->value, $pattern));
+ }
+
+ $segments = preg_split($pattern, $this->value, $limit, $flags);
+
+ return ! empty($segments) ? collect($segments) : collect();
+ }
+
+ /**
+ * Cap a string with a single instance of a given value.
+ *
+ * @param string $cap
+ * @return static
+ */
+ public function finish($cap)
+ {
+ return new static(Str::finish($this->value, $cap));
+ }
+
+ /**
+ * Determine if a given string matches a given pattern.
+ *
+ * @param array|string $pattern
+ * @return bool
+ */
+ public function is($pattern)
+ {
+ return Str::is($pattern, $this->value);
+ }
+
+ /**
+ * Determine if the given string is empty.
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return $this->value === '';
+ }
+
+ /**
+ * Determine if the given string is not empty.
+ *
+ * @return bool
+ */
+ public function isNotEmpty()
+ {
+ return ! $this->isEmpty();
+ }
+
+ /**
+ * Convert a string to kebab case.
+ *
+ * @return static
+ */
+ public function kebab()
+ {
+ return new static(Str::kebab($this->value));
+ }
+
+ /**
+ * Return the length of the given string.
+ *
+ * @param string $encoding
+ * @return int
+ */
+ public function length($encoding = null)
+ {
+ return Str::length($this->value, $encoding);
+ }
+
+ /**
+ * Limit the number of characters in a string.
+ *
+ * @param int $limit
+ * @param string $end
+ * @return static
+ */
+ public function limit($limit = 100, $end = '...')
+ {
+ return new static(Str::limit($this->value, $limit, $end));
+ }
+
+ /**
+ * Convert the given string to lower-case.
+ *
+ * @return static
+ */
+ public function lower()
+ {
+ return new static(Str::lower($this->value));
+ }
+
+ /**
+ * Get the string matching the given pattern.
+ *
+ * @param string $pattern
+ * @return static
+ */
+ public function match($pattern)
+ {
+ preg_match($pattern, $this->value, $matches);
+
+ if (! $matches) {
+ return new static();
+ }
+
+ return new static($matches[1] ?? $matches[0]);
+ }
+
+ /**
+ * Get the string matching the given pattern.
+ *
+ * @param string $pattern
+ * @return \Hyperf\Utils\Collection
+ */
+ public function matchAll($pattern)
+ {
+ preg_match_all($pattern, $this->value, $matches);
+
+ if (empty($matches[0])) {
+ return collect();
+ }
+
+ return collect($matches[1] ?? $matches[0]);
+ }
+
+ /**
+ * Determine if the string matches the given pattern.
+ *
+ * @param string $pattern
+ * @return bool
+ */
+ public function test($pattern)
+ {
+ return $this->match($pattern)->isNotEmpty();
+ }
+
+ /**
+ * Pad both sides of the string with another.
+ *
+ * @param int $length
+ * @param string $pad
+ * @return static
+ */
+ public function padBoth($length, $pad = ' ')
+ {
+ return new static(Str::padBoth($this->value, $length, $pad));
+ }
+
+ /**
+ * Pad the left side of the string with another.
+ *
+ * @param int $length
+ * @param string $pad
+ * @return static
+ */
+ public function padLeft($length, $pad = ' ')
+ {
+ return new static(Str::padLeft($this->value, $length, $pad));
+ }
+
+ /**
+ * Pad the right side of the string with another.
+ *
+ * @param int $length
+ * @param string $pad
+ * @return static
+ */
+ public function padRight($length, $pad = ' ')
+ {
+ return new static(Str::padRight($this->value, $length, $pad));
+ }
+
+ /**
+ * Parse a Class@method style callback into class and method.
+ *
+ * @param null|string $default
+ * @return array
+ */
+ public function parseCallback($default = null)
+ {
+ return Str::parseCallback($this->value, $default);
+ }
+
+ /**
+ * Call the given callback and return a new string.
+ *
+ * @return static
+ */
+ public function pipe(callable $callback)
+ {
+ return new static(call_user_func($callback, $this));
+ }
+
+ /**
+ * Get the plural form of an English word.
+ *
+ * @param int $count
+ * @return static
+ */
+ public function plural($count = 2)
+ {
+ return new static(Str::plural($this->value, $count));
+ }
+
+ /**
+ * Pluralize the last word of an English, studly caps case string.
+ *
+ * @param int $count
+ * @return static
+ */
+ public function pluralStudly($count = 2)
+ {
+ return new static(Str::pluralStudly($this->value, $count));
+ }
+
+ /**
+ * Prepend the given values to the string.
+ *
+ * @param array $values
+ * @return static
+ */
+ public function prepend(...$values)
+ {
+ return new static(implode('', $values) . $this->value);
+ }
+
+ /**
+ * Remove any occurrence of the given string in the subject.
+ *
+ * @param array|string $search
+ * @param bool $caseSensitive
+ * @return static
+ */
+ public function remove($search, $caseSensitive = true)
+ {
+ return new static(Str::remove($search, $this->value, $caseSensitive));
+ }
+
+ /**
+ * Repeat the string.
+ *
+ * @return static
+ */
+ public function repeat(int $times)
+ {
+ return new static(Str::repeat($this->value, $times));
+ }
+
+ /**
+ * Replace the given value in the given string.
+ *
+ * @param string|string[] $search
+ * @param string|string[] $replace
+ * @return static
+ */
+ public function replace($search, $replace)
+ {
+ return new static(Str::replace($search, $replace, $this->value));
+ }
+
+ /**
+ * Replace a given value in the string sequentially with an array.
+ *
+ * @param string $search
+ * @return static
+ */
+ public function replaceArray($search, array $replace)
+ {
+ return new static(Str::replaceArray($search, $replace, $this->value));
+ }
+
+ /**
+ * Replace the first occurrence of a given value in the string.
+ *
+ * @param string $search
+ * @param string $replace
+ * @return static
+ */
+ public function replaceFirst($search, $replace)
+ {
+ return new static(Str::replaceFirst($search, $replace, $this->value));
+ }
+
+ /**
+ * Replace the last occurrence of a given value in the string.
+ *
+ * @param string $search
+ * @param string $replace
+ * @return static
+ */
+ public function replaceLast($search, $replace)
+ {
+ return new static(Str::replaceLast($search, $replace, $this->value));
+ }
+
+ /**
+ * Replace the patterns matching the given regular expression.
+ *
+ * @param string $pattern
+ * @param \Closure|string $replace
+ * @param int $limit
+ * @return static
+ */
+ public function replaceMatches($pattern, $replace, $limit = -1)
+ {
+ if ($replace instanceof Closure) {
+ return new static(preg_replace_callback($pattern, $replace, $this->value, $limit));
+ }
+
+ return new static(preg_replace($pattern, $replace, $this->value, $limit));
+ }
+
+ /**
+ * Begin a string with a single instance of a given value.
+ *
+ * @param string $prefix
+ * @return static
+ */
+ public function start($prefix)
+ {
+ return new static(Str::start($this->value, $prefix));
+ }
+
+ /**
+ * Strip HTML and PHP tags from the given string.
+ *
+ * @param null|string|string[] $allowedTags
+ * @return static
+ */
+ public function stripTags($allowedTags = null)
+ {
+ return new static(strip_tags($this->value, $allowedTags));
+ }
+
+ /**
+ * Convert the given string to upper-case.
+ *
+ * @return static
+ */
+ public function upper()
+ {
+ return new static(Str::upper($this->value));
+ }
+
+ /**
+ * Convert the given string to title case.
+ *
+ * @return static
+ */
+ public function title()
+ {
+ return new static(Str::title($this->value));
+ }
+
+ /**
+ * Get the singular form of an English word.
+ *
+ * @return static
+ */
+ public function singular()
+ {
+ return new static(Str::singular($this->value));
+ }
+
+ /**
+ * Generate a URL friendly "slug" from a given string.
+ *
+ * @param string $separator
+ * @param null|string $language
+ * @return static
+ */
+ public function slug($separator = '-', $language = 'en')
+ {
+ return new static(Str::slug($this->value, $separator, $language));
+ }
+
+ /**
+ * Convert a string to snake case.
+ *
+ * @param string $delimiter
+ * @return static
+ */
+ public function snake($delimiter = '_')
+ {
+ return new static(Str::snake($this->value, $delimiter));
+ }
+
+ /**
+ * Determine if a given string starts with a given substring.
+ *
+ * @param array|string $needles
+ * @return bool
+ */
+ public function startsWith($needles)
+ {
+ return Str::startsWith($this->value, $needles);
+ }
+
+ /**
+ * Convert a value to studly caps case.
+ *
+ * @return static
+ */
+ public function studly()
+ {
+ return new static(Str::studly($this->value));
+ }
+
+ /**
+ * Returns the portion of the string specified by the start and length parameters.
+ *
+ * @param int $start
+ * @param null|int $length
+ * @return static
+ */
+ public function substr($start, $length = null)
+ {
+ return new static(Str::substr($this->value, $start, $length));
+ }
+
+ /**
+ * Returns the number of substring occurrences.
+ *
+ * @param string $needle
+ * @param null|int $offset
+ * @param null|int $length
+ * @return int
+ */
+ public function substrCount($needle, $offset = null, $length = null)
+ {
+ return Str::substrCount($this->value, $needle, $offset ?? 0, $length);
+ }
+
+ /**
+ * Trim the string of the given characters.
+ *
+ * @param string $characters
+ * @return static
+ */
+ public function trim($characters = null)
+ {
+ return new static(trim(...array_merge([$this->value], func_get_args())));
+ }
+
+ /**
+ * Left trim the string of the given characters.
+ *
+ * @param string $characters
+ * @return static
+ */
+ public function ltrim($characters = null)
+ {
+ return new static(ltrim(...array_merge([$this->value], func_get_args())));
+ }
+
+ /**
+ * Right trim the string of the given characters.
+ *
+ * @param string $characters
+ * @return static
+ */
+ public function rtrim($characters = null)
+ {
+ return new static(rtrim(...array_merge([$this->value], func_get_args())));
+ }
+
+ /**
+ * Make a string's first character uppercase.
+ *
+ * @return static
+ */
+ public function ucfirst()
+ {
+ return new static(Str::ucfirst($this->value));
+ }
+
+ /**
+ * Replaces the first or the last ones chars from a string by a given char.
+ *
+ * @param int $offset if is negative it starts from the end
+ * @param string $replacement default is *
+ * @return static
+ */
+ public function mask(int $offset = 0, int $length = 0, string $replacement = '*')
+ {
+ return new static(Str::mask($this->value, $offset, $length, $replacement));
+ }
+
+ /**
+ * Execute the given callback if the string is empty.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function whenEmpty($callback)
+ {
+ if ($this->isEmpty()) {
+ $result = $callback($this);
+
+ return is_null($result) ? $this : $result;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Execute the given callback if the string is not empty.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function whenNotEmpty($callback)
+ {
+ if ($this->isNotEmpty()) {
+ $result = $callback($this);
+
+ return is_null($result) ? $this : $result;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Limit the number of words in a string.
+ *
+ * @param int $words
+ * @param string $end
+ * @return static
+ */
+ public function words($words = 100, $end = '...')
+ {
+ return new static(Str::words($this->value, $words, $end));
+ }
+
+ /**
+ * Get the number of words a string contains.
+ *
+ * @return int
+ */
+ public function wordCount()
+ {
+ return str_word_count($this->value);
+ }
+
+ /**
+ * Convert the object to a string when JSON encoded.
+ *
+ * @return string
+ */
+ public function jsonSerialize()
+ {
+ return $this->__toString();
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Traits/Conditionable.php b/addons/epay/library/hyperf/utils/src/Traits/Conditionable.php
new file mode 100644
index 0000000..f21fd1c
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Traits/Conditionable.php
@@ -0,0 +1,57 @@
+getTargetObject();
+ return $target->{$name}(...$arguments);
+ }
+
+ public function __get($name)
+ {
+ $target = $this->getTargetObject();
+ return $target->{$name};
+ }
+
+ public function __set($name, $value)
+ {
+ $target = $this->getTargetObject();
+ return $target->{$name} = $value;
+ }
+
+ protected function getTargetObject()
+ {
+ if (! isset($this->proxyKey)) {
+ throw new \RuntimeException('$proxyKey property of class missing.');
+ }
+ return Context::get($this->proxyKey);
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Traits/ForwardsCalls.php b/addons/epay/library/hyperf/utils/src/Traits/ForwardsCalls.php
new file mode 100644
index 0000000..aa3d080
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Traits/ForwardsCalls.php
@@ -0,0 +1,53 @@
+{$method}(...$parameters);
+ } catch (Error|BadMethodCallException $e) {
+ $pattern = '~^Call to undefined method (?P[^:]+)::(?P[^\(]+)\(\)$~';
+
+ if (! preg_match($pattern, $e->getMessage(), $matches)) {
+ throw $e;
+ }
+
+ if ($matches['class'] !== get_class($object) || $matches['method'] !== $method) {
+ throw $e;
+ }
+
+ static::throwBadMethodCallException($method);
+ }
+ }
+
+ /**
+ * Throw a bad method call exception for the given method.
+ * @throws BadMethodCallException
+ */
+ protected static function throwBadMethodCallException(string $method): void
+ {
+ throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $method));
+ }
+}
diff --git a/addons/epay/library/hyperf/utils/src/Traits/Macroable.php b/addons/epay/library/hyperf/utils/src/Traits/Macroable.php
new file mode 100644
index 0000000..815e027
--- /dev/null
+++ b/addons/epay/library/hyperf/utils/src/Traits/Macroable.php
@@ -0,0 +1,20 @@
+popTimeout = $timeout;
+ }
+
+ /**
+ * @param null|float $timeout seconds
+ */
+ public function wait(Closure $closure, ?float $timeout = null)
+ {
+ if ($timeout === null) {
+ $timeout = $this->popTimeout;
+ }
+
+ $channel = new Channel(1);
+ Coroutine::create(function () use ($channel, $closure) {
+ try {
+ $result = $closure();
+ } catch (Throwable $exception) {
+ $result = new ExceptionThrower($exception);
+ } finally {
+ $channel->push($result ?? null, $this->pushTimeout);
+ }
+ });
+
+ $result = $channel->pop($timeout);
+ if ($result === false && $channel->isTimeout()) {
+ throw new WaitTimeoutException(sprintf('Channel wait failed, reason: Timed out for %s s', $timeout));
+ }
+ if ($result instanceof ExceptionThrower) {
+ throw $result->getThrowable();
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayApplicationInterface.php b/addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayApplicationInterface.php
new file mode 100644
index 0000000..b4e296a
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayApplicationInterface.php
@@ -0,0 +1,83 @@
+
+ *
+ * @param string $gateway
+ * @param array $params
+ *
+ * @return Collection|Response
+ */
+ public function pay($gateway, $params);
+
+ /**
+ * Query an order.
+ *
+ * @author yansongda
+ *
+ * @param string|array $order
+ *
+ * @return Collection
+ */
+ public function find($order, string $type);
+
+ /**
+ * Refund an order.
+ *
+ * @author yansongda
+ *
+ * @return Collection
+ */
+ public function refund(array $order);
+
+ /**
+ * Cancel an order.
+ *
+ * @author yansongda
+ *
+ * @param string|array $order
+ *
+ * @return Collection
+ */
+ public function cancel($order);
+
+ /**
+ * Close an order.
+ *
+ * @author yansongda
+ *
+ * @param string|array $order
+ *
+ * @return Collection
+ */
+ public function close($order);
+
+ /**
+ * Verify a request.
+ *
+ * @author yansongda
+ *
+ * @param string|array|null $content
+ *
+ * @return Collection
+ */
+ public function verify($content, bool $refund);
+
+ /**
+ * Echo success to server.
+ *
+ * @author yansongda
+ *
+ * @return Response
+ */
+ public function success();
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayInterface.php b/addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayInterface.php
new file mode 100644
index 0000000..8286763
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Contracts/GatewayInterface.php
@@ -0,0 +1,20 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @return Collection|Response
+ */
+ public function pay($endpoint, array $payload);
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events.php b/addons/epay/library/v2/Yansongda/Pay/Events.php
new file mode 100644
index 0000000..a747305
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events.php
@@ -0,0 +1,98 @@
+
+ *
+ * @method static Event dispatch(Event $event) Dispatches an event to all registered listeners
+ * @method static array getListeners($eventName = null) Gets the listeners of a specific event or all listeners sorted by descending priority.
+ * @method static int|void getListenerPriority($eventName, $listener) Gets the listener priority for a specific event.
+ * @method static bool hasListeners($eventName = null) Checks whether an event has any registered listeners.
+ * @method static void addListener($eventName, $listener, $priority = 0) Adds an event listener that listens on the specified events.
+ * @method static removeListener($eventName, $listener) Removes an event listener from the specified events.
+ * @method static void addSubscriber(EventSubscriberInterface $subscriber) Adds an event subscriber.
+ * @method static void removeSubscriber(EventSubscriberInterface $subscriber)
+ */
+class Events
+{
+ /**
+ * dispatcher.
+ *
+ * @var EventDispatcher
+ */
+ protected static $dispatcher;
+
+ /**
+ * Forward call.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @throws Exception
+ *
+ * @return mixed
+ */
+ public static function __callStatic($method, $args)
+ {
+ return call_user_func_array([self::getDispatcher(), $method], $args);
+ }
+
+ /**
+ * Forward call.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @throws Exception
+ *
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array([self::getDispatcher(), $method], $args);
+ }
+
+ /**
+ * setDispatcher.
+ *
+ * @author yansongda
+ */
+ public static function setDispatcher(EventDispatcher $dispatcher)
+ {
+ self::$dispatcher = $dispatcher;
+ }
+
+ /**
+ * getDispatcher.
+ *
+ * @author yansongda
+ */
+ public static function getDispatcher(): EventDispatcher
+ {
+ if (self::$dispatcher) {
+ return self::$dispatcher;
+ }
+
+ return self::$dispatcher = self::createDispatcher();
+ }
+
+ /**
+ * createDispatcher.
+ *
+ * @author yansongda
+ */
+ public static function createDispatcher(): EventDispatcher
+ {
+ return new EventDispatcher();
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/ApiRequested.php b/addons/epay/library/v2/Yansongda/Pay/Events/ApiRequested.php
new file mode 100644
index 0000000..845ac87
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/ApiRequested.php
@@ -0,0 +1,31 @@
+endpoint = $endpoint;
+ $this->result = $result;
+
+ parent::__construct($driver, $gateway);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/ApiRequesting.php b/addons/epay/library/v2/Yansongda/Pay/Events/ApiRequesting.php
new file mode 100644
index 0000000..14c9fdd
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/ApiRequesting.php
@@ -0,0 +1,31 @@
+endpoint = $endpoint;
+ $this->payload = $payload;
+
+ parent::__construct($driver, $gateway);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/Event.php b/addons/epay/library/v2/Yansongda/Pay/Events/Event.php
new file mode 100644
index 0000000..1ef883c
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/Event.php
@@ -0,0 +1,40 @@
+
+ */
+ public function __construct(string $driver, string $gateway)
+ {
+ $this->driver = $driver;
+ $this->gateway = $gateway;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/MethodCalled.php b/addons/epay/library/v2/Yansongda/Pay/Events/MethodCalled.php
new file mode 100644
index 0000000..d199f23
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/MethodCalled.php
@@ -0,0 +1,33 @@
+
+ */
+ public function __construct(string $driver, string $gateway, string $endpoint, array $payload = [])
+ {
+ $this->endpoint = $endpoint;
+ $this->payload = $payload;
+
+ parent::__construct($driver, $gateway);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/PayStarted.php b/addons/epay/library/v2/Yansongda/Pay/Events/PayStarted.php
new file mode 100644
index 0000000..845d93c
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/PayStarted.php
@@ -0,0 +1,31 @@
+endpoint = $endpoint;
+ $this->payload = $payload;
+
+ parent::__construct($driver, $gateway);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/PayStarting.php b/addons/epay/library/v2/Yansongda/Pay/Events/PayStarting.php
new file mode 100644
index 0000000..ad73d9d
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/PayStarting.php
@@ -0,0 +1,23 @@
+params = $params;
+
+ parent::__construct($driver, $gateway);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/RequestReceived.php b/addons/epay/library/v2/Yansongda/Pay/Events/RequestReceived.php
new file mode 100644
index 0000000..3620b12
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/RequestReceived.php
@@ -0,0 +1,25 @@
+
+ */
+ public function __construct(string $driver, string $gateway, array $data)
+ {
+ $this->data = $data;
+
+ parent::__construct($driver, $gateway);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Events/SignFailed.php b/addons/epay/library/v2/Yansongda/Pay/Events/SignFailed.php
new file mode 100644
index 0000000..4e45932
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Events/SignFailed.php
@@ -0,0 +1,25 @@
+
+ */
+ public function __construct(string $driver, string $gateway, array $data)
+ {
+ $this->data = $data;
+
+ parent::__construct($driver, $gateway);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Exceptions/BusinessException.php b/addons/epay/library/v2/Yansongda/Pay/Exceptions/BusinessException.php
new file mode 100644
index 0000000..8e94a4f
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Exceptions/BusinessException.php
@@ -0,0 +1,19 @@
+
+ *
+ * @param string $message
+ * @param array|string $raw
+ */
+ public function __construct($message, $raw = [])
+ {
+ parent::__construct('ERROR_BUSINESS: '.$message, $raw, self::ERROR_BUSINESS);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Exceptions/Exception.php b/addons/epay/library/v2/Yansongda/Pay/Exceptions/Exception.php
new file mode 100644
index 0000000..9d007ad
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Exceptions/Exception.php
@@ -0,0 +1,44 @@
+
+ *
+ * @param string $message
+ * @param array|string $raw
+ * @param int|string $code
+ */
+ public function __construct($message = '', $raw = [], $code = self::UNKNOWN_ERROR)
+ {
+ $message = '' === $message ? 'Unknown Error' : $message;
+ $this->raw = is_array($raw) ? $raw : [$raw];
+
+ parent::__construct($message, intval($code));
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Exceptions/GatewayException.php b/addons/epay/library/v2/Yansongda/Pay/Exceptions/GatewayException.php
new file mode 100644
index 0000000..e844801
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Exceptions/GatewayException.php
@@ -0,0 +1,20 @@
+
+ *
+ * @param string $message
+ * @param array|string $raw
+ * @param int $code
+ */
+ public function __construct($message, $raw = [], $code = self::ERROR_GATEWAY)
+ {
+ parent::__construct('ERROR_GATEWAY: '.$message, $raw, $code);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidArgumentException.php b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidArgumentException.php
new file mode 100644
index 0000000..2ecb16b
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidArgumentException.php
@@ -0,0 +1,19 @@
+
+ *
+ * @param string $message
+ * @param array|string $raw
+ */
+ public function __construct($message, $raw = [])
+ {
+ parent::__construct('INVALID_ARGUMENT: '.$message, $raw, self::INVALID_ARGUMENT);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidConfigException.php b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidConfigException.php
new file mode 100644
index 0000000..5719310
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidConfigException.php
@@ -0,0 +1,19 @@
+
+ *
+ * @param string $message
+ * @param array|string $raw
+ */
+ public function __construct($message, $raw = [])
+ {
+ parent::__construct('INVALID_CONFIG: '.$message, $raw, self::INVALID_CONFIG);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidGatewayException.php b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidGatewayException.php
new file mode 100644
index 0000000..3f4067d
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidGatewayException.php
@@ -0,0 +1,19 @@
+
+ *
+ * @param string $message
+ * @param array|string $raw
+ */
+ public function __construct($message, $raw = [])
+ {
+ parent::__construct('INVALID_GATEWAY: '.$message, $raw, self::INVALID_GATEWAY);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidSignException.php b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidSignException.php
new file mode 100644
index 0000000..135c8a5
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Exceptions/InvalidSignException.php
@@ -0,0 +1,19 @@
+
+ *
+ * @param string $message
+ * @param array|string $raw
+ */
+ public function __construct($message, $raw = [])
+ {
+ parent::__construct('INVALID_SIGN: '.$message, $raw, self::INVALID_SIGN);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay.php
new file mode 100644
index 0000000..30df2e7
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay.php
@@ -0,0 +1,423 @@
+ 'https://openapi.alipay.com/gateway.do?charset=utf-8',
+ self::MODE_SERVICE => 'https://openapi.alipay.com/gateway.do?charset=utf-8',
+ self::MODE_DEV => 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8',
+ ];
+
+ /**
+ * Alipay payload.
+ *
+ * @var array
+ */
+ protected $payload;
+
+ /**
+ * Alipay gateway.
+ *
+ * @var string
+ */
+ protected $gateway;
+
+ /**
+ * extends.
+ *
+ * @var array
+ */
+ protected $extends;
+
+ /**
+ * Bootstrap.
+ *
+ * @author yansongda
+ *
+ * @throws \Exception
+ */
+ public function __construct(Config $config)
+ {
+ $this->gateway = Support::create($config)->getBaseUri();
+ $this->payload = [
+ 'app_id' => $config->get('app_id'),
+ 'method' => '',
+ 'format' => 'JSON',
+ 'charset' => 'utf-8',
+ 'sign_type' => 'RSA2',
+ 'version' => '1.0',
+ 'return_url' => $config->get('return_url'),
+ 'notify_url' => $config->get('notify_url'),
+ 'timestamp' => date('Y-m-d H:i:s'),
+ 'sign' => '',
+ 'biz_content' => '',
+ 'app_auth_token' => $config->get('app_auth_token'),
+ ];
+
+ if ($config->get('app_cert_public_key') && $config->get('alipay_root_cert')) {
+ $this->payload['app_cert_sn'] = Support::getCertSN($config->get('app_cert_public_key'));
+ $this->payload['alipay_root_cert_sn'] = Support::getRootCertSN($config->get('alipay_root_cert'));
+ }
+ }
+
+ /**
+ * Magic pay.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param array $params
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidConfigException
+ * @throws InvalidGatewayException
+ * @throws InvalidSignException
+ *
+ * @return Response|Collection
+ */
+ public function __call($method, $params)
+ {
+ if (isset($this->extends[$method])) {
+ return $this->makeExtend($method, ...$params);
+ }
+
+ return $this->pay($method, ...$params);
+ }
+
+ /**
+ * Pay an order.
+ *
+ * @author yansongda
+ *
+ * @param string $gateway
+ * @param array $params
+ *
+ * @throws InvalidGatewayException
+ *
+ * @return Response|Collection
+ */
+ public function pay($gateway, $params = [])
+ {
+ Events::dispatch(new Events\PayStarting('Alipay', $gateway, $params));
+
+ $this->payload['return_url'] = $params['return_url'] ?? $this->payload['return_url'];
+ $this->payload['notify_url'] = $params['notify_url'] ?? $this->payload['notify_url'];
+
+ unset($params['return_url'], $params['notify_url']);
+
+ $this->payload['biz_content'] = json_encode($params);
+
+ $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway';
+
+ if (class_exists($gateway)) {
+ return $this->makePay($gateway);
+ }
+
+ throw new InvalidGatewayException("Pay Gateway [{$gateway}] not exists");
+ }
+
+ /**
+ * Verify sign.
+ *
+ * @author yansongda
+ *
+ * @param array|null $data
+ *
+ * @throws InvalidSignException
+ * @throws InvalidConfigException
+ */
+ public function verify($data = null, bool $refund = false): Collection
+ {
+ if (is_null($data)) {
+ $request = Request::createFromGlobals();
+
+ $data = $request->request->count() > 0 ? $request->request->all() : $request->query->all();
+ }
+
+ if (isset($data['fund_bill_list'])) {
+ $data['fund_bill_list'] = htmlspecialchars_decode($data['fund_bill_list']);
+ }
+
+ Events::dispatch(new Events\RequestReceived('Alipay', '', $data));
+
+ if (Support::verifySign($data)) {
+ return new Collection($data);
+ }
+
+ Events::dispatch(new Events\SignFailed('Alipay', '', $data));
+
+ throw new InvalidSignException('Alipay Sign Verify FAILED', $data);
+ }
+
+ /**
+ * Query an order.
+ *
+ * @author yansongda
+ *
+ * @param string|array $order
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function find($order, string $type = 'wap'): Collection
+ {
+ $gateway = get_class($this).'\\'.Str::studly($type).'Gateway';
+
+ if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) {
+ throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method");
+ }
+
+ $config = call_user_func([new $gateway(), 'find'], $order);
+
+ $this->payload['method'] = $config['method'];
+ $this->payload['biz_content'] = $config['biz_content'];
+ $this->payload['sign'] = Support::generateSign($this->payload);
+
+ Events::dispatch(new Events\MethodCalled('Alipay', 'Find', $this->gateway, $this->payload));
+
+ return Support::requestApi($this->payload);
+ }
+
+ /**
+ * Refund an order.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function refund(array $order): Collection
+ {
+ $this->payload['method'] = 'alipay.trade.refund';
+ $this->payload['biz_content'] = json_encode($order);
+ $this->payload['sign'] = Support::generateSign($this->payload);
+
+ Events::dispatch(new Events\MethodCalled('Alipay', 'Refund', $this->gateway, $this->payload));
+
+ return Support::requestApi($this->payload);
+ }
+
+ /**
+ * Cancel an order.
+ *
+ * @author yansongda
+ *
+ * @param array|string $order
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function cancel($order): Collection
+ {
+ $this->payload['method'] = 'alipay.trade.cancel';
+ $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
+ $this->payload['sign'] = Support::generateSign($this->payload);
+
+ Events::dispatch(new Events\MethodCalled('Alipay', 'Cancel', $this->gateway, $this->payload));
+
+ return Support::requestApi($this->payload);
+ }
+
+ /**
+ * Close an order.
+ *
+ * @param string|array $order
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function close($order): Collection
+ {
+ $this->payload['method'] = 'alipay.trade.close';
+ $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
+ $this->payload['sign'] = Support::generateSign($this->payload);
+
+ Events::dispatch(new Events\MethodCalled('Alipay', 'Close', $this->gateway, $this->payload));
+
+ return Support::requestApi($this->payload);
+ }
+
+ /**
+ * Download bill.
+ *
+ * @author yansongda
+ *
+ * @param string|array $bill
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function download($bill): string
+ {
+ $this->payload['method'] = 'alipay.data.dataservice.bill.downloadurl.query';
+ $this->payload['biz_content'] = json_encode(is_array($bill) ? $bill : ['bill_type' => 'trade', 'bill_date' => $bill]);
+ $this->payload['sign'] = Support::generateSign($this->payload);
+
+ Events::dispatch(new Events\MethodCalled('Alipay', 'Download', $this->gateway, $this->payload));
+
+ $result = Support::requestApi($this->payload);
+
+ return ($result instanceof Collection) ? $result->get('bill_download_url') : '';
+ }
+
+ /**
+ * Reply success to alipay.
+ *
+ * @author yansongda
+ */
+ public function success(): Response
+ {
+ Events::dispatch(new Events\MethodCalled('Alipay', 'Success', $this->gateway));
+
+ return new Response('success');
+ }
+
+ /**
+ * extend.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ * @throws InvalidArgumentException
+ */
+ public function extend(string $method, callable $function, bool $now = true): ?Collection
+ {
+ if (!$now && !method_exists($this, $method)) {
+ $this->extends[$method] = $function;
+
+ return null;
+ }
+
+ $customize = $function($this->payload);
+
+ if (!is_array($customize) && !($customize instanceof Collection)) {
+ throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
+ }
+
+ Events::dispatch(new Events\MethodCalled('Alipay', 'extend', $this->gateway, $customize));
+
+ if (is_array($customize)) {
+ $this->payload = $customize;
+ $this->payload['sign'] = Support::generateSign($this->payload);
+
+ return Support::requestApi($this->payload);
+ }
+
+ return $customize;
+ }
+
+ /**
+ * Make pay gateway.
+ *
+ * @author yansongda
+ *
+ * @throws InvalidGatewayException
+ *
+ * @return Response|Collection
+ */
+ protected function makePay(string $gateway)
+ {
+ $app = new $gateway();
+
+ if ($app instanceof GatewayInterface) {
+ return $app->pay($this->gateway, array_filter($this->payload, function ($value) {
+ return '' !== $value && !is_null($value);
+ }));
+ }
+
+ throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface");
+ }
+
+ /**
+ * makeExtend.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ protected function makeExtend(string $method, array ...$params): Collection
+ {
+ $params = count($params) >= 1 ? $params[0] : $params;
+
+ $function = $this->extends[$method];
+
+ $customize = $function($this->payload, $params);
+
+ if (!is_array($customize) && !($customize instanceof Collection)) {
+ throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
+ }
+
+ Events::dispatch(new Events\MethodCalled(
+ 'Alipay',
+ 'extend - '.$method,
+ $this->gateway,
+ is_array($customize) ? $customize : $customize->toArray()
+ ));
+
+ if (is_array($customize)) {
+ $this->payload = $customize;
+ $this->payload['sign'] = Support::generateSign($this->payload);
+
+ return Support::requestApi($this->payload);
+ }
+
+ return $customize;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/AppGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/AppGateway.php
new file mode 100644
index 0000000..860b44b
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/AppGateway.php
@@ -0,0 +1,38 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws InvalidConfigException
+ * @throws InvalidArgumentException
+ */
+ public function pay($endpoint, array $payload): Response
+ {
+ $payload['method'] = 'alipay.trade.app.pay';
+
+ $biz_array = json_decode($payload['biz_content'], true);
+ if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) {
+ $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid];
+ }
+ $payload['biz_content'] = json_encode(array_merge($biz_array, ['product_code' => 'QUICK_MSECURITY_PAY']));
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Alipay', 'App', $endpoint, $payload));
+
+ return new Response(http_build_query($payload));
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Gateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Gateway.php
new file mode 100644
index 0000000..5f57cc3
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Gateway.php
@@ -0,0 +1,40 @@
+
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct()
+ {
+ $this->mode = Support::getInstance()->mode;
+ }
+
+ /**
+ * Pay an order.
+ *
+ * @author yansongda
+ *
+ * @param string $endpoint
+ *
+ * @return Collection
+ */
+ abstract public function pay($endpoint, array $payload);
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/MiniGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/MiniGateway.php
new file mode 100644
index 0000000..3868c3d
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/MiniGateway.php
@@ -0,0 +1,46 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ *
+ * @see https://docs.alipay.com/mini/introduce/pay
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $biz_array = json_decode($payload['biz_content'], true);
+ if (empty($biz_array['buyer_id']) && empty($biz_array['buyer_open_id'])) {
+ throw new InvalidArgumentException('buyer_id or buyer_open_id required');
+ }
+ if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) {
+ $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid];
+ }
+ $payload['biz_content'] = json_encode($biz_array);
+ $payload['method'] = 'alipay.trade.create';
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Alipay', 'Mini', $endpoint, $payload));
+
+ return Support::requestApi($payload);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/PosGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/PosGateway.php
new file mode 100644
index 0000000..de04761
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/PosGateway.php
@@ -0,0 +1,47 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws InvalidArgumentException
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['method'] = 'alipay.trade.pay';
+ $biz_array = json_decode($payload['biz_content'], true);
+ if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) {
+ $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid];
+ }
+ $payload['biz_content'] = json_encode(array_merge(
+ $biz_array,
+ [
+ 'product_code' => 'FACE_TO_FACE_PAYMENT',
+ 'scene' => 'bar_code',
+ ]
+ ));
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Alipay', 'Pos', $endpoint, $payload));
+
+ return Support::requestApi($payload);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/RefundGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/RefundGateway.php
new file mode 100644
index 0000000..abbb71f
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/RefundGateway.php
@@ -0,0 +1,21 @@
+
+ *
+ * @param $order
+ */
+ public function find($order): array
+ {
+ return [
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => json_encode(is_array($order) ? $order : ['out_trade_no' => $order]),
+ ];
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/ScanGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/ScanGateway.php
new file mode 100644
index 0000000..9d6cbc3
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/ScanGateway.php
@@ -0,0 +1,41 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['method'] = 'alipay.trade.precreate';
+ $biz_array = json_decode($payload['biz_content'], true);
+ if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) {
+ $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid];
+ }
+ $payload['biz_content'] = json_encode(array_merge($biz_array, ['product_code' => '']));
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Alipay', 'Scan', $endpoint, $payload));
+
+ return Support::requestApi($payload);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Support.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Support.php
new file mode 100644
index 0000000..289bb94
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/Support.php
@@ -0,0 +1,452 @@
+
+ *
+ * @property string app_id alipay app_id
+ * @property string ali_public_key
+ * @property string private_key
+ * @property array http http options
+ * @property string mode current mode
+ * @property array log log options
+ * @property string pid ali pid
+ */
+class Support
+{
+ use HasHttpRequest;
+
+ /**
+ * Alipay gateway.
+ *
+ * @var string
+ */
+ protected $baseUri;
+
+ /**
+ * Config.
+ *
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * Instance.
+ *
+ * @var Support
+ */
+ private static $instance;
+
+ /**
+ * Bootstrap.
+ *
+ * @author yansongda
+ */
+ private function __construct(Config $config)
+ {
+ $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)];
+ $this->config = $config;
+
+ $this->setHttpOptions();
+ }
+
+ /**
+ * __get.
+ *
+ * @author yansongda
+ *
+ * @param $key
+ *
+ * @return mixed|Config|null
+ */
+ public function __get($key)
+ {
+ return $this->getConfig($key);
+ }
+
+ /**
+ * create.
+ *
+ * @author yansongda
+ *
+ * @return Support
+ */
+ public static function create(Config $config)
+ {
+ if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) {
+ self::$instance = new self($config);
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * getInstance.
+ *
+ * @author yansongda
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return Support
+ */
+ public static function getInstance()
+ {
+ if (is_null(self::$instance)) {
+ throw new InvalidArgumentException('You Should [Create] First Before Using');
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * clear.
+ *
+ * @author yansongda
+ */
+ public function clear()
+ {
+ self::$instance = null;
+ }
+
+ /**
+ * Get Alipay API result.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public static function requestApi(array $data): Collection
+ {
+ Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data));
+
+ $data = array_filter($data, function ($value) {
+ return ('' == $value || is_null($value)) ? false : true;
+ });
+
+ $result = json_decode(self::$instance->post('', $data), true);
+
+ Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
+
+ return self::processingApiResult($data, $result);
+ }
+
+ /**
+ * Generate sign.
+ *
+ * @author yansongda
+ *
+ * @throws InvalidConfigException
+ */
+ public static function generateSign(array $params): string
+ {
+ $privateKey = self::$instance->private_key;
+
+ if (is_null($privateKey)) {
+ throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
+ }
+
+ if (Str::endsWith($privateKey, '.pem')) {
+ $privateKey = openssl_pkey_get_private(
+ Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey
+ );
+ } else {
+ $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
+ wordwrap($privateKey, 64, "\n", true).
+ "\n-----END RSA PRIVATE KEY-----";
+ }
+
+ openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
+
+ $sign = base64_encode($sign);
+
+ Log::debug('Alipay Generate Sign', [$params, $sign]);
+
+ if (is_resource($privateKey)) {
+ openssl_free_key($privateKey);
+ }
+
+ return $sign;
+ }
+
+ /**
+ * Verify sign.
+ *
+ * @author yansongda
+ *
+ * @param bool $sync
+ * @param string|null $sign
+ *
+ * @throws InvalidConfigException
+ */
+ public static function verifySign(array $data, $sync = false, $sign = null): bool
+ {
+ $publicKey = self::$instance->ali_public_key;
+
+ if (is_null($publicKey)) {
+ throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
+ }
+
+ if (Str::endsWith($publicKey, '.crt')) {
+ $publicKey = file_get_contents($publicKey);
+ } elseif (Str::endsWith($publicKey, '.pem')) {
+ $publicKey = openssl_pkey_get_public(
+ Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey
+ );
+ } else {
+ $publicKey = "-----BEGIN PUBLIC KEY-----\n".
+ wordwrap($publicKey, 64, "\n", true).
+ "\n-----END PUBLIC KEY-----";
+ }
+
+ $sign = $sign ?? $data['sign'];
+
+ $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true);
+
+ $isVerify = 1 === openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
+
+ if (is_resource($publicKey)) {
+ openssl_free_key($publicKey);
+ }
+
+ return $isVerify;
+ }
+
+ /**
+ * Get signContent that is to be signed.
+ *
+ * @author yansongda
+ *
+ * @param bool $verify
+ */
+ public static function getSignContent(array $data, $verify = false): string
+ {
+ ksort($data);
+
+ $stringToBeSigned = '';
+ foreach ($data as $k => $v) {
+ if ($verify && 'sign' != $k && 'sign_type' != $k) {
+ $stringToBeSigned .= $k.'='.$v.'&';
+ }
+ if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) {
+ $stringToBeSigned .= $k.'='.$v.'&';
+ }
+ }
+
+ Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
+
+ return trim($stringToBeSigned, '&');
+ }
+
+ /**
+ * Convert encoding.
+ *
+ * @author yansongda
+ *
+ * @param string|array $data
+ * @param string $to
+ * @param string $from
+ */
+ public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
+ {
+ return Arr::encoding((array) $data, $to, $from);
+ }
+
+ /**
+ * Get service config.
+ *
+ * @author yansongda
+ *
+ * @param string|null $key
+ * @param mixed|null $default
+ *
+ * @return mixed|null
+ */
+ public function getConfig($key = null, $default = null)
+ {
+ if (is_null($key)) {
+ return $this->config->all();
+ }
+
+ if ($this->config->has($key)) {
+ return $this->config[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Get Base Uri.
+ *
+ * @author yansongda
+ *
+ * @return string
+ */
+ public function getBaseUri()
+ {
+ return $this->baseUri;
+ }
+
+ /**
+ * 生成应用证书SN.
+ *
+ * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
+ *
+ * @param $certPath
+ *
+ * @throws /Exception
+ */
+ public static function getCertSN($certPath): string
+ {
+ if (!is_file($certPath)) {
+ throw new Exception('unknown certPath -- [getCertSN]');
+ }
+ $x509data = file_get_contents($certPath);
+ if (false === $x509data) {
+ throw new Exception('Alipay CertSN Error -- [getCertSN]');
+ }
+ openssl_x509_read($x509data);
+ $certdata = openssl_x509_parse($x509data);
+ if (empty($certdata)) {
+ throw new Exception('Alipay openssl_x509_parse Error -- [getCertSN]');
+ }
+ $issuer_arr = [];
+ foreach ($certdata['issuer'] as $key => $val) {
+ $issuer_arr[] = $key.'='.$val;
+ }
+ $issuer = implode(',', array_reverse($issuer_arr));
+ Log::debug('getCertSN:', [$certPath, $issuer, $certdata['serialNumber']]);
+
+ return md5($issuer.$certdata['serialNumber']);
+ }
+
+ /**
+ * 生成支付宝根证书SN.
+ *
+ * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
+ *
+ * @param $certPath
+ *
+ * @return string
+ *
+ * @throws /Exception
+ */
+ public static function getRootCertSN($certPath)
+ {
+ if (!is_file($certPath)) {
+ throw new Exception('unknown certPath -- [getRootCertSN]');
+ }
+ $x509data = file_get_contents($certPath);
+ if (false === $x509data) {
+ throw new Exception('Alipay CertSN Error -- [getRootCertSN]');
+ }
+ $kCertificateEnd = '-----END CERTIFICATE-----';
+ $certStrList = explode($kCertificateEnd, $x509data);
+ $md5_arr = [];
+ foreach ($certStrList as $one) {
+ if (!empty(trim($one))) {
+ $_x509data = $one.$kCertificateEnd;
+ openssl_x509_read($_x509data);
+ $_certdata = openssl_x509_parse($_x509data);
+ if (in_array($_certdata['signatureTypeSN'], ['RSA-SHA256', 'RSA-SHA1'])) {
+ $issuer_arr = [];
+ foreach ($_certdata['issuer'] as $key => $val) {
+ $issuer_arr[] = $key.'='.$val;
+ }
+ $_issuer = implode(',', array_reverse($issuer_arr));
+ if (0 === strpos($_certdata['serialNumber'], '0x')) {
+ $serialNumber = self::bchexdec($_certdata['serialNumber']);
+ } else {
+ $serialNumber = $_certdata['serialNumber'];
+ }
+ $md5_arr[] = md5($_issuer.$serialNumber);
+ Log::debug('getRootCertSN Sub:', [$certPath, $_issuer, $serialNumber]);
+ }
+ }
+ }
+
+ return implode('_', $md5_arr);
+ }
+
+ /**
+ * processingApiResult.
+ *
+ * @author yansongda
+ *
+ * @param $data
+ * @param $result
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ protected static function processingApiResult($data, $result): Collection
+ {
+ $method = str_replace('.', '_', $data['method']).'_response';
+
+ if (!isset($result['sign']) || '10000' != $result[$method]['code']) {
+ throw new GatewayException('Get Alipay API Error:'.$result[$method]['msg'].(isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''), $result);
+ }
+
+ if (self::verifySign($result[$method], true, $result['sign'])) {
+ return new Collection($result[$method]);
+ }
+
+ Events::dispatch(new Events\SignFailed('Alipay', '', $result));
+
+ throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
+ }
+
+ /**
+ * Set Http options.
+ *
+ * @author yansongda
+ */
+ protected function setHttpOptions(): self
+ {
+ if ($this->config->has('http') && is_array($this->config->get('http'))) {
+ $this->config->forget('http.base_uri');
+ $this->httpOptions = $this->config->get('http');
+ }
+
+ return $this;
+ }
+
+ /**
+ * 0x转高精度数字.
+ *
+ * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
+ *
+ * @param $hex
+ *
+ * @return int|string
+ */
+ private static function bchexdec($hex)
+ {
+ $dec = 0;
+ $len = strlen($hex);
+ for ($i = 1; $i <= $len; ++$i) {
+ if (ctype_xdigit($hex[$i - 1])) {
+ $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
+ }
+ }
+
+ return str_replace('.00', '', $dec);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/TransferGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/TransferGateway.php
new file mode 100644
index 0000000..1015ac3
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/TransferGateway.php
@@ -0,0 +1,49 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['method'] = 'alipay.fund.trans.uni.transfer';
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Alipay', 'Transfer', $endpoint, $payload));
+
+ return Support::requestApi($payload);
+ }
+
+ /**
+ * Find.
+ *
+ * @author yansongda
+ *
+ * @param $order
+ */
+ public function find($order): array
+ {
+ return [
+ 'method' => 'alipay.fund.trans.order.query',
+ 'biz_content' => json_encode(is_array($order) ? $order : ['out_biz_no' => $order]),
+ ];
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WapGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WapGateway.php
new file mode 100644
index 0000000..dda8c65
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WapGateway.php
@@ -0,0 +1,26 @@
+
+ */
+ protected function getMethod(): string
+ {
+ return 'alipay.trade.wap.pay';
+ }
+
+ /**
+ * Get productCode config.
+ *
+ * @author yansongda
+ */
+ protected function getProductCode(): string
+ {
+ return 'QUICK_WAP_WAY';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WebGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WebGateway.php
new file mode 100644
index 0000000..37a0cd9
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Alipay/WebGateway.php
@@ -0,0 +1,104 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws InvalidConfigException
+ * @throws InvalidArgumentException
+ */
+ public function pay($endpoint, array $payload): Response
+ {
+ $biz_array = json_decode($payload['biz_content'], true);
+ $biz_array['product_code'] = $this->getProductCode();
+
+ $method = $biz_array['http_method'] ?? 'POST';
+
+ unset($biz_array['http_method']);
+ if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) {
+ $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid];
+ }
+ $payload['method'] = $this->getMethod();
+ $payload['biz_content'] = json_encode($biz_array);
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Alipay', 'Web/Wap', $endpoint, $payload));
+
+ return $this->buildPayHtml($endpoint, $payload, $method);
+ }
+
+ /**
+ * Find.
+ *
+ * @author yansongda
+ *
+ * @param $order
+ */
+ public function find($order): array
+ {
+ return [
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => json_encode(is_array($order) ? $order : ['out_trade_no' => $order]),
+ ];
+ }
+
+ /**
+ * Build Html response.
+ *
+ * @author yansongda
+ *
+ * @param string $endpoint
+ * @param array $payload
+ * @param string $method
+ */
+ protected function buildPayHtml($endpoint, $payload, $method = 'POST'): Response
+ {
+ if ('GET' === strtoupper($method)) {
+ return new RedirectResponse($endpoint.'&'.http_build_query($payload));
+ }
+
+ $sHtml = "";
+ $sHtml .= "";
+
+ return new Response($sHtml);
+ }
+
+ /**
+ * Get method config.
+ *
+ * @author yansongda
+ */
+ protected function getMethod(): string
+ {
+ return 'alipay.trade.page.pay';
+ }
+
+ /**
+ * Get productCode config.
+ *
+ * @author yansongda
+ */
+ protected function getProductCode(): string
+ {
+ return 'FAST_INSTANT_TRADE_PAY';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat.php
new file mode 100644
index 0000000..80fcebd
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat.php
@@ -0,0 +1,366 @@
+ 'https://api.mch.weixin.qq.com/',
+ self::MODE_DEV => 'https://api.mch.weixin.qq.com/xdc/apiv2sandbox/',
+ self::MODE_HK => 'https://apihk.mch.weixin.qq.com/',
+ self::MODE_SERVICE => 'https://api.mch.weixin.qq.com/',
+ self::MODE_US => 'https://apius.mch.weixin.qq.com/',
+ ];
+
+ /**
+ * Wechat payload.
+ *
+ * @var array
+ */
+ protected $payload;
+
+ /**
+ * Wechat gateway.
+ *
+ * @var string
+ */
+ protected $gateway;
+
+ /**
+ * Bootstrap.
+ *
+ * @author yansongda
+ *
+ * @throws Exception
+ */
+ public function __construct(Config $config)
+ {
+ $this->gateway = Support::create($config)->getBaseUri();
+ $this->payload = [
+ 'appid' => $config->get('app_id', ''),
+ 'mch_id' => $config->get('mch_id', ''),
+ 'nonce_str' => Str::random(),
+ 'notify_url' => $config->get('notify_url', ''),
+ 'sign' => '',
+ 'trade_type' => '',
+ 'spbill_create_ip' => Request::createFromGlobals()->getClientIp(),
+ ];
+
+ if ($config->get('mode', self::MODE_NORMAL) === static::MODE_SERVICE) {
+ $this->payload = array_merge($this->payload, [
+ 'sub_mch_id' => $config->get('sub_mch_id'),
+ 'sub_appid' => $config->get('sub_app_id', ''),
+ ]);
+ }
+ }
+
+ /**
+ * Magic pay.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param string $params
+ *
+ * @throws InvalidGatewayException
+ *
+ * @return Response|Collection
+ */
+ public function __call($method, $params)
+ {
+ return self::pay($method, ...$params);
+ }
+
+ /**
+ * Pay an order.
+ *
+ * @author yansongda
+ *
+ * @param string $gateway
+ * @param array $params
+ *
+ * @throws InvalidGatewayException
+ *
+ * @return Response|Collection
+ */
+ public function pay($gateway, $params = [])
+ {
+ Events::dispatch(new Events\PayStarting('Wechat', $gateway, $params));
+
+ $this->payload = array_merge($this->payload, $params);
+
+ $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway';
+
+ if (class_exists($gateway)) {
+ return $this->makePay($gateway);
+ }
+
+ throw new InvalidGatewayException("Pay Gateway [{$gateway}] Not Exists");
+ }
+
+ /**
+ * Verify data.
+ *
+ * @author yansongda
+ *
+ * @param string|null $content
+ *
+ * @throws InvalidSignException
+ * @throws InvalidArgumentException
+ */
+ public function verify($content = null, bool $refund = false): Collection
+ {
+ $content = $content ?? Request::createFromGlobals()->getContent();
+
+ Events::dispatch(new Events\RequestReceived('Wechat', '', [$content]));
+
+ $data = Support::fromXml($content);
+ if ($refund) {
+ $decrypt_data = Support::decryptRefundContents($data['req_info']);
+ $data = array_merge(Support::fromXml($decrypt_data), $data);
+ }
+
+ Log::debug('Resolved The Received Wechat Request Data', $data);
+
+ if ($refund || Support::generateSign($data) === $data['sign']) {
+ return new Collection($data);
+ }
+
+ Events::dispatch(new Events\SignFailed('Wechat', '', $data));
+
+ throw new InvalidSignException('Wechat Sign Verify FAILED', $data);
+ }
+
+ /**
+ * Query an order.
+ *
+ * @author yansongda
+ *
+ * @param string|array $order
+ *
+ * @throws GatewayException
+ * @throws InvalidSignException
+ * @throws InvalidArgumentException
+ */
+ public function find($order, string $type = 'wap'): Collection
+ {
+ unset($this->payload['spbill_create_ip']);
+
+ $gateway = get_class($this).'\\'.Str::studly($type).'Gateway';
+
+ if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) {
+ throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method");
+ }
+
+ $config = call_user_func([new $gateway(), 'find'], $order);
+
+ $this->payload = Support::filterPayload($this->payload, $config['order']);
+
+ Events::dispatch(new Events\MethodCalled('Wechat', 'Find', $this->gateway, $this->payload));
+
+ return Support::requestApi(
+ $config['endpoint'],
+ $this->payload,
+ $config['cert']
+ );
+ }
+
+ /**
+ * Refund an order.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidSignException
+ * @throws InvalidArgumentException
+ */
+ public function refund(array $order): Collection
+ {
+ unset($this->payload['spbill_create_ip']);
+
+ $this->payload = Support::filterPayload($this->payload, $order, true);
+
+ Events::dispatch(new Events\MethodCalled('Wechat', 'Refund', $this->gateway, $this->payload));
+
+ return Support::requestApi(
+ 'secapi/pay/refund',
+ $this->payload,
+ true
+ );
+ }
+
+ /**
+ * Cancel an order.
+ *
+ * @author yansongda
+ *
+ * @param array $order
+ *
+ * @throws GatewayException
+ * @throws InvalidSignException
+ * @throws InvalidArgumentException
+ */
+ public function cancel($order): Collection
+ {
+ unset($this->payload['spbill_create_ip']);
+
+ $this->payload = Support::filterPayload($this->payload, $order);
+
+ Events::dispatch(new Events\MethodCalled('Wechat', 'Cancel', $this->gateway, $this->payload));
+
+ return Support::requestApi(
+ 'secapi/pay/reverse',
+ $this->payload,
+ true
+ );
+ }
+
+ /**
+ * Close an order.
+ *
+ * @author yansongda
+ *
+ * @param string|array $order
+ *
+ * @throws GatewayException
+ * @throws InvalidSignException
+ * @throws InvalidArgumentException
+ */
+ public function close($order): Collection
+ {
+ unset($this->payload['spbill_create_ip']);
+
+ $this->payload = Support::filterPayload($this->payload, $order);
+
+ Events::dispatch(new Events\MethodCalled('Wechat', 'Close', $this->gateway, $this->payload));
+
+ return Support::requestApi('pay/closeorder', $this->payload);
+ }
+
+ /**
+ * Echo success to server.
+ *
+ * @author yansongda
+ *
+ * @throws InvalidArgumentException
+ */
+ public function success(): Response
+ {
+ Events::dispatch(new Events\MethodCalled('Wechat', 'Success', $this->gateway));
+
+ return new Response(
+ Support::toXml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']),
+ 200,
+ ['Content-Type' => 'application/xml']
+ );
+ }
+
+ /**
+ * Download the bill.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ */
+ public function download(array $params): string
+ {
+ unset($this->payload['spbill_create_ip']);
+
+ $this->payload = Support::filterPayload($this->payload, $params, true);
+
+ Events::dispatch(new Events\MethodCalled('Wechat', 'Download', $this->gateway, $this->payload));
+
+ $result = Support::getInstance()->post(
+ 'pay/downloadbill',
+ Support::getInstance()->toXml($this->payload)
+ );
+
+ if (is_array($result)) {
+ throw new GatewayException('Get Wechat API Error: '.$result['return_msg'], $result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Make pay gateway.
+ *
+ * @author yansongda
+ *
+ * @param string $gateway
+ *
+ * @throws InvalidGatewayException
+ *
+ * @return Response|Collection
+ */
+ protected function makePay($gateway)
+ {
+ $app = new $gateway();
+
+ if ($app instanceof GatewayInterface) {
+ return $app->pay($this->gateway, array_filter($this->payload, function ($value) {
+ return '' !== $value && !is_null($value);
+ }));
+ }
+
+ throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface");
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/AppGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/AppGateway.php
new file mode 100644
index 0000000..b37b965
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/AppGateway.php
@@ -0,0 +1,62 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ * @throws Exception
+ */
+ public function pay($endpoint, array $payload): Response
+ {
+ $payload['appid'] = Support::getInstance()->appid;
+ $payload['trade_type'] = $this->getTradeType();
+
+ if (Wechat::MODE_SERVICE === $this->mode) {
+ $payload['sub_appid'] = Support::getInstance()->sub_appid;
+ }
+
+ $pay_request = [
+ 'appid' => Wechat::MODE_SERVICE === $this->mode ? $payload['sub_appid'] : $payload['appid'],
+ 'partnerid' => Wechat::MODE_SERVICE === $this->mode ? $payload['sub_mch_id'] : $payload['mch_id'],
+ 'prepayid' => $this->preOrder($payload)->get('prepay_id'),
+ 'timestamp' => strval(time()),
+ 'noncestr' => Str::random(),
+ 'package' => 'Sign=WXPay',
+ ];
+ $pay_request['sign'] = Support::generateSign($pay_request);
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'App', $endpoint, $pay_request));
+
+ return new JsonResponse($pay_request);
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return 'APP';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Gateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Gateway.php
new file mode 100644
index 0000000..439a908
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Gateway.php
@@ -0,0 +1,88 @@
+
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct()
+ {
+ $this->mode = Support::getInstance()->mode;
+ }
+
+ /**
+ * Pay an order.
+ *
+ * @author yansongda
+ *
+ * @param string $endpoint
+ *
+ * @return Collection
+ */
+ abstract public function pay($endpoint, array $payload);
+
+ /**
+ * Find.
+ *
+ * @author yansongda
+ *
+ * @param string|array $order
+ */
+ public function find($order): array
+ {
+ return [
+ 'endpoint' => 'pay/orderquery',
+ 'order' => is_array($order) ? $order : ['out_trade_no' => $order],
+ 'cert' => false,
+ ];
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ *
+ * @return string
+ */
+ abstract protected function getTradeType();
+
+ /**
+ * Schedule an order.
+ *
+ * @author yansongda
+ *
+ * @param array $payload
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ protected function preOrder($payload): Collection
+ {
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\MethodCalled('Wechat', 'PreOrder', '', $payload));
+
+ return Support::requestApi('pay/unifiedorder', $payload);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/GroupRedpackGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/GroupRedpackGateway.php
new file mode 100644
index 0000000..de690d6
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/GroupRedpackGateway.php
@@ -0,0 +1,57 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['wxappid'] = $payload['appid'];
+ $payload['amt_type'] = 'ALL_RAND';
+
+ if (Wechat::MODE_SERVICE === $this->mode) {
+ $payload['msgappid'] = $payload['appid'];
+ }
+
+ unset($payload['appid'], $payload['trade_type'],
+ $payload['notify_url'], $payload['spbill_create_ip']);
+
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'Group Redpack', $endpoint, $payload));
+
+ return Support::requestApi(
+ 'mmpaymkttransfers/sendgroupredpack',
+ $payload,
+ true
+ );
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return '';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php
new file mode 100644
index 0000000..8878dd2
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php
@@ -0,0 +1,35 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['appid'] = Support::getInstance()->miniapp_id;
+
+ if (Wechat::MODE_SERVICE === $this->mode) {
+ $payload['sub_appid'] = Support::getInstance()->sub_miniapp_id;
+ $this->payRequestUseSubAppId = true;
+ }
+
+ return parent::pay($endpoint, $payload);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MpGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MpGateway.php
new file mode 100644
index 0000000..1c7fd9d
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/MpGateway.php
@@ -0,0 +1,59 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ * @throws Exception
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['trade_type'] = $this->getTradeType();
+
+ $pay_request = [
+ 'appId' => !$this->payRequestUseSubAppId ? $payload['appid'] : $payload['sub_appid'],
+ 'timeStamp' => strval(time()),
+ 'nonceStr' => Str::random(),
+ 'package' => 'prepay_id='.$this->preOrder($payload)->get('prepay_id'),
+ 'signType' => 'MD5',
+ ];
+ $pay_request['paySign'] = Support::generateSign($pay_request);
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'JSAPI', $endpoint, $pay_request));
+
+ return new Collection($pay_request);
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return 'JSAPI';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/PosGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/PosGateway.php
new file mode 100644
index 0000000..01061b6
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/PosGateway.php
@@ -0,0 +1,44 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ unset($payload['trade_type'], $payload['notify_url']);
+
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'Pos', $endpoint, $payload));
+
+ return Support::requestApi('pay/micropay', $payload);
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return 'MICROPAY';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php
new file mode 100644
index 0000000..db7d06f
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php
@@ -0,0 +1,61 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['wxappid'] = $payload['appid'];
+
+ if ('cli' !== php_sapi_name()) {
+ $payload['client_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR');
+ }
+
+ if (Wechat::MODE_SERVICE === $this->mode) {
+ $payload['msgappid'] = $payload['appid'];
+ }
+
+ unset($payload['appid'], $payload['trade_type'],
+ $payload['notify_url'], $payload['spbill_create_ip']);
+
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'Redpack', $endpoint, $payload));
+
+ return Support::requestApi(
+ 'mmpaymkttransfers/sendredpack',
+ $payload,
+ true
+ );
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return '';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RefundGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RefundGateway.php
new file mode 100644
index 0000000..e354cf5
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/RefundGateway.php
@@ -0,0 +1,50 @@
+
+ *
+ * @param $order
+ */
+ public function find($order): array
+ {
+ return [
+ 'endpoint' => 'pay/refundquery',
+ 'order' => is_array($order) ? $order : ['out_trade_no' => $order],
+ 'cert' => false,
+ ];
+ }
+
+ /**
+ * Pay an order.
+ *
+ * @author yansongda
+ *
+ * @param string $endpoint
+ *
+ * @throws InvalidArgumentException
+ */
+ public function pay($endpoint, array $payload)
+ {
+ throw new InvalidArgumentException('Not Support Refund In Pay');
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function getTradeType()
+ {
+ throw new InvalidArgumentException('Not Support Refund In Pay');
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/ScanGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/ScanGateway.php
new file mode 100644
index 0000000..1338665
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/ScanGateway.php
@@ -0,0 +1,44 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR');
+ $payload['trade_type'] = $this->getTradeType();
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'Scan', $endpoint, $payload));
+
+ return $this->preOrder($payload);
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return 'NATIVE';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Support.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Support.php
new file mode 100644
index 0000000..e1fc983
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/Support.php
@@ -0,0 +1,460 @@
+
+ *
+ * @property string appid
+ * @property string app_id
+ * @property string miniapp_id
+ * @property string sub_appid
+ * @property string sub_app_id
+ * @property string sub_miniapp_id
+ * @property string mch_id
+ * @property string sub_mch_id
+ * @property string key
+ * @property string return_url
+ * @property string cert_client
+ * @property string cert_key
+ * @property array log
+ * @property array http
+ * @property string mode
+ */
+class Support
+{
+ use HasHttpRequest;
+
+ /**
+ * Wechat gateway.
+ *
+ * @var string
+ */
+ protected $baseUri;
+
+ /**
+ * Config.
+ *
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * Instance.
+ *
+ * @var Support
+ */
+ private static $instance;
+
+ /**
+ * Bootstrap.
+ *
+ * @author yansongda
+ */
+ private function __construct(Config $config)
+ {
+ $this->baseUri = Wechat::URL[$config->get('mode', Wechat::MODE_NORMAL)];
+ $this->config = $config;
+
+ $this->setHttpOptions();
+ }
+
+ /**
+ * __get.
+ *
+ * @author yansongda
+ *
+ * @param $key
+ *
+ * @return mixed|Config|null
+ */
+ public function __get($key)
+ {
+ return $this->getConfig($key);
+ }
+
+ /**
+ * create.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ *
+ * @return Support
+ */
+ public static function create(Config $config)
+ {
+ if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) {
+ self::$instance = new self($config);
+
+ self::setDevKey();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * getInstance.
+ *
+ * @author yansongda
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return Support
+ */
+ public static function getInstance()
+ {
+ if (is_null(self::$instance)) {
+ throw new InvalidArgumentException('You Should [Create] First Before Using');
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * clear.
+ *
+ * @author yansongda
+ */
+ public static function clear()
+ {
+ self::$instance = null;
+ }
+
+ /**
+ * Request wechat api.
+ *
+ * @author yansongda
+ *
+ * @param string $endpoint
+ * @param array $data
+ * @param bool $cert
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public static function requestApi($endpoint, $data, $cert = false): Collection
+ {
+ Events::dispatch(new Events\ApiRequesting('Wechat', '', self::$instance->getBaseUri().$endpoint, $data));
+
+ //xmlData需增加headers配置,否则微信方无法接收到参数
+ $options = [
+ 'headers' => [
+ 'Content-Type' => 'application/xml',
+ ],
+ ];
+
+ $certOptions = $cert ? [
+ 'cert' => self::$instance->cert_client,
+ 'ssl_key' => self::$instance->cert_key,
+ ] : [];
+
+ $result = self::$instance->post(
+ $endpoint,
+ self::toXml($data),
+ array_merge($options, $certOptions)
+ );
+ $result = is_array($result) ? $result : self::fromXml($result);
+
+ Events::dispatch(new Events\ApiRequested('Wechat', '', self::$instance->getBaseUri().$endpoint, $result));
+
+ return self::processingApiResult($endpoint, $result);
+ }
+
+ /**
+ * Filter payload.
+ *
+ * @author yansongda
+ *
+ * @param array $payload
+ * @param array|string $params
+ * @param bool $preserve_notify_url
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function filterPayload($payload, $params, $preserve_notify_url = false): array
+ {
+ $type = self::getTypeName($params['type'] ?? '');
+
+ $payload = array_merge(
+ $payload,
+ is_array($params) ? $params : ['out_trade_no' => $params]
+ );
+ $payload['appid'] = self::$instance->getConfig($type, '');
+
+ if (Wechat::MODE_SERVICE === self::$instance->getConfig('mode', Wechat::MODE_NORMAL)) {
+ $payload['sub_appid'] = self::$instance->getConfig('sub_'.$type, '');
+ }
+
+ unset($payload['trade_type'], $payload['type']);
+ if (!$preserve_notify_url) {
+ unset($payload['notify_url']);
+ }
+
+ $payload['sign'] = self::generateSign($payload);
+
+ return $payload;
+ }
+
+ /**
+ * Generate wechat sign.
+ *
+ * @author yansongda
+ *
+ * @param array $data
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function generateSign($data): string
+ {
+ $key = self::$instance->key;
+
+ if (is_null($key)) {
+ throw new InvalidArgumentException('Missing Wechat Config -- [key]');
+ }
+
+ ksort($data);
+
+ $string = md5(self::getSignContent($data).'&key='.$key);
+
+ Log::debug('Wechat Generate Sign Before UPPER', [$data, $string]);
+
+ return strtoupper($string);
+ }
+
+ /**
+ * Generate sign content.
+ *
+ * @author yansongda
+ *
+ * @param array $data
+ */
+ public static function getSignContent($data): string
+ {
+ $buff = '';
+
+ foreach ($data as $k => $v) {
+ $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
+ }
+
+ Log::debug('Wechat Generate Sign Content Before Trim', [$data, $buff]);
+
+ return trim($buff, '&');
+ }
+
+ /**
+ * Decrypt refund contents.
+ *
+ * @author yansongda
+ *
+ * @param string $contents
+ */
+ public static function decryptRefundContents($contents): string
+ {
+ return openssl_decrypt(
+ base64_decode($contents),
+ 'AES-256-ECB',
+ md5(self::$instance->key),
+ OPENSSL_RAW_DATA
+ );
+ }
+
+ /**
+ * Convert array to xml.
+ *
+ * @author yansongda
+ *
+ * @param array $data
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function toXml($data): string
+ {
+ if (!is_array($data) || count($data) <= 0) {
+ throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!');
+ }
+
+ $xml = '';
+ foreach ($data as $key => $val) {
+ $xml .= is_numeric($val) ? '<'.$key.'>'.$val.''.$key.'>' :
+ '<'.$key.'>'.$key.'>';
+ }
+ $xml .= ' ';
+
+ return $xml;
+ }
+
+ /**
+ * Convert xml to array.
+ *
+ * @author yansongda
+ *
+ * @param string $xml
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function fromXml($xml): array
+ {
+ if (!$xml) {
+ throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!');
+ }
+
+ if (\PHP_VERSION_ID < 80000) {
+ libxml_disable_entity_loader(true);
+ }
+
+ return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
+ }
+
+ /**
+ * Get service config.
+ *
+ * @author yansongda
+ *
+ * @param string|null $key
+ * @param mixed|null $default
+ *
+ * @return mixed|null
+ */
+ public function getConfig($key = null, $default = null)
+ {
+ if (is_null($key)) {
+ return $this->config->all();
+ }
+
+ if ($this->config->has($key)) {
+ return $this->config[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Get app id according to param type.
+ *
+ * @author yansongda
+ *
+ * @param string $type
+ */
+ public static function getTypeName($type = ''): string
+ {
+ switch ($type) {
+ case '':
+ $type = 'app_id';
+ break;
+ case 'app':
+ $type = 'appid';
+ break;
+ default:
+ $type = $type.'_id';
+ }
+
+ return $type;
+ }
+
+ /**
+ * Get Base Uri.
+ *
+ * @author yansongda
+ *
+ * @return string
+ */
+ public function getBaseUri()
+ {
+ return $this->baseUri;
+ }
+
+ /**
+ * processingApiResult.
+ *
+ * @author yansongda
+ *
+ * @param $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ *
+ * @return Collection
+ */
+ protected static function processingApiResult($endpoint, array $result)
+ {
+ if (!isset($result['return_code']) || 'SUCCESS' != $result['return_code']) {
+ throw new GatewayException('Get Wechat API Error:'.($result['return_msg'] ?? $result['retmsg'] ?? ''), $result);
+ }
+
+ if (isset($result['result_code']) && 'SUCCESS' != $result['result_code']) {
+ throw new BusinessException('Wechat Business Error: '.$result['err_code'].' - '.$result['err_code_des'], $result);
+ }
+
+ if (false !== strpos($endpoint, 'xdc/apiv2getsignkey') ||
+ false !== strpos($endpoint, 'mmpaymkttransfers') ||
+ self::generateSign($result) === $result['sign']) {
+ return new Collection($result);
+ }
+
+ Events::dispatch(new Events\SignFailed('Wechat', '', $result));
+
+ throw new InvalidSignException('Wechat Sign Verify FAILED', $result);
+ }
+
+ /**
+ * setDevKey.
+ *
+ * @author yansongda
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ * @throws Exception
+ *
+ * @return Support
+ */
+ private static function setDevKey()
+ {
+ if (Wechat::MODE_DEV == self::$instance->mode) {
+ $data = [
+ 'mch_id' => self::$instance->mch_id,
+ 'nonce_str' => Str::random(),
+ ];
+ $data['sign'] = self::generateSign($data);
+
+ $result = self::requestApi('https://api.mch.weixin.qq.com/xdc/apiv2getsignkey/sign/getsignkey', $data);
+
+ self::$instance->config->set('key', $result['sandbox_signkey']);
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Set Http options.
+ *
+ * @author yansongda
+ */
+ private function setHttpOptions(): self
+ {
+ if ($this->config->has('http') && is_array($this->config->get('http'))) {
+ $this->config->forget('http.base_uri');
+ $this->httpOptions = $this->config->get('http');
+ }
+
+ return $this;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/TransferGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/TransferGateway.php
new file mode 100644
index 0000000..bbd97b3
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/TransferGateway.php
@@ -0,0 +1,80 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): Collection
+ {
+ if (Wechat::MODE_SERVICE === $this->mode) {
+ unset($payload['sub_mch_id'], $payload['sub_appid']);
+ }
+
+ $type = Support::getTypeName($payload['type'] ?? '');
+
+ $payload['mch_appid'] = Support::getInstance()->getConfig($type, '');
+ $payload['mchid'] = $payload['mch_id'];
+
+ if ('cli' !== php_sapi_name() && !isset($payload['spbill_create_ip'])) {
+ $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR');
+ }
+
+ unset($payload['appid'], $payload['mch_id'], $payload['trade_type'],
+ $payload['notify_url'], $payload['type']);
+
+ $payload['sign'] = Support::generateSign($payload);
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'Transfer', $endpoint, $payload));
+
+ return Support::requestApi(
+ 'mmpaymkttransfers/promotion/transfers',
+ $payload,
+ true
+ );
+ }
+
+ /**
+ * Find.
+ *
+ * @author yansongda
+ *
+ * @param $order
+ */
+ public function find($order): array
+ {
+ return [
+ 'endpoint' => 'mmpaymkttransfers/gettransferinfo',
+ 'order' => is_array($order) ? $order : ['partner_trade_no' => $order],
+ 'cert' => true,
+ ];
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return '';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WapGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WapGateway.php
new file mode 100644
index 0000000..0c0c920
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WapGateway.php
@@ -0,0 +1,47 @@
+
+ *
+ * @param string $endpoint
+ *
+ * @throws GatewayException
+ * @throws InvalidArgumentException
+ * @throws InvalidSignException
+ */
+ public function pay($endpoint, array $payload): RedirectResponse
+ {
+ $payload['trade_type'] = $this->getTradeType();
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'Wap', $endpoint, $payload));
+
+ $mweb_url = $this->preOrder($payload)->get('mweb_url');
+
+ $url = is_null(Support::getInstance()->return_url) ? $mweb_url : $mweb_url.
+ '&redirect_url='.urlencode(Support::getInstance()->return_url);
+
+ return new RedirectResponse($url);
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @author yansongda
+ */
+ protected function getTradeType(): string
+ {
+ return 'MWEB';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WebGateway.php b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WebGateway.php
new file mode 100644
index 0000000..9fe0978
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Gateways/Wechat/WebGateway.php
@@ -0,0 +1,86 @@
+
+ *
+ */
+ public function pay($endpoint, array $payload): Response
+ {
+ $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR');
+ $payload['trade_type'] = $this->getTradeType();
+
+ $code_url = $this->preOrder($payload)['code_url'];
+ $params = [
+ 'body' => $payload['body'],
+ 'code_url' => $code_url,
+ 'out_trade_no' => $payload['out_trade_no'],
+ 'return_url' => Support::getInstance()->return_url,
+ 'total_fee' => $payload['total_fee'],
+ ];
+
+ $params['sign'] = md5(implode('', $params) . Support::getInstance()->app_id);
+ $endpoint = addon_url("epay/api/wechat");
+
+ Events::dispatch(new Events\PayStarted('Wechat', 'Web/Wap', $endpoint, $payload));
+
+ return $this->buildPayHtml($endpoint, $params);
+ }
+
+ /**
+ * Build Html response.
+ *
+ * @param string $endpoint
+ * @param array $payload
+ * @param string $method
+ *
+ * @return Response
+ * @author yansongda
+ *
+ */
+ protected function buildPayHtml($endpoint, $payload, $method = 'POST'): Response
+ {
+ if (strtoupper($method) === 'GET') {
+ return new RedirectResponse($endpoint . '?' . http_build_query($payload));
+ }
+
+ $sHtml = "";
+ $sHtml .= "";
+
+ return new Response($sHtml);
+ }
+
+ /**
+ * Get trade type config.
+ *
+ * @return string
+ * @author yansongda
+ *
+ */
+ protected function getTradeType(): string
+ {
+ return 'NATIVE';
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/LICENSE b/addons/epay/library/v2/Yansongda/Pay/LICENSE
new file mode 100644
index 0000000..6ceb153
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 yansongda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/addons/epay/library/v2/Yansongda/Pay/Listeners/KernelLogSubscriber.php b/addons/epay/library/v2/Yansongda/Pay/Listeners/KernelLogSubscriber.php
new file mode 100644
index 0000000..2250de3
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Listeners/KernelLogSubscriber.php
@@ -0,0 +1,114 @@
+ 'methodName')
+ * * array('eventName' => array('methodName', $priority))
+ * * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
+ *
+ * @return array The event names to listen to
+ */
+ public static function getSubscribedEvents()
+ {
+ return [
+ Events\PayStarting::class => ['writePayStartingLog', 256],
+ Events\PayStarted::class => ['writePayStartedLog', 256],
+ Events\ApiRequesting::class => ['writeApiRequestingLog', 256],
+ Events\ApiRequested::class => ['writeApiRequestedLog', 256],
+ Events\SignFailed::class => ['writeSignFailedLog', 256],
+ Events\RequestReceived::class => ['writeRequestReceivedLog', 256],
+ Events\MethodCalled::class => ['writeMethodCalledLog', 256],
+ ];
+ }
+
+ /**
+ * writePayStartingLog.
+ *
+ * @author yansongda
+ */
+ public function writePayStartingLog(Events\PayStarting $event)
+ {
+ Log::debug("Starting To {$event->driver}", [$event->gateway, $event->params]);
+ }
+
+ /**
+ * writePayStartedLog.
+ *
+ * @author yansongda
+ */
+ public function writePayStartedLog(Events\PayStarted $event)
+ {
+ Log::info(
+ "{$event->driver} {$event->gateway} Has Started",
+ [$event->endpoint, $event->payload]
+ );
+ }
+
+ /**
+ * writeApiRequestingLog.
+ *
+ * @author yansongda
+ */
+ public function writeApiRequestingLog(Events\ApiRequesting $event)
+ {
+ Log::debug("Requesting To {$event->driver} Api", [$event->endpoint, $event->payload]);
+ }
+
+ /**
+ * writeApiRequestedLog.
+ *
+ * @author yansongda
+ */
+ public function writeApiRequestedLog(Events\ApiRequested $event)
+ {
+ Log::debug("Result Of {$event->driver} Api", $event->result);
+ }
+
+ /**
+ * writeSignFailedLog.
+ *
+ * @author yansongda
+ */
+ public function writeSignFailedLog(Events\SignFailed $event)
+ {
+ Log::warning("{$event->driver} Sign Verify FAILED", $event->data);
+ }
+
+ /**
+ * writeRequestReceivedLog.
+ *
+ * @author yansongda
+ */
+ public function writeRequestReceivedLog(Events\RequestReceived $event)
+ {
+ Log::info("Received {$event->driver} Request", $event->data);
+ }
+
+ /**
+ * writeMethodCalledLog.
+ *
+ * @author yansongda
+ */
+ public function writeMethodCalledLog(Events\MethodCalled $event)
+ {
+ Log::info("{$event->driver} {$event->gateway} Method Has Called", [$event->endpoint, $event->payload]);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Log.php b/addons/epay/library/v2/Yansongda/Pay/Log.php
new file mode 100644
index 0000000..c1f8c3e
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Log.php
@@ -0,0 +1,49 @@
+
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return mixed
+ */
+ public static function __callStatic($method, $args)
+ {
+ return forward_static_call_array([BaseLog::class, $method], $args);
+ }
+
+ /**
+ * Forward call.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array([BaseLog::class, $method], $args);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Pay/Pay.php b/addons/epay/library/v2/Yansongda/Pay/Pay.php
new file mode 100644
index 0000000..eb33e3d
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Pay/Pay.php
@@ -0,0 +1,131 @@
+
+ *
+ * @throws Exception
+ */
+ public function __construct(array $config)
+ {
+ $this->config = new Config($config);
+
+ $this->registerLogService();
+ $this->registerEventService();
+ }
+
+ /**
+ * Magic static call.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param array $params
+ *
+ * @throws InvalidGatewayException
+ * @throws Exception
+ */
+ public static function __callStatic($method, $params): GatewayApplicationInterface
+ {
+ $app = new self(...$params);
+
+ return $app->create($method);
+ }
+
+ /**
+ * Create a instance.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ *
+ * @throws InvalidGatewayException
+ */
+ protected function create($method): GatewayApplicationInterface
+ {
+ $gateway = __NAMESPACE__.'\\Gateways\\'.Str::studly($method);
+
+ if (class_exists($gateway)) {
+ return self::make($gateway);
+ }
+
+ throw new InvalidGatewayException("Gateway [{$method}] Not Exists");
+ }
+
+ /**
+ * Make a gateway.
+ *
+ * @author yansongda
+ *
+ * @param string $gateway
+ *
+ * @throws InvalidGatewayException
+ */
+ protected function make($gateway): GatewayApplicationInterface
+ {
+ $app = new $gateway($this->config);
+
+ if ($app instanceof GatewayApplicationInterface) {
+ return $app;
+ }
+
+ throw new InvalidGatewayException("Gateway [{$gateway}] Must Be An Instance Of GatewayApplicationInterface");
+ }
+
+ /**
+ * Register log service.
+ *
+ * @author yansongda
+ *
+ * @throws Exception
+ */
+ protected function registerLogService()
+ {
+ $config = $this->config->get('log');
+ $config['identify'] = 'yansongda.pay';
+
+ $logger = new Logger();
+ $logger->setConfig($config);
+
+ Log::setInstance($logger);
+ }
+
+ /**
+ * Register event service.
+ *
+ * @author yansongda
+ */
+ protected function registerEventService()
+ {
+ Events::setDispatcher(Events::createDispatcher());
+
+ Events::addSubscriber(new KernelLogSubscriber());
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Arr.php b/addons/epay/library/v2/Yansongda/Supports/Arr.php
new file mode 100644
index 0000000..f247da8
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Arr.php
@@ -0,0 +1,605 @@
+ $value) {
+ [$innerKey, $innerValue] = call_user_func($callback, $key, $value);
+ $results[$innerKey] = $innerValue;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ */
+ public static function divide(array $array): array
+ {
+ return [
+ array_keys($array),
+ array_values($array),
+ ];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ */
+ public static function dot(array $array, string $prepend = ''): array
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
+ } else {
+ $results[$prepend.$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of items.
+ *
+ * @param array|string $keys
+ */
+ public static function except(array $array, $keys): array
+ {
+ return array_diff_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * access array.
+ *
+ * if not array access, return original.
+ *
+ * @author yansongda
+ *
+ * @param mixed $data
+ *
+ * @return mixed
+ */
+ public static function access($data)
+ {
+ if (!self::accessible($data) &&
+ !(is_object($data) && method_exists($data, 'toArray'))) {
+ return $data;
+ }
+
+ return is_object($data) ? $data->toArray() : $data;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|int $key
+ *
+ * @return bool
+ */
+ public static function exists($array, $key)
+ {
+ $array = self::access($array);
+
+ if ($array instanceof ArrayAccess) {
+ return $array->offsetExists($key);
+ }
+
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|array $keys
+ *
+ * @return bool
+ */
+ public static function has($array, $keys)
+ {
+ $array = self::access($array);
+
+ $keys = (array) $keys;
+
+ if (!$array || $keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+
+ if (static::exists($array, $key)) {
+ continue;
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine if any of the keys exist in an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|array $keys
+ *
+ * @return bool
+ */
+ public static function hasAny($array, $keys)
+ {
+ $array = self::access($array);
+
+ if (is_null($keys)) {
+ return false;
+ }
+
+ $keys = (array) $keys;
+
+ if (!$array) {
+ return false;
+ }
+
+ if ($keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ if (static::has($array, $key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Fetch a flattened array of a nested array element.
+ */
+ public static function fetch(array $array, string $key): array
+ {
+ $results = [];
+
+ foreach (explode('.', $key) as $segment) {
+ $results = [];
+ foreach ($array as $value) {
+ $value = (array) $value;
+ $results[] = $value[$segment];
+ }
+ $array = array_values($results);
+ }
+
+ return array_values($results);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function first(array $array, callable $callback, $default = null)
+ {
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $key, $value)) {
+ return $value;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function last(array $array, callable $callback, $default = null)
+ {
+ return static::first(array_reverse($array), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ */
+ public static function flatten(array $array): array
+ {
+ $return = [];
+ array_walk_recursive(
+ $array,
+ function ($x) use (&$return) {
+ $return[] = $x;
+ }
+ );
+
+ return $return;
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array $array
+ * @param array|string $keys
+ */
+ public static function forget(&$array, $keys)
+ {
+ $original = &$array;
+
+ $keys = (array) $keys;
+
+ if (0 === count($keys)) {
+ return;
+ }
+
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+
+ continue;
+ }
+
+ $parts = explode('.', $key);
+
+ // clean up before each pass
+ $array = &$original;
+
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function get(array $array, string $key, $default = null)
+ {
+ if (is_null($key)) {
+ return $array;
+ }
+
+ if (isset($array[$key])) {
+ return $array[$key];
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (!is_array($array) || !array_key_exists($segment, $array)) {
+ return $default;
+ }
+ $array = $array[$segment];
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array|string $keys
+ */
+ public static function only(array $array, $keys): array
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Pluck an array of values from an array.
+ *
+ * @param string $key
+ */
+ public static function pluck(array $array, string $value, ?string $key = null): array
+ {
+ $results = [];
+
+ foreach ($array as $item) {
+ $itemValue = is_object($item) ? $item->{$value} : $item[$value];
+ // If the key is "null", we will just append the value to the array and keep
+ // looping. Otherwise we will key the array using the value of the key we
+ // received from the developer. Then we'll return the final array form.
+ if (is_null($key)) {
+ $results[] = $itemValue;
+ } else {
+ $itemKey = is_object($item) ? $item->{$key} : $item[$key];
+ $results[$itemKey] = $itemValue;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @param mixed $value
+ * @param mixed $key
+ *
+ * @return array
+ */
+ public static function prepend(array $array, $value, $key = null)
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function pull(array &$array, string $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * Get one or a specified number of random values from an array.
+ *
+ * @param array $array
+ * @param int|null $number
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random(array $array, $number = null)
+ {
+ $requested = is_null($number) ? 1 : $number;
+
+ $count = count($array);
+
+ $number = $requested > $count ? $count : $requested;
+
+ if (is_null($number)) {
+ return $array[array_rand($array)];
+ }
+
+ if (0 === (int) $number) {
+ return [];
+ }
+
+ $keys = array_rand($array, $number);
+
+ $results = [];
+
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ *
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param mixed $value
+ */
+ public static function set(array &$array, string $key, $value): array
+ {
+ if (is_null($key)) {
+ return $array = $value;
+ }
+
+ $keys = explode('.', $key);
+
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (!isset($array[$key]) || !is_array($array[$key])) {
+ $array[$key] = [];
+ }
+ $array = &$array[$key];
+ }
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ /**
+ * Sort the array using the given Closure.
+ */
+ public static function sort(array $array, callable $callback): array
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ $results[$key] = $callback($value);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Shuffle the given array and return the result.
+ *
+ * @param array $array
+ * @param int|null $seed
+ *
+ * @return array
+ */
+ public static function shuffle(array $array, $seed = null): array
+ {
+ if (is_null($seed)) {
+ shuffle($array);
+ } else {
+ mt_srand($seed);
+ shuffle($array);
+ mt_srand();
+ }
+
+ return $array;
+ }
+
+ /**
+ * Convert the array into a query string.
+ */
+ public static function query(array $array): string
+ {
+ return http_build_query($array, null, '&', PHP_QUERY_RFC3986);
+ }
+
+ /**
+ * Filter the array using the given callback.
+ */
+ public static function where(array $array, ?callable $callback = null): array
+ {
+ return array_filter($array, $callback ?? function ($value) use ($callback) {
+ if (static::accessible($value)) {
+ $value = static::where($value, $callback);
+ }
+
+ if (is_array($value) && 0 === count($value)) {
+ $value = null;
+ }
+
+ return '' !== $value && !is_null($value);
+ }, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * Convert encoding.
+ *
+ * @author yansongda
+ *
+ * @param string $from_encoding
+ */
+ public static function encoding(array $array, string $to_encoding, $from_encoding = 'gb2312'): array
+ {
+ $encoded = [];
+
+ foreach ($array as $key => $value) {
+ $encoded[$key] = is_array($value) ? self::encoding($value, $to_encoding, $from_encoding) :
+ mb_convert_encoding($value, $to_encoding, $from_encoding);
+ }
+
+ return $encoded;
+ }
+
+ /**
+ * camelCaseKey.
+ *
+ * @author yansongda
+ *
+ * @param mixed $data
+ *
+ * @return mixed
+ */
+ public static function camelCaseKey($data)
+ {
+ if (!self::accessible($data) &&
+ !(is_object($data) && method_exists($data, 'toArray'))) {
+ return $data;
+ }
+
+ $result = [];
+ $data = self::access($data);
+
+ foreach ($data as $key => $value) {
+ $result[is_string($key) ? Str::camel($key) : $key] = self::camelCaseKey($value);
+ }
+
+ return $result;
+ }
+
+ /**
+ * snakeCaseKey.
+ *
+ * @author yansongda
+ *
+ * @param mixed $data
+ *
+ * @return mixed
+ */
+ public static function snakeCaseKey($data)
+ {
+ if (!self::accessible($data) &&
+ !(is_object($data) && method_exists($data, 'toArray'))) {
+ return $data;
+ }
+
+ $data = self::access($data);
+ $result = [];
+
+ foreach ($data as $key => $value) {
+ $result[is_string($key) ? Str::snake($key) : $key] = self::snakeCaseKey($value);
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Collection.php b/addons/epay/library/v2/Yansongda/Supports/Collection.php
new file mode 100644
index 0000000..b5ceaa5
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Collection.php
@@ -0,0 +1,386 @@
+ $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * To string.
+ */
+ public function __toString(): string
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * Get a data by key.
+ *
+ * @return mixed
+ */
+ public function __get(string $key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Assigns a value to the specified data.
+ *
+ * @param mixed $value
+ */
+ public function __set(string $key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Whether or not an data exists by key.
+ */
+ public function __isset(string $key): bool
+ {
+ return $this->has($key);
+ }
+
+ /**
+ * Unsets an data by key.
+ */
+ public function __unset(string $key)
+ {
+ $this->forget($key);
+ }
+
+ /**
+ * Return all items.
+ */
+ public function all(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * Return specific items.
+ */
+ public function only(array $keys): array
+ {
+ $return = [];
+
+ foreach ($keys as $key) {
+ $value = $this->get($key);
+
+ if (!is_null($value)) {
+ $return[$key] = $value;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get all items except for those with the specified keys.
+ *
+ * @param mixed $keys
+ *
+ * @return static
+ */
+ public function except($keys)
+ {
+ $keys = is_array($keys) ? $keys : func_get_args();
+
+ return new static(Arr::except($this->items, $keys));
+ }
+
+ /**
+ * Merge data.
+ *
+ * @param Collection|array $items
+ */
+ public function merge($items): array
+ {
+ foreach ($items as $key => $value) {
+ $this->set($key, $value);
+ }
+
+ return $this->all();
+ }
+
+ /**
+ * To determine Whether the specified element exists.
+ */
+ public function has(string $key): bool
+ {
+ return !is_null(Arr::get($this->items, $key));
+ }
+
+ /**
+ * Retrieve the first item.
+ *
+ * @return mixed
+ */
+ public function first()
+ {
+ return reset($this->items);
+ }
+
+ /**
+ * Retrieve the last item.
+ *
+ * @return mixed
+ */
+ public function last()
+ {
+ $end = end($this->items);
+
+ reset($this->items);
+
+ return $end;
+ }
+
+ /**
+ * add the item value.
+ *
+ * @param mixed $value
+ */
+ public function add(string $key, $value)
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Set the item value.
+ *
+ * @param mixed $value
+ */
+ public function set(string $key, $value)
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Retrieve item from Collection.
+ *
+ * @param string|null $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get(?string $key = null, $default = null)
+ {
+ return Arr::get($this->items, $key, $default);
+ }
+
+ /**
+ * Remove item form Collection.
+ */
+ public function forget(string $key)
+ {
+ Arr::forget($this->items, $key);
+ }
+
+ /**
+ * Build to array.
+ */
+ public function toArray(): array
+ {
+ return $this->all();
+ }
+
+ /**
+ * Build to json.
+ */
+ public function toJson(int $option = JSON_UNESCAPED_UNICODE): string
+ {
+ return json_encode($this->all(), $option);
+ }
+
+ /**
+ * (PHP 5 >= 5.4.0)
+ * Specify data which should be serialized to JSON.
+ *
+ * @see http://php.net/manual/en/jsonserializable.jsonserialize.php
+ *
+ * @return mixed data which can be serialized by json_encode ,
+ * which is a value of any type other than a resource
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return $this->items;
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * String representation of object.
+ *
+ * @see http://php.net/manual/en/serializable.serialize.php
+ *
+ * @return string the string representation of the object or null
+ */
+ public function serialize()
+ {
+ return serialize($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Retrieve an external iterator.
+ *
+ * @see http://php.net/manual/en/iteratoraggregate.getiterator.php
+ *
+ * @return ArrayIterator An instance of an object implementing Iterator or
+ * ArrayIterator
+ */
+ public function getIterator(): \Traversable
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Count elements of an object.
+ *
+ * @see http://php.net/manual/en/countable.count.php
+ *
+ * @return int The custom count as an integer.
+ *
+ *
+ * The return value is cast to an integer
+ */
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Constructs the object.
+ *
+ * @see http://php.net/manual/en/serializable.unserialize.php
+ *
+ * @param string $data
+ * The string representation of the object.
+ *
+ *
+ * @return mixed|void
+ */
+ public function unserialize($data)
+ {
+ return $this->items = unserialize($data);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Whether a offset exists.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetexists.php
+ *
+ * @param mixed $offset
+ * An offset to check for.
+ *
+ *
+ * @return bool true on success or false on failure.
+ * The return value will be casted to boolean if non-boolean was returned
+ */
+ public function offsetExists($offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to unset.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset
+ * The offset to unset.
+ *
+ */
+ public function offsetUnset($offset): void
+ {
+ if ($this->offsetExists($offset)) {
+ $this->forget($offset);
+ }
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to retrieve.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetget.php
+ *
+ * @param mixed $offset
+ * The offset to retrieve.
+ *
+ *
+ * @return mixed Can return all value types
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->offsetExists($offset) ? $this->get($offset) : null;
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to set.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetset.php
+ *
+ * @param mixed $offset
+ * The offset to assign the value to.
+ *
+ * @param mixed $value
+ * The value to set.
+ *
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ $this->set($offset, $value);
+ }
+
+ /**
+ * (PHP 8.1+) Magic method for serialization.
+ *
+ * @return array
+ */
+ public function __serialize(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * (PHP 8.1+) Magic method for unserialization.
+ *
+ * @param array $data
+ */
+ public function __unserialize(array $data): void
+ {
+ $this->items = $data;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Config.php b/addons/epay/library/v2/Yansongda/Supports/Config.php
new file mode 100644
index 0000000..a660bad
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Config.php
@@ -0,0 +1,7 @@
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/addons/epay/library/v2/Yansongda/Supports/Log.php b/addons/epay/library/v2/Yansongda/Supports/Log.php
new file mode 100644
index 0000000..4901a2d
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Log.php
@@ -0,0 +1,91 @@
+
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @throws \Exception
+ */
+ public function __call($method, $args): void
+ {
+ call_user_func_array([self::getInstance(), $method], $args);
+ }
+
+ /**
+ * __callStatic.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @throws \Exception
+ */
+ public static function __callStatic($method, $args): void
+ {
+ forward_static_call_array([self::getInstance(), $method], $args);
+ }
+
+ /**
+ * getInstance.
+ *
+ * @author yansongda
+ *
+ * @return \Yansongda\Supports\Logger
+ */
+ public static function getInstance(): Logger
+ {
+ if (is_null(self::$instance)) {
+ self::$instance = new Logger();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * setInstance.
+ *
+ * @author yansongda
+ *
+ * @param \Yansongda\Supports\Logger $logger
+ *
+ * @throws \Exception
+ */
+ public static function setInstance(Logger $logger): void
+ {
+ self::$instance = $logger;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Logger.php b/addons/epay/library/v2/Yansongda/Supports/Logger.php
new file mode 100644
index 0000000..7232c59
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Logger.php
@@ -0,0 +1,240 @@
+ null,
+ 'identify' => 'yansongda.supports',
+ 'level' => BaseLogger::DEBUG,
+ 'type' => 'daily',
+ 'max_files' => 30,
+ ];
+
+ /**
+ * Forward call.
+ *
+ * @author yansongda
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @throws Exception
+ */
+ public function __call($method, $args): void
+ {
+ call_user_func_array([$this->getLogger(), $method], $args);
+ }
+
+ /**
+ * Set logger.
+ *
+ * @author yansongda
+ */
+ public function setLogger(LoggerInterface $logger): Logger
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
+ /**
+ * Return the logger instance.
+ *
+ * @author yansongda
+ *
+ * @throws Exception
+ */
+ public function getLogger(): LoggerInterface
+ {
+ if (is_null($this->logger)) {
+ $this->logger = $this->createLogger();
+ }
+
+ return $this->logger;
+ }
+
+ /**
+ * Make a default log instance.
+ *
+ * @author yansongda
+ *
+ * @throws Exception
+ */
+ public function createLogger(): BaseLogger
+ {
+ $handler = $this->getHandler();
+
+ $handler->setFormatter($this->getFormatter());
+
+ $logger = new BaseLogger($this->config['identify']);
+
+ $logger->pushHandler($handler);
+
+ return $logger;
+ }
+
+ /**
+ * setFormatter.
+ *
+ * @author yansongda
+ *
+ * @return $this
+ */
+ public function setFormatter(FormatterInterface $formatter): self
+ {
+ $this->formatter = $formatter;
+
+ return $this;
+ }
+
+ /**
+ * getFormatter.
+ *
+ * @author yansongda
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ if (is_null($this->formatter)) {
+ $this->formatter = $this->createFormatter();
+ }
+
+ return $this->formatter;
+ }
+
+ /**
+ * createFormatter.
+ *
+ * @author yansongda
+ */
+ public function createFormatter(): LineFormatter
+ {
+ return new LineFormatter(
+ "%datetime% > %channel%.%level_name% > %message% %context% %extra%\n\n",
+ null,
+ false,
+ true
+ );
+ }
+
+ /**
+ * setHandler.
+ *
+ * @author yansongda
+ *
+ * @return $this
+ */
+ public function setHandler(AbstractHandler $handler): self
+ {
+ $this->handler = $handler;
+
+ return $this;
+ }
+
+ /**
+ * getHandler.
+ *
+ * @author yansongda
+ *
+ * @throws \Exception
+ */
+ public function getHandler(): AbstractHandler
+ {
+ if (is_null($this->handler)) {
+ $this->handler = $this->createHandler();
+ }
+
+ return $this->handler;
+ }
+
+ /**
+ * createHandler.
+ *
+ * @author yansongda
+ *
+ * @throws \Exception
+ *
+ * @return \Monolog\Handler\RotatingFileHandler|\Monolog\Handler\StreamHandler
+ */
+ public function createHandler(): AbstractHandler
+ {
+ $file = $this->config['file'] ?? sys_get_temp_dir().'/logs/'.$this->config['identify'].'.log';
+
+ if ('single' === $this->config['type']) {
+ return new StreamHandler($file, $this->config['level']);
+ }
+
+ return new RotatingFileHandler($file, $this->config['max_files'], $this->config['level']);
+ }
+
+ /**
+ * setConfig.
+ *
+ * @author yansongda
+ *
+ * @return $this
+ */
+ public function setConfig(array $config): self
+ {
+ $this->config = array_merge($this->config, $config);
+
+ return $this;
+ }
+
+ /**
+ * getConfig.
+ *
+ * @author yansongda
+ */
+ public function getConfig(): array
+ {
+ return $this->config;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Logger/StdoutHandler.php b/addons/epay/library/v2/Yansongda/Supports/Logger/StdoutHandler.php
new file mode 100644
index 0000000..bba384f
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Logger/StdoutHandler.php
@@ -0,0 +1,36 @@
+output = $output ?? new ConsoleOutput();
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * Writes the record down to the log of the implementing handler.
+ */
+ protected function write(array $record): void
+ {
+ $this->output->writeln($record['formatted']);
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Str.php b/addons/epay/library/v2/Yansongda/Supports/Str.php
new file mode 100644
index 0000000..b540be6
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Str.php
@@ -0,0 +1,570 @@
+ $val) {
+ $value = str_replace($val, $key, $value);
+ }
+
+ return preg_replace('/[^\x20-\x7E]/u', '', $value);
+ }
+
+ /**
+ * Get the portion of a string before a given value.
+ */
+ public static function before(string $subject, string $search): string
+ {
+ return '' === $search ? $subject : explode($search, $subject)[0];
+ }
+
+ /**
+ * Convert a value to camel case.
+ */
+ public static function camel(string $value): string
+ {
+ if (isset(static::$camelCache[$value])) {
+ return static::$camelCache[$value];
+ }
+
+ return static::$camelCache[$value] = lcfirst(static::studly($value));
+ }
+
+ /**
+ * Determine if a given string contains a given substring.
+ *
+ * @param string|array $needles
+ */
+ public static function contains(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' !== $needle && false !== mb_strpos($haystack, $needle)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if a given string ends with a given substring.
+ *
+ * @param string|array $needles
+ */
+ public static function endsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if (substr($haystack, -strlen($needle)) === (string) $needle) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Cap a string with a single instance of a given value.
+ */
+ public static function finish(string $value, string $cap): string
+ {
+ $quoted = preg_quote($cap, '/');
+
+ return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap;
+ }
+
+ /**
+ * Determine if a given string matches a given pattern.
+ *
+ * @param string|array $pattern
+ */
+ public static function is($pattern, string $value): bool
+ {
+ $patterns = is_array($pattern) ? $pattern : (array) $pattern;
+
+ if (empty($patterns)) {
+ return false;
+ }
+
+ foreach ($patterns as $pattern) {
+ // If the given value is an exact match we can of course return true right
+ // from the beginning. Otherwise, we will translate asterisks and do an
+ // actual pattern match against the two strings to see if they match.
+ if ($pattern == $value) {
+ return true;
+ }
+
+ $pattern = preg_quote($pattern, '#');
+
+ // Asterisks are translated into zero-or-more regular expression wildcards
+ // to make it convenient to check if the strings starts with the given
+ // pattern such as "library/*", making any string check convenient.
+ $pattern = str_replace('\*', '.*', $pattern);
+
+ if (1 === preg_match('#^'.$pattern.'\z#u', $value)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Convert a string to kebab case.
+ */
+ public static function kebab(string $value): string
+ {
+ return static::snake($value, '-');
+ }
+
+ /**
+ * Return the length of the given string.
+ *
+ * @param string $encoding
+ */
+ public static function length(string $value, ?string $encoding = null): int
+ {
+ if (null !== $encoding) {
+ return mb_strlen($value, $encoding);
+ }
+
+ return mb_strlen($value);
+ }
+
+ /**
+ * Limit the number of characters in a string.
+ */
+ public static function limit(string $value, int $limit = 100, string $end = '...'): string
+ {
+ if (mb_strwidth($value, 'UTF-8') <= $limit) {
+ return $value;
+ }
+
+ return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end;
+ }
+
+ /**
+ * Convert the given string to lower-case.
+ */
+ public static function lower(string $value): string
+ {
+ return mb_strtolower($value, 'UTF-8');
+ }
+
+ /**
+ * Limit the number of words in a string.
+ */
+ public static function words(string $value, int $words = 100, string $end = '...'): string
+ {
+ preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches);
+
+ if (!isset($matches[0]) || static::length($value) === static::length($matches[0])) {
+ return $value;
+ }
+
+ return rtrim($matches[0]).$end;
+ }
+
+ /**
+ * Parse a Class.
+ */
+ public static function parseCallback(string $callback, ?string $default = null): array
+ {
+ return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
+ }
+
+ /**
+ * Generate a more truly "random" alpha-numeric string.
+ *
+ * @throws Exception
+ */
+ public static function random(int $length = 16): string
+ {
+ $string = '';
+
+ while (($len = strlen($string)) < $length) {
+ $size = $length - $len;
+
+ $bytes = function_exists('random_bytes') ? random_bytes($size) : mt_rand();
+
+ $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Replace a given value in the string sequentially with an array.
+ */
+ public static function replaceArray(string $search, array $replace, string $subject): string
+ {
+ foreach ($replace as $value) {
+ $subject = static::replaceFirst($search, $value, $subject);
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Replace the first occurrence of a given value in the string.
+ */
+ public static function replaceFirst(string $search, string $replace, string $subject): string
+ {
+ if ('' == $search) {
+ return $subject;
+ }
+
+ $position = strpos($subject, $search);
+
+ if (false !== $position) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Replace the last occurrence of a given value in the string.
+ */
+ public static function replaceLast(string $search, string $replace, string $subject): string
+ {
+ $position = strrpos($subject, $search);
+
+ if (false !== $position) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Begin a string with a single instance of a given value.
+ */
+ public static function start(string $value, string $prefix): string
+ {
+ $quoted = preg_quote($prefix, '/');
+
+ return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
+ }
+
+ /**
+ * Convert the given string to upper-case.
+ */
+ public static function upper(string $value): string
+ {
+ return mb_strtoupper($value, 'UTF-8');
+ }
+
+ /**
+ * Convert the given string to title case.
+ */
+ public static function title(string $value): string
+ {
+ return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+ }
+
+ /**
+ * Generate a URL friendly "slug" from a given string.
+ */
+ public static function slug(string $title, string $separator = '-', string $language = 'en'): string
+ {
+ $title = static::ascii($title, $language);
+
+ // Convert all dashes/underscores into separator
+ $flip = '-' == $separator ? '_' : '-';
+
+ $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
+
+ // Replace @ with the word 'at'
+ $title = str_replace('@', $separator.'at'.$separator, $title);
+
+ // Remove all characters that are not the separator, letters, numbers, or whitespace.
+ $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($title));
+
+ // Replace all separator characters and whitespace by a single separator
+ $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
+
+ return trim($title, $separator);
+ }
+
+ /**
+ * Convert a string to snake case.
+ */
+ public static function snake(string $value, string $delimiter = '_'): string
+ {
+ $key = $value;
+
+ if (isset(static::$snakeCache[$key][$delimiter])) {
+ return static::$snakeCache[$key][$delimiter];
+ }
+
+ if (!ctype_lower($value)) {
+ $value = preg_replace('/\s+/u', '', ucwords($value));
+
+ $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
+ }
+
+ return static::$snakeCache[$key][$delimiter] = $value;
+ }
+
+ /**
+ * Determine if a given string starts with a given substring.
+ *
+ * @param string|array $needles
+ */
+ public static function startsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' !== $needle && substr($haystack, 0, strlen($needle)) === (string) $needle) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Convert a value to studly caps case.
+ */
+ public static function studly(string $value): string
+ {
+ $key = $value;
+
+ if (isset(static::$studlyCache[$key])) {
+ return static::$studlyCache[$key];
+ }
+
+ $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+ return static::$studlyCache[$key] = str_replace(' ', '', $value);
+ }
+
+ /**
+ * Returns the portion of string specified by the start and length parameters.
+ */
+ public static function substr(string $string, int $start, ?int $length = null): string
+ {
+ return mb_substr($string, $start, $length, 'UTF-8');
+ }
+
+ /**
+ * Make a string's first character uppercase.
+ */
+ public static function ucfirst(string $string): string
+ {
+ return static::upper(static::substr($string, 0, 1)).static::substr($string, 1);
+ }
+
+ /**
+ * Convert string's encoding.
+ *
+ * @author yansongda
+ */
+ public static function encoding(string $string, string $to = 'utf-8', string $from = 'gb2312'): string
+ {
+ return mb_convert_encoding($string, $to, $from);
+ }
+
+ /**
+ * Returns the replacements for the ascii method.
+ *
+ * Note: Adapted from Stringy\Stringy.
+ *
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ */
+ protected static function charsArray(): array
+ {
+ static $charsArray;
+
+ if (isset($charsArray)) {
+ return $charsArray;
+ }
+
+ return $charsArray = [
+ '0' => ['°', '₀', '۰', '0'],
+ '1' => ['¹', '₁', '۱', '1'],
+ '2' => ['²', '₂', '۲', '2'],
+ '3' => ['³', '₃', '۳', '3'],
+ '4' => ['⁴', '₄', '۴', '٤', '4'],
+ '5' => ['⁵', '₅', '۵', '٥', '5'],
+ '6' => ['⁶', '₆', '۶', '٦', '6'],
+ '7' => ['⁷', '₇', '۷', '7'],
+ '8' => ['⁸', '₈', '۸', '8'],
+ '9' => ['⁹', '₉', '۹', '9'],
+ 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'],
+ 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'],
+ 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'],
+ 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'],
+ 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'],
+ 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'],
+ 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'],
+ 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'],
+ 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i'],
+ 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'],
+ 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'],
+ 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'],
+ 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'],
+ 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'],
+ 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö'],
+ 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'],
+ 'q' => ['ყ', 'q'],
+ 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'],
+ 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'],
+ 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'],
+ 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü'],
+ 'v' => ['в', 'ვ', 'ϐ', 'v'],
+ 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'],
+ 'x' => ['χ', 'ξ', 'x'],
+ 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'],
+ 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'],
+ 'aa' => ['ع', 'आ', 'آ'],
+ 'ae' => ['æ', 'ǽ'],
+ 'ai' => ['ऐ'],
+ 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'],
+ 'dj' => ['ђ', 'đ'],
+ 'dz' => ['џ', 'ძ'],
+ 'ei' => ['ऍ'],
+ 'gh' => ['غ', 'ღ'],
+ 'ii' => ['ई'],
+ 'ij' => ['ij'],
+ 'kh' => ['х', 'خ', 'ხ'],
+ 'lj' => ['љ'],
+ 'nj' => ['њ'],
+ 'oe' => ['ö', 'œ', 'ؤ'],
+ 'oi' => ['ऑ'],
+ 'oii' => ['ऒ'],
+ 'ps' => ['ψ'],
+ 'sh' => ['ш', 'შ', 'ش'],
+ 'shch' => ['щ'],
+ 'ss' => ['ß'],
+ 'sx' => ['ŝ'],
+ 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'],
+ 'ts' => ['ц', 'ც', 'წ'],
+ 'ue' => ['ü'],
+ 'uu' => ['ऊ'],
+ 'ya' => ['я'],
+ 'yu' => ['ю'],
+ 'zh' => ['ж', 'ჟ', 'ژ'],
+ '(c)' => ['©'],
+ 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'],
+ 'B' => ['Б', 'Β', 'ब', 'B'],
+ 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'],
+ 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'],
+ 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E'],
+ 'F' => ['Ф', 'Φ', 'F'],
+ 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'],
+ 'H' => ['Η', 'Ή', 'Ħ', 'H'],
+ 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I'],
+ 'J' => ['J'],
+ 'K' => ['К', 'Κ', 'K'],
+ 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'],
+ 'M' => ['М', 'Μ', 'M'],
+ 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'],
+ 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'],
+ 'P' => ['П', 'Π', 'P'],
+ 'Q' => ['Q'],
+ 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'],
+ 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'],
+ 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'],
+ 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'],
+ 'V' => ['В', 'V'],
+ 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'],
+ 'X' => ['Χ', 'Ξ', 'X'],
+ 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'],
+ 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'],
+ 'AE' => ['Æ', 'Ǽ'],
+ 'Ch' => ['Ч'],
+ 'Dj' => ['Ђ'],
+ 'Dz' => ['Џ'],
+ 'Gx' => ['Ĝ'],
+ 'Hx' => ['Ĥ'],
+ 'Ij' => ['IJ'],
+ 'Jx' => ['Ĵ'],
+ 'Kh' => ['Х'],
+ 'Lj' => ['Љ'],
+ 'Nj' => ['Њ'],
+ 'Oe' => ['Œ'],
+ 'Ps' => ['Ψ'],
+ 'Sh' => ['Ш'],
+ 'Shch' => ['Щ'],
+ 'Ss' => ['ẞ'],
+ 'Th' => ['Þ'],
+ 'Ts' => ['Ц'],
+ 'Ya' => ['Я'],
+ 'Yu' => ['Ю'],
+ 'Zh' => ['Ж'],
+ ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0"],
+ ];
+ }
+
+ /**
+ * Returns the language specific replacements for the ascii method.
+ *
+ * Note: Adapted from Stringy\Stringy.
+ *
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ */
+ protected static function languageSpecificCharsArray(string $language): ?array
+ {
+ static $languageSpecific;
+ if (!isset($languageSpecific)) {
+ $languageSpecific = [
+ 'bg' => [
+ ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'],
+ ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'],
+ ],
+ 'de' => [
+ ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'],
+ ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'],
+ ],
+ ];
+ }
+
+ return isset($languageSpecific[$language]) ? $languageSpecific[$language] : null;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Traits/Accessable.php b/addons/epay/library/v2/Yansongda/Supports/Traits/Accessable.php
new file mode 100644
index 0000000..a477561
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Traits/Accessable.php
@@ -0,0 +1,142 @@
+
+ *
+ * @return mixed
+ */
+ public function __get(string $key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * __set.
+ *
+ * @author yansongda
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ public function __set(string $key, $value)
+ {
+ return $this->set($key, $value);
+ }
+
+ /**
+ * get.
+ *
+ * @author yansongda
+ *
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get(?string $key = null, $default = null)
+ {
+ if (is_null($key)) {
+ return method_exists($this, 'toArray') ? $this->toArray() : $default;
+ }
+
+ $method = 'get';
+ foreach (explode('_', $key) as $item) {
+ $method .= ucfirst($item);
+ }
+
+ if (method_exists($this, $method)) {
+ return $this->{$method}();
+ }
+
+ return $default;
+ }
+
+ /**
+ * set.
+ *
+ * @author yansongda
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function set(string $key, $value)
+ {
+ $method = 'set';
+ foreach (explode('_', $key) as $item) {
+ $method .= ucfirst($item);
+ }
+
+ if (method_exists($this, $method)) {
+ return $this->{$method}($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Whether a offset exists.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetexists.php
+ *
+ * @param mixed $offset an offset to check for
+ *
+ * @return bool true on success or false on failure.
+ *
+ * The return value will be casted to boolean if non-boolean was returned.
+ */
+ public function offsetExists($offset)
+ {
+ return !is_null($this->get($offset));
+ }
+
+ /**
+ * Offset to retrieve.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetget.php
+ *
+ * @param mixed $offset the offset to retrieve
+ *
+ * @return mixed can return all value types
+ */
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * Offset to set.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetset.php
+ *
+ * @param mixed $offset the offset to assign the value to
+ * @param mixed $value the value to set
+ *
+ * @return void
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->set($offset, $value);
+ }
+
+ /**
+ * Offset to unset.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset the offset to unset
+ *
+ * @return void
+ */
+ public function offsetUnset($offset)
+ {
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Traits/Arrayable.php b/addons/epay/library/v2/Yansongda/Supports/Traits/Arrayable.php
new file mode 100644
index 0000000..eb73c6c
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Traits/Arrayable.php
@@ -0,0 +1,32 @@
+
+ *
+ * @throws \ReflectionException
+ */
+ public function toArray(): array
+ {
+ $result = [];
+
+ foreach ((new ReflectionClass($this))->getProperties() as $item) {
+ $k = $item->getName();
+ $method = 'get'.Str::studly($k);
+
+ $result[Str::snake($k)] = method_exists($this, $method) ? $this->{$method}() : $this->{$k};
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Traits/HasHttpRequest.php b/addons/epay/library/v2/Yansongda/Supports/Traits/HasHttpRequest.php
new file mode 100644
index 0000000..6e98d24
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Traits/HasHttpRequest.php
@@ -0,0 +1,229 @@
+
+ *
+ * @return array|string
+ */
+ public function get(string $endpoint, array $query = [], array $headers = [])
+ {
+ return $this->request('get', $endpoint, [
+ 'headers' => $headers,
+ 'query' => $query,
+ ]);
+ }
+
+ /**
+ * Send a POST request.
+ *
+ * @author yansongda
+ *
+ * @param string|array $data
+ *
+ * @return array|string
+ */
+ public function post(string $endpoint, $data, array $options = [])
+ {
+ if (!is_array($data)) {
+ $options['body'] = $data;
+ } else {
+ $options['form_params'] = $data;
+ }
+
+ return $this->request('post', $endpoint, $options);
+ }
+
+ /**
+ * Send request.
+ *
+ * @author yansongda
+ *
+ * @return array|string
+ */
+ public function request(string $method, string $endpoint, array $options = [])
+ {
+ return $this->unwrapResponse($this->getHttpClient()->{$method}($endpoint, $options));
+ }
+
+ /**
+ * Set http client.
+ *
+ * @author yansongda
+ *
+ * @return $this
+ */
+ public function setHttpClient(Client $client): self
+ {
+ $this->httpClient = $client;
+
+ return $this;
+ }
+
+ /**
+ * Return http client.
+ */
+ public function getHttpClient(): Client
+ {
+ if (is_null($this->httpClient)) {
+ $this->httpClient = $this->getDefaultHttpClient();
+ }
+
+ return $this->httpClient;
+ }
+
+ /**
+ * Get default http client.
+ *
+ * @author yansongda
+ */
+ public function getDefaultHttpClient(): Client
+ {
+ return new Client($this->getOptions());
+ }
+
+ /**
+ * setBaseUri.
+ *
+ * @author yansongda
+ *
+ * @return $this
+ */
+ public function setBaseUri(string $url): self
+ {
+ if (property_exists($this, 'baseUri')) {
+ $parsedUrl = parse_url($url);
+
+ $this->baseUri = ($parsedUrl['scheme'] ?? 'http').'://'.
+ $parsedUrl['host'].(isset($parsedUrl['port']) ? (':'.$parsedUrl['port']) : '');
+ }
+
+ return $this;
+ }
+
+ /**
+ * getBaseUri.
+ *
+ * @author yansongda
+ */
+ public function getBaseUri(): string
+ {
+ return property_exists($this, 'baseUri') ? $this->baseUri : '';
+ }
+
+ public function getTimeout(): float
+ {
+ return property_exists($this, 'timeout') ? $this->timeout : 5.0;
+ }
+
+ public function setTimeout(float $timeout): self
+ {
+ if (property_exists($this, 'timeout')) {
+ $this->timeout = $timeout;
+ }
+
+ return $this;
+ }
+
+ public function getConnectTimeout(): float
+ {
+ return property_exists($this, 'connectTimeout') ? $this->connectTimeout : 3.0;
+ }
+
+ public function setConnectTimeout(float $connectTimeout): self
+ {
+ if (property_exists($this, 'connectTimeout')) {
+ $this->connectTimeout = $connectTimeout;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get default options.
+ *
+ * @author yansongda
+ */
+ public function getOptions(): array
+ {
+ return array_merge([
+ 'base_uri' => $this->getBaseUri(),
+ 'timeout' => $this->getTimeout(),
+ 'connect_timeout' => $this->getConnectTimeout(),
+ ], $this->getHttpOptions());
+ }
+
+ /**
+ * setOptions.
+ *
+ * @author yansongda
+ *
+ * @return $this
+ */
+ public function setOptions(array $options): self
+ {
+ return $this->setHttpOptions($options);
+ }
+
+ public function getHttpOptions(): array
+ {
+ return $this->httpOptions;
+ }
+
+ public function setHttpOptions(array $httpOptions): self
+ {
+ $this->httpOptions = $httpOptions;
+
+ return $this;
+ }
+
+ /**
+ * Convert response.
+ *
+ * @author yansongda
+ *
+ * @return array|string
+ */
+ public function unwrapResponse(ResponseInterface $response)
+ {
+ $contentType = $response->getHeaderLine('Content-Type');
+ $contents = $response->getBody()->getContents();
+
+ if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
+ return json_decode($contents, true);
+ } elseif (false !== stripos($contentType, 'xml')) {
+ return json_decode(json_encode(simplexml_load_string($contents, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
+ }
+
+ return $contents;
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Traits/Serializable.php b/addons/epay/library/v2/Yansongda/Supports/Traits/Serializable.php
new file mode 100644
index 0000000..5e2010b
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Traits/Serializable.php
@@ -0,0 +1,85 @@
+
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ return $this->serialize();
+ }
+
+ /**
+ * Specify data which should be serialized to JSON.
+ *
+ * @see https://php.net/manual/en/jsonserializable.jsonserialize.php
+ *
+ * @return mixed data which can be serialized by json_encode ,
+ * which is a value of any type other than a resource
+ *
+ * @since 5.4.0
+ */
+ public function jsonSerialize()
+ {
+ if (method_exists($this, 'toArray')) {
+ return $this->toArray();
+ }
+
+ return [];
+ }
+
+ /**
+ * String representation of object.
+ *
+ * @see https://php.net/manual/en/serializable.serialize.php
+ *
+ * @return string the string representation of the object or null
+ *
+ * @since 5.1.0
+ */
+ public function serialize()
+ {
+ if (method_exists($this, 'toArray')) {
+ return json_encode($this->toArray());
+ }
+
+ return json_encode([]);
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @see https://php.net/manual/en/serializable.unserialize.php
+ *
+ * @param string $serialized
+ * The string representation of the object.
+ *
+ *
+ * @since 5.1.0
+ */
+ public function unserialize($serialized)
+ {
+ $data = json_decode($serialized, true);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new RuntimeException('Invalid Json Format');
+ }
+
+ foreach ($data as $key => $item) {
+ if (method_exists($this, 'set')) {
+ $this->set($key, $item);
+ }
+ }
+ }
+}
diff --git a/addons/epay/library/v2/Yansongda/Supports/Traits/ShouldThrottle.php b/addons/epay/library/v2/Yansongda/Supports/Traits/ShouldThrottle.php
new file mode 100644
index 0000000..cbdeb71
--- /dev/null
+++ b/addons/epay/library/v2/Yansongda/Supports/Traits/ShouldThrottle.php
@@ -0,0 +1,147 @@
+ 60,
+ 'period' => 60,
+ 'count' => 0,
+ 'reset_time' => 0,
+ ];
+
+ /**
+ * isThrottled.
+ *
+ * @author yansongda
+ *
+ * @param string $key
+ * @param int $limit
+ * @param int $period
+ * @param bool $auto_add
+ *
+ * @return bool
+ */
+ public function isThrottled($key, $limit = 60, $period = 60, $auto_add = false)
+ {
+ if (-1 === $limit) {
+ return false;
+ }
+
+ $now = microtime(true) * 1000;
+
+ $this->redis->zremrangebyscore($key, 0, $now - $period * 1000);
+
+ $this->_throttle = [
+ 'limit' => $limit,
+ 'period' => $period,
+ 'count' => $this->getThrottleCounts($key, $period),
+ 'reset_time' => $this->getThrottleResetTime($key, $now),
+ ];
+
+ if ($this->_throttle['count'] < $limit) {
+ if ($auto_add) {
+ $this->throttleAdd($key, $period);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 限流 + 1.
+ *
+ * @author yansongda
+ *
+ * @param string $key
+ * @param int $period
+ */
+ public function throttleAdd($key, $period = 60)
+ {
+ $now = microtime(true) * 1000;
+
+ $this->redis->zadd($key, [$now => $now]);
+ $this->redis->expire($key, $period * 2);
+ }
+
+ /**
+ * getResetTime.
+ *
+ * @author yansongda
+ *
+ * @param $key
+ * @param $now
+ *
+ * @return int
+ */
+ public function getThrottleResetTime($key, $now)
+ {
+ $data = $this->redis->zrangebyscore(
+ $key,
+ $now - $this->_throttle['period'] * 1000,
+ $now,
+ ['limit' => [0, 1]]
+ );
+
+ if (0 === count($data)) {
+ return $this->_throttle['reset_time'] = time() + $this->_throttle['period'];
+ }
+
+ return intval($data[0] / 1000) + $this->_throttle['period'];
+ }
+
+ /**
+ * 获取限流相关信息.
+ *
+ * @author yansongda
+ *
+ * @param string|null $key
+ * @param mixed|null $default
+ *
+ * @return array|null
+ */
+ public function getThrottleInfo($key = null, $default = null)
+ {
+ if (is_null($key)) {
+ return $this->_throttle;
+ }
+
+ if (isset($this->_throttle[$key])) {
+ return $this->_throttle[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * 获取已使用次数.
+ *
+ * @author yansongda
+ *
+ * @param string $key
+ * @param int $period
+ *
+ * @return string
+ */
+ public function getThrottleCounts($key, $period = 60)
+ {
+ $now = microtime(true) * 1000;
+
+ return $this->redis->zcount($key, $now - $period * 1000, $now);
+ }
+}
diff --git a/addons/epay/library/v2/version.txt b/addons/epay/library/v2/version.txt
new file mode 100644
index 0000000..d92b16e
--- /dev/null
+++ b/addons/epay/library/v2/version.txt
@@ -0,0 +1 @@
+v2.10.6
\ No newline at end of file
diff --git a/addons/epay/library/v3/Yansongda/Pay/Contract/ConfigInterface.php b/addons/epay/library/v3/Yansongda/Pay/Contract/ConfigInterface.php
new file mode 100644
index 0000000..5b0cf67
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Contract/ConfigInterface.php
@@ -0,0 +1,22 @@
+getBody();
+
+ if (!is_null($result = $packer->unpack($body))) {
+ return $result;
+ }
+
+ throw new InvalidResponseException(Exception::UNPACK_RESPONSE_ERROR, 'Unpack Response Error', ['body' => $body, 'response' => $response]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Direction/CollectionDirection.php b/addons/epay/library/v3/Yansongda/Pay/Direction/CollectionDirection.php
new file mode 100644
index 0000000..21562db
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Direction/CollectionDirection.php
@@ -0,0 +1,27 @@
+parse($packer, $response)
+ );
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Direction/NoHttpRequestDirection.php b/addons/epay/library/v3/Yansongda/Pay/Direction/NoHttpRequestDirection.php
new file mode 100644
index 0000000..fb9f631
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Direction/NoHttpRequestDirection.php
@@ -0,0 +1,17 @@
+{$method}(...$args);
+
+ return;
+ }
+
+ throw new InvalidConfigException(Exception\Exception::EVENT_CONFIG_ERROR);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Event/ApiRequested.php b/addons/epay/library/v3/Yansongda/Pay/Event/ApiRequested.php
new file mode 100644
index 0000000..c5539ca
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Event/ApiRequested.php
@@ -0,0 +1,9 @@
+provider = $provider;
+ $this->contents = $contents;
+ $this->params = $params;
+
+ parent::__construct($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Event/Event.php b/addons/epay/library/v3/Yansongda/Pay/Event/Event.php
new file mode 100644
index 0000000..7947c1e
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Event/Event.php
@@ -0,0 +1,17 @@
+rocket = $rocket;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Event/MethodCalled.php b/addons/epay/library/v3/Yansongda/Pay/Event/MethodCalled.php
new file mode 100644
index 0000000..f83b73f
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Event/MethodCalled.php
@@ -0,0 +1,25 @@
+provider = $provider;
+ $this->name = $name;
+ $this->params = $params;
+
+ parent::__construct($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Event/PayFinish.php b/addons/epay/library/v3/Yansongda/Pay/Event/PayFinish.php
new file mode 100644
index 0000000..b913ca4
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Event/PayFinish.php
@@ -0,0 +1,9 @@
+plugins = $plugins;
+ $this->params = $params;
+
+ parent::__construct($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Exception/ContainerException.php b/addons/epay/library/v3/Yansongda/Pay/Exception/ContainerException.php
new file mode 100644
index 0000000..1753417
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Exception/ContainerException.php
@@ -0,0 +1,19 @@
+extra = $extra;
+
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Exception/InvalidConfigException.php b/addons/epay/library/v3/Yansongda/Pay/Exception/InvalidConfigException.php
new file mode 100644
index 0000000..903a10c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Exception/InvalidConfigException.php
@@ -0,0 +1,18 @@
+response = $extra;
+ $this->exception = $exception;
+
+ parent::__construct($message, $code, $extra, $previous);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Exception/ServiceException.php b/addons/epay/library/v3/Yansongda/Pay/Exception/ServiceException.php
new file mode 100644
index 0000000..0a848de
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Exception/ServiceException.php
@@ -0,0 +1,18 @@
+get('alipay');
+
+ return $alipay[get_tenant($params)] ?? [];
+ }
+}
+
+if (!function_exists('get_public_cert')) {
+ function get_public_cert(string $key): string
+ {
+ return Str::endsWith($key, ['.cer', '.crt', '.pem']) ? file_get_contents($key) : $key;
+ }
+}
+
+if (!function_exists('get_private_cert')) {
+ function get_private_cert(string $key): string
+ {
+ if (Str::endsWith($key, ['.crt', '.pem'])) {
+ return file_get_contents($key);
+ }
+
+ return "-----BEGIN RSA PRIVATE KEY-----\n".
+ wordwrap($key, 64, "\n", true).
+ "\n-----END RSA PRIVATE KEY-----";
+ }
+}
+
+if (!function_exists('verify_alipay_sign')) {
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ * @throws InvalidResponseException
+ */
+ function verify_alipay_sign(array $params, string $contents, string $sign): void
+ {
+ $public = get_alipay_config($params)['alipay_public_cert_path'] ?? null;
+
+ if (empty($public)) {
+ throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [alipay_public_cert_path]');
+ }
+
+ // 优化publickey判断
+ if (!Str::endsWith($public, ['.cer', '.crt', '.pem']) && stripos($public, '-----BEGIN PUBLIC KEY-----') === false) {
+ $public = "-----BEGIN PUBLIC KEY-----\n".
+ wordwrap($public, 64, "\n", true).
+ "\n-----END PUBLIC KEY-----";
+ }
+
+ $result = 1 === openssl_verify(
+ $contents,
+ base64_decode($sign),
+ get_public_cert($public),
+ OPENSSL_ALGO_SHA256
+ );
+
+ if (!$result) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, 'Verify Alipay Response Sign Failed', func_get_args());
+ }
+ }
+}
+
+if (!function_exists('get_wechat_config')) {
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ function get_wechat_config(array $params): array
+ {
+ $wechat = Pay::get(ConfigInterface::class)->get('wechat');
+
+ return $wechat[get_tenant($params)] ?? [];
+ }
+}
+
+if (!function_exists('get_wechat_base_uri')) {
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ function get_wechat_base_uri(array $params): string
+ {
+ $config = get_wechat_config($params);
+
+ return Wechat::URL[$config['mode'] ?? Pay::MODE_NORMAL];
+ }
+}
+
+if (!function_exists('get_wechat_sign')) {
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidConfigException
+ */
+ function get_wechat_sign(array $params, string $contents): string
+ {
+ $privateKey = get_wechat_config($params)['mch_secret_cert'] ?? null;
+
+ if (empty($privateKey)) {
+ throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_secret_cert]');
+ }
+
+ $privateKey = get_private_cert($privateKey);
+
+ openssl_sign($contents, $sign, $privateKey, 'sha256WithRSAEncryption');
+
+ return base64_encode($sign);
+ }
+}
+
+if (!function_exists('get_wechat_sign_v2')) {
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidConfigException
+ */
+ function get_wechat_sign_v2(array $params, array $payload, bool $upper = true): string
+ {
+ $key = get_wechat_config($params)['mch_secret_key_v2'] ?? null;
+
+ if (empty($key)) {
+ throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_secret_key_v2]');
+ }
+
+ ksort($payload);
+
+ $buff = '';
+
+ foreach ($payload as $k => $v) {
+ $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
+ }
+
+ $sign = md5($buff.'key='.$key);
+
+ return $upper ? strtoupper($sign) : $sign;
+ }
+}
+
+if (!function_exists('verify_wechat_sign')) {
+ /**
+ * @param ResponseInterface|ServerRequestInterface $message
+ *
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws InvalidResponseException
+ * @throws ServiceNotFoundException
+ * @throws InvalidParamsException
+ */
+ function verify_wechat_sign(MessageInterface $message, array $params): void
+ {
+ if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {
+ return;
+ }
+
+ $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
+ $timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
+ $random = $message->getHeaderLine('Wechatpay-Nonce');
+ $sign = $message->getHeaderLine('Wechatpay-Signature');
+ $body = (string) $message->getBody();
+
+ $content = $timestamp."\n".$random."\n".$body."\n";
+ $public = get_wechat_config($params)['wechat_public_cert_path'][$wechatSerial] ?? null;
+
+ if (empty($sign)) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
+ }
+
+ $public = get_public_cert(
+ empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public
+ );
+
+ $result = 1 === openssl_verify(
+ $content,
+ base64_decode($sign),
+ $public,
+ 'sha256WithRSAEncryption'
+ );
+
+ if (!$result) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
+ }
+ }
+}
+
+if (!function_exists('encrypt_wechat_contents')) {
+ function encrypt_wechat_contents(string $contents, string $publicKey): ?string
+ {
+ if (openssl_public_encrypt($contents, $encrypted, get_public_cert($publicKey), OPENSSL_PKCS1_OAEP_PADDING)) {
+ return base64_encode($encrypted);
+ }
+
+ return null;
+ }
+}
+
+if (!function_exists('reload_wechat_public_certs')) {
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ * @throws InvalidParamsException
+ * @throws InvalidResponseException
+ */
+ function reload_wechat_public_certs(array $params, ?string $serialNo = null): string
+ {
+ $data = Pay::wechat()->pay(
+ [PreparePlugin::class, WechatPublicCertsPlugin::class, RadarSignPlugin::class, ParserPlugin::class],
+ $params
+ )->get('data', []);
+
+ foreach ($data as $item) {
+ $certs[$item['serial_no']] = decrypt_wechat_resource($item['encrypt_certificate'], $params)['ciphertext'] ?? '';
+ }
+
+ $wechatConfig = get_wechat_config($params);
+
+ Pay::get(ConfigInterface::class)->set(
+ 'wechat.'.get_tenant($params).'.wechat_public_cert_path',
+ ((array) ($wechatConfig['wechat_public_cert_path'] ?? [])) + ($certs ?? [])
+ );
+
+ if (!is_null($serialNo) && empty($certs[$serialNo])) {
+ throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Get Wechat Public Cert Error');
+ }
+
+ return $certs[$serialNo] ?? '';
+ }
+}
+
+if (!function_exists('get_wechat_public_certs')) {
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ * @throws InvalidParamsException
+ * @throws InvalidResponseException
+ */
+ function get_wechat_public_certs(array $params = [], ?string $path = null): void
+ {
+ reload_wechat_public_certs($params);
+
+ $config = get_wechat_config($params);
+
+ if (empty($path)) {
+ var_dump($config['wechat_public_cert_path']);
+
+ return;
+ }
+
+ foreach ($config['wechat_public_cert_path'] as $serialNo => $cert) {
+ file_put_contents($path.'/'.$serialNo.'.crt', $cert);
+ }
+ }
+}
+
+if (!function_exists('decrypt_wechat_resource')) {
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws InvalidResponseException
+ * @throws ServiceNotFoundException
+ */
+ function decrypt_wechat_resource(array $resource, array $params): array
+ {
+ $ciphertext = base64_decode($resource['ciphertext'] ?? '');
+ $secret = get_wechat_config($params)['mch_secret_key'] ?? null;
+
+ if (strlen($ciphertext) <= Wechat::AUTH_TAG_LENGTH_BYTE) {
+ throw new InvalidResponseException(Exception::INVALID_CIPHERTEXT_PARAMS);
+ }
+
+ if (is_null($secret) || Wechat::MCH_SECRET_KEY_LENGTH_BYTE != strlen($secret)) {
+ throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_secret_key]');
+ }
+
+ switch ($resource['algorithm'] ?? '') {
+ case 'AEAD_AES_256_GCM':
+ $resource['ciphertext'] = decrypt_wechat_resource_aes_256_gcm($ciphertext, $secret, $resource['nonce'] ?? '', $resource['associated_data'] ?? '');
+
+ break;
+
+ default:
+ throw new InvalidResponseException(Exception::INVALID_REQUEST_ENCRYPTED_METHOD);
+ }
+
+ return $resource;
+ }
+}
+
+if (!function_exists('decrypt_wechat_resource_aes_256_gcm')) {
+ /**
+ * @return array|string
+ *
+ * @throws InvalidResponseException
+ */
+ function decrypt_wechat_resource_aes_256_gcm(string $ciphertext, string $secret, string $nonce, string $associatedData)
+ {
+ $decrypted = openssl_decrypt(
+ substr($ciphertext, 0, -Wechat::AUTH_TAG_LENGTH_BYTE),
+ 'aes-256-gcm',
+ $secret,
+ OPENSSL_RAW_DATA,
+ $nonce,
+ substr($ciphertext, -Wechat::AUTH_TAG_LENGTH_BYTE),
+ $associatedData
+ );
+
+ if ('certificate' !== $associatedData) {
+ $decrypted = json_decode(strval($decrypted), true);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new InvalidResponseException(Exception::INVALID_REQUEST_ENCRYPTED_DATA);
+ }
+ }
+
+ return $decrypted;
+ }
+}
+
+if (!function_exists('get_unipay_config')) {
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ function get_unipay_config(array $params): array
+ {
+ $unipay = Pay::get(ConfigInterface::class)->get('unipay');
+
+ return $unipay[get_tenant($params)] ?? [];
+ }
+}
+
+if (!function_exists('verify_unipay_sign')) {
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws InvalidResponseException
+ * @throws ServiceNotFoundException
+ */
+ function verify_unipay_sign(array $params, string $contents, string $sign): void
+ {
+ if (empty($params['signPubKeyCert'])
+ && empty($public = get_unipay_config($params)['unipay_public_cert_path'] ?? null)) {
+ throw new InvalidConfigException(Exception::UNIPAY_CONFIG_ERROR, 'Missing Unipay Config -- [unipay_public_cert_path]');
+ }
+
+ $result = 1 === openssl_verify(
+ hash('sha256', $contents),
+ base64_decode($sign),
+ get_public_cert($params['signPubKeyCert'] ?? $public ?? ''),
+ 'sha256'
+ );
+
+ if (!$result) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, 'Verify Unipay Response Sign Failed', func_get_args());
+ }
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Logger.php b/addons/epay/library/v3/Yansongda/Pay/Logger.php
new file mode 100644
index 0000000..4643160
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Logger.php
@@ -0,0 +1,48 @@
+get('logger.enable', false)) {
+ return;
+ }
+
+ $class = Pay::get(LoggerInterface::class);
+
+ if ($class instanceof \Psr\Log\LoggerInterface || $class instanceof \Yansongda\Supports\Logger) {
+ $class->{$method}(...$args);
+
+ return;
+ }
+
+ throw new InvalidConfigException(Exception\Exception::LOGGER_CONFIG_ERROR);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Packer/JsonPacker.php b/addons/epay/library/v3/Yansongda/Pay/Packer/JsonPacker.php
new file mode 100644
index 0000000..3c64202
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Packer/JsonPacker.php
@@ -0,0 +1,26 @@
+';
+
+ foreach ($payload as $key => $val) {
+ $xml .= is_numeric($val) ? '<'.$key.'>'.$val.''.$key.'>' :
+ '<'.$key.'>'.$key.'>';
+ }
+
+ $xml .= '';
+
+ return $xml;
+ }
+
+ public function unpack(string $payload): ?array
+ {
+ if (empty($payload)) {
+ return [];
+ }
+
+ if (PHP_VERSION_ID < 80000) {
+ libxml_disable_entity_loader();
+ }
+
+ $data = json_decode(json_encode(
+ simplexml_load_string($payload, 'SimpleXMLElement', LIBXML_NOCDATA),
+ JSON_UNESCAPED_UNICODE
+ ), true);
+
+ if (JSON_ERROR_NONE === json_last_error()) {
+ return $data;
+ }
+
+ return null;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Pay.php b/addons/epay/library/v3/Yansongda/Pay/Pay.php
new file mode 100644
index 0000000..7f3c799
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Pay.php
@@ -0,0 +1,267 @@
+registerServices($config, $container);
+
+ Pay::set(DirectionInterface::class, CollectionDirection::class);
+ Pay::set(PackerInterface::class, JsonPacker::class);
+ }
+
+ /**
+ * @return mixed
+ *
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ public static function __callStatic(string $service, array $config)
+ {
+ if (!empty($config)) {
+ self::config(...$config);
+ }
+
+ return self::get($service);
+ }
+
+ /**
+ * @param null|Closure|ContainerInterface $container
+ *
+ * @throws ContainerException
+ */
+ public static function config(array $config = [], $container = null): bool
+ {
+ if (self::hasContainer() && !($config['_force'] ?? false)) {
+ return false;
+ }
+
+ new self($config, $container);
+
+ return true;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ *
+ * @param mixed $value
+ *
+ * @throws ContainerException
+ */
+ public static function set(string $name, $value): void
+ {
+ try {
+ $container = Pay::getContainer();
+
+ if ($container instanceof LaravelContainer) {
+ $container->singleton($name, $value instanceof Closure ? $value : static fn () => $value);
+
+ return;
+ }
+
+ if (method_exists($container, 'set')) {
+ $container->set(...func_get_args());
+
+ return;
+ }
+ } catch (ContainerNotFoundException $e) {
+ throw $e;
+ } catch (Throwable $e) {
+ throw new ContainerException($e->getMessage());
+ }
+
+ throw new ContainerException('Current container does NOT support `set` method');
+ }
+
+ /**
+ * @codeCoverageIgnore
+ *
+ * @return mixed
+ *
+ * @throws ContainerException
+ */
+ public static function make(string $service, array $parameters = [])
+ {
+ try {
+ $container = Pay::getContainer();
+
+ if (method_exists($container, 'make')) {
+ return $container->make(...func_get_args());
+ }
+ } catch (ContainerNotFoundException $e) {
+ throw $e;
+ } catch (Throwable $e) {
+ throw new ContainerException($e->getMessage());
+ }
+
+ $parameters = array_values($parameters);
+
+ return new $service(...$parameters);
+ }
+
+ /**
+ * @return mixed
+ *
+ * @throws ServiceNotFoundException
+ * @throws ContainerException
+ */
+ public static function get(string $service)
+ {
+ try {
+ return Pay::getContainer()->get($service);
+ } catch (NotFoundExceptionInterface $e) {
+ throw new ServiceNotFoundException($e->getMessage());
+ } catch (ContainerNotFoundException $e) {
+ throw $e;
+ } catch (Throwable $e) {
+ throw new ContainerException($e->getMessage());
+ }
+ }
+
+ /**
+ * @throws ContainerNotFoundException
+ */
+ public static function has(string $service): bool
+ {
+ return Pay::getContainer()->has($service);
+ }
+
+ /**
+ * @param null|Closure|ContainerInterface $container
+ */
+ public static function setContainer($container): void
+ {
+ self::$container = $container;
+ }
+
+ /**
+ * @throws ContainerNotFoundException
+ */
+ public static function getContainer(): ContainerInterface
+ {
+ if (self::$container instanceof ContainerInterface) {
+ return self::$container;
+ }
+
+ if (self::$container instanceof Closure) {
+ return (self::$container)();
+ }
+
+ throw new ContainerNotFoundException('`getContainer()` failed! Maybe you should `setContainer()` first', Exception\Exception::CONTAINER_NOT_FOUND);
+ }
+
+ public static function hasContainer(): bool
+ {
+ return self::$container instanceof ContainerInterface || self::$container instanceof Closure;
+ }
+
+ public static function clear(): void
+ {
+ self::$container = null;
+ }
+
+ /**
+ * @param mixed $data
+ *
+ * @throws ContainerException
+ */
+ public static function registerService(string $service, $data): void
+ {
+ $var = new $service();
+
+ if ($var instanceof ServiceProviderInterface) {
+ $var->register($data);
+ }
+ }
+
+ /**
+ * @param null|Closure|ContainerInterface $container
+ *
+ * @throws ContainerException
+ */
+ private function registerServices(array $config, $container = null): void
+ {
+ foreach (array_merge($this->coreService, $this->service) as $service) {
+ self::registerService($service, ContainerServiceProvider::class == $service ? $container : $config);
+ }
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/CallbackPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/CallbackPlugin.php
new file mode 100644
index 0000000..48ea386
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/CallbackPlugin.php
@@ -0,0 +1,65 @@
+ $rocket]);
+
+ $this->formatPayload($rocket);
+ $sign = $rocket->getParams()['sign'] ?? false;
+
+ if (!$sign) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', $rocket->getParams());
+ }
+
+ verify_alipay_sign($rocket->getParams(), $this->getSignContent($rocket->getPayload()), $sign);
+
+ $rocket->setDirection(NoHttpRequestDirection::class)
+ ->setDestination($rocket->getPayload())
+ ;
+
+ Logger::info('[alipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function formatPayload(Rocket $rocket): void
+ {
+ $payload = (new Collection($rocket->getParams()))
+ ->filter(fn ($v, $k) => '' !== $v && !is_null($v) && 'sign' != $k && 'sign_type' != $k && !Str::startsWith($k, '_'))
+ ;
+
+ $rocket->setPayload($payload);
+ }
+
+ protected function getSignContent(Collection $payload): string
+ {
+ return $payload->sortKeys()->toString();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillDownloadUrlQueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillDownloadUrlQueryPlugin.php
new file mode 100644
index 0000000..f885b10
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillDownloadUrlQueryPlugin.php
@@ -0,0 +1,18 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.bill.ereceipt.apply',
+ 'biz_content' => array_merge(
+ [
+ 'type' => 'FUND_DETAIL',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][BillEreceiptApplyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillEreceiptQueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillEreceiptQueryPlugin.php
new file mode 100644
index 0000000..2e74009
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Data/BillEreceiptQueryPlugin.php
@@ -0,0 +1,18 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.ebpp.pdeduct.pay',
+ 'biz_content' => array_merge(
+ [
+ 'agent_channel' => 'PUBLICFORM',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][PdeductPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignAddPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignAddPlugin.php
new file mode 100644
index 0000000..5033698
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignAddPlugin.php
@@ -0,0 +1,37 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.ebpp.pdeduct.sign.add',
+ 'biz_content' => array_merge(
+ [
+ 'charge_inst' => 'CQCENTERELECTRIC',
+ 'agent_channel' => 'PUBLICPLATFORM',
+ 'deduct_prod_code' => 'INST_DIRECT_DEDUCT',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][PdeductSignAddPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignCancelPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignCancelPlugin.php
new file mode 100644
index 0000000..83af535
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Ebpp/PdeductSignCancelPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.ebpp.pdeduct.sign.cancel',
+ 'biz_content' => array_merge(
+ [
+ 'agent_channel' => 'PUBLICPLATFORM',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][PdeductSignCancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AccountQueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AccountQueryPlugin.php
new file mode 100644
index 0000000..1e3d54e
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AccountQueryPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.account.query',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'TRANS_ACCOUNT_NO_PWD',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][AccountQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderFreezePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderFreezePlugin.php
new file mode 100644
index 0000000..72edabb
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderFreezePlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.auth.order.freeze',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'PRE_AUTH',
+ ],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[alipay][AuthOrderFreezePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderUnfreezePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderUnfreezePlugin.php
new file mode 100644
index 0000000..d7bbfa3
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/AuthOrderUnfreezePlugin.php
@@ -0,0 +1,18 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.trans.common.query',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'TRANS_ACCOUNT_NO_PWD',
+ 'biz_scene' => 'DIRECT_TRANSFER',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][TransCommonQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransPagePayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransPagePayPlugin.php
new file mode 100644
index 0000000..a5511f2
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransPagePayPlugin.php
@@ -0,0 +1,33 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.fund.trans.page.pay',
+ 'biz_content' => $rocket->getParams(),
+ ])
+ ;
+
+ Logger::info('[alipay][TransPagePayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransTobankTransferPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransTobankTransferPlugin.php
new file mode 100644
index 0000000..394584c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Fund/TransTobankTransferPlugin.php
@@ -0,0 +1,15 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.trans.uni.transfer',
+ 'biz_content' => array_merge(
+ [
+ 'biz_scene' => 'DIRECT_TRANSFER',
+ 'product_code' => 'TRANS_ACCOUNT_NO_PWD',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][TransUniTransferPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/GeneralPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/GeneralPlugin.php
new file mode 100644
index 0000000..36a7313
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/GeneralPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $this->doSomethingBefore($rocket);
+
+ $rocket->mergePayload([
+ 'method' => $this->getMethod(),
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[alipay][GeneralPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function doSomethingBefore(Rocket $rocket): void
+ {
+ }
+
+ abstract protected function getMethod(): string;
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/HtmlResponsePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/HtmlResponsePlugin.php
new file mode 100644
index 0000000..40b018c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/HtmlResponsePlugin.php
@@ -0,0 +1,71 @@
+ $rocket]);
+
+ $radar = $rocket->getRadar();
+
+ $response = 'GET' === $radar->getMethod() ?
+ $this->buildRedirect($radar->getUri()->__toString(), $rocket->getPayload()) :
+ $this->buildHtml($radar->getUri()->__toString(), $rocket->getPayload());
+
+ $rocket->setDestination($response);
+
+ Logger::info('[alipay][HtmlResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ protected function buildRedirect(string $endpoint, Collection $payload): Response
+ {
+ $url = $endpoint.(false === strpos($endpoint, '?') ? '?' : '&').$payload->query();
+
+ $content = sprintf(
+ '
+
+
+
+
+
+ Redirecting to %1$s
+
+
+ Redirecting to %1$s.
+
+ ',
+ htmlspecialchars($url, ENT_QUOTES)
+ );
+
+ return new Response(302, ['Location' => $url], $content);
+ }
+
+ protected function buildHtml(string $endpoint, Collection $payload): Response
+ {
+ $sHtml = "";
+ $sHtml .= "";
+
+ return new Response(200, [], $sHtml);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/LaunchPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/LaunchPlugin.php
new file mode 100644
index 0000000..025225e
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/LaunchPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ if (should_do_http_request($rocket->getDirection())) {
+ $response = Collection::wrap($rocket->getDestination());
+ $result = $response->get($this->getResultKey($rocket->getPayload()));
+
+ $this->verifySign($rocket->getParams(), $response, $result);
+
+ $rocket->setDestination(Collection::wrap($result));
+ }
+
+ Logger::info('[alipay][LaunchPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws InvalidResponseException
+ * @throws ServiceNotFoundException
+ */
+ protected function verifySign(array $params, Collection $response, ?array $result): void
+ {
+ $sign = $response->get('sign', '');
+
+ if ('' === $sign || is_null($result)) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, 'Verify Alipay Response Sign Failed: sign is empty', $response);
+ }
+
+ verify_alipay_sign($params, json_encode($result, JSON_UNESCAPED_UNICODE), $sign);
+ }
+
+ protected function getResultKey(Collection $payload): string
+ {
+ $method = $payload->get('method');
+
+ return str_replace('.', '_', $method).'_response';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/PreparePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/PreparePlugin.php
new file mode 100644
index 0000000..b4ace01
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/PreparePlugin.php
@@ -0,0 +1,214 @@
+ $rocket]);
+
+ $rocket->mergePayload($this->getPayload($rocket->getParams()));
+
+ Logger::info('[alipay][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidConfigException
+ */
+ protected function getPayload(array $params): array
+ {
+ $tenant = get_tenant($params);
+ $config = get_alipay_config($params);
+
+ $payload = [
+ 'app_id' => $config['app_id'] ?? '',
+ 'method' => '',
+ 'format' => 'JSON',
+ 'return_url' => $this->getReturnUrl($params, $config),
+ 'charset' => 'utf-8',
+ 'sign_type' => 'RSA2',
+ 'sign' => '',
+ 'timestamp' => date('Y-m-d H:i:s'),
+ 'version' => '1.0',
+ 'notify_url' => $this->getNotifyUrl($params, $config),
+ 'app_auth_token' => $this->getAppAuthToken($params, $config),
+ 'biz_content' => [],
+ ];
+ if (!empty($config['app_cert_public_key']) && !empty($config['alipay_root_cert'])) {
+ $payload = array_merge($payload, ['app_cert_sn' => $this->getAppCertSn($tenant, $config), 'alipay_root_cert_sn' => $this->getAlipayRootCertSn($tenant, $config)]);
+ }
+ return $payload;
+ }
+
+ protected function getReturnUrl(array $params, array $config): string
+ {
+ if (!empty($params['_return_url'])) {
+ return $params['_return_url'];
+ }
+
+ return $config['return_url'] ?? '';
+ }
+
+ protected function getNotifyUrl(array $params, array $config): string
+ {
+ if (!empty($params['_notify_url'])) {
+ return $params['_notify_url'];
+ }
+
+ return $config['notify_url'] ?? '';
+ }
+
+ protected function getAppAuthToken(array $params, array $config): string
+ {
+ if (!empty($params['_app_auth_token'])) {
+ return $params['_app_auth_token'];
+ }
+
+ return $config['app_auth_token'] ?? '';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getAppCertSn(string $tenant, array $config): string
+ {
+ if (!empty($config['app_public_cert_sn'])) {
+ return $config['app_public_cert_sn'];
+ }
+
+ $path = $config['app_public_cert_path'] ?? null;
+
+ if (is_null($path)) {
+ throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [app_public_cert_path]');
+ }
+
+ $cert = file_get_contents($path);
+ $ssl = openssl_x509_parse($cert);
+
+ if (false === $ssl) {
+ throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Parse `app_public_cert_path` Error');
+ }
+
+ $result = $this->getCertSn($ssl['issuer'] ?? [], $ssl['serialNumber'] ?? '');
+
+ Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.app_public_cert_sn', $result);
+
+ return $result;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getAlipayRootCertSn(string $tenant, array $config): string
+ {
+ if (!empty($config['alipay_root_cert_sn'])) {
+ return $config['alipay_root_cert_sn'];
+ }
+
+ $path = $config['alipay_root_cert_path'] ?? null;
+
+ if (is_null($path)) {
+ throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [alipay_root_cert_path]');
+ }
+
+ $sn = '';
+ $exploded = explode('-----END CERTIFICATE-----', file_get_contents($path));
+
+ foreach ($exploded as $cert) {
+ if (empty(trim($cert))) {
+ continue;
+ }
+
+ $ssl = openssl_x509_parse($cert.'-----END CERTIFICATE-----');
+
+ if (false === $ssl) {
+ throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Invalid alipay_root_cert');
+ }
+
+ $detail = $this->formatCert($ssl);
+
+ if ('sha1WithRSAEncryption' == $detail['signatureTypeLN'] || 'sha256WithRSAEncryption' == $detail['signatureTypeLN']) {
+ $sn .= $this->getCertSn($detail['issuer'], $detail['serialNumber']).'_';
+ }
+ }
+
+ $result = substr($sn, 0, -1);
+
+ Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.alipay_root_cert_sn', $result);
+
+ return $result;
+ }
+
+ protected function getCertSn(array $issuer, string $serialNumber): string
+ {
+ return md5(
+ $this->array2string(array_reverse($issuer)).$serialNumber
+ );
+ }
+
+ protected function array2string(array $array): string
+ {
+ $string = [];
+
+ foreach ($array as $key => $value) {
+ $string[] = $key.'='.$value;
+ }
+
+ return implode(',', $string);
+ }
+
+ protected function formatCert(array $ssl): array
+ {
+ if (0 === strpos($ssl['serialNumber'] ?? '', '0x')) {
+ $ssl['serialNumber'] = $this->hex2dec($ssl['serialNumberHex'] ?? '');
+ }
+
+ return $ssl;
+ }
+
+ protected function hex2dec(string $hex): string
+ {
+ $dec = '0';
+ $len = strlen($hex);
+
+ for ($i = 1; $i <= $len; ++$i) {
+ $dec = bcadd(
+ $dec,
+ bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i), 0), 0),
+ 0
+ );
+ }
+
+ return $dec;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/RadarSignPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/RadarSignPlugin.php
new file mode 100644
index 0000000..b8f3d57
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/RadarSignPlugin.php
@@ -0,0 +1,144 @@
+ $rocket]);
+
+ $this->sign($rocket);
+
+ $this->reRadar($rocket);
+
+ Logger::info('[alipay][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function sign(Rocket $rocket): void
+ {
+ $this->formatPayload($rocket);
+
+ $sign = $this->getSign($rocket);
+
+ $rocket->mergePayload(['sign' => $sign]);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function reRadar(Rocket $rocket): void
+ {
+ $params = $rocket->getParams();
+
+ $rocket->setRadar(new Request(
+ $this->getMethod($params),
+ $this->getUrl($params),
+ $this->getHeaders(),
+ $this->getBody($rocket->getPayload()),
+ ));
+ }
+
+ protected function formatPayload(Rocket $rocket): void
+ {
+ $payload = $rocket->getPayload()->filter(fn ($v, $k) => '' !== $v && !is_null($v) && 'sign' != $k);
+
+ $contents = array_filter($payload->get('biz_content', []), fn ($v, $k) => !Str::startsWith(strval($k), '_'), ARRAY_FILTER_USE_BOTH);
+
+ $rocket->setPayload(
+ $payload->merge(['biz_content' => json_encode($contents)])
+ );
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getSign(Rocket $rocket): string
+ {
+ $privateKey = $this->getPrivateKey($rocket->getParams());
+
+ $content = $rocket->getPayload()->sortKeys()->toString();
+
+ openssl_sign($content, $sign, $privateKey, OPENSSL_ALGO_SHA256);
+
+ return base64_encode($sign);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getPrivateKey(array $params): string
+ {
+ $privateKey = get_alipay_config($params)['app_secret_cert'] ?? null;
+
+ if (is_null($privateKey)) {
+ throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [app_secret_cert]');
+ }
+
+ return get_private_cert($privateKey);
+ }
+
+ protected function getMethod(array $params): string
+ {
+ return strtoupper($params['_method'] ?? 'POST');
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUrl(array $params): string
+ {
+ $config = get_alipay_config($params);
+
+ return Alipay::URL[$config['mode'] ?? Pay::MODE_NORMAL];
+ }
+
+ protected function getHeaders(): array
+ {
+ return [
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ ];
+ }
+
+ protected function getBody(Collection $payload): string
+ {
+ return $payload->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/AppShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/AppShortcut.php
new file mode 100644
index 0000000..d40509c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/AppShortcut.php
@@ -0,0 +1,47 @@
+buildResponse(),
+ ];
+ }
+
+ protected function buildResponse(): PluginInterface
+ {
+ return new class() implements PluginInterface {
+ public function assembly(Rocket $rocket, Closure $next): Rocket
+ {
+ $rocket->setDestination(new Response());
+
+ /* @var Rocket $rocket */
+ $rocket = $next($rocket);
+
+ $response = $this->buildHtml($rocket->getPayload());
+
+ return $rocket->setDestination($response);
+ }
+
+ protected function buildHtml(Collection $payload): Response
+ {
+ return new Response(200, [], Arr::query($payload->all()));
+ }
+ };
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/CancelShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/CancelShortcut.php
new file mode 100644
index 0000000..752ac23
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/CancelShortcut.php
@@ -0,0 +1,18 @@
+refundPlugins();
+ }
+
+ if (method_exists($this, $typeMethod)) {
+ return $this->{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Query action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ QueryPlugin::class,
+ ];
+ }
+
+ protected function refundPlugins(): array
+ {
+ return [
+ FastRefundQueryPlugin::class,
+ ];
+ }
+
+ protected function transferPlugins(): array
+ {
+ return [
+ TransCommonQueryPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/RefundShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/RefundShortcut.php
new file mode 100644
index 0000000..d1981c2
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Shortcut/RefundShortcut.php
@@ -0,0 +1,18 @@
+mergePayload($rocket->getParams());
+ }
+
+ protected function getMethod(): string
+ {
+ return 'alipay.system.oauth.token';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/AppPayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/AppPayPlugin.php
new file mode 100644
index 0000000..250e1aa
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/AppPayPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.app.pay',
+ 'biz_content' => array_merge(
+ ['product_code' => 'QUICK_MSECURITY_PAY'],
+ $rocket->getParams(),
+ ),
+ ])
+ ;
+
+ Logger::info('[alipay][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/CancelPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/CancelPlugin.php
new file mode 100644
index 0000000..40db0b4
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/CancelPlugin.php
@@ -0,0 +1,18 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.page.pay',
+ 'biz_content' => array_merge(
+ ['product_code' => 'FAST_INSTANT_TRADE_PAY'],
+ $rocket->getParams()
+ ),
+ ])
+ ;
+
+ Logger::info('[alipay][PagePayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PageRefundPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PageRefundPlugin.php
new file mode 100644
index 0000000..3234fbf
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PageRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.page.refund',
+ 'biz_content' => $rocket->getParams(),
+ ])
+ ;
+
+ Logger::info('[alipay][PageRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PayPlugin.php
new file mode 100644
index 0000000..d6a12b9
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PayPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'FACE_TO_FACE_PAYMENT',
+ 'scene' => 'bar_code',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[alipay][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PreCreatePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PreCreatePlugin.php
new file mode 100644
index 0000000..f8e4a7f
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/PreCreatePlugin.php
@@ -0,0 +1,33 @@
+loadAlipayServiceProvider($rocket);
+ }
+
+ protected function getMethod(): string
+ {
+ return 'alipay.trade.precreate';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/QueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/QueryPlugin.php
new file mode 100644
index 0000000..3c24d50
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/Trade/QueryPlugin.php
@@ -0,0 +1,18 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.wap.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'QUICK_WAP_PAY',
+ ],
+ $rocket->getParams(),
+ ),
+ ])
+ ;
+
+ Logger::info('[alipay][WapPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementExecutionPlanModifyPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementExecutionPlanModifyPlugin.php
new file mode 100644
index 0000000..97abb71
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementExecutionPlanModifyPlugin.php
@@ -0,0 +1,18 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.user.agreement.page.sign',
+ 'biz_content' => array_merge(
+ ['product_code' => 'CYCLE_PAY_AUTH'],
+ $rocket->getParams()
+ ),
+ ])
+ ;
+
+ Logger::info('[alipay][AgreementPageSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementQueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementQueryPlugin.php
new file mode 100644
index 0000000..590a5a5
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementQueryPlugin.php
@@ -0,0 +1,33 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.agreement.query',
+ 'biz_content' => array_merge(
+ ['personal_product_code' => 'CYCLE_PAY_AUTH_P'],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[alipay][AgreementQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementTransferPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementTransferPlugin.php
new file mode 100644
index 0000000..60fac81
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementTransferPlugin.php
@@ -0,0 +1,33 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.agreement.transfer',
+ 'biz_content' => array_merge(
+ ['target_product_code' => 'CYCLE_PAY_AUTH_P'],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[alipay][AgreementTransferPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementUnsignPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementUnsignPlugin.php
new file mode 100644
index 0000000..117cce2
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/AgreementUnsignPlugin.php
@@ -0,0 +1,33 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.agreement.unsign',
+ 'biz_content' => array_merge(
+ ['personal_product_code' => 'CYCLE_PAY_AUTH_P'],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[alipay][AgreementUnsignPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/InfoSharePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/InfoSharePlugin.php
new file mode 100644
index 0000000..65de288
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Alipay/User/InfoSharePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.info.share',
+ 'auth_token' => $rocket->getParams()['auth_token'] ?? '',
+ ]);
+
+ Logger::info('[alipay][InfoSharePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/ParserPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/ParserPlugin.php
new file mode 100644
index 0000000..64fbf4f
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/ParserPlugin.php
@@ -0,0 +1,74 @@
+getDestination();
+
+ return $rocket->setDestination(
+ $this->getDirection($rocket)->parse($this->getPacker($rocket), $response)
+ );
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getDirection(Rocket $rocket): DirectionInterface
+ {
+ $packer = Pay::get($rocket->getDirection());
+
+ $packer = is_string($packer) ? Pay::get($packer) : $packer;
+
+ if (!$packer instanceof DirectionInterface) {
+ throw new InvalidConfigException(Exception::INVALID_PARSER);
+ }
+
+ return $packer;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getPacker(Rocket $rocket): PackerInterface
+ {
+ $packer = Pay::get($rocket->getPacker());
+
+ $packer = is_string($packer) ? Pay::get($packer) : $packer;
+
+ if (!$packer instanceof PackerInterface) {
+ throw new InvalidConfigException(Exception::INVALID_PACKER);
+ }
+
+ return $packer;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/CallbackPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/CallbackPlugin.php
new file mode 100644
index 0000000..b234dbd
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/CallbackPlugin.php
@@ -0,0 +1,62 @@
+ $rocket]);
+
+ $this->formatPayload($rocket);
+
+ $params = $rocket->getParams();
+ $signature = $params['signature'] ?? false;
+
+ if (!$signature) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', $params);
+ }
+
+ verify_unipay_sign($params, $rocket->getPayload()->sortKeys()->toString(), $signature);
+
+ $rocket->setDirection(NoHttpRequestDirection::class)
+ ->setDestination($rocket->getPayload())
+ ;
+
+ Logger::info('[unipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function formatPayload(Rocket $rocket): void
+ {
+ $payload = (new Collection($rocket->getParams()))
+ ->filter(fn ($v, $k) => 'signature' != $k && !Str::startsWith($k, '_'))
+ ;
+
+ $rocket->setPayload($payload);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/GeneralPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/GeneralPlugin.php
new file mode 100644
index 0000000..2c53dd9
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/GeneralPlugin.php
@@ -0,0 +1,84 @@
+ $rocket]);
+
+ $rocket->setRadar($this->getRequest($rocket));
+ $this->doSomething($rocket);
+
+ Logger::info('[unipay][GeneralPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getRequest(Rocket $rocket): RequestInterface
+ {
+ return new Request(
+ $this->getMethod(),
+ $this->getUrl($rocket),
+ $this->getHeaders(),
+ );
+ }
+
+ protected function getMethod(): string
+ {
+ return 'POST';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUrl(Rocket $rocket): string
+ {
+ $url = $this->getUri($rocket);
+
+ if (0 === strpos($url, 'http')) {
+ return $url;
+ }
+
+ $config = get_unipay_config($rocket->getParams());
+
+ return Unipay::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
+ }
+
+ protected function getHeaders(): array
+ {
+ return [
+ 'User-Agent' => 'yansongda/pay-v3',
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
+ ];
+ }
+
+ abstract protected function doSomething(Rocket $rocket): void;
+
+ abstract protected function getUri(Rocket $rocket): string;
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/HtmlResponsePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/HtmlResponsePlugin.php
new file mode 100644
index 0000000..e900e00
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/HtmlResponsePlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $radar = $rocket->getRadar();
+
+ $response = $this->buildHtml($radar->getUri()->__toString(), $rocket->getPayload());
+
+ $rocket->setDestination($response);
+
+ Logger::info('[unipay][HtmlResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ protected function buildHtml(string $endpoint, Collection $payload): Response
+ {
+ $sHtml = "";
+ $sHtml .= "";
+
+ return new Response(200, [], $sHtml);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/LaunchPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/LaunchPlugin.php
new file mode 100644
index 0000000..563bde3
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/LaunchPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ if (should_do_http_request($rocket->getDirection())) {
+ $response = Collection::wrap($rocket->getDestination());
+ $signature = $response->get('signature');
+ $response->forget('signature');
+
+ verify_unipay_sign(
+ $rocket->getParams(),
+ $response->sortKeys()->toString(),
+ $signature
+ );
+
+ $rocket->setDestination($response);
+ }
+
+ Logger::info('[unipay][LaunchPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/CancelPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/CancelPlugin.php
new file mode 100644
index 0000000..c6397c8
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/CancelPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '31',
+ 'txnSubType' => '00',
+ 'channelType' => '07',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/PagePayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/PagePayPlugin.php
new file mode 100644
index 0000000..2038347
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/PagePayPlugin.php
@@ -0,0 +1,32 @@
+setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'bizType' => '000201',
+ 'txnType' => '01',
+ 'txnSubType' => '01',
+ 'channelType' => '07',
+ ])
+ ;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/QueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/QueryPlugin.php
new file mode 100644
index 0000000..c4060f8
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/QueryPlugin.php
@@ -0,0 +1,28 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '00',
+ 'txnSubType' => '00',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/RefundPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/RefundPlugin.php
new file mode 100644
index 0000000..79c2a1f
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/RefundPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '04',
+ 'txnSubType' => '00',
+ 'channelType' => '07',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/WapPayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/WapPayPlugin.php
new file mode 100644
index 0000000..4048591
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/OnlineGateway/WapPayPlugin.php
@@ -0,0 +1,32 @@
+setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'bizType' => '000201',
+ 'txnType' => '01',
+ 'txnSubType' => '01',
+ 'channelType' => '08',
+ ])
+ ;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/PreparePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/PreparePlugin.php
new file mode 100644
index 0000000..598cfd8
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/PreparePlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $rocket->mergePayload($this->getPayload($rocket->getParams()));
+
+ Logger::info('[unipay][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidConfigException
+ */
+ protected function getPayload(array $params): array
+ {
+ $tenant = get_tenant($params);
+ $config = get_unipay_config($params);
+
+ $init = [
+ 'version' => '5.1.0',
+ 'encoding' => 'utf-8',
+ 'backUrl' => $config['notify_url'] ?? '',
+ 'currencyCode' => '156',
+ 'accessType' => '0',
+ 'signature' => '',
+ 'signMethod' => '01',
+ 'merId' => $config['mch_id'] ?? '',
+ 'frontUrl' => $config['return_url'] ?? '',
+ 'certId' => $this->getCertId($tenant, $config),
+ ];
+
+ return array_merge(
+ $init,
+ array_filter($params, fn ($v, $k) => !Str::startsWith(strval($k), '_'), ARRAY_FILTER_USE_BOTH),
+ );
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/CancelPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/CancelPlugin.php
new file mode 100644
index 0000000..80498bf
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/CancelPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '31',
+ 'txnSubType' => '00',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosNormalPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosNormalPlugin.php
new file mode 100644
index 0000000..2c5b8fc
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosNormalPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '01',
+ 'txnSubType' => '06',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosPreAuthPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosPreAuthPlugin.php
new file mode 100644
index 0000000..ff205dc
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/PosPreAuthPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000201',
+ 'txnType' => '02',
+ 'txnSubType' => '04',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/QueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/QueryPlugin.php
new file mode 100644
index 0000000..cc82020
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/QueryPlugin.php
@@ -0,0 +1,33 @@
+getParams());
+
+ if (Pay::MODE_SANDBOX === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return 'https://101.231.204.80:5000/gateway/api/backTransReq.do';
+ }
+
+ return parent::getUri($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/RefundPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/RefundPlugin.php
new file mode 100644
index 0000000..6f7d167
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/RefundPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '04',
+ 'txnSubType' => '00',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanFeePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanFeePlugin.php
new file mode 100644
index 0000000..922b047
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanFeePlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000601',
+ 'txnType' => '13',
+ 'txnSubType' => '08',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanNormalPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanNormalPlugin.php
new file mode 100644
index 0000000..15284ed
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanNormalPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '01',
+ 'txnSubType' => '07',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreAuthPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreAuthPlugin.php
new file mode 100644
index 0000000..559fc63
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreAuthPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '02',
+ 'txnSubType' => '05',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreOrderPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreOrderPlugin.php
new file mode 100644
index 0000000..cd3e461
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/QrCode/ScanPreOrderPlugin.php
@@ -0,0 +1,29 @@
+mergePayload([
+ 'bizType' => '000000',
+ 'txnType' => '01',
+ 'txnSubType' => '01',
+ 'channelType' => '08',
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/RadarSignPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/RadarSignPlugin.php
new file mode 100644
index 0000000..3aa6124
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/RadarSignPlugin.php
@@ -0,0 +1,100 @@
+ $rocket]);
+
+ $this->sign($rocket);
+
+ $this->reRadar($rocket);
+
+ Logger::info('[unipay][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function sign(Rocket $rocket): void
+ {
+ $payload = $rocket->getPayload()->filter(fn ($v, $k) => 'signature' != $k);
+ $config = $this->getConfig($rocket->getParams());
+
+ $rocket->mergePayload([
+ 'signature' => $this->getSignature($config['certs']['pkey'] ?? '', $payload),
+ ]);
+ }
+
+ protected function reRadar(Rocket $rocket): void
+ {
+ $body = $this->getBody($rocket->getPayload());
+ $radar = $rocket->getRadar();
+
+ if (!empty($body) && !empty($radar)) {
+ $radar = $radar->withBody(Utils::streamFor($body));
+
+ $rocket->setRadar($radar);
+ }
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getConfig(array $params): array
+ {
+ $config = get_unipay_config($params);
+
+ if (empty($config['certs']['pkey'])) {
+ $this->getCertId($params['_config'] ?? 'default', $config);
+
+ $config = get_unipay_config($params);
+ }
+
+ return $config;
+ }
+
+ protected function getSignature(string $pkey, Collection $payload): string
+ {
+ $content = $payload->sortKeys()->toString();
+
+ openssl_sign(hash('sha256', $content), $sign, $pkey, 'sha256');
+
+ return base64_encode($sign);
+ }
+
+ protected function getBody(Collection $payload): string
+ {
+ return $payload->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/CancelShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/CancelShortcut.php
new file mode 100644
index 0000000..802d97c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/CancelShortcut.php
@@ -0,0 +1,42 @@
+{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Cancel action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ CancelPlugin::class,
+ ];
+ }
+
+ protected function qrCodePlugins(): array
+ {
+ return [
+ \Yansongda\Pay\Plugin\Unipay\QrCode\CancelPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/PosShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/PosShortcut.php
new file mode 100644
index 0000000..93dc107
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/PosShortcut.php
@@ -0,0 +1,43 @@
+{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Pos action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ PosNormalPlugin::class,
+ ];
+ }
+
+ protected function preAuthPlugins(): array
+ {
+ return [
+ PosPreAuthPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/QueryShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/QueryShortcut.php
new file mode 100644
index 0000000..a039372
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/QueryShortcut.php
@@ -0,0 +1,42 @@
+{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Query action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ QueryPlugin::class,
+ ];
+ }
+
+ protected function qrCodePlugins(): array
+ {
+ return [
+ \Yansongda\Pay\Plugin\Unipay\QrCode\QueryPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/RefundShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/RefundShortcut.php
new file mode 100644
index 0000000..862ee6b
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/RefundShortcut.php
@@ -0,0 +1,42 @@
+{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Refund action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ RefundPlugin::class,
+ ];
+ }
+
+ protected function qrCodePlugins(): array
+ {
+ return [
+ \Yansongda\Pay\Plugin\Unipay\QrCode\RefundPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/ScanShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/ScanShortcut.php
new file mode 100644
index 0000000..a60127d
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/ScanShortcut.php
@@ -0,0 +1,59 @@
+{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Scan action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ ScanNormalPlugin::class,
+ ];
+ }
+
+ protected function preAuthPlugins(): array
+ {
+ return [
+ ScanPreAuthPlugin::class,
+ ];
+ }
+
+ protected function preOrderPlugins(): array
+ {
+ return [
+ ScanPreOrderPlugin::class,
+ ];
+ }
+
+ protected function feePlugins(): array
+ {
+ return [
+ ScanFeePlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/WapShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/WapShortcut.php
new file mode 100644
index 0000000..dcdcc90
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Unipay/Shortcut/WapShortcut.php
@@ -0,0 +1,20 @@
+ $rocket]);
+
+ $this->formatRequestAndParams($rocket);
+
+ /* @phpstan-ignore-next-line */
+ verify_wechat_sign($rocket->getDestinationOrigin(), $rocket->getParams());
+
+ $body = json_decode((string) $rocket->getDestination()->getBody(), true);
+
+ $rocket->setDirection(NoHttpRequestDirection::class)->setPayload(new Collection($body));
+
+ $body['resource'] = decrypt_wechat_resource($body['resource'] ?? [], $rocket->getParams());
+
+ $rocket->setDestination(new Collection($body));
+
+ Logger::info('[wechat][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function formatRequestAndParams(Rocket $rocket): void
+ {
+ $request = $rocket->getParams()['request'] ?? null;
+
+ if (!$request instanceof ServerRequestInterface) {
+ throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
+ }
+
+ $rocket->setDestination(clone $request)
+ ->setDestinationOrigin($request)
+ ->setParams($rocket->getParams()['params'] ?? [])
+ ;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ApplyPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ApplyPlugin.php
new file mode 100644
index 0000000..64967dc
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ApplyPlugin.php
@@ -0,0 +1,55 @@
+getParams());
+ $payload = $rocket->getPayload();
+ $key = $this->getConfigKey($rocket->getParams());
+
+ $wechatId = [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ 'sp_appid' => $payload->get('sp_appid', $config[$key] ?? ''),
+ ];
+
+ if (!$payload->has('notify_url')) {
+ $wechatId['notify_url'] = $config['notify_url'] ?? null;
+ }
+
+ $rocket->mergePayload($wechatId);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryPlugin.php
new file mode 100644
index 0000000..7a1cca1
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryPlugin.php
@@ -0,0 +1,60 @@
+getPayload();
+ $config = get_wechat_config($rocket->getParams());
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ if ($payload->has('refund_id')) {
+ return 'v3/ecommerce/refunds/id/'.$payload->get('refund_id').'?sub_mchid='.$subMchId;
+ }
+
+ if ($payload->has('out_refund_no')) {
+ return 'v3/ecommerce/refunds/out-refund-no/'.$payload->get('out_refund_no').'?sub_mchid='.$subMchId;
+ }
+
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryReturnAdvancePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryReturnAdvancePlugin.php
new file mode 100644
index 0000000..503e440
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/QueryReturnAdvancePlugin.php
@@ -0,0 +1,56 @@
+getPayload();
+ $config = get_wechat_config($rocket->getParams());
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ if (!$payload->has('refund_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/ecommerce/refunds/'.$payload->get('refund_id').'/return-advance?sub_mchid='.$subMchId;
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ReturnAdvancePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ReturnAdvancePlugin.php
new file mode 100644
index 0000000..8270b2e
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Ecommerce/Refund/ReturnAdvancePlugin.php
@@ -0,0 +1,56 @@
+getPayload();
+
+ if (!$payload->has('refund_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/ecommerce/refunds/'.$payload->get('refund_id').'/return-advance';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function doSomething(Rocket $rocket): void
+ {
+ $config = get_wechat_config($rocket->getParams());
+
+ $rocket->setPayload(new Collection([
+ 'sub_mchid' => $rocket->getPayload()->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]));
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryDayEndPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryDayEndPlugin.php
new file mode 100644
index 0000000..7964773
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryDayEndPlugin.php
@@ -0,0 +1,38 @@
+getPayload();
+
+ if (!$payload->has('account_type') || !$payload->has('date')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/merchant/fund/dayendbalance/'.
+ $payload->get('account_type').
+ '?date='.$payload->get('date');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryPlugin.php
new file mode 100644
index 0000000..9e10e5d
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Balance/QueryPlugin.php
@@ -0,0 +1,37 @@
+getPayload();
+
+ if (!$payload->has('account_type')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/merchant/fund/balance/'.
+ $payload->get('account_type');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/AddReceiverPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/AddReceiverPlugin.php
new file mode 100644
index 0000000..6510ca5
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/AddReceiverPlugin.php
@@ -0,0 +1,83 @@
+getParams();
+ $config = get_wechat_config($rocket->getParams());
+ $extra = $this->getWechatId($config, $rocket->getPayload());
+
+ if (!empty($params['name'] ?? '')) {
+ $params = $this->loadSerialNo($params);
+
+ $name = $this->getEncryptUserName($params);
+ $params['name'] = $name;
+ $extra['name'] = $name;
+ $rocket->setParams($params);
+ }
+
+ $rocket->mergePayload($extra);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/profitsharing/receivers/add';
+ }
+
+ protected function getWechatId(array $config, Collection $payload): array
+ {
+ $wechatId = [
+ 'appid' => $config['mp_app_id'] ?? null,
+ ];
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $wechatId['sub_mchid'] = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+ }
+
+ return $wechatId;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function getEncryptUserName(array $params): string
+ {
+ $name = $params['name'] ?? '';
+ $publicKey = $this->getPublicKey($params, $params['_serial_no'] ?? '');
+
+ return encrypt_wechat_contents($name, $publicKey);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/CreatePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/CreatePlugin.php
new file mode 100644
index 0000000..9f4cc66
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/CreatePlugin.php
@@ -0,0 +1,89 @@
+getPayload();
+ $params = $this->loadSerialNo($rocket->getParams());
+
+ $extra = $this->getWechatExtra($params, $payload);
+ $extra['receivers'] = $this->getReceivers($params);
+
+ $rocket->setParams($params);
+ $rocket->mergePayload($extra);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/profitsharing/orders';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getWechatExtra(array $params, Collection $payload): array
+ {
+ $config = get_wechat_config($params);
+
+ $extra = [
+ 'appid' => $config['mp_app_id'] ?? null,
+ ];
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $extra['sub_mchid'] = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+ }
+
+ return $extra;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function getReceivers(array $params): array
+ {
+ $publicKey = $this->getPublicKey($params, $params['_serial_no'] ?? '');
+ $receivers = $params['receivers'] ?? [];
+
+ foreach ($receivers as $key => $receiver) {
+ if (!empty($receiver['name'])) {
+ $receivers[$key]['name'] = encrypt_wechat_contents($receiver['name'], $publicKey);
+ }
+ }
+
+ return $receivers;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DeleteReceiverPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DeleteReceiverPlugin.php
new file mode 100644
index 0000000..9ea3ae1
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DeleteReceiverPlugin.php
@@ -0,0 +1,45 @@
+getParams());
+
+ $wechatId = [
+ 'appid' => $config['mp_app_id'] ?? null,
+ ];
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $wechatId['sub_mchid'] = $rocket->getPayload()
+ ->get('sub_mchid', $config['sub_mch_id'] ?? '')
+ ;
+ }
+
+ $rocket->mergePayload($wechatId);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/profitsharing/receivers/delete';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DownloadBillPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DownloadBillPlugin.php
new file mode 100644
index 0000000..8fa9170
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/DownloadBillPlugin.php
@@ -0,0 +1,43 @@
+getPayload();
+
+ if (!$payload->has('download_url')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return $payload->get('download_url');
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setDirection(OriginResponseDirection::class);
+
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryAmountsPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryAmountsPlugin.php
new file mode 100644
index 0000000..7bd73c7
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryAmountsPlugin.php
@@ -0,0 +1,42 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('transaction_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/profitsharing/transactions/'.
+ $payload->get('transaction_id').
+ '/amounts';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryMerchantConfigsPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryMerchantConfigsPlugin.php
new file mode 100644
index 0000000..06df349
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryMerchantConfigsPlugin.php
@@ -0,0 +1,49 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $config = get_wechat_config($rocket->getParams());
+ $payload = $rocket->getPayload();
+
+ if (Pay::MODE_SERVICE !== ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::METHOD_NOT_SUPPORTED);
+ }
+
+ return 'v3/profitsharing/merchant-configs/'.
+ $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryPlugin.php
new file mode 100644
index 0000000..141693c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryPlugin.php
@@ -0,0 +1,56 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $config = get_wechat_config($rocket->getParams());
+
+ if (!$payload->has('out_order_no') || !$payload->has('transaction_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $url = 'v3/profitsharing/orders/'.
+ $payload->get('out_order_no').
+ '?transaction_id='.$payload->get('transaction_id');
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $url .= '&sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+ }
+
+ return $url;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryReturnPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryReturnPlugin.php
new file mode 100644
index 0000000..a3e4888
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/QueryReturnPlugin.php
@@ -0,0 +1,56 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $config = get_wechat_config($rocket->getParams());
+
+ if (!$payload->has('out_return_no') || !$payload->has('out_order_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $url = 'v3/profitsharing/return-orders/'.
+ $payload->get('out_return_no').
+ '?out_order_no='.$payload->get('out_order_no');
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $url .= '&sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+ }
+
+ return $url;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/ReturnPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/ReturnPlugin.php
new file mode 100644
index 0000000..5e0f37a
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/ReturnPlugin.php
@@ -0,0 +1,40 @@
+getParams());
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $rocket->mergePayload([
+ 'sub_mchid' => $rocket->getPayload()
+ ->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]);
+ }
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/profitsharing/return-orders';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/UnfreezePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/UnfreezePlugin.php
new file mode 100644
index 0000000..5ccb837
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Profitsharing/UnfreezePlugin.php
@@ -0,0 +1,40 @@
+getPayload();
+ $config = get_wechat_config($rocket->getParams());
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null) && !$payload->has('sub_mchid')) {
+ $rocket->mergePayload([
+ 'sub_mchid' => $config['sub_mch_id'] ?? '',
+ ]);
+ }
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/profitsharing/orders/unfreeze';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateBillReceiptPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateBillReceiptPlugin.php
new file mode 100644
index 0000000..add6ce2
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateBillReceiptPlugin.php
@@ -0,0 +1,33 @@
+getPayload();
+
+ if (!$payload->has('out_batch_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/transfer/bill-receipt';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateDetailReceiptPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateDetailReceiptPlugin.php
new file mode 100644
index 0000000..882b376
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreateDetailReceiptPlugin.php
@@ -0,0 +1,33 @@
+getPayload();
+
+ if (!$payload->has('out_detail_no') || !$payload->has('accept_type')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/transfer-detail/electronic-receipts';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreatePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreatePlugin.php
new file mode 100644
index 0000000..a8459a4
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/CreatePlugin.php
@@ -0,0 +1,99 @@
+getParams();
+ $extra = $this->getWechatId($params, $rocket->getPayload());
+
+ if (!empty($params['transfer_detail_list'][0]['user_name'] ?? '')) {
+ $params = $this->loadSerialNo($params);
+
+ $rocket->setParams($params);
+
+ $extra['transfer_detail_list'] = $this->getEncryptUserName($params);
+ }
+
+ $rocket->mergePayload($extra);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/transfer/batches';
+ }
+
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ return 'v3/partner-transfer/batches';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getWechatId(array $params, Collection $payload): array
+ {
+ $config = get_wechat_config($params);
+ $key = $this->getConfigKey($params);
+
+ $appId = [
+ 'appid' => $payload->get('appid', $config[$key] ?? ''),
+ ];
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $appId = [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+
+ return $appId;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function getEncryptUserName(array $params): array
+ {
+ $lists = $params['transfer_detail_list'] ?? [];
+ $publicKey = $this->getPublicKey($params, $params['_serial_no'] ?? '');
+
+ foreach ($lists as $key => $list) {
+ $lists[$key]['user_name'] = encrypt_wechat_contents($list['user_name'], $publicKey);
+ }
+
+ return $lists;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/DownloadReceiptPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/DownloadReceiptPlugin.php
new file mode 100644
index 0000000..1bd5025
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/DownloadReceiptPlugin.php
@@ -0,0 +1,43 @@
+getPayload();
+
+ if (!$payload->has('download_url')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return $payload->get('download_url');
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setDirection(OriginResponseDirection::class);
+
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchDetailIdPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchDetailIdPlugin.php
new file mode 100644
index 0000000..b2251dd
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchDetailIdPlugin.php
@@ -0,0 +1,60 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('batch_id') || !$payload->get('detail_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/transfer/batches/batch-id/'.
+ $payload->get('batch_id').
+ '/details/detail-id/'.
+ $payload->get('detail_id');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('batch_id') || !$payload->has('detail_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/partner-transfer/batches/batch-id/'.
+ $payload->get('batch_id').
+ '/details/detail-id/'.
+ $payload->get('detail_id');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchIdPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchIdPlugin.php
new file mode 100644
index 0000000..c87a4f2
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBatchIdPlugin.php
@@ -0,0 +1,64 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('batch_id') || !$payload->has('need_query_detail')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $batchId = $payload->get('batch_id');
+
+ $payload->forget('batch_id');
+
+ return 'v3/transfer/batches/batch-id/'.$batchId.
+ '?'.$payload->query();
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('batch_id') || !$payload->has('need_query_detail')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $batchId = $payload->get('batch_id');
+
+ $payload->forget('batch_id');
+
+ return 'v3/partner-transfer/batches/batch-id/'.$batchId.
+ '?'.$payload->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBillReceiptPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBillReceiptPlugin.php
new file mode 100644
index 0000000..5fdd9da
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryBillReceiptPlugin.php
@@ -0,0 +1,40 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('out_batch_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/transfer/bill-receipt/'.$payload->get('out_batch_no');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryDetailReceiptPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryDetailReceiptPlugin.php
new file mode 100644
index 0000000..39aa13f
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryDetailReceiptPlugin.php
@@ -0,0 +1,40 @@
+getPayload();
+
+ if (!$payload->has('out_detail_no') || !$payload->has('accept_type')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $rocket->setPayload(null);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/transfer-detail/electronic-receipts?'.$rocket->getPayload()->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchDetailNoPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchDetailNoPlugin.php
new file mode 100644
index 0000000..199430b
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchDetailNoPlugin.php
@@ -0,0 +1,60 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('out_batch_no') || !$payload->has('out_detail_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/transfer/batches/out-batch-no/'.
+ $payload->get('out_batch_no').
+ '/details/out-detail-no/'.
+ $payload->get('out_detail_no');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('out_batch_no') || !$payload->has('out_detail_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/partner-transfer/batches/out-batch-no/'.
+ $payload->get('out_batch_no').
+ '/details/out-detail-no/'.
+ $payload->get('out_detail_no');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchNoPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchNoPlugin.php
new file mode 100644
index 0000000..ec5a191
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Fund/Transfer/QueryOutBatchNoPlugin.php
@@ -0,0 +1,64 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('out_batch_no') || !$payload->has('need_query_detail')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $outBatchNo = $payload->get('out_batch_no');
+
+ $payload->forget('out_batch_no');
+
+ return 'v3/transfer/batches/out-batch-no/'.$outBatchNo.
+ '?'.$payload->query();
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('out_batch_no') || !$payload->has('need_query_detail')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $outBatchNo = $payload->get('out_batch_no');
+
+ $payload->forget('out_batch_no');
+
+ return 'v3/partner-transfer/batches/out-batch-no/'.$outBatchNo.
+ '?'.$payload->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralPlugin.php
new file mode 100644
index 0000000..82e8702
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralPlugin.php
@@ -0,0 +1,97 @@
+ $rocket]);
+
+ $rocket->setRadar($this->getRequest($rocket));
+ $this->doSomething($rocket);
+
+ Logger::info('[wechat][GeneralPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getRequest(Rocket $rocket): RequestInterface
+ {
+ return new Request(
+ $this->getMethod(),
+ $this->getUrl($rocket),
+ $this->getHeaders(),
+ );
+ }
+
+ protected function getMethod(): string
+ {
+ return 'POST';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUrl(Rocket $rocket): string
+ {
+ $params = $rocket->getParams();
+
+ $url = Pay::MODE_SERVICE === (get_wechat_config($params)['mode'] ?? null) ? $this->getPartnerUri($rocket) : $this->getUri($rocket);
+
+ return 0 === strpos($url, 'http') ? $url : (get_wechat_base_uri($params).$url);
+ }
+
+ protected function getHeaders(): array
+ {
+ return [
+ 'Accept' => 'application/json, text/plain, application/x-gzip',
+ 'User-Agent' => 'yansongda/pay-v3',
+ 'Content-Type' => 'application/json; charset=utf-8',
+ ];
+ }
+
+ protected function getConfigKey(array $params): string
+ {
+ $key = ($params['_type'] ?? 'mp').'_app_id';
+
+ if ('app_app_id' === $key) {
+ $key = 'app_id';
+ }
+
+ return $key;
+ }
+
+ abstract protected function doSomething(Rocket $rocket): void;
+
+ abstract protected function getUri(Rocket $rocket): string;
+
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ return $this->getUri($rocket);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralV2Plugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralV2Plugin.php
new file mode 100644
index 0000000..ce6713d
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/GeneralV2Plugin.php
@@ -0,0 +1,42 @@
+ 'application/xml',
+ 'User-Agent' => 'yansongda/pay-v3',
+ ];
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function doSomething(Rocket $rocket): void
+ {
+ $config = get_wechat_config($rocket->getParams());
+ $configKey = $this->getConfigKey($rocket->getParams());
+
+ $rocket->setPacker(XmlPacker::class)->mergeParams(['_version' => 'v2']);
+
+ $rocket->mergePayload([
+ 'appid' => $config[$configKey] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ ]);
+ }
+
+ abstract protected function getUri(Rocket $rocket): string;
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/LaunchPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/LaunchPlugin.php
new file mode 100644
index 0000000..68e3983
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/LaunchPlugin.php
@@ -0,0 +1,67 @@
+ $rocket]);
+
+ if (should_do_http_request($rocket->getDirection())) {
+ verify_wechat_sign($rocket->getDestinationOrigin(), $rocket->getParams());
+
+ $rocket->setDestination($this->validateResponse($rocket));
+ }
+
+ Logger::info('[wechat][LaunchPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @return null|array|Collection|MessageInterface
+ *
+ * @throws InvalidResponseException
+ */
+ protected function validateResponse(Rocket $rocket)
+ {
+ $response = $rocket->getDestination();
+
+ if ($response instanceof ResponseInterface
+ && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300)) {
+ throw new InvalidResponseException(Exception::INVALID_RESPONSE_CODE);
+ }
+
+ return $response;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/CreatePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/CreatePlugin.php
new file mode 100644
index 0000000..d5bc8c2
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/CreatePlugin.php
@@ -0,0 +1,37 @@
+getParams());
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('belong_merchant')) {
+ $rocket->mergePayload(['belong_merchant' => $config['mch_id']]);
+ }
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/marketing/favor/coupon-stocks';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/PausePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/PausePlugin.php
new file mode 100644
index 0000000..4e27fe1
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/PausePlugin.php
@@ -0,0 +1,50 @@
+getParams());
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('stock_creator_mchid')) {
+ $rocket->mergePayload(['stock_creator_mchid' => $config['mch_id']]);
+ }
+
+ $rocket->getPayload()->forget('stock_id');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/stocks/'.$payload->get('stock_id').'/pause';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryCouponDetailPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryCouponDetailPlugin.php
new file mode 100644
index 0000000..c05a923
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryCouponDetailPlugin.php
@@ -0,0 +1,51 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_wechat_config($params);
+
+ if (!$payload->has('coupon_id') || !$payload->has('openid')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/users/'.
+ $payload->get('openid').
+ '/coupons/'.$payload->get('coupon_id').
+ '?appid='.$payload->get('appid', $config[$this->getConfigKey($params)] ?? '');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockDetailPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockDetailPlugin.php
new file mode 100644
index 0000000..d21d836
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockDetailPlugin.php
@@ -0,0 +1,50 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_wechat_config($params);
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/stocks/'.
+ $payload->get('stock_id').
+ '?stock_creator_mchid='.$payload->get('stock_creator_mchid', $config['mch_id'] ?? '');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockItemsPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockItemsPlugin.php
new file mode 100644
index 0000000..eefe524
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockItemsPlugin.php
@@ -0,0 +1,58 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_wechat_config($params);
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ if (!$payload->has('stock_creator_mchid')) {
+ $rocket->mergePayload(['stock_creator_mchid' => $config['mch_id']]);
+ }
+
+ $query = $rocket->getPayload()->all();
+
+ unset($query['stock_id']);
+
+ return 'v3/marketing/favor/stocks/'.
+ $payload->get('stock_id').
+ '/items?'.http_build_query($query);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockMerchantsPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockMerchantsPlugin.php
new file mode 100644
index 0000000..9f3278b
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockMerchantsPlugin.php
@@ -0,0 +1,58 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_wechat_config($params);
+
+ if (!$payload->has('stock_creator_mchid')) {
+ $rocket->mergePayload(['stock_creator_mchid' => $config['mch_id']]);
+ }
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $query = $rocket->getPayload()->all();
+
+ unset($query['stock_id']);
+
+ return 'v3/marketing/favor/stocks/'.
+ $payload->get('stock_id').
+ '/merchants?'.http_build_query($query);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockRefundFlowPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockRefundFlowPlugin.php
new file mode 100644
index 0000000..baff7ac
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockRefundFlowPlugin.php
@@ -0,0 +1,42 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/stocks/'.
+ $payload->get('stock_id').
+ '/refund-flow';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockUseFlowPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockUseFlowPlugin.php
new file mode 100644
index 0000000..a639158
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStockUseFlowPlugin.php
@@ -0,0 +1,42 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/stocks/'.
+ $payload->get('stock_id').
+ '/use-flow';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStocksPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStocksPlugin.php
new file mode 100644
index 0000000..700c9a4
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryStocksPlugin.php
@@ -0,0 +1,44 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $params = $rocket->getParams();
+ $config = get_wechat_config($params);
+
+ if (!$rocket->getPayload()->has('stock_creator_mchid')) {
+ $rocket->mergePayload(['stock_creator_mchid' => $config['mch_id']]);
+ }
+
+ return 'v3/marketing/favor/stocks?'.$rocket->getPayload()->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryUserCouponsPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryUserCouponsPlugin.php
new file mode 100644
index 0000000..80496c0
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/QueryUserCouponsPlugin.php
@@ -0,0 +1,62 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_wechat_config($params);
+
+ if (!$payload->has('openid')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ if (!$payload->has('appid')) {
+ $rocket->mergePayload(['appid' => $config[$this->getConfigKey($params)] ?? '']);
+ }
+
+ if (!$payload->has('creator_mchid')) {
+ $rocket->mergePayload(['creator_mchid' => $config['mch_id']]);
+ }
+
+ $query = $rocket->getPayload()->all();
+
+ unset($query['openid']);
+
+ return 'v3/marketing/favor/users/'.
+ $payload->get('openid').
+ '/coupons?'.http_build_query($query);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/RestartPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/RestartPlugin.php
new file mode 100644
index 0000000..602956a
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/RestartPlugin.php
@@ -0,0 +1,51 @@
+getPayload();
+ $params = $rocket->getParams();
+ $config = get_wechat_config($params);
+
+ if (!$payload->has('stock_creator_mchid')) {
+ $rocket->mergePayload(['stock_creator_mchid' => $config['mch_id']]);
+ }
+
+ $rocket->getPayload()->forget('stock_id');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/stocks/'.$payload->get('stock_id').'/restart';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SendPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SendPlugin.php
new file mode 100644
index 0000000..cf9017e
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SendPlugin.php
@@ -0,0 +1,54 @@
+getParams();
+ $config = get_wechat_config($params);
+
+ if (!$rocket->getPayload()->has('appid')) {
+ $rocket->mergePayload(['appid' => $config[$this->getConfigKey($params)] ?? '']);
+ }
+
+ if (!$rocket->getPayload()->has('stock_creator_mchid')) {
+ $rocket->mergePayload(['stock_creator_mchid' => $config['mch_id']]);
+ }
+
+ $rocket->getPayload()->forget('openid');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('openid')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/users/'.$payload->get('openid').'/coupons';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SetCallbackPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SetCallbackPlugin.php
new file mode 100644
index 0000000..e620c1d
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/SetCallbackPlugin.php
@@ -0,0 +1,36 @@
+getParams());
+
+ $rocket->mergePayload([
+ 'mchid' => $config['mch_id'] ?? '',
+ ]);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/marketing/favor/callbacks';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/StartPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/StartPlugin.php
new file mode 100644
index 0000000..f26ad4c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Marketing/Coupon/StartPlugin.php
@@ -0,0 +1,50 @@
+getParams();
+ $config = get_wechat_config($params);
+
+ if (!$rocket->getPayload()->has('stock_creator_mchid')) {
+ $rocket->mergePayload(['stock_creator_mchid' => $config['mch_id']]);
+ }
+
+ $rocket->getPayload()->forget('stock_id');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('stock_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/marketing/favor/stocks/'.$payload->get('stock_id').'/start';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Papay/ApplyPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Papay/ApplyPlugin.php
new file mode 100644
index 0000000..5443b00
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Papay/ApplyPlugin.php
@@ -0,0 +1,21 @@
+getParams());
+ $wechatId = $this->getWechatId($config, $rocket->getParams());
+
+ if (!$rocket->getPayload()->has('notify_url')) {
+ $wechatId['notify_url'] = $config['notify_url'] ?? '';
+ }
+
+ $rocket->mergePayload($wechatId);
+ $rocket->mergePayload([
+ 'sign' => get_wechat_sign_v2($rocket->getParams(), $rocket->getPayload()->all()),
+ ]);
+
+ $rocket->setDestination($rocket->getPayload());
+ $rocket->setDirection(NoHttpRequestDirection::class);
+
+ return $next($rocket);
+ }
+
+ protected function getWechatId(array $config, array $params): array
+ {
+ $configKey = $this->getConfigKey($params);
+
+ return [
+ 'appid' => $config[$configKey] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function getConfigKey(array $params): string
+ {
+ $key = ($params['_type'] ?? 'mp').'_app_id';
+
+ if ('app_app_id' === $key) {
+ $key = 'app_id';
+ }
+
+ return $key;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/ClosePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/ClosePlugin.php
new file mode 100644
index 0000000..6740c31
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/ClosePlugin.php
@@ -0,0 +1,12 @@
+ $this->getAppId($rocket),
+ 'partnerid' => get_wechat_config($rocket->getParams())['mch_id'] ?? null,
+ 'prepayid' => $prepayId,
+ 'package' => 'Sign=WXPay',
+ 'noncestr' => Str::random(32),
+ 'timestamp' => time().'',
+ ]);
+
+ $config->set('sign', $this->getSign($config, $rocket->getParams()));
+
+ return $config;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getSign(Collection $invokeConfig, array $params): string
+ {
+ $contents = $invokeConfig->get('appid', '')."\n".
+ $invokeConfig->get('timestamp', '')."\n".
+ $invokeConfig->get('noncestr', '')."\n".
+ $invokeConfig->get('prepayid', '')."\n";
+
+ return get_wechat_sign($params, $contents);
+ }
+
+ protected function getConfigKey(): string
+ {
+ return 'app_id';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/InvokePrepayV2Plugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/InvokePrepayV2Plugin.php
new file mode 100644
index 0000000..8ec4ba9
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/InvokePrepayV2Plugin.php
@@ -0,0 +1,49 @@
+getParams();
+
+ $config = new Config([
+ 'appId' => $this->getAppId($rocket),
+ 'partnerId' => get_wechat_config($params)['mch_id'] ?? null,
+ 'prepayId' => $prepayId,
+ 'package' => 'Sign=WXPay',
+ 'nonceStr' => Str::random(32),
+ 'timeStamp' => time().'',
+ ]);
+
+ $config->set('sign', get_wechat_sign_v2($params, $config->all()));
+
+ return $config;
+ }
+
+ protected function getConfigKey(): string
+ {
+ return 'app_id';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/PrepayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/PrepayPlugin.php
new file mode 100644
index 0000000..12df20e
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/App/PrepayPlugin.php
@@ -0,0 +1,28 @@
+getPayload();
+
+ if (!$payload->has('combine_out_trade_no') && !$payload->has('out_trade_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/combine-transactions/out-trade-no/'.
+ $payload->get('combine_out_trade_no', $payload->get('out_trade_no')).
+ '/close';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function doSomething(Rocket $rocket): void
+ {
+ $config = get_wechat_config($rocket->getParams());
+
+ $rocket->setPayload(new Collection([
+ 'combine_appid' => $config['combine_appid'] ?? '',
+ 'sub_orders' => $rocket->getParams()['sub_orders'] ?? [],
+ ]));
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/DownloadBillPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/DownloadBillPlugin.php
new file mode 100644
index 0000000..2d17451
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/DownloadBillPlugin.php
@@ -0,0 +1,12 @@
+getPayload();
+
+ if (!$payload->has('combine_out_trade_no') && !$payload->has('transaction_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/combine-transactions/out-trade-no/'.
+ $payload->get('combine_out_trade_no', $payload->get('transaction_id'));
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/QueryRefundPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/QueryRefundPlugin.php
new file mode 100644
index 0000000..16ba93a
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Combine/QueryRefundPlugin.php
@@ -0,0 +1,12 @@
+getPayload();
+
+ if (!$payload->has('out_trade_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/pay/transactions/out-trade-no/'.
+ $payload->get('out_trade_no').
+ '/close';
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('out_trade_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/pay/partner/transactions/out-trade-no/'.
+ $payload->get('out_trade_no').
+ '/close';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setDirection(OriginResponseDirection::class);
+
+ $config = get_wechat_config($rocket->getParams());
+
+ $body = [
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+
+ if (Pay::MODE_SERVICE == ($config['mode'] ?? null)) {
+ $body = [
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $rocket->getPayload()->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+
+ $rocket->setPayload(new Collection($body));
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/CombinePrepayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/CombinePrepayPlugin.php
new file mode 100644
index 0000000..24363da
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/CombinePrepayPlugin.php
@@ -0,0 +1,50 @@
+getParams());
+ $collection = $rocket->getPayload();
+
+ $payload = $this->getWechatId($config);
+
+ if (!$collection->has('notify_url')) {
+ $payload['notify_url'] = $config['notify_url'] ?? '';
+ }
+
+ if (!$collection->has('combine_out_trade_no')) {
+ $payload['combine_out_trade_no'] = $rocket->getParams()['out_trade_no'];
+ }
+
+ $rocket->mergePayload($payload);
+ }
+
+ protected function getWechatId(array $config): array
+ {
+ return [
+ 'combine_appid' => $config['combine_app_id'] ?? '',
+ 'combine_mchid' => $config['combine_mch_id'] ?? '',
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/DownloadBillPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/DownloadBillPlugin.php
new file mode 100644
index 0000000..a7651d7
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/DownloadBillPlugin.php
@@ -0,0 +1,40 @@
+getPayload();
+
+ if (!$payload->has('download_url')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return $payload->get('download_url');
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setDirection(OriginResponseDirection::class);
+
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetFlowBillPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetFlowBillPlugin.php
new file mode 100644
index 0000000..a170038
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetFlowBillPlugin.php
@@ -0,0 +1,26 @@
+getParams());
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetTradeBillPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetTradeBillPlugin.php
new file mode 100644
index 0000000..692e2cf
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/GetTradeBillPlugin.php
@@ -0,0 +1,26 @@
+getParams());
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayPlugin.php
new file mode 100644
index 0000000..0de41e9
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayPlugin.php
@@ -0,0 +1,110 @@
+ $rocket]);
+
+ $prepayId = $rocket->getDestination()->get('prepay_id');
+
+ if (is_null($prepayId)) {
+ Logger::error('[wechat][InvokePrepayPlugin] 预下单失败:响应缺少 prepay_id 参数,请自行检查参数是否符合微信要求', $rocket->getDestination()->all());
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, 'Prepay Response Error: Missing PrepayId', $rocket->getDestination()->all());
+ }
+
+ $config = $this->getInvokeConfig($rocket, $prepayId);
+
+ $rocket->setDestination($config);
+
+ Logger::info('[wechat][InvokePrepayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws \Exception
+ */
+ protected function getInvokeConfig(Rocket $rocket, string $prepayId): Config
+ {
+ $config = new Config([
+ 'appId' => $this->getAppId($rocket),
+ 'timeStamp' => time().'',
+ 'nonceStr' => Str::random(32),
+ 'package' => 'prepay_id='.$prepayId,
+ 'signType' => 'RSA',
+ ]);
+
+ $config->set('paySign', $this->getSign($config, $rocket->getParams()));
+
+ return $config;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getSign(Collection $invokeConfig, array $params): string
+ {
+ $contents = $invokeConfig->get('appId', '')."\n".
+ $invokeConfig->get('timeStamp', '')."\n".
+ $invokeConfig->get('nonceStr', '')."\n".
+ $invokeConfig->get('package', '')."\n";
+
+ return get_wechat_sign($params, $contents);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getAppId(Rocket $rocket): string
+ {
+ $config = get_wechat_config($rocket->getParams());
+ $payload = $rocket->getPayload();
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null) && $payload->has('sub_appid')) {
+ return $payload->get('sub_appid', '');
+ }
+
+ return $config[$this->getConfigKey()] ?? '';
+ }
+
+ protected function getConfigKey(): string
+ {
+ return 'mp_app_id';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayV2Plugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayV2Plugin.php
new file mode 100644
index 0000000..1c4313c
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/InvokePrepayV2Plugin.php
@@ -0,0 +1,40 @@
+ $this->getAppId($rocket),
+ 'timeStamp' => time().'',
+ 'nonceStr' => Str::random(32),
+ 'package' => 'prepay_id='.$prepayId,
+ 'signType' => 'MD5',
+ ]);
+
+ $config->set('paySign', get_wechat_sign_v2($rocket->getParams(), $config->toArray()));
+
+ return $config;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/PrepayPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/PrepayPlugin.php
new file mode 100644
index 0000000..5ffaa2a
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/PrepayPlugin.php
@@ -0,0 +1,70 @@
+getParams());
+
+ $wechatId = $this->getWechatId($config, $rocket);
+
+ if (!$rocket->getPayload()->has('notify_url')) {
+ $wechatId['notify_url'] = $config['notify_url'] ?? null;
+ }
+
+ $rocket->mergePayload($wechatId);
+ }
+
+ protected function getWechatId(array $config, Rocket $rocket): array
+ {
+ $payload = $rocket->getPayload();
+ $configKey = $this->getConfigKey($rocket->getParams());
+
+ $result = [
+ 'appid' => $config[$configKey] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $result = [
+ 'sp_appid' => $config[$configKey] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? null),
+ ];
+
+ $subAppId = $payload->get('sub_appid', $config['sub_'.$configKey] ?? null);
+
+ if (!empty($subAppId)) {
+ $result['sub_appid'] = $subAppId;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryPlugin.php
new file mode 100644
index 0000000..68071c7
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryPlugin.php
@@ -0,0 +1,79 @@
+getParams());
+ $payload = $rocket->getPayload();
+
+ if ($payload->has('transaction_id')) {
+ return 'v3/pay/transactions/id/'.
+ $payload->get('transaction_id').
+ '?mchid='.($config['mch_id'] ?? '');
+ }
+
+ if ($payload->has('out_trade_no')) {
+ return 'v3/pay/transactions/out-trade-no/'.
+ $payload->get('out_trade_no').
+ '?mchid='.($config['mch_id'] ?? '');
+ }
+
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidParamsException
+ */
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ $config = get_wechat_config($rocket->getParams());
+ $payload = $rocket->getPayload();
+
+ if ($payload->has('transaction_id')) {
+ return 'v3/pay/partner/transactions/id/'.
+ $payload->get('transaction_id').
+ '?sp_mchid='.($config['mch_id'] ?? '').
+ '&sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? null);
+ }
+
+ if ($payload->has('out_trade_no')) {
+ return 'v3/pay/partner/transactions/out-trade-no/'.
+ $payload->get('out_trade_no').
+ '?sp_mchid='.($config['mch_id'] ?? '').
+ '&sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? null);
+ }
+
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryRefundPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryRefundPlugin.php
new file mode 100644
index 0000000..e30df72
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/QueryRefundPlugin.php
@@ -0,0 +1,53 @@
+getPayload();
+
+ if (!$payload->has('out_refund_no')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/refund/domestic/refunds/'.$payload->get('out_refund_no');
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getPartnerUri(Rocket $rocket): string
+ {
+ $config = get_wechat_config($rocket->getParams());
+ $url = parent::getPartnerUri($rocket);
+
+ return $url.'?sub_mchid='.$rocket->getPayload()->get('sub_mchid', $config['sub_mch_id'] ?? '');
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/RefundPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/RefundPlugin.php
new file mode 100644
index 0000000..3be79d0
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/Common/RefundPlugin.php
@@ -0,0 +1,41 @@
+getParams());
+ $payload = $rocket->getPayload();
+
+ if (empty($payload->get('notify_url')) && !empty($config['notify_url'])) {
+ $merge['notify_url'] = $config['notify_url'];
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
+ $merge['sub_mchid'] = $payload->get('sub_mchid', $config['sub_mch_id'] ?? null);
+ }
+
+ $rocket->mergePayload($merge ?? []);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/ClosePlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/ClosePlugin.php
new file mode 100644
index 0000000..9d805cf
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Pay/H5/ClosePlugin.php
@@ -0,0 +1,12 @@
+ $rocket]);
+
+ $rocket->mergePayload($this->getPayload($rocket->getParams()));
+
+ Logger::info('[wechat][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function getPayload(array $params): array
+ {
+ return array_filter($params, fn ($v, $k) => !Str::startsWith(strval($k), '_'), ARRAY_FILTER_USE_BOTH);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/RadarSignPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/RadarSignPlugin.php
new file mode 100644
index 0000000..87faad6
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/RadarSignPlugin.php
@@ -0,0 +1,170 @@
+jsonPacker = $jsonPacker ?? new JsonPacker();
+ $this->xmlPacker = $xmlPacker ?? new XmlPacker();
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function assembly(Rocket $rocket, Closure $next): Rocket
+ {
+ Logger::debug('[wechat][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
+
+ switch ($rocket->getParams()['_version'] ?? 'default') {
+ case 'v2':
+ $radar = $this->v2($rocket);
+
+ break;
+
+ default:
+ $radar = $this->v3($rocket);
+
+ break;
+ }
+
+ $rocket->setRadar($radar);
+
+ Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidConfigException
+ * @throws \Exception
+ */
+ protected function v2(Rocket $rocket): RequestInterface
+ {
+ $rocket->mergePayload(['nonce_str' => Str::random(32)]);
+ $rocket->mergePayload([
+ 'sign' => get_wechat_sign_v2($rocket->getParams(), $rocket->getPayload()->all()),
+ ]);
+
+ return $rocket->getRadar()->withBody(
+ Utils::streamFor($this->xmlPacker->pack($rocket->getPayload()->all()))
+ );
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ * @throws \Exception
+ */
+ protected function v3(Rocket $rocket): RequestInterface
+ {
+ $timestamp = time();
+ $random = Str::random(32);
+ $body = $this->v3PayloadToString($rocket->getPayload());
+ $contents = $this->v3GetContents($rocket, $timestamp, $random);
+ $authorization = $this->v3GetWechatAuthorization($rocket->getParams(), $timestamp, $random, $contents);
+ $radar = $rocket->getRadar()->withHeader('Authorization', $authorization);
+
+ if (!empty($rocket->getParams()['_serial_no'])) {
+ $radar = $radar->withHeader('Wechatpay-Serial', $rocket->getParams()['_serial_no']);
+ }
+
+ if (!empty($body)) {
+ $radar = $radar->withBody(Utils::streamFor($body));
+ }
+
+ return $radar;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function v3GetWechatAuthorization(array $params, int $timestamp, string $random, string $contents): string
+ {
+ $config = get_wechat_config($params);
+ $mchPublicCertPath = $config['mch_public_cert_path'] ?? null;
+
+ if (empty($mchPublicCertPath)) {
+ throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_public_cert_path]');
+ }
+
+ $ssl = openssl_x509_parse(get_public_cert($mchPublicCertPath));
+
+ if (empty($ssl['serialNumberHex'])) {
+ throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Parse [mch_public_cert_path] Serial Number Error');
+ }
+
+ $auth = sprintf(
+ 'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
+ $config['mch_id'] ?? '',
+ $random,
+ $timestamp,
+ $ssl['serialNumberHex'],
+ get_wechat_sign($params, $contents),
+ );
+
+ return 'WECHATPAY2-SHA256-RSA2048 '.$auth;
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function v3GetContents(Rocket $rocket, int $timestamp, string $random): string
+ {
+ $request = $rocket->getRadar();
+
+ if (is_null($request)) {
+ throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
+ }
+
+ $uri = $request->getUri();
+
+ return $request->getMethod()."\n".
+ $uri->getPath().(empty($uri->getQuery()) ? '' : '?'.$uri->getQuery())."\n".
+ $timestamp."\n".
+ $random."\n".
+ $this->v3PayloadToString($rocket->getPayload())."\n";
+ }
+
+ protected function v3PayloadToString(?Collection $payload): string
+ {
+ return (is_null($payload) || 0 === $payload->count()) ? '' : $this->jsonPacker->pack($payload->all());
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/CompleteComplaintPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/CompleteComplaintPlugin.php
new file mode 100644
index 0000000..ca15541
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/CompleteComplaintPlugin.php
@@ -0,0 +1,54 @@
+setDirection(OriginResponseDirection::class);
+
+ $payload = $rocket->getPayload();
+ $config = get_wechat_config($rocket->getParams());
+
+ $rocket->setPayload(new Collection([
+ 'complainted_mchid' => $payload->get('complainted_mchid', $config['mch_id'] ?? ''),
+ ]));
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('complaint_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/merchant-service/complaints-v2/'.
+ $payload->get('complaint_id').
+ '/complete';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DeleteCallbackPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DeleteCallbackPlugin.php
new file mode 100644
index 0000000..326854a
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DeleteCallbackPlugin.php
@@ -0,0 +1,32 @@
+setDirection(OriginResponseDirection::class);
+
+ $rocket->setPayload(null);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/merchant-service/complaint-notifications';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DownloadMediaPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DownloadMediaPlugin.php
new file mode 100644
index 0000000..b5e8ce3
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/DownloadMediaPlugin.php
@@ -0,0 +1,43 @@
+getPayload();
+
+ if (!$payload->has('media_url')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return $payload->get('media_url');
+ }
+
+ protected function getMethod(): string
+ {
+ return 'GET';
+ }
+
+ protected function doSomething(Rocket $rocket): void
+ {
+ $rocket->setDirection(OriginResponseDirection::class);
+
+ $rocket->setPayload(null);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryCallbackPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryCallbackPlugin.php
new file mode 100644
index 0000000..30857d6
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryCallbackPlugin.php
@@ -0,0 +1,29 @@
+setPayload(null);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/merchant-service/complaint-notifications';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintDetailPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintDetailPlugin.php
new file mode 100644
index 0000000..4c4fc49
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintDetailPlugin.php
@@ -0,0 +1,40 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('complaint_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/merchant-service/complaints-v2/'.$payload->get('complaint_id');
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintNegotiationPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintNegotiationPlugin.php
new file mode 100644
index 0000000..7aef299
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintNegotiationPlugin.php
@@ -0,0 +1,45 @@
+setPayload(null);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+ $complaintId = $payload->get('complaint_id');
+
+ if (is_null($complaintId)) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ $payload->forget('complaint_id');
+
+ return 'v3/merchant-service/complaints-v2/'.
+ $complaintId.
+ '/negotiation-historys?'.$payload->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintsPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintsPlugin.php
new file mode 100644
index 0000000..aadc6a1
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/QueryComplaintsPlugin.php
@@ -0,0 +1,29 @@
+setPayload(null);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/merchant-service/complaints-v2?'.$rocket->getPayload()->query();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/ResponseComplaintPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/ResponseComplaintPlugin.php
new file mode 100644
index 0000000..b941e2b
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/ResponseComplaintPlugin.php
@@ -0,0 +1,57 @@
+setDirection(OriginResponseDirection::class);
+
+ $config = get_wechat_config($rocket->getParams());
+ $payload = $rocket->getPayload();
+
+ $payload->forget('complaint_id');
+
+ if (!$payload->has('complainted_mchid')) {
+ $rocket->mergePayload([
+ 'complainted_mchid' => $config['mch_id'] ?? '',
+ ]);
+ }
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('complaint_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/merchant-service/complaints-v2/'.
+ $payload->get('complaint_id').
+ '/response';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/SetCallbackPlugin.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/SetCallbackPlugin.php
new file mode 100644
index 0000000..6bd83b5
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Risk/Complaints/SetCallbackPlugin.php
@@ -0,0 +1,23 @@
+getPayload()->forget('complaint_id');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getUri(Rocket $rocket): string
+ {
+ $payload = $rocket->getPayload();
+
+ if (!$payload->has('complaint_id')) {
+ throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
+ }
+
+ return 'v3/merchant-service/complaints-v2/'.
+ $payload->get('complaint_id').
+ '/update-refund-progress';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/AppShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/AppShortcut.php
new file mode 100644
index 0000000..c0d50e5
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/AppShortcut.php
@@ -0,0 +1,20 @@
+combinePlugins();
+ }
+
+ $typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
+
+ if (method_exists($this, $typeMethod)) {
+ return $this->{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Query action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ ClosePlugin::class,
+ ];
+ }
+
+ protected function combinePlugins(): array
+ {
+ return [
+ \Yansongda\Pay\Plugin\Wechat\Pay\Combine\ClosePlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/MiniShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/MiniShortcut.php
new file mode 100644
index 0000000..3e2f4ce
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/MiniShortcut.php
@@ -0,0 +1,20 @@
+{$typeMethod}($params);
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Papay action [{$typeMethod}] not supported");
+ }
+
+ /**
+ * 返回只签约(委托代扣)参数.
+ *
+ * @see https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_3.shtml
+ */
+ public function ContractPlugins(): array
+ {
+ return [
+ PreparePlugin::class,
+ OnlyContractPlugin::class,
+ ];
+ }
+
+ /**
+ * 申请代扣.
+ *
+ * @see https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_8.shtml
+ */
+ public function applyPlugins(): array
+ {
+ return [
+ PreparePlugin::class,
+ ApplyPlugin::class,
+ RadarSignPlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ /**
+ * 支付中签约.
+ *
+ * @see https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_5.shtml
+ */
+ protected function defaultPlugins(array $params): array
+ {
+ return [
+ PreparePlugin::class,
+ ContractOrderPlugin::class,
+ RadarSignPlugin::class,
+ $this->getInvoke($params),
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function getInvoke(array $params): string
+ {
+ switch ($params['_type'] ?? 'default') {
+ case 'app':
+ return \Yansongda\Pay\Plugin\Wechat\Pay\App\InvokePrepayV2Plugin::class;
+
+ case 'mini':
+ return \Yansongda\Pay\Plugin\Wechat\Pay\Mini\InvokePrepayV2Plugin::class;
+
+ default:
+ return InvokePrepayV2Plugin::class;
+ }
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/PosShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/PosShortcut.php
new file mode 100644
index 0000000..b384de4
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/PosShortcut.php
@@ -0,0 +1,18 @@
+combinePlugins();
+ }
+
+ $typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
+
+ if (method_exists($this, $typeMethod)) {
+ return $this->{$typeMethod}();
+ }
+
+ throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Query action [{$typeMethod}] not supported");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ QueryPlugin::class,
+ ];
+ }
+
+ protected function refundPlugins(): array
+ {
+ return [
+ QueryRefundPlugin::class,
+ ];
+ }
+
+ protected function combinePlugins(): array
+ {
+ return [
+ \Yansongda\Pay\Plugin\Wechat\Pay\Combine\QueryPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/RefundShortcut.php b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/RefundShortcut.php
new file mode 100644
index 0000000..5eacb01
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Plugin/Wechat/Shortcut/RefundShortcut.php
@@ -0,0 +1,18 @@
+setPayload(null);
+ }
+
+ protected function getUri(Rocket $rocket): string
+ {
+ return 'v3/certificates';
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Provider/AbstractProvider.php b/addons/epay/library/v3/Yansongda/Pay/Provider/AbstractProvider.php
new file mode 100644
index 0000000..58fc4a6
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Provider/AbstractProvider.php
@@ -0,0 +1,159 @@
+getPlugins($params);
+
+ if (empty($params['_no_common_plugins'])) {
+ $plugins = $this->mergeCommonPlugins($plugins);
+ }
+
+ return $this->pay($plugins, $params);
+ }
+
+ /**
+ * @return null|array|Collection|MessageInterface
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ */
+ public function pay(array $plugins, array $params)
+ {
+ Logger::info('[AbstractProvider] 即将进行 pay 操作', func_get_args());
+
+ Event::dispatch(new Event\PayStarted($plugins, $params, null));
+
+ $this->verifyPlugin($plugins);
+
+ /* @var Pipeline $pipeline */
+ $pipeline = Pay::make(Pipeline::class);
+
+ /* @var Rocket $rocket */
+ $rocket = $pipeline
+ ->send((new Rocket())->setParams($params)->setPayload(new Collection()))
+ ->through($plugins)
+ ->via('assembly')
+ ->then(fn ($rocket) => $this->ignite($rocket))
+ ;
+
+ Event::dispatch(new Event\PayFinish($rocket));
+
+ $destination = $rocket->getDestination();
+
+ if (ArrayDirection::class === $rocket->getDirection() && $destination instanceof Collection) {
+ return $destination->toArray();
+ }
+
+ return $destination;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidResponseException
+ * @throws InvalidConfigException
+ */
+ public function ignite(Rocket $rocket): Rocket
+ {
+ if (!should_do_http_request($rocket->getDirection())) {
+ return $rocket;
+ }
+
+ /* @var HttpClientInterface $http */
+ $http = Pay::get(HttpClientInterface::class);
+
+ if (!$http instanceof ClientInterface) {
+ throw new InvalidConfigException(Exception::HTTP_CLIENT_CONFIG_ERROR);
+ }
+
+ Logger::info('[AbstractProvider] 准备请求支付服务商 API', $rocket->toArray());
+
+ Event::dispatch(new Event\ApiRequesting($rocket));
+
+ try {
+ $response = $http->sendRequest($rocket->getRadar());
+
+ $contents = (string) $response->getBody();
+
+ $rocket->setDestination($response->withBody(Utils::streamFor($contents)))
+ ->setDestinationOrigin($response->withBody(Utils::streamFor($contents)))
+ ;
+ } catch (Throwable $e) {
+ Logger::error('[AbstractProvider] 请求支付服务商 API 出错', ['message' => $e->getMessage(), 'rocket' => $rocket->toArray(), 'trace' => $e->getTrace()]);
+
+ throw new InvalidResponseException(Exception::REQUEST_RESPONSE_ERROR, $e->getMessage(), [], $e);
+ }
+
+ Logger::info('[AbstractProvider] 请求支付服务商 API 成功', ['response' => $response, 'rocket' => $rocket->toArray()]);
+
+ Event::dispatch(new Event\ApiRequested($rocket));
+
+ return $rocket;
+ }
+
+ abstract public function mergeCommonPlugins(array $plugins): array;
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function verifyPlugin(array $plugins): void
+ {
+ foreach ($plugins as $plugin) {
+ if (is_callable($plugin)) {
+ continue;
+ }
+
+ if ((is_object($plugin)
+ || (is_string($plugin) && class_exists($plugin)))
+ && in_array(PluginInterface::class, class_implements($plugin))) {
+ continue;
+ }
+
+ throw new InvalidParamsException(Exception::PLUGIN_ERROR, "[{$plugin}] is not incompatible");
+ }
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Provider/Alipay.php b/addons/epay/library/v3/Yansongda/Pay/Provider/Alipay.php
new file mode 100644
index 0000000..4520ae7
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Provider/Alipay.php
@@ -0,0 +1,178 @@
+ 'https://openapi.alipay.com/gateway.do?charset=utf-8',
+ Pay::MODE_SANDBOX => 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8',
+ Pay::MODE_SERVICE => 'https://openapi.alipay.com/gateway.do?charset=utf-8',
+ ];
+
+ /**
+ * @return null|array|Collection|MessageInterface
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function __call(string $shortcut, array $params)
+ {
+ $plugin = '\\Yansongda\\Pay\\Plugin\\Alipay\\Shortcut\\'.
+ Str::studly($shortcut).'Shortcut';
+
+ return $this->call($plugin, ...$params);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function find($order)
+ {
+ $order = is_array($order) ? $order : ['out_trade_no' => $order];
+
+ Event::dispatch(new Event\MethodCalled('alipay', __METHOD__, $order, null));
+
+ return $this->__call('query', [$order]);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function cancel($order)
+ {
+ $order = is_array($order) ? $order : ['out_trade_no' => $order];
+
+ Event::dispatch(new Event\MethodCalled('alipay', __METHOD__, $order, null));
+
+ return $this->__call('cancel', [$order]);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function close($order)
+ {
+ $order = is_array($order) ? $order : ['out_trade_no' => $order];
+
+ Event::dispatch(new Event\MethodCalled('alipay', __METHOD__, $order, null));
+
+ return $this->__call('close', [$order]);
+ }
+
+ /**
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function refund(array $order)
+ {
+ Event::dispatch(new Event\MethodCalled('alipay', __METHOD__, $order, null));
+
+ return $this->__call('refund', [$order]);
+ }
+
+ /**
+ * @param null|array|ServerRequestInterface $contents
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ */
+ public function callback($contents = null, ?array $params = null): Collection
+ {
+ $request = $this->getCallbackParams($contents);
+
+ Event::dispatch(new Event\CallbackReceived('alipay', $request->all(), $params, null));
+
+ return $this->pay(
+ [CallbackPlugin::class],
+ $request->merge($params)->all()
+ );
+ }
+
+ public function success(): ResponseInterface
+ {
+ return new Response(200, [], 'success');
+ }
+
+ public function mergeCommonPlugins(array $plugins): array
+ {
+ return array_merge(
+ [PreparePlugin::class],
+ $plugins,
+ [RadarSignPlugin::class],
+ [LaunchPlugin::class, ParserPlugin::class],
+ );
+ }
+
+ /**
+ * @param null|array|ServerRequestInterface $contents
+ */
+ protected function getCallbackParams($contents = null): Collection
+ {
+ if (is_array($contents)) {
+ return Collection::wrap($contents);
+ }
+
+ if ($contents instanceof ServerRequestInterface) {
+ return Collection::wrap('GET' === $contents->getMethod() ? $contents->getQueryParams() :
+ $contents->getParsedBody());
+ }
+
+ $request = ServerRequest::fromGlobals();
+
+ return Collection::wrap(
+ array_merge($request->getQueryParams(), $request->getParsedBody())
+ );
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Provider/Unipay.php b/addons/epay/library/v3/Yansongda/Pay/Provider/Unipay.php
new file mode 100644
index 0000000..8dedd91
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Provider/Unipay.php
@@ -0,0 +1,168 @@
+ 'https://gateway.95516.com/',
+ Pay::MODE_SANDBOX => 'https://gateway.test.95516.com/',
+ Pay::MODE_SERVICE => 'https://gateway.95516.com',
+ ];
+
+ /**
+ * @return null|array|Collection|MessageInterface
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function __call(string $shortcut, array $params)
+ {
+ $plugin = '\\Yansongda\\Pay\\Plugin\\Unipay\\Shortcut\\'.
+ Str::studly($shortcut).'Shortcut';
+
+ return $this->call($plugin, ...$params);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function find($order)
+ {
+ if (!is_array($order)) {
+ throw new InvalidParamsException(Exception::UNIPAY_FIND_STRING_NOT_SUPPORTED);
+ }
+
+ Event::dispatch(new Event\MethodCalled('unipay', __METHOD__, $order, null));
+
+ return $this->__call('query', [$order]);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function cancel($order)
+ {
+ if (!is_array($order)) {
+ throw new InvalidParamsException(Exception::UNIPAY_CANCEL_STRING_NOT_SUPPORTED);
+ }
+
+ Event::dispatch(new Event\MethodCalled('unipay', __METHOD__, $order, null));
+
+ return $this->__call('cancel', [$order]);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @return array|Collection
+ *
+ * @throws InvalidParamsException
+ */
+ public function close($order)
+ {
+ throw new InvalidParamsException(Exception::METHOD_NOT_SUPPORTED, 'Unipay does not support close api');
+ }
+
+ /**
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function refund(array $order)
+ {
+ Event::dispatch(new Event\MethodCalled('unipay', __METHOD__, $order, null));
+
+ return $this->__call('refund', [$order]);
+ }
+
+ /**
+ * @param null|array|ServerRequestInterface $contents
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ */
+ public function callback($contents = null, ?array $params = null): Collection
+ {
+ $request = $this->getCallbackParams($contents);
+
+ Event::dispatch(new Event\CallbackReceived('unipay', $request->all(), $params, null));
+
+ return $this->pay(
+ [CallbackPlugin::class],
+ $request->merge($params)->all()
+ );
+ }
+
+ public function success(): ResponseInterface
+ {
+ return new Response(200, [], 'success');
+ }
+
+ public function mergeCommonPlugins(array $plugins): array
+ {
+ return array_merge(
+ [PreparePlugin::class],
+ $plugins,
+ [RadarSignPlugin::class],
+ [LaunchPlugin::class, ParserPlugin::class],
+ );
+ }
+
+ /**
+ * @param null|array|ServerRequestInterface $contents
+ */
+ protected function getCallbackParams($contents = null): Collection
+ {
+ if (is_array($contents)) {
+ return Collection::wrap($contents);
+ }
+
+ if ($contents instanceof ServerRequestInterface) {
+ return Collection::wrap($contents->getParsedBody());
+ }
+
+ $request = ServerRequest::fromGlobals();
+
+ return Collection::wrap($request->getParsedBody());
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Provider/Wechat.php b/addons/epay/library/v3/Yansongda/Pay/Provider/Wechat.php
new file mode 100644
index 0000000..ece1ed9
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Provider/Wechat.php
@@ -0,0 +1,178 @@
+ 'https://api.mch.weixin.qq.com/',
+ Pay::MODE_SANDBOX => 'https://api.mch.weixin.qq.com/sandboxnew/',
+ Pay::MODE_SERVICE => 'https://api.mch.weixin.qq.com/',
+ ];
+
+ /**
+ * @return null|array|Collection|MessageInterface
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function __call(string $shortcut, array $params)
+ {
+ $plugin = '\\Yansongda\\Pay\\Plugin\\Wechat\\Shortcut\\'.
+ Str::studly($shortcut).'Shortcut';
+
+ return $this->call($plugin, ...$params);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function find($order)
+ {
+ $order = is_array($order) ? $order : ['transaction_id' => $order];
+
+ Event::dispatch(new Event\MethodCalled('wechat', __METHOD__, $order, null));
+
+ return $this->__call('query', [$order]);
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @throws InvalidParamsException
+ */
+ public function cancel($order): void
+ {
+ throw new InvalidParamsException(Exception::METHOD_NOT_SUPPORTED, 'Wechat does not support cancel api');
+ }
+
+ /**
+ * @param array|string $order
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function close($order): void
+ {
+ $order = is_array($order) ? $order : ['out_trade_no' => $order];
+
+ Event::dispatch(new Event\MethodCalled('wechat', __METHOD__, $order, null));
+
+ $this->__call('close', [$order]);
+ }
+
+ /**
+ * @return array|Collection
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function refund(array $order)
+ {
+ Event::dispatch(new Event\MethodCalled('wechat', __METHOD__, $order, null));
+
+ return $this->__call('refund', [$order]);
+ }
+
+ /**
+ * @param null|array|ServerRequestInterface $contents
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ */
+ public function callback($contents = null, ?array $params = null): Collection
+ {
+ $request = $this->getCallbackParams($contents);
+
+ Event::dispatch(new Event\CallbackReceived('wechat', clone $request, $params, null));
+
+ return $this->pay(
+ [CallbackPlugin::class],
+ ['request' => $request, 'params' => $params]
+ );
+ }
+
+ public function success(): ResponseInterface
+ {
+ return new Response(
+ 200,
+ ['Content-Type' => 'application/json'],
+ json_encode(['code' => 'SUCCESS', 'message' => '成功']),
+ );
+ }
+
+ public function mergeCommonPlugins(array $plugins): array
+ {
+ return array_merge(
+ [PreparePlugin::class],
+ $plugins,
+ [RadarSignPlugin::class],
+ [LaunchPlugin::class, ParserPlugin::class],
+ );
+ }
+
+ /**
+ * @param null|array|ServerRequestInterface $contents
+ */
+ protected function getCallbackParams($contents = null): ServerRequestInterface
+ {
+ if (is_array($contents) && isset($contents['body'], $contents['headers'])) {
+ return new ServerRequest('POST', 'http://localhost', $contents['headers'], $contents['body']);
+ }
+
+ if (is_array($contents)) {
+ return new ServerRequest('POST', 'http://localhost', [], json_encode($contents));
+ }
+
+ if ($contents instanceof ServerRequestInterface) {
+ return $contents;
+ }
+
+ return ServerRequest::fromGlobals();
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Request.php b/addons/epay/library/v3/Yansongda/Pay/Request.php
new file mode 100644
index 0000000..6628591
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Request.php
@@ -0,0 +1,27 @@
+ $this->getUri()->__toString(),
+ 'method' => $this->getMethod(),
+ 'headers' => $this->getHeaders(),
+ 'body' => (string) $this->getBody(),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Rocket.php b/addons/epay/library/v3/Yansongda/Pay/Rocket.php
new file mode 100644
index 0000000..ae99249
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Rocket.php
@@ -0,0 +1,148 @@
+radar;
+ }
+
+ public function setRadar(?RequestInterface $radar): Rocket
+ {
+ $this->radar = $radar;
+
+ return $this;
+ }
+
+ public function getParams(): array
+ {
+ return $this->params;
+ }
+
+ public function setParams(array $params): Rocket
+ {
+ $this->params = $params;
+
+ return $this;
+ }
+
+ public function mergeParams(array $params): Rocket
+ {
+ $this->params = array_merge($this->params, $params);
+
+ return $this;
+ }
+
+ public function getPayload(): ?Collection
+ {
+ return $this->payload;
+ }
+
+ public function setPayload(?Collection $payload): Rocket
+ {
+ $this->payload = $payload;
+
+ return $this;
+ }
+
+ public function mergePayload(array $payload): Rocket
+ {
+ if (empty($this->payload)) {
+ $this->payload = new Collection();
+ }
+
+ $this->payload = $this->payload->merge($payload);
+
+ return $this;
+ }
+
+ public function getPacker(): string
+ {
+ return $this->packer;
+ }
+
+ public function setPacker(string $packer): Rocket
+ {
+ $this->packer = $packer;
+
+ return $this;
+ }
+
+ public function getDirection(): string
+ {
+ return $this->direction;
+ }
+
+ public function setDirection(string $direction): Rocket
+ {
+ $this->direction = $direction;
+
+ return $this;
+ }
+
+ /**
+ * @return null|array|Collection|MessageInterface
+ */
+ public function getDestination()
+ {
+ return $this->destination;
+ }
+
+ /**
+ * @param null|array|Collection|MessageInterface $destination
+ */
+ public function setDestination($destination): Rocket
+ {
+ $this->destination = $destination;
+
+ return $this;
+ }
+
+ public function getDestinationOrigin(): ?MessageInterface
+ {
+ return $this->destinationOrigin;
+ }
+
+ public function setDestinationOrigin(?MessageInterface $destinationOrigin): Rocket
+ {
+ $this->destinationOrigin = $destinationOrigin;
+
+ return $this;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Service/AlipayServiceProvider.php b/addons/epay/library/v3/Yansongda/Pay/Service/AlipayServiceProvider.php
new file mode 100644
index 0000000..9e0492a
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Service/AlipayServiceProvider.php
@@ -0,0 +1,26 @@
+ [
+ 'enable' => false,
+ 'file' => null,
+ 'identify' => 'yansongda.pay',
+ 'level' => 'debug',
+ 'type' => 'daily',
+ 'max_files' => 30,
+ ],
+ 'http' => [
+ 'timeout' => 5.0,
+ 'connect_timeout' => 3.0,
+ 'headers' => [
+ 'User-Agent' => 'yansongda/pay-v3',
+ ],
+ ],
+ 'mode' => Pay::MODE_NORMAL,
+ ];
+
+ /**
+ * @param mixed $data
+ *
+ * @throws ContainerException
+ */
+ public function register($data = null): void
+ {
+ $config = new Config(array_replace_recursive($this->config, $data ?? []));
+
+ Pay::set(ConfigInterface::class, $config);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Service/ContainerServiceProvider.php b/addons/epay/library/v3/Yansongda/Pay/Service/ContainerServiceProvider.php
new file mode 100644
index 0000000..649ea2d
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Service/ContainerServiceProvider.php
@@ -0,0 +1,107 @@
+ LaravelContainer::class,
+ 'hyperf' => HyperfContainer::class,
+ ];
+
+ /**
+ * @param mixed $data
+ *
+ * @throws ContainerException
+ */
+ public function register($data = null): void
+ {
+ if ($data instanceof ContainerInterface || $data instanceof Closure) {
+ Pay::setContainer($data);
+
+ return;
+ }
+
+ if (Pay::hasContainer()) {
+ return;
+ }
+
+ foreach ($this->detectApplication as $framework => $application) {
+ $method = $framework.'Application';
+
+ if (class_exists($application) && method_exists($this, $method) && $this->{$method}()) {
+ return;
+ }
+ }
+
+ $this->defaultApplication();
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ContainerNotFoundException
+ */
+ protected function laravelApplication(): bool
+ {
+ Pay::setContainer(static fn () => LaravelContainer::getInstance());
+
+ Pay::set(\Yansongda\Pay\Contract\ContainerInterface::class, LaravelContainer::getInstance());
+
+ if (!Pay::has(ContainerInterface::class)) {
+ Pay::set(ContainerInterface::class, LaravelContainer::getInstance());
+ }
+
+ return true;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ContainerNotFoundException
+ */
+ protected function hyperfApplication(): bool
+ {
+ if (!HyperfContainer::hasContainer()) {
+ return false;
+ }
+
+ Pay::setContainer(static fn () => HyperfContainer::getContainer());
+
+ Pay::set(\Yansongda\Pay\Contract\ContainerInterface::class, HyperfContainer::getContainer());
+
+ if (!Pay::has(ContainerInterface::class)) {
+ Pay::set(ContainerInterface::class, HyperfContainer::getContainer());
+ }
+
+ return true;
+ }
+
+ /**
+ * @throws ContainerNotFoundException
+ */
+ protected function defaultApplication(): void
+ {
+ if (!class_exists(DefaultContainer::class)) {
+ throw new ContainerNotFoundException('Init failed! Maybe you should install `hyperf/pimple` first', Exception::CONTAINER_NOT_FOUND);
+ }
+
+ $container = (new DefaultContainer())();
+
+ Pay::setContainer($container);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Service/EventServiceProvider.php b/addons/epay/library/v3/Yansongda/Pay/Service/EventServiceProvider.php
new file mode 100644
index 0000000..ffa1c24
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Service/EventServiceProvider.php
@@ -0,0 +1,26 @@
+get('http', []));
+
+ Pay::set(HttpClientInterface::class, $service);
+ }
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Service/LoggerServiceProvider.php b/addons/epay/library/v3/Yansongda/Pay/Service/LoggerServiceProvider.php
new file mode 100644
index 0000000..d38632b
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Service/LoggerServiceProvider.php
@@ -0,0 +1,37 @@
+get('logger.enable', false)) {
+ $logger = new Logger(array_merge(
+ ['identify' => 'yansongda.pay'],
+ $config->get('logger', [])
+ ));
+
+ Pay::set(LoggerInterface::class, $logger);
+ }
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Service/UnipayServiceProvider.php b/addons/epay/library/v3/Yansongda/Pay/Service/UnipayServiceProvider.php
new file mode 100644
index 0000000..6ee2691
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Service/UnipayServiceProvider.php
@@ -0,0 +1,26 @@
+getCerts($config);
+ $ssl = openssl_x509_parse($certs['cert'] ?? '');
+
+ if (false === $ssl) {
+ throw new InvalidConfigException(Exception::UNIPAY_CONFIG_ERROR, 'Parse `mch_cert_path` Error');
+ }
+
+ $certs['cert_id'] = $ssl['serialNumber'] ?? '';
+
+ Pay::get(ConfigInterface::class)->set('unipay.'.$tenant.'.certs', $certs);
+
+ return $certs['cert_id'];
+ }
+
+ /**
+ * @return array ['cert' => 公钥, 'pkey' => 私钥, 'extracerts' => array]
+ *
+ * @throws InvalidConfigException
+ */
+ protected function getCerts(array $config): array
+ {
+ $path = $config['mch_cert_path'] ?? null;
+ $password = $config['mch_cert_password'] ?? null;
+
+ if (is_null($path) || is_null($password)) {
+ throw new InvalidConfigException(Exception::UNIPAY_CONFIG_ERROR, 'Missing Unipay Config -- [mch_cert_path] or [mch_cert_password]');
+ }
+
+ if (false === openssl_pkcs12_read(file_get_contents($path), $certs, $password)) {
+ throw new InvalidConfigException(Exception::UNIPAY_CONFIG_ERROR, 'Read `mch_cert_path` Error');
+ }
+
+ return $certs;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Pay/Traits/HasWechatEncryption.php b/addons/epay/library/v3/Yansongda/Pay/Traits/HasWechatEncryption.php
new file mode 100644
index 0000000..c9adb60
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Pay/Traits/HasWechatEncryption.php
@@ -0,0 +1,61 @@
+getParams();
+ $config = get_alipay_config($params);
+ $serviceProviderId = $config['service_provider_id'] ?? null;
+
+ if (Pay::MODE_SERVICE !== ($config['mode'] ?? Pay::MODE_NORMAL)
+ || empty($serviceProviderId)) {
+ return;
+ }
+
+ $rocket->mergeParams([
+ 'extend_params' => array_merge($params['extend_params'] ?? [], ['sys_service_provider_id' => $serviceProviderId]),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Arr.php b/addons/epay/library/v3/Yansongda/Supports/Arr.php
new file mode 100644
index 0000000..a888aca
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Arr.php
@@ -0,0 +1,624 @@
+all();
+ } elseif (!is_array($values)) {
+ continue;
+ }
+ $results[] = $values;
+ }
+
+ return array_merge([], ...$results);
+ }
+
+ /**
+ * Cross join the given arrays, returning all possible permutations.
+ *
+ * @param array ...$arrays
+ */
+ public static function crossJoin(...$arrays): array
+ {
+ $results = [[]];
+ foreach ($arrays as $index => $array) {
+ $append = [];
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+ $append[] = $product;
+ }
+ }
+ $results = $append;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ */
+ public static function divide(array $array): array
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ */
+ public static function dot(array $array, string $prepend = ''): array
+ {
+ $results = [];
+ foreach ($array as $key => $value) {
+ if (is_array($value) && !empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
+ } else {
+ $results[$prepend.$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of keys.
+ *
+ * @param array|string $keys
+ */
+ public static function except(array $array, $keys): array
+ {
+ static::forget($array, $keys);
+
+ return $array;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param array|\ArrayAccess $array
+ * @param int|string $key
+ */
+ public static function exists($array, $key): bool
+ {
+ if ($array instanceof ArrayAccess) {
+ return $array->offsetExists($key);
+ }
+
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param mixed|null $default
+ */
+ public static function first(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return $default;
+ }
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $value, $key)) {
+ return $value;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param mixed|null $default
+ */
+ public static function last(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ return empty($array) ? $default : end($array);
+ }
+
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ *
+ * @param float|int $depth
+ */
+ public static function flatten(array $array, $depth = INF): array
+ {
+ $result = [];
+ foreach ($array as $item) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+ if (!is_array($item)) {
+ $result[] = $item;
+ } elseif (1 === $depth) {
+ $result = array_merge($result, array_values($item));
+ } else {
+ $result = array_merge($result, static::flatten($item, $depth - 1));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array|string $keys
+ */
+ public static function forget(array &$array, $keys): void
+ {
+ $original = &$array;
+ $keys = (array) $keys;
+ if (0 === count($keys)) {
+ return;
+ }
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+ continue;
+ }
+ $parts = explode('.', $key);
+ // clean up before each pass
+ $array = &$original;
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param array|\ArrayAccess $array
+ * @param int|string|null $key
+ * @param mixed $default
+ */
+ public static function get($array, $key = null, $default = null)
+ {
+ if (!static::accessible($array)) {
+ return $default;
+ }
+ if (is_null($key)) {
+ return $array;
+ }
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+ if (!is_string($key) || false === strpos($key, '.')) {
+ return $array[$key] ?? $default;
+ }
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($array) && static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return $default;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param array|\ArrayAccess $array
+ * @param array|string|null $keys
+ */
+ public static function has($array, $keys): bool
+ {
+ if (is_null($keys)) {
+ return false;
+ }
+ $keys = (array) $keys;
+ if (!$array) {
+ return false;
+ }
+ if ([] === $keys) {
+ return false;
+ }
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+ if (static::exists($array, $key)) {
+ continue;
+ }
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if an array is associative.
+ * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ */
+ public static function isAssoc(array $array): bool
+ {
+ $keys = array_keys($array);
+
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array|string $keys
+ */
+ public static function only(array $array, $keys): array
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Pluck an array of values from an array.
+ *
+ * @param array|string $value
+ * @param array|string|null $key
+ */
+ public static function pluck(array $array, $value, $key = null): array
+ {
+ $results = [];
+
+ foreach ($array as $item) {
+ $itemValue = data_get($item, $value);
+ // If the key is "null", we will just append the value to the array and keep
+ // looping. Otherwise we will key the array using the value of the key we
+ // received from the developer. Then we'll return the final array form.
+ if (is_null($key)) {
+ $results[] = $itemValue;
+ } else {
+ $itemKey = data_get($item, $key);
+ if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
+ $itemKey = (string) $itemKey;
+ }
+ $results[$itemKey] = $itemValue;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @param mixed|null $key
+ * @param mixed $value
+ */
+ public static function prepend(array $array, $value, $key = null): array
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param mixed|null $default
+ */
+ public static function pull(array &$array, string $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * Get one or a specified number of random values from an array.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random(array $array, int $number = 1): array
+ {
+ $count = count($array);
+ if ($number > $count) {
+ throw new InvalidArgumentException("You requested $number items, but there are only $count items available.");
+ }
+
+ if (0 === $number) {
+ return [];
+ }
+
+ $keys = array_rand($array, $number);
+ $results = [];
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param int|string|null $key
+ * @param mixed $value
+ */
+ public static function set(array &$array, $key, $value): array
+ {
+ if (is_null($key)) {
+ return $array = $value;
+ }
+ if (!is_string($key)) {
+ $array[$key] = $value;
+
+ return $array;
+ }
+ $keys = explode('.', $key);
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (!isset($array[$key]) || !is_array($array[$key])) {
+ $array[$key] = [];
+ }
+ $array = &$array[$key];
+ }
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ /**
+ * Shuffle the given array and return the result.
+ */
+ public static function shuffle(array $array, int $seed = null): array
+ {
+ if (is_null($seed)) {
+ shuffle($array);
+ } else {
+ srand($seed);
+ usort($array, function () {
+ return rand(-1, 1);
+ });
+ }
+
+ return $array;
+ }
+
+ /**
+ * Sort the array using the given Closure.
+ */
+ public static function sort(array $array, callable $callback): array
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ $results[$key] = $callback($value);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Recursively sort an array by keys and values.
+ */
+ public static function sortRecursive(array $array): array
+ {
+ foreach ($array as &$value) {
+ if (is_array($value)) {
+ $value = static::sortRecursive($value);
+ }
+ }
+ if (static::isAssoc($array)) {
+ ksort($array);
+ } else {
+ sort($array);
+ }
+
+ return $array;
+ }
+
+ public static function query(array $array, int $encodingType = PHP_QUERY_RFC1738): string
+ {
+ return http_build_query($array, '', '&', $encodingType);
+ }
+
+ public static function toString(array $array, string $separator = '&'): string
+ {
+ $result = '';
+
+ foreach ($array as $key => $value) {
+ $result .= $key.'='.$value.$separator;
+ }
+
+ return substr($result, 0, 0 - Str::length($separator));
+ }
+
+ /**
+ * Filter the array using the given callback.
+ */
+ public static function where(array $array, callable $callback): array
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * If the given value is not an array and not null, wrap it in one.
+ *
+ * @param mixed $value
+ */
+ public static function wrap($value): array
+ {
+ if (is_null($value)) {
+ return [];
+ }
+
+ return !is_array($value) ? [$value] : $value;
+ }
+
+ /**
+ * Make array elements unique.
+ */
+ public static function unique(array $array): array
+ {
+ $result = [];
+ foreach ($array as $key => $item) {
+ if (is_array($item)) {
+ $result[$key] = self::unique($item);
+ } else {
+ $result[$key] = $item;
+ }
+ }
+
+ if (!self::isAssoc($result)) {
+ return array_unique($result);
+ }
+
+ return $result;
+ }
+
+ public static function merge(array $array1, array $array2, bool $unique = true): array
+ {
+ $isAssoc = static::isAssoc($array1 ?: $array2);
+ if ($isAssoc) {
+ foreach ($array2 as $key => $value) {
+ if (is_array($value)) {
+ $array1[$key] = static::merge($array1[$key] ?? [], $value, $unique);
+ } else {
+ $array1[$key] = $value;
+ }
+ }
+ } else {
+ foreach ($array2 as $value) {
+ if ($unique && in_array($value, $array1, true)) {
+ continue;
+ }
+ $array1[] = $value;
+ }
+
+ $array1 = array_values($array1);
+ }
+
+ return $array1;
+ }
+
+ /**
+ * Convert encoding.
+ */
+ public static function encoding(array $array, string $to_encoding, string $from_encoding = 'gb2312'): array
+ {
+ $encoded = [];
+
+ foreach ($array as $key => $value) {
+ $encoded[$key] = is_array($value) ? self::encoding($value, $to_encoding, $from_encoding) :
+ mb_convert_encoding($value, $to_encoding, $from_encoding);
+ }
+
+ return $encoded;
+ }
+
+ /**
+ * camelCaseKey.
+ *
+ * @param mixed $data
+ *
+ * @return mixed
+ */
+ public static function camelCaseKey($data)
+ {
+ if (!self::accessible($data) &&
+ !(is_object($data) && method_exists($data, 'toArray'))) {
+ return $data;
+ }
+
+ $result = [];
+
+ foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) {
+ $result[is_string($key) ? Str::camel($key) : $key] = self::camelCaseKey($value);
+ }
+
+ return $result;
+ }
+
+ /**
+ * snakeCaseKey.
+ *
+ * @param mixed $data
+ *
+ * @return mixed
+ */
+ public static function snakeCaseKey($data)
+ {
+ if (!self::accessible($data) &&
+ !(is_object($data) && method_exists($data, 'toArray'))) {
+ return $data;
+ }
+
+ $result = [];
+
+ foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) {
+ $result[is_string($key) ? Str::snake($key) : $key] = self::snakeCaseKey($value);
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Collection.php b/addons/epay/library/v3/Yansongda/Supports/Collection.php
new file mode 100644
index 0000000..292d278
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Collection.php
@@ -0,0 +1,531 @@
+getArrayableItems($items) as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * Wrap the given value in a collection if applicable.
+ *
+ * @param mixed $value
+ */
+ public static function wrap($value): self
+ {
+ return $value instanceof self ? new static($value) : new static(Arr::wrap($value));
+ }
+
+ /**
+ * Get the underlying items from the given collection if applicable.
+ *
+ * @param array|static $value
+ */
+ public static function unwrap($value): array
+ {
+ return $value instanceof self ? $value->all() : $value;
+ }
+
+ /**
+ * Return all items.
+ */
+ public function all(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * Return specific items.
+ */
+ public function only(array $keys): array
+ {
+ $return = [];
+
+ foreach ($keys as $key) {
+ $value = $this->get($key);
+
+ if (!is_null($value)) {
+ $return[$key] = $value;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get all items except for those with the specified keys.
+ *
+ * @param mixed $keys
+ */
+ public function except($keys): self
+ {
+ $keys = is_array($keys) ? $keys : func_get_args();
+
+ return new static(Arr::except($this->items, $keys));
+ }
+
+ /**
+ * Run a filter over each of the items.
+ */
+ public function filter(callable $callback = null): self
+ {
+ if ($callback) {
+ return new static(Arr::where($this->items, $callback));
+ }
+
+ return new static(array_filter($this->items));
+ }
+
+ /**
+ * Merge the collection with the given items.
+ *
+ * @param mixed $items
+ */
+ public function merge($items): self
+ {
+ return new static(array_merge($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * To determine Whether the specified element exists.
+ *
+ * @param string|int $key
+ */
+ public function has($key): bool
+ {
+ return !is_null(Arr::get($this->items, $key));
+ }
+
+ /**
+ * Retrieve the first item.
+ *
+ * @return mixed
+ */
+ public function first()
+ {
+ return reset($this->items);
+ }
+
+ /**
+ * Retrieve the last item.
+ *
+ * @return mixed
+ */
+ public function last()
+ {
+ $end = end($this->items);
+
+ reset($this->items);
+
+ return $end;
+ }
+
+ /**
+ * add the item value.
+ *
+ * @param string|int|null $key
+ * @param mixed $value
+ */
+ public function add($key, $value): void
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Set the item value.
+ *
+ * @param string|int|null $key
+ * @param mixed $value
+ */
+ public function set($key, $value): void
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Retrieve item from Collection.
+ *
+ * @param string|int|null $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get($key = null, $default = null)
+ {
+ return Arr::get($this->items, $key, $default);
+ }
+
+ /**
+ * Remove item form Collection.
+ *
+ * @param string|int $key
+ */
+ public function forget($key): void
+ {
+ Arr::forget($this->items, $key);
+ }
+
+ /**
+ * Get a flattened array of the items in the collection.
+ *
+ * @param float|int $depth
+ */
+ public function flatten($depth = INF): self
+ {
+ return new static(Arr::flatten($this->items, $depth));
+ }
+
+ /**
+ * Run a map over each of the items.
+ */
+ public function map(callable $callback): self
+ {
+ $keys = array_keys($this->items);
+ $items = array_map($callback, $this->items, $keys);
+
+ return new static(array_combine($keys, $items));
+ }
+
+ /**
+ * Get and remove the last item from the collection.
+ *
+ * @return mixed
+ */
+ public function pop()
+ {
+ return array_pop($this->items);
+ }
+
+ /**
+ * Push an item onto the beginning of the collection.
+ *
+ * @param mixed|null $key
+ * @param mixed $value
+ */
+ public function prepend($value, $key = null): self
+ {
+ $this->items = Arr::prepend($this->items, $value, $key);
+
+ return $this;
+ }
+
+ /**
+ * Push an item onto the end of the collection.
+ *
+ * @param mixed $value
+ */
+ public function push($value): self
+ {
+ $this->offsetSet(null, $value);
+
+ return $this;
+ }
+
+ /**
+ * Get and remove an item from the collection.
+ *
+ * @param mixed $default
+ * @param mixed $key
+ *
+ * @return mixed
+ */
+ public function pull($key, $default = null)
+ {
+ return Arr::pull($this->items, $key, $default);
+ }
+
+ /**
+ * Put an item in the collection by key.
+ *
+ * @param mixed $key
+ * @param mixed $value
+ */
+ public function put($key, $value): self
+ {
+ $this->offsetSet($key, $value);
+
+ return $this;
+ }
+
+ /**
+ * Get one or a specified number of items randomly from the collection.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function random(?int $number = null): self
+ {
+ return new static(Arr::random($this->items, $number ?? 1));
+ }
+
+ /**
+ * Reduce the collection to a single value.
+ *
+ * @param mixed|null $initial
+ */
+ public function reduce(callable $callback, $initial = null)
+ {
+ return array_reduce($this->items, $callback, $initial);
+ }
+
+ /**
+ * Reset the keys on the underlying array.
+ */
+ public function values(): self
+ {
+ return new static(array_values($this->items));
+ }
+
+ /**
+ * Determine if all items in the collection pass the given test.
+ *
+ * @param callable|string $key
+ */
+ public function every($key): bool
+ {
+ $callback = $this->valueRetriever($key);
+
+ foreach ($this->items as $k => $v) {
+ if (!$callback($v, $k)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Chunk the underlying collection array.
+ */
+ public function chunk(int $size): self
+ {
+ if ($size <= 0) {
+ return new static();
+ }
+ $chunks = [];
+ foreach (array_chunk($this->items, $size, true) as $chunk) {
+ $chunks[] = new static($chunk);
+ }
+
+ return new static($chunks);
+ }
+
+ /**
+ * Sort through each item with a callback.
+ */
+ public function sort(callable $callback = null): self
+ {
+ $items = $this->items;
+ $callback ? uasort($items, $callback) : asort($items);
+
+ return new static($items);
+ }
+
+ /**
+ * Sort the collection using the given callback.
+ *
+ * @param callable|string $callback
+ */
+ public function sortBy($callback, int $options = SORT_REGULAR, bool $descending = false): self
+ {
+ $results = [];
+ $callback = $this->valueRetriever($callback);
+ // First we will loop through the items and get the comparator from a callback
+ // function which we were given. Then, we will sort the returned values and
+ // and grab the corresponding values for the sorted keys from this array.
+ foreach ($this->items as $key => $value) {
+ $results[$key] = $callback($value, $key);
+ }
+ $descending ? arsort($results, $options) : asort($results, $options);
+ // Once we have sorted all of the keys in the array, we will loop through them
+ // and grab the corresponding model so we can set the underlying items list
+ // to the sorted version. Then we'll just return the collection instance.
+ foreach (array_keys($results) as $key) {
+ $results[$key] = $this->items[$key];
+ }
+
+ return new static($results);
+ }
+
+ /**
+ * Sort the collection in descending order using the given callback.
+ *
+ * @param callable|string $callback
+ */
+ public function sortByDesc($callback, int $options = SORT_REGULAR): self
+ {
+ return $this->sortBy($callback, $options, true);
+ }
+
+ /**
+ * Sort the collection keys.
+ */
+ public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): self
+ {
+ $items = $this->items;
+ $descending ? krsort($items, $options) : ksort($items, $options);
+
+ return new static($items);
+ }
+
+ /**
+ * Sort the collection keys in descending order.
+ */
+ public function sortKeysDesc(int $options = SORT_REGULAR): self
+ {
+ return $this->sortKeys($options, true);
+ }
+
+ public function query(int $encodingType = PHP_QUERY_RFC1738): string
+ {
+ return Arr::query($this->all(), $encodingType);
+ }
+
+ public function toString(string $separator = '&'): string
+ {
+ return Arr::toString($this->all(), $separator);
+ }
+
+ /**
+ * Build to array.
+ */
+ public function toArray(): array
+ {
+ return $this->all();
+ }
+
+ /**
+ * Build to json.
+ */
+ public function toJson(int $option = JSON_UNESCAPED_UNICODE): string
+ {
+ return json_encode($this->all(), $option);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Retrieve an external iterator.
+ *
+ * @see http://php.net/manual/en/iteratoraggregate.getiterator.php
+ *
+ * @return ArrayIterator An instance of an object implementing Iterator or
+ * ArrayIterator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Count elements of an object.
+ *
+ * @see http://php.net/manual/en/countable.count.php
+ *
+ * @return int The custom count as an integer.
+ *
+ *
+ * The return value is cast to an integer
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to unset.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset
+ * The offset to unset.
+ *
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ if ($this->offsetExists($offset)) {
+ $this->forget($offset);
+ }
+ }
+
+ /**
+ * Determine if the given value is callable, but not a string.
+ *
+ * @param mixed $value
+ */
+ protected function useAsCallable($value): bool
+ {
+ return !is_string($value) && is_callable($value);
+ }
+
+ /**
+ * Get a value retrieving callback.
+ *
+ * @param mixed $value
+ */
+ protected function valueRetriever($value): callable
+ {
+ if ($this->useAsCallable($value)) {
+ return $value;
+ }
+
+ return function ($item) use ($value) {
+ return data_get($item, $value);
+ };
+ }
+
+ /**
+ * Results array of items from Collection or Arrayable.
+ *
+ * @param mixed $items
+ */
+ protected function getArrayableItems($items): array
+ {
+ if (is_array($items)) {
+ return $items;
+ }
+
+ if ($items instanceof self) {
+ return $items->all();
+ }
+
+ if ($items instanceof JsonSerializable) {
+ return $items->jsonSerialize();
+ }
+
+ return (array) $items;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Config.php b/addons/epay/library/v3/Yansongda/Supports/Config.php
new file mode 100644
index 0000000..c6e3ae9
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Config.php
@@ -0,0 +1,9 @@
+all();
+ } elseif (!is_array($target)) {
+ return value($default);
+ }
+ $result = [];
+ foreach ($target as $item) {
+ $result[] = data_get($item, $key);
+ }
+
+ return in_array('*', $key) ? Arr::collapse($result) : $result;
+ }
+ if (Arr::accessible($target) && Arr::exists($target, $segment)) {
+ $target = $target[$segment];
+ } elseif (is_object($target) && isset($target->{$segment})) {
+ $target = $target->{$segment};
+ } else {
+ return value($default);
+ }
+ }
+
+ return $target;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Logger.php b/addons/epay/library/v3/Yansongda/Supports/Logger.php
new file mode 100644
index 0000000..35ac7c3
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Logger.php
@@ -0,0 +1,174 @@
+ null,
+ 'identify' => 'yansongda.supports',
+ 'level' => BaseLogger::DEBUG,
+ 'type' => 'daily',
+ 'max_files' => 30,
+ ];
+
+ /**
+ * Bootstrap.
+ */
+ public function __construct(array $config = [])
+ {
+ $this->setConfig($config);
+ }
+
+ /**
+ * Forward call.
+ *
+ * @throws Exception
+ */
+ public function __call(string $method, array $args): void
+ {
+ call_user_func_array([$this->getLogger(), $method], $args);
+ }
+
+ /**
+ * Set logger.
+ */
+ public function setLogger(LoggerInterface $logger): Logger
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
+ /**
+ * Return the logger instance.
+ *
+ * @throws Exception
+ */
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger ??= $this->createLogger();
+ }
+
+ public function createLogger(): BaseLogger
+ {
+ $handler = $this->getHandler();
+
+ $handler->setFormatter($this->getFormatter());
+
+ $logger = new BaseLogger($this->config['identify']);
+
+ $logger->pushHandler($handler);
+
+ return $logger;
+ }
+
+ /**
+ * setFormatter.
+ *
+ * @return $this
+ */
+ public function setFormatter(FormatterInterface $formatter): self
+ {
+ $this->formatter = $formatter;
+
+ return $this;
+ }
+
+ /**
+ * getFormatter.
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ return $this->formatter ??= $this->createFormatter();
+ }
+
+ /**
+ * createFormatter.
+ */
+ public function createFormatter(): LineFormatter
+ {
+ return new LineFormatter(
+ "%datetime% > %channel%.%level_name% > %message% %context% %extra%\n\n",
+ null,
+ false,
+ true
+ );
+ }
+
+ /**
+ * setHandler.
+ *
+ * @return $this
+ */
+ public function setHandler(AbstractProcessingHandler $handler): self
+ {
+ $this->handler = $handler;
+
+ return $this;
+ }
+
+ public function getHandler(): AbstractProcessingHandler
+ {
+ return $this->handler ??= $this->createHandler();
+ }
+
+ public function createHandler(): AbstractProcessingHandler
+ {
+ $file = $this->config['file'] ?? sys_get_temp_dir().'/logs/'.$this->config['identify'].'.log';
+
+ if ('single' === $this->config['type']) {
+ return new StreamHandler($file, $this->config['level']);
+ }
+
+ return new RotatingFileHandler($file, $this->config['max_files'], $this->config['level']);
+ }
+
+ /**
+ * setConfig.
+ *
+ * @return $this
+ */
+ public function setConfig(array $config): self
+ {
+ $this->config = array_merge($this->config, $config);
+
+ return $this;
+ }
+
+ /**
+ * getConfig.
+ */
+ public function getConfig(): array
+ {
+ return $this->config;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Logger/StdoutHandler.php b/addons/epay/library/v3/Yansongda/Supports/Logger/StdoutHandler.php
new file mode 100644
index 0000000..ef53188
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Logger/StdoutHandler.php
@@ -0,0 +1,33 @@
+output = $output ?? new ConsoleOutput();
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * Writes the record down to the log of the implementing handler.
+ */
+ protected function write(array $record): void
+ {
+ $this->output->writeln($record['formatted']);
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Pipeline.php b/addons/epay/library/v3/Yansongda/Supports/Pipeline.php
new file mode 100644
index 0000000..8a6ea20
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Pipeline.php
@@ -0,0 +1,158 @@
+container = $container;
+ }
+
+ /**
+ * Set the object being sent through the pipeline.
+ *
+ * @param mixed $passable
+ */
+ public function send($passable): self
+ {
+ $this->passable = $passable;
+
+ return $this;
+ }
+
+ /**
+ * Set the array of pipes.
+ *
+ * @param array|mixed $pipes
+ */
+ public function through($pipes): self
+ {
+ $this->pipes = is_array($pipes) ? $pipes : func_get_args();
+
+ return $this;
+ }
+
+ /**
+ * Set the method to call on the pipes.
+ */
+ public function via(string $method): self
+ {
+ $this->method = $method;
+
+ return $this;
+ }
+
+ /**
+ * Run the pipeline with a final destination callback.
+ */
+ public function then(Closure $destination)
+ {
+ $pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination));
+
+ return $pipeline($this->passable);
+ }
+
+ /**
+ * Get the final piece of the Closure onion.
+ */
+ protected function prepareDestination(Closure $destination): Closure
+ {
+ return static function ($passable) use ($destination) {
+ return $destination($passable);
+ };
+ }
+
+ /**
+ * Get a Closure that represents a slice of the application onion.
+ */
+ protected function carry(): Closure
+ {
+ return function ($stack, $pipe) {
+ return function ($passable) use ($stack, $pipe) {
+ if (is_callable($pipe)) {
+ // If the pipe is an instance of a Closure, we will just call it directly but
+ // otherwise we'll resolve the pipes out of the container and call it with
+ // the appropriate method and arguments, returning the results back out.
+ return $pipe($passable, $stack);
+ }
+ if (!is_object($pipe)) {
+ [$name, $parameters] = $this->parsePipeString($pipe);
+
+ // If the pipe is a string we will parse the string and resolve the class out
+ // of the dependency injection container. We can then build a callable and
+ // execute the pipe function giving in the parameters that are required.
+ $pipe = $this->container->get($name);
+
+ $parameters = array_merge([$passable, $stack], $parameters);
+ } else {
+ // If the pipe is already an object we'll just make a callable and pass it to
+ // the pipe as-is. There is no need to do any extra parsing and formatting
+ // since the object we're given was already a fully instantiated object.
+ $parameters = [$passable, $stack];
+ }
+
+ $carry = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters);
+
+ return $this->handleCarry($carry);
+ };
+ };
+ }
+
+ /**
+ * Parse full pipe string to get name and parameters.
+ */
+ protected function parsePipeString(string $pipe): array
+ {
+ [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);
+
+ if (is_string($parameters)) {
+ $parameters = explode(',', $parameters);
+ }
+
+ return [$name, $parameters];
+ }
+
+ /**
+ * Handle the value returned from each pipe before passing it to the next.
+ *
+ * @param mixed $carry
+ *
+ * @return mixed
+ */
+ protected function handleCarry($carry)
+ {
+ return $carry;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Str.php b/addons/epay/library/v3/Yansongda/Supports/Str.php
new file mode 100644
index 0000000..a2a19d3
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Str.php
@@ -0,0 +1,587 @@
+ $val) {
+ $value = str_replace($val, $key, $value);
+ }
+
+ return preg_replace('/[^\x20-\x7E]/u', '', $value);
+ }
+
+ /**
+ * Get the portion of a string before a given value.
+ */
+ public static function before(string $subject, string $search): string
+ {
+ return '' === $search ? $subject : explode($search, $subject)[0];
+ }
+
+ /**
+ * Convert a value to camel case.
+ */
+ public static function camel(string $value): string
+ {
+ if (isset(static::$camelCache[$value])) {
+ return static::$camelCache[$value];
+ }
+
+ return static::$camelCache[$value] = lcfirst(static::studly($value));
+ }
+
+ /**
+ * Determine if a given string contains a given substring.
+ *
+ * @param string|array $needles
+ */
+ public static function contains(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' !== $needle && false !== mb_strpos($haystack, $needle)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if a given string ends with a given substring.
+ *
+ * @param string|array $needles
+ */
+ public static function endsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if (substr($haystack, -strlen($needle)) === (string) $needle) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Cap a string with a single instance of a given value.
+ */
+ public static function finish(string $value, string $cap): string
+ {
+ $quoted = preg_quote($cap, '/');
+
+ return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap;
+ }
+
+ /**
+ * Determine if a given string matches a given pattern.
+ *
+ * @param string|array $pattern
+ */
+ public static function is($pattern, string $value): bool
+ {
+ $patterns = Arr::wrap($pattern);
+
+ if (empty($patterns)) {
+ return false;
+ }
+
+ foreach ($patterns as $pattern) {
+ // If the given value is an exact match we can of course return true right
+ // from the beginning. Otherwise, we will translate asterisks and do an
+ // actual pattern match against the two strings to see if they match.
+ if ($pattern == $value) {
+ return true;
+ }
+
+ $pattern = preg_quote($pattern, '#');
+
+ // Asterisks are translated into zero-or-more regular expression wildcards
+ // to make it convenient to check if the strings starts with the given
+ // pattern such as "library/*", making any string check convenient.
+ $pattern = str_replace('\*', '.*', $pattern);
+
+ if (1 === preg_match('#^'.$pattern.'\z#u', $value)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Convert a string to kebab case.
+ */
+ public static function kebab(string $value): string
+ {
+ return static::snake($value, '-');
+ }
+
+ /**
+ * Return the length of the given string.
+ */
+ public static function length(string $value, ?string $encoding = null): int
+ {
+ if (null !== $encoding) {
+ return mb_strlen($value, $encoding);
+ }
+
+ return mb_strlen($value);
+ }
+
+ /**
+ * Limit the number of characters in a string.
+ */
+ public static function limit(string $value, int $limit = 100, string $end = '...'): string
+ {
+ if (mb_strwidth($value, 'UTF-8') <= $limit) {
+ return $value;
+ }
+
+ return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end;
+ }
+
+ /**
+ * Convert the given string to lower-case.
+ */
+ public static function lower(string $value): string
+ {
+ return mb_strtolower($value, 'UTF-8');
+ }
+
+ /**
+ * Limit the number of words in a string.
+ */
+ public static function words(string $value, int $words = 100, string $end = '...'): string
+ {
+ preg_match('/^\s*+\S++\s*+{1,'.$words.'}/u', $value, $matches);
+
+ if (!isset($matches[0]) || static::length($value) === static::length($matches[0])) {
+ return $value;
+ }
+
+ return rtrim($matches[0]).$end;
+ }
+
+ /**
+ * Parse a Class.
+ */
+ public static function parseCallback(string $callback, ?string $default = null): array
+ {
+ return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
+ }
+
+ /**
+ * Generate a more truly "random" alpha-numeric string.
+ *
+ * @throws Exception
+ */
+ public static function random(int $length = 16): string
+ {
+ $string = '';
+
+ while (($len = strlen($string)) < $length) {
+ $size = $length - $len;
+
+ $bytes = random_bytes($size);
+
+ $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
+ }
+
+ return $string;
+ }
+
+ public static function uuidV4(): string
+ {
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+
+ // 32 bits for "time_low"
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff),
+
+ // 16 bits for "time_mid"
+ mt_rand(0, 0xffff),
+
+ // 16 bits for "time_hi_and_version",
+ // four most significant bits holds version number 4
+ mt_rand(0, 0x0fff) | 0x4000,
+
+ // 16 bits, 8 bits for "clk_seq_hi_res",
+ // 8 bits for "clk_seq_low",
+ // two most significant bits holds zero and one for variant DCE1.1
+ mt_rand(0, 0x3fff) | 0x8000,
+
+ // 48 bits for "node"
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
+ );
+ }
+
+ /**
+ * Replace a given value in the string sequentially with an array.
+ */
+ public static function replaceArray(string $search, array $replace, string $subject): string
+ {
+ foreach ($replace as $value) {
+ $subject = static::replaceFirst($search, $value, $subject);
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Replace the first occurrence of a given value in the string.
+ */
+ public static function replaceFirst(string $search, string $replace, string $subject): string
+ {
+ if ('' == $search) {
+ return $subject;
+ }
+
+ $position = strpos($subject, $search);
+
+ if (false !== $position) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Replace the last occurrence of a given value in the string.
+ */
+ public static function replaceLast(string $search, string $replace, string $subject): string
+ {
+ $position = strrpos($subject, $search);
+
+ if (false !== $position) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ /**
+ * Begin a string with a single instance of a given value.
+ */
+ public static function start(string $value, string $prefix): string
+ {
+ $quoted = preg_quote($prefix, '/');
+
+ return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
+ }
+
+ /**
+ * Convert the given string to upper-case.
+ */
+ public static function upper(string $value): string
+ {
+ return mb_strtoupper($value, 'UTF-8');
+ }
+
+ /**
+ * Convert the given string to title case.
+ */
+ public static function title(string $value): string
+ {
+ return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+ }
+
+ /**
+ * Generate a URL friendly "slug" from a given string.
+ */
+ public static function slug(string $title, string $separator = '-', string $language = 'en'): string
+ {
+ $title = static::ascii($title, $language);
+
+ // Convert all dashes/underscores into separator
+ $flip = '-' == $separator ? '_' : '-';
+
+ $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
+
+ // Replace @ with the word 'at'
+ $title = str_replace('@', $separator.'at'.$separator, $title);
+
+ // Remove all characters that are not the separator, letters, numbers, or whitespace.
+ $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($title));
+
+ // Replace all separator characters and whitespace by a single separator
+ $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
+
+ return trim($title, $separator);
+ }
+
+ /**
+ * Convert a string to snake case.
+ */
+ public static function snake(string $value, string $delimiter = '_'): string
+ {
+ $key = $value;
+
+ if (isset(static::$snakeCache[$key][$delimiter])) {
+ return static::$snakeCache[$key][$delimiter];
+ }
+
+ if (!ctype_lower($value)) {
+ $value = preg_replace('/\s+/u', '', ucwords($value));
+
+ $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
+ }
+
+ return static::$snakeCache[$key][$delimiter] = $value;
+ }
+
+ /**
+ * Determine if a given string starts with a given substring.
+ *
+ * @param string|array $needles
+ */
+ public static function startsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' !== $needle && substr($haystack, 0, strlen($needle)) === (string) $needle) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Convert a value to studly caps case.
+ */
+ public static function studly(string $value): string
+ {
+ $key = $value;
+
+ if (isset(static::$studlyCache[$key])) {
+ return static::$studlyCache[$key];
+ }
+
+ $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+ return static::$studlyCache[$key] = str_replace(' ', '', $value);
+ }
+
+ /**
+ * Returns the portion of string specified by the start and length parameters.
+ */
+ public static function substr(string $string, int $start, ?int $length = null): string
+ {
+ return mb_substr($string, $start, $length, 'UTF-8');
+ }
+
+ /**
+ * Make a string's first character uppercase.
+ */
+ public static function ucfirst(string $string): string
+ {
+ return static::upper(static::substr($string, 0, 1)).static::substr($string, 1);
+ }
+
+ /**
+ * Convert string's encoding.
+ */
+ public static function encoding(string $string, string $to = 'utf-8', string $from = 'gb2312'): string
+ {
+ return mb_convert_encoding($string, $to, $from);
+ }
+
+ /**
+ * Returns the replacements for the ascii method.
+ *
+ * Note: Adapted from Stringy\Stringy.
+ *
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ */
+ protected static function charsArray(): array
+ {
+ static $charsArray;
+
+ if (isset($charsArray)) {
+ return $charsArray;
+ }
+
+ return $charsArray = [
+ '0' => ['°', '₀', '۰', '0'],
+ '1' => ['¹', '₁', '۱', '1'],
+ '2' => ['²', '₂', '۲', '2'],
+ '3' => ['³', '₃', '۳', '3'],
+ '4' => ['⁴', '₄', '۴', '٤', '4'],
+ '5' => ['⁵', '₅', '۵', '٥', '5'],
+ '6' => ['⁶', '₆', '۶', '٦', '6'],
+ '7' => ['⁷', '₇', '۷', '7'],
+ '8' => ['⁸', '₈', '۸', '8'],
+ '9' => ['⁹', '₉', '۹', '9'],
+ 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'],
+ 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'],
+ 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'],
+ 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'],
+ 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'],
+ 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'],
+ 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'],
+ 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'],
+ 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i'],
+ 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'],
+ 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'],
+ 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'],
+ 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'],
+ 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'],
+ 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö'],
+ 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'],
+ 'q' => ['ყ', 'q'],
+ 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'],
+ 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'],
+ 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'],
+ 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü'],
+ 'v' => ['в', 'ვ', 'ϐ', 'v'],
+ 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'],
+ 'x' => ['χ', 'ξ', 'x'],
+ 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'],
+ 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'],
+ 'aa' => ['ع', 'आ', 'آ'],
+ 'ae' => ['æ', 'ǽ'],
+ 'ai' => ['ऐ'],
+ 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'],
+ 'dj' => ['ђ', 'đ'],
+ 'dz' => ['џ', 'ძ'],
+ 'ei' => ['ऍ'],
+ 'gh' => ['غ', 'ღ'],
+ 'ii' => ['ई'],
+ 'ij' => ['ij'],
+ 'kh' => ['х', 'خ', 'ხ'],
+ 'lj' => ['љ'],
+ 'nj' => ['њ'],
+ 'oe' => ['ö', 'œ', 'ؤ'],
+ 'oi' => ['ऑ'],
+ 'oii' => ['ऒ'],
+ 'ps' => ['ψ'],
+ 'sh' => ['ш', 'შ', 'ش'],
+ 'shch' => ['щ'],
+ 'ss' => ['ß'],
+ 'sx' => ['ŝ'],
+ 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'],
+ 'ts' => ['ц', 'ც', 'წ'],
+ 'ue' => ['ü'],
+ 'uu' => ['ऊ'],
+ 'ya' => ['я'],
+ 'yu' => ['ю'],
+ 'zh' => ['ж', 'ჟ', 'ژ'],
+ '(c)' => ['©'],
+ 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'],
+ 'B' => ['Б', 'Β', 'ब', 'B'],
+ 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'],
+ 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'],
+ 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E'],
+ 'F' => ['Ф', 'Φ', 'F'],
+ 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'],
+ 'H' => ['Η', 'Ή', 'Ħ', 'H'],
+ 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I'],
+ 'J' => ['J'],
+ 'K' => ['К', 'Κ', 'K'],
+ 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'],
+ 'M' => ['М', 'Μ', 'M'],
+ 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'],
+ 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'],
+ 'P' => ['П', 'Π', 'P'],
+ 'Q' => ['Q'],
+ 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'],
+ 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'],
+ 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'],
+ 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'],
+ 'V' => ['В', 'V'],
+ 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'],
+ 'X' => ['Χ', 'Ξ', 'X'],
+ 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'],
+ 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'],
+ 'AE' => ['Æ', 'Ǽ'],
+ 'Ch' => ['Ч'],
+ 'Dj' => ['Ђ'],
+ 'Dz' => ['Џ'],
+ 'Gx' => ['Ĝ'],
+ 'Hx' => ['Ĥ'],
+ 'Ij' => ['IJ'],
+ 'Jx' => ['Ĵ'],
+ 'Kh' => ['Х'],
+ 'Lj' => ['Љ'],
+ 'Nj' => ['Њ'],
+ 'Oe' => ['Œ'],
+ 'Ps' => ['Ψ'],
+ 'Sh' => ['Ш'],
+ 'Shch' => ['Щ'],
+ 'Ss' => ['ẞ'],
+ 'Th' => ['Þ'],
+ 'Ts' => ['Ц'],
+ 'Ya' => ['Я'],
+ 'Yu' => ['Ю'],
+ 'Zh' => ['Ж'],
+ ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0"],
+ ];
+ }
+
+ /**
+ * Returns the language specific replacements for the ascii method.
+ *
+ * Note: Adapted from Stringy\Stringy.
+ *
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ */
+ protected static function languageSpecificCharsArray(string $language): ?array
+ {
+ static $languageSpecific;
+ if (!isset($languageSpecific)) {
+ $languageSpecific = [
+ 'bg' => [
+ ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'],
+ ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'],
+ ],
+ 'de' => [
+ ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'],
+ ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'],
+ ],
+ ];
+ }
+
+ return $languageSpecific[$language] ?? null;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Traits/Accessable.php b/addons/epay/library/v3/Yansongda/Supports/Traits/Accessable.php
new file mode 100644
index 0000000..8e5abd8
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Traits/Accessable.php
@@ -0,0 +1,146 @@
+get($key);
+ }
+
+ /**
+ * Whether or not an data exists by key.
+ */
+ public function __isset(string $key): bool
+ {
+ return !is_null($this->get($key));
+ }
+
+ /**
+ * Unsets an data by key.
+ */
+ public function __unset(string $key)
+ {
+ $this->offsetUnset($key);
+ }
+
+ /**
+ * __set.
+ *
+ * @param mixed $value
+ */
+ public function __set(string $key, $value): void
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * get.
+ *
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get(?string $key = null, $default = null)
+ {
+ if (is_null($key)) {
+ return method_exists($this, 'toArray') ? $this->toArray() : $default;
+ }
+
+ $method = 'get'.Str::studly($key);
+
+ if (method_exists($this, $method)) {
+ return $this->{$method}();
+ }
+
+ return $default;
+ }
+
+ /**
+ * set.
+ *
+ * @param mixed $value
+ */
+ public function set(string $key, $value): self
+ {
+ $method = 'set'.Str::studly($key);
+
+ if (method_exists($this, $method)) {
+ $this->{$method}($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Whether a offset exists.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetexists.php
+ *
+ * @param mixed $offset an offset to check for
+ *
+ * @return bool true on success or false on failure.
+ *
+ * The return value will be casted to boolean if non-boolean was returned.
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return !is_null($this->get($offset));
+ }
+
+ /**
+ * Offset to retrieve.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetget.php
+ *
+ * @param mixed $offset the offset to retrieve
+ *
+ * @return mixed can return all value types
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * Offset to set.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetset.php
+ *
+ * @param mixed $offset the offset to assign the value to
+ * @param mixed $value the value to set
+ *
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ $this->set($offset, $value);
+ }
+
+ /**
+ * Offset to unset.
+ *
+ * @see https://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset the offset to unset
+ *
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Traits/Arrayable.php b/addons/epay/library/v3/Yansongda/Supports/Traits/Arrayable.php
new file mode 100644
index 0000000..973e02a
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Traits/Arrayable.php
@@ -0,0 +1,28 @@
+getProperties() as $item) {
+ $k = $item->getName();
+ $method = 'get'.Str::studly($k);
+
+ $result[Str::snake($k)] = method_exists($this, $method) ? $this->{$method}() : $this->{$k};
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v3/Yansongda/Supports/Traits/Serializable.php b/addons/epay/library/v3/Yansongda/Supports/Traits/Serializable.php
new file mode 100644
index 0000000..1cace58
--- /dev/null
+++ b/addons/epay/library/v3/Yansongda/Supports/Traits/Serializable.php
@@ -0,0 +1,65 @@
+toArray();
+ }
+
+ return [];
+ }
+
+ public function __unserialize(array $data): void
+ {
+ $this->unserializeArray($data);
+ }
+
+ public function __toString(): string
+ {
+ return $this->toJson();
+ }
+
+ public function serialize(): ?string
+ {
+ return serialize($this);
+ }
+
+ public function unserialize($data): void
+ {
+ unserialize($data);
+ }
+
+ /**
+ * toJson.
+ */
+ public function toJson(int $option = JSON_UNESCAPED_UNICODE): string
+ {
+ return json_encode($this->__serialize(), $option);
+ }
+
+ /**
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return $this->__serialize();
+ }
+
+ public function unserializeArray(array $data): self
+ {
+ foreach ($data as $key => $item) {
+ if (method_exists($this, 'set')) {
+ $this->set($key, $item);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/addons/epay/library/v3/version.txt b/addons/epay/library/v3/version.txt
new file mode 100644
index 0000000..de1eb44
--- /dev/null
+++ b/addons/epay/library/v3/version.txt
@@ -0,0 +1 @@
+v3.4.1
\ No newline at end of file
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Artful.php b/addons/epay/library/v3new/Yansongda/Artful/Artful.php
new file mode 100644
index 0000000..cf9ba48
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Artful.php
@@ -0,0 +1,342 @@
+registerServices($config, $container);
+
+ Artful::set(DirectionInterface::class, CollectionDirection::class);
+ Artful::set(PackerInterface::class, JsonPacker::class);
+ }
+
+ /**
+ * @return mixed
+ *
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ public static function __callStatic(string $service, array $config)
+ {
+ if (!empty($config)) {
+ self::config(...$config);
+ }
+
+ return self::get($service);
+ }
+
+ /**
+ * @throws ContainerException
+ */
+ public static function config(array $config = [], null|Closure|ContainerInterface $container = null): bool
+ {
+ if (self::hasContainer() && !($config['_force'] ?? false)) {
+ return false;
+ }
+
+ new self($config, $container);
+
+ return true;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ *
+ * @throws ContainerException
+ */
+ public static function set(string $name, mixed $value): void
+ {
+ try {
+ $container = Artful::getContainer();
+
+ if ($container instanceof LaravelContainer) {
+ $container->singleton($name, $value instanceof Closure ? $value : static fn () => $value);
+
+ return;
+ }
+
+ if (method_exists($container, 'set')) {
+ $container->set(...func_get_args());
+
+ return;
+ }
+ } catch (ContainerNotFoundException $e) {
+ throw $e;
+ } catch (Throwable $e) {
+ throw new ContainerException('容器异常: '.$e->getMessage());
+ }
+
+ throw new ContainerException('容器异常: 当前容器类型不支持 `set` 方法');
+ }
+
+ /**
+ * @codeCoverageIgnore
+ *
+ * @throws ContainerException
+ */
+ public static function make(string $service, array $parameters = []): mixed
+ {
+ try {
+ $container = Artful::getContainer();
+
+ if (method_exists($container, 'make')) {
+ return $container->make(...func_get_args());
+ }
+ } catch (ContainerNotFoundException $e) {
+ throw $e;
+ } catch (Throwable $e) {
+ throw new ContainerException('容器异常: '.$e->getMessage());
+ }
+
+ $parameters = array_values($parameters);
+
+ return new $service(...$parameters);
+ }
+
+ /**
+ * @throws ServiceNotFoundException
+ * @throws ContainerException
+ */
+ public static function get(string $service): mixed
+ {
+ try {
+ return Artful::getContainer()->get($service);
+ } catch (NotFoundExceptionInterface $e) {
+ throw new ServiceNotFoundException('服务未找到: '.$e->getMessage());
+ } catch (ContainerNotFoundException $e) {
+ throw $e;
+ } catch (Throwable $e) {
+ throw new ContainerException('容器异常: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * @throws ContainerNotFoundException
+ */
+ public static function has(string $service): bool
+ {
+ return Artful::getContainer()->has($service);
+ }
+
+ public static function setContainer(null|Closure|ContainerInterface $container): void
+ {
+ self::$container = $container;
+ }
+
+ /**
+ * @throws ContainerNotFoundException
+ */
+ public static function getContainer(): ContainerInterface
+ {
+ if (self::$container instanceof ContainerInterface) {
+ return self::$container;
+ }
+
+ if (self::$container instanceof Closure) {
+ return (self::$container)();
+ }
+
+ throw new ContainerNotFoundException('容器未找到: `getContainer()` 方法调用失败! 或许你应该先 `setContainer()`');
+ }
+
+ public static function hasContainer(): bool
+ {
+ return self::$container instanceof ContainerInterface || self::$container instanceof Closure;
+ }
+
+ public static function clear(): void
+ {
+ self::$container = null;
+ }
+
+ /**
+ * @throws ContainerException
+ */
+ public static function load(string $service, mixed $data = null): void
+ {
+ self::registerService($service, $data);
+ }
+
+ /**
+ * @throws ContainerException
+ */
+ public static function registerService(string $service, mixed $data = null): void
+ {
+ $var = new $service();
+
+ if ($var instanceof ServiceProviderInterface) {
+ $var->register($data);
+ }
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public static function shortcut(string $shortcut, array $params = []): null|Collection|MessageInterface|Rocket
+ {
+ if (!class_exists($shortcut) || !in_array(ShortcutInterface::class, class_implements($shortcut))) {
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_INVALID, "参数异常: [{$shortcut}] 未实现 `ShortcutInterface`");
+ }
+
+ /* @var ShortcutInterface $shortcutInstance */
+ $shortcutInstance = self::get($shortcut);
+
+ return self::artful($shortcutInstance->getPlugins($params), $params);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ */
+ public static function artful(array $plugins, array $params): null|Collection|MessageInterface|Rocket
+ {
+ Event::dispatch(new Event\ArtfulStart($plugins, $params));
+
+ self::verifyPlugin($plugins);
+
+ /* @var Pipeline $pipeline */
+ $pipeline = self::make(Pipeline::class);
+
+ /* @var Rocket $rocket */
+ $rocket = $pipeline
+ ->send((new Rocket())->setParams($params)->setPayload(new Collection()))
+ ->through($plugins)
+ ->via('assembly')
+ ->then(static fn ($rocket) => self::ignite($rocket));
+
+ Event::dispatch(new Event\ArtfulEnd($rocket));
+
+ if (!empty($params['_return_rocket'])) {
+ return $rocket;
+ }
+
+ return $rocket->getDestination();
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws InvalidResponseException
+ * @throws ServiceNotFoundException
+ */
+ public static function ignite(Rocket $rocket): Rocket
+ {
+ if (!should_do_http_request($rocket->getDirection())) {
+ return $rocket;
+ }
+
+ /* @var HttpClientFactoryInterface $httpFactory */
+ $httpFactory = self::get(HttpClientFactoryInterface::class);
+ /* @var Config $config */
+ $config = self::get(ConfigInterface::class);
+
+ if (!$httpFactory instanceof HttpClientFactoryInterface) {
+ throw new InvalidParamsException(Exception::PARAMS_HTTP_CLIENT_FACTORY_INVALID, '参数异常: 配置的 `HttpClientFactoryInterface` 不符合规范');
+ }
+
+ Logger::info('[Artful] 准备请求第三方 API', $rocket->toArray());
+
+ $http = $httpFactory->create(array_merge($config->get('http', []), $rocket->getPayload()?->get('_http') ?? []));
+
+ Event::dispatch(new Event\HttpStart($rocket));
+
+ try {
+ $response = $http->sendRequest($rocket->getRadar());
+
+ $rocket->setDestination(clone $response)
+ ->setDestinationOrigin(clone $response);
+ } catch (Throwable $e) {
+ Logger::error('[Artful] 请求第三方 API 出错', ['message' => $e->getMessage(), 'rocket' => $rocket->toArray(), 'trace' => $e->getTrace()]);
+
+ throw new InvalidResponseException(Exception::REQUEST_RESPONSE_ERROR, '响应异常: 请求第三方 API 出错 - '.$e->getMessage(), [], $e);
+ }
+
+ Logger::info('[Artful] 请求第三方 API 成功', ['rocket' => $rocket->toArray()]);
+
+ Event::dispatch(new Event\HttpEnd($rocket));
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected static function verifyPlugin(array $plugins): void
+ {
+ foreach ($plugins as $plugin) {
+ if (is_callable($plugin)) {
+ continue;
+ }
+
+ if ((is_object($plugin)
+ || (is_string($plugin) && class_exists($plugin)))
+ && in_array(PluginInterface::class, class_implements($plugin))) {
+ continue;
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_INCOMPATIBLE, "参数异常: [{$plugin}] 插件未实现 `PluginInterface`");
+ }
+ }
+
+ /**
+ * @throws ContainerException
+ */
+ private function registerServices(array $config, null|Closure|ContainerInterface $container = null): void
+ {
+ foreach ($this->coreService as $service) {
+ self::registerService($service, ContainerServiceProvider::class == $service ? $container : $config);
+ }
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Contract/ConfigInterface.php b/addons/epay/library/v3new/Yansongda/Artful/Contract/ConfigInterface.php
new file mode 100644
index 0000000..80916d5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Contract/ConfigInterface.php
@@ -0,0 +1,14 @@
+getBody();
+
+ if (!is_null($result = $packer->unpack($body, $params))) {
+ return new Collection($result);
+ }
+
+ throw new InvalidResponseException(Exception::RESPONSE_UNPACK_ERROR, '响应异常: 解包错误', ['body' => $body, 'response' => $response]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Direction/NoHttpRequestDirection.php b/addons/epay/library/v3new/Yansongda/Artful/Direction/NoHttpRequestDirection.php
new file mode 100644
index 0000000..8201c90
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Direction/NoHttpRequestDirection.php
@@ -0,0 +1,17 @@
+{$method}(...$args);
+
+ return;
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_EVENT_DRIVER_INVALID, '参数异常: 配置的 `EventDispatcherInterface` 不符合 PSR 规范');
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Event/ArtfulEnd.php b/addons/epay/library/v3new/Yansongda/Artful/Event/ArtfulEnd.php
new file mode 100644
index 0000000..b59ca80
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Event/ArtfulEnd.php
@@ -0,0 +1,7 @@
+extra = $extra;
+
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Exception/InvalidConfigException.php b/addons/epay/library/v3new/Yansongda/Artful/Exception/InvalidConfigException.php
new file mode 100644
index 0000000..838c558
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Exception/InvalidConfigException.php
@@ -0,0 +1,18 @@
+response = $extra;
+
+ parent::__construct($message, $code, $extra, $previous);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Exception/ServiceNotFoundException.php b/addons/epay/library/v3new/Yansongda/Artful/Exception/ServiceNotFoundException.php
new file mode 100644
index 0000000..20824d0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Exception/ServiceNotFoundException.php
@@ -0,0 +1,16 @@
+filter(static fn ($v, $k) => !Str::startsWith($k, '_') && !is_null($v) && (empty($closure) || $closure($k, $v)));
+}
+
+function get_radar_method(?Collection $payload): ?string
+{
+ $string = $payload?->get('_method') ?? null;
+
+ if (is_null($string)) {
+ return null;
+ }
+
+ return strtoupper($string);
+}
+
+function get_radar_url(?Collection $payload): ?string
+{
+ return $payload?->get('_url') ?? null;
+}
+
+function get_radar_body(?Collection $payload): mixed
+{
+ return $payload?->get('_body') ?? null;
+}
+
+function get_radar_headers(?Collection $payload): mixed
+{
+ return $payload?->get('_headers') ?? null;
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/HttpClientFactory.php b/addons/epay/library/v3new/Yansongda/Artful/HttpClientFactory.php
new file mode 100644
index 0000000..c162f6e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/HttpClientFactory.php
@@ -0,0 +1,39 @@
+container->has(HttpClientInterface::class)) {
+ if (($http = $this->container->get(HttpClientInterface::class)) instanceof ClientInterface) {
+ return $http;
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_HTTP_CLIENT_INVALID, '参数异常: `HttpClient` 不符合 PSR 规范');
+ }
+
+ return new Client($options);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Logger.php b/addons/epay/library/v3new/Yansongda/Artful/Logger.php
new file mode 100644
index 0000000..2b3c6dd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Logger.php
@@ -0,0 +1,49 @@
+get('logger.enable', false)) {
+ return;
+ }
+
+ $class = Artful::get(LoggerInterface::class);
+
+ if ($class instanceof \Psr\Log\LoggerInterface) {
+ $class->{$method}(...$args);
+
+ return;
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_LOGGER_DRIVER_INVALID, '配置异常: 配置的 `LoggerInterface` 不符合 PSR 规范');
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Packer/JsonPacker.php b/addons/epay/library/v3new/Yansongda/Artful/Packer/JsonPacker.php
new file mode 100644
index 0000000..39ed979
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Packer/JsonPacker.php
@@ -0,0 +1,26 @@
+isEmpty()) || empty($payload)) {
+ return '';
+ }
+
+ return Collection::wrap($payload)->toJson();
+ }
+
+ public function unpack(string $payload, ?array $params = null): ?array
+ {
+ return Arr::wrapJson($payload);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Packer/QueryPacker.php b/addons/epay/library/v3new/Yansongda/Artful/Packer/QueryPacker.php
new file mode 100644
index 0000000..607049d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Packer/QueryPacker.php
@@ -0,0 +1,22 @@
+query();
+ }
+
+ public function unpack(string $payload, ?array $params = null): array
+ {
+ return Arr::wrapQuery($payload, !empty($params['_unpack_raw']));
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Packer/XmlPacker.php b/addons/epay/library/v3new/Yansongda/Artful/Packer/XmlPacker.php
new file mode 100644
index 0000000..2aeaea9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Packer/XmlPacker.php
@@ -0,0 +1,22 @@
+toXml();
+ }
+
+ public function unpack(string $payload, ?array $params = null): array
+ {
+ return Arr::wrapXml($payload);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Plugin/AddPayloadBodyPlugin.php b/addons/epay/library/v3new/Yansongda/Artful/Plugin/AddPayloadBodyPlugin.php
new file mode 100644
index 0000000..3affeae
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Plugin/AddPayloadBodyPlugin.php
@@ -0,0 +1,33 @@
+ $rocket]);
+
+ $packer = get_packer($rocket->getPacker());
+
+ $rocket->mergePayload(['_body' => $packer->pack(filter_params($rocket->getPayload()))]);
+
+ Logger::info('[AddPayloadBodyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Plugin/AddRadarPlugin.php b/addons/epay/library/v3new/Yansongda/Artful/Plugin/AddRadarPlugin.php
new file mode 100644
index 0000000..f842f4d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Plugin/AddRadarPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ $rocket->setRadar(new Request(
+ get_radar_method($payload) ?? 'POST',
+ get_radar_url($payload),
+ get_radar_headers($payload) ?? $this->getHeaders(),
+ get_radar_body($payload),
+ ));
+
+ Logger::info('[AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function getHeaders(): array
+ {
+ return [
+ 'User-Agent' => 'yansongda/artful-v1',
+ 'Content-Type' => 'application/json;charset=utf-8',
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Plugin/ParserPlugin.php b/addons/epay/library/v3new/Yansongda/Artful/Plugin/ParserPlugin.php
new file mode 100644
index 0000000..daa9701
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Plugin/ParserPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $response = $rocket->getDestination();
+ $direction = get_direction($rocket->getDirection());
+ $packer = get_packer($rocket->getPacker());
+ $payload = $rocket->getPayload();
+
+ if (!is_null($response) && !($response instanceof ResponseInterface)) {
+ throw new InvalidParamsException(Exception::PARAMS_PARSER_DIRECTION_INVALID, '参数异常: 解析插件中 `Rocket` 的 `destination` 只能是 `null` 或者 `ResponseInterface`');
+ }
+
+ $rocket->setDestination($direction->guide($packer, $response, $payload?->all() ?? []));
+
+ Logger::debug('[ParserPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Plugin/StartPlugin.php b/addons/epay/library/v3new/Yansongda/Artful/Plugin/StartPlugin.php
new file mode 100644
index 0000000..f32f2ee
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Plugin/StartPlugin.php
@@ -0,0 +1,24 @@
+ $rocket]);
+
+ $rocket->mergePayload($rocket->getParams());
+
+ Logger::info('[StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Rocket.php b/addons/epay/library/v3new/Yansongda/Artful/Rocket.php
new file mode 100644
index 0000000..97b39fd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Rocket.php
@@ -0,0 +1,180 @@
+radar;
+ }
+
+ public function setRadar(?RequestInterface $radar): Rocket
+ {
+ $this->radar = $radar;
+
+ return $this;
+ }
+
+ public function getParams(): array
+ {
+ return $this->params;
+ }
+
+ public function setParams(array $params): Rocket
+ {
+ $this->params = $params;
+
+ return $this;
+ }
+
+ public function mergeParams(array $params): Rocket
+ {
+ $this->params = array_merge($this->params, $params);
+
+ return $this;
+ }
+
+ public function getPayload(): ?Collection
+ {
+ return $this->payload;
+ }
+
+ public function setPayload(null|array|Collection $payload): Rocket
+ {
+ if (is_array($payload)) {
+ $payload = new Collection($payload);
+ }
+
+ $this->payload = $payload;
+
+ return $this;
+ }
+
+ public function mergePayload(array $payload): Rocket
+ {
+ if (empty($this->payload)) {
+ $this->payload = new Collection();
+ }
+
+ $this->payload = $this->payload->merge($payload);
+
+ return $this;
+ }
+
+ public function exceptPayload(mixed $key): Rocket
+ {
+ if (empty($this->payload)) {
+ return $this;
+ }
+
+ $this->payload = $this->payload->except($key);
+
+ return $this;
+ }
+
+ public function getPacker(): string
+ {
+ return $this->packer;
+ }
+
+ public function setPacker(string $packer): Rocket
+ {
+ $this->packer = $packer;
+
+ return $this;
+ }
+
+ public function getDirection(): string
+ {
+ return $this->direction;
+ }
+
+ public function setDirection(string $direction): Rocket
+ {
+ $this->direction = $direction;
+
+ return $this;
+ }
+
+ public function getDestination(): null|Collection|MessageInterface
+ {
+ return $this->destination;
+ }
+
+ public function setDestination(null|Collection|MessageInterface $destination): Rocket
+ {
+ $this->destination = $destination;
+
+ return $this;
+ }
+
+ public function getDestinationOrigin(): null|RequestInterface|ResponseInterface
+ {
+ return $this->destinationOrigin;
+ }
+
+ public function setDestinationOrigin(null|RequestInterface|ResponseInterface $destinationOrigin): Rocket
+ {
+ $this->destinationOrigin = $destinationOrigin;
+
+ return $this;
+ }
+
+ public function toArray(): array
+ {
+ $request = $this->getRadar();
+ $destination = $this->getDestinationOrigin();
+
+ return [
+ 'radar' => [
+ 'url' => $request?->getUri()->__toString(),
+ 'method' => $request?->getMethod(),
+ 'headers' => $request?->getHeaders(),
+ 'body' => (string) $request?->getBody(),
+ ],
+ 'params' => $this->getParams(),
+ 'payload' => $this->getPayload()?->toArray(),
+ 'packer' => $this->getPacker(),
+ 'direction' => $this->getDirection(),
+ 'destination' => $this->getDestination(),
+ 'destination_origin' => [
+ 'status' => $destination instanceof ResponseInterface ? $destination->getStatusCode() : null,
+ 'headers' => $destination?->getHeaders(),
+ 'body' => (string) $destination?->getBody(),
+ ],
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Service/ConfigServiceProvider.php b/addons/epay/library/v3new/Yansongda/Artful/Service/ConfigServiceProvider.php
new file mode 100644
index 0000000..3a6ff5e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Service/ConfigServiceProvider.php
@@ -0,0 +1,42 @@
+ [
+ 'enable' => false,
+ 'file' => null,
+ 'identify' => 'yansongda.artful',
+ 'level' => 'debug',
+ 'type' => 'daily',
+ 'max_files' => 30,
+ ],
+ 'http' => [
+ 'timeout' => 5.0,
+ 'connect_timeout' => 3.0,
+ 'headers' => [
+ 'User-Agent' => 'yansongda/artful-v1',
+ ],
+ ],
+ ];
+
+ /**
+ * @throws ContainerException
+ */
+ public function register(mixed $data = null): void
+ {
+ $config = new Config(array_replace_recursive($this->config, $data ?? []));
+
+ Artful::set(ConfigInterface::class, $config);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Service/ContainerServiceProvider.php b/addons/epay/library/v3new/Yansongda/Artful/Service/ContainerServiceProvider.php
new file mode 100644
index 0000000..5083298
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Service/ContainerServiceProvider.php
@@ -0,0 +1,108 @@
+ LaravelContainer::class,
+ 'hyperf' => HyperfContainer::class,
+ ];
+
+ /**
+ * @throws ContainerException
+ */
+ public function register(mixed $data = null): void
+ {
+ if ($data instanceof ContainerInterface || $data instanceof Closure) {
+ Artful::setContainer($data);
+
+ return;
+ }
+
+ if (Artful::hasContainer()) {
+ return;
+ }
+
+ foreach ($this->detectApplication as $framework => $application) {
+ $method = $framework.'Application';
+
+ if (class_exists($application) && method_exists($this, $method) && $this->{$method}()) {
+ return;
+ }
+ }
+
+ $this->defaultApplication();
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ContainerNotFoundException
+ */
+ protected function laravelApplication(): bool
+ {
+ Artful::setContainer(static fn () => LaravelContainer::getInstance());
+
+ Artful::set(\Yansongda\Artful\Contract\ContainerInterface::class, LaravelContainer::getInstance());
+
+ if (!Artful::has(ContainerInterface::class)) {
+ Artful::set(ContainerInterface::class, LaravelContainer::getInstance());
+ }
+
+ return true;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ContainerNotFoundException
+ */
+ protected function hyperfApplication(): bool
+ {
+ if (!HyperfContainer::hasContainer()) {
+ return false;
+ }
+
+ Artful::setContainer(static fn () => HyperfContainer::getContainer());
+
+ Artful::set(\Yansongda\Artful\Contract\ContainerInterface::class, HyperfContainer::getContainer());
+
+ if (!Artful::has(ContainerInterface::class)) {
+ Artful::set(ContainerInterface::class, HyperfContainer::getContainer());
+ }
+
+ return true;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ContainerNotFoundException
+ */
+ protected function defaultApplication(): void
+ {
+ if (!class_exists(DefaultContainer::class)) {
+ throw new ContainerNotFoundException('容器未找到: Init failed! Maybe you should install `hyperf/pimple` first', Exception::CONTAINER_NOT_FOUND);
+ }
+
+ $container = (new DefaultContainer())();
+
+ Artful::setContainer($container);
+
+ Artful::set(\Yansongda\Artful\Contract\ContainerInterface::class, $container);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Artful/Service/EventServiceProvider.php b/addons/epay/library/v3new/Yansongda/Artful/Service/EventServiceProvider.php
new file mode 100644
index 0000000..de89a57
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Artful/Service/EventServiceProvider.php
@@ -0,0 +1,24 @@
+ false,
+ 'file' => null,
+ 'identify' => 'yansongda.artful',
+ 'level' => 'DEBUG',
+ 'type' => 'daily',
+ 'max_files' => 30,
+ 'formatter' => "%datetime% > %channel%.%level_name% > %message% %context% %extra%\n\n",
+ ];
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ public function register(mixed $data = null): void
+ {
+ /* @var ConfigInterface $config */
+ $config = Artful::get(ConfigInterface::class);
+
+ $this->config = array_merge($this->config, $config->get('logger', []));
+
+ if (class_exists(Logger::class) && (true === ($this->config['enable'] ?? false))) {
+ $logger = new Logger($this->config['identify']);
+
+ $logger->pushHandler($this->getDefaultHandler()
+ ->setFormatter($this->getDefaultFormatter()));
+
+ Artful::set(LoggerInterface::class, $logger);
+ }
+ }
+
+ protected function getDefaultFormatter(): LineFormatter
+ {
+ return new LineFormatter(
+ $this->config['formatter'],
+ null,
+ false,
+ true
+ );
+ }
+
+ protected function getDefaultHandler(): AbstractProcessingHandler
+ {
+ $file = $this->config['file'] ?? (sys_get_temp_dir().'/logs/'.$this->config['identify'].'.log');
+
+ return match ($this->config['type']) {
+ 'single' => new StreamHandler($file, $this->config['level']),
+ default => new RotatingFileHandler($file, $this->config['max_files'], $this->config['level']),
+ };
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Contract/ProviderInterface.php b/addons/epay/library/v3new/Yansongda/Pay/Contract/ProviderInterface.php
new file mode 100644
index 0000000..018d715
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Contract/ProviderInterface.php
@@ -0,0 +1,28 @@
+provider = $provider;
+ $this->contents = $contents;
+ $this->params = $params;
+
+ parent::__construct($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Event/MethodCalled.php b/addons/epay/library/v3new/Yansongda/Pay/Event/MethodCalled.php
new file mode 100644
index 0000000..2836f27
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Event/MethodCalled.php
@@ -0,0 +1,26 @@
+provider = $provider;
+ $this->name = $name;
+ $this->params = $params;
+
+ parent::__construct($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Event/PayEnd.php b/addons/epay/library/v3new/Yansongda/Pay/Event/PayEnd.php
new file mode 100644
index 0000000..682a2c6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Event/PayEnd.php
@@ -0,0 +1,9 @@
+plugins = $plugins;
+ $this->params = $params;
+
+ parent::__construct($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Exception/DecryptException.php b/addons/epay/library/v3new/Yansongda/Pay/Exception/DecryptException.php
new file mode 100644
index 0000000..f2a869d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Exception/DecryptException.php
@@ -0,0 +1,19 @@
+extra = $extra;
+
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Exception/InvalidSignException.php b/addons/epay/library/v3new/Yansongda/Pay/Exception/InvalidSignException.php
new file mode 100644
index 0000000..d75b7ca
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Exception/InvalidSignException.php
@@ -0,0 +1,23 @@
+callback = $extra;
+
+ parent::__construct($message, $code, $extra, $previous);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Functions.php b/addons/epay/library/v3new/Yansongda/Pay/Functions.php
new file mode 100644
index 0000000..b9a435d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Functions.php
@@ -0,0 +1,656 @@
+ $payload?->get('_service_url') ?? $payload?->get('_url') ?? null,
+ Pay::MODE_SANDBOX => $payload?->get('_sandbox_url') ?? $payload?->get('_url') ?? null,
+ default => $payload?->get('_url') ?? null,
+ };
+}
+
+/**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+function get_provider_config(string $provider, array $params = []): array
+{
+ /** @var ConfigInterface $config */
+ $config = Pay::get(ConfigInterface::class);
+
+ return $config->get($provider, [])[get_tenant($params)] ?? [];
+}
+
+/**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
+function get_alipay_config(array $params = []): array
+{
+ $alipay = Pay::get(ConfigInterface::class)->get('alipay');
+
+ return $alipay[get_tenant($params)] ?? [];
+}
+
+function get_alipay_url(array $config, ?Collection $payload): string
+{
+ $url = get_radar_url($config, $payload) ?? '';
+
+ if (str_starts_with($url, 'http')) {
+ return $url;
+ }
+
+ return Alipay::URL[$config['mode'] ?? Pay::MODE_NORMAL];
+}
+
+/**
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+function verify_alipay_sign(array $config, string $contents, string $sign): void
+{
+ if (empty($sign)) {
+ throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证支付宝签名失败-支付宝签名为空', func_get_args());
+ }
+
+ $public = $config['alipay_public_cert_path'] ?? null;
+
+ if (empty($public)) {
+ throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [alipay_public_cert_path]');
+ }
+
+ $result = 1 === openssl_verify(
+ $contents,
+ base64_decode($sign),
+ get_public_cert($public),
+ OPENSSL_ALGO_SHA256
+ );
+
+ if (!$result) {
+ throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证支付宝签名失败', func_get_args());
+ }
+}
+
+/**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
+function get_wechat_config(array $params = []): array
+{
+ $wechat = Pay::get(ConfigInterface::class)->get('wechat');
+
+ return $wechat[get_tenant($params)] ?? [];
+}
+
+function get_wechat_method(?Collection $payload): string
+{
+ return get_radar_method($payload) ?? 'POST';
+}
+
+/**
+ * @throws InvalidParamsException
+ */
+function get_wechat_url(array $config, ?Collection $payload): string
+{
+ $url = get_radar_url($config, $payload);
+
+ if (empty($url)) {
+ throw new InvalidParamsException(Exception::PARAMS_WECHAT_URL_MISSING, '参数异常: 微信 `_url` 或 `_service_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
+ }
+
+ if (str_starts_with($url, 'http')) {
+ return $url;
+ }
+
+ return Wechat::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
+}
+
+/**
+ * @throws InvalidParamsException
+ */
+function get_wechat_body(?Collection $payload): mixed
+{
+ $body = get_radar_body($payload);
+
+ if (is_null($body)) {
+ throw new InvalidParamsException(Exception::PARAMS_WECHAT_BODY_MISSING, '参数异常: 微信 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`');
+ }
+
+ return $body;
+}
+
+function get_wechat_type_key(array $params): string
+{
+ $key = ($params['_type'] ?? 'mp').'_app_id';
+
+ if ('app_app_id' === $key) {
+ $key = 'app_id';
+ }
+
+ return $key;
+}
+
+/**
+ * @throws InvalidConfigException
+ */
+function get_wechat_sign(array $config, string $contents): string
+{
+ $privateKey = $config['mch_secret_cert'] ?? null;
+
+ if (empty($privateKey)) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_cert]');
+ }
+
+ $privateKey = get_private_cert($privateKey);
+
+ openssl_sign($contents, $sign, $privateKey, 'sha256WithRSAEncryption');
+
+ return base64_encode($sign);
+}
+
+/**
+ * @throws InvalidConfigException
+ */
+function get_wechat_sign_v2(array $config, array $payload, bool $upper = true): string
+{
+ $key = $config['mch_secret_key_v2'] ?? null;
+
+ if (empty($key)) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]');
+ }
+
+ ksort($payload);
+
+ $buff = '';
+
+ foreach ($payload as $k => $v) {
+ $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
+ }
+
+ $sign = md5($buff.'key='.$key);
+
+ return $upper ? strtoupper($sign) : $sign;
+}
+
+/**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws InvalidSignException
+ * @throws ServiceNotFoundException
+ */
+function verify_wechat_sign(ResponseInterface|ServerRequestInterface $message, array $params): void
+{
+ if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {
+ return;
+ }
+
+ $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
+ $timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
+ $random = $message->getHeaderLine('Wechatpay-Nonce');
+ $sign = $message->getHeaderLine('Wechatpay-Signature');
+ $body = (string) $message->getBody();
+
+ $content = $timestamp."\n".$random."\n".$body."\n";
+ $public = get_provider_config('wechat', $params)['wechat_public_cert_path'][$wechatSerial] ?? null;
+
+ if (empty($sign)) {
+ throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', ['headers' => $message->getHeaders(), 'body' => $body]);
+ }
+
+ $public = get_public_cert(
+ empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public
+ );
+
+ $result = 1 === openssl_verify(
+ $content,
+ base64_decode($sign),
+ $public,
+ 'sha256WithRSAEncryption'
+ );
+
+ if (!$result) {
+ throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', ['headers' => $message->getHeaders(), 'body' => $body]);
+ }
+}
+
+/**
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+function verify_wechat_sign_v2(array $config, array $destination): void
+{
+ $sign = $destination['sign'] ?? null;
+
+ if (empty($sign)) {
+ throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', $destination);
+ }
+
+ $key = $config['mch_secret_key_v2'] ?? null;
+
+ if (empty($key)) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]');
+ }
+
+ if (get_wechat_sign_v2($config, $destination) !== $sign) {
+ throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', $destination);
+ }
+}
+
+function encrypt_wechat_contents(string $contents, string $publicKey): ?string
+{
+ if (openssl_public_encrypt($contents, $encrypted, get_public_cert($publicKey), OPENSSL_PKCS1_OAEP_PADDING)) {
+ return base64_encode($encrypted);
+ }
+
+ return null;
+}
+
+function decrypt_wechat_contents(string $encrypted, array $config): ?string
+{
+ if (openssl_private_decrypt(base64_decode($encrypted), $decrypted, get_private_cert($config['mch_secret_cert'] ?? ''), OPENSSL_PKCS1_OAEP_PADDING)) {
+ return $decrypted;
+ }
+
+ return null;
+}
+
+/**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+function reload_wechat_public_certs(array $params, ?string $serialNo = null): string
+{
+ $data = Pay::wechat()->pay(
+ [StartPlugin::class, WechatPublicCertsPlugin::class, AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class],
+ $params
+ )->get('data', []);
+
+ $wechatConfig = get_provider_config('wechat', $params);
+
+ foreach ($data as $item) {
+ $certs[$item['serial_no']] = decrypt_wechat_resource($item['encrypt_certificate'], $wechatConfig)['ciphertext'] ?? '';
+ }
+
+ Pay::get(ConfigInterface::class)->set(
+ 'wechat.'.get_tenant($params).'.wechat_public_cert_path',
+ ((array) ($wechatConfig['wechat_public_cert_path'] ?? [])) + ($certs ?? []),
+ );
+
+ if (!is_null($serialNo) && empty($certs[$serialNo])) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 获取微信 wechat_public_cert_path 配置失败');
+ }
+
+ return $certs[$serialNo] ?? '';
+}
+
+/**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+function get_wechat_public_certs(array $params = [], ?string $path = null): void
+{
+ reload_wechat_public_certs($params);
+
+ $config = get_provider_config('wechat', $params);
+
+ if (empty($path)) {
+ var_dump($config['wechat_public_cert_path']);
+
+ return;
+ }
+
+ foreach ($config['wechat_public_cert_path'] as $serialNo => $cert) {
+ file_put_contents($path.'/'.$serialNo.'.crt', $cert);
+ }
+}
+
+/**
+ * @throws InvalidConfigException
+ * @throws DecryptException
+ */
+function decrypt_wechat_resource(array $resource, array $config): array
+{
+ $ciphertext = base64_decode($resource['ciphertext'] ?? '');
+ $secret = $config['mch_secret_key'] ?? null;
+
+ if (strlen($ciphertext) <= Wechat::AUTH_TAG_LENGTH_BYTE) {
+ throw new DecryptException(Exception::DECRYPT_WECHAT_CIPHERTEXT_PARAMS_INVALID, '加解密异常: ciphertext 位数过短');
+ }
+
+ if (is_null($secret) || Wechat::MCH_SECRET_KEY_LENGTH_BYTE != strlen($secret)) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key]');
+ }
+
+ $resource['ciphertext'] = match ($resource['algorithm'] ?? '') {
+ 'AEAD_AES_256_GCM' => decrypt_wechat_resource_aes_256_gcm($ciphertext, $secret, $resource['nonce'] ?? '', $resource['associated_data'] ?? ''),
+ default => throw new DecryptException(Exception::DECRYPT_WECHAT_DECRYPTED_METHOD_INVALID, '加解密异常: algorithm 不支持'),
+ };
+
+ return $resource;
+}
+
+/**
+ * @throws DecryptException
+ */
+function decrypt_wechat_resource_aes_256_gcm(string $ciphertext, string $secret, string $nonce, string $associatedData): array|string
+{
+ $decrypted = openssl_decrypt(
+ substr($ciphertext, 0, -Wechat::AUTH_TAG_LENGTH_BYTE),
+ 'aes-256-gcm',
+ $secret,
+ OPENSSL_RAW_DATA,
+ $nonce,
+ substr($ciphertext, -Wechat::AUTH_TAG_LENGTH_BYTE),
+ $associatedData
+ );
+
+ if (false === $decrypted) {
+ throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 解密失败,请检查微信 mch_secret_key 是否正确');
+ }
+
+ if ('certificate' !== $associatedData) {
+ $decrypted = json_decode($decrypted, true);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 待解密数据非正常数据');
+ }
+ }
+
+ return $decrypted;
+}
+
+/**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+function get_wechat_serial_no(array $params): string
+{
+ if (!empty($params['_serial_no'])) {
+ return $params['_serial_no'];
+ }
+
+ $config = get_provider_config('wechat', $params);
+
+ if (empty($config['wechat_public_cert_path'])) {
+ reload_wechat_public_certs($params);
+
+ $config = get_provider_config('wechat', $params);
+ }
+
+ mt_srand();
+
+ return strval(array_rand($config['wechat_public_cert_path']));
+}
+
+/**
+ * @throws InvalidParamsException
+ */
+function get_wechat_public_key(array $config, string $serialNo): string
+{
+ $publicKey = $config['wechat_public_cert_path'][$serialNo] ?? null;
+
+ if (empty($publicKey)) {
+ throw new InvalidParamsException(Exception::PARAMS_WECHAT_SERIAL_NOT_FOUND, '参数异常: 微信公钥序列号未找到 - '.$serialNo);
+ }
+
+ return $publicKey;
+}
+
+/**
+ * @throws InvalidConfigException
+ */
+function get_wechat_miniprogram_pay_sign(array $config, string $url, string $payload): string
+{
+ if (empty($config['mini_app_key_virtual_pay'])) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mini_app_key_virtual_pay]');
+ }
+
+ return hash_hmac('sha256', $url.'&'.$payload, $config['mini_app_key_virtual_pay']);
+}
+
+function get_wechat_miniprogram_user_sign(string $sessionKey, string $payload): string
+{
+ return hash_hmac('sha256', $payload, $sessionKey);
+}
+
+/**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')]
+function get_unipay_config(array $params = []): array
+{
+ $unipay = Pay::get(ConfigInterface::class)->get('unipay');
+
+ return $unipay[get_tenant($params)] ?? [];
+}
+
+/**
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+function verify_unipay_sign(array $config, string $contents, string $sign, ?string $signPublicKeyCert = null): void
+{
+ if (empty($sign)) {
+ throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', func_get_args());
+ }
+
+ if (empty($signPublicKeyCert) && empty($public = $config['unipay_public_cert_path'] ?? null)) {
+ throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [unipay_public_cert_path]');
+ }
+
+ $result = 1 === openssl_verify(
+ hash('sha256', $contents),
+ base64_decode($sign),
+ get_public_cert($signPublicKeyCert ?? $public ?? ''),
+ 'sha256'
+ );
+
+ if (!$result) {
+ throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', func_get_args());
+ }
+}
+
+/**
+ * @throws InvalidParamsException
+ */
+function get_unipay_url(array $config, ?Collection $payload): string
+{
+ $url = get_radar_url($config, $payload);
+
+ if (empty($url)) {
+ throw new InvalidParamsException(Exception::PARAMS_UNIPAY_URL_MISSING, '参数异常: 银联 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
+ }
+
+ if (str_starts_with($url, 'http')) {
+ return $url;
+ }
+
+ return Unipay::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
+}
+
+/**
+ * @throws InvalidParamsException
+ */
+function get_unipay_body(?Collection $payload): string
+{
+ $body = get_radar_body($payload);
+
+ if (is_null($body)) {
+ throw new InvalidParamsException(Exception::PARAMS_UNIPAY_BODY_MISSING, '参数异常: 银联 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`');
+ }
+
+ return $body;
+}
+
+/**
+ * @throws InvalidConfigException
+ */
+function get_unipay_sign_qra(array $config, array $payload): string
+{
+ $key = $config['mch_secret_key'] ?? null;
+
+ if (empty($key)) {
+ throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]');
+ }
+
+ ksort($payload);
+
+ $buff = '';
+
+ foreach ($payload as $k => $v) {
+ $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
+ }
+
+ return strtoupper(md5($buff.'key='.$key));
+}
+
+/**
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+function verify_unipay_sign_qra(array $config, array $destination): void
+{
+ $sign = $destination['sign'] ?? null;
+
+ if (empty($sign)) {
+ throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', $destination);
+ }
+
+ $key = $config['mch_secret_key'] ?? null;
+
+ if (empty($key)) {
+ throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]');
+ }
+
+ if (get_unipay_sign_qra($config, $destination) !== $sign) {
+ throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', $destination);
+ }
+}
+
+function get_jsb_url(array $config, ?Collection $payload): string
+{
+ $url = get_radar_url($config, $payload) ?? '';
+
+ if (str_starts_with($url, 'http')) {
+ return $url;
+ }
+
+ return Jsb::URL[$config['mode'] ?? Pay::MODE_NORMAL];
+}
+
+/**
+ * @throws InvalidConfigException
+ * @throws InvalidSignException
+ */
+function verify_jsb_sign(array $config, string $content, string $sign): void
+{
+ if (empty($sign)) {
+ throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 江苏银行签名为空', func_get_args());
+ }
+
+ $publicCert = $config['jsb_public_cert_path'] ?? null;
+
+ if (empty($publicCert)) {
+ throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [jsb_public_cert_path]');
+ }
+
+ $result = 1 === openssl_verify(
+ $content,
+ base64_decode($sign),
+ get_public_cert($publicCert)
+ );
+
+ if (!$result) {
+ 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/addons/epay/library/v3new/Yansongda/Pay/Pay.php b/addons/epay/library/v3new/Yansongda/Pay/Pay.php
new file mode 100644
index 0000000..d10beba
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Pay.php
@@ -0,0 +1,108 @@
+ $rocket]);
+
+ $rocket->mergePayload(['sign' => $this->getSign($rocket)]);
+
+ Logger::info('[Alipay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getSign(Rocket $rocket): string
+ {
+ $privateKey = $this->getPrivateKey($rocket->getParams());
+
+ $content = $rocket->getPayload()->sortKeys()->toString();
+
+ openssl_sign($content, $sign, $privateKey, OPENSSL_ALGO_SHA256);
+
+ return base64_encode($sign);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getPrivateKey(array $params): string
+ {
+ $privateKey = get_provider_config('alipay', $params)['app_secret_cert'] ?? null;
+
+ if (is_null($privateKey)) {
+ throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [app_secret_cert]');
+ }
+
+ return get_private_cert($privateKey);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AddRadarPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AddRadarPlugin.php
new file mode 100644
index 0000000..cc99de1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AddRadarPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('alipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setRadar(new Request(
+ // 这里因为支付宝的 payload 里不包含 _method,所以需要取 params 中的
+ get_radar_method(new Collection($params)) ?? 'POST',
+ get_alipay_url($config, $payload),
+ $this->getHeaders(),
+ // 不能用 packer,支付宝接收的是 x-www-form-urlencoded 返回的又是 json,packer 用的是返回.
+ $payload?->query() ?? '',
+ ));
+
+ Logger::info('[Alipay][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function getHeaders(): array
+ {
+ return [
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'yansongda/pay-v3',
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AppCallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AppCallbackPlugin.php
new file mode 100644
index 0000000..ab9129a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/AppCallbackPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('alipay', $params);
+
+ if (empty($params['alipay_trade_app_pay_response'])) {
+ throw new InvalidParamsException(Exception::PARAMS_CALLBACK_REQUEST_INVALID);
+ }
+
+ $value = filter_params($params['alipay_trade_app_pay_response']);
+
+ verify_alipay_sign($config, $value->toJson(), $params['sign'] ?? '');
+
+ $rocket->setPayload($params)
+ ->setDirection(NoHttpRequestDirection::class)
+ ->setDestination($rocket->getPayload());
+
+ Logger::info('[Alipay][AppCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/CallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/CallbackPlugin.php
new file mode 100644
index 0000000..cb6c850
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/CallbackPlugin.php
@@ -0,0 +1,48 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('alipay', $params);
+
+ $value = filter_params($params, fn ($k, $v) => '' !== $v && 'sign' != $k && 'sign_type' != $k);
+
+ verify_alipay_sign($config, $value->sortKeys()->toString(), $params['sign'] ?? '');
+
+ $rocket->setPayload($params)
+ ->setDirection(NoHttpRequestDirection::class)
+ ->setDestination($rocket->getPayload());
+
+ Logger::info('[Alipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/FormatPayloadBizContentPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/FormatPayloadBizContentPlugin.php
new file mode 100644
index 0000000..4c637aa
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/FormatPayloadBizContentPlugin.php
@@ -0,0 +1,33 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ $rocket->setPayload(
+ filter_params($payload->all(), fn ($k, $v) => '' !== $v && 'sign' != $k)
+ ->merge([
+ 'biz_content' => json_encode(filter_params($payload->get('biz_content', []))),
+ ])
+ );
+
+ Logger::info('[Alipay][FormatPayloadBizContentPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/AppPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/AppPayPlugin.php
new file mode 100644
index 0000000..9fbbc62
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/AppPayPlugin.php
@@ -0,0 +1,32 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.app.pay',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][PCreditPayInstallment][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/H5PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/H5PayPlugin.php
new file mode 100644
index 0000000..d468db4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/H5PayPlugin.php
@@ -0,0 +1,48 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.wap.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'QUICK_WAP_WAY',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Fund][PCreditPayInstallment][H5PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/PosPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/PosPayPlugin.php
new file mode 100644
index 0000000..e18f134
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/PosPayPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'FACE_TO_FACE_PAYMENT',
+ 'scene' => 'bar_code',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Fund][PCreditPayInstallment][PosPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/ScanPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/ScanPayPlugin.php
new file mode 100644
index 0000000..35c7d11
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/PCreditPayInstallment/ScanPayPlugin.php
@@ -0,0 +1,41 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.precreate',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][PCreditPayInstallment][ScanPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/OnsettlePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/OnsettlePlugin.php
new file mode 100644
index 0000000..2926941
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/OnsettlePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.order.onsettle.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Royalty][Query][OnsettlePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/RatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/RatePlugin.php
new file mode 100644
index 0000000..aeea5e8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/RatePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.royalty.rate.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Royalty][Query][RatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/SettlePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/SettlePlugin.php
new file mode 100644
index 0000000..94c8c7e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Query/SettlePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.order.settle.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Royalty][Query][SettlePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/BindPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/BindPlugin.php
new file mode 100644
index 0000000..2498da6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/BindPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.royalty.relation.bind',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Royalty][Relation][BindPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/QueryPlugin.php
new file mode 100644
index 0000000..3af441e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.royalty.relation.batchquery',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Royalty][Relation][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/UnbindPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/UnbindPlugin.php
new file mode 100644
index 0000000..376fc62
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Relation/UnbindPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.royalty.relation.unbind',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Royalty][Relation][UnbindPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Request/SettleOrderPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Request/SettleOrderPlugin.php
new file mode 100644
index 0000000..d01b340
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Royalty/Request/SettleOrderPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.order.settle',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Royalty][Request][SettleOrderPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/ApplyReceiptPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/ApplyReceiptPlugin.php
new file mode 100644
index 0000000..83a650f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/ApplyReceiptPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.bill.ereceipt.apply',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Transfer][ApplyReceiptPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Bill/QueryUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Bill/QueryUrlPlugin.php
new file mode 100644
index 0000000..8cb1edd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Bill/QueryUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Transfer][Bill][QueryUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/QueryPlugin.php
new file mode 100644
index 0000000..c030417
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.trans.common.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Transfer][Fund][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/TransferPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/TransferPlugin.php
new file mode 100644
index 0000000..d7a4033
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/Fund/TransferPlugin.php
@@ -0,0 +1,37 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.trans.uni.transfer',
+ 'biz_content' => array_merge(
+ [
+ 'biz_scene' => 'DIRECT_TRANSFER',
+ 'product_code' => 'TRANS_ACCOUNT_NO_PWD',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Fund][Transfer][Fund][TransferPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryAccountPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryAccountPlugin.php
new file mode 100644
index 0000000..4297e88
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryAccountPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.account.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Transfer][QueryAccountPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryReceiptPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryReceiptPlugin.php
new file mode 100644
index 0000000..5efdcfd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/QueryReceiptPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.bill.ereceipt.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Transfer][QueryReceiptPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/RefundPlugin.php
new file mode 100644
index 0000000..f7e41ea
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Fund/Transfer/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.trans.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Fund][Transfer][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/AppPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/AppPayPlugin.php
new file mode 100644
index 0000000..2df8f13
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/AppPayPlugin.php
@@ -0,0 +1,38 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.fund.trans.app.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'STD_RED_PACKET',
+ 'biz_scene' => 'PERSONAL_PAY',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Marketing][Redpack][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/WebPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/WebPayPlugin.php
new file mode 100644
index 0000000..4c38b18
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Marketing/Redpack/WebPayPlugin.php
@@ -0,0 +1,37 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.fund.trans.page.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'STD_APP_TRANSFER',
+ ],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[Alipay][Marketing][Redpack][WebPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/AuthPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/AuthPlugin.php
new file mode 100644
index 0000000..4d0dbf9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/AuthPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.info.auth',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][Authorization][AuthPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/QueryPlugin.php
new file mode 100644
index 0000000..5d4f40f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.open.auth.userauth.relationship.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][Authorization][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/TokenPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/TokenPlugin.php
new file mode 100644
index 0000000..a267e80
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Authorization/TokenPlugin.php
@@ -0,0 +1,32 @@
+ $rocket]);
+
+ $rocket->mergePayload(array_merge(
+ [
+ 'method' => 'alipay.system.oauth.token',
+ ],
+ $rocket->getParams(),
+ ));
+
+ Logger::info('[Alipay][Member][Authorization][TokenPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/CertifyPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/CertifyPlugin.php
new file mode 100644
index 0000000..d5e8bb4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/CertifyPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.certify.open.certify',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][Certification][CertifyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/InitPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/InitPlugin.php
new file mode 100644
index 0000000..4e97cab
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/InitPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.certify.open.initialize',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'FACE',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Member][Certification][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/QueryPlugin.php
new file mode 100644
index 0000000..b43fdd9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Certification/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.certify.open.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][Certification][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/DetailPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/DetailPlugin.php
new file mode 100644
index 0000000..ab71f87
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/DetailPlugin.php
@@ -0,0 +1,33 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+
+ $rocket->mergePayload([
+ 'auth_token' => $params['auth_token'] ?? $params['_auth_token'] ?? '',
+ 'method' => 'alipay.user.info.share',
+ 'biz_content' => [],
+ ]);
+
+ Logger::info('[Alipay][Member][DetailPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppInitPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppInitPlugin.php
new file mode 100644
index 0000000..f3242bb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppInitPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.check.initialize',
+ 'biz_content' => array_merge(
+ [
+ 'biz_code' => 'DATA_DIGITAL_BIZ_CODE_FACE_CHECK_LIVE',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceCheck][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppQueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppQueryPlugin.php
new file mode 100644
index 0000000..81ce033
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceCheck/AppQueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.check.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceCheck][AppQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppInitPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppInitPlugin.php
new file mode 100644
index 0000000..5eb4eb8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppInitPlugin.php
@@ -0,0 +1,36 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.verification.initialize',
+ 'biz_content' => array_merge(
+ [
+ 'biz_code' => 'DATA_DIGITAL_BIZ_CODE_FACE_VERIFICATION',
+ 'identity_type' => 'CERT_INFO',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceVerification][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppQueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppQueryPlugin.php
new file mode 100644
index 0000000..aec240b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/AppQueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.verification.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceVerification][AppQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5InitPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5InitPlugin.php
new file mode 100644
index 0000000..3cd5ac2
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5InitPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.certify.initialize',
+ 'biz_content' => array_merge(
+ [
+ 'biz_code' => 'FUTURE_TECH_BIZ_FACE_SDK',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceVerification][H5InitPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5QueryPlugin.php
new file mode 100644
index 0000000..7dda061
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.certify.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceVerification][H5QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5VerifyPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5VerifyPlugin.php
new file mode 100644
index 0000000..572c8b3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/H5VerifyPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.certify.verify',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceVerification][H5VerifyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/ServerVerifyPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/ServerVerifyPlugin.php
new file mode 100644
index 0000000..358294f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/FaceVerification/ServerVerifyPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.face.source.certify',
+ 'biz_content' => array_merge(
+ [
+ 'cert_type' => 'IDENTITY_CARD',
+ ],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[Alipay][Member][FaceVerification][ServerVerifyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/AppInitPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/AppInitPlugin.php
new file mode 100644
index 0000000..88a55e0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/AppInitPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.ocr.mobile.initialize',
+ 'biz_content' => array_merge(
+ [
+ 'biz_code' => 'DATA_DIGITAL_BIZ_CODE_OCR',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Member][Ocr][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/DetectPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/DetectPlugin.php
new file mode 100644
index 0000000..d8e1d92
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/DetectPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.ocr.common.detect',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][Ocr][DetectPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/ServerDetectPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/ServerDetectPlugin.php
new file mode 100644
index 0000000..eaa5518
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Member/Ocr/ServerDetectPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'datadigital.fincloud.generalsaas.ocr.server.detect',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Member][Ocr][ServerDetectPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Bill/QueryUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Bill/QueryUrlPlugin.php
new file mode 100644
index 0000000..0bbe513
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Bill/QueryUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Bill][QueryUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/AppPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/AppPayPlugin.php
new file mode 100644
index 0000000..87807cd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/AppPayPlugin.php
@@ -0,0 +1,32 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.app.pay',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Pay][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/CancelPlugin.php
new file mode 100644
index 0000000..1ce0c4c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/CancelPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.cancel',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Pay][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/ClosePlugin.php
new file mode 100644
index 0000000..f609d43
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Pay][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/PayPlugin.php
new file mode 100644
index 0000000..d15a01b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/PayPlugin.php
@@ -0,0 +1,46 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'GENERAL_WITHHOLDING',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Pay][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/QueryPlugin.php
new file mode 100644
index 0000000..a4d6dbf
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Pay][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/RefundPlugin.php
new file mode 100644
index 0000000..6e1d259
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Pay/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Pay][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/ModifyPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/ModifyPlugin.php
new file mode 100644
index 0000000..a86adf9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/ModifyPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.agreement.executionplan.modify',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Sign][ModifyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/QueryPlugin.php
new file mode 100644
index 0000000..7d3dedb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.agreement.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Sign][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/SignPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/SignPlugin.php
new file mode 100644
index 0000000..25eb408
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/SignPlugin.php
@@ -0,0 +1,32 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.user.agreement.page.sign',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Sign][SignPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/UnsignPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/UnsignPlugin.php
new file mode 100644
index 0000000..d5fae2e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Agreement/Sign/UnsignPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.user.agreement.unsign',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Agreement][Sign][UnsignPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/ClosePlugin.php
new file mode 100644
index 0000000..c5cc0c9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][App][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/PayPlugin.php
new file mode 100644
index 0000000..4438aa7
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/PayPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.app.pay',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][App][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryBillUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryBillUrlPlugin.php
new file mode 100644
index 0000000..de0761e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryBillUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][App][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryPlugin.php
new file mode 100644
index 0000000..9370dc9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][App][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryRefundPlugin.php
new file mode 100644
index 0000000..4d0bbf6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/QueryRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][App][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/RefundPlugin.php
new file mode 100644
index 0000000..f4e658b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/App/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][App][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/AppFreezePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/AppFreezePlugin.php
new file mode 100644
index 0000000..b2585b5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/AppFreezePlugin.php
@@ -0,0 +1,37 @@
+ $rocket]);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.fund.auth.order.app.freeze',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'PREAUTH_PAY',
+ ],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Auth][AppFreezePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/CancelPlugin.php
new file mode 100644
index 0000000..0470b73
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/CancelPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.auth.operation.cancel',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Auth][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/PosFreezePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/PosFreezePlugin.php
new file mode 100644
index 0000000..9f5aa38
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/PosFreezePlugin.php
@@ -0,0 +1,36 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.auth.order.freeze',
+ 'biz_content' => array_merge(
+ [
+ 'auth_code_type' => 'bar_code',
+ 'product_code' => 'PREAUTH_PAY',
+ ],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Auth][PosFreezePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/QueryPlugin.php
new file mode 100644
index 0000000..1a496ef
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.auth.operation.detail.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Auth][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/ScanFreezePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/ScanFreezePlugin.php
new file mode 100644
index 0000000..113e3b0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/ScanFreezePlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.auth.order.voucher.create',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'PREAUTH_PAY',
+ ],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Auth][ScanFreezePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/UnfreezePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/UnfreezePlugin.php
new file mode 100644
index 0000000..d77e846
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Auth/UnfreezePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.fund.auth.order.unfreeze',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Auth][AppFreezePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Bill/QueryUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Bill/QueryUrlPlugin.php
new file mode 100644
index 0000000..6f20870
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Bill/QueryUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Bill][QueryUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/ClosePlugin.php
new file mode 100644
index 0000000..70a2fa7
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Pay][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/PayPlugin.php
new file mode 100644
index 0000000..fdf77bf
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/PayPlugin.php
@@ -0,0 +1,46 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'PREAUTH_PAY',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Pay][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryPlugin.php
new file mode 100644
index 0000000..b0070f8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Pay][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryRefundPlugin.php
new file mode 100644
index 0000000..1878430
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Pay][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/RefundPlugin.php
new file mode 100644
index 0000000..730d360
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Pay][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/SyncPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/SyncPlugin.php
new file mode 100644
index 0000000..8c205b6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Authorization/Pay/SyncPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.orderinfo.sync',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Authorization][Pay][SyncPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/InitPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/InitPlugin.php
new file mode 100644
index 0000000..2c6057a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/InitPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'zoloz.authentication.smilepay.initialize',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Face][InitPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/QueryPlugin.php
new file mode 100644
index 0000000..5fff49b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Face/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'zoloz.authentication.customer.ftoken.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Face][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/ClosePlugin.php
new file mode 100644
index 0000000..6a95844
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][H5][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/PayPlugin.php
new file mode 100644
index 0000000..3fdcb15
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/PayPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.wap.pay',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][H5][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryBillUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryBillUrlPlugin.php
new file mode 100644
index 0000000..8377b72
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryBillUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][H5][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryPlugin.php
new file mode 100644
index 0000000..0032459
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][H5][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryRefundPlugin.php
new file mode 100644
index 0000000..1e42b56
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/QueryRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][H5][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/RefundPlugin.php
new file mode 100644
index 0000000..066bb6e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/H5/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][H5][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/CancelPlugin.php
new file mode 100644
index 0000000..68a0496
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/CancelPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.cancel',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Mini][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php
new file mode 100644
index 0000000..86d3955
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Mini][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/PayPlugin.php
new file mode 100644
index 0000000..407e776
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/PayPlugin.php
@@ -0,0 +1,46 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.create',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'JSAPI_PAY',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php
new file mode 100644
index 0000000..e8dd2ea
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Mini][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php
new file mode 100644
index 0000000..61c0c0f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Mini][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryRefundPlugin.php
new file mode 100644
index 0000000..8f690c0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/QueryRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Mini][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/RefundPlugin.php
new file mode 100644
index 0000000..c927370
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Mini/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Mini][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php
new file mode 100644
index 0000000..4324d12
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.cancel',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php
new file mode 100644
index 0000000..1bb49c3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Pos][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/PayPlugin.php
new file mode 100644
index 0000000..6fdfa80
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/PayPlugin.php
@@ -0,0 +1,46 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.pay',
+ 'biz_content' => array_merge(
+ [
+ 'scene' => 'bar_code',
+ ],
+ $rocket->getParams(),
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php
new file mode 100644
index 0000000..6857612
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Pos][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php
new file mode 100644
index 0000000..0e54469
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Pos][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryRefundPlugin.php
new file mode 100644
index 0000000..66844dd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/QueryRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Pos][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php
new file mode 100644
index 0000000..b245616
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Pos][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CancelPlugin.php
new file mode 100644
index 0000000..b8cf716
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CancelPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.cancel',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php
new file mode 100644
index 0000000..a35a0e5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CreatePlugin.php
new file mode 100644
index 0000000..c74b41d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/CreatePlugin.php
@@ -0,0 +1,41 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.create',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/PayPlugin.php
new file mode 100644
index 0000000..4ceecda
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/PayPlugin.php
@@ -0,0 +1,41 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.precreate',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php
new file mode 100644
index 0000000..7700491
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php
new file mode 100644
index 0000000..95b498e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryRefundPlugin.php
new file mode 100644
index 0000000..95c3ad2
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/QueryRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/RefundPlugin.php
new file mode 100644
index 0000000..79c145f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Scan/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Scan][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/ClosePlugin.php
new file mode 100644
index 0000000..22cd791
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/ClosePlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.close',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Web][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/PayPlugin.php
new file mode 100644
index 0000000..6de7c2e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/PayPlugin.php
@@ -0,0 +1,48 @@
+ $rocket]);
+
+ $this->loadAlipayServiceProvider($rocket);
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ 'method' => 'alipay.trade.page.pay',
+ 'biz_content' => array_merge(
+ [
+ 'product_code' => 'FAST_INSTANT_TRADE_PAY',
+ ],
+ $rocket->getParams()
+ ),
+ ]);
+
+ Logger::info('[Alipay][Pay][Web][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryBillUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryBillUrlPlugin.php
new file mode 100644
index 0000000..a61b972
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryBillUrlPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.data.dataservice.bill.downloadurl.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Web][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryPlugin.php
new file mode 100644
index 0000000..a4f9237
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Web][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryRefundPlugin.php
new file mode 100644
index 0000000..c0f319f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/QueryRefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.fastpay.refund.query',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Web][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/RefundPlugin.php
new file mode 100644
index 0000000..70fb333
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/Pay/Web/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'method' => 'alipay.trade.refund',
+ 'biz_content' => $rocket->getParams(),
+ ]);
+
+ Logger::info('[Alipay][Pay][Web][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseHtmlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseHtmlPlugin.php
new file mode 100644
index 0000000..ad6af68
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseHtmlPlugin.php
@@ -0,0 +1,71 @@
+ $rocket]);
+
+ $radar = $rocket->getRadar();
+
+ $response = 'GET' === $radar->getMethod()
+ ? $this->buildRedirect($radar->getUri()->__toString(), $rocket->getPayload())
+ : $this->buildHtml($radar->getUri()->__toString(), $rocket->getPayload());
+
+ $rocket->setDestination($response);
+
+ Logger::info('[Alipay][ResponseHtmlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ protected function buildRedirect(string $endpoint, Collection $payload): Response
+ {
+ $url = $endpoint.(!str_contains($endpoint, '?') ? '?' : '&').$payload->query();
+
+ $content = sprintf(
+ '
+
+
+
+
+
+ Redirecting to %1$s
+
+
+ Redirecting to %1$s.
+
+ ',
+ htmlspecialchars($url, ENT_QUOTES)
+ );
+
+ return new Response(302, ['Location' => $url], $content);
+ }
+
+ protected function buildHtml(string $endpoint, Collection $payload): Response
+ {
+ $sHtml = "";
+ $sHtml .= "";
+
+ return new Response(200, [], $sHtml);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseInvokeStringPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseInvokeStringPlugin.php
new file mode 100644
index 0000000..d08ccf8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponseInvokeStringPlugin.php
@@ -0,0 +1,31 @@
+ $rocket]);
+
+ $response = new Response(200, [], Arr::query($rocket->getPayload()->all()));
+
+ $rocket->setDestination($response);
+
+ Logger::info('[Alipay][ResponseInvokeStringPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponsePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponsePlugin.php
new file mode 100644
index 0000000..36f2870
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/ResponsePlugin.php
@@ -0,0 +1,51 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $payload = $rocket->getPayload();
+ $resultKey = str_replace('.', '_', $payload->get('method')).'_response';
+
+ if (should_do_http_request($rocket->getDirection()) && $destination instanceof Collection) {
+ $sign = $destination->get('sign', '');
+ $response = $destination->get($resultKey, $destination->all());
+
+ if (empty($sign) && '10000' !== ($response['code'] ?? 'null')) {
+ throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, '支付宝网关响应异常: '.($response['sub_msg'] ?? $response['msg'] ?? '未知错误,请查看支付宝原始响应'), $rocket->getDestination());
+ }
+
+ $rocket->setDestination(new Collection(array_merge(
+ ['_sign' => $sign],
+ $response
+ )));
+ }
+
+ Logger::info('[Alipay][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/StartPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/StartPlugin.php
new file mode 100644
index 0000000..187a762
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/StartPlugin.php
@@ -0,0 +1,210 @@
+ $rocket]);
+
+ $rocket->mergePayload($this->getPayload($rocket->getParams()));
+
+ Logger::info('[Alipay][StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ * @throws InvalidConfigException
+ */
+ protected function getPayload(array $params): array
+ {
+ $tenant = get_tenant($params);
+ $config = get_provider_config('alipay', $params);
+
+ return [
+ 'app_id' => $config['app_id'] ?? '',
+ 'method' => '',
+ 'format' => 'JSON',
+ 'return_url' => $this->getReturnUrl($params, $config),
+ 'charset' => 'utf-8',
+ 'sign_type' => 'RSA2',
+ 'sign' => '',
+ 'timestamp' => date('Y-m-d H:i:s'),
+ 'version' => '1.0',
+ 'notify_url' => $this->getNotifyUrl($params, $config),
+ 'app_auth_token' => $this->getAppAuthToken($params, $config),
+ 'app_cert_sn' => $this->getAppCertSn($tenant, $config),
+ 'alipay_root_cert_sn' => $this->getAlipayRootCertSn($tenant, $config),
+ 'biz_content' => [],
+ ];
+ }
+
+ protected function getReturnUrl(array $params, array $config): string
+ {
+ if (!empty($params['_return_url'])) {
+ return $params['_return_url'];
+ }
+
+ return $config['return_url'] ?? '';
+ }
+
+ protected function getNotifyUrl(array $params, array $config): string
+ {
+ if (!empty($params['_notify_url'])) {
+ return $params['_notify_url'];
+ }
+
+ return $config['notify_url'] ?? '';
+ }
+
+ protected function getAppAuthToken(array $params, array $config): string
+ {
+ if (!empty($params['_app_auth_token'])) {
+ return $params['_app_auth_token'];
+ }
+
+ return $config['app_auth_token'] ?? '';
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getAppCertSn(string $tenant, array $config): string
+ {
+ if (!empty($config['app_public_cert_sn'])) {
+ return $config['app_public_cert_sn'];
+ }
+
+ $path = $config['app_public_cert_path'] ?? null;
+
+ if (is_null($path)) {
+ throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [app_public_cert_path]');
+ }
+
+ $ssl = openssl_x509_parse(get_public_cert($path));
+
+ if (false === $ssl) {
+ throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 解析 `app_public_cert_path` 失败');
+ }
+
+ $result = $this->getCertSn($ssl['issuer'] ?? [], $ssl['serialNumber'] ?? '');
+
+ Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.app_public_cert_sn', $result);
+
+ return $result;
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ protected function getAlipayRootCertSn(string $tenant, array $config): string
+ {
+ if (!empty($config['alipay_root_cert_sn'])) {
+ return $config['alipay_root_cert_sn'];
+ }
+
+ $path = $config['alipay_root_cert_path'] ?? null;
+
+ if (is_null($path)) {
+ throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [alipay_root_cert_path]');
+ }
+
+ $sn = '';
+ $exploded = explode('-----END CERTIFICATE-----', get_public_cert($path));
+
+ foreach ($exploded as $cert) {
+ if (empty(trim($cert))) {
+ continue;
+ }
+
+ $ssl = openssl_x509_parse($cert.'-----END CERTIFICATE-----');
+
+ if (false === $ssl) {
+ throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 解析 `alipay_root_cert` 失败');
+ }
+
+ $detail = $this->formatCert($ssl);
+
+ if ('sha1WithRSAEncryption' == $detail['signatureTypeLN'] || 'sha256WithRSAEncryption' == $detail['signatureTypeLN']) {
+ $sn .= $this->getCertSn($detail['issuer'], $detail['serialNumber']).'_';
+ }
+ }
+
+ $result = substr($sn, 0, -1);
+
+ Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.alipay_root_cert_sn', $result);
+
+ return $result;
+ }
+
+ protected function getCertSn(array $issuer, string $serialNumber): string
+ {
+ return md5($this->array2string(array_reverse($issuer)).$serialNumber);
+ }
+
+ protected function array2string(array $array): string
+ {
+ $string = [];
+
+ foreach ($array as $key => $value) {
+ $string[] = $key.'='.$value;
+ }
+
+ return implode(',', $string);
+ }
+
+ protected function formatCert(array $ssl): array
+ {
+ if (str_starts_with($ssl['serialNumber'] ?? '', '0x')) {
+ $ssl['serialNumber'] = $this->hex2dec($ssl['serialNumberHex'] ?? '');
+ }
+
+ return $ssl;
+ }
+
+ protected function hex2dec(string $hex): string
+ {
+ $dec = '0';
+ $len = strlen($hex);
+
+ for ($i = 1; $i <= $len; ++$i) {
+ $dec = bcadd(
+ $dec,
+ bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i), 0), 0),
+ 0
+ );
+ }
+
+ return $dec;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/VerifySignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/VerifySignaturePlugin.php
new file mode 100644
index 0000000..5e667e5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Alipay/V2/VerifySignaturePlugin.php
@@ -0,0 +1,57 @@
+ $rocket]);
+
+ if (!should_do_http_request($rocket->getDirection())) {
+ return $rocket;
+ }
+
+ $destination = $rocket->getDestination();
+
+ if ((!$destination instanceof Collection) || empty($result = $destination->except('_sign')->all())) {
+ throw new InvalidParamsException(Exception::RESPONSE_EMPTY, '参数异常: 支付宝验证签名时待验签参数不正确', $destination);
+ }
+
+ $config = get_provider_config('alipay', $rocket->getParams());
+
+ verify_alipay_sign($config, json_encode($result, JSON_UNESCAPED_UNICODE), $destination->get('_sign', ''));
+
+ Logger::info('[Alipay][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php
new file mode 100644
index 0000000..7a83f97
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/AddRadarPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/AddRadarPlugin.php
new file mode 100644
index 0000000..b7a020c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/CallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/CallbackPlugin.php
new file mode 100644
index 0000000..c348f48
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php
new file mode 100644
index 0000000..3926149
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php
new file mode 100644
index 0000000..3dbca6d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php
new file mode 100644
index 0000000..8f15e3a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php
new file mode 100644
index 0000000..7186461
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/ResponsePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Douyin/V1/Pay/ResponsePlugin.php
new file mode 100644
index 0000000..df4939a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddPayloadSignPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddPayloadSignPlugin.php
new file mode 100644
index 0000000..a3c0295
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddPayloadSignPlugin.php
@@ -0,0 +1,65 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('jsb', $params);
+ $payload = $rocket->getPayload();
+
+ if (empty($payload) || $payload->isEmpty()) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少支付必要参数。可能插件用错顺序,应该先使用 `业务插件`');
+ }
+
+ $privateCertPath = $config['mch_secret_cert_path'] ?? '';
+
+ if (empty($privateCertPath)) {
+ throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [mch_secret_cert_path]');
+ }
+
+ $rocket->mergePayload([
+ 'signType' => 'RSA',
+ 'sign' => $this->getSignature(get_private_cert($privateCertPath), $payload),
+ ]);
+
+ Logger::info('[Jsb][AddPayloadSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function getSignature(string $pkey, Collection $payload): string
+ {
+ $content = $payload->sortKeys()->toString();
+
+ openssl_sign($content, $signature, $pkey);
+
+ return base64_encode($signature);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddRadarPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddRadarPlugin.php
new file mode 100644
index 0000000..870382a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/AddRadarPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('jsb', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setRadar(new Request(
+ strtoupper($params['_method'] ?? 'POST'),
+ get_jsb_url($config, $payload),
+ $this->getHeaders(),
+ $this->getBody($payload),
+ ));
+
+ Logger::info('[Jsb][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function getHeaders(): array
+ {
+ return [
+ 'Content-Type' => 'text/html',
+ 'User-Agent' => 'yansongda/pay-v3',
+ ];
+ }
+
+ protected function getBody(Collection $payload): string
+ {
+ $sign = $payload->get('sign');
+ $signType = $payload->get('signType');
+
+ $payload->forget('sign');
+ $payload->forget('signType');
+
+ $payload = $payload->sortKeys();
+
+ $payload->set('sign', $sign);
+ $payload->set('signType', $signType);
+
+ return $payload->toString();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/CallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/CallbackPlugin.php
new file mode 100644
index 0000000..bb9940d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/CallbackPlugin.php
@@ -0,0 +1,70 @@
+ $rocket]);
+
+ $this->formatRequestAndParams($rocket);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('jsb', $params);
+
+ $payload = $rocket->getPayload();
+ $signature = $payload->get('sign');
+
+ $payload->forget('sign');
+ $payload->forget('signType');
+
+ verify_jsb_sign($config, $payload->sortKeys()->toString(), $signature);
+
+ $rocket->setDirection(NoHttpRequestDirection::class)
+ ->setDestination($rocket->getPayload());
+
+ Logger::info('[Jsb][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function formatRequestAndParams(Rocket $rocket): void
+ {
+ $request = $rocket->getParams()['request'] ?? null;
+
+ if (!$request instanceof Collection) {
+ throw new InvalidParamsException(Exception::PARAMS_CALLBACK_REQUEST_INVALID);
+ }
+
+ $rocket->setPayload($request)->setParams($rocket->getParams()['params'] ?? []);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/PayPlugin.php
new file mode 100644
index 0000000..dc9392d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/PayPlugin.php
@@ -0,0 +1,49 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('jsb', $params);
+ $backUrl = $rocket->getPayload()['notify_url'] ?? $config['notify_url'] ?? null;
+
+ if (!$backUrl) {
+ throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [notify_url]');
+ }
+
+ $rocket->mergePayload([
+ 'service' => 'atPay',
+ 'backUrl' => $backUrl,
+ ]);
+
+ Logger::info('[Jsb][Pay][Scan][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/QueryPlugin.php
new file mode 100644
index 0000000..47b4e89
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/QueryPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'deviceNo' => '1234567890',
+ 'service' => 'payCheck',
+ ]);
+
+ Logger::info('[Jsb][Pay][Scan][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/RefundPlugin.php
new file mode 100644
index 0000000..e770810
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/Pay/Scan/RefundPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ 'deviceNo' => '1234567890',
+ 'service' => 'payRefund',
+ ]);
+
+ Logger::info('[Jsb][Pay][Scan][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/ResponsePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/ResponsePlugin.php
new file mode 100644
index 0000000..023ba95
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/ResponsePlugin.php
@@ -0,0 +1,52 @@
+ $rocket]);
+
+ $this->validateResponse($rocket);
+
+ Logger::info('[Jsb][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidResponseException
+ */
+ protected function validateResponse(Rocket $rocket): void
+ {
+ $destination = $rocket->getDestination();
+ $destinationOrigin = $rocket->getDestinationOrigin();
+
+ if ($destinationOrigin instanceof ResponseInterface
+ && ($destinationOrigin->getStatusCode() < 200 || $destinationOrigin->getStatusCode() >= 300)) {
+ throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '江苏银行返回状态码异常,请检查参数是否错误', $rocket->getDestination());
+ }
+
+ if ($destination instanceof Collection && '000000' !== $destination->get('respCode')) {
+ throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, sprintf('江苏银行返回错误: respCode:%s respMsg:%s', $destination->get('respCode'), $destination->get('respMsg')), $rocket->getDestination());
+ }
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/StartPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/StartPlugin.php
new file mode 100644
index 0000000..22a1b12
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/StartPlugin.php
@@ -0,0 +1,49 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('jsb', $params);
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload(array_merge($params, [
+ 'createData' => date('Ymd'),
+ 'createTime' => date('His'),
+ 'bizDate' => date('Ymd'),
+ 'msgId' => Str::uuidV4(),
+ 'svrCode' => $config['svr_code'] ?? '',
+ 'partnerId' => $config['partner_id'] ?? '',
+ 'channelNo' => 'm',
+ 'publicKeyCode' => $config['public_key_code'] ?? '',
+ 'version' => 'v1.0.0',
+ 'charset' => 'utf-8',
+ ]));
+
+ Logger::info('[Jsb][StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/VerifySignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/VerifySignaturePlugin.php
new file mode 100644
index 0000000..635cbf1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Jsb/VerifySignaturePlugin.php
@@ -0,0 +1,76 @@
+ $rocket]);
+
+ if (should_do_http_request($rocket->getDirection())) {
+ $params = $rocket->getParams();
+ $config = get_provider_config('jsb', $params);
+
+ $body = (string) $rocket->getDestinationOrigin()->getBody();
+ $signatureData = $this->getSignatureData($body);
+
+ verify_jsb_sign($config, $signatureData['data'] ?? '', $signatureData['sign'] ?? '');
+ }
+
+ Logger::info('[Jsb][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ private function getSignatureData(string $body): array
+ {
+ if (Str::contains($body, '&-&')) {
+ $beginIndex = strpos($body, '&signType=');
+ $endIndex = strpos($body, '&-&');
+ $data = substr($body, 0, $beginIndex).substr($body, $endIndex);
+
+ $signIndex = strpos($body, '&sign=');
+ $signature = substr($body, $signIndex + strlen('&sign='), $endIndex - ($signIndex + strlen('&sign=')));
+ } else {
+ $result = Arr::wrapQuery($body, true);
+ $result = Collection::wrap($result);
+ $signature = $result->get('sign');
+ $result->forget('sign');
+ $result->forget('signType');
+ $data = $result->sortKeys()->toString();
+ }
+
+ return [
+ 'sign' => $signature,
+ 'data' => $data,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/AddRadarPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/AddRadarPlugin.php
new file mode 100644
index 0000000..4b5b569
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/AddRadarPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setRadar(new Request(
+ get_radar_method($payload) ?? 'POST',
+ get_unipay_url($config, $payload),
+ $this->getHeaders(),
+ get_unipay_body($payload),
+ ));
+
+ Logger::info('[Unipay][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function getHeaders(): array
+ {
+ return [
+ 'User-Agent' => 'yansongda/pay-v3',
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/AddPayloadSignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/AddPayloadSignaturePlugin.php
new file mode 100644
index 0000000..62a9585
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/AddPayloadSignaturePlugin.php
@@ -0,0 +1,63 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ if (empty($payload) || $payload->isEmpty()) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 银联支付必要参数缺失。可能插件用错顺序,应该先使用 `业务插件`');
+ }
+
+ $rocket->mergePayload([
+ 'signature' => $this->getSignature($config['certs']['pkey'] ?? '', filter_params($rocket->getPayload(), fn ($k, $v) => 'signature' != $k)),
+ ]);
+
+ Logger::info('[Unipay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getSignature(string $pkey, Collection $payload): string
+ {
+ if (empty($pkey)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 银联支付配置文件中未找到 `certs.pkey` 配置项。可能插件用错顺序,应该先使用 `StartPlugin`');
+ }
+
+ $content = $payload->sortKeys()->toString();
+
+ openssl_sign(hash('sha256', $content), $sign, $pkey, 'sha256');
+
+ return base64_encode($sign);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/CallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/CallbackPlugin.php
new file mode 100644
index 0000000..2fc127b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/CallbackPlugin.php
@@ -0,0 +1,49 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPayload($params);
+
+ $collection = filter_params($params)->except('signature')->sortKeys();
+
+ verify_unipay_sign($config, $collection->toString(), $params['signature'] ?? '', $params['signPubKeyCert'] ?? null);
+
+ $rocket->setDirection(NoHttpRequestDirection::class)
+ ->setDestination($rocket->getPayload());
+
+ Logger::info('[Unipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/H5/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/H5/PayPlugin.php
new file mode 100644
index 0000000..991670b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/H5/PayPlugin.php
@@ -0,0 +1,56 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/frontTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000201',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'currencyCode' => '156',
+ 'merId' => $config['mch_id'] ?? '',
+ 'channelType' => $payload?->get('channelType') ?? '07',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '01',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '01',
+ 'frontUrl' => $payload?->get('frontUrl') ?? $config['return_url'] ?? '',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][H5][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/CancelPlugin.php
new file mode 100644
index 0000000..6c82829
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/CancelPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'channelType' => $payload?->get('channelType') ?? '08',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '31',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '00',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPlugin.php
new file mode 100644
index 0000000..7d361bf
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'currencyCode' => '156',
+ 'channelType' => $payload?->get('channelType') ?? '08',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '01',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '06',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][PosPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPreAuthPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPreAuthPlugin.php
new file mode 100644
index 0000000..b3f3f0f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/PosPreAuthPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000201',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'currencyCode' => '156',
+ 'channelType' => $payload?->get('channelType') ?? '08',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '02',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '04',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][PosPreAuthPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/QueryPlugin.php
new file mode 100644
index 0000000..2594d39
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/QueryPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ '_sandbox_url' => 'https://101.231.204.80:5000/gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '00',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '00',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/RefundPlugin.php
new file mode 100644
index 0000000..ff445db
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/RefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'channelType' => $payload?->get('channelType') ?? '08',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '04',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '00',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][QrCode][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanFeePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanFeePlugin.php
new file mode 100644
index 0000000..a7de9cc
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanFeePlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000601',
+ 'encoding' => 'utf-8',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'currencyCode' => '156',
+ 'channelType' => $payload?->get('channelType') ?? '07',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '13',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '08',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][ScanFeePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPlugin.php
new file mode 100644
index 0000000..5a0e241
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'currencyCode' => '156',
+ 'channelType' => $payload?->get('channelType') ?? '08',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '01',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '07',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][ScanPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreAuthPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreAuthPlugin.php
new file mode 100644
index 0000000..ee49436
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreAuthPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'currencyCode' => '156',
+ 'channelType' => $payload?->get('channelType') ?? '08',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '02',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '05',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][ScanPreAuthPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreOrderPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreOrderPlugin.php
new file mode 100644
index 0000000..0c44b8b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/QrCode/ScanPreOrderPlugin.php
@@ -0,0 +1,56 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/order.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'currencyCode' => '156',
+ 'channelType' => $payload?->get('channelType') ?? '08',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '01',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '01',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ 'frontUrl' => $payload?->get('frontUrl') ?? $config['return_url'] ?? null,
+ ]);
+
+ Logger::info('[Unipay][Pay][QrCode][ScanPreOrderPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/CancelPlugin.php
new file mode 100644
index 0000000..4379948
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/CancelPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'channelType' => $payload?->get('channelType') ?? '07',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '31',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '00',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][Web][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/PayPlugin.php
new file mode 100644
index 0000000..df20095
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/PayPlugin.php
@@ -0,0 +1,58 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->setDirection(ResponseDirection::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/frontTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000201',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'currencyCode' => '156',
+ 'channelType' => $payload?->get('channelType') ?? '07',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '01',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '01',
+ 'frontUrl' => $payload?->get('frontUrl') ?? $config['return_url'] ?? '',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][Web][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/QueryPlugin.php
new file mode 100644
index 0000000..e57e372
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/QueryPlugin.php
@@ -0,0 +1,52 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/queryTrans.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '00',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '00',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][Web][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/RefundPlugin.php
new file mode 100644
index 0000000..ab7b0b5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/Pay/Web/RefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(QueryPacker::class)
+ ->mergePayload([
+ '_url' => 'gateway/api/backTransReq.do',
+ 'encoding' => 'utf-8',
+ 'signature' => '',
+ 'bizType' => $payload?->get('bizType') ?? '000000',
+ 'accessType' => $payload?->get('accessType') ?? '0',
+ 'merId' => $config['mch_id'] ?? '',
+ 'channelType' => $payload?->get('channelType') ?? '07',
+ 'signMethod' => '01',
+ 'txnType' => $payload?->get('txnType') ?? '04',
+ 'txnSubType' => $payload?->get('txnSubType') ?? '00',
+ 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '',
+ 'version' => '5.1.0',
+ ]);
+
+ Logger::info('[Unipay][Pay][Web][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/ResponseHtmlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/ResponseHtmlPlugin.php
new file mode 100644
index 0000000..339deb0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/ResponseHtmlPlugin.php
@@ -0,0 +1,48 @@
+ $rocket]);
+
+ $radar = $rocket->getRadar();
+ $payload = $rocket->getPayload();
+
+ $response = $this->buildHtml($radar->getUri()->__toString(), filter_params($payload));
+
+ $rocket->setDestination($response);
+
+ Logger::info('[Unipay][ResponseHtmlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ protected function buildHtml(string $endpoint, Collection $payload): Response
+ {
+ $sHtml = "";
+ $sHtml .= "";
+
+ return new Response(200, [], $sHtml);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/StartPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/StartPlugin.php
new file mode 100644
index 0000000..ed3252c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/StartPlugin.php
@@ -0,0 +1,91 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $tenant = get_tenant($params);
+
+ $rocket->mergePayload(array_merge($params, [
+ '_unpack_raw' => true,
+ 'certId' => $this->getCertId($tenant, $config),
+ ]));
+
+ Logger::info('[Unipay][StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws ServiceNotFoundException
+ */
+ public function getCertId(string $tenant, array $config): string
+ {
+ if (!empty($config['certs']['cert_id'])) {
+ return $config['certs']['cert_id'];
+ }
+
+ $certs = $this->getCerts($config);
+ $ssl = openssl_x509_parse($certs['cert'] ?? '');
+
+ if (false === $ssl) {
+ throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 解析银联 `mch_cert_path` 失败,请检查参数是否正确');
+ }
+
+ $certs['cert_id'] = $ssl['serialNumber'] ?? '';
+
+ Pay::get(ConfigInterface::class)->set('unipay.'.$tenant.'.certs', $certs);
+
+ return $certs['cert_id'];
+ }
+
+ /**
+ * @return array ['cert' => 公钥, 'pkey' => 私钥, 'extracerts' => array]
+ *
+ * @throws InvalidConfigException
+ */
+ protected function getCerts(array $config): array
+ {
+ $path = $config['mch_cert_path'] ?? null;
+ $password = $config['mch_cert_password'] ?? null;
+
+ if (is_null($path) || is_null($password)) {
+ throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_cert_path] or [mch_cert_password]');
+ }
+
+ if (false === openssl_pkcs12_read(file_get_contents($path), $certs, $password)) {
+ throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 读取银联 `mch_cert_path` 失败,请确认参数是否正确');
+ }
+
+ return $certs;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/VerifySignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/VerifySignaturePlugin.php
new file mode 100644
index 0000000..aca6db4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Open/VerifySignaturePlugin.php
@@ -0,0 +1,60 @@
+ $rocket]);
+
+ if (!should_do_http_request($rocket->getDirection())) {
+ return $rocket;
+ }
+
+ $destination = $rocket->getDestination();
+
+ if (!$destination instanceof Collection) {
+ return $rocket;
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ verify_unipay_sign(
+ $config,
+ $destination->except('signature')->sortKeys()->toString(),
+ $destination->get('signature', ''),
+ $destination->get('signPubKeyCert')
+ );
+
+ Logger::info('[Unipay][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/AddPayloadSignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/AddPayloadSignaturePlugin.php
new file mode 100644
index 0000000..7c205e7
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/AddPayloadSignaturePlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ if (empty($payload) || $payload->isEmpty()) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 银联支付必要参数缺失。可能插件用错顺序,应该先使用 `业务插件`');
+ }
+
+ $rocket->mergePayload(['sign' => get_unipay_sign_qra($config, filter_params($payload)->all())]);
+
+ Logger::info('[Unipay][Qra][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/CallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/CallbackPlugin.php
new file mode 100644
index 0000000..3cc00ff
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/CallbackPlugin.php
@@ -0,0 +1,49 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $destination = filter_params($params);
+
+ if (isset($params['status']) && 0 == $params['status']) {
+ verify_unipay_sign_qra($config, $destination->all());
+ }
+
+ $rocket->setPayload($params)
+ ->setDirection(NoHttpRequestDirection::class)
+ ->setDestination($destination);
+
+ Logger::info('[Unipay][Qra][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/CancelPlugin.php
new file mode 100644
index 0000000..ecfe1b9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/CancelPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.micropay.reverse',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/PayPlugin.php
new file mode 100644
index 0000000..5ae7815
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/PayPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.micropay',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryOpenIdPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryOpenIdPlugin.php
new file mode 100644
index 0000000..d6dffd2
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryOpenIdPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.tools.authcodetoopenid',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Pos][QueryOpenIdPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryPlugin.php
new file mode 100644
index 0000000..c8aebab
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.query',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Pos][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryRefundPlugin.php
new file mode 100644
index 0000000..7d9280a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/QueryRefundPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.refundquery',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Pos][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/RefundPlugin.php
new file mode 100644
index 0000000..58096b9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Pos/RefundPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.refund',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Pos][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/ClosePlugin.php
new file mode 100644
index 0000000..c6e486f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/ClosePlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.close',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Scan][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/PayPlugin.php
new file mode 100644
index 0000000..ea253de
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/PayPlugin.php
@@ -0,0 +1,52 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.native',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Scan][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryPlugin.php
new file mode 100644
index 0000000..bff614d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.query',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Scan][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryRefundPlugin.php
new file mode 100644
index 0000000..9458bbe
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/QueryRefundPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.refundquery',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Scan][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/RefundPlugin.php
new file mode 100644
index 0000000..d0c57b8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/Scan/RefundPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('unipay', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'https://qra.95516.com/pay/gateway',
+ 'service' => 'unified.trade.refund',
+ 'charset' => 'UTF-8',
+ 'sign_type' => 'MD5',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ ]);
+
+ Logger::info('[Unipay][Qra][Scan][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/StartPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/StartPlugin.php
new file mode 100644
index 0000000..bc0339a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/StartPlugin.php
@@ -0,0 +1,26 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+
+ $rocket->mergePayload($params);
+
+ Logger::info('[Unipay][Qra][StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/VerifySignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/VerifySignaturePlugin.php
new file mode 100644
index 0000000..5f86622
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Unipay/Qra/VerifySignaturePlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $config = get_provider_config('unipay', $rocket->getParams());
+
+ if (!should_do_http_request($rocket->getDirection())) {
+ return $rocket;
+ }
+
+ verify_unipay_sign_qra($config, $rocket->getDestination()?->all() ?? []);
+
+ Logger::info('[Unipay][Qra][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/AddRadarPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/AddRadarPlugin.php
new file mode 100644
index 0000000..9b085ef
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/AddRadarPlugin.php
@@ -0,0 +1,76 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+
+ $rocket->setRadar(new Request(
+ get_wechat_method($payload),
+ get_wechat_url($config, $payload),
+ $this->getHeaders($payload),
+ get_wechat_body($payload),
+ ));
+
+ Logger::info('[Wechat][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function getHeaders(?Collection $payload): array
+ {
+ $headers = [
+ 'Accept' => 'application/json, text/plain, application/x-gzip',
+ 'User-Agent' => 'yansongda/pay-v3',
+ 'Content-Type' => 'application/json; charset=utf-8',
+ ];
+
+ // 当 body 里有加密内容时,需要传递此参数用于微信区分
+ if (!empty($serialNo = $payload?->get('_serial_no'))) {
+ $headers['Wechatpay-Serial'] = $serialNo;
+ }
+
+ if (!empty($authorization = $payload?->get('_authorization'))) {
+ $headers['Authorization'] = $authorization;
+ }
+
+ if (!empty($contentType = $payload?->get('_content_type'))) {
+ $headers['Content-Type'] = $contentType;
+ }
+
+ if (!empty($accept = $payload?->get('_accept'))) {
+ $headers['Accept'] = $accept;
+ }
+
+ return $headers;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/ResponsePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/ResponsePlugin.php
new file mode 100644
index 0000000..f284dc1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/ResponsePlugin.php
@@ -0,0 +1,46 @@
+ $rocket]);
+
+ $this->validateResponse($rocket);
+
+ Logger::info('[Wechat][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidResponseException
+ */
+ protected function validateResponse(Rocket $rocket): void
+ {
+ $response = $rocket->getDestinationOrigin();
+
+ if ($response instanceof ResponseInterface
+ && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300)) {
+ throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '微信返回状态码异常,请检查参数是否错误', $rocket->getDestination());
+ }
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/StartPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/StartPlugin.php
new file mode 100644
index 0000000..101deee
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/StartPlugin.php
@@ -0,0 +1,26 @@
+ $rocket]);
+
+ $rocket->mergePayload($rocket->getParams());
+
+ Logger::info('[Wechat][StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/AddPayloadSignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/AddPayloadSignaturePlugin.php
new file mode 100644
index 0000000..9bc0d31
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/AddPayloadSignaturePlugin.php
@@ -0,0 +1,40 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+
+ $rocket->mergePayload([
+ 'sign' => get_wechat_sign_v2($config, filter_params($rocket->getPayload())->all()),
+ ]);
+
+ Logger::info('[Wechat][V2][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ApplyPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ApplyPlugin.php
new file mode 100644
index 0000000..36bc989
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ApplyPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'pay/pappayapply',
+ '_content_type' => 'application/xml',
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ 'sign_type' => 'MD5',
+ 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '',
+ ]);
+
+ Logger::info('[Wechat][V2][Papay][Direct][ApplyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ContractOrderPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ContractOrderPlugin.php
new file mode 100644
index 0000000..fb7c1d5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/ContractOrderPlugin.php
@@ -0,0 +1,56 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'pay/contractorder',
+ '_content_type' => 'application/xml',
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'contract_appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'contract_mchid' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ 'sign_type' => 'MD5',
+ 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '',
+ 'contract_notify_url' => $payload?->get('contract_notify_url') ?? $config['notify_url'] ?? '',
+ ]);
+
+ Logger::info('[Wechat][V2][Papay][Direct][ContractOrderPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/MiniOnlyContractPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/MiniOnlyContractPlugin.php
new file mode 100644
index 0000000..7f30d7a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Papay/Direct/MiniOnlyContractPlugin.php
@@ -0,0 +1,52 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDirection(NoHttpRequestDirection::class)
+ ->mergePayload([
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '',
+ 'timestamp' => time(),
+ ]);
+
+ Logger::info('[Wechat][V2][Papay][Direct][OnlyContractPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ /** @var Rocket $rocket */
+ $rocket = $next($rocket);
+
+ $rocket->setDestination($rocket->getPayload());
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/App/InvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/App/InvokePlugin.php
new file mode 100644
index 0000000..b30e2cf
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/App/InvokePlugin.php
@@ -0,0 +1,97 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id') ?? null;
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][V2][Pay][App][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId));
+
+ Logger::info('[Wechat][V2][Pay][App][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appid' => $this->getAppId($payload, $config),
+ 'partnerid' => $this->getPartnerId($payload, $config),
+ 'prepayid' => $prepayId,
+ 'package' => 'Sign=WXPay',
+ 'noncestr' => Str::random(32),
+ 'timestamp' => time().'',
+ ]);
+
+ $invokeConfig->set('sign', get_wechat_sign_v2($config, $invokeConfig->all()));
+
+ return $invokeConfig;
+ }
+
+ protected function getAppId(?Collection $payload, array $config): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_app_id'] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config['app_id'] ?? '';
+ }
+
+ protected function getPartnerId(?Collection $payload, array $config): string
+ {
+ return $payload?->get('_invoke_partnerid') ?? $config['mch_id'] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Mini/InvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Mini/InvokePlugin.php
new file mode 100644
index 0000000..992fc72
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Mini/InvokePlugin.php
@@ -0,0 +1,91 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id') ?? null;
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][V2][Pay][Mini][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId));
+
+ Logger::info('[Wechat][V2][Pay][Mini][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appId' => $this->getAppId($payload, $config),
+ 'timeStamp' => time().'',
+ 'nonceStr' => Str::random(32),
+ 'package' => 'prepay_id='.$prepayId,
+ 'signType' => 'MD5',
+ ]);
+
+ $invokeConfig->set('paySign', get_wechat_sign_v2($config, $invokeConfig->all()));
+
+ return $invokeConfig;
+ }
+
+ protected function getAppId(?Collection $payload, array $config): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_mini_app_id'] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config['mini_app_id'] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php
new file mode 100644
index 0000000..831cf4b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php
@@ -0,0 +1,51 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'secapi/pay/reverse',
+ '_content_type' => 'application/xml',
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ 'sign_type' => 'MD5',
+ ]);
+
+ Logger::info('[Wechat][V2][Pay][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/PayPlugin.php
new file mode 100644
index 0000000..e3961f0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/PayPlugin.php
@@ -0,0 +1,51 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'pay/micropay',
+ '_content_type' => 'application/xml',
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ 'sign_type' => 'MD5',
+ ]);
+
+ Logger::info('[Wechat][V2][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php
new file mode 100644
index 0000000..0262578
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php
@@ -0,0 +1,51 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload([
+ '_url' => 'pay/orderquery',
+ '_content_type' => 'application/xml',
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'nonce_str' => Str::random(32),
+ 'sign_type' => 'MD5',
+ ]);
+
+ Logger::info('[Wechat][V2][Pay][Pos][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Redpack/SendPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Redpack/SendPlugin.php
new file mode 100644
index 0000000..453c6c2
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/Pay/Redpack/SendPlugin.php
@@ -0,0 +1,81 @@
+ $rocket]);
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config, $params);
+ }
+
+ $rocket->setPacker(XmlPacker::class)
+ ->mergePayload(array_merge(
+ [
+ '_url' => 'mmpaymkttransfers/sendredpack',
+ '_content_type' => 'application/xml',
+ 'nonce_str' => Str::random(32),
+ '_http' => [
+ 'ssl_key' => $config['mch_secret_cert'],
+ 'cert' => $config['mch_public_cert_path'],
+ ],
+ ],
+ $data ?? $this->normal($config, $params)
+ ));
+
+ Logger::info('[Wechat][V2][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config, array $params): array
+ {
+ return [
+ 'wxappid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config, array $params): array
+ {
+ $wechatTypeKey = get_wechat_type_key($params);
+
+ return [
+ 'wxappid' => $config[$wechatTypeKey] ?? '',
+ 'mch_id' => $config['mch_id'] ?? '',
+ 'sub_mch_id' => $payload->get('sub_mch_id', $config['sub_mch_id'] ?? ''),
+ 'msgappid' => $config['sub_'.$wechatTypeKey],
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/VerifySignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/VerifySignaturePlugin.php
new file mode 100644
index 0000000..9c7031e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V2/VerifySignaturePlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+
+ if (!should_do_http_request($rocket->getDirection())) {
+ return $rocket;
+ }
+
+ verify_wechat_sign_v2($config, $rocket->getDestination()?->all() ?? []);
+
+ Logger::info('[Wechat][V2][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/AddPayloadSignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/AddPayloadSignaturePlugin.php
new file mode 100644
index 0000000..06d365b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/AddPayloadSignaturePlugin.php
@@ -0,0 +1,99 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+
+ $timestamp = time();
+ $random = Str::random(32);
+ $signContent = $this->getSignatureContent($config, $payload, $timestamp, $random);
+ $signature = $this->getSignature($config, $timestamp, $random, $signContent);
+
+ $rocket->mergePayload(['_authorization' => $signature]);
+
+ Logger::info('[Wechat][V3][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getSignatureContent(array $config, ?Collection $payload, int $timestamp, string $random): string
+ {
+ $url = get_wechat_url($config, $payload);
+ $urlPath = parse_url($url, PHP_URL_PATH);
+ $urlQuery = parse_url($url, PHP_URL_QUERY);
+
+ return get_wechat_method($payload)."\n"
+ .$urlPath.(empty($urlQuery) ? '' : '?'.$urlQuery)."\n"
+ .$timestamp."\n"
+ .$random."\n"
+ .get_wechat_body($payload)."\n";
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ protected function getSignature(array $config, int $timestamp, string $random, string $contents): string
+ {
+ $mchPublicCertPath = $config['mch_public_cert_path'] ?? null;
+
+ if (empty($mchPublicCertPath)) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_public_cert_path]');
+ }
+
+ $ssl = openssl_x509_parse(get_public_cert($mchPublicCertPath));
+
+ if (empty($ssl['serialNumberHex'])) {
+ throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 解析微信配置 [mch_public_cert_path] 出错');
+ }
+
+ $auth = sprintf(
+ 'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
+ $config['mch_id'] ?? '',
+ $random,
+ $timestamp,
+ $ssl['serialNumberHex'],
+ get_wechat_sign($config, $contents),
+ );
+
+ return 'WECHATPAY2-SHA256-RSA2048 '.$auth;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/CallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/CallbackPlugin.php
new file mode 100644
index 0000000..107924c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/CallbackPlugin.php
@@ -0,0 +1,76 @@
+ $rocket]);
+
+ $this->init($rocket);
+
+ $params = $rocket->getParams();
+
+ /* @phpstan-ignore-next-line */
+ verify_wechat_sign($rocket->getDestinationOrigin(), $params);
+
+ $body = json_decode((string) $rocket->getDestination()->getBody(), true);
+
+ $rocket->setDirection(NoHttpRequestDirection::class)->setPayload(new Collection($body));
+
+ $body['resource'] = decrypt_wechat_resource($body['resource'] ?? [], get_provider_config('wechat', $params));
+
+ $rocket->setDestination(new Collection($body));
+
+ Logger::info('[Wechat][V3][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function init(Rocket $rocket): void
+ {
+ $request = $rocket->getParams()['_request'] ?? null;
+ $params = $rocket->getParams()['_params'] ?? [];
+
+ if (!$request instanceof ServerRequestInterface) {
+ throw new InvalidParamsException(Exception::PARAMS_CALLBACK_REQUEST_INVALID, '参数异常: 微信回调参数不正确');
+ }
+
+ $rocket->setDestination(clone $request)
+ ->setDestinationOrigin($request)
+ ->setParams($params);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/CompletePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/CompletePlugin.php
new file mode 100644
index 0000000..9e6aeb6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/CompletePlugin.php
@@ -0,0 +1,52 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $complaintId = $payload?->get('complaint_id') ?? null;
+
+ if (empty($complaintId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 反馈处理完成,参数缺少 `complaint_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'POST',
+ '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/complete',
+ '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/complete',
+ 'complainted_mchid' => $payload->get('complainted_mchid') ?? $config['mch_id'],
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][CompletePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/DeleteCallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/DeleteCallbackPlugin.php
new file mode 100644
index 0000000..4a29260
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/DeleteCallbackPlugin.php
@@ -0,0 +1,32 @@
+ $rocket]);
+
+ $rocket->setPayload([
+ '_method' => 'DELETE',
+ '_url' => 'v3/merchant-service/complaint-notifications',
+ '_service_url' => 'v3/merchant-service/complaint-notifications',
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][DeleteCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryCallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryCallbackPlugin.php
new file mode 100644
index 0000000..aa636ce
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryCallbackPlugin.php
@@ -0,0 +1,32 @@
+ $rocket]);
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/merchant-service/complaint-notifications',
+ '_service_url' => 'v3/merchant-service/complaint-notifications',
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][QueryCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryDetailPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryDetailPlugin.php
new file mode 100644
index 0000000..5764716
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryDetailPlugin.php
@@ -0,0 +1,72 @@
+ $rocket]);
+
+ $complaintId = $rocket->getPayload()?->get('complaint_id') ?? null;
+
+ if (empty($complaintId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询投诉单详情,参数缺少 `complaint_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId,
+ '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId,
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ /** @var Rocket $rocket */
+ $rocket = $next($rocket);
+
+ Logger::debug('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件开始后置装载', ['rocket' => $rocket]);
+
+ $destination = $rocket->getDestination();
+
+ if ($destination instanceof Collection && !empty($payerPhone = $destination->get('payer_phone'))) {
+ $decryptPayerPhone = decrypt_wechat_contents($payerPhone, get_provider_config('wechat', $rocket->getParams()));
+
+ if (empty($decryptPayerPhone)) {
+ throw new InvalidConfigException(Exception::DECRYPT_WECHAT_ENCRYPTED_CONTENTS_INVALID, '参数异常: 查询投诉单详情,参数 `payer_phone` 解密失败');
+ }
+
+ $destination->set('payer_phone', $decryptPayerPhone);
+ }
+
+ Logger::debug('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件后置装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryImagePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryImagePlugin.php
new file mode 100644
index 0000000..ab22edb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryImagePlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $mediaId = $rocket->getPayload()?->get('media_id') ?? null;
+
+ if (empty($mediaId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 图片请求接口,参数缺少 `media_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/merchant-service/images/'.$mediaId,
+ '_service_url' => 'v3/merchant-service/images/'.$mediaId,
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][QueryImagePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryNegotiationPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryNegotiationPlugin.php
new file mode 100644
index 0000000..43a771e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryNegotiationPlugin.php
@@ -0,0 +1,56 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $complaintId = $payload?->get('complaint_id') ?? null;
+
+ if (empty($complaintId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询投诉单协商历史,参数缺少 `complaint_id`');
+ }
+
+ $query = $this->normal($payload);
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/negotiation-historys'.$query,
+ '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/negotiation-historys'.$query,
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][QueryNegotiationPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(Collection $payload): string
+ {
+ $query = filter_params($payload)->except('complaint_id')->query();
+
+ return empty($query) ? '' : '?'.$query;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryPlugin.php
new file mode 100644
index 0000000..190eb3d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/QueryPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询投诉单列表,缺少必要参数');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/merchant-service/complaints-v2?'.$query,
+ '_service_url' => 'v3/merchant-service/complaints-v2?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/ResponsePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/ResponsePlugin.php
new file mode 100644
index 0000000..6ed6050
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/ResponsePlugin.php
@@ -0,0 +1,52 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $complaintId = $payload?->get('complaint_id') ?? null;
+
+ if (empty($complaintId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 回复用户,参数缺少 `complaint_id`');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/response',
+ '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/response',
+ 'complainted_mchid' => $payload->get('complainted_mchid', $config['mch_id']),
+ ])->exceptPayload('complaint_id');
+
+ Logger::info('[Wechat][Extend][Complaints][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/SetCallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/SetCallbackPlugin.php
new file mode 100644
index 0000000..cc366fa
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/SetCallbackPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ $rocket->setPayload([
+ '_method' => 'POST',
+ '_url' => 'v3/merchant-service/complaint-notifications',
+ '_service_url' => 'v3/merchant-service/complaint-notifications',
+ 'url' => $payload?->get('url', '') ?? '',
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][SetCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateCallbackPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateCallbackPlugin.php
new file mode 100644
index 0000000..06a41e5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateCallbackPlugin.php
@@ -0,0 +1,35 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ $rocket->setPayload([
+ '_method' => 'PUT',
+ '_url' => 'v3/merchant-service/complaint-notifications',
+ '_service_url' => 'v3/merchant-service/complaint-notifications',
+ 'url' => $payload?->get('url', '') ?? '',
+ ]);
+
+ Logger::info('[Wechat][Extend][Complaints][UpdateCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateRefundPlugin.php
new file mode 100644
index 0000000..6dcf317
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/Complaints/UpdateRefundPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $complaintId = $rocket->getPayload()?->get('complaint_id') ?? null;
+
+ if (empty($complaintId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 更新退款审批结果,参数缺少 `complaint_id`');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/update-refund-progress',
+ '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/update-refund-progress',
+ ])->exceptPayload('complaint_id');
+
+ Logger::info('[Wechat][Extend][Complaints][UpdateRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/AddReceiverPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/AddReceiverPlugin.php
new file mode 100644
index 0000000..f3877c5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/AddReceiverPlugin.php
@@ -0,0 +1,134 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ 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, $params, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/profitsharing/receivers/add',
+ '_service_url' => 'v3/profitsharing/receivers/add',
+ ],
+ $data ?? $this->normal($payload, $params, $config),
+ ));
+
+ Logger::info('[Wechat][Extend][ProfitSharing][AddReceiverPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function normal(Collection $payload, array $params, array $config): array
+ {
+ $data = [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ ];
+
+ if (!$payload->has('name')) {
+ return $data;
+ }
+
+ return array_merge($data, $this->encryptSensitiveData($params, $config, $payload));
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function service(Collection $payload, array $params, array $config): array
+ {
+ $wechatTypeKey = get_wechat_type_key($params);
+
+ $data = [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ 'appid' => $config[$wechatTypeKey] ?? '',
+ ];
+
+ if ('PERSONAL_SUB_OPENID' === $payload->get('type')) {
+ $data['sub_appid'] = $config['sub_'.$wechatTypeKey] ?? '';
+ }
+
+ if (!$payload->has('name')) {
+ return $data;
+ }
+
+ return array_merge($data, $this->encryptSensitiveData($params, $config, $payload));
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function encryptSensitiveData(array $params, array $config, Collection $payload): array
+ {
+ $data['_serial_no'] = get_wechat_serial_no($params);
+
+ $config = get_provider_config('wechat', $params);
+ $publicKey = get_wechat_public_key($config, $data['_serial_no']);
+
+ $data['name'] = encrypt_wechat_contents($payload->get('name'), $publicKey);
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/CreatePlugin.php
new file mode 100644
index 0000000..1b3e346
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/CreatePlugin.php
@@ -0,0 +1,136 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ 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, $params, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/profitsharing/orders',
+ '_service_url' => 'v3/profitsharing/orders',
+ ],
+ $data ?? $this->normal($payload, $params, $config),
+ ));
+
+ Logger::info('[Wechat][Extend][ProfitSharing][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function normal(Collection $payload, array $params, array $config): array
+ {
+ $data = [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ ];
+
+ if (!$payload->has('receivers.0.name')) {
+ return $data;
+ }
+
+ return array_merge($data, $this->encryptSensitiveData($params, $config, $payload));
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function service(Collection $payload, array $params, array $config): array
+ {
+ $wechatTypeKey = get_wechat_type_key($params);
+
+ $data = [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ 'appid' => $config[$wechatTypeKey] ?? '',
+ ];
+
+ if ('PERSONAL_SUB_OPENID' === $payload->get('receivers.0.type')) {
+ $data['sub_appid'] = $config['sub_'.$wechatTypeKey] ?? '';
+ }
+
+ if (!$payload->has('receivers.0.name')) {
+ return $data;
+ }
+
+ return array_merge($data, $this->encryptSensitiveData($params, $config, $payload));
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function encryptSensitiveData(array $params, array $config, Collection $payload): array
+ {
+ $data['_serial_no'] = get_wechat_serial_no($params);
+
+ $config = get_provider_config('wechat', $params);
+ $publicKey = get_wechat_public_key($config, $data['_serial_no']);
+
+ foreach ($payload->get('receivers', []) as $key => $list) {
+ $data['receivers'][$key]['name'] = encrypt_wechat_contents($list['name'], $publicKey);
+ }
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DeleteReceiverPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DeleteReceiverPlugin.php
new file mode 100644
index 0000000..07dac6e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DeleteReceiverPlugin.php
@@ -0,0 +1,84 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ 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, $params, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/profitsharing/receivers/delete',
+ '_service_url' => 'v3/profitsharing/receivers/delete',
+ ],
+ $data ?? $this->normal($params, $config),
+ ));
+
+ Logger::info('[Wechat][Extend][ProfitSharing][DeleteReceiverPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $params, array $config): array
+ {
+ return [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $params, array $config): array
+ {
+ $wechatTypeKEY = get_wechat_type_key($params);
+
+ $data = [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ 'appid' => $config[$wechatTypeKEY] ?? '',
+ ];
+
+ if ('PERSONAL_SUB_OPENID' === $payload->get('type')) {
+ $data['sub_appid'] = $config['sub_'.$wechatTypeKEY] ?? '';
+ }
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DownloadBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DownloadBillPlugin.php
new file mode 100644
index 0000000..c98b46d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/DownloadBillPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载电子回单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][Extend][ProfitSharing][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/GetBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/GetBillPlugin.php
new file mode 100644
index 0000000..09bac2d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/GetBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 分账 申请账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/profitsharing/bills?'.$query,
+ '_service_url' => 'v3/profitsharing/bills?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Extend][ProfitSharing][GetBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryAmountsPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryAmountsPlugin.php
new file mode 100644
index 0000000..ff604f9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryAmountsPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $transactionId = $rocket->getPayload()?->get('transaction_id');
+
+ if (is_null($transactionId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询剩余待分金额,参数缺少 `transaction_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/profitsharing/transactions/'.$transactionId.'/amounts',
+ '_service_url' => 'v3/profitsharing/transactions/'.$transactionId.'/amounts',
+ ]);
+
+ Logger::info('[Wechat][Extend][ProfitSharing][QueryAmountsPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryMerchantConfigsPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryMerchantConfigsPlugin.php
new file mode 100644
index 0000000..3620fa5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryMerchantConfigsPlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $subMchId = $payload?->get('sub_mch_id') ?? $config['sub_mch_id'] ?? 'null';
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 查询最大分账比例,只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_service_url' => 'v3/profitsharing/merchant-configs/'.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][Extend][ProfitSharing][QueryMerchantConfigsPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryPlugin.php
new file mode 100644
index 0000000..02ba6e0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $outOrderNo = $payload?->get('out_order_no') ?? null;
+ $transactionId = $payload?->get('transaction_id') ?? null;
+ $subMchId = $payload?->get('sub_mchid') ?? $config['sub_mch_id'] ?? 'null';
+
+ if (empty($outOrderNo) || empty($transactionId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询分账结果, 缺少必要参数 `out_order_no`, `transaction_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/profitsharing/orders/'.$outOrderNo.'?transaction_id='.$transactionId,
+ '_service_url' => 'v3/profitsharing/orders/'.$outOrderNo.'?sub_mchid='.$subMchId.'&transaction_id='.$transactionId,
+ ]);
+
+ Logger::info('[Wechat][Extend][ProfitSharing][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryReturnPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryReturnPlugin.php
new file mode 100644
index 0000000..5509b2d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/QueryReturnPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $outOrderNo = $payload?->get('out_order_no') ?? null;
+ $outReturnNo = $payload?->get('out_return_no') ?? null;
+ $subMchId = $payload?->get('sub_mchid') ?? $config['sub_mch_id'] ?? 'null';
+
+ if (empty($outOrderNo) || empty($outReturnNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询分账结果, 缺少必要参数 `out_order_no`, `out_return_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/profitsharing/return-orders/'.$outReturnNo.'?out_order_no='.$outOrderNo,
+ '_service_url' => 'v3/profitsharing/return-orders/'.$outReturnNo.'?sub_mchid='.$subMchId.'&out_order_no='.$outOrderNo,
+ ]);
+
+ Logger::info('[Wechat][Extend][ProfitSharing][QueryReturnPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/ReturnPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/ReturnPlugin.php
new file mode 100644
index 0000000..dd8c616
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/ReturnPlugin.php
@@ -0,0 +1,75 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ 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' => 'v3/profitsharing/return-orders',
+ '_service_url' => 'v3/profitsharing/return-orders',
+ ],
+ $data ?? $this->normal($payload, $config),
+ ));
+
+ Logger::info('[Wechat][Extend][ProfitSharing][ReturnPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(Collection $payload, array $config): array
+ {
+ return [
+ 'return_mchid' => $payload->get('return_mchid', $config['mch_id'] ?? ''),
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ 'return_mchid' => $payload->get('return_mchid', $config['mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/UnfreezePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/UnfreezePlugin.php
new file mode 100644
index 0000000..3c85140
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Extend/ProfitSharing/UnfreezePlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ 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' => 'v3/profitsharing/orders/unfreeze',
+ '_service_url' => 'v3/profitsharing/orders/unfreeze',
+ ],
+ $data ?? $this->normal(),
+ ));
+
+ Logger::info('[Wechat][Extend][ProfitSharing][UnfreezePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/QueryPlugin.php
new file mode 100644
index 0000000..d64a302
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/QueryPlugin.php
@@ -0,0 +1,44 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $mchId = $rocket->getPayload()?->get('mchid') ?? $config['mch_id'] ?? 'null';
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/callbacks?mchid='.$mchId,
+ '_service_url' => 'v3/marketing/favor/callbacks?mchid='.$mchId,
+ ]);
+
+ Logger::info('[Wechat][Marketing][Coupon][Callback][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/SetPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/SetPlugin.php
new file mode 100644
index 0000000..e066ecb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Callback/SetPlugin.php
@@ -0,0 +1,48 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/marketing/favor/callbacks',
+ '_service_url' => 'v3/marketing/favor/callbacks',
+ 'mchid' => $payload?->get('mchid') ?? $config['mch_id'] ?? '',
+ 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '',
+ ],
+ ));
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Callback][SetPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/DetailPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/DetailPlugin.php
new file mode 100644
index 0000000..84a2577
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/DetailPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $openId = $payload?->get('openid') ?? null;
+ $couponId = $payload?->get('coupon_id') ?? null;
+ $appId = $payload?->get('appid') ?? $config[get_wechat_type_key($params)] ?? 'null';
+
+ if (empty($openId) || empty($couponId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券详情,参数缺少 `openid` 或 `coupon_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/users/'.$openId.'/coupons/'.$couponId.'?appid='.$appId,
+ '_service_url' => 'v3/marketing/favor/users/'.$openId.'/coupons/'.$couponId.'?appid='.$appId,
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Coupons][DetailPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/QueryUserPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/QueryUserPlugin.php
new file mode 100644
index 0000000..dc94d70
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/QueryUserPlugin.php
@@ -0,0 +1,66 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $openId = $payload?->get('openid') ?? null;
+
+ if (empty($openId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 根据商户号查用户的券,参数缺少 `openid`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/users/'.$openId.'/coupons?'.$this->normal($payload, $params, $config),
+ '_service_url' => 'v3/marketing/favor/users/'.$openId.'/coupons?'.$this->normal($payload, $params, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Coupons][QueryUserPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(Collection $payload, array $params, array $config): string
+ {
+ $appId = $payload->get('appid');
+
+ if (is_null($appId)) {
+ $payload->set('appid', $config[get_wechat_type_key($params)] ?? '');
+ }
+
+ return filter_params($payload)->except('openid')->query();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/SendPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/SendPlugin.php
new file mode 100644
index 0000000..178690e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Coupons/SendPlugin.php
@@ -0,0 +1,70 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $openId = $payload?->get('openid') ?? null;
+
+ if (empty($openId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发放指定批次的代金券,参数缺少 `openid`');
+ }
+
+ $rocket->setPayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/marketing/favor/users/'.$openId.'/coupons',
+ '_service_url' => 'v3/marketing/favor/users/'.$openId.'/coupons',
+ ],
+ $this->normal($payload, $params, $config),
+ ));
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Coupons][SendPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(Collection $payload, array $params, array $config): array
+ {
+ if (empty($payload->get('appid'))) {
+ $payload->set('appid', $config[get_wechat_type_key($params)] ?? '');
+ }
+
+ if (empty($payload->get('stock_creator_mchid'))) {
+ $payload->set('stock_creator_mchid', $config['mch_id'] ?? '');
+ }
+
+ return $payload->except('openid')->all();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/CreatePlugin.php
new file mode 100644
index 0000000..3d34944
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/CreatePlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $belongMerchant = $rocket->getPayload()?->get('belong_merchant') ?? $config['mch_id'];
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/marketing/favor/coupon-stocks',
+ '_service_url' => 'v3/marketing/favor/coupon-stocks',
+ 'belong_merchant' => $belongMerchant,
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/PausePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/PausePlugin.php
new file mode 100644
index 0000000..780b1a1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/PausePlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $stockId = $payload?->get('stock_id') ?? null;
+ $stockCreatorMchId = $payload?->get('stock_creator_mchid') ?? $config['mch_id'] ?? '';
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 暂停代金券批次,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'POST',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/pause',
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/pause',
+ 'stock_creator_mchid' => $stockCreatorMchId,
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][PausePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryDetailPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryDetailPlugin.php
new file mode 100644
index 0000000..4b311d4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryDetailPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $stockId = $payload?->get('stock_id') ?? null;
+ $mchId = $payload?->get('stock_creator_mchid') ?? $config['mch_id'] ?? 'null';
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券批次详情,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'?stock_creator_mchid='.$mchId,
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'?stock_creator_mchid='.$mchId,
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryDetailPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryItemsPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryItemsPlugin.php
new file mode 100644
index 0000000..ef68a98
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryItemsPlugin.php
@@ -0,0 +1,65 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $stockId = $payload?->get('stock_id') ?? null;
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券可用单品,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/items?'.$this->normal($payload, $config),
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/items?'.$this->normal($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryItemsPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ public function normal(Collection $payload, array $config): string
+ {
+ $stockCreatorMchId = $payload->get('stock_creator_mchid');
+
+ if (is_null($stockCreatorMchId)) {
+ $payload->set('stock_creator_mchid', $config['mch_id'] ?? '');
+ }
+
+ return filter_params($payload)->except('stock_id')->query();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryMerchantsPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryMerchantsPlugin.php
new file mode 100644
index 0000000..0777a46
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryMerchantsPlugin.php
@@ -0,0 +1,65 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $stockId = $payload?->get('stock_id') ?? null;
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券可用商户,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/merchants?'.$this->normal($payload, $config),
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/merchants?'.$this->normal($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryMerchantsPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ public function normal(Collection $payload, array $config): string
+ {
+ $stockCreatorMchId = $payload->get('stock_creator_mchid');
+
+ if (is_null($stockCreatorMchId)) {
+ $payload->set('stock_creator_mchid', $config['mch_id'] ?? '');
+ }
+
+ return filter_params($payload)->except('stock_id')->query();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryPlugin.php
new file mode 100644
index 0000000..503c238
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryPlugin.php
@@ -0,0 +1,64 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少代金券相关参数');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/stocks?'.$this->normal($payload, $config),
+ '_service_url' => 'v3/marketing/favor/stocks?'.$this->normal($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ public function normal(Collection $payload, array $config): string
+ {
+ $stockCreatorMchId = $payload->get('stock_creator_mchid');
+
+ if (is_null($stockCreatorMchId)) {
+ $payload->set('stock_creator_mchid', $config['mch_id'] ?? '');
+ }
+
+ return filter_params($payload)->query();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryRefundFlowPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryRefundFlowPlugin.php
new file mode 100644
index 0000000..243fa30
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryRefundFlowPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $stockId = $rocket->getPayload()?->get('stock_id') ?? null;
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载批次退款明细,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/refund-flow',
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/refund-flow',
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryRefundFlowPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryUseFlowPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryUseFlowPlugin.php
new file mode 100644
index 0000000..0de6bc4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryUseFlowPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $stockId = $rocket->getPayload()?->get('stock_id') ?? null;
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载批次核销明细,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/use-flow',
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/use-flow',
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryUseFlowPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/RestartPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/RestartPlugin.php
new file mode 100644
index 0000000..c1393dc
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/RestartPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $stockId = $payload?->get('stock_id') ?? null;
+ $stockCreatorMchId = $payload?->get('stock_creator_mchid') ?? $config['mch_id'] ?? '';
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 激活代金券,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'POST',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/restart',
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/restart',
+ 'stock_creator_mchid' => $stockCreatorMchId,
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][RestartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/StartPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/StartPlugin.php
new file mode 100644
index 0000000..8fae020
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Coupon/Stock/StartPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $stockId = $payload?->get('stock_id') ?? null;
+
+ if (empty($stockId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 激活代金券,参数缺少 `stock_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'POST',
+ '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/start',
+ '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/start',
+ 'stock_creator_mchid' => $payload->get('stock_creator_mchid') ?? $config['mch_id'] ?? '',
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Coupon][Stock][StartPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryDayEndPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryDayEndPlugin.php
new file mode 100644
index 0000000..c1a5c82
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryDayEndPlugin.php
@@ -0,0 +1,67 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $accountType = $payload?->get('account_type') ?? null;
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 查询电商平台账户日终余额,只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ if (empty($accountType)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电商平台账户日终余额,参数缺少 `account_type`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_service_url' => 'v3/merchant/fund/dayendbalance/'.$accountType.$this->service($payload),
+ ]);
+
+ Logger::info('[Wechat][Marketing][ECommerceBalance][QueryDayEndPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function service(Collection $payload): string
+ {
+ $query = filter_params($payload)->except('account_type');
+
+ if ($query->isEmpty()) {
+ return '';
+ }
+
+ return '?'.$query->query();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryPlugin.php
new file mode 100644
index 0000000..942064a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryPlugin.php
@@ -0,0 +1,53 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $accountType = $rocket->getPayload()?->get('account_type') ?? null;
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 查询电商平台账户实时余额,只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ if (empty($accountType)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电商平台账户实时余额,参数缺少 `account_type`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_service_url' => 'v3/merchant/fund/balance/'.$accountType,
+ ]);
+
+ Logger::info('[Wechat][Marketing][ECommerceBalance][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ApplyPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ApplyPlugin.php
new file mode 100644
index 0000000..c7aa7d3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ApplyPlugin.php
@@ -0,0 +1,60 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+ $subMchId = $payload?->get('sub_mchid') ?? $config['sub_mch_id'] ?? '';
+ $spAppId = $payload?->get('sp_appid') ?? $config[get_wechat_type_key($params)] ?? '';
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-申请退款,只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-申请退款,缺少必要参数');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_service_url' => 'v3/ecommerce/refunds/apply',
+ 'sub_mchid' => $subMchId,
+ 'sp_appid' => $spAppId,
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][ECommerceRefund][ApplyPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryByWxPlugin.php
new file mode 100644
index 0000000..6caf710
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryByWxPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+ $refundId = $payload?->get('refund_id') ?? null;
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-查询单笔退款(按微信支付退款单号),只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ if (is_null($refundId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-查询单笔退款(按微信支付退款单号),缺少必要参数 `refund_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_service_url' => 'v3/ecommerce/refunds/id/'.$refundId.'?sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'),
+ ]);
+
+ Logger::info('[Wechat][Marketing][ECommerceRefund][QueryBatchByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryPlugin.php
new file mode 100644
index 0000000..00f910f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-查询单笔退款(按商户退款单号),只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ if (is_null($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-查询单笔退款(按商户退款单号),缺少必要参数 `out_refund_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_service_url' => 'v3/ecommerce/refunds/out-refund-no/'.$outRefundNo.'?sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][ECommerceRefund][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryReturnAdvancePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryReturnAdvancePlugin.php
new file mode 100644
index 0000000..f4a22a3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryReturnAdvancePlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+ $refundId = $payload?->get('refund_id') ?? null;
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-查询垫付回补结果,只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ if (is_null($refundId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-查询垫付回补结果,缺少必要参数 `refund_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_service_url' => 'v3/ecommerce/refunds/'.$refundId.'/return-advance?sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id']),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][ECommerceRefund][QueryReturnAdvancePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ReturnAdvancePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ReturnAdvancePlugin.php
new file mode 100644
index 0000000..9a256f5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/ECommerceRefund/ReturnAdvancePlugin.php
@@ -0,0 +1,56 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+ $refundId = $payload?->get('refund_id') ?? null;
+
+ if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-垫付退款回补,只支持服务商模式,当前配置为普通商户模式');
+ }
+
+ if (is_null($refundId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-垫付退款回补,缺少必要参数 `refund_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'POST',
+ '_service_url' => 'v3/ecommerce/refunds/'.$refundId.'/return-advance',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][ECommerceRefund][ReturnAdvancePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/CreatePlugin.php
new file mode 100644
index 0000000..c197545
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/CreatePlugin.php
@@ -0,0 +1,80 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+
+ $rocket->mergePayload(array_merge([
+ '_method' => 'POST',
+ '_url' => 'v3/new-tax-control-fapiao/fapiao-applications',
+ ], $this->encryptSensitiveData($payload, $params, $config)));
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ * @throws DecryptException
+ */
+ protected function encryptSensitiveData(?Collection $payload, array $params, array $config): array
+ {
+ $data['_serial_no'] = get_wechat_serial_no($params);
+
+ $config = get_provider_config('wechat', $params);
+ $publicKey = get_wechat_public_key($config, $data['_serial_no']);
+
+ $phone = $payload?->get('buyer_information.phone') ?? null;
+ $email = $payload?->get('buyer_information.email') ?? null;
+
+ if (!is_null($phone)) {
+ $data['buyer_information']['phone'] = encrypt_wechat_contents($phone, $publicKey);
+ }
+
+ if (!is_null($email)) {
+ $data['buyer_information']['email'] = encrypt_wechat_contents($email, $publicKey);
+ }
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/DownloadPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/DownloadPlugin.php
new file mode 100644
index 0000000..485029e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/DownloadPlugin.php
@@ -0,0 +1,46 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $downloadUrl = $payload?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载发票文件,缺少 `download_url` 参数');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl.'&'.filter_params($payload)->except('download_url')->query(),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][DownloadPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetBaseInformationPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetBaseInformationPlugin.php
new file mode 100644
index 0000000..a4df4f8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetBaseInformationPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/new-tax-control-fapiao/merchant/base-information',
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][GetBaseInformationPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetDownloadInfoPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetDownloadInfoPlugin.php
new file mode 100644
index 0000000..27d5d8f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetDownloadInfoPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $applyId = $payload?->get('fapiao_apply_id') ?? null;
+ $fapiaoId = $payload?->get('fapiao_id') ?? null;
+
+ if (empty($applyId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电子发票,参数缺少 `fapiao_apply_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/new-tax-control-fapiao/fapiao-applications/'.$applyId.'/fapiao-files'.(empty($fapiaoId) ? '' : '?fapiao_id='.$fapiaoId),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][GetDownloadInfoPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetTaxCodePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetTaxCodePlugin.php
new file mode 100644
index 0000000..c40a3a1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetTaxCodePlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $offset = $payload?->get('offset') ?? null;
+ $limit = $payload?->get('limit') ?? null;
+
+ if (empty($offset) || empty($limit)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 获取商户可开具的商品和服务税收分类编码对照表,缺少 `offset` 或 `limit` 参数');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/new-tax-control-fapiao/merchant/tax-codes?offset='.$offset.'&limit='.$limit,
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][GetTaxCodePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/ReversePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/ReversePlugin.php
new file mode 100644
index 0000000..170ca48
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/ReversePlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $applyId = $payload?->get('fapiao_apply_id') ?? null;
+
+ if (empty($applyId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 冲红电子发票,参数缺少 `fapiao_apply_id`');
+ }
+
+ $rocket->setPayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/new-tax-control-fapiao/fapiao-applications/'.$applyId.'/reverse',
+ ],
+ filter_params($payload)->except('fapiao_apply_id')->all(),
+ ));
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][ReversePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/CreateCardTemplatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/CreateCardTemplatePlugin.php
new file mode 100644
index 0000000..d7436f1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/CreateCardTemplatePlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/new-tax-control-fapiao/card-template',
+ 'card_appid' => $payload?->get('card_appid') ?? $config['mp_app_id'],
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][CreateCardTemplatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/GetTitleUrlPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/GetTitleUrlPlugin.php
new file mode 100644
index 0000000..ed6ccbf
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/GetTitleUrlPlugin.php
@@ -0,0 +1,64 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+
+ if (empty($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 获取抬头填写链接,缺少必要参数');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/new-tax-control-fapiao/user-title/title-url?'.$this->getQuery($payload, $params)->query(),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][GetTitleUrlPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws ServiceNotFoundException
+ */
+ protected function getQuery(Collection $payload, array $params): Collection
+ {
+ $config = get_provider_config('wechat', $params);
+
+ return filter_params($payload)->merge([
+ 'appid' => $payload->get('appid', $config[get_wechat_type_key($params)] ?? ''),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryConfigPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryConfigPlugin.php
new file mode 100644
index 0000000..7ac20b2
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryConfigPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/new-tax-control-fapiao/merchant/development-config',
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][QueryConfigPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryPlugin.php
new file mode 100644
index 0000000..c0863f0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $applyId = $payload?->get('fapiao_apply_id') ?? null;
+ $fapiaoId = $payload?->get('fapiao_id') ?? null;
+
+ if (empty($applyId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电子发票,参数缺少 `fapiao_apply_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/new-tax-control-fapiao/fapiao-applications/'.$applyId.(empty($fapiaoId) ? '' : '?fapiao_id='.$fapiaoId),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryUserTitlePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryUserTitlePlugin.php
new file mode 100644
index 0000000..750cce3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/QueryUserTitlePlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (empty($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 获取用户填写的抬头,缺少必要参数');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/new-tax-control-fapiao/user-title?'.filter_params($payload)->query(),
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][QueryUserTitlePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/UpdateConfigPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/UpdateConfigPlugin.php
new file mode 100644
index 0000000..72a2d4c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Fapiao/UpdateConfigPlugin.php
@@ -0,0 +1,30 @@
+ $rocket]);
+
+ $rocket->mergePayload([
+ '_method' => 'PATCH',
+ '_url' => 'v3/new-tax-control-fapiao/merchant/development-config',
+ ]);
+
+ Logger::info('[Wechat][V3][Marketing][Fapiao][UpdateConfigPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php
new file mode 100644
index 0000000..91cd699
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $outBillNo = $payload?->get('out_bill_no') ?? null;
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 撤销转账,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($outBillNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 撤销转账,参数缺少 `out_bill_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'POST',
+ '_url' => 'v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$outBillNo.'/cancel',
+ ]);
+
+ Logger::info('[Wechat][Marketing][MchTransfer][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php
new file mode 100644
index 0000000..fd2de0c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php
@@ -0,0 +1,102 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 发起商家转账,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (is_null($payload) || $payload->isEmpty()) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发起商家转账参数,参数缺失');
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/fund-app/mch-transfer/transfer-bills',
+ 'appid' => $payload->get('appid', $config[get_wechat_type_key($params)] ?? ''),
+ ],
+ $this->normal($params, $payload)
+ ));
+
+ Logger::info('[Wechat][Marketing][MchTransfer][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ */
+ protected function normal(array $params, Collection $payload): array
+ {
+ if (!$payload->has('user_name')) {
+ return [];
+ }
+
+ return $this->encryptSensitiveData($params, $payload);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function encryptSensitiveData(array $params, Collection $payload): array
+ {
+ $data['_serial_no'] = get_wechat_serial_no($params);
+
+ $config = get_provider_config('wechat', $params);
+ $publicKey = get_wechat_public_key($config, $data['_serial_no']);
+
+ $data['user_name'] = encrypt_wechat_contents($payload->get('user_name'), $publicKey);
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php
new file mode 100644
index 0000000..b8ac3ea
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php
@@ -0,0 +1,77 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $destination = $rocket->getDestination();
+ $packageInfo = $destination?->get('package_info');
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: Android调起用户确认收款,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (is_null($packageInfo)) {
+ Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeAndroidPlugin] Android调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'Android调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo));
+
+ Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeAndroidPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config
+ {
+ return new Config([
+ 'businessType' => 'requestMerchantTransfer',
+ 'query' => http_build_query([
+ 'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '',
+ 'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '',
+ 'package' => $packageInfo,
+ ]),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php
new file mode 100644
index 0000000..f124cc9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php
@@ -0,0 +1,77 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $destination = $rocket->getDestination();
+ $packageInfo = $destination?->get('package_info');
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: iOS调起用户确认收款,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (is_null($packageInfo)) {
+ Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeIosPlugin] iOS调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'iOS调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo));
+
+ Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeIosPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config
+ {
+ return new Config([
+ 'businessType' => 'requestMerchantTransfer',
+ 'query' => http_build_query([
+ 'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '',
+ 'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '',
+ 'package' => $packageInfo,
+ ]),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeJsapiPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeJsapiPlugin.php
new file mode 100644
index 0000000..ac7c5ac
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeJsapiPlugin.php
@@ -0,0 +1,74 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $destination = $rocket->getDestination();
+ $packageInfo = $destination?->get('package_info');
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: JSAPI调起用户确认收款,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (is_null($packageInfo)) {
+ Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeJsapiPlugin] JSAPI调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'JSAPI调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo));
+
+ Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeJsapiPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config
+ {
+ return new Config([
+ 'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '',
+ 'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '',
+ 'package' => $packageInfo,
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryByWxPlugin.php
new file mode 100644
index 0000000..a112900
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryByWxPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $transferBillNo = $payload?->get('transfer_bill_no') ?? null;
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过微信单号查询转账单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($transferBillNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过微信单号查询转账单,参数缺少 `transfer_bill_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/'.$transferBillNo,
+ ]);
+
+ Logger::info('[Wechat][Marketing][MchTransfer][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryPlugin.php
new file mode 100644
index 0000000..f7b8020
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/MchTransfer/QueryPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $outBillNo = $payload?->get('out_bill_no') ?? null;
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过商户单号查询转账单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($outBillNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过商户单号查询转账单,参数缺少 `out_bill_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$outBillNo,
+ ]);
+
+ Logger::info('[Wechat][Marketing][MchTransfer][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryByWxPlugin.php
new file mode 100644
index 0000000..d0e9490
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryByWxPlugin.php
@@ -0,0 +1,57 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $batchId = $payload?->get('batch_id') ?? null;
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过微信批次单号查询批次单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($batchId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过微信批次单号查询批次单,参数缺少 `batch_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/transfer/batches/batch-id/'.$batchId.'?'.filter_params($payload)->except('batch_id')->query(),
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][Batch][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryPlugin.php
new file mode 100644
index 0000000..d787f3e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryPlugin.php
@@ -0,0 +1,57 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $outBatchNo = $payload?->get('out_batch_no') ?? null;
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过商家批次单号查询批次单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($outBatchNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过商家批次单号查询批次单,参数缺少 `out_batch_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/transfer/batches/out-batch-no/'.$outBatchNo.'?'.filter_params($payload)->except('out_batch_no')->query(),
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][Batch][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/CreatePlugin.php
new file mode 100644
index 0000000..a617255
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/CreatePlugin.php
@@ -0,0 +1,109 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 发起商家转账,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (is_null($payload) || $payload->isEmpty()) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发起商家转账参数,参数缺失');
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/transfer/batches',
+ 'appid' => $payload->get('appid', $config[get_wechat_type_key($params)] ?? ''),
+ ],
+ $this->normal($params, $payload)
+ ));
+
+ Logger::info('[Wechat][Marketing][Transfer][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ */
+ protected function normal(array $params, Collection $payload): array
+ {
+ if (!$payload->has('transfer_detail_list.0.user_name')) {
+ return [];
+ }
+
+ return $this->encryptSensitiveData($params, $payload);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function encryptSensitiveData(array $params, Collection $payload): array
+ {
+ $data['transfer_detail_list'] = $payload->get('transfer_detail_list', []);
+ $data['_serial_no'] = get_wechat_serial_no($params);
+
+ $config = get_provider_config('wechat', $params);
+ $publicKey = get_wechat_public_key($config, $data['_serial_no']);
+
+ foreach ($data['transfer_detail_list'] as $key => $list) {
+ if (!empty($list['user_name'])) {
+ $data['transfer_detail_list'][$key]['user_name'] = encrypt_wechat_contents($list['user_name'], $publicKey);
+ }
+ }
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryByWxPlugin.php
new file mode 100644
index 0000000..e2d7505
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryByWxPlugin.php
@@ -0,0 +1,57 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $batchId = $payload?->get('batch_id') ?? null;
+ $detailId = $payload?->get('detail_id') ?? null;
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过微信明细单号查询明细单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($batchId) || empty($detailId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过微信明细单号查询明细单,参数缺少 `batch_id` 或 `detail_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/transfer/batches/batch-id/'.$batchId.'/details/detail-id/'.$detailId,
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][Detail][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryPlugin.php
new file mode 100644
index 0000000..d401a68
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryPlugin.php
@@ -0,0 +1,57 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+ $payload = $rocket->getPayload();
+ $outBatchNo = $payload?->get('out_batch_no') ?? null;
+ $outDetailNo = $payload?->get('out_detail_no') ?? null;
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过商家明细单号查询明细单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($outBatchNo) || empty($outDetailNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过商家明细单号查询明细单,参数缺少 `out_batch_no` 或 `out_detail_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/transfer/batches/out-batch-no/'.$outBatchNo.'/details/out-detail-no/'.$outDetailNo,
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][Detail][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/DownloadReceiptPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/DownloadReceiptPlugin.php
new file mode 100644
index 0000000..049ea5f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/DownloadReceiptPlugin.php
@@ -0,0 +1,57 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+ $config = get_provider_config('wechat', $rocket->getParams());
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 下载电子回单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载电子回单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][DownloadReceiptPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/CreatePlugin.php
new file mode 100644
index 0000000..9775669
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/CreatePlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 转账账单电子回单申请受理接口,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/transfer/bill-receipt',
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][Receipt][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/QueryPlugin.php
new file mode 100644
index 0000000..c4032cc
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/Receipt/QueryPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $outBatchNo = $rocket->getPayload()?->get('out_batch_no') ?? null;
+ $config = get_provider_config('wechat', $rocket->getParams());
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 查询转账账单电子回单接口,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (empty($outBatchNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询转账账单电子回单接口,参数缺少 `out_batch_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/transfer/bill-receipt/'.$outBatchNo,
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][Receipt][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/CreatePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/CreatePlugin.php
new file mode 100644
index 0000000..28cd2a8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/CreatePlugin.php
@@ -0,0 +1,50 @@
+ $rocket]);
+
+ $config = get_provider_config('wechat', $rocket->getParams());
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 受理转账明细电子回单,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/transfer-detail/electronic-receipts',
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][ReceiptDetail][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/QueryPlugin.php
new file mode 100644
index 0000000..efe4670
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/QueryPlugin.php
@@ -0,0 +1,56 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $rocket->getParams());
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 查询转账明细电子回单受理结果API,只支持普通商户模式,当前配置为服务商模式');
+ }
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询转账明细电子回单受理结果API,参数为空');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/transfer-detail/electronic-receipts?'.filter_params($payload)->query(),
+ ]);
+
+ Logger::info('[Wechat][Marketing][Transfer][ReceiptDetail][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/ClosePlugin.php
new file mode 100644
index 0000000..e6209e8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/ClosePlugin.php
@@ -0,0 +1,78 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 关闭订单,参数缺少 `out_trade_no`');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload(array_merge(
+ [
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close',
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close',
+ '_method' => 'POST',
+ ],
+ $data ?? $this->normal($config)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][App][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): array
+ {
+ return [
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/DownloadBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/DownloadBillPlugin.php
new file mode 100644
index 0000000..e9ed826
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/DownloadBillPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 下载交易对账单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][App][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetFundBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetFundBillPlugin.php
new file mode 100644
index 0000000..be05cfd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetFundBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请资金账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/fundflowbill?'.$query,
+ '_service_url' => 'v3/bill/fundflowbill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][App][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetTradeBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetTradeBillPlugin.php
new file mode 100644
index 0000000..dfba012
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/GetTradeBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请交易账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/tradebill?'.$query,
+ '_service_url' => 'v3/bill/tradebill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][App][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/InvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/InvokePlugin.php
new file mode 100644
index 0000000..1347d97
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/InvokePlugin.php
@@ -0,0 +1,111 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id');
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][V3][Pay][App][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId));
+
+ Logger::info('[Wechat][V3][Pay][App][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appid' => $this->getAppId($payload, $config),
+ 'partnerid' => $this->getPartnerId($payload, $config),
+ 'prepayid' => $prepayId,
+ 'package' => 'Sign=WXPay',
+ 'noncestr' => Str::random(32),
+ 'timestamp' => time().'',
+ ]);
+
+ $invokeConfig->set('sign', $this->getSign($invokeConfig, $config));
+
+ return $invokeConfig;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ protected function getSign(Collection $invokeConfig, array $config): string
+ {
+ $contents = $invokeConfig->get('appid', '')."\n"
+ .$invokeConfig->get('timestamp', '')."\n"
+ .$invokeConfig->get('noncestr', '')."\n"
+ .$invokeConfig->get('prepayid', '')."\n";
+
+ return get_wechat_sign($config, $contents);
+ }
+
+ protected function getAppId(?Collection $payload, array $config): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_app_id'] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config['app_id'] ?? '';
+ }
+
+ protected function getPartnerId(?Collection $payload, array $config): string
+ {
+ return $payload?->get('_invoke_partnerid') ?? $config['mch_id'] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/PayPlugin.php
new file mode 100644
index 0000000..2b8008d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/PayPlugin.php
@@ -0,0 +1,78 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: APP下单,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/app',
+ '_service_url' => 'v3/pay/partner/transactions/app',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ ],
+ $data ?? $this->normal($config)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][App][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): array
+ {
+ return [
+ 'appid' => $config['app_id'] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sp_appid' => $config['app_id'] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryByWxPlugin.php
new file mode 100644
index 0000000..b0a7bd7
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryByWxPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $transactionId = $payload?->get('transaction_id') ?? null;
+
+ if (empty($transactionId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 通过微信订单号查询订单,参数缺少 `transaction_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][App][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? 'null',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? 'null',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryPlugin.php
new file mode 100644
index 0000000..eb2ded4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 通过商户订单号查询订单,参数缺少 `out_trade_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][App][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? '',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryRefundPlugin.php
new file mode 100644
index 0000000..9c09f15
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/QueryRefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (empty($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 查询退款订单,参数缺少 `out_refund_no`');
+ }
+
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo,
+ '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][App][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/RefundPlugin.php
new file mode 100644
index 0000000..c66896e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/App/RefundPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 退款申请,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === $config['mode']) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds',
+ '_service_url' => 'v3/refund/domestic/refunds',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ],
+ $data ?? $this->normal()
+ ));
+
+ Logger::info('[Wechat][V3][Pay][App][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/DownloadPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/DownloadPlugin.php
new file mode 100644
index 0000000..3ec1650
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/DownloadPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 下载交易对账单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][Bill][DownloadPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetFundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetFundPlugin.php
new file mode 100644
index 0000000..2f51eb5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetFundPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请资金账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/fundflowbill?'.$query,
+ '_service_url' => 'v3/bill/fundflowbill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][Bill][GetFundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetTradePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetTradePlugin.php
new file mode 100644
index 0000000..d0f54cc
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Bill/GetTradePlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请交易账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/tradebill?'.$query,
+ '_service_url' => 'v3/bill/tradebill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][Bill][GetTradePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppInvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppInvokePlugin.php
new file mode 100644
index 0000000..8a98c15
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppInvokePlugin.php
@@ -0,0 +1,111 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id');
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][Pay][Combine][AppInvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId));
+
+ Logger::info('[Wechat][Pay][Combine][AppInvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appid' => $this->getAppId($payload, $config),
+ 'partnerid' => $this->getPartnerId($payload, $config),
+ 'prepayid' => $prepayId,
+ 'package' => 'Sign=WXPay',
+ 'noncestr' => Str::random(32),
+ 'timestamp' => time().'',
+ ]);
+
+ $invokeConfig->set('sign', $this->getSign($invokeConfig, $config));
+
+ return $invokeConfig;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ protected function getSign(Collection $invokeConfig, array $config): string
+ {
+ $contents = $invokeConfig->get('appid', '')."\n"
+ .$invokeConfig->get('timestamp', '')."\n"
+ .$invokeConfig->get('noncestr', '')."\n"
+ .$invokeConfig->get('prepayid', '')."\n";
+
+ return get_wechat_sign($config, $contents);
+ }
+
+ protected function getAppId(?Collection $payload, array $config): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_app_id'] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config['app_id'] ?? '';
+ }
+
+ protected function getPartnerId(?Collection $payload, array $config): string
+ {
+ return $payload?->get('_invoke_partnerid') ?? $config['mch_id'] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppPayPlugin.php
new file mode 100644
index 0000000..5044c4c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/AppPayPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: APP合单 下单,参数为空');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/combine-transactions/app',
+ '_service_url' => 'v3/combine-transactions/app',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ 'combine_appid' => $payload->get('combine_appid', $config['app_id'] ?? ''),
+ 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''),
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php
new file mode 100644
index 0000000..8af5263
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php
@@ -0,0 +1,57 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $combineOutTradeNo = $payload?->get('combine_out_trade_no') ?? null;
+
+ if (empty($combineOutTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单关单,参数缺少 `combine_out_trade_no`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo.'/close',
+ '_service_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo.'/close',
+ 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''),
+ ])
+ ->exceptPayload('combine_out_trade_no');
+
+ Logger::info('[Wechat][Pay][Combine][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/DownloadBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/DownloadBillPlugin.php
new file mode 100644
index 0000000..91b47fd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/DownloadBillPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 下载交易对账单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetFundBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetFundBillPlugin.php
new file mode 100644
index 0000000..4c0850f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetFundBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 申请资金账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/fundflowbill?'.$query,
+ '_service_url' => 'v3/bill/fundflowbill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetTradeBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetTradeBillPlugin.php
new file mode 100644
index 0000000..f93b06d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/GetTradeBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 申请交易账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/tradebill?'.$query,
+ '_service_url' => 'v3/bill/tradebill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/H5PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/H5PayPlugin.php
new file mode 100644
index 0000000..76efcd5
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/H5PayPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5合单 下单,参数为空');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/combine-transactions/h5',
+ '_service_url' => 'v3/combine-transactions/h5',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''),
+ 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''),
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][H5PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiInvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiInvokePlugin.php
new file mode 100644
index 0000000..2c469e1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiInvokePlugin.php
@@ -0,0 +1,106 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id');
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][Pay][Combine][JsapiInvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $prepayId));
+
+ Logger::info('[Wechat][Pay][Combine][JsapiInvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appId' => $this->getAppId($payload, $config, $params),
+ 'timeStamp' => time().'',
+ 'nonceStr' => Str::random(32),
+ 'package' => 'prepay_id='.$prepayId,
+ 'signType' => 'RSA',
+ ]);
+
+ $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config));
+
+ return $invokeConfig;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ protected function getSign(Collection $invokeConfig, array $config): string
+ {
+ $contents = $invokeConfig->get('appId', '')."\n"
+ .$invokeConfig->get('timeStamp', '')."\n"
+ .$invokeConfig->get('nonceStr', '')."\n"
+ .$invokeConfig->get('package', '')."\n";
+
+ return get_wechat_sign($config, $contents);
+ }
+
+ protected function getAppId(?Collection $payload, array $config, array $params): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_'.get_wechat_type_key($params)] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config[get_wechat_type_key($params)] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiPayPlugin.php
new file mode 100644
index 0000000..f4323bd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/JsapiPayPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi合单 下单,参数为空');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/combine-transactions/jsapi',
+ '_service_url' => 'v3/combine-transactions/jsapi',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''),
+ 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''),
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][JsapiPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniInvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniInvokePlugin.php
new file mode 100644
index 0000000..7828158
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniInvokePlugin.php
@@ -0,0 +1,105 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id');
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][Pay][Combine][MiniInvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId));
+
+ Logger::info('[Wechat][Pay][Combine][MiniInvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appId' => $this->getAppId($payload, $config),
+ 'timeStamp' => time().'',
+ 'nonceStr' => Str::random(32),
+ 'package' => 'prepay_id='.$prepayId,
+ 'signType' => 'RSA',
+ ]);
+
+ $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config));
+
+ return $invokeConfig;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ protected function getSign(Collection $invokeConfig, array $config): string
+ {
+ $contents = $invokeConfig->get('appId', '')."\n"
+ .$invokeConfig->get('timeStamp', '')."\n"
+ .$invokeConfig->get('nonceStr', '')."\n"
+ .$invokeConfig->get('package', '')."\n";
+
+ return get_wechat_sign($config, $contents);
+ }
+
+ protected function getAppId(?Collection $payload, array $config): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_mini_app_id'] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config['mini_app_id'] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniPayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniPayPlugin.php
new file mode 100644
index 0000000..3ec706b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/MiniPayPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini合单 下单,参数为空');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/combine-transactions/jsapi',
+ '_service_url' => 'v3/combine-transactions/jsapi',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ 'combine_appid' => $payload->get('combine_appid', $config['mini_app_id'] ?? ''),
+ 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''),
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][MiniPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/NativePayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/NativePayPlugin.php
new file mode 100644
index 0000000..c435b67
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/NativePayPlugin.php
@@ -0,0 +1,55 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native合单 下单,参数为空');
+ }
+
+ $rocket->mergePayload([
+ '_method' => 'POST',
+ '_url' => 'v3/combine-transactions/native',
+ '_service_url' => 'v3/combine-transactions/native',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''),
+ 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''),
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][NativePayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryPlugin.php
new file mode 100644
index 0000000..c1d5b46
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryPlugin.php
@@ -0,0 +1,43 @@
+ $rocket]);
+
+ $combineOutTradeNo = $rocket->getPayload()?->get('combine_out_trade_no') ?? null;
+
+ if (empty($combineOutTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单查询,参数缺少 `combine_out_trade_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo,
+ '_service_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo,
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryRefundPlugin.php
new file mode 100644
index 0000000..9f1c637
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/QueryRefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (empty($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单查询退款订单,参数缺少 `out_refund_no`');
+ }
+
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo,
+ '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][Pay][Combine][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/RefundPlugin.php
new file mode 100644
index 0000000..08efaa8
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Combine/RefundPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 退款申请,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === $config['mode']) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds',
+ '_service_url' => 'v3/refund/domestic/refunds',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ],
+ $data ?? $this->normal()
+ ));
+
+ Logger::info('[Wechat][Pay][Combine][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php
new file mode 100644
index 0000000..4b946bb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php
@@ -0,0 +1,78 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 关闭订单,参数缺少 `out_trade_no`');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close',
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close',
+ ],
+ $data ?? $this->normal($config)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][H5][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): array
+ {
+ return [
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/DownloadBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/DownloadBillPlugin.php
new file mode 100644
index 0000000..326f4de
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/DownloadBillPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 下载交易对账单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][H5][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetFundBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetFundBillPlugin.php
new file mode 100644
index 0000000..b58789d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetFundBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 申请资金账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/fundflowbill?'.$query,
+ '_service_url' => 'v3/bill/fundflowbill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][H5][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetTradeBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetTradeBillPlugin.php
new file mode 100644
index 0000000..6f420c6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/GetTradeBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 申请交易账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/tradebill?'.$query,
+ '_service_url' => 'v3/bill/tradebill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][H5][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/PayPlugin.php
new file mode 100644
index 0000000..6fde0cb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/PayPlugin.php
@@ -0,0 +1,81 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 下单,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $params, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/h5',
+ '_service_url' => 'v3/pay/partner/transactions/h5',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ ],
+ $data ?? $this->normal($params, $config)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][H5][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $params, array $config): array
+ {
+ return [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $params, array $config): array
+ {
+ $configKey = get_wechat_type_key($params);
+
+ return [
+ 'sp_appid' => $config[$configKey] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryByWxPlugin.php
new file mode 100644
index 0000000..aa81152
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryByWxPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $transactionId = $payload?->get('transaction_id') ?? null;
+
+ if (empty($transactionId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 通过微信订单号查询订单,参数缺少 `transaction_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][H5][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? 'null',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? 'null',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryPlugin.php
new file mode 100644
index 0000000..9786a4b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 通过商户订单号查询订单,参数缺少 `out_trade_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][H5][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? '',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryRefundPlugin.php
new file mode 100644
index 0000000..c282160
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/QueryRefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (empty($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 查询退款订单,参数缺少 `out_refund_no`');
+ }
+
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo,
+ '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][H5][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/RefundPlugin.php
new file mode 100644
index 0000000..9dbd42f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/H5/RefundPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 退款申请,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === $config['mode']) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds',
+ '_service_url' => 'v3/refund/domestic/refunds',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ],
+ $data ?? $this->normal()
+ ));
+
+ Logger::info('[Wechat][V3][Pay][H5][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php
new file mode 100644
index 0000000..4aad2b0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php
@@ -0,0 +1,78 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 关闭订单,参数缺少 `out_trade_no`');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close',
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close',
+ ],
+ $data ?? $this->normal($config)
+ ));
+
+ Logger::info('[Wechat][Pay][Jsapi][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): array
+ {
+ return [
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/DownloadBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/DownloadBillPlugin.php
new file mode 100644
index 0000000..46328e6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/DownloadBillPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 下载交易对账单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][Pay][Jsapi][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php
new file mode 100644
index 0000000..2fa7b89
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 申请资金账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/fundflowbill?'.$query,
+ '_service_url' => 'v3/bill/fundflowbill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Pay][Jsapi][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetTradeBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetTradeBillPlugin.php
new file mode 100644
index 0000000..584807b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/GetTradeBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 申请交易账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/tradebill?'.$query,
+ '_service_url' => 'v3/bill/tradebill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Pay][Jsapi][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/InvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/InvokePlugin.php
new file mode 100644
index 0000000..9762c4f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/InvokePlugin.php
@@ -0,0 +1,106 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id');
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][V3][Pay][Jsapi][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $prepayId));
+
+ Logger::info('[Wechat][V3][Pay][Jsapi][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appId' => $this->getAppId($payload, $config, $params),
+ 'timeStamp' => time().'',
+ 'nonceStr' => Str::random(32),
+ 'package' => 'prepay_id='.$prepayId,
+ 'signType' => 'RSA',
+ ]);
+
+ $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config));
+
+ return $invokeConfig;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ protected function getSign(Collection $invokeConfig, array $config): string
+ {
+ $contents = $invokeConfig->get('appId', '')."\n"
+ .$invokeConfig->get('timeStamp', '')."\n"
+ .$invokeConfig->get('nonceStr', '')."\n"
+ .$invokeConfig->get('package', '')."\n";
+
+ return get_wechat_sign($config, $contents);
+ }
+
+ protected function getAppId(?Collection $payload, array $config, array $params): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_'.get_wechat_type_key($params)] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config[get_wechat_type_key($params)] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php
new file mode 100644
index 0000000..7642cf3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php
@@ -0,0 +1,87 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 下单,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config, $params);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/jsapi',
+ '_service_url' => 'v3/pay/partner/transactions/jsapi',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ ],
+ $data ?? $this->normal($config, $params)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][Jsapi][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config, array $params): array
+ {
+ return [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config, array $params): array
+ {
+ $wechatTypeKey = get_wechat_type_key($params);
+
+ $data = [
+ 'sp_appid' => $config[$wechatTypeKey] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+
+ if ($payload->has('payer.sub_openid')) {
+ $data['sub_appid'] = $config['sub_'.$wechatTypeKey] ?? '';
+ }
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php
new file mode 100644
index 0000000..bbd08cd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $transactionId = $payload?->get('transaction_id') ?? null;
+
+ if (empty($transactionId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 通过微信订单号查询订单,参数缺少 `transaction_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][Pay][Jsapi][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? 'null',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? 'null',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryPlugin.php
new file mode 100644
index 0000000..c52825a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 通过商户订单号查询订单,参数缺少 `out_trade_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][Pay][Jsapi][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? '',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php
new file mode 100644
index 0000000..f050eb3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (empty($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 查询退款订单,参数缺少 `out_refund_no`');
+ }
+
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo,
+ '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][Pay][Jsapi][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/RefundPlugin.php
new file mode 100644
index 0000000..b419cd6
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Jsapi/RefundPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 退款申请,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === $config['mode']) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds',
+ '_service_url' => 'v3/refund/domestic/refunds',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ],
+ $data ?? $this->normal()
+ ));
+
+ Logger::info('[Wechat][Pay][Jsapi][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php
new file mode 100644
index 0000000..cfaf85c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php
@@ -0,0 +1,78 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 关闭订单,参数缺少 `out_trade_no`');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close',
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close',
+ ],
+ $data ?? $this->normal($config)
+ ));
+
+ Logger::info('[Wechat][Pay][Mini][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): array
+ {
+ return [
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/DownloadBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/DownloadBillPlugin.php
new file mode 100644
index 0000000..27f82e3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/DownloadBillPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 下载交易对账单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][Pay][Mini][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetFundBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetFundBillPlugin.php
new file mode 100644
index 0000000..4c3f701
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetFundBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 申请资金账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/fundflowbill?'.$query,
+ '_service_url' => 'v3/bill/fundflowbill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Pay][Mini][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetTradeBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetTradeBillPlugin.php
new file mode 100644
index 0000000..7cd9cbe
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/GetTradeBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 申请交易账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/tradebill?'.$query,
+ '_service_url' => 'v3/bill/tradebill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Pay][Mini][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/InvokePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/InvokePlugin.php
new file mode 100644
index 0000000..793de71
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/InvokePlugin.php
@@ -0,0 +1,105 @@
+ $rocket]);
+
+ $destination = $rocket->getDestination();
+ $prepayId = $destination?->get('prepay_id');
+
+ if (is_null($prepayId)) {
+ Logger::error('[Wechat][Pay][Mini][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+
+ throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
+ }
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId));
+
+ Logger::info('[Wechat][Pay][Mini][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ * @throws Throwable 生成随机串失败
+ */
+ protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config
+ {
+ $invokeConfig = new Config([
+ 'appId' => $this->getAppId($payload, $config),
+ 'timeStamp' => time().'',
+ 'nonceStr' => Str::random(32),
+ 'package' => 'prepay_id='.$prepayId,
+ 'signType' => 'RSA',
+ ]);
+
+ $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config));
+
+ return $invokeConfig;
+ }
+
+ /**
+ * @throws InvalidConfigException
+ */
+ protected function getSign(Collection $invokeConfig, array $config): string
+ {
+ $contents = $invokeConfig->get('appId', '')."\n"
+ .$invokeConfig->get('timeStamp', '')."\n"
+ .$invokeConfig->get('nonceStr', '')."\n"
+ .$invokeConfig->get('package', '')."\n";
+
+ return get_wechat_sign($config, $contents);
+ }
+
+ protected function getAppId(?Collection $payload, array $config): string
+ {
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ return $payload?->get('_invoke_appid') ?? $config['sub_mini_app_id'] ?? '';
+ }
+
+ return $payload?->get('_invoke_appid') ?? $config['mini_app_id'] ?? '';
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/PayPlugin.php
new file mode 100644
index 0000000..5549c0d
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/PayPlugin.php
@@ -0,0 +1,84 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 下单,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/jsapi',
+ '_service_url' => 'v3/pay/partner/transactions/jsapi',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ ],
+ $data ?? $this->normal($config)
+ ));
+
+ Logger::info('[Wechat][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): array
+ {
+ return [
+ 'appid' => $config['mini_app_id'] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ $data = [
+ 'sp_appid' => $config['mini_app_id'] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+
+ if ($payload->has('payer.sub_openid')) {
+ $data['sub_appid'] = $config['sub_mini_app_id'] ?? '';
+ }
+
+ return $data;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryByWxPlugin.php
new file mode 100644
index 0000000..90d6da1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryByWxPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $transactionId = $payload?->get('transaction_id') ?? null;
+
+ if (empty($transactionId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 通过微信订单号查询订单,参数缺少 `transaction_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][Pay][Mini][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? 'null',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? 'null',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php
new file mode 100644
index 0000000..22450b1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 通过商户订单号查询订单,参数缺少 `out_trade_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][Pay][Mini][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? '',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryRefundPlugin.php
new file mode 100644
index 0000000..9302ea0
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/QueryRefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (empty($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 查询退款订单,参数缺少 `out_refund_no`');
+ }
+
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo,
+ '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][Pay][Mini][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/RefundPlugin.php
new file mode 100644
index 0000000..306f978
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Mini/RefundPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 退款申请,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === $config['mode']) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds',
+ '_service_url' => 'v3/refund/domestic/refunds',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ],
+ $data ?? $this->normal()
+ ));
+
+ Logger::info('[Wechat][Pay][Mini][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php
new file mode 100644
index 0000000..62c65eb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php
@@ -0,0 +1,78 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 关闭订单,参数缺少 `out_trade_no`');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close',
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close',
+ ],
+ $data ?? $this->normal($config)
+ ));
+
+ Logger::info('[Wechat][Pay][Native][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): array
+ {
+ return [
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/DownloadBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/DownloadBillPlugin.php
new file mode 100644
index 0000000..7c4e11f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/DownloadBillPlugin.php
@@ -0,0 +1,45 @@
+ $rocket]);
+
+ $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null;
+
+ if (empty($downloadUrl)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 下载交易对账单,参数缺少 `download_url`');
+ }
+
+ $rocket->setDirection(OriginResponseDirection::class)
+ ->setPayload([
+ '_method' => 'GET',
+ '_url' => $downloadUrl,
+ '_service_url' => $downloadUrl,
+ ]);
+
+ Logger::info('[Wechat][Pay][Native][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetFundBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetFundBillPlugin.php
new file mode 100644
index 0000000..af88b6a
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetFundBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 申请资金账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/fundflowbill?'.$query,
+ '_service_url' => 'v3/bill/fundflowbill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][Native][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetTradeBillPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetTradeBillPlugin.php
new file mode 100644
index 0000000..88fa99b
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/GetTradeBillPlugin.php
@@ -0,0 +1,47 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 申请交易账单,参数为空');
+ }
+
+ $query = filter_params($payload)->query();
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/bill/tradebill?'.$query,
+ '_service_url' => 'v3/bill/tradebill?'.$query,
+ ]);
+
+ Logger::info('[Wechat][Pay][Native][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/PayPlugin.php
new file mode 100644
index 0000000..151b281
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/PayPlugin.php
@@ -0,0 +1,81 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 下单,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $params, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/native',
+ '_service_url' => 'v3/pay/partner/transactions/native',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''),
+ ],
+ $data ?? $this->normal($params, $config)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][Native][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $params, array $config): array
+ {
+ return [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $params, array $config): array
+ {
+ $configKey = get_wechat_type_key($params);
+
+ return [
+ 'sp_appid' => $config[$configKey] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryByWxPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryByWxPlugin.php
new file mode 100644
index 0000000..e5b67ad
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryByWxPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $transactionId = $payload?->get('transaction_id') ?? null;
+
+ if (empty($transactionId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 通过微信订单号查询订单,参数缺少 `transaction_id`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][Pay][Native][QueryBatchByWxPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? 'null',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? 'null',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryPlugin.php
new file mode 100644
index 0000000..50a14d7
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryPlugin.php
@@ -0,0 +1,68 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 通过商户订单号查询订单,参数缺少 `out_trade_no`');
+ }
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config),
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config),
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][Native][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $config): string
+ {
+ return http_build_query([
+ 'mchid' => $config['mch_id'] ?? '',
+ ]);
+ }
+
+ protected function service(Collection $payload, array $config): string
+ {
+ return http_build_query([
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryRefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryRefundPlugin.php
new file mode 100644
index 0000000..ff323c3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/QueryRefundPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (empty($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 查询退款订单,参数缺少 `out_refund_no`');
+ }
+
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo,
+ '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][Pay][Native][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/RefundPlugin.php
new file mode 100644
index 0000000..4a22300
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Native/RefundPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 退款申请,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === $config['mode']) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds',
+ '_service_url' => 'v3/refund/domestic/refunds',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ],
+ $data ?? $this->normal()
+ ));
+
+ Logger::info('[Wechat][V3][Pay][Native][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php
new file mode 100644
index 0000000..2da3ac3
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php
@@ -0,0 +1,83 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ $outTradeNo = $payload?->get('out_trade_no') ?? null;
+
+ if (empty($outTradeNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 付款码支付撤销订单,参数缺少 `out_trade_no`');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($payload, $params, $config);
+ }
+
+ $rocket->setPayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/reverse',
+ '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/reverse',
+ ],
+ $data ?? $this->normal($params, $config)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $params, array $config): array
+ {
+ return [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $params, array $config): array
+ {
+ $configKey = get_wechat_type_key($params);
+
+ return [
+ 'sp_appid' => $config[$configKey] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php
new file mode 100644
index 0000000..c97f7cb
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php
@@ -0,0 +1,81 @@
+ $rocket]);
+
+ $payload = $rocket->getPayload();
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $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, $params, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/pay/transactions/codepay',
+ '_service_url' => 'v3/pay/partner/transactions/codepay',
+ ],
+ $data ?? $this->normal($params, $config)
+ ));
+
+ Logger::info('[Wechat][V3][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(array $params, array $config): array
+ {
+ return [
+ 'appid' => $config[get_wechat_type_key($params)] ?? '',
+ 'mchid' => $config['mch_id'] ?? '',
+ ];
+ }
+
+ protected function service(Collection $payload, array $params, array $config): array
+ {
+ $configKey = get_wechat_type_key($params);
+
+ return [
+ 'sp_appid' => $config[$configKey] ?? '',
+ 'sp_mchid' => $config['mch_id'] ?? '',
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/QueryPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/QueryPlugin.php
new file mode 100644
index 0000000..52a4122
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/QueryPlugin.php
@@ -0,0 +1,54 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+ $outRefundNo = $payload?->get('out_refund_no') ?? null;
+
+ if (empty($outRefundNo)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询退款订单,参数缺少 `out_refund_no`');
+ }
+
+ $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo,
+ '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId,
+ ]);
+
+ Logger::info('[Wechat][V3][Pay][Refund][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundAbnormalPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundAbnormalPlugin.php
new file mode 100644
index 0000000..9203689
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundAbnormalPlugin.php
@@ -0,0 +1,118 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $payload = $rocket->getPayload();
+ $config = get_provider_config('wechat', $params);
+ $refundId = $payload?->get('refund_id') ?? null;
+
+ if (empty($refundId)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发起异常退款,参数缺少 `refund_id`');
+ }
+
+ if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
+ $data = $this->service($params, $config, $payload);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds/'.$refundId.'/apply-abnormal-refund',
+ '_service_url' => 'v3/refund/domestic/refunds/'.$refundId.'/apply-abnormal-refund',
+ ],
+ $data ?? $this->normal($params, $config, $payload)
+ ))->exceptPayload('refund_id');
+
+ Logger::info('[Wechat][V3][Pay][Refund][RefundAbnormalPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ */
+ protected function normal(array $params, array $config, Collection $payload): array
+ {
+ return $this->encryptSensitiveData($params, $config, $payload);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function service(array $params, array $config, Collection $payload): array
+ {
+ $data = [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+
+ return array_merge($data, $this->encryptSensitiveData($params, $config, $payload));
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws DecryptException
+ * @throws InvalidConfigException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ protected function encryptSensitiveData(array $params, array $config, Collection $payload): array
+ {
+ if ($payload->has('bank_account') && $payload->has('real_name')) {
+ $data['_serial_no'] = get_wechat_serial_no($params);
+
+ $config = get_provider_config('wechat', $params);
+ $publicKey = get_wechat_public_key($config, $data['_serial_no']);
+
+ $data['real_name'] = encrypt_wechat_contents($payload->get('real_name'), $publicKey);
+ $data['bank_account'] = encrypt_wechat_contents($payload->get('bank_account'), $publicKey);
+ }
+
+ return $data ?? [];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundPlugin.php
new file mode 100644
index 0000000..51bf5b4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/Pay/Refund/RefundPlugin.php
@@ -0,0 +1,73 @@
+ $rocket]);
+
+ $params = $rocket->getParams();
+ $config = get_provider_config('wechat', $params);
+ $payload = $rocket->getPayload();
+
+ if (is_null($payload)) {
+ throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 退款申请,参数为空');
+ }
+
+ if (Pay::MODE_SERVICE === $config['mode']) {
+ $data = $this->service($payload, $config);
+ }
+
+ $rocket->mergePayload(array_merge(
+ [
+ '_method' => 'POST',
+ '_url' => 'v3/refund/domestic/refunds',
+ '_service_url' => 'v3/refund/domestic/refunds',
+ 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null),
+ ],
+ $data ?? $this->normal()
+ ));
+
+ Logger::info('[Wechat][V3][Pay][Refund][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+
+ protected function normal(): array
+ {
+ return [];
+ }
+
+ protected function service(Collection $payload, array $config): array
+ {
+ return [
+ 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/VerifySignaturePlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/VerifySignaturePlugin.php
new file mode 100644
index 0000000..9f651bc
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/VerifySignaturePlugin.php
@@ -0,0 +1,48 @@
+ $rocket]);
+
+ if (!should_do_http_request($rocket->getDirection()) || is_null($rocket->getDestinationOrigin())) {
+ return $rocket;
+ }
+
+ verify_wechat_sign($rocket->getDestinationOrigin(), $rocket->getParams());
+
+ Logger::info('[Wechat][V3][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $rocket;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/WechatPublicCertsPlugin.php b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/WechatPublicCertsPlugin.php
new file mode 100644
index 0000000..7d9af61
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Plugin/Wechat/V3/WechatPublicCertsPlugin.php
@@ -0,0 +1,27 @@
+ $rocket]);
+
+ $rocket->setPayload([
+ '_method' => 'GET',
+ '_url' => 'v3/certificates',
+ ]);
+
+ Logger::info('[Wechat][V3][WechatPublicCertsPlugin] 插件装载完毕', ['rocket' => $rocket]);
+
+ return $next($rocket);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Provider/Alipay.php b/addons/epay/library/v3new/Yansongda/Pay/Provider/Alipay.php
new file mode 100644
index 0000000..c93004e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Provider/Alipay.php
@@ -0,0 +1,175 @@
+ 'https://openapi.alipay.com/gateway.do?charset=utf-8',
+ Pay::MODE_SANDBOX => 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8',
+ Pay::MODE_SERVICE => 'https://openapi.alipay.com/gateway.do?charset=utf-8',
+ ];
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket
+ {
+ $plugin = '\Yansongda\Pay\Shortcut\Alipay\\'.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('alipay', __METHOD__, $order, null));
+
+ return $this->__call('query', [$order]);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function cancel(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('alipay', __METHOD__, $order, null));
+
+ return $this->__call('cancel', [$order]);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function close(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('alipay', __METHOD__, $order, null));
+
+ return $this->__call('close', [$order]);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function refund(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('alipay', __METHOD__, $order, null));
+
+ return $this->__call('refund', [$order]);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ */
+ public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection
+ {
+ $request = $this->getCallbackParams($contents);
+
+ Event::dispatch(new CallbackReceived('alipay', $request->all(), $params, null));
+
+ return $this->pay([CallbackPlugin::class], $request->merge($params)->all());
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ */
+ public function appCallback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection
+ {
+ $request = $this->getCallbackParams($contents);
+
+ return $this->pay([AppCallbackPlugin::class], $request->merge($params)->all());
+ }
+
+ public function success(): ResponseInterface
+ {
+ return new Response(200, [], 'success');
+ }
+
+ public function mergeCommonPlugins(array $plugins): array
+ {
+ return array_merge(
+ [StartPlugin::class],
+ $plugins,
+ [FormatPayloadBizContentPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::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) {
+ return Collection::wrap('GET' === $contents->getMethod() ? $contents->getQueryParams()
+ : $contents->getParsedBody());
+ }
+
+ $request = ServerRequest::fromGlobals();
+
+ return Collection::wrap(
+ array_merge($request->getQueryParams(), $request->getParsedBody())
+ );
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Provider/Douyin.php b/addons/epay/library/v3new/Yansongda/Pay/Provider/Douyin.php
new file mode 100644
index 0000000..dfe77c1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Provider/Jsb.php b/addons/epay/library/v3new/Yansongda/Pay/Provider/Jsb.php
new file mode 100644
index 0000000..1190846
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Provider/Jsb.php
@@ -0,0 +1,155 @@
+ 'https://mybank.jsbchina.cn:577/eis/merchant/merchantServices.htm',
+ Pay::MODE_SANDBOX => 'https://epaytest.jsbchina.cn:9999/eis/merchant/merchantServices.htm',
+ ];
+
+ /**
+ * @param mixed $name
+ * @param mixed $params
+ *
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function __call($name, $params): null|Collection|MessageInterface|Rocket
+ {
+ $plugin = '\Yansongda\Pay\Shortcut\Jsb\\'.Str::studly($name).'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);
+ }
+
+ public function mergeCommonPlugins(array $plugins): array
+ {
+ return array_merge(
+ [StartPlugin::class],
+ $plugins,
+ [AddPayloadSignPlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class],
+ );
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ public function cancel(array $order): Collection|Rocket
+ {
+ throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, 'Jsb does not support cancel api');
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ public function close(array $order): Collection|Rocket
+ {
+ throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, 'Jsb does not support close api');
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function refund(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('jsb', __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('jsb', $request->all(), $params, null));
+
+ return $this->pay(
+ [CallbackPlugin::class],
+ ['request' => $request, 'params' => $params]
+ );
+ }
+
+ public function success(): ResponseInterface
+ {
+ return new Response(
+ 200,
+ ['Content-Type' => 'text/html'],
+ 'success',
+ );
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function query(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('jsb', __METHOD__, $order, null));
+
+ return $this->__call('query', [$order]);
+ }
+
+ protected function getCallbackParams($contents = null): Collection
+ {
+ if (is_array($contents)) {
+ return Collection::wrap($contents);
+ }
+
+ if ($contents instanceof ServerRequestInterface) {
+ return Collection::wrap($contents->getParsedBody());
+ }
+
+ $request = ServerRequest::fromGlobals();
+
+ return Collection::wrap($request->getParsedBody());
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Provider/Unipay.php b/addons/epay/library/v3new/Yansongda/Pay/Provider/Unipay.php
new file mode 100644
index 0000000..08d7cce
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Provider/Unipay.php
@@ -0,0 +1,156 @@
+ 'https://gateway.95516.com/',
+ Pay::MODE_SANDBOX => 'https://gateway.test.95516.com/',
+ Pay::MODE_SERVICE => 'https://gateway.95516.com/',
+ ];
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket
+ {
+ $plugin = '\Yansongda\Pay\Shortcut\Unipay\\'.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('unipay', __METHOD__, $order, null));
+
+ return $this->__call('query', [$order]);
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function cancel(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('unipay', __METHOD__, $order, null));
+
+ return $this->__call('cancel', [$order]);
+ }
+
+ /**
+ * @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('unipay', __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('unipay', $request->all(), $params, null));
+
+ return $this->pay(
+ [CallbackPlugin::class],
+ $request->merge($params)->all()
+ );
+ }
+
+ public function success(): ResponseInterface
+ {
+ return new Response(200, [], 'success');
+ }
+
+ public function mergeCommonPlugins(array $plugins): array
+ {
+ return array_merge(
+ [StartPlugin::class],
+ $plugins,
+ [AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ParserPlugin::class],
+ );
+ }
+
+ protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): Collection
+ {
+ if (is_array($contents)) {
+ return Collection::wrap($contents);
+ }
+
+ if ($contents instanceof ServerRequestInterface) {
+ return Collection::wrap($contents->getParsedBody());
+ }
+
+ $request = ServerRequest::fromGlobals();
+
+ return Collection::wrap($request->getParsedBody());
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Provider/Wechat.php b/addons/epay/library/v3new/Yansongda/Pay/Provider/Wechat.php
new file mode 100644
index 0000000..fb88094
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Provider/Wechat.php
@@ -0,0 +1,171 @@
+ 'https://api.mch.weixin.qq.com/',
+ Pay::MODE_SANDBOX => 'https://api.mch.weixin.qq.com/sandboxnew/',
+ Pay::MODE_SERVICE => 'https://api.mch.weixin.qq.com/',
+ ];
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket
+ {
+ $plugin = '\Yansongda\Pay\Shortcut\Wechat\\'.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('wechat', __METHOD__, $order, null));
+
+ return $this->__call('query', [$order]);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ public function cancel(array $order): Collection|Rocket
+ {
+ throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 微信不支持 cancel API');
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function close(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('wechat', __METHOD__, $order, null));
+
+ $this->__call('close', [$order]);
+
+ return new Collection();
+ }
+
+ /**
+ * @throws ContainerException
+ * @throws InvalidParamsException
+ * @throws ServiceNotFoundException
+ */
+ public function refund(array $order): Collection|Rocket
+ {
+ Event::dispatch(new MethodCalled('wechat', __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('wechat', clone $request, $params, null));
+
+ return $this->pay(
+ [CallbackPlugin::class],
+ ['_request' => $request, '_params' => $params]
+ );
+ }
+
+ public function success(): ResponseInterface
+ {
+ return new Response(
+ 200,
+ ['Content-Type' => 'application/json'],
+ json_encode(['code' => 'SUCCESS', 'message' => '成功']),
+ );
+ }
+
+ public function mergeCommonPlugins(array $plugins): array
+ {
+ return array_merge(
+ [StartPlugin::class],
+ $plugins,
+ [AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class],
+ );
+ }
+
+ protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): ServerRequestInterface
+ {
+ if (is_array($contents) && isset($contents['body'], $contents['headers'])) {
+ return new ServerRequest('POST', 'http://localhost', $contents['headers'], $contents['body']);
+ }
+
+ if (is_array($contents)) {
+ return new ServerRequest('POST', 'http://localhost', [], json_encode($contents));
+ }
+
+ if ($contents instanceof ServerRequestInterface) {
+ return $contents;
+ }
+
+ return ServerRequest::fromGlobals();
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Service/AlipayServiceProvider.php b/addons/epay/library/v3new/Yansongda/Pay/Service/AlipayServiceProvider.php
new file mode 100644
index 0000000..b74a0bd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Service/AlipayServiceProvider.php
@@ -0,0 +1,24 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->posPlugins();
+ }
+
+ protected function agreementPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AgreementCancelPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function authorizationPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AuthorizationCancelPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function miniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniCancelPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function posPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ PosCancelPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function scanPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanCancelPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/CloseShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/CloseShortcut.php
new file mode 100644
index 0000000..016d849
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/CloseShortcut.php
@@ -0,0 +1,159 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->webPlugins();
+ }
+
+ protected function agreementPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AgreementClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function appPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function authorizationPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AuthorizationClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function miniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function posPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ PosClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function scanPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function h5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5ClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function webPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ WebClosePlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/H5Shortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/H5Shortcut.php
new file mode 100644
index 0000000..ebc99f4
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/H5Shortcut.php
@@ -0,0 +1,30 @@
+refundPlugins();
+ }
+
+ if (method_exists($this, $method)) {
+ return $this->{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->webPlugins();
+ }
+
+ protected function agreementPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AgreementQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function appPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function authorizationPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AuthorizationQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function facePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ FaceQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function miniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function posPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ PosQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function scanPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function h5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5QueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function webPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ WebQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function transferPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ TransferQueryPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundPlugins(): array
+ {
+ return $this->refundWebPlugins();
+ }
+
+ protected function refundAppPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppQueryRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundAuthorizationPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AuthorizationQueryRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundMiniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniQueryRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundPosPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ PosQueryRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundScanPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanQueryRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundH5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5QueryRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundWebPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ WebQueryRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/RefundShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/RefundShortcut.php
new file mode 100644
index 0000000..bf30461
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/RefundShortcut.php
@@ -0,0 +1,174 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->webPlugins();
+ }
+
+ protected function agreementPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AgreementRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function appPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function authorizationPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AuthorizationRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function miniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function posPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ PosRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function scanPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function h5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5RefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function webPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ WebRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function transferPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ FundTransferRefundPlugin::class,
+ FormatPayloadBizContentPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/ScanShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/ScanShortcut.php
new file mode 100644
index 0000000..ec22dcc
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Alipay/ScanShortcut.php
@@ -0,0 +1,32 @@
+{$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/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Douyin/RefundShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Douyin/RefundShortcut.php
new file mode 100644
index 0000000..6bbac50
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/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/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Jsb/QueryShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Jsb/QueryShortcut.php
new file mode 100644
index 0000000..83af51c
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Jsb/QueryShortcut.php
@@ -0,0 +1,30 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->webPlugins();
+ }
+
+ protected function webPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ webCancelPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qrCodePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ QrCodeCancelPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qraPosPlugins(): array
+ {
+ return [
+ QraStartPlugin::class,
+ QraPosCancelQueryPlugin::class,
+ QraAddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ QraVerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/H5Shortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/H5Shortcut.php
new file mode 100644
index 0000000..a1ac035
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/H5Shortcut.php
@@ -0,0 +1,30 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ PosPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function preAuthPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ PosPreAuthPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qraPlugins(): array
+ {
+ return [
+ QraStartPlugin::class,
+ PayPlugin::class,
+ QraAddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ QraVerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/QueryShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/QueryShortcut.php
new file mode 100644
index 0000000..7f0b29e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/QueryShortcut.php
@@ -0,0 +1,97 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->webPlugins();
+ }
+
+ protected function webPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ WebQueryPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qrCodePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ QrCodeQueryPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qraPosPlugins(): array
+ {
+ return [
+ QraStartPlugin::class,
+ QraPosQueryPlugin::class,
+ QraAddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ QraVerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qraPosRefundPlugins(): array
+ {
+ return [
+ QraStartPlugin::class,
+ QraPosQueryRefundPlugin::class,
+ QraAddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ QraVerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/RefundShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/RefundShortcut.php
new file mode 100644
index 0000000..7539677
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/RefundShortcut.php
@@ -0,0 +1,83 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->webPlugins();
+ }
+
+ protected function webPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ WebRefundPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qrCodePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ QrCodeRefundPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function qraPosPlugins(): array
+ {
+ return [
+ QraStartPlugin::class,
+ QraPosRefundPlugin::class,
+ QraAddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ QraVerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/ScanShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/ScanShortcut.php
new file mode 100644
index 0000000..74cbf9e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/ScanShortcut.php
@@ -0,0 +1,89 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function preAuthPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanPreAuthPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function preOrderPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanPreOrderPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function feePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ScanFeePlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/WebShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/WebShortcut.php
new file mode 100644
index 0000000..5cae9a9
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Unipay/WebShortcut.php
@@ -0,0 +1,30 @@
+combinePlugins();
+ }
+
+ $method = Str::camel($params['_action'] ?? 'default').'Plugins';
+
+ if (method_exists($this, $method)) {
+ return $this->{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->jsapiPlugins();
+ }
+
+ protected function appPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppClosePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function H5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5ClosePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function jsapiPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ JsapiClosePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function miniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniClosePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function nativePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ NativeClosePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function combinePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ CombineClosePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/H5Shortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/H5Shortcut.php
new file mode 100644
index 0000000..f8a95ec
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/H5Shortcut.php
@@ -0,0 +1,32 @@
+{$method}($params);
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function defaultPlugins(array $params): array
+ {
+ return $this->orderPlugins($params);
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function orderPlugins(array $params): array
+ {
+ return [
+ StartPlugin::class,
+ ContractOrderPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ $this->getInvoke($params),
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function contractPlugins(array $params): array
+ {
+ return match ($params['_type'] ?? 'default') {
+ 'mini' => [StartPlugin::class, MiniOnlyContractPlugin::class, AddPayloadSignaturePlugin::class],
+ default => throw new InvalidParamsException(Exception::PARAMS_WECHAT_PAPAY_TYPE_NOT_SUPPORTED, '参数异常: 微信扣关服务纯签约,当前传递的 `_type` 类型不支持')
+ };
+ }
+
+ protected function applyPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ ApplyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ /**
+ * @throws InvalidParamsException
+ */
+ protected function getInvoke(array $params): string
+ {
+ return match ($params['_type'] ?? 'default') {
+ 'app' => AppInvokePlugin::class,
+ 'mini' => MiniInvokePlugin::class,
+ default => throw new InvalidParamsException(Exception::PARAMS_WECHAT_PAPAY_TYPE_NOT_SUPPORTED, '参数异常: 微信扣关服务支付中签约,当前传递的 `_type` 类型不支持')
+ };
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/PosShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/PosShortcut.php
new file mode 100644
index 0000000..9681f20
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/PosShortcut.php
@@ -0,0 +1,32 @@
+combinePlugins();
+ }
+
+ $method = Str::camel($params['_action'] ?? 'default').'Plugins';
+
+ if (method_exists($this, $method)) {
+ return $this->{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->jsapiPlugins();
+ }
+
+ protected function appPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppQueryPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function combinePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ CombineQueryPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function h5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5QueryPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function jsapiPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ JsapiQueryPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function miniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniQueryPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function nativePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ NativeQueryPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundPlugins(): array
+ {
+ return $this->refundJsapiPlugins();
+ }
+
+ protected function refundAppPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppQueryRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundCombinePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ CombineQueryRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundH5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5QueryRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundJsapiPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ JsapiQueryRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundMiniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniQueryRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function refundNativePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ NativeQueryRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function transferPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ TransferQueryPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/RedpackShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/RedpackShortcut.php
new file mode 100644
index 0000000..3d5ed4e
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/RedpackShortcut.php
@@ -0,0 +1,32 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ protected function defaultPlugins(): array
+ {
+ return $this->jsapiPlugins();
+ }
+
+ protected function appPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ AppRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function combinePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ CombineRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function h5Plugins(): array
+ {
+ return [
+ StartPlugin::class,
+ H5RefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function jsapiPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ JsapiRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function miniPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ MiniRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ protected function nativePlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ NativeRefundPlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/ScanShortcut.php b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/ScanShortcut.php
new file mode 100644
index 0000000..9059e51
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Shortcut/Wechat/ScanShortcut.php
@@ -0,0 +1,32 @@
+{$method}();
+ }
+
+ throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
+ }
+
+ public function defaultPlugins(): array
+ {
+ return $this->transferPlugins();
+ }
+
+ public function transferPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ \Yansongda\Pay\Plugin\Wechat\V3\Marketing\Transfer\CreatePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+
+ public function mchTransferPlugins(): array
+ {
+ return [
+ StartPlugin::class,
+ CreatePlugin::class,
+ AddPayloadBodyPlugin::class,
+ AddPayloadSignaturePlugin::class,
+ AddRadarPlugin::class,
+ VerifySignaturePlugin::class,
+ ResponsePlugin::class,
+ ParserPlugin::class,
+ ];
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Pay/Traits/SupportServiceProviderTrait.php b/addons/epay/library/v3new/Yansongda/Pay/Traits/SupportServiceProviderTrait.php
new file mode 100644
index 0000000..14ae8ae
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Pay/Traits/SupportServiceProviderTrait.php
@@ -0,0 +1,35 @@
+getParams();
+ $config = get_provider_config('alipay', $params);
+ $serviceProviderId = $config['service_provider_id'] ?? null;
+
+ if (Pay::MODE_SERVICE !== ($config['mode'] ?? Pay::MODE_NORMAL)
+ || empty($serviceProviderId)) {
+ return;
+ }
+
+ $rocket->mergeParams([
+ 'extend_params' => array_merge($params['extend_params'] ?? [], ['sys_service_provider_id' => $serviceProviderId]),
+ ]);
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Arr.php b/addons/epay/library/v3new/Yansongda/Supports/Arr.php
new file mode 100644
index 0000000..a7a4022
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Arr.php
@@ -0,0 +1,550 @@
+all();
+ } elseif (!is_array($values)) {
+ continue;
+ }
+ $results[] = $values;
+ }
+
+ return array_merge([], ...$results);
+ }
+
+ public static function crossJoin(...$arrays): array
+ {
+ $results = [[]];
+ foreach ($arrays as $index => $array) {
+ $append = [];
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+ $append[] = $product;
+ }
+ }
+ $results = $append;
+ }
+
+ return $results;
+ }
+
+ public static function divide(array $array): array
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ public static function dot(array $array, string $prepend = ''): array
+ {
+ $results = [];
+ foreach ($array as $key => $value) {
+ if (is_array($value) && !empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
+ } else {
+ $results[$prepend.$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ public static function except(array $array, array|string $keys): array
+ {
+ static::forget($array, $keys);
+
+ return $array;
+ }
+
+ public static function exists(array|ArrayAccess $array, int|string $key): bool
+ {
+ if ($array instanceof ArrayAccess) {
+ return $array->offsetExists($key);
+ }
+
+ return array_key_exists($key, $array);
+ }
+
+ public static function first(array $array, ?callable $callback = null, mixed $default = null): mixed
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return $default;
+ }
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $value, $key)) {
+ return $value;
+ }
+ }
+
+ return $default;
+ }
+
+ public static function last(array $array, ?callable $callback = null, mixed $default = null): mixed
+ {
+ if (is_null($callback)) {
+ return empty($array) ? $default : end($array);
+ }
+
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ public static function flatten(array $array, float|int $depth = INF): array
+ {
+ $result = [];
+ foreach ($array as $item) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+ if (!is_array($item)) {
+ $result[] = $item;
+ } elseif (1 === $depth) {
+ $result = array_merge($result, array_values($item));
+ } else {
+ $result = array_merge($result, static::flatten($item, $depth - 1));
+ }
+ }
+
+ return $result;
+ }
+
+ public static function forget(array &$array, array|string $keys): void
+ {
+ $original = &$array;
+ $keys = (array) $keys;
+ if (0 === count($keys)) {
+ return;
+ }
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+
+ continue;
+ }
+ $parts = explode('.', $key);
+ // clean up before each pass
+ $array = &$original;
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ public static function get(array|ArrayAccess $array, null|int|string $key = null, mixed $default = null): mixed
+ {
+ if (!static::accessible($array)) {
+ return $default;
+ }
+ if (is_null($key)) {
+ return $array;
+ }
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+ if (!is_string($key) || !str_contains($key, '.')) {
+ return $array[$key] ?? $default;
+ }
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($array) && static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return $default;
+ }
+ }
+
+ return $array;
+ }
+
+ public static function has(array|ArrayAccess $array, null|array|string $keys): bool
+ {
+ if (is_null($keys)) {
+ return false;
+ }
+ $keys = (array) $keys;
+ if (!$array) {
+ return false;
+ }
+ if ([] === $keys) {
+ return false;
+ }
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+ if (static::exists($array, $key)) {
+ continue;
+ }
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static function isAssoc(array $array): bool
+ {
+ $keys = array_keys($array);
+
+ return array_keys($keys) !== $keys;
+ }
+
+ public static function only(array $array, array|string $keys): array
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ public static function pluck(array $array, array|string $value, null|array|string $key = null): array
+ {
+ $results = [];
+
+ foreach ($array as $item) {
+ $itemValue = data_get($item, $value);
+ // If the key is "null", we will just append the value to the array and keep
+ // looping, Otherwise we will key the array using the value of the key we
+ // received from the developer. Then we'll return the final array form.
+ if (is_null($key)) {
+ $results[] = $itemValue;
+ } else {
+ $itemKey = data_get($item, $key);
+ if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
+ $itemKey = (string) $itemKey;
+ }
+ $results[$itemKey] = $itemValue;
+ }
+ }
+
+ return $results;
+ }
+
+ public static function prepend(array $array, mixed $value, mixed $key = null): array
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ public static function pull(array &$array, string $key, mixed $default = null): mixed
+ {
+ $value = static::get($array, $key, $default);
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ public static function random(array $array, int $number = 1): array
+ {
+ $count = count($array);
+ if ($number > $count) {
+ throw new InvalidArgumentException("You requested {$number} items, but there are only {$count} items available.");
+ }
+
+ if (0 === $number) {
+ return [];
+ }
+
+ $keys = array_rand($array, $number);
+ $results = [];
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ * If no key is given to the method, the entire array will be replaced.
+ */
+ public static function set(array &$array, null|int|string $key, mixed $value): array
+ {
+ if (is_null($key)) {
+ return $array = $value;
+ }
+ if (!is_string($key)) {
+ $array[$key] = $value;
+
+ return $array;
+ }
+ $keys = explode('.', $key);
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (!isset($array[$key]) || !is_array($array[$key])) {
+ $array[$key] = [];
+ }
+ $array = &$array[$key];
+ }
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ public static function shuffle(array $array, ?int $seed = null): array
+ {
+ if (is_null($seed)) {
+ shuffle($array);
+ } else {
+ srand($seed);
+ usort($array, function () {
+ return rand(-1, 1);
+ });
+ }
+
+ return $array;
+ }
+
+ public static function sort(array $array, callable $callback): array
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ $results[$key] = $callback($value);
+ }
+
+ return $results;
+ }
+
+ public static function sortRecursive(array $array): array
+ {
+ foreach ($array as &$value) {
+ if (is_array($value)) {
+ $value = static::sortRecursive($value);
+ }
+ }
+ if (static::isAssoc($array)) {
+ ksort($array);
+ } else {
+ sort($array);
+ }
+
+ return $array;
+ }
+
+ public static function query(array $array, int $encodingType = PHP_QUERY_RFC1738): string
+ {
+ return http_build_query($array, '', '&', $encodingType);
+ }
+
+ public static function toString(array $array, string $separator = '&'): string
+ {
+ $result = '';
+
+ foreach ($array as $key => $value) {
+ $result .= $key.'='.$value.$separator;
+ }
+
+ return substr($result, 0, 0 - Str::length($separator));
+ }
+
+ public static function where(array $array, callable $callback): array
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ public static function wrap(mixed $value): array
+ {
+ if (is_null($value)) {
+ return [];
+ }
+
+ return !is_array($value) ? [$value] : $value;
+ }
+
+ public static function wrapJson(string $json): ?array
+ {
+ $result = json_decode($json, true);
+
+ return is_array($result) ? $result : null;
+ }
+
+ public static function wrapXml(string $xml): array
+ {
+ if (empty($xml)) {
+ return [];
+ }
+
+ $data = json_decode(json_encode(
+ simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA),
+ JSON_UNESCAPED_UNICODE
+ ), true);
+
+ if (JSON_ERROR_NONE === json_last_error()) {
+ return $data;
+ }
+
+ return [];
+ }
+
+ /**
+ * @param bool $raw 是否原始解析,有些情况下,原始解析会更好
+ * @param bool $spaceToPlus 是否将空格转换为加号
+ */
+ public static function wrapQuery(string $query, bool $raw = false, bool $spaceToPlus = false): array
+ {
+ if (!$raw) {
+ parse_str($query, $data);
+
+ return $spaceToPlus ? self::querySpaceToPlus($data) : $data;
+ }
+
+ if (empty($query) || !Str::contains($query, '=')) {
+ return [];
+ }
+
+ $result = [];
+
+ foreach (explode('&', $query) as $item) {
+ $pos = strpos($item, '=');
+
+ $result[substr($item, 0, $pos)] = substr($item, $pos + 1);
+ }
+
+ return $result;
+ }
+
+ public static function querySpaceToPlus(array $data): array
+ {
+ foreach ($data as $key => $item) {
+ $data[$key] = is_array($item) ? self::querySpaceToPlus($item) : str_replace(' ', '+', $item);
+ }
+
+ return $data;
+ }
+
+ public static function unique(array $array): array
+ {
+ $result = [];
+ foreach ($array as $key => $item) {
+ if (is_array($item)) {
+ $result[$key] = self::unique($item);
+ } else {
+ $result[$key] = $item;
+ }
+ }
+
+ if (!self::isAssoc($result)) {
+ return array_unique($result);
+ }
+
+ return $result;
+ }
+
+ public static function merge(array $array1, array $array2, bool $unique = true): array
+ {
+ $isAssoc = static::isAssoc($array1 ?: $array2);
+ if ($isAssoc) {
+ foreach ($array2 as $key => $value) {
+ if (is_array($value)) {
+ $array1[$key] = static::merge($array1[$key] ?? [], $value, $unique);
+ } else {
+ $array1[$key] = $value;
+ }
+ }
+ } else {
+ foreach ($array2 as $value) {
+ if ($unique && in_array($value, $array1, true)) {
+ continue;
+ }
+ $array1[] = $value;
+ }
+
+ $array1 = array_values($array1);
+ }
+
+ return $array1;
+ }
+
+ public static function encoding(array $array, string $to_encoding, string $from_encoding = 'gb2312'): array
+ {
+ $encoded = [];
+
+ foreach ($array as $key => $value) {
+ $encoded[$key] = is_array($value) ? self::encoding($value, $to_encoding, $from_encoding) :
+ mb_convert_encoding($value, $to_encoding, $from_encoding);
+ }
+
+ return $encoded;
+ }
+
+ public static function camelCaseKey(mixed $data): mixed
+ {
+ if (!self::accessible($data)
+ && !(is_object($data) && method_exists($data, 'toArray'))) {
+ return $data;
+ }
+
+ $result = [];
+
+ foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) {
+ $result[is_string($key) ? Str::camel($key) : $key] = self::camelCaseKey($value);
+ }
+
+ return $result;
+ }
+
+ public static function snakeCaseKey(mixed $data): mixed
+ {
+ if (!self::accessible($data)
+ && !(is_object($data) && method_exists($data, 'toArray'))) {
+ return $data;
+ }
+
+ $result = [];
+
+ foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) {
+ $result[is_string($key) ? Str::snake($key) : $key] = self::snakeCaseKey($value);
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Collection.php b/addons/epay/library/v3new/Yansongda/Supports/Collection.php
new file mode 100644
index 0000000..2c3d349
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Collection.php
@@ -0,0 +1,361 @@
+getArrayableItems($items) as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ public static function wrap(mixed $value): self
+ {
+ return $value instanceof self ? new static($value) : new static(Arr::wrap($value));
+ }
+
+ public static function wrapJson(string $json): self
+ {
+ return new static(Arr::wrapJson($json));
+ }
+
+ public static function wrapXml(string $xml): self
+ {
+ return new static(Arr::wrapXml($xml));
+ }
+
+ public static function wrapQuery(string $query, bool $raw = false, bool $spaceToPlus = false): self
+ {
+ return new static(Arr::wrapQuery($query, $raw, $spaceToPlus));
+ }
+
+ public static function unwrap(array|Collection $value): array
+ {
+ return $value instanceof self ? $value->all() : $value;
+ }
+
+ public function all(): array
+ {
+ return $this->items;
+ }
+
+ public function only(array $keys): array
+ {
+ $return = [];
+
+ foreach ($keys as $key) {
+ $value = $this->get($key);
+
+ if (!is_null($value)) {
+ $return[$key] = $value;
+ }
+ }
+
+ return $return;
+ }
+
+ public function except(mixed $keys): self
+ {
+ $keys = is_array($keys) ? $keys : func_get_args();
+
+ return new static(Arr::except($this->items, $keys));
+ }
+
+ public function filter(?callable $callback = null): self
+ {
+ if ($callback) {
+ return new static(Arr::where($this->items, $callback));
+ }
+
+ return new static(array_filter($this->items));
+ }
+
+ public function merge(mixed $items): self
+ {
+ return new static(array_merge($this->items, $this->getArrayableItems($items)));
+ }
+
+ public function has(int|string $key): bool
+ {
+ return !is_null(Arr::get($this->items, $key));
+ }
+
+ public function first(): mixed
+ {
+ return reset($this->items);
+ }
+
+ public function last(): mixed
+ {
+ $end = end($this->items);
+
+ reset($this->items);
+
+ return $end;
+ }
+
+ public function add(null|int|string $key, mixed $value): void
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ public function set(null|int|string $key, mixed $value): void
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ public function get(null|int|string $key = null, mixed $default = null): mixed
+ {
+ return Arr::get($this->items, $key, $default);
+ }
+
+ public function forget(int|string $key): void
+ {
+ Arr::forget($this->items, $key);
+ }
+
+ public function flatten(float|int $depth = INF): self
+ {
+ return new static(Arr::flatten($this->items, $depth));
+ }
+
+ public function map(callable $callback): self
+ {
+ $keys = array_keys($this->items);
+ $items = array_map($callback, $this->items, $keys);
+
+ return new static(array_combine($keys, $items));
+ }
+
+ public function pop(): mixed
+ {
+ return array_pop($this->items);
+ }
+
+ public function prepend(mixed $value, mixed $key = null): self
+ {
+ $this->items = Arr::prepend($this->items, $value, $key);
+
+ return $this;
+ }
+
+ public function push(mixed $value): self
+ {
+ $this->offsetSet(null, $value);
+
+ return $this;
+ }
+
+ public function pull(mixed $key, mixed $default = null): mixed
+ {
+ return Arr::pull($this->items, $key, $default);
+ }
+
+ public function put(mixed $key, mixed $value): self
+ {
+ $this->offsetSet($key, $value);
+
+ return $this;
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ public function random(?int $number = null): self
+ {
+ return new static(Arr::random($this->items, $number ?? 1));
+ }
+
+ public function reduce(callable $callback, mixed $initial = null): mixed
+ {
+ return array_reduce($this->items, $callback, $initial);
+ }
+
+ public function values(): self
+ {
+ return new static(array_values($this->items));
+ }
+
+ public function every(callable|string $key): bool
+ {
+ $callback = $this->valueRetriever($key);
+
+ foreach ($this->items as $k => $v) {
+ if (!$callback($v, $k)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function chunk(int $size): self
+ {
+ if ($size <= 0) {
+ return new static();
+ }
+ $chunks = [];
+ foreach (array_chunk($this->items, $size, true) as $chunk) {
+ $chunks[] = new static($chunk);
+ }
+
+ return new static($chunks);
+ }
+
+ public function sort(?callable $callback = null): self
+ {
+ $items = $this->items;
+ $callback ? uasort($items, $callback) : asort($items);
+
+ return new static($items);
+ }
+
+ public function sortBy(callable|string $callback, int $options = SORT_REGULAR, bool $descending = false): self
+ {
+ $results = [];
+ $callback = $this->valueRetriever($callback);
+ // First we will loop through the items and get the comparator from a callback
+ // function which we were given. Then, we will sort the returned values
+ // and grab the corresponding values for the sorted keys from this array.
+ foreach ($this->items as $key => $value) {
+ $results[$key] = $callback($value, $key);
+ }
+ $descending ? arsort($results, $options) : asort($results, $options);
+ // Once we have sorted all the keys in the array, we will loop through them
+ // and grab the corresponding model, so we can set the underlying items list
+ // to the sorted version. Then we'll just return the collection instance.
+ foreach (array_keys($results) as $key) {
+ $results[$key] = $this->items[$key];
+ }
+
+ return new static($results);
+ }
+
+ public function sortByDesc(callable|string $callback, int $options = SORT_REGULAR): self
+ {
+ return $this->sortBy($callback, $options, true);
+ }
+
+ public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): self
+ {
+ $items = $this->items;
+ $descending ? krsort($items, $options) : ksort($items, $options);
+
+ return new static($items);
+ }
+
+ public function sortKeysDesc(int $options = SORT_REGULAR): self
+ {
+ return $this->sortKeys($options, true);
+ }
+
+ public function query(int $encodingType = PHP_QUERY_RFC1738): string
+ {
+ return Arr::query($this->all(), $encodingType);
+ }
+
+ public function toString(string $separator = '&'): string
+ {
+ return Arr::toString($this->all(), $separator);
+ }
+
+ public function toArray(): array
+ {
+ return $this->all();
+ }
+
+ public function toXml(): string
+ {
+ $xml = '';
+
+ foreach ($this->all() as $key => $val) {
+ $xml .= is_numeric($val) ? '<'.$key.'>'.$val.''.$key.'>' :
+ '<'.$key.'>'.$key.'>';
+ }
+
+ $xml .= ' ';
+
+ return $xml;
+ }
+
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ public function isEmpty(): bool
+ {
+ return empty($this->items);
+ }
+
+ public function isNotEmpty(): bool
+ {
+ return !$this->isEmpty();
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ if ($this->offsetExists($offset)) {
+ $this->forget($offset);
+ }
+ }
+
+ protected function useAsCallable(mixed $value): bool
+ {
+ return !is_string($value) && is_callable($value);
+ }
+
+ protected function valueRetriever(mixed $value): callable
+ {
+ if ($this->useAsCallable($value)) {
+ return $value;
+ }
+
+ return function ($item) use ($value) {
+ return data_get($item, $value);
+ };
+ }
+
+ protected function getArrayableItems(mixed $items): array
+ {
+ if (is_array($items)) {
+ return $items;
+ }
+
+ if ($items instanceof self) {
+ return $items->all();
+ }
+
+ if ($items instanceof JsonSerializable) {
+ return $items->jsonSerialize();
+ }
+
+ return (array) $items;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Config.php b/addons/epay/library/v3new/Yansongda/Supports/Config.php
new file mode 100644
index 0000000..2788d05
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Config.php
@@ -0,0 +1,7 @@
+all();
+ } elseif (!is_array($target)) {
+ return value($default);
+ }
+ $result = [];
+ foreach ($target as $item) {
+ $result[] = data_get($item, $key);
+ }
+
+ return in_array('*', $key) ? Arr::collapse($result) : $result;
+ }
+ if (Arr::accessible($target) && Arr::exists($target, $segment)) {
+ $target = $target[$segment];
+ } elseif (is_object($target) && isset($target->{$segment})) {
+ $target = $target->{$segment};
+ } else {
+ return value($default);
+ }
+ }
+
+ return $target;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Pipeline.php b/addons/epay/library/v3new/Yansongda/Supports/Pipeline.php
new file mode 100644
index 0000000..dcaa29f
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Pipeline.php
@@ -0,0 +1,115 @@
+container = $container;
+ }
+
+ public function send(mixed $passable): self
+ {
+ $this->passable = $passable;
+
+ return $this;
+ }
+
+ public function through(mixed $pipes): self
+ {
+ $this->pipes = is_array($pipes) ? $pipes : func_get_args();
+
+ return $this;
+ }
+
+ /**
+ * Set the method to call on the pipes.
+ */
+ public function via(string $method): self
+ {
+ $this->method = $method;
+
+ return $this;
+ }
+
+ public function then(Closure $destination): mixed
+ {
+ $pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination));
+
+ return $pipeline($this->passable);
+ }
+
+ protected function prepareDestination(Closure $destination): Closure
+ {
+ return static function ($passable) use ($destination) {
+ return $destination($passable);
+ };
+ }
+
+ protected function carry(): Closure
+ {
+ return function ($stack, $pipe) {
+ return function ($passable) use ($stack, $pipe) {
+ if (is_callable($pipe)) {
+ // If the pipe is an instance of a Closure, we will just call it directly, but
+ // otherwise we'll resolve the pipes out of the container and call it with
+ // the appropriate method and arguments, returning the results back out.
+ return $pipe($passable, $stack);
+ }
+ if (!is_object($pipe)) {
+ [$name, $parameters] = $this->parsePipeString($pipe);
+
+ // If the pipe is a string we will parse the string and resolve the class out
+ // of the dependency injection container. We can then build a callable and
+ // execute the pipe function giving in the parameters that are required.
+ $pipe = $this->container->get($name);
+
+ $parameters = array_merge([$passable, $stack], $parameters);
+ } else {
+ // If the pipe is already an object we'll just make a callable and pass it to
+ // the pipe as-is. There is no need to do any extra parsing and formatting
+ // since the object we're given was already a fully instantiated object.
+ $parameters = [$passable, $stack];
+ }
+
+ $carry = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters);
+
+ return $this->handleCarry($carry);
+ };
+ };
+ }
+
+ protected function parsePipeString(string $pipe): array
+ {
+ [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);
+
+ if (is_string($parameters)) {
+ $parameters = explode(',', $parameters);
+ }
+
+ return [$name, $parameters];
+ }
+
+ protected function handleCarry(mixed $carry): mixed
+ {
+ return $carry;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Str.php b/addons/epay/library/v3new/Yansongda/Supports/Str.php
new file mode 100644
index 0000000..2abad72
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Str.php
@@ -0,0 +1,455 @@
+ $val) {
+ $value = str_replace($val, $key, $value);
+ }
+
+ return preg_replace('/[^\x20-\x7E]/u', '', $value);
+ }
+
+ public static function before(string $subject, string $search): string
+ {
+ return '' === $search ? $subject : explode($search, $subject)[0];
+ }
+
+ public static function camel(string $value): string
+ {
+ return lcfirst(static::studly($value));
+ }
+
+ public static function contains(string $haystack, array|string $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if (str_contains($haystack, $needle)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static function endsWith(string $haystack, array|string $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if (str_ends_with($haystack, $needle)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static function finish(string $value, string $cap): string
+ {
+ $quoted = preg_quote($cap, '/');
+
+ return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap;
+ }
+
+ public static function is(array|string $pattern, string $value): bool
+ {
+ $patterns = Arr::wrap($pattern);
+
+ if (empty($patterns)) {
+ return false;
+ }
+
+ foreach ($patterns as $pattern) {
+ // If the given value is an exact match we can of course return true right
+ // from the beginning. Otherwise, we will translate asterisks and do an
+ // actual pattern match against the two strings to see if they match.
+ if ($pattern == $value) {
+ return true;
+ }
+
+ $pattern = preg_quote($pattern, '#');
+
+ // Asterisks are translated into zero-or-more regular expression wildcards
+ // to make it convenient to check if the strings starts with the given
+ // pattern such as "library/*", making any string check convenient.
+ $pattern = str_replace('\*', '.*', $pattern);
+
+ if (1 === preg_match('#^'.$pattern.'\z#u', $value)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static function kebab(string $value): string
+ {
+ return static::snake($value, '-');
+ }
+
+ public static function length(string $value, ?string $encoding = null): int
+ {
+ if (null !== $encoding) {
+ return mb_strlen($value, $encoding);
+ }
+
+ return mb_strlen($value);
+ }
+
+ public static function limit(string $value, int $limit = 100, string $end = '...'): string
+ {
+ if (mb_strwidth($value, 'UTF-8') <= $limit) {
+ return $value;
+ }
+
+ return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end;
+ }
+
+ public static function lower(string $value): string
+ {
+ return mb_strtolower($value, 'UTF-8');
+ }
+
+ public static function words(string $value, int $words = 100, string $end = '...'): string
+ {
+ preg_match('/^\s*+\S++\s*+{1,'.$words.'}/u', $value, $matches);
+
+ if (!isset($matches[0]) || static::length($value) === static::length($matches[0])) {
+ return $value;
+ }
+
+ return rtrim($matches[0]).$end;
+ }
+
+ public static function parseCallback(string $callback, ?string $default = null): array
+ {
+ return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
+ }
+
+ public static function random(int $length = 16): string
+ {
+ $string = '';
+
+ while (($len = strlen($string)) < $length) {
+ $size = $length - $len;
+
+ $bytes = random_bytes($size);
+
+ $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
+ }
+
+ return $string;
+ }
+
+ public static function uuidV4(): string
+ {
+ return sprintf(
+ '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+ // 32 bits for "time_low"
+ mt_rand(0, 0xFFFF),
+ mt_rand(0, 0xFFFF),
+
+ // 16 bits for "time_mid"
+ mt_rand(0, 0xFFFF),
+
+ // 16 bits for "time_hi_and_version",
+ // four most significant bits holds version number 4
+ mt_rand(0, 0x0FFF) | 0x4000,
+
+ // 16 bits, 8 bits for "clk_seq_hi_res",
+ // 8 bits for "clk_seq_low",
+ // two most significant bits holds zero and one for variant DCE1.1
+ mt_rand(0, 0x3FFF) | 0x8000,
+
+ // 48 bits for "node"
+ mt_rand(0, 0xFFFF),
+ mt_rand(0, 0xFFFF),
+ mt_rand(0, 0xFFFF)
+ );
+ }
+
+ public static function replaceArray(string $search, array $replace, string $subject): string
+ {
+ foreach ($replace as $value) {
+ $subject = static::replaceFirst($search, $value, $subject);
+ }
+
+ return $subject;
+ }
+
+ public static function replaceFirst(string $search, string $replace, string $subject): string
+ {
+ if ('' == $search) {
+ return $subject;
+ }
+
+ $position = strpos($subject, $search);
+
+ if (false !== $position) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ public static function replaceLast(string $search, string $replace, string $subject): string
+ {
+ $position = strrpos($subject, $search);
+
+ if (false !== $position) {
+ return substr_replace($subject, $replace, $position, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ public static function start(string $value, string $prefix): string
+ {
+ $quoted = preg_quote($prefix, '/');
+
+ return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
+ }
+
+ public static function upper(string $value): string
+ {
+ return mb_strtoupper($value, 'UTF-8');
+ }
+
+ public static function title(string $value): string
+ {
+ return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+ }
+
+ public static function slug(string $title, string $separator = '-', string $language = 'en'): string
+ {
+ $title = static::ascii($title, $language);
+
+ // Convert all dashes/underscores into separator
+ $flip = '-' == $separator ? '_' : '-';
+
+ $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
+
+ // Replace @ with the word 'at'
+ $title = str_replace('@', $separator.'at'.$separator, $title);
+
+ // Remove all characters that are not the separator, letters, numbers, or whitespace.
+ $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($title));
+
+ // Replace all separator characters and whitespace by a single separator
+ $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
+
+ return trim($title, $separator);
+ }
+
+ public static function snake(string $value, string $delimiter = '_'): string
+ {
+ if (!ctype_lower($value)) {
+ $value = preg_replace('/\s+/u', '', ucwords($value));
+
+ $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
+ }
+
+ return $value;
+ }
+
+ public static function startsWith(int|string $haystack, array|string $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if (str_starts_with(strval($haystack), $needle)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static function studly(string $value, string $gap = ''): string
+ {
+ $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+ return str_replace(' ', $gap, $value);
+ }
+
+ public static function substr(string $string, int $start, ?int $length = null): string
+ {
+ return mb_substr($string, $start, $length, 'UTF-8');
+ }
+
+ public static function ucfirst(string $string): string
+ {
+ return static::upper(static::substr($string, 0, 1)).static::substr($string, 1);
+ }
+
+ public static function encoding(string $string, string $to = 'utf-8', string $from = 'gb2312'): string
+ {
+ return mb_convert_encoding($string, $to, $from);
+ }
+
+ /**
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ */
+ protected static function charsArray(): array
+ {
+ static $charsArray;
+
+ if (isset($charsArray)) {
+ return $charsArray;
+ }
+
+ return $charsArray = [
+ '0' => ['°', '₀', '۰', '0'],
+ '1' => ['¹', '₁', '۱', '1'],
+ '2' => ['²', '₂', '۲', '2'],
+ '3' => ['³', '₃', '۳', '3'],
+ '4' => ['⁴', '₄', '۴', '٤', '4'],
+ '5' => ['⁵', '₅', '۵', '٥', '5'],
+ '6' => ['⁶', '₆', '۶', '٦', '6'],
+ '7' => ['⁷', '₇', '۷', '7'],
+ '8' => ['⁸', '₈', '۸', '8'],
+ '9' => ['⁹', '₉', '۹', '9'],
+ 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'],
+ 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'],
+ 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'],
+ 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'],
+ 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'],
+ 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'],
+ 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'],
+ 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'],
+ 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i'],
+ 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'],
+ 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'],
+ 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'],
+ 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'],
+ 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'],
+ 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö'],
+ 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'],
+ 'q' => ['ყ', 'q'],
+ 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'],
+ 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'],
+ 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'],
+ 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü'],
+ 'v' => ['в', 'ვ', 'ϐ', 'v'],
+ 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'],
+ 'x' => ['χ', 'ξ', 'x'],
+ 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'],
+ 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'],
+ 'aa' => ['ع', 'आ', 'آ'],
+ 'ae' => ['æ', 'ǽ'],
+ 'ai' => ['ऐ'],
+ 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'],
+ 'dj' => ['ђ', 'đ'],
+ 'dz' => ['џ', 'ძ'],
+ 'ei' => ['ऍ'],
+ 'gh' => ['غ', 'ღ'],
+ 'ii' => ['ई'],
+ 'ij' => ['ij'],
+ 'kh' => ['х', 'خ', 'ხ'],
+ 'lj' => ['љ'],
+ 'nj' => ['њ'],
+ 'oe' => ['ö', 'œ', 'ؤ'],
+ 'oi' => ['ऑ'],
+ 'oii' => ['ऒ'],
+ 'ps' => ['ψ'],
+ 'sh' => ['ш', 'შ', 'ش'],
+ 'shch' => ['щ'],
+ 'ss' => ['ß'],
+ 'sx' => ['ŝ'],
+ 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'],
+ 'ts' => ['ц', 'ც', 'წ'],
+ 'ue' => ['ü'],
+ 'uu' => ['ऊ'],
+ 'ya' => ['я'],
+ 'yu' => ['ю'],
+ 'zh' => ['ж', 'ჟ', 'ژ'],
+ '(c)' => ['©'],
+ 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'],
+ 'B' => ['Б', 'Β', 'ब', 'B'],
+ 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'],
+ 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'],
+ 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E'],
+ 'F' => ['Ф', 'Φ', 'F'],
+ 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'],
+ 'H' => ['Η', 'Ή', 'Ħ', 'H'],
+ 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I'],
+ 'J' => ['J'],
+ 'K' => ['К', 'Κ', 'K'],
+ 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'],
+ 'M' => ['М', 'Μ', 'M'],
+ 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'],
+ 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'],
+ 'P' => ['П', 'Π', 'P'],
+ 'Q' => ['Q'],
+ 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'],
+ 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'],
+ 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'],
+ 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'],
+ 'V' => ['В', 'V'],
+ 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'],
+ 'X' => ['Χ', 'Ξ', 'X'],
+ 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'],
+ 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'],
+ 'AE' => ['Æ', 'Ǽ'],
+ 'Ch' => ['Ч'],
+ 'Dj' => ['Ђ'],
+ 'Dz' => ['Џ'],
+ 'Gx' => ['Ĝ'],
+ 'Hx' => ['Ĥ'],
+ 'Ij' => ['IJ'],
+ 'Jx' => ['Ĵ'],
+ 'Kh' => ['Х'],
+ 'Lj' => ['Љ'],
+ 'Nj' => ['Њ'],
+ 'Oe' => ['Œ'],
+ 'Ps' => ['Ψ'],
+ 'Sh' => ['Ш'],
+ 'Shch' => ['Щ'],
+ 'Ss' => ['ẞ'],
+ 'Th' => ['Þ'],
+ 'Ts' => ['Ц'],
+ 'Ya' => ['Я'],
+ 'Yu' => ['Ю'],
+ 'Zh' => ['Ж'],
+ ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0"],
+ ];
+ }
+
+ /**
+ * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt
+ */
+ protected static function languageSpecificCharsArray(string $language): ?array
+ {
+ static $languageSpecific;
+ if (!isset($languageSpecific)) {
+ $languageSpecific = [
+ 'bg' => [
+ ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'],
+ ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'],
+ ],
+ 'de' => [
+ ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'],
+ ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'],
+ ],
+ ];
+ }
+
+ return $languageSpecific[$language] ?? null;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Traits/Accessable.php b/addons/epay/library/v3new/Yansongda/Supports/Traits/Accessable.php
new file mode 100644
index 0000000..5576dd1
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Traits/Accessable.php
@@ -0,0 +1,73 @@
+get($key);
+ }
+
+ public function __isset(string $key): bool
+ {
+ return !is_null($this->get($key));
+ }
+
+ public function __unset(string $key): void
+ {
+ $this->offsetUnset($key);
+ }
+
+ public function __set(string $key, mixed $value): void
+ {
+ $this->set($key, $value);
+ }
+
+ public function get(?string $key = null, mixed $default = null): mixed
+ {
+ if (is_null($key)) {
+ return method_exists($this, 'toArray') ? $this->toArray() : $default;
+ }
+
+ $method = 'get'.Str::studly($key);
+
+ if (method_exists($this, $method)) {
+ return $this->{$method}();
+ }
+
+ return $default;
+ }
+
+ public function set(string $key, mixed $value): self
+ {
+ $method = 'set'.Str::studly($key);
+
+ if (method_exists($this, $method)) {
+ $this->{$method}($value);
+ }
+
+ return $this;
+ }
+
+ public function offsetExists(mixed $offset): bool
+ {
+ return !is_null($this->get($offset));
+ }
+
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->get($offset);
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ $this->set($offset, $value);
+ }
+
+ public function offsetUnset(mixed $offset): void {}
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Traits/Arrayable.php b/addons/epay/library/v3new/Yansongda/Supports/Traits/Arrayable.php
new file mode 100644
index 0000000..0e0d3dd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Traits/Arrayable.php
@@ -0,0 +1,25 @@
+getProperties() as $item) {
+ $k = $item->getName();
+ $method = 'get'.Str::studly($k);
+
+ $result[Str::snake($k)] = method_exists($this, $method) ? $this->{$method}() : $this->{$k};
+ }
+
+ return $result;
+ }
+}
diff --git a/addons/epay/library/v3new/Yansongda/Supports/Traits/Serializable.php b/addons/epay/library/v3new/Yansongda/Supports/Traits/Serializable.php
new file mode 100644
index 0000000..9735dcd
--- /dev/null
+++ b/addons/epay/library/v3new/Yansongda/Supports/Traits/Serializable.php
@@ -0,0 +1,58 @@
+toArray();
+ }
+
+ return [];
+ }
+
+ public function __unserialize(array $data): void
+ {
+ $this->unserializeArray($data);
+ }
+
+ public function __toString(): string
+ {
+ return $this->toJson();
+ }
+
+ public function serialize(): ?string
+ {
+ return serialize($this);
+ }
+
+ public function unserialize(string $data): void
+ {
+ unserialize($data);
+ }
+
+ public function toJson(int $option = JSON_UNESCAPED_UNICODE): string
+ {
+ return json_encode($this->__serialize(), $option);
+ }
+
+ public function jsonSerialize(): array
+ {
+ return $this->__serialize();
+ }
+
+ public function unserializeArray(array $data): self
+ {
+ foreach ($data as $key => $item) {
+ if (method_exists($this, 'set')) {
+ $this->set(strval($key), $item);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/addons/epay/library/v3new/version.txt b/addons/epay/library/v3new/version.txt
new file mode 100644
index 0000000..9de88e1
--- /dev/null
+++ b/addons/epay/library/v3new/version.txt
@@ -0,0 +1 @@
+v3.7.14
\ No newline at end of file
diff --git a/addons/epay/licenses/yansongda-pay.txt b/addons/epay/licenses/yansongda-pay.txt
new file mode 100644
index 0000000..6ceb153
--- /dev/null
+++ b/addons/epay/licenses/yansongda-pay.txt
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 yansongda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/addons/epay/view/api/alipay.html b/addons/epay/view/api/alipay.html
new file mode 100644
index 0000000..9edf12d
--- /dev/null
+++ b/addons/epay/view/api/alipay.html
@@ -0,0 +1,47 @@
+
+
+ 支付宝支付
+
+ 请在 60 秒内完成支付
+
+
+
+
+
+
+
+
+
+
+
订单标题:{$orderData.title}
+
订单编号:{$orderData.orderid}
+
订单价格:¥{$orderData.amount} 元
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/epay/view/api/wechat.html b/addons/epay/view/api/wechat.html
new file mode 100644
index 0000000..07af957
--- /dev/null
+++ b/addons/epay/view/api/wechat.html
@@ -0,0 +1,90 @@
+{if $type=='jsapi'}
+
+
+
+ 正在发起微信支付
+ 如果页面未自动跳转
+
+
+
+
+
+
+
+{elseif $type=='pc' /}
+
+
+ 微信支付
+
+ 请在 60 秒内完成支付
+
+
+
+
+
+
+
+
+
+
+
订单标题:{$orderData.title}
+
订单编号:{$orderData.orderid}
+
订单价格:¥{$orderData.amount} 元
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{/if}
diff --git a/addons/epay/view/index/index.html b/addons/epay/view/index/index.html
new file mode 100644
index 0000000..e70024a
--- /dev/null
+++ b/addons/epay/view/index/index.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
开发工作
+
+
+
在你的PHP代码中调用相关代码进行支付,请参考控制器代码
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/epay/view/layout/default.html b/addons/epay/view/layout/default.html
new file mode 100644
index 0000000..03c9404
--- /dev/null
+++ b/addons/epay/view/layout/default.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+ {$title} - {$site.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{__CONTENT__}
+
+
+
+
+
+
+
+
Copyright © {$site.name} 2017-{:date("Y")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/wechat/.addonrc b/addons/wechat/.addonrc
new file mode 100644
index 0000000..148d21b
--- /dev/null
+++ b/addons/wechat/.addonrc
@@ -0,0 +1 @@
+{"files":["application\/admin\/lang\/zh-cn\/wechat\/autoreply.php","application\/admin\/lang\/zh-cn\/wechat\/config.php","application\/admin\/lang\/zh-cn\/wechat\/response.php","application\/admin\/model\/WechatAutoreply.php","application\/admin\/model\/WechatContext.php","application\/admin\/model\/WechatCaptcha.php","application\/admin\/model\/WechatConfig.php","application\/admin\/model\/WechatResponse.php","application\/admin\/view\/wechat\/response\/select.html","application\/admin\/view\/wechat\/response\/add.html","application\/admin\/view\/wechat\/response\/edit.html","application\/admin\/view\/wechat\/response\/index.html","application\/admin\/view\/wechat\/menu\/index.html","application\/admin\/view\/wechat\/autoreply\/add.html","application\/admin\/view\/wechat\/autoreply\/edit.html","application\/admin\/view\/wechat\/autoreply\/index.html","application\/admin\/view\/wechat\/config\/add.html","application\/admin\/view\/wechat\/config\/edit.html","application\/admin\/view\/wechat\/config\/index.html","application\/admin\/controller\/wechat\/Response.php","application\/admin\/controller\/wechat\/Autoreply.php","application\/admin\/controller\/wechat\/Config.php","application\/admin\/controller\/wechat\/Menu.php","public\/assets\/js\/backend\/wechat\/autoreply.js","public\/assets\/js\/backend\/wechat\/config.js","public\/assets\/js\/backend\/wechat\/response.js","public\/assets\/js\/backend\/wechat\/menu.js","public\/assets\/addons\/wechat\/images\/mobile_index.png","public\/assets\/addons\/wechat\/images\/mobile_header_bg.png","public\/assets\/addons\/wechat\/images\/weixin_icon.png","public\/assets\/addons\/wechat\/images\/mobile_footer_bg.png","public\/assets\/addons\/wechat\/css\/menu.css"],"license":"regular","licenseto":"26425","licensekey":"IafQnvsMgWuA382t WNVtAQbS7OFZNjPNfUp5xg==","domains":["gynhc.com"],"licensecodes":[],"validations":["baca5b0e114fc9a55d47e7adb2332062"],"menus":["wechat","wechat\/autoreply","wechat\/autoreply\/index","wechat\/autoreply\/add","wechat\/autoreply\/edit","wechat\/autoreply\/del","wechat\/autoreply\/multi","wechat\/config","wechat\/config\/index","wechat\/config\/add","wechat\/config\/edit","wechat\/config\/del","wechat\/config\/multi","wechat\/menu","wechat\/menu\/index","wechat\/menu\/add","wechat\/menu\/edit","wechat\/menu\/del","wechat\/menu\/remote","wechat\/menu\/sync","wechat\/menu\/multi","wechat\/response","wechat\/response\/index","wechat\/response\/add","wechat\/response\/edit","wechat\/response\/del","wechat\/response\/select","wechat\/response\/multi"]}
\ No newline at end of file
diff --git a/addons/wechat/Wechat.php b/addons/wechat/Wechat.php
new file mode 100644
index 0000000..df65392
--- /dev/null
+++ b/addons/wechat/Wechat.php
@@ -0,0 +1,110 @@
+ 'wechat',
+ 'title' => '微信管理',
+ 'icon' => 'fa fa-wechat',
+ 'sublist' => [
+ [
+ 'name' => 'wechat/autoreply',
+ 'title' => '自动回复管理',
+ 'icon' => 'fa fa-reply-all',
+ 'sublist' => [
+ ['name' => 'wechat/autoreply/index', 'title' => '查看'],
+ ['name' => 'wechat/autoreply/add', 'title' => '添加'],
+ ['name' => 'wechat/autoreply/edit', 'title' => '修改'],
+ ['name' => 'wechat/autoreply/del', 'title' => '删除'],
+ ['name' => 'wechat/autoreply/multi', 'title' => '批量更新'],
+ ]
+ ],
+ [
+ 'name' => 'wechat/config',
+ 'title' => '配置管理',
+ 'icon' => 'fa fa-cog',
+ 'sublist' => [
+ ['name' => 'wechat/config/index', 'title' => '查看'],
+ ['name' => 'wechat/config/add', 'title' => '添加'],
+ ['name' => 'wechat/config/edit', 'title' => '修改'],
+ ['name' => 'wechat/config/del', 'title' => '删除'],
+ ['name' => 'wechat/config/multi', 'title' => '批量更新'],
+ ]
+ ],
+ [
+ 'name' => 'wechat/menu',
+ 'title' => '菜单管理',
+ 'icon' => 'fa fa-list',
+ 'sublist' => [
+ ['name' => 'wechat/menu/index', 'title' => '查看'],
+ ['name' => 'wechat/menu/add', 'title' => '添加'],
+ ['name' => 'wechat/menu/edit', 'title' => '修改'],
+ ['name' => 'wechat/menu/del', 'title' => '删除'],
+ ['name' => 'wechat/menu/remote', 'title' => '加载远程菜单'],
+ ['name' => 'wechat/menu/sync', 'title' => '同步'],
+ ['name' => 'wechat/menu/multi', 'title' => '批量更新'],
+ ]
+ ],
+ [
+ 'name' => 'wechat/response',
+ 'title' => '资源管理',
+ 'icon' => 'fa fa-list-alt',
+ 'sublist' => [
+ ['name' => 'wechat/response/index', 'title' => '查看'],
+ ['name' => 'wechat/response/add', 'title' => '添加'],
+ ['name' => 'wechat/response/edit', 'title' => '修改'],
+ ['name' => 'wechat/response/del', 'title' => '删除'],
+ ['name' => 'wechat/response/select', 'title' => '选择'],
+ ['name' => 'wechat/response/multi', 'title' => '批量更新'],
+ ]
+ ]
+ ]
+ ]
+ ];
+ Menu::create($menu);
+ return true;
+ }
+
+ /**
+ * 插件卸载方法
+ * @return bool
+ */
+ public function uninstall()
+ {
+ Menu::delete('wechat');
+ return true;
+ }
+
+ /**
+ * 插件启用方法
+ */
+ public function enable()
+ {
+ Menu::enable('wechat');
+ }
+
+ /**
+ * 插件禁用方法
+ */
+ public function disable()
+ {
+ Menu::disable('wechat');
+ }
+
+}
diff --git a/addons/wechat/config.php b/addons/wechat/config.php
new file mode 100644
index 0000000..ac39a83
--- /dev/null
+++ b/addons/wechat/config.php
@@ -0,0 +1,100 @@
+ 'app_id',
+ 'title' => '微信公众号app_id',
+ 'type' => 'string',
+ 'content' => [],
+ 'value' => '',
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'secret',
+ 'title' => '微信公众号secret',
+ 'type' => 'string',
+ 'content' => [],
+ 'value' => '',
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'token',
+ 'title' => 'token',
+ 'type' => 'string',
+ 'content' => [],
+ 'value' => '',
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'aes_key',
+ 'title' => 'aes_key',
+ 'type' => 'string',
+ 'content' => [],
+ 'value' => '',
+ 'rule' => '',
+ 'msg' => '',
+ 'tip' => '',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'debug',
+ 'title' => '调试模式',
+ 'type' => 'radio',
+ 'content' => [
+ '否',
+ '是',
+ ],
+ 'value' => '0',
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'log_level',
+ 'title' => '日志记录等级',
+ 'type' => 'select',
+ 'content' => [
+ 'debug' => 'debug',
+ 'info' => 'info',
+ 'notice' => 'notice',
+ 'warning' => 'warning',
+ 'error' => 'error',
+ 'critical' => 'critical',
+ 'alert' => 'alert',
+ 'emergency' => 'emergency',
+ ],
+ 'value' => 'info',
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '生产环境日志记录等级',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+ [
+ 'name' => 'oauth_callback',
+ 'title' => '登录回调',
+ 'type' => 'string',
+ 'content' => [],
+ 'value' => 'https://www.example.com/addons/wechat/index/callback',
+ 'rule' => 'required',
+ 'msg' => '',
+ 'tip' => '',
+ 'ok' => '',
+ 'extend' => '',
+ ],
+];
diff --git a/addons/wechat/controller/Captcha.php b/addons/wechat/controller/Captcha.php
new file mode 100644
index 0000000..3d819d6
--- /dev/null
+++ b/addons/wechat/controller/Captcha.php
@@ -0,0 +1,74 @@
+request->post("captcha");
+ $event = $this->request->post("event");
+ $result = WechatCaptcha::check($captcha, $event, null, false);
+ if ($result) {
+ $this->success("验证码正确");
+ } else {
+ $this->error("验证码错误");
+ }
+ }
+
+ /**
+ * 验证码发送接口
+ */
+ public function send()
+ {
+ $ip = $this->request->ip();
+ $event = $this->request->post("event");
+ if (!$event) {
+ $this->error("参数错误");
+ }
+ $captch = WechatCaptcha::where('ip', $ip)
+ ->where('event', $event)
+ ->whereTime('createtime', '-2 minutes')
+ ->find();
+ if ($captch) {
+ $this->error("获取频繁,请稍后重试");
+ }
+ $token = Wechat::getAccessToken();
+ if (!$token) {
+ $this->error("发送失败,请稍后重试");
+ }
+ $context = \fast\Random::alnum(6);
+ $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={$token}";
+ $params = [
+ 'expire_seconds' => 120,
+ 'action_name' => 'QR_STR_SCENE',
+ 'action_info' => [
+ 'scene' => [
+ 'scene_str' => "captcha_" . $event . "_" . $ip . "_" . $context,
+ ]
+ ],
+ ];
+ //获取验证码
+ $result = Http::sendRequest($url, json_encode($params));
+ if ($result['ret']) {
+ $msg = (array)json_decode($result['msg'], true);
+ if (isset($msg['ticket']) && isset($msg['url'])) {
+ \think\Cookie::set('captcha_' . $event, $context);
+ $this->success("", null, ['image' => "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" . urlencode($msg['ticket']), 'url' => $msg['url']]);
+ }
+ }
+ $this->error("获取失败!请稍后重试");
+ }
+
+}
diff --git a/addons/wechat/controller/Index.php b/addons/wechat/controller/Index.php
new file mode 100644
index 0000000..9f5dfe6
--- /dev/null
+++ b/addons/wechat/controller/Index.php
@@ -0,0 +1,166 @@
+app = Factory::officialAccount(Config::load());
+ }
+
+ /**
+ *
+ */
+ public function index()
+ {
+ $this->error("当前插件暂无前台页面");
+ }
+
+ /**
+ * 微信API对接接口
+ */
+ public function api()
+ {
+ $this->app->server->push(function ($message) {
+ $wechatService = new WechatService;
+
+ $matches = null;
+ $openid = $message['FromUserName'] ?? '';
+ $to_openid = $message['ToUserName'] ?? '';
+
+ $unknownMessage = WechatConfig::getValue('default.unknown.message');
+ $unknownMessage = $unknownMessage ?: "";
+
+ switch ($message['MsgType']) {
+ case 'event': //事件消息
+ $event = $message['Event'];
+ $eventkey = $message['EventKey'] ?: $message['Event'];
+ //验证码消息
+ if (in_array($event, ['subscribe', 'SCAN']) && preg_match("/^(qrscene_)?captcha_([a-zA-Z0-9]+)_([0-9\.]+)_([0-9a-zA-Z]{6})/", $eventkey, $matches)) {
+ return WechatCaptcha::send($openid, $matches[2], $matches[3], $matches[4]);
+ }
+ switch ($event) {
+ case 'subscribe'://添加关注
+ $subscribeMessage = WechatConfig::getValue('default.subscribe.message');
+ $subscribeMessage = $subscribeMessage ?: "欢迎关注我们!";
+ return $subscribeMessage;
+ case 'unsubscribe'://取消关注
+ return '';
+ case 'LOCATION'://获取地理位置
+ return '';
+ case 'VIEW': //跳转链接,eventkey为链接
+ return '';
+ case 'SCAN': //扫码
+ return '';
+ default:
+ break;
+ }
+
+ $wechatResponse = WechatResponse::where(["eventkey" => $eventkey, 'status' => 'normal'])->find();
+ if ($wechatResponse) {
+ $responseContent = (array)json_decode($wechatResponse['content'], true);
+ $wechatContext = WechatContext::where(['openid' => $openid])->order('id', 'desc')->find();
+ $data = ['eventkey' => $eventkey, 'command' => '', 'refreshtime' => time(), 'openid' => $openid];
+ if ($wechatContext) {
+ $wechatContext->save($data);
+ } else {
+ $wechatContext = WechatContext::create($data, true);
+ }
+ $result = $wechatService->response($this, $openid, '', $responseContent, $wechatContext);
+ if ($result) {
+ return $result;
+ }
+ }
+ return $unknownMessage;
+ case 'text': //文字消息
+ case 'image': //图片消息
+ case 'voice': //语音消息
+ case 'video': //视频消息
+ case 'location': //坐标消息
+ case 'link': //链接消息
+ default: //其它消息
+ //自动回复处理
+ if ($message['MsgType'] == 'text') {
+ $autoreply = null;
+ $autoreplyList = WechatAutoreply::where('status', 'normal')->field('id,text,eventkey')->order('weigh DESC,id DESC')->select();
+ try {
+ foreach ($autoreplyList as $index => $item) {
+ //完全匹配和正则匹配
+ if ($item['text'] == $message['Content'] || (in_array(mb_substr($item['text'], 0, 1), ['#', '~', '/']) && preg_match($item['text'], $message['Content'], $matches))) {
+ $autoreply = $item;
+ break;
+ }
+ }
+ } catch (\Exception $e) {
+
+ }
+
+ if ($autoreply) {
+ $wechatResponse = WechatResponse::where(["eventkey" => $autoreply['eventkey'], 'status' => 'normal'])->find();
+ if ($wechatResponse) {
+ $responseContent = (array)json_decode($wechatResponse['content'], true);
+ $wechatContext = WechatContext::where(['openid' => $openid])->order('id', 'desc')->find();
+ $result = $wechatService->response($this, $openid, $message['Content'], $responseContent, $wechatContext, $matches);
+ if ($result) {
+ return $result;
+ }
+ }
+ }
+ }
+ return $unknownMessage;
+ }
+ return ""; //SUCCESS
+ });
+
+ $response = $this->app->server->serve();
+ // 将响应输出
+ $response->send();
+ return;
+ }
+
+ /**
+ * 登录回调
+ */
+ public function callback()
+ {
+
+ }
+
+ /**
+ * 支付回调
+ */
+ public function notify()
+ {
+ Log::record(file_get_contents('php://input'), "notify");
+ $response = $this->app->handlePaidNotify(function ($message, $fail) {
+ // 你的逻辑
+ return true;
+ // 或者错误消息
+ $fail('Order not exists.');
+ });
+
+ $response->send();
+ return;
+ }
+
+}
diff --git a/addons/wechat/info.ini b/addons/wechat/info.ini
new file mode 100644
index 0000000..0623a64
--- /dev/null
+++ b/addons/wechat/info.ini
@@ -0,0 +1,10 @@
+name = wechat
+title = 微信管理
+intro = 在线管理微信公众号插件
+author = FastAdmin
+website =
+version = 1.2.5
+state = 1
+url = /addons/wechat
+license = regular
+licenseto = 26425
diff --git a/addons/wechat/install.sql b/addons/wechat/install.sql
new file mode 100644
index 0000000..a920b42
--- /dev/null
+++ b/addons/wechat/install.sql
@@ -0,0 +1,81 @@
+
+CREATE TABLE IF NOT EXISTS `__PREFIX__wechat_autoreply` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `title` varchar(100) NOT NULL DEFAULT '' COMMENT '标题',
+ `text` varchar(100) NOT NULL DEFAULT '' COMMENT '触发文本',
+ `eventkey` varchar(50) NOT NULL DEFAULT '' COMMENT '响应事件',
+ `remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
+ `weigh` int(10) NOT NULL DEFAULT '0' COMMENT '权重',
+ `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '添加时间',
+ `updatetime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `status` varchar(30) NOT NULL DEFAULT '' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ KEY `eventkey` (`eventkey`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='微信自动回复表';
+
+BEGIN;
+INSERT INTO `__PREFIX__wechat_autoreply`(`id`, `title`, `text`, `eventkey`, `remark`, `weigh`, `createtime`, `updatetime`, `status`) VALUES (1, '输入hello', 'hello', '58c7d908c4570', '', 1, 1493366855, 1493366855, 'normal');
+INSERT INTO `__PREFIX__wechat_autoreply`(`id`, `title`, `text`, `eventkey`, `remark`, `weigh`, `createtime`, `updatetime`, `status`) VALUES (2, '输入你好', '你好', '58fdfaa9e1965', '', 2, 1493704976, 1493704976, 'normal');
+COMMIT;
+
+CREATE TABLE IF NOT EXISTS `__PREFIX__wechat_captcha` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `event` varchar(100) DEFAULT '' COMMENT '事件',
+ `openid` varchar(255) DEFAULT NULL COMMENT '用户openid',
+ `context` varchar(20) DEFAULT NULL COMMENT '上下文',
+ `code` varchar(30) DEFAULT NULL COMMENT '验证码',
+ `times` int(10) unsigned DEFAULT '0' COMMENT '验证次数',
+ `ip` varchar(50) DEFAULT '' COMMENT 'IP',
+ `createtime` bigint(16) DEFAULT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ KEY `ip` (`ip`,`event`) USING BTREE,
+ KEY `openid` (`openid`(191),`event`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='微信公众号验证码';
+
+CREATE TABLE IF NOT EXISTS `__PREFIX__wechat_config` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL DEFAULT '' COMMENT '配置名称',
+ `title` varchar(50) NOT NULL DEFAULT '' COMMENT '配置标题',
+ `value` text NOT NULL COMMENT '配置值',
+ `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `updatetime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='微信配置表';
+
+BEGIN;
+INSERT INTO `__PREFIX__wechat_config` VALUES ('1', 'menu', '公众号菜单', '[{\"name\":\"外部链接\",\"sub_button\":[{\"name\":\"百度\",\"type\":\"view\",\"url\":\"https:\\/\\/www.baidu.com\"},{\"name\":\"微信开放平台\",\"type\":\"view\",\"url\":\"https:\\/\\/open.weixin.qq.com\"}]},{\"name\":\"在线客服\",\"type\":\"click\",\"key\":\"58cb852984970\"},{\"name\":\"关于我们\",\"type\":\"click\",\"key\":\"58bf944aa0777\"}]', '1497398820', '1500538185'), ('2', 'service', '客服配置', '{\"onlinetime\":\"09:00-18:00\",\"offlinemsg\":\"请在工作时间联系客服!\",\"nosessionmsg\":\"当前没有客服在线!请稍后重试!\",\"waitformsg\":\"请问有什么可以帮到您?\"}', '1497429674', '1497429674'), ('3', 'signin', '连续登录配置', '{\"s1\":\"100\",\"s2\":\"200\",\"s3\":\"300\",\"sn\":\"500\"}', '1497429711', '1497429711');
+COMMIT;
+
+CREATE TABLE IF NOT EXISTS `__PREFIX__wechat_context` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `openid` varchar(64) NOT NULL DEFAULT '',
+ `type` varchar(30) NOT NULL DEFAULT '' COMMENT '类型',
+ `eventkey` varchar(64) NOT NULL DEFAULT '',
+ `command` varchar(64) NOT NULL DEFAULT '',
+ `message` varchar(255) NOT NULL DEFAULT '' COMMENT '内容',
+ `refreshtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '最后刷新时间',
+ `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `updatetime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `openid` (`openid`,`eventkey`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信上下文表';
+
+CREATE TABLE IF NOT EXISTS `__PREFIX__wechat_response` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `title` varchar(100) NOT NULL DEFAULT '' COMMENT '资源名',
+ `eventkey` varchar(128) NOT NULL DEFAULT '' COMMENT '事件',
+ `type` enum('text','image','news','voice','video','music','link','app') NOT NULL DEFAULT 'text' COMMENT '类型',
+ `content` text NOT NULL COMMENT '内容',
+ `remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
+ `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `updatetime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `status` varchar(30) NOT NULL DEFAULT '' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `eventkey` (`eventkey`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='微信资源表';
+
+BEGIN;
+INSERT INTO `__PREFIX__wechat_response` VALUES ('1', '签到送积分', '58adaf7876aab', 'app', '{\"app\":\"signin\"}', '', '1487777656', '1487777656', 'normal'), ('2', '关于我们', '58bf944aa0777', 'app', '{\"app\":\"page\",\"id\":\"1\"}', '', '1488950346', '1488950346', 'normal'), ('3', '自动回复1', '58c7d908c4570', 'text', '{\"content\":\"world\"}', '', '1489492232', '1489492232', 'normal'), ('5', '自动回复2', '58fdfaa9e1965', 'text', '{\"content\":\"你好!我是自动回复!\"}', '', '1493039785', '1493039785', 'normal');
+COMMIT;
+
diff --git a/addons/wechat/library/Config.php b/addons/wechat/library/Config.php
new file mode 100644
index 0000000..6b327f6
--- /dev/null
+++ b/addons/wechat/library/Config.php
@@ -0,0 +1,82 @@
+ $config['app_id'], // AppID
+ 'secret' => $config['secret'], // AppSecret
+ 'token' => $config['token'], // Token
+ 'aes_key' => $config['aes_key'], // EncodingAESKey,安全模式下请一定要填写!!!
+
+ /**
+ * 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
+ * 使用自定义类名时,构造函数将会接收一个 `EasyWeChat\Kernel\Http\Response` 实例
+ */
+ 'response_type' => 'array',
+
+ /**
+ * 日志配置
+ *
+ * level: 日志级别, 可选为:debug/info/notice/warning/error/critical/alert/emergency
+ * path:日志文件位置(绝对路径!!!),要求可写权限
+ */
+ 'log' => [
+ 'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod
+ 'channels' => [
+ // 测试环境
+ 'dev' => [
+ 'driver' => 'single',
+ 'path' => ROOT_PATH . '/runtime/log/easywechat.log',
+ 'level' => 'debug',
+ ],
+ // 生产环境
+ 'prod' => [
+ 'driver' => 'daily',
+ 'path' => ROOT_PATH . '/runtime/log/easywechat.log',
+ 'level' => $config['log_level'],
+ ],
+ ],
+ ],
+
+ /**
+ * 接口请求相关配置,超时时间等,具体可用参数请参考:
+ * http://docs.guzzlephp.org/en/stable/request-config.html
+ *
+ * - retries: 重试次数,默认 1,指定当 http 请求失败时重试的次数。
+ * - retry_delay: 重试延迟间隔(单位:ms),默认 500
+ * - log_template: 指定 HTTP 日志模板,请参考:https://github.com/guzzle/guzzle/blob/master/src/MessageFormatter.php
+ */
+ 'http' => [
+ 'max_retries' => 1,
+ 'retry_delay' => 500,
+ 'timeout' => 5.0,
+ // 'base_uri' => 'https://api.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
+ ],
+
+ /**
+ * OAuth 配置
+ *
+ * scopes:公众平台(snsapi_userinfo / snsapi_base),开放平台:snsapi_login
+ * callback:OAuth授权完成后的回调页地址
+ */
+ 'oauth' => [
+ 'scopes' => ['snsapi_userinfo'],
+ 'callback' => $config['oauth_callback'],
+ ],
+ ];
+ }
+
+}
diff --git a/addons/wechat/library/Response.php b/addons/wechat/library/Response.php
new file mode 100644
index 0000000..6df9504
--- /dev/null
+++ b/addons/wechat/library/Response.php
@@ -0,0 +1,27 @@
+ $value) {
+ if (in_array($value, ['.', '..'])) {
+ unset($dirList[$key]);
+ } else {
+ $name = substr($value, 0, -4);
+ $className = '\\addons\\wechat\\library\\response\\' . $name;
+ $appList[strtolower($name)] = new $className();
+ }
+ }
+ return $appList;
+ }
+
+ public static function appConfig()
+ {
+ $appConfigList = [];
+ $appList = self::getAppList();
+ foreach ($appList as $key => $value) {
+ if (method_exists($value, 'config')) {
+ $appConfigList[$key] = $value->config();
+ $appConfigList[$key]['available'] = $value->available();
+ $appConfigList[$key]['name'] .= ($appConfigList[$key]['available'] ? '' : '(未安装或未启用)');
+ }
+ }
+ return $appConfigList;
+ }
+
+ /**
+ * 应用交互
+ * @return array|bool|mixed|string
+ */
+ public function response($obj, $openid, $message, $content, $context, $matches = null)
+ {
+ if (isset($content['app'])) {
+ $response = '';
+ $entry = null;
+ $keyword = isset($content['searchregexindex']) && $content['searchregexindex'] > -1 && $matches && isset($matches[$content['searchregexindex']])
+ ? $matches[$content['searchregexindex']] : $message;
+ $appList = self::getAppList();
+ if (isset($appList[$content['app']])) {
+ $app = $appList[$content['app']];
+ if (method_exists($app, 'response')) {
+ $entry = $app->response($obj, $openid, $message, $content, $context, $matches, $keyword);
+ }
+ }
+ if ($entry) {
+ if (is_string($entry)) {
+ return $entry;
+ }
+ if ($entry instanceof News) {
+ $news = $entry;
+ } elseif ($entry instanceof NewsItem) {
+ $news = new News([$entry]);
+ } elseif (is_array($entry) || $entry instanceof \think\Model) {
+ if (isset($entry['title']) || isset($entry['description'])) {
+ $items = [
+ new NewsItem([
+ 'title' => $entry['title'] ?? ($entry['nickname'] ?? ''),
+ 'description' => $entry['description'] ?? '',
+ 'url' => $entry['fullurl'],
+ 'image' => cdnurl($entry['image'], true),
+ ]),
+ ];
+ $news = new News($items);
+ } else {
+ $items = [];
+ foreach ($entry as $item) {
+ if ($item instanceof NewsItem) {
+ $items[] = $item;
+ } else {
+ $items[] = new NewsItem([
+ 'title' => $item['title'] ?? ($item['nickname'] ?? ''),
+ 'description' => $item['description'] ?? '',
+ 'url' => $item['fullurl'],
+ 'image' => cdnurl($item['image'], true),
+ ]);
+ }
+ }
+ $news = new News($items);
+ }
+ }
+
+ if (isset($news)) {
+ $response = $news;
+ }
+ }
+ } else {
+ $response = $content['content'] ?? '';
+ }
+ return $response;
+ }
+
+ /**
+ * 获取Token
+ */
+ public static function getAccessToken()
+ {
+ $app = Factory::officialAccount(\addons\wechat\library\Config::load());
+ $accessToken = $app->access_token;
+ $token = $accessToken ? $accessToken->getToken() : [];
+ return $token['access_token'] ?? '';
+ }
+}
diff --git a/addons/wechat/library/response/Ask.php b/addons/wechat/library/response/Ask.php
new file mode 100644
index 0000000..f41a5fa
--- /dev/null
+++ b/addons/wechat/library/response/Ask.php
@@ -0,0 +1,94 @@
+available() ? '' : 'disabled="disabled"';
+ return [
+ 'name' => '知识付费问答社区',
+ 'config' => [
+ [
+ 'type' => 'text',
+ 'caption' => '问题ID',
+ 'field' => 'question_id',
+ 'extend' => 'class="form-control selectpage" data-source="ask/question/index" data-field="title" ' . $disabled,
+ 'options' => ''
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '文章ID',
+ 'field' => 'article_id',
+ 'extend' => 'class="form-control selectpage" data-source="ask/article/index" data-field="title"' . $disabled,
+ 'options' => ''
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索问题',
+ 'field' => 'searchquestion',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索文章',
+ 'field' => 'searcharticle',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '正则搜索匹配索引',
+ 'field' => 'searchregexindex',
+ 'rule' => '',
+ 'defaultvalue' => '1',
+ 'extend' => '',
+ 'options' => [],
+ ]
+ ]
+ ];
+ }
+
+ public function response($obj, $openid, $message, $content, $context, $matches = null, $keyword = '')
+ {
+ if (!$this->available()) {
+ return "请先在后台管理安装并启用《知识付费问答》插件";
+ }
+
+ if (isset($content['question_id']) && $content['question_id']) {
+ $entry = \addons\ask\model\Question::get($content['question_id']);
+ } elseif (isset($content['article_id']) && $content['article_id']) {
+ $entry = \addons\ask\model\Article::get($content['article_id']);
+ }
+
+ if (!$entry && $content['searchquestion']) {
+ $entry = \addons\ask\model\Question::where("title", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+ if (!$entry && $content['searcharticle']) {
+ $entry = \addons\ask\model\Article::where("title|description", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+ if (!$entry) {
+ return "未搜索到任何匹配信息";
+ }
+ return $entry;
+ }
+}
\ No newline at end of file
diff --git a/addons/wechat/library/response/Blog.php b/addons/wechat/library/response/Blog.php
new file mode 100644
index 0000000..1b6463c
--- /dev/null
+++ b/addons/wechat/library/response/Blog.php
@@ -0,0 +1,71 @@
+available() ? '' : 'disabled="disabled"';
+ return [
+ 'name' => '简单博客',
+ 'config' => [
+ [
+ 'type' => 'text',
+ 'caption' => '博客日志ID',
+ 'field' => 'post_id',
+ 'rule' => '',
+ 'extend' => 'class="form-control selectpage" data-source="blog/post/index" data-field="title" ' . $disabled,
+ 'options' => '',
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索日志',
+ 'field' => 'searchpost',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '正则搜索匹配索引',
+ 'field' => 'searchregexindex',
+ 'rule' => '',
+ 'defaultvalue' => '1',
+ 'extend' => '',
+ 'options' => [],
+ ]
+ ]
+ ];
+ }
+
+ public function response($obj, $openid, $message, $content, $context, $matches = null, $keyword = '')
+ {
+ if (!$this->available()) {
+ return "请先在后台管理安装并启用《简单博客》插件";
+ }
+ $entry = \addons\blog\model\Post::get($content['post_id']);
+ if ($entry) {
+ $entry['image'] = $entry['thumb'];
+ }
+ if (!$entry && $content['searchpost']) {
+ $entry = \addons\blog\model\Post::where("title|description", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+ if (!$entry) {
+ return "未搜索到任何匹配信息$keyword" . json_encode($matches);
+ }
+ return $entry;
+ }
+}
\ No newline at end of file
diff --git a/addons/wechat/library/response/Cms.php b/addons/wechat/library/response/Cms.php
new file mode 100644
index 0000000..5144d8a
--- /dev/null
+++ b/addons/wechat/library/response/Cms.php
@@ -0,0 +1,119 @@
+available() ? '' : 'disabled="disabled"';
+ return [
+ 'name' => 'CMS内容管理系统',
+ 'config' => [
+ [
+ 'type' => 'text',
+ 'caption' => '文章ID',
+ 'field' => 'archives_id',
+ 'rule' => '',
+ 'extend' => 'class="form-control selectpage" data-source="cms/archives/index" data-field="title" ' . $disabled,
+ 'options' => ''
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '单页ID',
+ 'field' => 'page_id',
+ 'rule' => '',
+ 'extend' => 'class="form-control selectpage" data-source="cms/page/index" data-field="title" ' . $disabled,
+ 'options' => ''
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '专题ID',
+ 'field' => 'special_id',
+ 'rule' => '',
+ 'extend' => 'class="form-control selectpage" data-source="cms/special/index" data-field="title" ' . $disabled,
+ 'options' => ''
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索文章',
+ 'field' => 'searcharchives',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索单页',
+ 'field' => 'searchpage',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索专题',
+ 'field' => 'searchspecial',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '正则搜索匹配索引',
+ 'field' => 'searchregexindex',
+ 'rule' => '',
+ 'defaultvalue' => '1',
+ 'extend' => '',
+ 'options' => [],
+ ]
+ ]
+ ];
+ }
+
+ public function response($obj, $openid, $message, $content, $context, $matches = null, $keyword = '')
+ {
+ if (!$this->available()) {
+ return "请先在后台管理安装并启用《CMS内容管理系统》插件";
+ }
+ $entry = null;
+ if (isset($content['archives_id']) && $content['archives_id']) {
+ $entry = \addons\cms\model\Archives::get($content['archives_id']);
+ } elseif (isset($content['page_id']) && $content['page_id']) {
+ $entry = \addons\cms\model\Page::get($content['page_id']);
+ } elseif (isset($content['special_id']) && $content['special_id']) {
+ $entry = \addons\cms\model\Special::get($content['special_id']);
+ }
+ if (!$entry && $content['searcharchives']) {
+ $entry = \addons\cms\model\Archives::where("title|description", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+ if (!$entry && $content['searchpage']) {
+ $entry = \addons\cms\model\Page::where("title|description", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+ if (!$entry && $content['searchspecial']) {
+ $entry = \addons\cms\model\Special::where("title|description", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+ if (!$entry) {
+ return "未搜索到任何匹配信息";
+ }
+ return $entry;
+ }
+}
\ No newline at end of file
diff --git a/addons/wechat/library/response/Signin.php b/addons/wechat/library/response/Signin.php
new file mode 100644
index 0000000..159c3f5
--- /dev/null
+++ b/addons/wechat/library/response/Signin.php
@@ -0,0 +1,70 @@
+available() ? '' : 'disabled="disabled"';
+ return [
+ 'name' => '会员签到送积分',
+ 'config' => []
+ ];
+ }
+
+ public function response($obj, $openid, $message, $content, $context, $matches = null, $keyword = '')
+ {
+ $signinInfo = get_addon_info('signin');
+ if (!$this->available()) {
+ return "请先在后台管理安装并启用《会员签到》插件";
+ }
+
+ $thirdInfo = get_addon_info('third');
+ if (!$thirdInfo || !$thirdInfo['state']) {
+ return "请先在后台管理安装并启用《第三方登录》插件";
+ }
+ $user = $this->getUserByOpenid($openid);
+ if (!$user) {
+ return "请先在会员中心绑定微信登录, 'wechat'], true, true) . "'>点击这里绑定 ";
+ }
+
+ if (version_compare($signinInfo['version'], '1.0.5', '<')) {
+ return "请升级《会员签到》插件到最新版本";
+ }
+
+ $result = Service::dosign($user->id);
+ if (isset($result['errmsg'])) {
+ return $result['errmsg'];
+ } else {
+ return '签到成功!连续签到' . $result['successions'] . '天!获得' . $result['score'] . '积分';
+ }
+ }
+
+ /**
+ * 根据Openid获取用户信息
+ * @param string $openid 微信OpenID
+ * @return User|null
+ */
+ public function getUserByOpenid($openid)
+ {
+ $third = Third::where('platform', 'wechat')->where('openid', $openid)->find();
+ if ($third && $third->user_id) {
+ return User::get($third->user_id);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/addons/wechat/library/response/Vote.php b/addons/wechat/library/response/Vote.php
new file mode 100644
index 0000000..5637429
--- /dev/null
+++ b/addons/wechat/library/response/Vote.php
@@ -0,0 +1,96 @@
+available() ? '' : 'disabled="disabled"';
+ return [
+ 'name' => '在线投票系统',
+ 'config' => [
+ [
+ 'type' => 'text',
+ 'caption' => '投票主题ID',
+ 'field' => 'subject_id',
+ 'extend' => 'class="form-control selectpage" data-source="vote/subject/index" data-field="title" ' . $disabled,
+ 'rule' => '',
+ 'options' => ''
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '参赛人员ID',
+ 'field' => 'player_id',
+ 'rule' => '',
+ 'extend' => 'class="form-control selectpage" data-source="vote/player/index" data-field="nickname" ' . $disabled,
+ 'options' => ''
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索主题',
+ 'field' => 'searchsubject',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'radio',
+ 'caption' => '开启搜索参赛人员',
+ 'field' => 'searchplayer',
+ 'rule' => '',
+ 'extend' => '',
+ 'options' => [
+ '1' => '是',
+ '0' => '否',
+ ],
+ ],
+ [
+ 'type' => 'text',
+ 'caption' => '正则搜索匹配索引',
+ 'field' => 'searchregexindex',
+ 'rule' => '',
+ 'defaultvalue' => '1',
+ 'extend' => '',
+ 'options' => [],
+ ]
+ ]
+ ];
+ }
+
+ public function response($obj, $openid, $message, $content, $context, $matches = null, $keyword = '')
+ {
+ if (!$this->available()) {
+ return "请先在后台管理安装并启用《在线投票系统》插件";
+ }
+ if (isset($content['subject_id']) && $content['subject_id']) {
+ $entry = \addons\vote\model\Subject::all($content['subject_id']);
+ } elseif (isset($content['player_id']) && $content['player_id']) {
+ $entry = \addons\vote\model\Player::all($content['player_id']);
+ }
+
+ if (!$entry && $content['searchsubject']) {
+ $entry = \addons\vote\model\Subject::where("title|description", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+ if (!$entry && $content['searchplayer']) {
+ $entry = \addons\vote\model\Player::where("nickname", 'like', "%{$keyword}%")->where('status', 'normal')->find();
+ }
+
+ if (!$entry) {
+ return "未搜索到任何匹配信息";
+ }
+ return $entry;
+ }
+}
\ No newline at end of file
diff --git a/addons/wechat/model/WechatAutoreply.php b/addons/wechat/model/WechatAutoreply.php
new file mode 100644
index 0000000..7c1a85d
--- /dev/null
+++ b/addons/wechat/model/WechatAutoreply.php
@@ -0,0 +1,16 @@
+ $openid, 'event' => $event])->whereTime('createtime', '-2 minutes')->find();
+ if ($captcha) {
+ return "验证码发送速度过快,请稍后重试";
+ }
+ $code = Random::numeric(config('captcha.length'));
+ $data = [
+ 'event' => $event,
+ 'openid' => $openid,
+ 'code' => $code,
+ 'ip' => $ip,
+ 'context' => $context
+ ];
+ self::create($data);
+ return "你的验证码是:{$code},2分钟内输入有效";
+ }
+
+ /**
+ * 检测验证码
+ * @param $code string 验证码
+ * @param $event string 事件
+ * @param $ip string IP
+ * @param $afterdelete bool 是否删除验证码
+ * @return bool
+ */
+ public static function check($code, $event, $ip = null, $afterdelete = true)
+ {
+ $ip = is_null($ip) ? request()->ip() : $ip;
+ $context = Cookie::get('captcha_' . $event);
+ $captcha = self::where(['ip' => $ip, 'event' => $event, 'context' => $context])->whereTime('createtime', '-2 minutes')->find();
+ if ($captcha && $captcha->code == $code && $captcha->times < 10 && $context) {
+ $captcha->setInc("times");
+ if ($afterdelete) {
+ $captcha->delete();
+ }
+ return true;
+ }
+ //验证大于10次或超时
+ if ($captcha && ($captcha->times >= 10 || time() - $captcha->createtime > 120)) {
+ $captcha->delete();
+ }
+
+ return false;
+ }
+
+}
diff --git a/addons/wechat/model/WechatConfig.php b/addons/wechat/model/WechatConfig.php
new file mode 100644
index 0000000..8fb04ff
--- /dev/null
+++ b/addons/wechat/model/WechatConfig.php
@@ -0,0 +1,32 @@
+ $name]);
+ return $item ? $item->value : '';
+ }
+
+}
diff --git a/addons/wechat/model/WechatContext.php b/addons/wechat/model/WechatContext.php
new file mode 100644
index 0000000..924cc8b
--- /dev/null
+++ b/addons/wechat/model/WechatContext.php
@@ -0,0 +1,16 @@
+writeln('==> 发布模式与发布套餐升级');
$this->upgradePublish($output);
+ $output->writeln('==> LBS / 店铺装修 / 支付 / 优惠券 / 举报升级(v1.7.0)');
+ $this->upgradeV170($output);
+
$output->writeln('==> 初始化后台菜单');
$this->menu($output);
@@ -236,6 +239,93 @@ class FcInstall extends Command
: ' - 发布模式与套餐配置已是最新,无需处理');
}
+ /**
+ * v1.7.0 升级:LBS 经纬度 / 店铺装修字段 / 收藏关注 / 支付与举报配置(幂等,可重复执行)
+ *
+ * 背景:本轮新增 ① 首页「按距离排序」与详情页真实地图 → fc_post 存经纬度;
+ * ② 店铺装修与推荐位 → fc_merchant 增 banner / notice / recommend;
+ * ③ 微信支付 JSAPI v2 与优惠券、举报原因字典 → 新增 fc_config 配置项。
+ * 规则:列「不存在才 ADD COLUMN」、配置「已存在则跳过」,**绝不覆盖运营已改过的值**。
+ * 新增业务表(fc_favorite / fc_follow / fc_coupon / fc_user_coupon)在上方的 tables() 中
+ * 以 CREATE TABLE IF NOT EXISTS 建立。
+ */
+ private function upgradeV170(Output $output)
+ {
+ $prefix = \think\Config::get('database.prefix');
+ $changed = 0;
+
+ // ① fc_post:发布坐标(gcj02),供首页按距离排序与详情页地图使用
+ $postTable = $prefix . 'fc_post';
+ $postCols = [
+ ['lat', 'decimal(10,6) NULL COMMENT \'纬度 gcj02(-90~90),未定位为空\'', '`address`'],
+ ['lng', 'decimal(10,6) NULL COMMENT \'经度 gcj02(-180~180),未定位为空\'', '`lat`'],
+ ];
+ foreach ($postCols as $col) {
+ if ($this->hasColumn($postTable, $col[0])) {
+ continue;
+ }
+ Db::execute("ALTER TABLE `{$postTable}` ADD COLUMN `{$col[0]}` {$col[1]} AFTER {$col[2]}");
+ $output->writeln(' - ' . $postTable . ' 新增列 ' . $col[0]);
+ $changed++;
+ }
+
+ // ② fc_merchant:店铺头图 / 店铺公告 / 推荐位(后台「店铺装修」可编辑)
+ $merchantTable = $prefix . 'fc_merchant';
+ $merchantCols = [
+ ['banner', "varchar(255) NOT NULL DEFAULT '' COMMENT '店铺头图'", '`logo`'],
+ ['notice', "varchar(255) NOT NULL DEFAULT '' COMMENT '店铺公告'", '`banner`'],
+ ['recommend', "tinyint(1) NOT NULL DEFAULT 0 COMMENT '推荐位:1 推荐 0 普通'", '`notice`'],
+ ];
+ foreach ($merchantCols as $col) {
+ if ($this->hasColumn($merchantTable, $col[0])) {
+ continue;
+ }
+ Db::execute("ALTER TABLE `{$merchantTable}` ADD COLUMN `{$col[0]}` {$col[1]} AFTER {$col[2]}");
+ $output->writeln(' - ' . $merchantTable . ' 新增列 ' . $col[0]);
+ $changed++;
+ }
+
+ // ③ 配置项(只补缺失的,不覆盖运营已改的值)
+ // 注意:**不再写入支付配置**(历史键 wx.mchId / wx.apiKey / wx.payNotifyUrl 已废弃)。
+ // 微信支付参数由 epay 插件维护:后台「插件管理 → 微信支付宝整合插件 → 配置」,
+ // 持久化在 addons/epay/config.php(wechat.miniapp_id / wechat.mch_id / wechat.key)。
+ $defaults = [
+ ['coupon.enabled', '1', 'coupon', '优惠券功能开关:1 开启 / 0 关闭'],
+ ['report.reasons', json_encode(['虚假信息', '违法违禁', '垃圾广告', '重复发布', '其他'], JSON_UNESCAPED_UNICODE), 'report', '举报原因字典(JSON 数组)'],
+ ];
+ $now = time();
+ $added = [];
+ foreach ($defaults as $d) {
+ if (Db::name('fc_config')->where('key', $d[0])->find()) {
+ continue;
+ }
+ Db::name('fc_config')->insert([
+ 'key' => $d[0], 'value' => $d[1], 'group_key' => $d[2], 'remark' => $d[3],
+ 'createtime' => $now, 'updatetime' => $now,
+ ]);
+ $added[] = $d[0];
+ $changed++;
+ }
+ if ($added) {
+ $output->writeln(' - 新增配置 ' . count($added) . ' 项:' . implode(' / ', $added));
+ }
+
+ // ④ 插件钩子缓存:`application/extra/addons.php` 的 hooks 会被 File 缓存(Cache::get('hooks')),
+ // 若该缓存是在「插件目录还不存在」时生成的,插件启用后也不会被加载 ——
+ // 典型症状:微信支付报 `Class 'Yansongda\Pay\Pay' not found`。
+ // 这里主动清一次,保证「插件装好/更新后即刻可用」。
+ try {
+ \think\Cache::rm('hooks');
+ $output->writeln(' - 已清理插件钩子缓存 hooks(epay 插件即时生效)');
+ } catch (\Exception $e) {
+ // 清理失败不影响主流程:下一次请求会按 extra/addons.php 重建
+ }
+
+ $output->writeln($changed
+ ? ' - 完成(新增结构 ' . $changed . ' 处)'
+ : ' - LBS / 店铺装修 / 支付 / 优惠券 / 举报配置已是最新,无需处理');
+ }
+
/** 表是否存在某列 */
private function hasColumn($table, $column)
{
@@ -261,9 +351,11 @@ class FcInstall extends Command
* 同城业务 (fc)
* ├─ 内容管理 分类管理 / 轮播管理 / 信息管理 / 评价管理 / 举报处理
* ├─ 用户与商家 商家管理 / 会员管理 / 消息管理
- * ├─ 交易管理 订单管理 / 会员套餐 / 资金流水 / 佣金明细 / 付费查看记录
+ * ├─ 交易管理 订单管理 / 会员套餐 / 优惠券管理 / 资金流水 / 佣金明细 / 付费查看记录
* ├─ 分销管理 分销关系 / 提现审核
* └─ 基础数据 服务区域 / 草稿箱
+ * v1.7.0 追加(幂等):数据总览 fc/dashboard(置首)、系统设置 fc/setting(置末),
+ * 以及上述「交易管理」下的优惠券管理 fc/coupon 与它们的节点权限。
*/
protected function menu(Output $output)
{
@@ -351,7 +443,86 @@ class FcInstall extends Command
]);
$done += $aff > 0 ? 1 : 1;
}
+
+ // 4) v1.7.0 新增功能项(按 name upsert:不存在则插入,存在则纠正归属/名称/图标/排序)
+ // ID 交给数据库自增分配(当前 fa_auth_rule 最大 ID 为 224),不写死 ID,故不会占用已用 ID。
+ // 顶级分组「同城业务」下:数据总览置首(weigh 99 > 内容管理 90)、系统设置置末(weigh 40 < 基础数据 50);
+ // 「交易管理」下:优惠券管理排在会员套餐之后、资金流水之前。
+ $groupId['fc'] = $topId;
+ $newItems = [
+ 'fc/dashboard' => ['fc', '数据总览', 'fa fa-dashboard', 99],
+ 'fc/setting' => ['fc', '系统设置', 'fa fa-cogs', 40],
+ 'fc/coupon' => ['fc/trade', '优惠券管理', 'fa fa-ticket', 88],
+ ];
+ $newIds = [];
+ $inserted = 0;
+ foreach ($newItems as $name => $m) {
+ if (!isset($groupId[$m[0]])) {
+ continue;
+ }
+ $row = $ruleTable->where('name', $name)->find();
+ $data = [
+ 'pid' => $groupId[$m[0]], 'title' => $m[1], 'icon' => $m[2], 'weigh' => $m[3],
+ 'ismenu' => 1, 'menutype' => 'addtabs', 'status' => 'normal', 'updatetime' => $now,
+ ];
+ if ($row) {
+ $ruleTable->where('id', $row['id'])->update($data);
+ $newIds[$name] = (int)$row['id'];
+ } else {
+ $data['type'] = 'menu';
+ $data['name'] = $name;
+ $data['url'] = '';
+ $data['createtime'] = $now;
+ $newIds[$name] = (int)$ruleTable->insertGetId($data);
+ $inserted++;
+ }
+ }
+
+ // 5) 新功能项的节点权限(Backend 按「控制器/方法」校验权限,缺节点则非超管会 403)
+ $nodes = [
+ 'fc/dashboard/index' => ['fc/dashboard', '查看'],
+ 'fc/setting/index' => ['fc/setting', '查看'],
+ 'fc/coupon/index' => ['fc/coupon', '查看'],
+ 'fc/coupon/add' => ['fc/coupon', '添加'],
+ 'fc/coupon/edit' => ['fc/coupon', '编辑'],
+ 'fc/coupon/del' => ['fc/coupon', '删除'],
+ 'fc/coupon/multi' => ['fc/coupon', '批量更新'],
+ ];
+ $nodeCount = 0;
+ foreach ($nodes as $name => $n) {
+ if (!isset($newIds[$n[0]])) {
+ continue;
+ }
+ $row = $ruleTable->where('name', $name)->find();
+ $data = [
+ 'pid' => $newIds[$n[0]], 'title' => $n[1], 'icon' => 'fa fa-circle-o',
+ 'ismenu' => 0, 'weigh' => 0, 'status' => 'normal', 'updatetime' => $now,
+ ];
+ if ($row) {
+ $ruleTable->where('id', $row['id'])->update($data);
+ } else {
+ $data['type'] = 'file';
+ $data['name'] = $name;
+ $data['url'] = '';
+ $data['menutype'] = 'addtabs';
+ $data['createtime'] = $now;
+ $ruleTable->insert($data);
+ }
+ $nodeCount++;
+ }
+
$output->writeln(' - 顶级 1 / 分组 ' . count($groups) . ' / 功能项 ' . $done);
+ $output->writeln(' - 新功能项 ' . count($newItems) . ' 个(本次新插入 ' . $inserted . ')+ 权限节点 ' . $nodeCount . ' 个'
+ . ':数据总览 fc/dashboard、系统设置 fc/setting、优惠券管理 fc/coupon');
+
+ // 左侧菜单来自 Auth::getSidebar() 的 `__menu__` 缓存:不清这里,本命令刚插入的
+ // 「数据总览 / 系统设置 / 优惠券管理」在缓存过期前不会出现在后台侧边栏。
+ try {
+ \think\Cache::rm('__menu__');
+ $output->writeln(' - 已清理后台菜单缓存 __menu__(新菜单项即时可见)');
+ } catch (\Exception $e) {
+ // 清理失败不影响主流程:缓存过期后会自动重建
+ }
}
/** 清空业务表(保留表结构) */
@@ -658,6 +829,57 @@ class FcInstall extends Command
PRIMARY KEY (`id`),
UNIQUE KEY `user_post` (`user_id`,`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-付费查看记录';",
+
+ 'favorite' => "CREATE TABLE IF NOT EXISTS `#P#favorite` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '收藏人',
+ `post_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '信息ID',
+ `createtime` int(10) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_post` (`user_id`,`post_id`),
+ KEY `post_id` (`post_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-信息收藏';",
+
+ 'follow' => "CREATE TABLE IF NOT EXISTS `#P#follow` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '关注发起人',
+ `target_user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '被关注人',
+ `createtime` int(10) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_target` (`user_id`,`target_user_id`),
+ KEY `target_user_id` (`target_user_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户关注';",
+
+ 'coupon' => "CREATE TABLE IF NOT EXISTS `#P#coupon` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(64) NOT NULL DEFAULT '' COMMENT '优惠券名称',
+ `type` varchar(16) NOT NULL DEFAULT 'fixed' COMMENT 'fixed 满减 / discount 折扣',
+ `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '面额(元);discount 时为折扣数如 8.50',
+ `min_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '使用门槛(元),0 为无门槛',
+ `total` int(10) NOT NULL DEFAULT '0' COMMENT '发行总量,0 为不限量',
+ `received` int(10) NOT NULL DEFAULT '0' COMMENT '已领取数量',
+ `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用:1 启用 0 停用',
+ `sort` int(10) NOT NULL DEFAULT '0' COMMENT '排序,越大越靠前',
+ `days` int(10) NOT NULL DEFAULT '0' COMMENT '领取后有效天数,0 为永久',
+ `createtime` int(10) DEFAULT NULL,
+ `updatetime` int(10) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `enabled` (`enabled`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-优惠券';",
+
+ 'user_coupon' => "CREATE TABLE IF NOT EXISTS `#P#user_coupon` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '领取人',
+ `coupon_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '优惠券ID',
+ `status` varchar(16) NOT NULL DEFAULT 'unused' COMMENT 'unused 未使用 / used 已使用 / expired 已过期',
+ `order_no` varchar(32) NOT NULL DEFAULT '' COMMENT '使用时的订单号',
+ `usetime` int(10) DEFAULT NULL COMMENT '使用时间',
+ `expiretime` int(10) DEFAULT NULL COMMENT '过期时间(领取时间 + days)',
+ `createtime` int(10) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `user_id` (`user_id`),
+ KEY `coupon_id` (`coupon_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户优惠券';",
];
}
diff --git a/application/admin/controller/fc/Coupon.php b/application/admin/controller/fc/Coupon.php
new file mode 100644
index 0000000..373c13a
--- /dev/null
+++ b/application/admin/controller/fc/Coupon.php
@@ -0,0 +1,231 @@
+request->filter(['strip_tags', 'trim']);
+ if (false === $this->request->isAjax()) {
+ return $this->view->fetch();
+ }
+
+ list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+ $list = Db::name('fc_coupon')
+ ->where($where)
+ ->order($sort, $order)
+ ->paginate($limit);
+
+ return json(['total' => $list->total(), 'rows' => $list->items()]);
+ }
+
+ /**
+ * 添加
+ */
+ public function add()
+ {
+ // 与 index() 一致:统一过滤 XSS 载荷(列表用 bootstrap-table 原样渲染,未过滤会造成存储型 XSS)
+ $this->request->filter(['strip_tags', 'trim']);
+ if (false === $this->request->isPost()) {
+ return $this->view->fetch();
+ }
+ $params = $this->request->post('row/a');
+ if (empty($params)) {
+ $this->error(__('Parameter %s can not be empty', ''));
+ }
+
+ $data = $this->buildData($params);
+ $now = time();
+ // 已领取数量只读,新增时固定为 0
+ $data['received'] = 0;
+ $data['createtime'] = $now;
+ $data['updatetime'] = $now;
+
+ if (false === Db::name('fc_coupon')->insert($data)) {
+ $this->error('优惠券添加失败');
+ }
+
+ $this->success();
+ return null;
+ }
+
+ /**
+ * 编辑
+ *
+ * @param mixed $ids
+ */
+ public function edit($ids = null)
+ {
+ // 与 index() 一致:统一过滤 XSS 载荷
+ $this->request->filter(['strip_tags', 'trim']);
+ $row = Db::name('fc_coupon')->where('id', (int)$ids)->find();
+ if (!$row) {
+ $this->error(__('No Results were found'));
+ }
+ if (false === $this->request->isPost()) {
+ $this->view->assign('row', $row);
+ return $this->view->fetch();
+ }
+
+ $params = $this->request->post('row/a');
+ if (empty($params)) {
+ $this->error(__('Parameter %s can not be empty', ''));
+ }
+
+ $data = $this->buildData($params);
+ // 已领取数量只读,编辑时不修改
+ $data['updatetime'] = time();
+
+ if (false === Db::name('fc_coupon')->where('id', (int)$ids)->update($data)) {
+ $this->error(__('No rows were updated'));
+ }
+
+ $this->success();
+ return null;
+ }
+
+ /**
+ * 删除
+ *
+ * @param mixed $ids
+ */
+ public function del($ids = null)
+ {
+ if (false === $this->request->isPost()) {
+ $this->error(__('Invalid parameters'));
+ }
+ $ids = $ids ?: $this->request->post('ids');
+ if (empty($ids)) {
+ $this->error(__('Parameter %s can not be empty', 'ids'));
+ }
+
+ $count = Db::name('fc_coupon')->where('id', 'in', $ids)->delete();
+ if (!$count) {
+ $this->error(__('No rows were deleted'));
+ }
+
+ $this->success();
+ return null;
+ }
+
+ /**
+ * 批量更新(仅允许「是否启用 / 排序」)
+ *
+ * @param mixed $ids
+ */
+ public function multi($ids = null)
+ {
+ if (false === $this->request->isPost()) {
+ $this->error(__('Invalid parameters'));
+ }
+ $ids = $ids ?: $this->request->post('ids');
+ if (empty($ids)) {
+ $this->error(__('Parameter %s can not be empty', 'ids'));
+ }
+ if (false === $this->request->has('params')) {
+ $this->error(__('No rows were updated'));
+ }
+
+ parse_str($this->request->post('params'), $values);
+ $values = array_intersect_key($values, array_flip(['enabled', 'sort']));
+ if (empty($values)) {
+ $this->error(__('You have no permission'));
+ }
+ if (isset($values['enabled'])) {
+ $values['enabled'] = !empty($values['enabled']) ? 1 : 0;
+ }
+ if (isset($values['sort'])) {
+ $values['sort'] = (int)$values['sort'];
+ }
+ $values['updatetime'] = time();
+
+ $count = Db::name('fc_coupon')->where('id', 'in', $ids)->update($values);
+ if (!$count) {
+ $this->error(__('No rows were updated'));
+ }
+
+ $this->success();
+ return null;
+ }
+
+ /**
+ * 校验并整理表单数据
+ *
+ * @param array $params
+ * @return array
+ */
+ protected function buildData(array $params)
+ {
+ $name = isset($params['name']) ? trim((string)$params['name']) : '';
+ if ($name === '') {
+ $this->error('优惠券名称不能为空');
+ }
+ if (mb_strlen($name, 'utf-8') > 64) {
+ $this->error('优惠券名称不能超过 64 个字');
+ }
+
+ $type = isset($params['type']) ? trim((string)$params['type']) : 'fixed';
+ if (!in_array($type, self::TYPES, true)) {
+ $this->error('优惠券类型只支持「满减券」或「折扣券」');
+ }
+
+ $amount = isset($params['amount']) ? round((float)$params['amount'], 2) : 0;
+ $minAmount = isset($params['min_amount']) ? round((float)$params['min_amount'], 2) : 0;
+ $total = isset($params['total']) ? (int)$params['total'] : 0;
+ $days = isset($params['days']) ? (int)$params['days'] : 0;
+ $sort = isset($params['sort']) ? (int)$params['sort'] : 0;
+ $enabled = !empty($params['enabled']) ? 1 : 0;
+
+ if ($type === 'fixed') {
+ if ($amount <= 0) {
+ $this->error('满减券的面额必须大于 0');
+ }
+ } else {
+ if ($amount <= 0 || $amount >= 10) {
+ $this->error('折扣券的折扣数必须大于 0 且小于 10(如 8.5 表示 8.5 折)');
+ }
+ }
+ if ($minAmount < 0) {
+ $this->error('使用门槛不能为负(0 表示无门槛)');
+ }
+ if ($total < 0) {
+ $this->error('发放总量不能为负(0 表示不限量)');
+ }
+ if ($days < 0) {
+ $this->error('有效天数不能为负(0 表示永久有效)');
+ }
+
+ return [
+ 'name' => $name,
+ 'type' => $type,
+ 'amount' => $amount,
+ 'min_amount' => $minAmount,
+ 'total' => $total,
+ 'days' => $days,
+ 'enabled' => $enabled,
+ 'sort' => $sort,
+ ];
+ }
+}
diff --git a/application/admin/controller/fc/Dashboard.php b/application/admin/controller/fc/Dashboard.php
new file mode 100644
index 0000000..e27fb5a
--- /dev/null
+++ b/application/admin/controller/fc/Dashboard.php
@@ -0,0 +1,122 @@
+ (int)Db::name('fc_post')->where('createtime', 'between', [$todayStart, $todayEnd])->count(),
+ 'totalPost' => (int)Db::name('fc_post')->count(),
+ 'pendingPost' => (int)Db::name('fc_post')->where('audit_status', 0)->count(),
+ // 订单额(仅统计已支付订单;paytime 缺失时回落 createtime)
+ 'todayOrderAmount' => round((float)Db::name('fc_order')->where('status', 'paid')
+ ->whereTime('paytime|createtime', 'between', [$todayStart, $todayEnd])->sum('amount'), 2),
+ 'totalOrderAmount' => round((float)Db::name('fc_order')->where('status', 'paid')->sum('amount'), 2),
+ // 用户 / 商家
+ 'memberCount' => (int)Db::name('fc_user_ext')->where('is_member', 1)->count(),
+ 'merchantCount' => (int)Db::name('fc_merchant')->count(),
+ 'userCount' => (int)Db::name('user')->count(),
+ // 分销佣金
+ 'commissionPending' => round((float)Db::name('fc_commission')->where('status', 0)->sum('amount'), 2),
+ 'commissionSettled' => round((float)Db::name('fc_commission')->where('status', 1)->sum('amount'), 2),
+ // 提现待审核
+ 'withdrawAuditing' => (int)Db::name('fc_withdraw')->where('status', 'auditing')->count(),
+ ];
+
+ /* ---------- 近 14 天趋势 ---------- */
+ $dateStart = $todayStart - (self::TREND_DAYS - 1) * 86400;
+ $days = [];
+ for ($i = 0; $i < self::TREND_DAYS; $i++) {
+ $days[] = date('Y-m-d', $dateStart + $i * 86400);
+ }
+
+ // 每天发布量
+ $postsMap = [];
+ $posts = Db::name('fc_post')
+ ->where('createtime', 'between time', [$dateStart, $todayEnd])
+ ->fieldRaw("FROM_UNIXTIME(`createtime`,'%Y-%m-%d') AS day, COUNT(*) AS num")
+ ->group('day')
+ ->select();
+ foreach ($posts as $row) {
+ $postsMap[$row['day']] = (int)$row['num'];
+ }
+
+ // 每天订单额(已支付;paytime 为空时按 createtime 归属)
+ $ordersMap = [];
+ $orders = Db::name('fc_order')
+ ->where('status', 'paid')
+ ->whereTime('paytime|createtime', 'between', [$dateStart, $todayEnd])
+ ->fieldRaw("FROM_UNIXTIME(IFNULL(`paytime`,`createtime`),'%Y-%m-%d') AS day, SUM(`amount`) AS num")
+ ->group('day')
+ ->select();
+ foreach ($orders as $row) {
+ $ordersMap[$row['day']] = round((float)$row['num'], 2);
+ }
+
+ // 每天新增注册用户(fa_user.createtime)
+ $membersMap = [];
+ $members = Db::name('user')
+ ->where('createtime', 'between time', [$dateStart, $todayEnd])
+ ->fieldRaw("FROM_UNIXTIME(`createtime`,'%Y-%m-%d') AS day, COUNT(*) AS num")
+ ->group('day')
+ ->select();
+ foreach ($members as $row) {
+ $membersMap[$row['day']] = (int)$row['num'];
+ }
+
+ $trend = [
+ 'dates' => $days,
+ 'posts' => [],
+ 'orders' => [],
+ 'members' => [],
+ ];
+ $trendRows = [];
+ foreach ($days as $day) {
+ $postNum = isset($postsMap[$day]) ? $postsMap[$day] : 0;
+ $orderNum = isset($ordersMap[$day]) ? $ordersMap[$day] : 0;
+ $memberNum = isset($membersMap[$day]) ? $membersMap[$day] : 0;
+
+ $trend['posts'][] = $postNum;
+ $trend['orders'][] = $orderNum;
+ $trend['members'][] = $memberNum;
+
+ $trendRows[] = [
+ 'date' => $day,
+ 'posts' => $postNum,
+ 'orders' => $orderNum,
+ 'members' => $memberNum,
+ ];
+ }
+
+ $this->view->assign('metrics', $metrics);
+ $this->view->assign('trendRows', $trendRows);
+ $this->assignconfig('trend', $trend);
+
+ return $this->view->fetch();
+ }
+}
diff --git a/application/admin/controller/fc/Setting.php b/application/admin/controller/fc/Setting.php
new file mode 100644
index 0000000..05df6a1
--- /dev/null
+++ b/application/admin/controller/fc/Setting.php
@@ -0,0 +1,168 @@
+request->isPost()) {
+ return $this->save();
+ }
+
+ $this->view->assign('row', $this->current());
+ $this->view->assign('pay', $this->payStatus());
+ return $this->view->fetch();
+ }
+
+ /**
+ * 保存基础配置
+ *
+ * 说明:支付配置已迁到插件页,本页不再接收 group=pay 的提交(如提交则给出明确指引)。
+ */
+ protected function save()
+ {
+ $params = $this->request->post('row/a');
+ if (!is_array($params)) {
+ $params = $this->request->post();
+ }
+ $group = isset($params['group']) ? trim((string)$params['group']) : 'base';
+
+ if ($group === 'pay') {
+ $this->error('微信支付参数已改由插件维护,请到「插件管理 → 微信支付宝整合插件 → 配置」填写');
+ }
+
+ $appName = isset($params['appName']) ? trim((string)$params['appName']) : '';
+ if ($appName === '') {
+ $this->error('小程序名称不能为空');
+ }
+
+ $this->setConfig([
+ 'base.appName' => ['value' => $appName, 'group_key' => 'base', 'remark' => '小程序名称'],
+ 'base.logo' => ['value' => isset($params['logo']) ? trim((string)$params['logo']) : '', 'group_key' => 'base', 'remark' => 'LOGO地址'],
+ 'base.servicePhone' => ['value' => isset($params['servicePhone']) ? trim((string)$params['servicePhone']) : '', 'group_key' => 'base', 'remark' => '客服电话'],
+ 'base.aboutUs' => ['value' => isset($params['aboutUs']) ? trim((string)$params['aboutUs']) : '', 'group_key' => 'base', 'remark' => '关于我们'],
+ 'base.shareTitle' => ['value' => isset($params['shareTitle']) ? trim((string)$params['shareTitle']) : '', 'group_key' => 'base', 'remark' => '分享标题'],
+ ]);
+
+ $this->success('基础配置已保存');
+ return null;
+ }
+
+ /**
+ * 批量写入 fc_config(存在则更新,不存在则新增)
+ *
+ * @param array $values 结构:键名 => ['value' => 值, 'group_key' => 分组, 'remark' => 备注]
+ */
+ protected function setConfig(array $values)
+ {
+ $now = time();
+ foreach ($values as $key => $item) {
+ $exist = Db::name('fc_config')->where('key', $key)->find();
+ if ($exist) {
+ Db::name('fc_config')->where('key', $key)->update([
+ 'value' => (string)$item['value'],
+ 'group_key' => $item['group_key'],
+ 'updatetime' => $now,
+ ]);
+ } else {
+ Db::name('fc_config')->insert([
+ 'key' => $key,
+ 'value' => (string)$item['value'],
+ 'group_key' => $item['group_key'],
+ 'remark' => $item['remark'],
+ 'createtime' => $now,
+ 'updatetime' => $now,
+ ]);
+ }
+ }
+ }
+
+ /**
+ * 当前基础配置(缺失时回落内置默认值)
+ */
+ protected function current()
+ {
+ $cfg = [];
+ foreach (Db::name('fc_config')->select() as $row) {
+ $cfg[$row['key']] = $row['value'];
+ }
+ $pick = function ($key, $default) use ($cfg) {
+ return (isset($cfg[$key]) && $cfg[$key] !== '') ? $cfg[$key] : $default;
+ };
+
+ return [
+ 'appName' => $pick('base.appName', '同城分类信息'),
+ 'logo' => $pick('base.logo', ''),
+ 'servicePhone' => $pick('base.servicePhone', '400-000-0000'),
+ 'aboutUs' => $pick('base.aboutUs', ''),
+ 'shareTitle' => $pick('base.shareTitle', ''),
+ ];
+ }
+
+ /**
+ * 支付配置状态(只读,数据来自 epay 插件配置)
+ *
+ * @return array 插件是否安装 / 是否就绪 / 关键项是否填写 / 回调地址 / 配置页地址 / 配置文件名
+ */
+ protected function payStatus()
+ {
+ $info = (array)get_addon_info('epay');
+ $addonConfig = (array)get_addon_config('epay');
+ $wx = (isset($addonConfig['wechat']) && is_array($addonConfig['wechat'])) ? $addonConfig['wechat'] : [];
+ $mode = isset($wx['mode']) ? (string)$wx['mode'] : 'normal';
+
+ // 与用户端 Pay::epayReady() 同一判定口径
+ if ($mode === 'service') {
+ $ready = !empty($wx['sub_mch_id']) && !empty($wx['key']) && !empty($wx['sub_miniapp_id']);
+ } else {
+ $ready = !empty($wx['miniapp_id']) && !empty($wx['mch_id']) && !empty($wx['key']);
+ }
+
+ return [
+ 'installed' => !empty($info),
+ 'ready' => $ready,
+ 'version' => isset($addonConfig['version']) ? (string)$addonConfig['version'] : '',
+ 'mode' => $mode === 'service' ? 'service' : 'normal',
+ 'miniappId' => isset($wx['miniapp_id']) ? (string)$wx['miniapp_id'] : '',
+ 'mchId' => isset($wx['mch_id']) ? (string)$wx['mch_id'] : '',
+ 'hasKey' => !empty($wx['key']),
+ 'notifyUrl' => $this->notifyUrl(),
+ 'configUrl' => url('addon/config', ['name' => 'epay']),
+ 'configFile' => 'addons/epay/config.php',
+ ];
+ }
+
+ /**
+ * 微信支付回调地址(与服务端 Pay::notifyUrl() 同一算法:当前域名 + 固定路径)
+ */
+ protected function notifyUrl()
+ {
+ $base = rtrim((string)$this->request->root(true), '/');
+ // 去掉入口文件(后台入口 s.php / 前台 index.php),展示与真实回调地址保持一致
+ $base = preg_replace('#/[a-zA-Z0-9_\-]+\.php$#i', '', $base);
+
+ return ($base !== '' ? $base : rtrim((string)$this->request->domain(), '/')) . '/api/v1/pay/notify';
+ }
+}
diff --git a/application/admin/lang/zh-cn/fc/coupon.php b/application/admin/lang/zh-cn/fc/coupon.php
new file mode 100644
index 0000000..30f73c9
--- /dev/null
+++ b/application/admin/lang/zh-cn/fc/coupon.php
@@ -0,0 +1,27 @@
+ 'ID',
+ '优惠券名称' => '优惠券名称',
+ '如:新人满 50 减 10' => '如:新人满 50 减 10',
+ '优惠券类型' => '优惠券类型',
+ '满减券(直接减面额)' => '满减券(直接减面额)',
+ '折扣券(按折扣数打折)' => '折扣券(按折扣数打折)',
+ '面额/折扣' => '面额/折扣',
+ '满减券填面额(元),折扣券填折扣数(如 8.5)' => '满减券填面额(元),折扣券填折扣数(如 8.5)',
+ '满减券:面额(元),必须大于 0;折扣券:折扣数(如 8.5 表示 8.5 折),必须大于 0 且小于 10。'
+ => '满减券:面额(元),必须大于 0;折扣券:折扣数(如 8.5 表示 8.5 折),必须大于 0 且小于 10。',
+ '使用门槛' => '使用门槛',
+ '单位:元。0 表示无门槛。' => '单位:元。0 表示无门槛。',
+ '发放总量' => '发放总量',
+ '0 表示不限量;发放到上限后用户端不再可领。' => '0 表示不限量;发放到上限后用户端不再可领。',
+ '已领取' => '已领取',
+ '已领取数量由用户领取自动累计,此处只读。' => '已领取数量由用户领取自动累计,此处只读。',
+ '有效天数' => '有效天数',
+ '用户领取后开始计算的有效天数,0 表示永久有效。' => '用户领取后开始计算的有效天数,0 表示永久有效。',
+ '是否启用' => '是否启用',
+ '排序' => '排序',
+ '创建时间' => '创建时间',
+ '更新时间' => '更新时间',
+ '操作' => '操作',
+];
diff --git a/application/admin/lang/zh-cn/fc/dashboard.php b/application/admin/lang/zh-cn/fc/dashboard.php
new file mode 100644
index 0000000..37f2984
--- /dev/null
+++ b/application/admin/lang/zh-cn/fc/dashboard.php
@@ -0,0 +1,26 @@
+ '核心看板',
+ '全部指标由后台直接统计;订单额仅统计「已支付」订单,佣金不含已驳回部分,提现为「待审核」笔数。'
+ => '全部指标由后台直接统计;订单额仅统计「已支付」订单,佣金不含已驳回部分,提现为「待审核」笔数。',
+ '今日发布量' => '今日发布量',
+ '累计发布量' => '累计发布量',
+ '待审核信息数' => '待审核信息数',
+ '今日订单额' => '今日订单额',
+ '累计订单额' => '累计订单额',
+ '会员数' => '会员数',
+ '商家数' => '商家数',
+ '注册用户数' => '注册用户数',
+ '待结算佣金' => '待结算佣金',
+ '已结算佣金' => '已结算佣金',
+ '待审核提现数' => '待审核提现数',
+ '近 14 天趋势' => '近 14 天趋势',
+ '(发布量按发布时间统计;订单额按支付时间统计;新增会员为新增注册用户)'
+ => '(发布量按发布时间统计;订单额按支付时间统计;新增会员为新增注册用户)',
+ '日期' => '日期',
+ '发布量' => '发布量',
+ '订单额' => '订单额',
+ '订单额(元)' => '订单额(元)',
+ '新增注册用户' => '新增注册用户',
+];
diff --git a/application/admin/lang/zh-cn/fc/setting.php b/application/admin/lang/zh-cn/fc/setting.php
new file mode 100644
index 0000000..fdc5720
--- /dev/null
+++ b/application/admin/lang/zh-cn/fc/setting.php
@@ -0,0 +1,67 @@
+ '说明',
+ '本页维护基础配置,保存后写入 fc_config,后台与用户端接口读到的是同一份配置。'
+ => '本页维护基础配置,保存后写入 fc_config,后台与用户端接口读到的是同一份配置。',
+ '基础配置对应 base.* 键,与「小程序端」显示的小程序名称/LOGO/客服电话/关于我们/分享标题共用。'
+ => '基础配置对应 base.* 键,与「小程序端」显示的小程序名称/LOGO/客服电话/关于我们/分享标题共用。',
+ '支付配置已改由 epay 插件维护(见下方只读状态),本页不再重复填写商户号与密钥。'
+ => '支付配置已改由 epay 插件维护(见下方只读状态),本页不再重复填写商户号与密钥。',
+
+ '基础配置' => '基础配置',
+ '小程序名称' => '小程序名称',
+ '显示在小程序首页与分享卡片上' => '显示在小程序首页与分享卡片上',
+ '必填。建议不超过 20 个字。' => '必填。建议不超过 20 个字。',
+ 'LOGO' => '小程序LOGO',
+ '可留空,留空时使用内置图标' => '可留空,留空时使用内置图标',
+ '上传' => '上传',
+ '选择' => '选择',
+ '客服电话' => '客服电话',
+ '用于「联系客服」入口,可填手机号或 400 电话。' => '用于「联系客服」入口,可填手机号或 400 电话。',
+ '关于我们' => '关于我们',
+ '展示在「关于我们」页面' => '展示在「关于我们」页面',
+ '分享标题' => '分享标题',
+ '转发小程序时显示的标题' => '转发小程序时显示的标题',
+ '保存基础配置' => '保存基础配置',
+
+ '支付配置' => '支付配置',
+ '(由 epay 插件统一维护,本页只读展示)' => '(由 epay 插件统一维护,本页只读展示)',
+ '微信支付参数请在「插件管理 → 微信支付宝整合插件 → 配置」中填写,保存后立即对用户端支付生效。'
+ => '微信支付参数请在「插件管理 → 微信支付宝整合插件 → 配置」中填写,保存后立即对用户端支付生效。',
+ '小程序支付的 AppID 必须填在插件的 miniapp_id(小程序 AppID)字段,填公众号的 appid 不生效。'
+ => '小程序支付的 AppID 必须填在插件的 miniapp_id(小程序 AppID)字段,填公众号的 appid 不生效。',
+ '前往插件配置' => '前往插件配置',
+ '插件与就绪状态' => '插件与就绪状态',
+ '插件已安装' => '插件已安装',
+ '插件未安装' => '插件未安装',
+ '支付参数已就绪' => '支付参数已就绪',
+ '支付参数尚未配置完整' => '支付参数尚未配置完整',
+ '接口版本 / 模式' => '接口版本 / 模式',
+ '版本' => '版本',
+ '模式' => '模式',
+ '服务商模式' => '服务商模式',
+ '普通商户模式' => '普通商户模式',
+ '小程序 AppID(miniapp_id)' => '小程序 AppID(miniapp_id)',
+ '微信支付商户号(mch_id)' => '微信支付商户号(mch_id)',
+ 'API 密钥(key)' => 'API 密钥(key)',
+ '未填写' => '未填写',
+ '已配置' => '已配置',
+ '未配置' => '未配置',
+ '支付回调地址' => '支付回调地址',
+ '下单时自动随请求下发给微信,无需在商户平台另行配置。'
+ => '下单时自动随请求下发给微信,无需在商户平台另行配置。',
+ '插件配置文件' => '插件配置文件',
+ '该文件已纳入版本库(空白模板);在服务器上填写后请勿把真实密钥提交回仓库。'
+ => '该文件已纳入版本库(空白模板);在服务器上填写后请勿把真实密钥提交回仓库。',
+
+ /* 分组=pay 的提交在本页已废弃(支付参数由插件维护),保留提示文案供控制器复用 */
+ '微信支付参数已改由插件维护,请到「插件管理 → 微信支付宝整合插件 → 配置」填写'
+ => '微信支付参数已改由插件维护,请到「插件管理 → 微信支付宝整合插件 → 配置」填写',
+];
diff --git a/application/admin/view/fc/coupon/add.html b/application/admin/view/fc/coupon/add.html
new file mode 100644
index 0000000..27839d1
--- /dev/null
+++ b/application/admin/view/fc/coupon/add.html
@@ -0,0 +1,67 @@
+
diff --git a/application/admin/view/fc/coupon/edit.html b/application/admin/view/fc/coupon/edit.html
new file mode 100644
index 0000000..4a4d3af
--- /dev/null
+++ b/application/admin/view/fc/coupon/edit.html
@@ -0,0 +1,74 @@
+
diff --git a/application/admin/view/fc/coupon/index.html b/application/admin/view/fc/coupon/index.html
new file mode 100644
index 0000000..76a3dd0
--- /dev/null
+++ b/application/admin/view/fc/coupon/index.html
@@ -0,0 +1,24 @@
+
+ {:build_heading()}
+
+
+
diff --git a/application/admin/view/fc/dashboard/index.html b/application/admin/view/fc/dashboard/index.html
new file mode 100644
index 0000000..2b99db1
--- /dev/null
+++ b/application/admin/view/fc/dashboard/index.html
@@ -0,0 +1,158 @@
+
+ {:build_heading()}
+
+
+
+ {:__('核心看板')}:
+ {:__('全部指标由后台直接统计;订单额仅统计「已支付」订单,佣金不含已驳回部分,提现为「待审核」笔数。')}
+
+
+
+
+
+
+
{$metrics.todayPost}
+
{:__('今日发布量')}
+
+
+
+
+
+
+
+
{$metrics.totalPost}
+
{:__('累计发布量')}
+
+
+
+
+
+
+
+
{$metrics.pendingPost}
+
{:__('待审核信息数')}
+
+
+
+
+
+
+
+
¥{$metrics.todayOrderAmount}
+
{:__('今日订单额')}
+
+
+
+
+
+
+
+
+
+
+
¥{$metrics.totalOrderAmount}
+
{:__('累计订单额')}
+
+
+
+
+
+
+
+
{$metrics.memberCount}
+
{:__('会员数')}
+
+
+
+
+
+
+
+
{$metrics.merchantCount}
+
{:__('商家数')}
+
+
+
+
+
+
+
+
{$metrics.userCount}
+
{:__('注册用户数')}
+
+
+
+
+
+
+
+
+
+
+
¥{$metrics.commissionPending}
+
{:__('待结算佣金')}
+
+
+
+
+
+
+
+
¥{$metrics.commissionSettled}
+
{:__('已结算佣金')}
+
+
+
+
+
+
+
+
{$metrics.withdrawAuditing}
+
{:__('待审核提现数')}
+
+
+
+
+
+
+
+ {:__('近 14 天趋势')}
+ {:__('(发布量按发布时间统计;订单额按支付时间统计;新增会员为新增注册用户)')}
+
+
+
+
+
+
+
+
+ {:__('日期')}
+ {:__('发布量')}
+ {:__('订单额')}
+ {:__('新增注册用户')}
+
+
+
+ {foreach $trendRows as $item}
+
+ {$item.date}
+ {$item.posts}
+ ¥{$item.orders}
+ {$item.members}
+
+ {/foreach}
+
+
+
+
+
diff --git a/application/admin/view/fc/merchant/add.html b/application/admin/view/fc/merchant/add.html
index c3e1f12..1c29d71 100644
--- a/application/admin/view/fc/merchant/add.html
+++ b/application/admin/view/fc/merchant/add.html
@@ -66,6 +66,36 @@
+
+
+
+
+
+