Browse Source

epay插件

lizhen_gitee 11 months ago
parent
commit
e0d7985b2c
100 changed files with 4766 additions and 0 deletions
  1. 1 0
      addons/epay/.addonrc
  2. 100 0
      addons/epay/Epay.php
  3. 0 0
      addons/epay/certs/alipayCertPublicKey.crt
  4. 0 0
      addons/epay/certs/alipayRootCert.crt
  5. 0 0
      addons/epay/certs/apiclient_cert.pem
  6. 0 0
      addons/epay/certs/apiclient_key.pem
  7. 0 0
      addons/epay/certs/appCertPublicKey.crt
  8. 441 0
      addons/epay/config.html
  9. 70 0
      addons/epay/config.php
  10. 234 0
      addons/epay/controller/Api.php
  11. 131 0
      addons/epay/controller/Index.php
  12. 10 0
      addons/epay/info.ini
  13. 18 0
      addons/epay/library/Collection.php
  14. 16 0
      addons/epay/library/OrderException.php
  15. 58 0
      addons/epay/library/RedirectResponse.php
  16. 26 0
      addons/epay/library/Response.php
  17. 466 0
      addons/epay/library/Service.php
  18. 110 0
      addons/epay/library/Wechat.php
  19. 2 0
      addons/epay/library/hyperf/context/.gitattributes
  20. 21 0
      addons/epay/library/hyperf/context/LICENSE
  21. 40 0
      addons/epay/library/hyperf/context/composer.json
  22. 112 0
      addons/epay/library/hyperf/context/src/Context.php
  23. 2 0
      addons/epay/library/hyperf/contract/.gitattributes
  24. 21 0
      addons/epay/library/hyperf/contract/LICENSE
  25. 33 0
      addons/epay/library/hyperf/contract/composer.json
  26. 16 0
      addons/epay/library/hyperf/contract/src/ApplicationInterface.php
  27. 22 0
      addons/epay/library/hyperf/contract/src/Castable.php
  28. 33 0
      addons/epay/library/hyperf/contract/src/CastsAttributes.php
  29. 24 0
      addons/epay/library/hyperf/contract/src/CastsInboundAttributes.php
  30. 17 0
      addons/epay/library/hyperf/contract/src/CompressInterface.php
  31. 41 0
      addons/epay/library/hyperf/contract/src/ConfigInterface.php
  32. 40 0
      addons/epay/library/hyperf/contract/src/ConnectionInterface.php
  33. 53 0
      addons/epay/library/hyperf/contract/src/ContainerInterface.php
  34. 17 0
      addons/epay/library/hyperf/contract/src/DispatcherInterface.php
  35. 25 0
      addons/epay/library/hyperf/contract/src/FrequencyInterface.php
  36. 17 0
      addons/epay/library/hyperf/contract/src/IdGeneratorInterface.php
  37. 30 0
      addons/epay/library/hyperf/contract/src/LengthAwarePaginatorInterface.php
  38. 17 0
      addons/epay/library/hyperf/contract/src/MiddlewareInitializerInterface.php
  39. 32 0
      addons/epay/library/hyperf/contract/src/NormalizerInterface.php
  40. 23 0
      addons/epay/library/hyperf/contract/src/OnCloseInterface.php
  41. 20 0
      addons/epay/library/hyperf/contract/src/OnHandShakeInterface.php
  42. 24 0
      addons/epay/library/hyperf/contract/src/OnMessageInterface.php
  43. 24 0
      addons/epay/library/hyperf/contract/src/OnOpenInterface.php
  44. 24 0
      addons/epay/library/hyperf/contract/src/OnPacketInterface.php
  45. 23 0
      addons/epay/library/hyperf/contract/src/OnReceiveInterface.php
  46. 21 0
      addons/epay/library/hyperf/contract/src/OnRequestInterface.php
  47. 19 0
      addons/epay/library/hyperf/contract/src/PackerInterface.php
  48. 95 0
      addons/epay/library/hyperf/contract/src/PaginatorInterface.php
  49. 30 0
      addons/epay/library/hyperf/contract/src/PoolInterface.php
  50. 27 0
      addons/epay/library/hyperf/contract/src/PoolOptionInterface.php
  51. 36 0
      addons/epay/library/hyperf/contract/src/ProcessInterface.php
  52. 22 0
      addons/epay/library/hyperf/contract/src/ResponseEmitterInterface.php
  53. 158 0
      addons/epay/library/hyperf/contract/src/SessionInterface.php
  54. 18 0
      addons/epay/library/hyperf/contract/src/StdoutLoggerInterface.php
  55. 20 0
      addons/epay/library/hyperf/contract/src/Synchronized.php
  56. 37 0
      addons/epay/library/hyperf/contract/src/TranslatorInterface.php
  57. 35 0
      addons/epay/library/hyperf/contract/src/TranslatorLoaderInterface.php
  58. 17 0
      addons/epay/library/hyperf/contract/src/UnCompressInterface.php
  59. 60 0
      addons/epay/library/hyperf/contract/src/ValidatorInterface.php
  60. 4 0
      addons/epay/library/hyperf/engine/.gitattributes
  61. 4 0
      addons/epay/library/hyperf/engine/.gitignore
  62. 89 0
      addons/epay/library/hyperf/engine/.php-cs-fixer.php
  63. 6 0
      addons/epay/library/hyperf/engine/.phpstorm.meta.php
  64. 46 0
      addons/epay/library/hyperf/engine/Dockerfile
  65. 7 0
      addons/epay/library/hyperf/engine/README.md
  66. 49 0
      addons/epay/library/hyperf/engine/composer.json
  67. 15 0
      addons/epay/library/hyperf/engine/phpunit.xml
  68. 142 0
      addons/epay/library/hyperf/engine/src/Channel.php
  69. 25 0
      addons/epay/library/hyperf/engine/src/Constant.php
  70. 134 0
      addons/epay/library/hyperf/engine/src/Contract/ChannelInterface.php
  71. 70 0
      addons/epay/library/hyperf/engine/src/Contract/CoroutineInterface.php
  72. 24 0
      addons/epay/library/hyperf/engine/src/Contract/Http/ClientInterface.php
  73. 23 0
      addons/epay/library/hyperf/engine/src/Contract/WebSocket/WebSocketInterface.php
  74. 100 0
      addons/epay/library/hyperf/engine/src/Coroutine.php
  75. 16 0
      addons/epay/library/hyperf/engine/src/Exception/CoroutineDestroyedException.php
  76. 16 0
      addons/epay/library/hyperf/engine/src/Exception/HttpClientException.php
  77. 16 0
      addons/epay/library/hyperf/engine/src/Exception/RunningInNonCoroutineException.php
  78. 16 0
      addons/epay/library/hyperf/engine/src/Exception/RuntimeException.php
  79. 20 0
      addons/epay/library/hyperf/engine/src/Extension.php
  80. 76 0
      addons/epay/library/hyperf/engine/src/Http/Client.php
  81. 22 0
      addons/epay/library/hyperf/engine/src/Http/FdGetter.php
  82. 47 0
      addons/epay/library/hyperf/engine/src/Http/RawResponse.php
  83. 16 0
      addons/epay/library/hyperf/engine/src/Socket.php
  84. 16 0
      addons/epay/library/hyperf/engine/src/WaitGroup.php
  85. 19 0
      addons/epay/library/hyperf/engine/src/WebSocket/Frame.php
  86. 27 0
      addons/epay/library/hyperf/engine/src/WebSocket/Opcode.php
  87. 59 0
      addons/epay/library/hyperf/engine/src/WebSocket/WebSocket.php
  88. 2 0
      addons/epay/library/hyperf/macroable/.gitattributes
  89. 22 0
      addons/epay/library/hyperf/macroable/LICENSE
  90. 39 0
      addons/epay/library/hyperf/macroable/composer.json
  91. 129 0
      addons/epay/library/hyperf/macroable/src/Macroable.php
  92. 2 0
      addons/epay/library/hyperf/pimple/.gitattributes
  93. 4 0
      addons/epay/library/hyperf/pimple/.gitignore
  94. 92 0
      addons/epay/library/hyperf/pimple/.php-cs-fixer.php
  95. 6 0
      addons/epay/library/hyperf/pimple/.phpstorm.meta.php
  96. 42 0
      addons/epay/library/hyperf/pimple/.travis.yml
  97. 195 0
      addons/epay/library/hyperf/pimple/README.md
  98. 53 0
      addons/epay/library/hyperf/pimple/composer.json
  99. 15 0
      addons/epay/library/hyperf/pimple/phpunit.xml
  100. 32 0
      addons/epay/library/hyperf/pimple/src/ConfigProvider.php

+ 1 - 0
addons/epay/.addonrc

@@ -0,0 +1 @@
+{"files":["application\\admin\\controller\\Epay.php","public\\assets\\addons\\epay\\css\\common.css","public\\assets\\addons\\epay\\images\\alipay.png","public\\assets\\addons\\epay\\images\\expired.png","public\\assets\\addons\\epay\\images\\logo-alipay.png","public\\assets\\addons\\epay\\images\\logo-wechat.png","public\\assets\\addons\\epay\\images\\paid.png","public\\assets\\addons\\epay\\images\\scan.png","public\\assets\\addons\\epay\\images\\screenshot-alipay.png","public\\assets\\addons\\epay\\images\\screenshot-wechat.png","public\\assets\\addons\\epay\\images\\wechat.png","public\\assets\\addons\\epay\\js\\common.js","public\\assets\\addons\\epay\\js\\jquery.qrcode.min.js","public\\assets\\addons\\epay\\less\\common.less"],"license":"regular","licenseto":"19079","licensekey":"Y9GgfWFzlqo5JHdu CPNx8ogKr2442e50aeseGw==","domains":["fastadmin2024.com"],"licensecodes":[],"validations":["05ce8531634d889b2059dda76b3aa45a"]}

+ 100 - 0
addons/epay/Epay.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace addons\epay;
+
+use addons\epay\library\Service;
+use think\Addons;
+use think\Config;
+use think\Loader;
+
+/**
+ * 微信支付宝整合插件
+ */
+class Epay extends Addons
+{
+
+    /**
+     * 插件安装方法
+     * @return bool
+     */
+    public function install()
+    {
+        return true;
+    }
+
+    /**
+     * 插件卸载方法
+     * @return bool
+     */
+    public function uninstall()
+    {
+        return true;
+    }
+
+    /**
+     * 插件启用方法
+     * @return bool
+     */
+    public function enable()
+    {
+        return true;
+    }
+
+    /**
+     * 插件禁用方法
+     * @return bool
+     */
+    public function disable()
+    {
+        return true;
+    }
+
+    // 支持自定义加载
+    public function epayConfigInit()
+    {
+        $this->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;
+            Loader::addNamespace('Yansongda\Pay', $libraryDir . $version . DS . 'Yansongda' . DS . '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', $libraryDir . $version . DS . 'Yansongda' . DS . 'Supports' . DS);
+            }
+
+            // V3需载入辅助函数
+            if ($version == Service::SDK_VERSION_V3) {
+                require_once $libraryDir . $version . DS . 'Yansongda' . DS . 'Pay' . DS . 'Functions.php';
+            }
+        }
+    }
+}

+ 0 - 0
addons/epay/certs/alipayCertPublicKey.crt


+ 0 - 0
addons/epay/certs/alipayRootCert.crt


+ 0 - 0
addons/epay/certs/apiclient_cert.pem


+ 0 - 0
addons/epay/certs/apiclient_key.pem


+ 0 - 0
addons/epay/certs/appCertPublicKey.crt


+ 441 - 0
addons/epay/config.html

@@ -0,0 +1,441 @@
+<form id="config-form" class="edit-form form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="panel panel-default panel-intro">
+        <div class="panel-heading">
+            <ul class="nav nav-tabs nav-group">
+                <li class="active"><a href="#wechat" data-toggle="tab">微信支付</a></li>
+                <li><a href="#alipay" data-toggle="tab">支付宝</a></li>
+            </ul>
+        </div>
+
+        <div class="panel-body">
+            <div id="myTabContent" class="tab-content">
+                {foreach $addon.config as $item}
+                {if $item.name=='version'}
+                <input type="hidden" value="{$item.value}" name="row[version]"/>
+
+                {elseif $item.name=='wechat'/}
+                <div class="tab-pane fade active in" id="wechat">
+                    <table class="table table-striped table-config">
+                        <tbody>
+                        <tr>
+                            <td width="20%">APP的app_id</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][appid]" value="{$item.value.appid|default=''}" class="form-control" data-rule="" data-tip="APP应用中支付时使用"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>公众号的app_id</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip="公众号中支付时使用"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>公众号的app_secret</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][app_secret]" value="{$item.value.app_secret|default=''}" class="form-control" data-rule="" data-tip="公众号中支付时使用"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>小程序的app_id</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][miniapp_id]" value="{$item.value.miniapp_id|default=''}" class="form-control" data-rule="" data-tip="仅在小程序支付时使用"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>微信支付商户号</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][mch_id]" value="{$item.value.mch_id|default=''}" class="form-control" data-rule="" data-tip=""/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>微信支付商户API密钥V2</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][key]" value="{$item.value.key|default=''}" class="form-control" data-rule="" data-tip=""/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>微信支付商户API密钥V3</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][key_v3]" value="{$item.value.key_v3|default=''}" class="form-control" data-rule="" data-tip=""/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>支付模式</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        {:Form::radios('row[wechat][mode]',['normal'=>'正式环境','dev'=>'沙箱环境','service'=>'服务商模式'],$item.value.mode??'normal')}
+                                        <div style="margin-top:5px;" data-type="dev" class="text-muted {if ($item.value.mode??'')!=='dev'}hidden{/if}">
+                                            <i class="fa fa-info-circle"></i> 沙箱环境:<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1&index=2" target="_blank">微信支付验收指引</a>
+                                        </div>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
+                            <td>子商户商户号ID</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][sub_mch_id]" value="{$item.value.sub_mch_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
+                            <td>子商户APP的app_id</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][sub_appid]" value="{$item.value.sub_appid|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
+                            <td>子商户公众号的app_id</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][sub_app_id]" value="{$item.value.sub_app_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
+                            <td>子商户小程序的app_id</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][sub_miniapp_id]" value="{$item.value.sub_miniapp_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>回调通知地址</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[wechat][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>微信支付API证书cert</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <div class="input-group">
+                                            <input id="c-cert_client" class="form-control" size="50" name="row[wechat][cert_client]" type="text" value="{$item.value.cert_client|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到">
+                                            <div class="input-group-addon no-border no-padding">
+                                                <span><button type="button" id="faupload-cert_client" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_client"}' data-mimetype="pem" data-input-id="c-cert_client" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                            </div>
+                                            <span class="msg-box n-right" for="c-cert_client"></span>
+                                        </div>
+                                        <div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>微信支付API证书key</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <div class="input-group">
+                                            <input id="c-cert_key" class="form-control" size="50" name="row[wechat][cert_key]" type="text" value="{$item.value.cert_key|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到">
+                                            <div class="input-group-addon no-border no-padding">
+                                                <span><button type="button" id="faupload-cert_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_key"}' data-mimetype="pem" data-input-id="c-cert_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                            </div>
+                                            <span class="msg-box n-right" for="c-cert_key"></span>
+                                        </div>
+                                        <div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+
+                        <tr>
+                            <td>记录日志</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        {:Form::radios('row[wechat][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)}
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        </tbody>
+                    </table>
+                </div>
+                {elseif $item.name=='alipay'}
+                <div class="tab-pane fade" id="alipay">
+                    <table class="table table-striped table-config">
+                        <tbody>
+                        <tr>
+                            <td>支付模式</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-12 col-xs-12">
+                                        {:Form::radios('row[alipay][mode]',['normal'=>'正式环境','dev'=>'沙箱环境', 'service'=>'服务商模式'],$item.value.mode??'normal')}
+
+                                        <div style="margin-top:5px;" data-mode="dev" class="text-muted {if ($item.value.mode??'')!=='dev'}hidden{/if}">
+                                            <i class="fa fa-info-circle"></i> 如果使用沙箱环境,务必使用沙箱的app_id和沙箱配置,以及使用沙箱账号进行测试。<br>
+                                            沙箱环境:<a href="https://openhome.alipay.com/develop/sandbox/app" target="_blank">https://openhome.alipay.com/develop/sandbox/app</a>
+                                        </div>
+                                    </div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr class="text-muted {if ($item.value.mode??'')!=='service'}hidden{/if}" data-mode="service">
+                            <td width="20%">服务商ID(pid)</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[alipay][pid]" value="{$item.value.pid|default=''}" class="form-control" data-rule="" data-tip=""/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td width="20%">应用ID(app_id)</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[alipay][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip=""/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>回调通知地址</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[alipay][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>支付跳转地址</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[alipay][return_url]" value="{$item.value.return_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>应用私钥(private_key)</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <input type="text" name="row[alipay][private_key]" value="{$item.value.private_key|default=''}" class="form-control" data-rule=""/>
+                                        <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/207/201602469554" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用私钥?</a></div>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>签名方式</td>
+                            <td>
+                                <div>
+                                    <div class="radio">
+                                        <label for="row[alipay][signtype]-publickey"><input id="row[alipay][signtype]-publickey" name="row[alipay][signtype]" {if isset($item.value.signtype)&&$item.value.signtype=='publickey'}checked{/if} type="radio" value="publickey"> 普通公钥</label>
+                                        <label for="row[alipay][signtype]-cert"><input id="row[alipay][signtype]-cert" {if isset($item.value.signtype)&&$item.value.signtype=='cert'}checked{/if} name="row[alipay][signtype]" type="radio" value="cert"> 公钥证书</label>
+                                    </div>
+                                </div>
+                                <div style="margin-top:5px;" class="text-muted">
+                                    <i class="fa fa-info-circle"></i> 如果要使用转账、提现功能,则必须使用公钥证书
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                <span data-signtype="publickey" class="{if ($item.value.signtype??'')==='cert'}hidden{/if}">支付宝公钥</span>
+                                <span data-signtype="cert" class="{if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}">支付宝公钥证书路径</span>
+                                (alipay_public_key)
+                            </td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <div class="input-group">
+                                            <input id="c-ali_public_key" class="form-control" size="50" name="row[alipay][ali_public_key]" type="text" value="{$item.value.ali_public_key|default=''|htmlentities}" placeholder="普通公钥请直接粘贴,公钥证书请点击右侧的上传">
+                                            <div class="input-group-addon no-border no-padding {if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}" data-signtype="cert">
+                                                <span><button type="button" id="faupload-ali_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"ali_public_key"}' data-mimetype="crt" data-input-id="c-ali_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                            </div>
+                                            <span class="msg-box n-right" for="c-ali_public_key"></span>
+                                        </div>
+                                        <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/FAQ/aba5803b-ad15-4474-aed6-92e43ea253ea" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝公钥?</a></div>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                <span data-signtype="publickey" class="{if ($item.value.signtype??'')==='cert'}hidden{/if}">应用公钥</span>
+                                <span data-signtype="cert" class="{if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}">应用公钥证书路径</span>
+                                (app_cert_public_key)
+                            </td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <div class="input-group">
+                                            <input id="c-app_cert_public_key" class="form-control" size="50" name="row[alipay][app_cert_public_key]" type="text" value="{$item.value.app_cert_public_key|default=''|htmlentities}">
+                                            <div class="input-group-addon no-border no-padding {if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}" data-signtype="cert">
+                                                <span><button type="button" id="faupload-app_cert_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"app_cert_public_key"}' data-mimetype="crt" data-input-id="c-app_cert_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                            </div>
+                                            <span class="msg-box n-right" for="c-app_cert_public_key"></span>
+                                        </div>
+                                        <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/FAQ/aba5803b-ad15-4474-aed6-92e43ea253ea" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用公钥?</a></div>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        <tr class="{if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}" data-signtype="cert">
+                            <td>支付宝根证书路径(alipay_root_cert)</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        <div class="input-group">
+                                            <input id="c-alipay_root_cert" class="form-control" size="50" name="row[alipay][alipay_root_cert]" type="text" value="{$item.value.alipay_root_cert|default=''|htmlentities}">
+                                            <div class="input-group-addon no-border no-padding">
+                                                <span><button type="button" id="faupload-alipay_root_cert" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"alipay_root_cert"}' data-mimetype="crt" data-input-id="c-alipay_root_cert" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                            </div>
+                                            <span class="msg-box n-right" for="c-alipay_root_cert"></span>
+                                        </div>
+                                        <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/271/201602474998" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝根证书?</a></div>
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+
+                        <tr>
+                            <td>记录日志</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        {:Form::radios('row[alipay][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)}
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+
+                        <tr>
+                            <td>PC端使用扫码支付</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        {:Form::radios('row[alipay][scanpay]',['1'=>'开启','0'=>'关闭'],$item.value.scanpay??0)}
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+                            </td>
+                        </tr>
+                        </tbody>
+                    </table>
+                </div>
+                {/if}
+                {/foreach}
+                <div class="form-group layer-footer">
+                    <label class="control-label col-xs-12 col-sm-2"></label>
+                    <div class="col-xs-12 col-sm-8">
+                        <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
+                        <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</form>
+<script>
+    require.callback = function () {
+        define('backend/addon', ['backend', 'form'], function (Backend, Form) {
+            var Controller = {
+                config: function () {
+                    $(document).on("click", ".nav-group li a[data-toggle='tab']", function () {
+                        if ($(this).attr("href") == "#all") {
+                            $(".tab-pane").addClass("active in");
+                        }
+
+                        return;
+                    });
+
+                    $(document).on("click", "input[name='row[wechat][mode]']", function () {
+                        $("#wechat [data-type]").addClass("hidden");
+                        $("#wechat [data-type='" + $(this).val() + "']").removeClass("hidden");
+                    });
+                    $(document).on("click", "input[name='row[alipay][mode]']", function () {
+                        $("#alipay [data-mode]").addClass("hidden");
+                        $("#alipay [data-mode='" + $(this).val() + "']").removeClass("hidden");
+                    });
+                    $(document).on("click", "input[name='row[alipay][signtype]']", function () {
+                        $("#alipay [data-signtype]").addClass("hidden");
+                        $("#alipay [data-signtype='" + $(this).val() + "']").removeClass("hidden");
+                    });
+
+                    Form.api.bindevent($("form[role=form]"));
+                }
+            };
+            return Controller;
+        });
+    };
+</script>

+ 70 - 0
addons/epay/config.php

@@ -0,0 +1,70 @@
+<?php
+
+return [
+    [
+        'name'    => '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'         => '',
+            'mode'           => 'normal',
+            'sub_mch_id'     => '',
+            'sub_appid'      => '',
+            'sub_app_id'     => '',
+            'sub_miniapp_id' => '',
+            'notify_url'     => '',
+            'cert_client'    => '/addons/epay/certs/apiclient_cert.pem',
+            'cert_key'       => '/addons/epay/certs/apiclient_key.pem',
+            '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'  => '',
+    ]
+];

+ 234 - 0
addons/epay/controller/Api.php

@@ -0,0 +1,234 @@
+<?php
+
+namespace addons\epay\controller;
+
+use addons\epay\library\Service;
+use addons\epay\library\Wechat;
+use addons\third\model\Third;
+use app\common\library\Auth;
+use Exception;
+use think\addons\Controller;
+use think\Response;
+use think\Session;
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Pay;
+
+/**
+ * API接口控制器
+ *
+ * @package addons\epay\controller
+ */
+class Api extends Controller
+{
+
+    protected $layout = 'default';
+    protected $config = [];
+
+    /**
+     * 默认方法
+     */
+    public function index()
+    {
+        return;
+    }
+
+    /**
+     * 外部提交
+     */
+    public function submit()
+    {
+        $this->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"));
+    }
+
+}

+ 131 - 0
addons/epay/controller/Index.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace addons\epay\controller;
+
+use addons\epay\library\Service;
+use fast\Random;
+use think\addons\Controller;
+use Exception;
+
+/**
+ * 微信支付宝整合插件首页
+ *
+ * 此控制器仅用于开发展示说明和测试,请自行添加一个新的控制器进行处理返回和回调事件,同时删除此控制器文件
+ *
+ * Class Index
+ * @package addons\epay\controller
+ */
+class Index extends Controller
+{
+    protected $layout = 'default';
+
+    protected $config = [];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        if (!config("app_debug")) {
+            $this->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"));
+    }
+}

+ 10 - 0
addons/epay/info.ini

@@ -0,0 +1,10 @@
+name = epay
+title = 微信支付宝整合
+intro = 可用于快速整合企业微信、支付宝支付功能
+author = FastAdmin
+website = https://www.fastadmin.net
+version = 1.3.6
+state = 1
+url = /addons/epay
+license = regular
+licenseto = 19079

+ 18 - 0
addons/epay/library/Collection.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace addons\epay\library;
+
+class Collection extends \Yansongda\Supports\Collection
+{
+
+    /**
+     * 创建 Collection 实例
+     * @access public
+     * @param  array $items 数据
+     * @return static
+     */
+    public static function make($items = [])
+    {
+        return new static($items);
+    }
+}

+ 16 - 0
addons/epay/library/OrderException.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace addons\epay\library;
+
+use think\Exception;
+
+class OrderException extends Exception
+{
+    public function __construct($message = "", $code = 0, $data = [])
+    {
+        $this->message = $message;
+        $this->code = $code;
+        $this->data = $data;
+    }
+
+}

+ 58 - 0
addons/epay/library/RedirectResponse.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace addons\epay\library;
+
+class RedirectResponse extends \Symfony\Component\HttpFoundation\RedirectResponse implements \JsonSerializable, \Serializable
+{
+    public function __toString()
+    {
+        return $this->getContent();
+    }
+
+    public function setTargetUrl($url)
+    {
+        if ('' === ($url ?? '')) {
+            throw new \InvalidArgumentException('无法跳转到空页面');
+        }
+
+        $this->targetUrl = $url;
+
+        $this->setContent(
+            sprintf('<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8" />
+        <meta http-equiv="refresh" content="0;url=\'%1$s\'" />
+
+        <title>正在跳转支付 %1$s</title>
+    </head>
+    <body>
+        <div id="redirect" style="display:none;">正在跳转支付 <a href="%1$s">%1$s</a></div>
+        <script type="text/javascript">
+            setTimeout(function(){
+                document.getElementById("redirect").style.display = "block";
+            }, 1000);
+        </script>
+    </body>
+</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
+
+        $this->headers->set('Location', $url);
+
+        return $this;
+    }
+
+    public function jsonSerialize()
+    {
+        return $this->getContent();
+    }
+
+    public function serialize()
+    {
+        return serialize($this->content);
+    }
+
+    public function unserialize($serialized)
+    {
+        return $this->content = unserialize($serialized);
+    }
+}

+ 26 - 0
addons/epay/library/Response.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace addons\epay\library;
+
+class Response extends \Symfony\Component\HttpFoundation\Response implements \JsonSerializable, \Serializable
+{
+    public function __toString()
+    {
+        return $this->getContent();
+    }
+
+    public function jsonSerialize()
+    {
+        return $this->getContent();
+    }
+
+    public function serialize()
+    {
+        return serialize($this->content);
+    }
+
+    public function unserialize($serialized)
+    {
+        return $this->content = unserialize($serialized);
+    }
+}

+ 466 - 0
addons/epay/library/Service.php

@@ -0,0 +1,466 @@
+<?php
+
+namespace addons\epay\library;
+
+use addons\third\model\Third;
+use app\common\library\Auth;
+use Exception;
+use think\Hook;
+use think\Session;
+use Yansongda\Pay\Pay;
+use Yansongda\Supports\Str;
+
+/**
+ * 订单服务类
+ *
+ * @package addons\epay\library
+ */
+class Service
+{
+
+    public const SDK_VERSION_V2 = 'v2';
+
+    public const SDK_VERSION_V3 = 'v3';
+
+    /**
+     * 提交订单
+     * @param array|float $amount    订单金额
+     * @param string      $orderid   订单号
+     * @param string      $type      支付类型,可选alipay或wechat
+     * @param string      $title     订单标题
+     * @param string      $notifyurl 通知回调URL
+     * @param string      $returnurl 跳转返回URL
+     * @param string      $method    支付方法
+     * @param string      $openid    Openid
+     * @param array       $custom    自定义微信支付宝相关配置
+     * @return Response|RedirectResponse|Collection
+     * @throws Exception
+     */
+    public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null, $openid = '', $custom = [])
+    {
+        $version = self::getSdkVersion();
+        $request = request();
+        $addonConfig = get_addon_config('epay');
+
+        if (!is_array($amount)) {
+            $params = [
+                'amount'    => $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,这里使用openid
+                    $params['buyer_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 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 (isset($config['cert_client']) && substr($config['cert_client'], 0, 8) == '/addons/') {
+            $config['cert_client'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_client'], 1));
+        }
+        if (isset($config['cert_key']) && substr($config['cert_key'], 0, 8) == '/addons/') {
+            $config['cert_key'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_key'], 1));
+        }
+        if (isset($config['app_cert_public_key']) && substr($config['app_cert_public_key'], 0, 8) == '/addons/') {
+            $config['app_cert_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['app_cert_public_key'], 1));
+        }
+        if (isset($config['alipay_root_cert']) && substr($config['alipay_root_cert'], 0, 8) == '/addons/') {
+            $config['alipay_root_cert'] = ROOT_PATH . str_replace('/', DS, substr($config['alipay_root_cert'], 1));
+        }
+        if (isset($config['ali_public_key']) && (Str::endsWith($config['ali_public_key'], '.crt') || Str::endsWith($config['ali_public_key'], '.pem'))) {
+            $config['ali_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['ali_public_key'], 1));
+        }
+
+        // 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'];
+
+                $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;
+    }
+}

+ 110 - 0
addons/epay/library/Wechat.php

@@ -0,0 +1,110 @@
+<?php
+
+namespace addons\epay\library;
+
+use fast\Http;
+use think\Cache;
+use think\Session;
+
+/**
+ * 微信授权
+ *
+ */
+class Wechat
+{
+    private $app_id = '';
+    private $app_secret = '';
+    private $scope = 'snsapi_userinfo';
+
+    public function __construct($app_id, $app_secret)
+    {
+        $this->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;
+    }
+}

+ 2 - 0
addons/epay/library/hyperf/context/.gitattributes

@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore

+ 21 - 0
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.

+ 40 - 0
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"
+        }
+    }
+}

+ 112 - 0
addons/epay/library/hyperf/context/src/Context.php

@@ -0,0 +1,112 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Context;
+
+use Hyperf\Engine\Coroutine;
+
+class Context
+{
+    protected static $nonCoContext = [];
+
+    public static function set(string $id, $value)
+    {
+        if (Coroutine::id() > 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;
+    }
+}

+ 2 - 0
addons/epay/library/hyperf/contract/.gitattributes

@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore

+ 21 - 0
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.

+ 33 - 0
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"
+        }
+    }
+}

+ 16 - 0
addons/epay/library/hyperf/contract/src/ApplicationInterface.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface ApplicationInterface
+{
+}

+ 22 - 0
addons/epay/library/hyperf/contract/src/Castable.php

@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface Castable
+{
+    /**
+     * Get the name of the caster class to use when casting from / to this cast target.
+     *
+     * @return CastsAttributes|CastsInboundAttributes|string
+     */
+    public static function castUsing();
+}

+ 33 - 0
addons/epay/library/hyperf/contract/src/CastsAttributes.php

@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface CastsAttributes
+{
+    /**
+     * Transform the attribute from the underlying model values.
+     *
+     * @param object $model
+     * @param mixed $value
+     * @return mixed
+     */
+    public function get($model, string $key, $value, array $attributes);
+
+    /**
+     * Transform the attribute to its underlying model values.
+     *
+     * @param object $model
+     * @param mixed $value
+     * @return array|string
+     */
+    public function set($model, string $key, $value, array $attributes);
+}

+ 24 - 0
addons/epay/library/hyperf/contract/src/CastsInboundAttributes.php

@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface CastsInboundAttributes
+{
+    /**
+     * Transform the attribute to its underlying model values.
+     *
+     * @param object $model
+     * @param mixed $value
+     * @return array
+     */
+    public function set($model, string $key, $value, array $attributes);
+}

+ 17 - 0
addons/epay/library/hyperf/contract/src/CompressInterface.php

@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface CompressInterface
+{
+    public function compress(): UnCompressInterface;
+}

+ 41 - 0
addons/epay/library/hyperf/contract/src/ConfigInterface.php

@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface ConfigInterface
+{
+    /**
+     * Finds an entry of the container by its identifier and returns it.
+     *
+     * @param string $key identifier of the entry to look for
+     * @param mixed $default default value of the entry when does not found
+     * @return mixed entry
+     */
+    public function get(string $key, $default = null);
+
+    /**
+     * Returns true if the container can return an entry for the given identifier.
+     * Returns false otherwise.
+     *
+     * @param string $keys identifier of the entry to look for
+     * @return bool
+     */
+    public function has(string $keys);
+
+    /**
+     * Set a value to the container by its identifier.
+     *
+     * @param string $key identifier of the entry to set
+     * @param mixed $value the value that save to container
+     */
+    public function set(string $key, $value);
+}

+ 40 - 0
addons/epay/library/hyperf/contract/src/ConnectionInterface.php

@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface ConnectionInterface
+{
+    /**
+     * Get the real connection from pool.
+     */
+    public function getConnection();
+
+    /**
+     * Reconnect the connection.
+     */
+    public function reconnect(): bool;
+
+    /**
+     * Check the connection is valid.
+     */
+    public function check(): bool;
+
+    /**
+     * Close the connection.
+     */
+    public function close(): bool;
+
+    /**
+     * Release the connection to pool.
+     */
+    public function release(): void;
+}

+ 53 - 0
addons/epay/library/hyperf/contract/src/ContainerInterface.php

@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Psr\Container\ContainerInterface as PsrContainerInterface;
+
+interface ContainerInterface extends PsrContainerInterface
+{
+    /**
+     * Build an entry of the container by its name.
+     * This method behave like get() except resolves the entry again every time.
+     * For example if the entry is a class then a new instance will be created each time.
+     * This method makes the container behave like a factory.
+     *
+     * @param string $name entry name or a class name
+     * @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters
+     *                          to specific values. Parameters not defined in this array will be resolved using
+     *                          the container.
+     * @throws InvalidArgumentException the name parameter must be of type string
+     * @throws NotFoundException no entry found for the given name
+     */
+    public function make(string $name, array $parameters = []);
+
+    /**
+     * Bind an arbitrary resolved entry to an identifier.
+     * Useful for testing 'get'.
+     *
+     * @param mixed $entry
+     */
+    public function set(string $name, $entry);
+
+    /**
+     * Unbind an arbitrary resolved entry.
+     */
+    public function unbind(string $name);
+
+    /**
+     * Bind an arbitrary definition to an identifier.
+     * Useful for testing 'make'.
+     *
+     * @param array|callable|string $definition
+     */
+    public function define(string $name, $definition);
+}

+ 17 - 0
addons/epay/library/hyperf/contract/src/DispatcherInterface.php

@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface DispatcherInterface
+{
+    public function dispatch(...$params);
+}

+ 25 - 0
addons/epay/library/hyperf/contract/src/FrequencyInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface FrequencyInterface
+{
+    /**
+     * Number of hit per time.
+     */
+    public function hit(int $number = 1): bool;
+
+    /**
+     * Hits per second.
+     */
+    public function frequency(): float;
+}

+ 17 - 0
addons/epay/library/hyperf/contract/src/IdGeneratorInterface.php

@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface IdGeneratorInterface
+{
+    public function generate();
+}

+ 30 - 0
addons/epay/library/hyperf/contract/src/LengthAwarePaginatorInterface.php

@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface LengthAwarePaginatorInterface extends PaginatorInterface
+{
+    /**
+     * Create a range of pagination URLs.
+     */
+    public function getUrlRange(int $start, int $end): array;
+
+    /**
+     * Determine the total number of items in the data store.
+     */
+    public function total(): int;
+
+    /**
+     * Get the page number of the last available page.
+     */
+    public function lastPage(): int;
+}

+ 17 - 0
addons/epay/library/hyperf/contract/src/MiddlewareInitializerInterface.php

@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface MiddlewareInitializerInterface
+{
+    public function initCoreMiddleware(string $serverName): void;
+}

+ 32 - 0
addons/epay/library/hyperf/contract/src/NormalizerInterface.php

@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface NormalizerInterface
+{
+    /**
+     * Normalizes an object into a set of arrays/scalars.
+     *
+     * @param mixed $object
+     * @return null|array|\ArrayObject|bool|float|int|string
+     */
+    public function normalize($object);
+
+    /**
+     * Denormalizes data back into an object of the given class.
+     *
+     * @param mixed $data Data to restore
+     * @param string $class The expected class to instantiate
+     * @return mixed|object
+     */
+    public function denormalize($data, string $class);
+}

+ 23 - 0
addons/epay/library/hyperf/contract/src/OnCloseInterface.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Swoole\Http\Response;
+use Swoole\Server;
+
+interface OnCloseInterface
+{
+    /**
+     * @param Response|Server $server
+     */
+    public function onClose($server, int $fd, int $reactorId): void;
+}

+ 20 - 0
addons/epay/library/hyperf/contract/src/OnHandShakeInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Swoole\Http\Request;
+use Swoole\Http\Response;
+
+interface OnHandShakeInterface
+{
+    public function onHandShake(Request $request, Response $response): void;
+}

+ 24 - 0
addons/epay/library/hyperf/contract/src/OnMessageInterface.php

@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Swoole\Http\Response;
+use Swoole\WebSocket\Frame;
+use Swoole\WebSocket\Server;
+
+interface OnMessageInterface
+{
+    /**
+     * @param Response|Server $server
+     */
+    public function onMessage($server, Frame $frame): void;
+}

+ 24 - 0
addons/epay/library/hyperf/contract/src/OnOpenInterface.php

@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Swoole\Http\Request;
+use Swoole\Http\Response;
+use Swoole\WebSocket\Server;
+
+interface OnOpenInterface
+{
+    /**
+     * @param Response|Server $server
+     */
+    public function onOpen($server, Request $request): void;
+}

+ 24 - 0
addons/epay/library/hyperf/contract/src/OnPacketInterface.php

@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Swoole\WebSocket\Server;
+
+interface OnPacketInterface
+{
+    /**
+     * @param Server $server
+     * @param mixed $data
+     * @param array $clientInfo
+     */
+    public function onPacket($server, $data, $clientInfo): void;
+}

+ 23 - 0
addons/epay/library/hyperf/contract/src/OnReceiveInterface.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Swoole\Coroutine\Server\Connection;
+use Swoole\Server as SwooleServer;
+
+interface OnReceiveInterface
+{
+    /**
+     * @param Connection|SwooleServer $server
+     */
+    public function onReceive($server, int $fd, int $reactorId, string $data): void;
+}

+ 21 - 0
addons/epay/library/hyperf/contract/src/OnRequestInterface.php

@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface OnRequestInterface
+{
+    /**
+     * @param mixed $request swoole request or psr server request
+     * @param mixed $response swoole response or swow session
+     */
+    public function onRequest($request, $response): void;
+}

+ 19 - 0
addons/epay/library/hyperf/contract/src/PackerInterface.php

@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface PackerInterface
+{
+    public function pack($data): string;
+
+    public function unpack(string $data);
+}

+ 95 - 0
addons/epay/library/hyperf/contract/src/PaginatorInterface.php

@@ -0,0 +1,95 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface PaginatorInterface
+{
+    /**
+     * Get the URL for a given page.
+     */
+    public function url(int $page): string;
+
+    /**
+     * Add a set of query string values to the paginator.
+     *
+     * @param array|string $key
+     * @return $this
+     */
+    public function appends($key, ?string $value = null);
+
+    /**
+     * Get / set the URL fragment to be appended to URLs.
+     *
+     * @return $this|string
+     */
+    public function fragment(?string $fragment = null);
+
+    /**
+     * The URL for the next page, or null.
+     */
+    public function nextPageUrl(): ?string;
+
+    /**
+     * Get the URL for the previous page, or null.
+     */
+    public function previousPageUrl(): ?string;
+
+    /**
+     * Get all of the items being paginated.
+     */
+    public function items(): array;
+
+    /**
+     * Get the "index" of the first item being paginated.
+     */
+    public function firstItem(): ?int;
+
+    /**
+     * Get the "index" of the last item being paginated.
+     */
+    public function lastItem(): ?int;
+
+    /**
+     * Determine how many items are being shown per page.
+     */
+    public function perPage(): int;
+
+    /**
+     * Determine the current page being paginated.
+     */
+    public function currentPage(): int;
+
+    /**
+     * Determine if there are enough items to split into multiple pages.
+     */
+    public function hasPages(): bool;
+
+    /**
+     * Determine if there is more items in the data store.
+     */
+    public function hasMorePages(): bool;
+
+    /**
+     * Determine if the list of items is empty or not.
+     */
+    public function isEmpty(): bool;
+
+    /**
+     * Determine if the list of items is not empty.
+     */
+    public function isNotEmpty(): bool;
+
+    /**
+     * Render the paginator using a given view.
+     */
+    public function render(?string $view = null, array $data = []): string;
+}

+ 30 - 0
addons/epay/library/hyperf/contract/src/PoolInterface.php

@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface PoolInterface
+{
+    /**
+     * Get a connection from the connection pool.
+     */
+    public function get(): ConnectionInterface;
+
+    /**
+     * Release a connection back to the connection pool.
+     */
+    public function release(ConnectionInterface $connection): void;
+
+    /**
+     * Close and clear the connection pool.
+     */
+    public function flush(): void;
+}

+ 27 - 0
addons/epay/library/hyperf/contract/src/PoolOptionInterface.php

@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface PoolOptionInterface
+{
+    public function getMaxConnections(): int;
+
+    public function getMinConnections(): int;
+
+    public function getConnectTimeout(): float;
+
+    public function getWaitTimeout(): float;
+
+    public function getHeartbeat(): float;
+
+    public function getMaxIdleTime(): float;
+}

+ 36 - 0
addons/epay/library/hyperf/contract/src/ProcessInterface.php

@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Swoole\Coroutine\Http\Server as CoHttpServer;
+use Swoole\Coroutine\Server as CoServer;
+use Swoole\Server;
+
+interface ProcessInterface
+{
+    /**
+     * Create the process object according to process number and bind to server.
+     * @param CoHttpServer|CoServer|Server $server
+     */
+    public function bind($server): void;
+
+    /**
+     * Determine if the process should start ?
+     * @param CoServer|Server $server
+     */
+    public function isEnable($server): bool;
+
+    /**
+     * The logical of process will place in here.
+     */
+    public function handle(): void;
+}

+ 22 - 0
addons/epay/library/hyperf/contract/src/ResponseEmitterInterface.php

@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Psr\Http\Message\ResponseInterface;
+
+interface ResponseEmitterInterface
+{
+    /**
+     * @param mixed $connection swoole response or swow session
+     */
+    public function emit(ResponseInterface $response, $connection, bool $withContent = true);
+}

+ 158 - 0
addons/epay/library/hyperf/contract/src/SessionInterface.php

@@ -0,0 +1,158 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface SessionInterface
+{
+    /**
+     * Starts the session storage.
+     *
+     * @throws \RuntimeException if session fails to start
+     * @return bool True if session started
+     */
+    public function start(): bool;
+
+    /**
+     * Returns the session ID.
+     *
+     * @return string The session ID
+     */
+    public function getId(): string;
+
+    /**
+     * Sets the session ID.
+     */
+    public function setId(string $id);
+
+    /**
+     * Returns the session name.
+     */
+    public function getName(): string;
+
+    /**
+     * Sets the session name.
+     */
+    public function setName(string $name);
+
+    /**
+     * Invalidates the current session.
+     *
+     * Clears all session attributes and flashes and regenerates the
+     * session and deletes the old session from persistence.
+     *
+     * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+     *                      will leave the system settings unchanged, 0 sets the cookie
+     *                      to expire with browser session. Time is in seconds, and is
+     *                      not a Unix timestamp.
+     *
+     * @return bool True if session invalidated, false if error
+     */
+    public function invalidate(?int $lifetime = null): bool;
+
+    /**
+     * Migrates the current session to a new session id while maintaining all
+     * session attributes.
+     *
+     * @param bool $destroy Whether to delete the old session or leave it to garbage collection
+     * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+     *                      will leave the system settings unchanged, 0 sets the cookie
+     *                      to expire with browser session. Time is in seconds, and is
+     *                      not a Unix timestamp.
+     *
+     * @return bool True if session migrated, false if error
+     */
+    public function migrate(bool $destroy = false, ?int $lifetime = null): bool;
+
+    /**
+     * Force the session to be saved and closed.
+     *
+     * This method is generally not required for real sessions as
+     * the session will be automatically saved at the end of
+     * code execution.
+     */
+    public function save(): void;
+
+    /**
+     * Checks if an attribute is defined.
+     *
+     * @param string $name The attribute name
+     *
+     * @return bool true if the attribute is defined, false otherwise
+     */
+    public function has(string $name): bool;
+
+    /**
+     * Returns an attribute.
+     *
+     * @param string $name The attribute name
+     * @param mixed $default The default value if not found
+     */
+    public function get(string $name, $default = null);
+
+    /**
+     * Sets an attribute.
+     * @param mixed $value
+     */
+    public function set(string $name, $value): void;
+
+    /**
+     * Put a key / value pair or array of key / value pairs in the session.
+     *
+     * @param array|string $key
+     * @param null|mixed $value
+     */
+    public function put($key, $value = null): void;
+
+    /**
+     * Returns attributes.
+     */
+    public function all(): array;
+
+    /**
+     * Sets attributes.
+     */
+    public function replace(array $attributes): void;
+
+    /**
+     * Removes an attribute, returning its value.
+     *
+     * @return mixed The removed value or null when it does not exist
+     */
+    public function remove(string $name);
+
+    /**
+     * Remove one or many items from the session.
+     *
+     * @param array|string $keys
+     */
+    public function forget($keys): void;
+
+    /**
+     * Clears all attributes.
+     */
+    public function clear(): void;
+
+    /**
+     * Checks if the session was started.
+     */
+    public function isStarted(): bool;
+
+    /**
+     * Get the previous URL from the session.
+     */
+    public function previousUrl(): ?string;
+
+    /**
+     * Set the "previous" URL in the session.
+     */
+    public function setPreviousUrl(string $url): void;
+}

+ 18 - 0
addons/epay/library/hyperf/contract/src/StdoutLoggerInterface.php

@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Psr\Log\LoggerInterface;
+
+interface StdoutLoggerInterface extends LoggerInterface
+{
+}

+ 20 - 0
addons/epay/library/hyperf/contract/src/Synchronized.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface Synchronized
+{
+    /**
+     * Whether the data has been synchronized.
+     */
+    public function isSynchronized(): bool;
+}

+ 37 - 0
addons/epay/library/hyperf/contract/src/TranslatorInterface.php

@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface TranslatorInterface
+{
+    /**
+     * Get the translation for a given key.
+     */
+    public function trans(string $key, array $replace = [], ?string $locale = null);
+
+    /**
+     * Get a translation according to an integer value.
+     *
+     * @param array|\Countable|int $number
+     */
+    public function transChoice(string $key, $number, array $replace = [], ?string $locale = null): string;
+
+    /**
+     * Get the default locale being used.
+     */
+    public function getLocale(): string;
+
+    /**
+     * Set the default locale.
+     */
+    public function setLocale(string $locale);
+}

+ 35 - 0
addons/epay/library/hyperf/contract/src/TranslatorLoaderInterface.php

@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface TranslatorLoaderInterface
+{
+    /**
+     * Load the messages for the given locale.
+     */
+    public function load(string $locale, string $group, ?string $namespace = null): array;
+
+    /**
+     * Add a new namespace to the loader.
+     */
+    public function addNamespace(string $namespace, string $hint);
+
+    /**
+     * Add a new JSON path to the loader.
+     */
+    public function addJsonPath(string $path);
+
+    /**
+     * Get an array of all the registered namespaces.
+     */
+    public function namespaces(): array;
+}

+ 17 - 0
addons/epay/library/hyperf/contract/src/UnCompressInterface.php

@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+interface UnCompressInterface
+{
+    public function uncompress();
+}

+ 60 - 0
addons/epay/library/hyperf/contract/src/ValidatorInterface.php

@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Contract;
+
+use Hyperf\Utils\Contracts\MessageBag;
+use Hyperf\Utils\Contracts\MessageProvider;
+
+interface ValidatorInterface extends MessageProvider
+{
+    /**
+     * Run the validator's rules against its data.
+     */
+    public function validate(): array;
+
+    /**
+     * Get the attributes and values that were validated.
+     */
+    public function validated(): array;
+
+    /**
+     * Determine if the data fails the validation rules.
+     */
+    public function fails(): bool;
+
+    /**
+     * Get the failed validation rules.
+     */
+    public function failed(): array;
+
+    /**
+     * Add conditions to a given field based on a Closure.
+     *
+     * @param array|string $attribute
+     * @param array|string $rules
+     * @return $this
+     */
+    public function sometimes($attribute, $rules, callable $callback);
+
+    /**
+     * Add an after validation callback.
+     *
+     * @param callable|string $callback
+     * @return $this
+     */
+    public function after($callback);
+
+    /**
+     * Get all of the validation error messages.
+     */
+    public function errors(): MessageBag;
+}

+ 4 - 0
addons/epay/library/hyperf/engine/.gitattributes

@@ -0,0 +1,4 @@
+/.github export-ignore
+/examples export-ignore
+/tests export-ignore
+

+ 4 - 0
addons/epay/library/hyperf/engine/.gitignore

@@ -0,0 +1,4 @@
+/vendor/
+composer.lock
+*.cache
+*.log

+ 89 - 0
addons/epay/library/hyperf/engine/.php-cs-fixer.php

@@ -0,0 +1,89 @@
+<?php
+
+$header = <<<'EOF'
+This file is part of Hyperf.
+
+@link     https://www.hyperf.io
+@document https://hyperf.wiki
+@contact  group@hyperf.io
+@license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+EOF;
+
+return (new PhpCsFixer\Config())
+    ->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);

+ 6 - 0
addons/epay/library/hyperf/engine/.phpstorm.meta.php

@@ -0,0 +1,6 @@
+<?php
+
+namespace PHPSTORM_META {
+    // Reflect
+    override(\Psr\Container\ContainerInterface::get(0), map('@'));
+}

+ 46 - 0
addons/epay/library/hyperf/engine/Dockerfile

@@ -0,0 +1,46 @@
+# Default Dockerfile
+#
+# @link     https://www.hyperf.io
+# @document https://hyperf.wiki
+# @contact  group@hyperf.io
+# @license  https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
+
+ARG PHP_VERSION
+ARG ALPINE_VERSION
+
+FROM hyperf/hyperf:${PHP_VERSION}-alpine-${ALPINE_VERSION}-swoole
+LABEL maintainer="Hyperf Developers <group@hyperf.io>" 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

+ 7 - 0
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
+```

+ 49 - 0
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"
+    }
+}

+ 15 - 0
addons/epay/library/hyperf/engine/phpunit.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/bootstrap.php"
+         backupGlobals="false"
+         backupStaticAttributes="false"
+         verbose="true"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false">
+    <testsuite name="Testsuite">
+        <directory>./tests/</directory>
+    </testsuite>
+</phpunit>

+ 142 - 0
addons/epay/library/hyperf/engine/src/Channel.php

@@ -0,0 +1,142 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine;
+
+use Hyperf\Engine\Contract\ChannelInterface;
+use Hyperf\Engine\Exception\RuntimeException;
+
+if (PHP_VERSION_ID > 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;
+        }
+    }
+}

+ 25 - 0
addons/epay/library/hyperf/engine/src/Constant.php

@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine;
+
+use Swoole\Coroutine\Http\Server as HttpServer;
+use Swoole\Coroutine\Server;
+
+class Constant
+{
+    public const ENGINE = 'Swoole';
+
+    public static function isCoroutineServer($server): bool
+    {
+        return $server instanceof Server || $server instanceof HttpServer;
+    }
+}

+ 134 - 0
addons/epay/library/hyperf/engine/src/Contract/ChannelInterface.php

@@ -0,0 +1,134 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Contract;
+
+if (PHP_VERSION_ID > 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();
+    }
+}

+ 70 - 0
addons/epay/library/hyperf/engine/src/Contract/CoroutineInterface.php

@@ -0,0 +1,70 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Contract;
+
+use Hyperf\Engine\Exception\CoroutineDestroyedException;
+use Hyperf\Engine\Exception\RunningInNonCoroutineException;
+
+interface CoroutineInterface
+{
+    /**
+     * @param callable $callable [required]
+     */
+    public function __construct(callable $callable);
+
+    /**
+     * @param mixed ...$data
+     * @return $this
+     */
+    public function execute(...$data);
+
+    /**
+     * @return int
+     */
+    public function getId();
+
+    /**
+     * @param callable $callable [required]
+     * @param mixed ...$data
+     * @return $this
+     */
+    public static function create(callable $callable, ...$data);
+
+    /**
+     * @return int returns coroutine id from current coroutine, -1 in non coroutine environment
+     */
+    public static function id();
+
+    /**
+     * Returns the parent coroutine ID.
+     * Returns 0 when running in the top level coroutine.
+     * @throws RunningInNonCoroutineException when running in non-coroutine context
+     * @throws CoroutineDestroyedException when the coroutine has been destroyed
+     */
+    public static function pid(?int $id = null);
+
+    /**
+     * Set config to coroutine.
+     */
+    public static function set(array $config);
+
+    /**
+     * @param null|int $id coroutine id
+     * @return null|\ArrayObject
+     */
+    public static function getContextFor(?int $id = null);
+
+    /**
+     * Execute callback when coroutine destruct.
+     */
+    public static function defer(callable $callable);
+}

+ 24 - 0
addons/epay/library/hyperf/engine/src/Contract/Http/ClientInterface.php

@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Contract\Http;
+
+use Hyperf\Engine\Http\RawResponse;
+
+interface ClientInterface
+{
+    public function set(array $settings): bool;
+
+    /**
+     * @param string[][] $headers
+     */
+    public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse;
+}

+ 23 - 0
addons/epay/library/hyperf/engine/src/Contract/WebSocket/WebSocketInterface.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Contract\WebSocket;
+
+interface WebSocketInterface
+{
+    public const ON_MESSAGE = 'message';
+
+    public const ON_CLOSE = 'close';
+
+    public function on(string $event, callable $callback): void;
+
+    public function start(): void;
+}

+ 100 - 0
addons/epay/library/hyperf/engine/src/Coroutine.php

@@ -0,0 +1,100 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine;
+
+use Hyperf\Engine\Contract\CoroutineInterface;
+use Hyperf\Engine\Exception\CoroutineDestroyedException;
+use Hyperf\Engine\Exception\RunningInNonCoroutineException;
+use Hyperf\Engine\Exception\RuntimeException;
+use Swoole\Coroutine as SwooleCo;
+
+class Coroutine implements CoroutineInterface
+{
+    /**
+     * @var callable
+     */
+    private $callable;
+
+    /**
+     * @var int
+     */
+    private $id;
+
+    public function __construct(callable $callable)
+    {
+        $this->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);
+    }
+}

+ 16 - 0
addons/epay/library/hyperf/engine/src/Exception/CoroutineDestroyedException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Exception;
+
+class CoroutineDestroyedException extends RuntimeException
+{
+}

+ 16 - 0
addons/epay/library/hyperf/engine/src/Exception/HttpClientException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Exception;
+
+class HttpClientException extends RuntimeException
+{
+}

+ 16 - 0
addons/epay/library/hyperf/engine/src/Exception/RunningInNonCoroutineException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Exception;
+
+class RunningInNonCoroutineException extends RuntimeException
+{
+}

+ 16 - 0
addons/epay/library/hyperf/engine/src/Exception/RuntimeException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Exception;
+
+class RuntimeException extends \RuntimeException
+{
+}

+ 20 - 0
addons/epay/library/hyperf/engine/src/Extension.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine;
+
+class Extension
+{
+    public static function isLoaded(): bool
+    {
+        return extension_loaded('Swoole');
+    }
+}

+ 76 - 0
addons/epay/library/hyperf/engine/src/Http/Client.php

@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Http;
+
+use Hyperf\Engine\Contract\Http\ClientInterface;
+use Hyperf\Engine\Exception\HttpClientException;
+use Swoole\Coroutine\Http\Client as HttpClient;
+
+class Client extends HttpClient implements ClientInterface
+{
+    public function set(array $settings): bool
+    {
+        return parent::set($settings);
+    }
+
+    /**
+     * @param string[][] $headers
+     */
+    public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse
+    {
+        $this->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;
+    }
+}

+ 22 - 0
addons/epay/library/hyperf/engine/src/Http/FdGetter.php

@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Http;
+
+use Swoole\Http\Response;
+
+class FdGetter
+{
+    public function get(Response $response): int
+    {
+        return $response->fd;
+    }
+}

+ 47 - 0
addons/epay/library/hyperf/engine/src/Http/RawResponse.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\Http;
+
+final class RawResponse
+{
+    /**
+     * @var int
+     */
+    public $statusCode = 0;
+
+    /**
+     * @var string[][]
+     */
+    public $headers = [];
+
+    /**
+     * @var string
+     */
+    public $body = '';
+
+    /**
+     * Protocol version.
+     * @var string
+     */
+    public $version = '';
+
+    /**
+     * @param string[][] $headers
+     */
+    public function __construct(int $statusCode, array $headers, string $body, string $version)
+    {
+        $this->statusCode = $statusCode;
+        $this->headers = $headers;
+        $this->body = $body;
+        $this->version = $version;
+    }
+}

+ 16 - 0
addons/epay/library/hyperf/engine/src/Socket.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine;
+
+class Socket extends \Swoole\Coroutine\Socket
+{
+}

+ 16 - 0
addons/epay/library/hyperf/engine/src/WaitGroup.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine;
+
+class WaitGroup extends \Swoole\Coroutine\WaitGroup
+{
+}

+ 19 - 0
addons/epay/library/hyperf/engine/src/WebSocket/Frame.php

@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\WebSocket;
+
+class Frame
+{
+    public const PING = '27890027';
+
+    public const PONG = '278a0027';
+}

+ 27 - 0
addons/epay/library/hyperf/engine/src/WebSocket/Opcode.php

@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\WebSocket;
+
+class Opcode
+{
+    public const CONTINUATION = 0;
+
+    public const TEXT = 1;
+
+    public const BINARY = 2;
+
+    public const CLOSE = 8;
+
+    public const PING = 9;
+
+    public const PONG = 10;
+}

+ 59 - 0
addons/epay/library/hyperf/engine/src/WebSocket/WebSocket.php

@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Engine\WebSocket;
+
+use Hyperf\Engine\Contract\WebSocket\WebSocketInterface;
+use Swoole\Http\Request;
+use Swoole\Http\Response;
+use Swoole\WebSocket\CloseFrame;
+
+class WebSocket implements WebSocketInterface
+{
+    /**
+     * @var Response
+     */
+    protected $connection;
+
+    /**
+     * @var array<string, callable>
+     */
+    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 = [];
+    }
+}

+ 2 - 0
addons/epay/library/hyperf/macroable/.gitattributes

@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore

+ 22 - 0
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.

+ 39 - 0
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"
+        }
+    }
+}

+ 129 - 0
addons/epay/library/hyperf/macroable/src/Macroable.php

@@ -0,0 +1,129 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Macroable;
+
+use BadMethodCallException;
+use Closure;
+use ReflectionClass;
+use ReflectionMethod;
+
+/**
+ * This file come from illuminate/macroable,
+ * thanks Laravel Team provide such a useful class.
+ */
+trait Macroable
+{
+    /**
+     * The registered string macros.
+     *
+     * @var array
+     */
+    protected static $macros = [];
+
+    /**
+     * Dynamically handle calls to the class.
+     *
+     * @param string $method
+     * @param array $parameters
+     * @throws \BadMethodCallException
+     * @return mixed
+     */
+    public static function __callStatic($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(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]);
+    }
+}

+ 2 - 0
addons/epay/library/hyperf/pimple/.gitattributes

@@ -0,0 +1,2 @@
+/tests export-ignore
+/.github export-ignore

+ 4 - 0
addons/epay/library/hyperf/pimple/.gitignore

@@ -0,0 +1,4 @@
+/vendor/
+composer.lock
+*.cache
+*.log

+ 92 - 0
addons/epay/library/hyperf/pimple/.php-cs-fixer.php

@@ -0,0 +1,92 @@
+<?php
+
+$header = <<<'EOF'
+This file is part of Hyperf.
+
+@link     https://www.hyperf.io
+@document https://hyperf.wiki
+@contact  group@hyperf.io
+@license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+EOF;
+
+return (new PhpCsFixer\Config())
+    ->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);

+ 6 - 0
addons/epay/library/hyperf/pimple/.phpstorm.meta.php

@@ -0,0 +1,6 @@
+<?php
+
+namespace PHPSTORM_META {
+    // Reflect
+    override(\Psr\Container\ContainerInterface::get(0), map('@'));
+}

+ 42 - 0
addons/epay/library/hyperf/pimple/.travis.yml

@@ -0,0 +1,42 @@
+language: php
+
+sudo: required
+
+matrix:
+  include:
+    - php: 7.2
+      env: SW_VERSION="4.4.17"
+    - php: 7.3
+      env: SW_VERSION="4.4.17"
+    - php: 7.4
+      env: SW_VERSION="4.4.17"
+    - php: master
+      env: SW_VERSION="4.4.17"
+
+  allow_failures:
+    - php: master
+
+services:
+  - mysql
+  - redis-server
+  - docker
+
+before_install:
+  - export PHP_MAJOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 1)"
+  - export PHP_MINOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 2)"
+  - echo $PHP_MAJOR
+  - echo $PHP_MINOR
+
+install:
+  - cd $TRAVIS_BUILD_DIR
+  - bash ./tests/swoole.install.sh
+  - phpenv config-rm xdebug.ini || echo "xdebug not available"
+  - phpenv config-add ./tests/ci.ini
+
+before_script:
+  - cd $TRAVIS_BUILD_DIR
+  - composer config -g process-timeout 900 && composer update
+
+script:
+  - composer analyse
+  - composer test

+ 195 - 0
addons/epay/library/hyperf/pimple/README.md

@@ -0,0 +1,195 @@
+# Pimple Container
+
+[![Build Status](https://travis-ci.org/hyperf-cloud/pimple-integration.svg?branch=master)](https://travis-ci.org/hyperf-cloud/pimple-integration)
+
+`hyperf/pimple` 是基于 `pimple/pimple` 实现的轻量级符合 `PSR11 规范` 的容器组件。可以减少其他框架使用 Hyperf 组件时的成本。
+
+## 安装
+
+```
+composer require "hyperf/pimple:1.1.*"
+```
+
+## 使用
+
+```php
+<?php
+
+use Hyperf\Pimple\ContainerFactory;
+
+$container = (new ContainerFactory())();
+
+```
+
+### `EasySwoole` 接入 `hyperf/translation`
+
+因为 `EasySwoole` 的容器组件暂时并没有实现 `PSR11` 规范,所以无法直接使用。
+
+1. 首先引入相关组件
+
+```
+composer require "hyperf/translation:1.1.*"
+composer require "hyperf/config:1.1.*"
+```
+
+2. 添加 国际化相关的 Provider
+
+```php
+<?php
+
+declare(strict_types=1);
+
+namespace App\Provider;
+
+use Hyperf\Contract\ConfigInterface;
+use Hyperf\Contract\ContainerInterface;
+use Hyperf\Contract\TranslatorLoaderInterface;
+use Hyperf\Pimple\ProviderInterface;
+use Hyperf\Translation\FileLoader;
+use Hyperf\Utils\Filesystem\Filesystem;
+
+class TranslatorLoaderProvider implements ProviderInterface
+{
+    public function register(ContainerInterface $container)
+    {
+        $container->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
+<?php
+
+declare(strict_types=1);
+
+namespace App\Provider;
+
+use Hyperf\Contract\ConfigInterface;
+use Hyperf\Contract\ContainerInterface;
+use Hyperf\Contract\TranslatorInterface;
+use Hyperf\Contract\TranslatorLoaderInterface;
+use Hyperf\Pimple\ProviderInterface;
+use Hyperf\Translation\Translator;
+
+class TranslatorProvider implements ProviderInterface
+{
+    public function register(ContainerInterface $container)
+    {
+        $container->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
+<?php
+
+declare(strict_types=1);
+
+namespace EasySwoole\EasySwoole;
+
+use EasySwoole\EasySwoole\AbstractInterface\Event;
+use EasySwoole\EasySwoole\Swoole\EventRegister;
+use EasySwoole\Http\Request;
+use EasySwoole\Http\Response;
+use Hyperf\Config\Config;
+use Hyperf\Contract\ConfigInterface;
+use Hyperf\Pimple\ContainerFactory;
+use App\Provider\TranslatorProvider;
+use App\Provider\TranslatorLoaderProvider;
+
+class EasySwooleEvent implements Event
+{
+    public static function initialize()
+    {
+        date_default_timezone_set('Asia/Shanghai');
+        $container = (new ContainerFactory([
+            TranslatorProvider::class,
+            TranslatorLoaderProvider::class,
+        ]))();
+        $container->set(ConfigInterface::class, new Config([
+            'translation' => [
+                'locale' => 'zh_CN',
+                'fallback_locale' => 'en',
+                'path' => EASYSWOOLE_ROOT . '/storage/languages',
+            ],
+        ]));
+    }
+}
+```
+
+4. 修改控制器,使用国际化组件
+
+```php
+<?php
+
+declare(strict_types=1);
+
+namespace App\HttpController;
+
+use EasySwoole\Http\AbstractInterface\Controller;
+use Hyperf\Contract\TranslatorInterface;
+use Hyperf\Utils\ApplicationContext;
+use Hyperf\Utils\Codec\Json;
+
+class Index extends Controller
+{
+    public function index()
+    {
+        $container = ApplicationContext::getContainer();
+        $translator = $container->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"}
+```
+
+
+

+ 53 - 0
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"
+        }
+    }
+}

+ 15 - 0
addons/epay/library/hyperf/pimple/phpunit.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/bootstrap.php"
+         backupGlobals="false"
+         backupStaticAttributes="false"
+         verbose="true"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false">
+    <testsuite name="Testsuite">
+        <directory>./tests/</directory>
+    </testsuite>
+</phpunit>

+ 32 - 0
addons/epay/library/hyperf/pimple/src/ConfigProvider.php

@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Hyperf\Pimple;
+
+class ConfigProvider
+{
+    public function __invoke(): array
+    {
+        return [
+            'dependencies' => [
+            ],
+            'commands' => [
+            ],
+            'annotations' => [
+                'scan' => [
+                    'paths' => [
+                        __DIR__,
+                    ],
+                ],
+            ],
+        ];
+    }
+}

Some files were not shown because too many files changed in this diff