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 @@ +
+ +
+ + +
+
+ {foreach $addon.config as $item} + {if $item.name=='version'} + + + {elseif $item.name=='wechat'/} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
APP的app_id +
+
+ +
+
+
+
公众号的app_id +
+
+ +
+
+
+
公众号的app_secret +
+
+ +
+
+
+
小程序的app_id +
+
+ +
+
+
+
微信支付商户号 +
+
+ +
+
+
+
商户APIv2密钥 +
+
+ +
+
+
+
V3配置 + + + + + + + + + + + + + + + + +
商户APIv3密钥 +
+
+ +
+
+
+
公钥ID +
+
+ +
+
+
+
公钥证书路径 +
+
+
+ +
+ +
+ +
+ +
+
+
+
+
商户API证书cert +
+
+
+ +
+ +
+ +
+ +
+
+
+
商户API证书key +
+
+
+ +
+ +
+ +
+ +
+
+
+
支付模式 +
+
+ {:Form::radios('row[wechat][mode]',['normal'=>'正式环境','dev'=>'沙箱环境','service'=>'服务商模式'],$item.value.mode??'normal')} +
+ 沙箱环境:微信支付验收指引 +
+
+
+
+
子商户商户号ID +
+
+ +
+
+
+
子商户APP的app_id +
+
+ +
+
+
+
子商户公众号的app_id +
+
+ +
+
+
+
子商户小程序的app_id +
+
+ +
+
+
+
回调通知地址 +
+
+ +
+
+
+
记录日志 +
+
+ {:Form::radios('row[wechat][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)} +
+
+
+
+
+ {elseif $item.name=='alipay'} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
支付模式 +
+
+ {:Form::radios('row[alipay][mode]',['normal'=>'正式环境','dev'=>'沙箱环境', 'service'=>'服务商模式'],$item.value.mode??'normal')} + +
+ 如果使用沙箱环境,务必使用沙箱的app_id和沙箱配置,以及使用沙箱账号进行测试。
+ 沙箱环境:https://openhome.alipay.com/develop/sandbox/app +
+
+
+
服务商ID(pid) +
+
+ +
+
+
+
应用ID(app_id) +
+
+ +
+
+
+
回调通知地址 +
+
+ +
+
+
+
支付跳转地址 +
+
+ +
+
+
+
签名方式 +
+
+ + +
+
+
+ 如果要使用转账、提现功能,则必须使用公钥证书 +
+ + +
+ 支付宝公钥 + 支付宝公钥证书路径 + (alipay_public_key) + +
+
+
+ +
+ +
+ +
+
+
+
+
+ 应用公钥 + 应用公钥证书路径 + (app_cert_public_key) + +
+
+
+ +
+ +
+ +
+
+
+
+
支付宝根证书路径(alipay_root_cert) +
+
+
+ +
+ +
+ +
+
+
+
+
应用私钥(private_key) + +
记录日志 +
+
+ {:Form::radios('row[alipay][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)} +
+
+
+
PC端使用扫码支付 +
+
+ {:Form::radios('row[alipay][scanpay]',['1'=>'开启','0'=>'关闭'],$item.value.scanpay??0)} +
+
+
+
+
+ {/if} + {/foreach} + +
+
+
+
+ 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 + +![Swoole Engine Test](https://github.com/hyperf/engine/workflows/Swoole%20Engine%20Test/badge.svg) + +``` +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}>"); + } 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 = "
"; + foreach ($payload as $key => $val) { + $val = str_replace("'", ''', $val); + $sHtml .= ""; + } + $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.'>'; + } + $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 = "
"; + foreach ($payload as $key => $val) { + $val = str_replace("'", ''', $val); + $sHtml .= ""; + } + $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.'>'; + } + + $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 = "
"; + foreach ($payload->all() as $key => $val) { + $val = str_replace("'", ''', $val); + $sHtml .= ""; + } + $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 = "
"; + foreach ($payload->all() as $key => $val) { + $sHtml .= ""; + } + $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 = "
"; + foreach ($payload->all() as $key => $val) { + $val = str_replace("'", ''', $val); + $sHtml .= ""; + } + $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 = "
"; + foreach ($payload->all() as $key => $val) { + $sHtml .= ""; + } + $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.'>'; + } + + $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 @@ +
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + {:__('满减券:面额(元),必须大于 0;折扣券:折扣数(如 8.5 表示 8.5 折),必须大于 0 且小于 10。')} +
+
+
+ +
+ + {:__('单位:元。0 表示无门槛。')} +
+
+
+ +
+ + {:__('0 表示不限量;发放到上限后用户端不再可领。')} +
+
+
+ +
+ + {:__('用户领取后开始计算的有效天数,0 表示永久有效。')} +
+
+
+ +
+ + + + +
+
+
+ +
+ +
+
+ +
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 @@ +
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + {:__('满减券:面额(元),必须大于 0;折扣券:折扣数(如 8.5 表示 8.5 折),必须大于 0 且小于 10。')} +
+
+
+ +
+ + {:__('单位:元。0 表示无门槛。')} +
+
+
+ +
+ + {:__('0 表示不限量;发放到上限后用户端不再可领。')} +
+
+
+ +
+ + {:__('已领取数量由用户领取自动累计,此处只读。')} +
+
+
+ +
+ + {:__('用户领取后开始计算的有效天数,0 表示永久有效。')} +
+
+
+ +
+ + + + +
+
+
+ +
+ +
+
+ +
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} + + + + + + + {/foreach} + +
{:__('日期')}{:__('发布量')}{:__('订单额')}{:__('新增注册用户')}
{$item.date}{$item.posts}¥{$item.orders}{$item.members}
+
+
+
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 @@ +
+ +
+
+ +
+ + +
+ +
+
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + + + {:__('开启后该商家会出现在「推荐商家」位置。')} +
    +
    +
    + +
    +
    + +
    + + +
    + +
    +
      +
      +
      +
      + +
      + +
      +
      +
      + +
      + + + + + {:__('开启后该商家会出现在「推荐商家」位置。')} +
      +