diff --git a/.gitignore b/.gitignore index 6785f82..08ac1df 100755 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,7 @@ /thinkphp/ /vendor/ /runtime/* -/addons/* /public/assets/libs/ -/public/assets/addons/* /public/uploads/* .DS_Store .idea diff --git a/addons/.htaccess b/addons/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/addons/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/addons/epay/.addonrc b/addons/epay/.addonrc new file mode 100644 index 0000000..87dd713 --- /dev/null +++ b/addons/epay/.addonrc @@ -0,0 +1 @@ +{"files":["application\/admin\/controller\/Epay.php","public\/assets\/addons\/epay\/js\/common.js","public\/assets\/addons\/epay\/js\/jquery.qrcode.min.js","public\/assets\/addons\/epay\/less\/common.less","public\/assets\/addons\/epay\/images\/wechat.png","public\/assets\/addons\/epay\/images\/alipay.png","public\/assets\/addons\/epay\/images\/logo-wechat.png","public\/assets\/addons\/epay\/images\/screenshot-wechat.png","public\/assets\/addons\/epay\/images\/paid.png","public\/assets\/addons\/epay\/images\/expired.png","public\/assets\/addons\/epay\/images\/scan.png","public\/assets\/addons\/epay\/images\/logo-alipay.png","public\/assets\/addons\/epay\/images\/screenshot-alipay.png","public\/assets\/addons\/epay\/css\/common.css"],"license":"regular","licenseto":"26425","licensekey":"W8TNHEKYuUG2P0jJ nFnAiAd3bF2UiAjXnZAWNw==","domains":["gynhc.com"],"licensecodes":[],"validations":["baca5b0e114fc9a55d47e7adb2332062"]} \ No newline at end of file diff --git a/addons/epay/Epay.php b/addons/epay/Epay.php new file mode 100644 index 0000000..952b5bd --- /dev/null +++ b/addons/epay/Epay.php @@ -0,0 +1,112 @@ +actionBegin(); + } + + // 插件方法加载开始 + public function addonActionBegin() + { + $this->actionBegin(); + } + + // 模块控制器方法加载开始 + public function actionBegin() + { + //添加命名空间 + if (!class_exists('\Yansongda\Pay\Pay')) { + + //SDK版本 + $version = Service::getSdkVersion(); + + $libraryDir = ADDON_PATH . 'epay' . DS . 'library' . DS; + + if (PHP_VERSION_ID >= 80000 && $version == Service::SDK_VERSION_V3) { + $yansongdaDir = $libraryDir . $version . 'new' . DS . 'Yansongda' . DS; + + // PHP 8.0+ 版本且为V3接口,需要加载 Artful 的命名空间 + Loader::addNamespace('Yansongda\Artful', $yansongdaDir . 'Artful' . DS); + require_once $yansongdaDir . 'Artful' . DS . 'Functions.php'; + } else { + $yansongdaDir = $libraryDir . $version . DS . 'Yansongda' . DS; + } + + // 注册Pay命名空间 + Loader::addNamespace('Yansongda\Pay', $yansongdaDir . 'Pay' . DS); + + $checkArr = [ + '\Hyperf\Context\Context' => 'context', + '\Hyperf\Contract\Castable' => 'contract', + '\Hyperf\Engine\Constant' => 'engine', + '\Hyperf\Macroable\Macroable' => 'macroable', + '\Hyperf\Pimple\Container' => 'pimple', + '\Hyperf\Utils\Arr' => 'utils', + ]; + foreach ($checkArr as $index => $item) { + if (!class_exists($index)) { + Loader::addNamespace(substr($index, 1, strrpos($index, '\\') - 1), $libraryDir . 'hyperf' . DS . $item . DS . 'src' . DS); + } + } + + if (!class_exists('\Yansongda\Supports\Logger')) { + Loader::addNamespace('Yansongda\Supports', $yansongdaDir . 'Supports' . DS); + } + + // V3需载入辅助函数 + if ($version == Service::SDK_VERSION_V3) { + require_once $yansongdaDir . 'Pay' . DS . 'Functions.php'; + } + } + } +} diff --git a/addons/epay/certs/alipayCertPublicKey.crt b/addons/epay/certs/alipayCertPublicKey.crt new file mode 100644 index 0000000..e69de29 diff --git a/addons/epay/certs/alipayRootCert.crt b/addons/epay/certs/alipayRootCert.crt new file mode 100644 index 0000000..e69de29 diff --git a/addons/epay/certs/apiclient_cert.pem b/addons/epay/certs/apiclient_cert.pem new file mode 100644 index 0000000..e69de29 diff --git a/addons/epay/certs/apiclient_key.pem b/addons/epay/certs/apiclient_key.pem new file mode 100644 index 0000000..e69de29 diff --git a/addons/epay/certs/appCertPublicKey.crt b/addons/epay/certs/appCertPublicKey.crt new file mode 100644 index 0000000..e69de29 diff --git a/addons/epay/certs/public_key.pem b/addons/epay/certs/public_key.pem new file mode 100644 index 0000000..e69de29 diff --git a/addons/epay/config.html b/addons/epay/config.html new file mode 100644 index 0000000..325f89b --- /dev/null +++ b/addons/epay/config.html @@ -0,0 +1,529 @@ +
+ diff --git a/addons/epay/config.php b/addons/epay/config.php new file mode 100644 index 0000000..92607f2 --- /dev/null +++ b/addons/epay/config.php @@ -0,0 +1,72 @@ + 'version', + 'title' => 'API版本(请勿修改该值)', + 'type' => 'radio', + 'content' => [], + 'value' => 'v2', + 'rule' => '', + 'msg' => '', + 'tip' => 'V2版本只支持微信支付V2密钥,V3版本只支持微信支付V3密钥,请勿修改该值!!!', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'wechat', + 'title' => '微信', + 'type' => 'array', + 'content' => [], + 'value' => [ + 'appid' => '', + 'app_id' => '', + 'app_secret' => '', + 'miniapp_id' => '', + 'mch_id' => '', + 'key' => '', + 'key_v3' => '', + 'public_key_id' => '', + 'public_key' => '/addons/epay/certs/public_key.pem', + 'cert_client' => '/addons/epay/certs/apiclient_cert.pem', + 'cert_key' => '/addons/epay/certs/apiclient_key.pem', + 'mode' => 'normal', + 'sub_mch_id' => '', + 'sub_appid' => '', + 'sub_app_id' => '', + 'sub_miniapp_id' => '', + 'notify_url' => '', + 'log' => '1', + ], + 'rule' => 'required', + 'msg' => '', + 'tip' => '微信参数配置', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'alipay', + 'title' => '支付宝', + 'type' => 'array', + 'content' => [], + 'value' => [ + 'app_id' => '', + 'mode' => 'normal', + 'notify_url' => '/addons/epay/api/notifyx/type/alipay', + 'return_url' => '/addons/epay/api/returnx/type/alipay', + 'private_key' => '', + 'signtype' => 'cert', + 'pid' => '', + 'ali_public_key' => '', + 'app_cert_public_key' => '', + 'alipay_root_cert' => '', + 'log' => '1', + 'scanpay' => '0', + ], + 'rule' => 'required', + 'msg' => '', + 'tip' => '支付宝参数配置', + 'ok' => '', + 'extend' => '', + ] +]; diff --git a/addons/epay/controller/Api.php b/addons/epay/controller/Api.php new file mode 100644 index 0000000..27340e4 --- /dev/null +++ b/addons/epay/controller/Api.php @@ -0,0 +1,234 @@ +request->filter('trim'); + $out_trade_no = $this->request->request("out_trade_no"); + $title = $this->request->request("title"); + $amount = $this->request->request('amount'); + $type = $this->request->request('type', $this->request->request('paytype')); + $method = $this->request->request('method', 'web'); + $openid = $this->request->request('openid', ''); + $auth_code = $this->request->request('auth_code', ''); + $notifyurl = $this->request->request('notifyurl', ''); + $returnurl = $this->request->request('returnurl', ''); + + if (!$amount || $amount < 0) { + $this->error("支付金额必须大于0"); + } + + if (!$type || !in_array($type, ['alipay', 'wechat'])) { + $this->error("支付类型错误"); + } + + $params = [ + 'type' => $type, + 'out_trade_no' => $out_trade_no, + 'title' => $title, + 'amount' => $amount, + 'method' => $method, + 'openid' => $openid, + 'auth_code' => $auth_code, + 'notifyurl' => $notifyurl, + 'returnurl' => $returnurl, + ]; + return Service::submitOrder($params); + } + + /** + * 微信支付(公众号支付&PC扫码支付) + */ + public function wechat() + { + $config = Service::getConfig('wechat'); + + $isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false; + $isMobile = $this->request->isMobile(); + $this->view->assign("isWechat", $isWechat); + $this->view->assign("isMobile", $isMobile); + + //发起PC支付(Scan支付)(PC扫码模式) + if ($this->request->isAjax()) { + $pay = Pay::wechat($config); + $orderid = $this->request->post("orderid"); + try { + $result = Service::isVersionV3() ? $pay->find(['out_trade_no' => $orderid]) : $pay->find($orderid, 'scan'); + $this->success("", "", ['status' => $result['trade_state'] ?? 'NOTPAY']); + } catch (GatewayException $e) { + $this->error("查询失败(1001)"); + } + } + + $orderData = Session::get("wechatorderdata"); + if (!$orderData) { + $this->error("请求参数错误"); + } + if ($isWechat && $isMobile) { + //发起公众号(jsapi支付),openid必须 + + //如果没有openid,则自动去获取openid + if (!isset($orderData['openid']) || !$orderData['openid']) { + $orderData['openid'] = Service::getOpenid(); + } + + $orderData['method'] = 'mp'; + $type = 'jsapi'; + $payData = Service::submitOrder($orderData); + if (!isset($payData['paySign'])) { + $this->error("创建订单失败,请返回重试", ""); + } + } else { + $orderData['method'] = 'scan'; + $type = 'pc'; + $payData = Service::submitOrder($orderData); + if (!isset($payData['code_url'])) { + $this->error("创建订单失败,请返回重试", ""); + } + } + $this->view->assign("orderData", $orderData); + $this->view->assign("payData", $payData); + $this->view->assign("type", $type); + + $this->view->assign("title", "微信支付"); + return $this->view->fetch(); + } + + /** + * 支付宝支付(PC扫码支付) + */ + public function alipay() + { + $config = Service::getConfig('alipay'); + + $isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false; + $isMobile = $this->request->isMobile(); + $this->view->assign("isWechat", $isWechat); + $this->view->assign("isMobile", $isMobile); + + if ($this->request->isAjax()) { + $orderid = $this->request->post("orderid"); + $pay = Pay::alipay($config); + try { + $result = $pay->find(['out_trade_no' => $orderid]); + if ($result['code'] == '10000' && $result['trade_status'] == 'TRADE_SUCCESS') { + $this->success("", "", ['status' => $result['trade_status']]); + } else { + $this->error("查询失败"); + } + } catch (GatewayException $e) { + $this->error("查询失败(1001)"); + } + } + + //发起PC支付(Scan支付)(PC扫码模式) + $orderData = Session::get("alipayorderdata"); + if (!$orderData) { + $this->error("请求参数错误"); + } + + $orderData['method'] = 'scan'; + $payData = Service::submitOrder($orderData); + if (!isset($payData['qr_code'])) { + $this->error("创建订单失败,请返回重试"); + } + + $type = 'pc'; + $this->view->assign("orderData", $orderData); + $this->view->assign("payData", $payData); + $this->view->assign("type", $type); + $this->view->assign("title", "支付宝支付"); + return $this->view->fetch(); + } + + /** + * 支付成功回调 + */ + public function notifyx() + { + $paytype = $this->request->param('paytype'); + $pay = Service::checkNotify($paytype); + if (!$pay) { + return json(['code' => 'FAIL', 'message' => '失败'], 500, ['Content-Type' => 'application/json']); + } + + // 获取回调数据,V3和V2的回调接收不同 + $data = Service::isVersionV3() ? $pay->callback() : $pay->verify(); + + try { + //微信支付V3返回和V2不同 + if (Service::isVersionV3() && $paytype === 'wechat') { + $data = $data['resource']['ciphertext']; + $data['total_fee'] = $data['amount']['total']; + } + + \think\Log::record($data); + //获取支付金额、订单号 + $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100; + $out_trade_no = $data['out_trade_no']; + + \think\Log::record("回调成功,订单号:{$out_trade_no},金额:{$payamount}"); + + //你可以在此编写订单逻辑 + } catch (Exception $e) { + \think\Log::record("回调逻辑处理错误:" . $e->getMessage(), "error"); + } + + //下面这句必须要执行,且在此之前不能有任何输出 + if (Service::isVersionV3()) { + return $pay->success()->getBody()->getContents(); + } else { + return $pay->success()->send(); + } + } + + /** + * 支付成功返回 + */ + public function returnx() + { + $paytype = $this->request->param('paytype'); + if (Service::checkReturn($paytype)) { + echo '签名错误'; + return; + } + + //你可以在这里定义你的提示信息,但切记不可在此编写逻辑 + $this->success("恭喜你!支付成功!", addon_url("epay/index/index")); + } + +} diff --git a/addons/epay/controller/Index.php b/addons/epay/controller/Index.php new file mode 100644 index 0000000..ea5a25a --- /dev/null +++ b/addons/epay/controller/Index.php @@ -0,0 +1,131 @@ +error("仅在开发环境下查看"); + } + } + + public function index() + { + $this->view->assign("title", "微信支付宝整合"); + return $this->view->fetch(); + } + + /** + * 体验,仅供开发测试 + */ + public function experience() + { + $amount = $this->request->post('amount'); + $type = $this->request->post('type'); + $method = $this->request->post('method'); + $openid = $this->request->post('openid', ""); + + if (!$amount || $amount < 0) { + $this->error("支付金额必须大于0"); + } + + if (!$type || !in_array($type, ['alipay', 'wechat'])) { + $this->error("支付类型不能为空"); + } + + if (in_array($method, ['miniapp', 'mp']) && !$openid) { + $this->error("openid不能为空"); + } + + //订单号 + $out_trade_no = date("YmdHis") . mt_rand(100000, 999999); + + //订单标题 + $title = '测试订单'; + + //回调链接 + $notifyurl = $this->request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type; + $returnurl = $this->request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $out_trade_no; + + $response = Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method, $openid); + + return $response; + } + + /** + * 支付成功,仅供开发测试 + */ + public function notifyx() + { + $paytype = $this->request->param('paytype'); + $pay = Service::checkNotify($paytype); + if (!$pay) { + return json(['code' => 'FAIL', 'message' => '失败'], 500, ['Content-Type' => 'application/json']); + } + + // 获取回调数据,V3和V2的回调接收不同 + $data = Service::isVersionV3() ? $pay->callback() : $pay->verify(); + + try { + //微信支付V3返回和V2不同 + if (Service::isVersionV3() && $paytype === 'wechat') { + $data = $data['resource']['ciphertext']; + $data['total_fee'] = $data['amount']['total']; + } + + \think\Log::record($data); + //获取支付金额、订单号 + $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100; + $out_trade_no = $data['out_trade_no']; + + \think\Log::record("回调成功,订单号:{$out_trade_no},金额:{$payamount}"); + + //你可以在此编写订单逻辑 + } catch (Exception $e) { + \think\Log::record("回调逻辑处理错误:" . $e->getMessage(), "error"); + } + + //下面这句必须要执行,且在此之前不能有任何输出 + if (Service::isVersionV3()) { + return $pay->success()->getBody()->getContents(); + } else { + return $pay->success()->send(); + } + } + + /** + * 支付返回,仅供开发测试 + */ + public function returnx() + { + $paytype = $this->request->param('paytype'); + $out_trade_no = $this->request->param('out_trade_no'); + $pay = Service::checkReturn($paytype); + if (!$pay) { + $this->error('签名错误', ''); + } + + //你可以在这里定义你的提示信息,但切记不可在此编写逻辑 + $this->success("请返回网站查看支付结果", addon_url("epay/index/index")); + } +} diff --git a/addons/epay/info.ini b/addons/epay/info.ini new file mode 100644 index 0000000..c130145 --- /dev/null +++ b/addons/epay/info.ini @@ -0,0 +1,10 @@ +name = epay +title = 微信支付宝整合 +intro = 可用于快速整合微信支付、支付宝支付功能 +author = FastAdmin +website = +version = 1.3.15 +state = 1 +url = /addons/epay +license = regular +licenseto = 26425 diff --git a/addons/epay/library/Collection.php b/addons/epay/library/Collection.php new file mode 100644 index 0000000..8b5743e --- /dev/null +++ b/addons/epay/library/Collection.php @@ -0,0 +1,18 @@ +message = $message; + $this->code = $code; + $this->data = $data; + } + +} diff --git a/addons/epay/library/RedirectResponse.php b/addons/epay/library/RedirectResponse.php new file mode 100644 index 0000000..aa91e46 --- /dev/null +++ b/addons/epay/library/RedirectResponse.php @@ -0,0 +1,63 @@ +getContent(); + } + + public function setTargetUrl($url) + { + if ('' === ($url ?? '')) { + throw new \InvalidArgumentException('无法跳转到空页面'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + +
+ * The return value is cast to an integer
+ */
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Constructs the object.
+ *
+ * @see http://php.net/manual/en/serializable.unserialize.php
+ *
+ * @param string $data
+ * The string representation of the object. + *
+ * + * @return mixed|void + */ + public function unserialize($data) + { + return $this->items = unserialize($data); + } + + /** + * (PHP 5 >= 5.0.0)+ * An offset to check for. + *
+ * + * @return bool true on success or false on failure. + * The return value will be casted to boolean if non-boolean was returned + */ + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + /** + * (PHP 5 >= 5.0.0)+ * The offset to unset. + *
+ */ + public function offsetUnset($offset): void + { + if ($this->offsetExists($offset)) { + $this->forget($offset); + } + } + + /** + * (PHP 5 >= 5.0.0)+ * The offset to retrieve. + *
+ * + * @return mixed Can return all value types + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->get($offset) : null; + } + + /** + * (PHP 5 >= 5.0.0)+ * The offset to assign the value to. + *
+ * @param mixed $value+ * The value to set. + *
+ */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + /** + * (PHP 8.1+) Magic method for serialization. + * + * @return array + */ + public function __serialize(): array + { + return $this->items; + } + + /** + * (PHP 8.1+) Magic method for unserialization. + * + * @param array $data + */ + public function __unserialize(array $data): void + { + $this->items = $data; + } +} diff --git a/addons/epay/library/v2/Yansongda/Supports/Config.php b/addons/epay/library/v2/Yansongda/Supports/Config.php new file mode 100644 index 0000000..a660bad --- /dev/null +++ b/addons/epay/library/v2/Yansongda/Supports/Config.php @@ -0,0 +1,7 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/addons/epay/library/v2/Yansongda/Supports/Log.php b/addons/epay/library/v2/Yansongda/Supports/Log.php new file mode 100644 index 0000000..4901a2d --- /dev/null +++ b/addons/epay/library/v2/Yansongda/Supports/Log.php @@ -0,0 +1,91 @@ + + * + * @param string $method + * @param array $args + * + * @throws \Exception + */ + public function __call($method, $args): void + { + call_user_func_array([self::getInstance(), $method], $args); + } + + /** + * __callStatic. + * + * @author yansongda