lizhen_gitee 1 rok pred
commit
05bbdac6f2
100 zmenil súbory, kde vykonal 8594 pridanie a 0 odobranie
  1. BIN
      .DS_Store
  2. 10 0
      .bowerrc
  3. 11 0
      .env.sample
  4. 1 0
      .htaccess
  5. 26 0
      404.html
  6. 191 0
      LICENSE
  7. 92 0
      README.md
  8. 65 0
      addons/epay/Epay.php
  9. 91 0
      addons/epay/assets/css/common.css
  10. 20 0
      addons/epay/assets/css/epay.css
  11. 100 0
      addons/epay/assets/css/wechat.css
  12. BIN
      addons/epay/assets/images/alipay.png
  13. BIN
      addons/epay/assets/images/expired.png
  14. BIN
      addons/epay/assets/images/logo-alipay.png
  15. BIN
      addons/epay/assets/images/logo-wechat.png
  16. BIN
      addons/epay/assets/images/logo.png
  17. BIN
      addons/epay/assets/images/paid.png
  18. BIN
      addons/epay/assets/images/scan.png
  19. BIN
      addons/epay/assets/images/tips.png
  20. BIN
      addons/epay/assets/images/wechat.png
  21. 52 0
      addons/epay/assets/js/common.js
  22. 113 0
      addons/epay/assets/less/common.less
  23. 28 0
      addons/epay/assets/less/epay.less
  24. 0 0
      addons/epay/certs/apiclient_cert.pem
  25. 0 0
      addons/epay/certs/apiclient_key.pem
  26. 69 0
      addons/epay/config.php
  27. 229 0
      addons/epay/controller/Api.php
  28. 108 0
      addons/epay/controller/Index.php
  29. 8 0
      addons/epay/info.ini
  30. 17 0
      addons/epay/library/OrderException.php
  31. 267 0
      addons/epay/library/Service.php
  32. 107 0
      addons/epay/library/Wechat.php
  33. 63 0
      addons/epay/library/Yansongda/Pay/Contracts/GatewayInterface.php
  34. 7 0
      addons/epay/library/Yansongda/Pay/Exceptions/Exception.php
  35. 28 0
      addons/epay/library/Yansongda/Pay/Exceptions/GatewayException.php
  36. 7 0
      addons/epay/library/Yansongda/Pay/Exceptions/InvalidArgumentException.php
  37. 291 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php
  38. 46 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/AppGateway.php
  39. 47 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/PosGateway.php
  40. 44 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/ScanGateway.php
  41. 44 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/TransferGateway.php
  42. 48 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/WapGateway.php
  43. 46 0
      addons/epay/library/Yansongda/Pay/Gateways/Alipay/WebGateway.php
  44. 50 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/AppGateway.php
  45. 82 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/GroupredpackGateway.php
  46. 49 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php
  47. 47 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/MpGateway.php
  48. 46 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/PosGateway.php
  49. 86 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php
  50. 38 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/ScanGateway.php
  51. 78 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/TransferGateway.php
  52. 41 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/WapGateway.php
  53. 69 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/WebGateway.php
  54. 354 0
      addons/epay/library/Yansongda/Pay/Gateways/Wechat/Wechat.php
  55. 134 0
      addons/epay/library/Yansongda/Pay/Pay.php
  56. 147 0
      addons/epay/library/Yansongda/Pay/Support/Config.php
  57. 119 0
      addons/epay/library/Yansongda/Pay/Traits/HasHttpRequest.php
  58. 86 0
      addons/epay/view/api/wechat.html
  59. 212 0
      addons/epay/view/index/index.html
  60. 115 0
      addons/epay/view/layout/default.html
  61. 750 0
      addons/unishop/Unishop.php
  62. 60 0
      addons/unishop/application/admin/controller/unishop/Ads.php
  63. 146 0
      addons/unishop/application/admin/controller/unishop/Category.php
  64. 166 0
      addons/unishop/application/admin/controller/unishop/Config.php
  65. 179 0
      addons/unishop/application/admin/controller/unishop/Delivery.php
  66. 115 0
      addons/unishop/application/admin/controller/unishop/Evaluate.php
  67. 509 0
      addons/unishop/application/admin/controller/unishop/Order.php
  68. 303 0
      addons/unishop/application/admin/controller/unishop/Product.php
  69. 50 0
      addons/unishop/application/admin/controller/unishop/market/Coupon.php
  70. 87 0
      addons/unishop/application/admin/controller/unishop/market/FlashProduct.php
  71. 503 0
      addons/unishop/application/admin/controller/unishop/market/FlashSale.php
  72. 13 0
      addons/unishop/application/admin/lang/zh-cn/unishop/ads.php
  73. 19 0
      addons/unishop/application/admin/lang/zh-cn/unishop/category.php
  74. 62 0
      addons/unishop/application/admin/lang/zh-cn/unishop/config.php
  75. 19 0
      addons/unishop/application/admin/lang/zh-cn/unishop/delivery.php
  76. 24 0
      addons/unishop/application/admin/lang/zh-cn/unishop/evaluate.php
  77. 14 0
      addons/unishop/application/admin/lang/zh-cn/unishop/market/coupon.php
  78. 35 0
      addons/unishop/application/admin/lang/zh-cn/unishop/market/flash_sale.php
  79. 57 0
      addons/unishop/application/admin/lang/zh-cn/unishop/order.php
  80. 37 0
      addons/unishop/application/admin/lang/zh-cn/unishop/product.php
  81. 49 0
      addons/unishop/application/admin/model/unishop/Ads.php
  82. 116 0
      addons/unishop/application/admin/model/unishop/Area.php
  83. 94 0
      addons/unishop/application/admin/model/unishop/Category.php
  84. 169 0
      addons/unishop/application/admin/model/unishop/Config.php
  85. 72 0
      addons/unishop/application/admin/model/unishop/Coupon.php
  86. 162 0
      addons/unishop/application/admin/model/unishop/Delivery.php
  87. 69 0
      addons/unishop/application/admin/model/unishop/DeliveryRule.php
  88. 78 0
      addons/unishop/application/admin/model/unishop/Evaluate.php
  89. 46 0
      addons/unishop/application/admin/model/unishop/FlashProduct.php
  90. 163 0
      addons/unishop/application/admin/model/unishop/FlashSale.php
  91. 128 0
      addons/unishop/application/admin/model/unishop/Order.php
  92. 31 0
      addons/unishop/application/admin/model/unishop/OrderExtend.php
  93. 22 0
      addons/unishop/application/admin/model/unishop/OrderProduct.php
  94. 30 0
      addons/unishop/application/admin/model/unishop/OrderRefund.php
  95. 19 0
      addons/unishop/application/admin/model/unishop/OrderRefundProduct.php
  96. 58 0
      addons/unishop/application/admin/model/unishop/Product.php
  97. 27 0
      addons/unishop/application/admin/model/unishop/User.php
  98. 27 0
      addons/unishop/application/admin/validate/unishop/Ads.php
  99. 27 0
      addons/unishop/application/admin/validate/unishop/Config.php
  100. 30 0
      addons/unishop/application/admin/validate/unishop/Coupon.php

BIN
.DS_Store


+ 10 - 0
.bowerrc

@@ -0,0 +1,10 @@
+{
+  "directory": "public/assets/libs",
+  "ignoredDependencies": [
+    "es6-promise",
+    "file-saver",
+    "html2canvas",
+    "jspdf",
+    "jspdf-autotable"
+  ]
+}

+ 11 - 0
.env.sample

@@ -0,0 +1,11 @@
+[app]
+debug = false
+trace = false
+
+[database]
+hostname = 127.0.0.1
+database = fastadmin
+username = root
+password = root
+hostport = 3306
+prefix = fa_

+ 1 - 0
.htaccess

@@ -0,0 +1 @@
+ 

+ 26 - 0
404.html

@@ -0,0 +1,26 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+<title>404</title>
+<style>
+	body{
+		background-color:#444;
+		font-size:14px;
+	}
+	h3{
+		font-size:60px;
+		color:#eee;
+		text-align:center;
+		padding-top:30px;
+		font-weight:normal;
+	}
+</style>
+</head>
+
+<body>
+<h3>404,您请求的文件不存在!</h3>
+</body>
+</html>

+ 191 - 0
LICENSE

@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "{}" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2017 Karson
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 92 - 0
README.md

@@ -0,0 +1,92 @@
+FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
+
+
+## 主要特性
+
+* 基于`Auth`验证的权限管理系统
+    * 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
+    * 支持单管理员多角色
+    * 支持管理子级数据或个人数据
+* 强大的一键生成功能
+    * 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
+    * 一键压缩打包JS和CSS文件,一键CDN静态资源部署
+    * 一键生成控制器菜单和规则
+    * 一键生成API接口文档
+* 完善的前端功能组件开发
+    * 基于`AdminLTE`二次开发
+    * 基于`Bootstrap`开发,自适应手机、平板、PC
+    * 基于`RequireJS`进行JS模块管理,按需加载
+    * 基于`Less`进行样式开发
+    * 基于`Bower`进行前端组件包管理
+* 强大的插件扩展功能,在线安装卸载升级插件
+* 通用的会员模块和API模块
+* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
+* 二级域名部署支持,同时域名支持绑定到插件
+* 多语言支持,服务端及客户端支持
+* 强大的第三方模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/ask.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html))
+* 支持CMS、博客、知识付费问答无缝整合[Xunsearch全文搜索](https://www.fastadmin.net/store/xunsearch.html)
+* 第三方小程序支持([预订小程序](https://www.fastadmin.net/store/ball.html)、[问答小程序](https://www.fastadmin.net/store/questions.html)、[活动报名小程序](https://www.fastadmin.net/store/huodong.html)、[商城小程序](https://www.fastadmin.net/store/xshop.html)、[博客小程序](https://www.fastadmin.net/store/blog.html))
+* 整合第三方短信接口(阿里云、腾讯云短信)
+* 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能
+* 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器)
+* 第三方登录(QQ、微信、微博)整合
+* 第三方支付(微信、支付宝)无缝整合,微信支持PC端扫码支付
+* 丰富的插件应用市场
+
+## 安装使用
+
+https://doc.fastadmin.net
+
+## 在线演示
+
+https://demo.fastadmin.net
+
+用户名:admin
+
+密 码:123456
+
+提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
+
+## 界面截图
+![控制台](https://gitee.com/uploads/images/2017/0411/113717_e99ff3e7_10933.png "控制台")
+
+## 问题反馈
+
+在使用中有任何问题,请使用以下联系方式联系我们
+
+交流社区: https://ask.fastadmin.net
+
+QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb)(满) [708784003](https://jq.qq.com/?_wv=1027&k=5ObjtwM)(满) [964776039](https://jq.qq.com/?_wv=1027&k=59qjU2P)(3群) [749803490](https://jq.qq.com/?_wv=1027&k=5tczi88)(满) [767103006](https://jq.qq.com/?_wv=1027&k=5Z1U751)(满) [675115483](https://jq.qq.com/?_wv=1027&k=54I6mts)(6群)
+
+Github: https://github.com/karsonzhang/fastadmin
+
+Gitee: https://gitee.com/karson/fastadmin
+
+## 特别鸣谢
+
+感谢以下的项目,排名不分先后
+
+ThinkPHP:http://www.thinkphp.cn
+
+AdminLTE:https://adminlte.io
+
+Bootstrap:http://getbootstrap.com
+
+jQuery:http://jquery.com
+
+Bootstrap-table:https://github.com/wenzhixin/bootstrap-table
+
+Nice-validator: https://validator.niceue.com
+
+SelectPage: https://github.com/TerryZ/SelectPage
+
+
+## 版权信息
+
+FastAdmin遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2017-2020 by FastAdmin (https://www.fastadmin.net)
+
+All rights reserved。

+ 65 - 0
addons/epay/Epay.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace addons\epay;
+
+use app\common\library\Menu;
+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 appInit()
+    {
+        //添加支付包的命名空间
+        Loader::addNamespace('Yansongda', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS);
+    }
+
+}

+ 91 - 0
addons/epay/assets/css/common.css

@@ -0,0 +1,91 @@
+/*!
+ * Start Bootstrap - Modern Business (http://startbootstrap.com/)
+ * Copyright 2013-2016 Start Bootstrap
+ * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
+ */
+/* Global Styles */
+html,
+body {
+  height: 100%;
+}
+body {
+  padding-top: 50px;
+  /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+.img-addon {
+  margin-bottom: 10px;
+  width: 100%;
+}
+.img-hover:hover {
+  opacity: 0.8;
+}
+.display-1 {
+  font-size: 44px;
+}
+.display-4 {
+  font-size: 24px;
+  line-height: 32px;
+}
+/* Home Page Carousel */
+header.carousel {
+  height: 50%;
+}
+header.carousel .item,
+header.carousel .item.active,
+header.carousel .carousel-inner {
+  height: 100%;
+}
+header.carousel .fill {
+  width: 100%;
+  height: 100%;
+}
+.error-404 {
+  font-size: 100px;
+}
+/* Pricing Page Styles */
+.price {
+  display: block;
+  font-size: 50px;
+  line-height: 50px;
+}
+.price sup {
+  top: -20px;
+  left: 2px;
+  font-size: 20px;
+}
+.period {
+  display: block;
+  font-style: italic;
+}
+/* Footer Styles */
+footer {
+  margin: 50px 0;
+}
+/* Responsive Styles */
+@media (max-width: 991px) {
+  .customer-img,
+  .img-related {
+    margin-bottom: 30px;
+  }
+}
+@media (max-width: 767px) {
+  .img-addon {
+    margin-bottom: 15px;
+  }
+  header.carousel .carousel {
+    height: 70%;
+  }
+}
+.carousel-body {
+  position: absolute;
+  width: 100%;
+  top: 25%;
+  text-align: center;
+  color: #fff;
+}
+.addonlist a > p {
+  margin-bottom: 15px;
+}

+ 20 - 0
addons/epay/assets/css/epay.css

@@ -0,0 +1,20 @@
+@import url("../../../css/bootstrap.min.css");
+@import url("../../../libs/font-awesome/css/font-awesome.min.css");
+html,
+body {
+  height: 100%;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  font-weight: 400;
+  overflow-x: hidden;
+  overflow-y: auto;
+  background: #f4f6f8;
+  font-size: 14px;
+  color: #616161;
+}
+.container {
+  max-width: 850px;
+  margin: 0 auto;
+  padding: 50px;
+}

+ 100 - 0
addons/epay/assets/css/wechat.css

@@ -0,0 +1,100 @@
+.wechat {
+    margin-top: 30px;
+}
+
+.wechat h2 {
+    margin: 0 0 15px 0;
+    padding-bottom: 15px;
+    border-bottom: 1px solid #eee;
+    position: relative;
+}
+
+.wechat-body {
+}
+
+.wechat-qrcode {
+    margin-bottom: 20px;
+    position: relative;
+}
+
+.wechat-qrcode img {
+    width: 100%;
+    border: 1px solid #eee;
+}
+
+.wechat-qrcode .expired {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    opacity: .95;
+    background: #fff url(../images/expired.png) center center no-repeat;
+}
+
+.wechat-qrcode .paid {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    opacity: .95;
+    background: #fff url(../images/paid.png) center center no-repeat;
+}
+
+.wechat-scan {
+    padding: 0;
+}
+
+.wechat-scan img {
+    width: 100%;
+}
+
+.wechat-tips {
+    height: 60px;
+    padding: 8px 0 8px 125px;
+    background: #00c800 url(../images/scan.png) 50px 12px no-repeat;
+    background-size: 36px 36px;
+}
+
+.wechat-tips p {
+    margin: 0;
+    font-size: 14px;
+    line-height: 22px;
+    color: #fff;
+    font-weight: 700
+}
+
+.wechat-time {
+    font-size: 14px;
+    margin-bottom: 15px;
+    position: absolute;
+    top: 15px;
+    right: 10px;
+    font-weight: normal;
+    display: none;
+}
+
+.wechat-time span {
+    color: red;
+}
+
+.wechat-order {
+    margin-bottom: 5px;
+}
+
+.wechat-order em {
+    font-style: normal;
+    color: #666;
+}
+
+.wechat-order em.wechat-price {
+    color: #ff3333;
+    font-weight: bold;
+}
+
+@media (max-width: 767px) {
+    .wechat {
+        margin-top: 20px;
+    }
+}

BIN
addons/epay/assets/images/alipay.png


BIN
addons/epay/assets/images/expired.png


BIN
addons/epay/assets/images/logo-alipay.png


BIN
addons/epay/assets/images/logo-wechat.png


BIN
addons/epay/assets/images/logo.png


BIN
addons/epay/assets/images/paid.png


BIN
addons/epay/assets/images/scan.png


BIN
addons/epay/assets/images/tips.png


BIN
addons/epay/assets/images/wechat.png


+ 52 - 0
addons/epay/assets/js/common.js

@@ -0,0 +1,52 @@
+$(function () {
+    $('.carousel').carousel({
+        interval: 5000 //changes the speed
+    });
+    $(".btn-experience").on("click", function () {
+        location.href = "/addons/epay/index/experience?amount=" + $("input[name=amount]").val() + "&type=" + $(this).data("type") + "&method=" + $("#method").val();
+    });
+
+    var si, xhr;
+    if (typeof queryParams != 'undefined') {
+        var queryResult = function () {
+            xhr && xhr.abort();
+            xhr = $.ajax({
+                url: "",
+                type: "post",
+                data: queryParams,
+                dataType: 'json',
+                success: function (ret) {
+                    if (ret.code == 1) {
+                        var data = ret.data;
+                        console.log(data);
+                        if (typeof data.trade_state != 'undefined') {
+                            if (data.trade_state == 'SUCCESS') {
+                                $(".wechat-qrcode .paid").removeClass("hidden");
+                                $(".wechat-tips p").html("支付成功!<br>3秒后将自动跳转...");
+                                setTimeout(function () {
+                                    location.href = queryParams.return_url;
+                                }, 3000);
+                                clearInterval(si);
+                            } else if (data.trade_state == 'REFUND') {
+                                $(".wechat-tips p").html("请求失败!<br>请返回重新发起支付");
+                                clearInterval(si);
+                            } else if (data.trade_state == 'NOTPAY') {
+                            } else if (data.trade_state == 'CLOSED') {
+                                $(".wechat-tips p").html("订单已关闭!<br>请返回重新发起支付");
+                                clearInterval(si);
+                            } else if (data.trade_state == 'USERPAYING') {
+                            } else if (data.trade_state == 'PAYERROR') {
+                                clearInterval(si);
+                            }
+                        }
+                    }
+                }
+            });
+        };
+        si = setInterval(function () {
+            queryResult();
+        }, 3000);
+        queryResult();
+    }
+
+});

+ 113 - 0
addons/epay/assets/less/common.less

@@ -0,0 +1,113 @@
+/*!
+ * Start Bootstrap - Modern Business (http://startbootstrap.com/)
+ * Copyright 2013-2016 Start Bootstrap
+ * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
+ */
+
+/* Global Styles */
+
+html,
+body {
+  height: 100%;
+}
+
+body {
+  padding-top: 50px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+
+}
+
+.img-addon {
+  margin-bottom: 10px;
+  width:100%;
+}
+
+.img-hover:hover {
+  opacity: 0.8;
+}
+
+.display-1 {
+  font-size:44px;
+}
+.display-4 {
+  font-size:24px;
+  line-height:32px;
+}
+
+/* Home Page Carousel */
+
+header.carousel {
+  height: 50%;
+}
+
+header.carousel .item,
+header.carousel .item.active,
+header.carousel .carousel-inner {
+  height: 100%;
+}
+
+header.carousel .fill {
+  width: 100%;
+  height: 100%;
+}
+
+.error-404 {
+  font-size: 100px;
+}
+
+/* Pricing Page Styles */
+
+.price {
+  display: block;
+  font-size: 50px;
+  line-height: 50px;
+}
+
+.price sup {
+  top: -20px;
+  left: 2px;
+  font-size: 20px;
+}
+
+.period {
+  display: block;
+  font-style: italic;
+}
+
+/* Footer Styles */
+
+footer {
+  margin: 50px 0;
+}
+
+/* Responsive Styles */
+
+@media(max-width:991px) {
+  .customer-img,
+  .img-related {
+    margin-bottom: 30px;
+  }
+}
+
+@media(max-width:767px) {
+  .img-addon {
+    margin-bottom: 15px;
+  }
+
+  header.carousel .carousel {
+    height: 70%;
+  }
+}
+.carousel-body {
+  position:absolute;
+  width: 100%;
+  top:25%;
+  text-align:center;
+  color:#fff;
+}
+
+.addonlist a > p{
+  margin-bottom:15px;
+}

+ 28 - 0
addons/epay/assets/less/epay.less

@@ -0,0 +1,28 @@
+@import (reference) "../../../../public/assets/less/bootstrap-less/mixins.less";
+@import (reference) "../../../../public/assets/less/bootstrap-less/variables.less";
+@import (reference) "../../../../public/assets/less/fastadmin/mixins.less";
+@import (reference) "../../../../public/assets/less/fastadmin/variables.less";
+@import "../../../../public/assets/less/lesshat.less";
+@import url("../../../css/bootstrap.min.css");
+@import url("../../../libs/font-awesome/css/font-awesome.min.css");
+
+html,
+body {
+  height: 100%;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  font-weight: 400;
+  overflow-x: hidden;
+  overflow-y: auto;
+  background: #f4f6f8;
+  font-size: 14px;
+  color: #616161;
+
+}
+
+.container {
+  max-width: 850px;
+  margin: 0 auto;
+  padding:50px;
+}

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


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


+ 69 - 0
addons/epay/config.php

@@ -0,0 +1,69 @@
+<?php
+
+return array (
+  0 => 
+  array (
+    'name' => 'wechat',
+    'title' => '微信',
+    'type' => 'array',
+    'content' => 
+    array (
+    ),
+    'value' => 
+    array (
+      'appid' => '',
+      'app_id' => 'wx6f3180dadc760358',
+      'app_secret' => '470015d03447c7141fee744e9b73d80a',
+      'miniapp_id' => '',
+      'mch_id' => 'harry',
+      'key' => '',
+      'notify_url' => '/addons/epay/api/notifyx/type/wechat',
+      'cert_client' => '/epay/certs/apiclient_cert.pem',
+      'cert_key' => '/epay/certs/apiclient_key.pem',
+      'log' => '1',
+    ),
+    'rule' => '',
+    'msg' => '',
+    'tip' => '微信参数配置',
+    'ok' => '',
+    'extend' => '',
+  ),
+  1 => 
+  array (
+    'name' => 'alipay',
+    'title' => '支付宝',
+    'type' => 'array',
+    'content' => 
+    array (
+    ),
+    'value' => 
+    array (
+      'app_id' => '',
+      'notify_url' => '/addons/epay/api/notifyx/type/alipay',
+      'return_url' => '/addons/epay/api/returnx/type/alipay',
+      'ali_public_key' => '',
+      'private_key' => '',
+      'log' => 1,
+    ),
+    'rule' => 'required',
+    'msg' => '',
+    'tip' => '支付宝参数配置',
+    'ok' => '',
+    'extend' => '',
+  ),
+  2 => 
+  array (
+    'name' => '__tips__',
+    'title' => '温馨提示',
+    'type' => 'array',
+    'content' => 
+    array (
+    ),
+    'value' => '请注意微信支付证书路径位于/addons/epay/certs目录下,请替换成你自己的证书<br>appid:APP的appid<br>app_id:公众号的appid<br>app_secret:公众号的secret<br>miniapp_id:小程序ID<br>mch_id:微信商户ID<br>key:微信商户支付的密钥',
+    'rule' => '',
+    'msg' => '',
+    'tip' => '微信参数配置',
+    'ok' => '',
+    'extend' => '',
+  ),
+);

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

@@ -0,0 +1,229 @@
+<?php
+
+namespace addons\epay\controller;
+
+use addons\epay\library\Service;
+use addons\epay\library\Wechat;
+use Endroid\QrCode\QrCode;
+use think\addons\Controller;
+use think\Response;
+use think\Session;
+use Yansongda\Pay\Pay;
+
+/**
+ * API接口控制器
+ *
+ * @package addons\epay\controller
+ */
+class Api extends Controller
+{
+
+    protected $layout = 'default';
+    protected $config = [];
+
+    /**
+     * 默认方法
+     */
+    public function index()
+    {
+        $this->error();
+    }
+
+    /**
+     * 外部提交
+     */
+    public function submit()
+    {
+        $out_trade_no = $this->request->request("out_trade_no");
+        $title = $this->request->request("title");
+        $amount = $this->request->request('amount');
+        $type = $this->request->request('type');
+        $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);
+    }
+
+    /**
+     * 微信支付
+     * @return string
+     */
+    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);
+
+        if ($isWechat) {
+            //发起公众号(jsapi支付)
+            $orderData = Session::get("wechatorderdata");
+            $openid = Session::get('openid');
+            //如果没有openid
+            if (!$openid) {
+                $wechat = new Wechat($config['wechat']['app_id'], $config['wechat']['app_secret']);
+                $openid = $wechat->getOpenid();
+            }
+
+            $orderData['method'] = 'mp';
+            $orderData['openid'] = $openid;
+            $payData = Service::submitOrder($orderData);
+            $payData = json_decode($payData, true);
+            if (!isset($payData['appId'])) {
+                $this->error("创建订单失败,请返回重试");
+            }
+            $type = 'jsapi';
+            $this->view->assign("orderData", $orderData);
+            $this->view->assign("payData", $payData);
+        } else {
+            //发起PC支付(Native支付)
+            $body = $this->request->request("body");
+            $code_url = $this->request->request("code_url");
+            $out_trade_no = $this->request->request("out_trade_no");
+            $return_url = $this->request->request("return_url");
+            $total_fee = $this->request->request("total_fee");
+
+            $sign = $this->request->request("sign");
+
+            $data = [
+                'body'         => $body,
+                'code_url'     => $code_url,
+                'out_trade_no' => $out_trade_no,
+                'return_url'   => $return_url,
+                'total_fee'    => $total_fee,
+            ];
+            if ($sign != md5(implode('', $data) . $config['wechat']['appid'])) {
+                $this->error("签名不正确");
+            }
+
+            if ($this->request->isAjax()) {
+                $pay = new Pay($config);
+                $result = $pay->driver('wechat')->gateway('scan')->find($out_trade_no);
+                if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
+                    $this->success("", "", ['trade_state' => $result['trade_state']]);
+                } else {
+                    $this->error("查询失败");
+                }
+            }
+            $data['sign'] = $sign;
+            $type = 'pc';
+            $this->view->assign("data", $data);
+        }
+
+        $this->view->assign("type", $type);
+        $this->view->assign("title", "微信支付");
+        return $this->view->fetch();
+    }
+
+    /**
+     * 支付成功回调
+     */
+    public function notifyx()
+    {
+        $type = $this->request->param('type');
+        $data = $this->request->request('', null, 'trim');
+        $config = Service::getConfig($type);
+        $pay = new Pay($config);
+        if (!$pay->driver($type)->gateway()->verify($data)) {
+            echo '签名错误';
+            return;
+        }
+
+        //你可以在这里你的业务处理逻辑,比如处理你的订单状态、给会员加余额等等功能
+        //下面这句必须要执行,且在此之前不能有任何输出
+        echo "success";
+        return;
+    }
+
+    /**
+     * 支付成功返回
+     */
+    public function returnx()
+    {
+        $type = $this->request->param('type');
+        $data = $this->request->request('', null, 'trim');
+        $config = Service::getConfig($type);
+        $pay = new Pay($config);
+        if ($type == 'alipay' && !$pay->driver($type)->gateway()->verify($data)) {
+            echo '签名错误';
+            return;
+        }
+
+        //你可以在这里定义你的提示信息,但切记不可在此编写逻辑
+        $this->success("恭喜你!支付成功!", addon_url("epay/index/index"));
+
+        return;
+    }
+
+    /**
+     * 生成二维码
+     * @return Response
+     */
+    public function qrcode()
+    {
+        $text = $this->request->get('text', 'hello world');
+        $size = $this->request->get('size', 250);
+        $padding = $this->request->get('padding', 15);
+        $errorcorrection = $this->request->get('errorcorrection', 'medium');
+        $foreground = $this->request->get('foreground', "#ffffff");
+        $background = $this->request->get('background', "#000000");
+        $logo = $this->request->get('logo');
+        $logosize = $this->request->get('logosize');
+        $label = $this->request->get('label');
+        $labelfontsize = $this->request->get('labelfontsize');
+        $labelhalign = $this->request->get('labelhalign');
+        $labelvalign = $this->request->get('labelvalign');
+
+        // 前景色
+        list($r, $g, $b) = sscanf($foreground, "#%02x%02x%02x");
+        $foregroundcolor = ['r' => $r, 'g' => $g, 'b' => $b];
+
+        // 背景色
+        list($r, $g, $b) = sscanf($background, "#%02x%02x%02x");
+        $backgroundcolor = ['r' => $r, 'g' => $g, 'b' => $b];
+
+        $qrCode = new QrCode();
+        $qrCode
+            ->setText($text)
+            ->setSize($size)
+            ->setPadding($padding)
+            ->setErrorCorrection($errorcorrection)
+            ->setForegroundColor($foregroundcolor)
+            ->setBackgroundColor($backgroundcolor)
+            ->setLogoSize($logosize)
+            ->setLabelFontPath(ROOT_PATH . 'public/assets/fonts/Times New Roman.ttf')
+            ->setLabel($label)
+            ->setLabelFontSize($labelfontsize)
+            ->setLabelHalign($labelhalign)
+            ->setLabelValign($labelvalign)
+            ->setImageType(QrCode::IMAGE_TYPE_PNG);
+        //也可以直接使用render方法输出结果
+        //$qrCode->render();
+        return new Response($qrCode->get(), 200, ['Content-Type' => $qrCode->getContentType()]);
+    }
+
+}

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

@@ -0,0 +1,108 @@
+<?php
+
+namespace addons\epay\controller;
+
+use addons\epay\library\Service;
+use fast\Random;
+use think\addons\Controller;
+use Yansongda\Pay\Log;
+use Yansongda\Pay\Pay;
+use Exception;
+
+/**
+ * 微信支付宝插件首页
+ *
+ * 此控制器仅用于开发展示说明和体验,建议自行添加一个新的控制器进行处理返回和回调事件,同时删除此控制器文件
+ *
+ * Class Index
+ * @package addons\epay\controller
+ */
+class Index extends Controller
+{
+
+    protected $layout = 'default';
+
+    protected $config = [];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+    }
+
+    public function index()
+    {
+        $this->view->assign("title", "FastAdmin微信支付宝整合插件");
+        return $this->view->fetch();
+    }
+
+    /**
+     * 体验,仅供开发测试
+     */
+    public function experience()
+    {
+        $amount = $this->request->request('amount');
+        $type = $this->request->request('type');
+        $method = $this->request->request('method');
+
+        if (!$amount || $amount < 0) {
+            $this->error("支付金额必须大于0");
+        }
+
+        if (!$type || !in_array($type, ['alipay', 'wechat'])) {
+            $this->error("支付类型不能为空");
+        }
+
+        //订单号
+        $out_trade_no = date("YmdHis") . mt_rand(100000, 999999);
+
+        //订单标题
+        $title = 'FastAdmin测试订单';
+
+        //回调链接
+        $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;
+
+        return Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method);
+    }
+
+    /**
+     * 支付成功,仅供开发测试
+     */
+    public function notifyx()
+    {
+        $paytype = $this->request->param('paytype');
+        $pay = \addons\epay\library\Service::checkNotify($paytype);
+        if (!$pay) {
+            echo '签名错误';
+            return;
+        }
+        $data = $pay->verify();
+        try {
+            $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
+            $out_trade_no = $data['out_trade_no'];
+
+            //你可以在此编写订单逻辑
+        } catch (Exception $e) {
+        }
+        echo $pay->success();
+    }
+
+    /**
+     * 支付返回,仅供开发测试
+     */
+    public function returnx()
+    {
+        $paytype = $this->request->param('paytype');
+        $out_trade_no = $this->request->param('out_trade_no');
+        $pay = \addons\epay\library\Service::checkReturn($paytype);
+        if (!$pay) {
+            $this->error('签名错误');
+        }
+
+        //你可以在这里通过out_trade_no去验证订单状态
+        //但是不可以在此编写订单逻辑!!!
+
+        $this->success("请返回网站查看支付结果", addon_url("epay/index/index"));
+    }
+
+}

+ 8 - 0
addons/epay/info.ini

@@ -0,0 +1,8 @@
+name = epay
+title = 微信支付宝整合
+intro = 可用于整合微信、支付宝付款,快速整合FastAdmin的其它模块
+author = Karson
+website = https://www.fastadmin.net
+version = 1.0.5
+state = 1
+url = /addons/epay.html

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

@@ -0,0 +1,17 @@
+<?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;
+    }
+
+}

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

@@ -0,0 +1,267 @@
+<?php
+
+namespace addons\epay\library;
+
+use Exception;
+use think\Log;
+use think\Response;
+use think\Session;
+use Yansongda\Pay\Pay;
+
+/**
+ * 订单服务类
+ *
+ * @package addons\epay\library
+ */
+class Service
+{
+
+    public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null)
+    {
+        if (!is_array($amount)) {
+            $params = [
+                'amount'    => $amount,
+                'orderid'   => $orderid,
+                'type'      => $type,
+                'title'     => $title,
+                'notifyurl' => $notifyurl,
+                'returnurl' => $returnurl,
+                'method'    => $method,
+            ];
+        } else {
+            $params = $amount;
+        }
+        $type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';
+        $method = isset($params['method']) ? $params['method'] : 'web';
+        $orderid = isset($params['orderid']) ? $params['orderid'] : date("YmdHis") . mt_rand(100000, 999999);
+        $amount = isset($params['amount']) ? $params['amount'] : 1;
+        $title = isset($params['title']) ? $params['title'] : "支付";
+        $auth_code = isset($params['auth_code']) ? $params['auth_code'] : '';
+        $openid = isset($params['openid']) ? $params['openid'] : '';
+
+        $request = request();
+        $notifyurl = isset($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'notify';
+        $returnurl = isset($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'return/out_trade_no/' . $orderid;
+        $html = '';
+        $config = Service::getConfig($type);
+        $config[$type]['notify_url'] = $notifyurl;
+        $config[$type]['return_url'] = $returnurl;
+
+        if ($type == 'alipay') {
+            //创建支付对象
+            $pay = new Pay($config);
+            //支付宝支付,请根据你的需求,仅选择你所需要的即可
+            $params = [
+                'out_trade_no' => $orderid,//你的订单号
+                'total_amount' => $amount,//单位元
+                'subject'      => $title,
+            ];
+            //如果是移动端自动切换为wap
+            $method = $request->isMobile() ? 'wap' : $method;
+
+            switch ($method) {
+                case 'web':
+                    //电脑支付,跳转
+                    $html = $pay->driver($type)->gateway('web')->pay($params);
+                    Response::create($html)->send();
+                    break;
+                case 'wap':
+                    //手机网页支付,跳转
+                    $html = $pay->driver($type)->gateway('wap')->pay($params);
+                    Response::create($html)->send();
+                    break;
+                case 'app':
+                    //APP支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('app')->pay($params);
+                    break;
+                case 'scan':
+                    //扫码支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('scan')->pay($params);
+                    break;
+                case 'pos':
+                    //刷卡支付,直接返回字符串
+                    //刷卡支付必须要有auth_code
+                    $params['auth_code'] = $auth_code;
+                    $html = $pay->driver($type)->gateway('pos')->pay($params);
+                    break;
+                default:
+                    //其它支付类型请参考:https://docs.pay.yansongda.cn/alipay
+            }
+        } else {
+            //如果是PC支付,判断当前环境,进行跳转
+            if ($method == 'web') {
+                if ((strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false)) {
+                    Session::delete("openid");
+                    Session::set("wechatorderdata", $params);
+                    $url = addon_url('epay/api/wechat', [], true, true);
+                    header("location:{$url}");
+                    exit;
+                } elseif ($request->isMobile()) {
+                    $method = 'wap';
+                }
+            }
+
+            //创建支付对象
+            $pay = new Pay($config);
+            $params = [
+                'out_trade_no' => $orderid,//你的订单号
+                'body'         => $title,
+                'total_fee'    => $amount * 100, //单位分
+            ];
+            switch ($method) {
+                case 'web':
+                    //电脑支付,跳转到自定义展示页面(FastAdmin独有)
+                    $html = $pay->driver($type)->gateway('web')->pay($params);
+                    Response::create($html)->send();
+                    break;
+                case 'mp':
+                    //公众号支付
+                    //公众号支付必须有openid
+                    $params['openid'] = $openid;
+                    $html = $pay->driver($type)->gateway('mp')->pay($params);
+                    break;
+                case 'wap':
+                    //手机网页支付,跳转
+                    $params['spbill_create_ip'] = $request->ip(0, false);
+                    $html = $pay->driver($type)->gateway('wap')->pay($params);
+                    header("location:{$html}");
+                    exit;
+                    break;
+                case 'app':
+                    //APP支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('app')->pay($params);
+                    break;
+                case 'scan':
+                    //扫码支付,直接返回字符串
+                    $html = $pay->driver($type)->gateway('scan')->pay($params);
+                    break;
+                case 'pos':
+                    //刷卡支付,直接返回字符串
+                    //刷卡支付必须要有auth_code
+                    $params['auth_code'] = $auth_code;
+                    $html = $pay->driver($type)->gateway('pos')->pay($params);
+                    break;
+                case 'miniapp':
+                    //小程序支付,直接返回字符串
+                    //小程序支付必须要有openid
+                    $params['openid'] = $openid;
+                    $html = $pay->driver($type)->gateway('miniapp')->pay($params);
+                    break;
+                default:
+            }
+        }
+        //返回字符串
+        $html = is_array($html) ? json_encode($html) : $html;
+        return $html;
+    }
+
+    /**
+     * 创建支付对象
+     * @param string $type   支付类型
+     * @param array  $config 配置信息
+     * @return bool
+     */
+    public static function createPay($type, $config = [])
+    {
+        $type = strtolower($type);
+        if (!in_array($type, ['wechat', 'alipay'])) {
+            return false;
+        }
+        $config = self::getConfig($type);
+        $config = array_merge($config[$type], $config);
+        $pay = new Pay($config);
+        return $pay;
+    }
+
+    /**
+     * 验证回调是否成功
+     * @param string $type   支付类型
+     * @param array  $config 配置信息
+     * @return bool|Pay
+     */
+    public static function checkNotify($type, $config = [])
+    {
+        $type = strtolower($type);
+        if (!in_array($type, ['wechat', 'alipay'])) {
+            return false;
+        }
+        try {
+            $pay = new Pay(self::getConfig($type));
+            $data = $type == 'wechat' ? file_get_contents("php://input") : request()->post('', null, 'trim');
+
+            $data = $pay->driver($type)->gateway()->verify($data);
+
+            if ($type == 'alipay') {
+                if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
+                    return $pay;
+                }
+            } else {
+                return $pay;
+            }
+        } catch (Exception $e) {
+            return false;
+        }
+
+        return false;
+    }
+
+    /**
+     * 验证返回是否成功
+     * @param string $type   支付类型
+     * @param array  $config 配置信息
+     * @return bool|Pay
+     */
+    public static function checkReturn($type, $config = [])
+    {
+        $type = strtolower($type);
+        if (!in_array($type, ['wechat', 'alipay'])) {
+            return false;
+        }
+        //微信无需验证
+        if ($type == 'wechat') {
+            return true;
+        }
+        try {
+            $pay = new Pay(self::getConfig($type));
+            $data = $type == 'wechat' ? file_get_contents("php://input") : request()->get('', null, 'trim');
+            $data = $pay->driver($type)->gateway()->verify($data);
+            if ($data) {
+                return $pay;
+            }
+        } catch (Exception $e) {
+            return false;
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取配置
+     * @param string $type 支付类型
+     * @return array|mixed
+     */
+    public static function getConfig($type = 'wechat')
+    {
+        $config = get_addon_config('epay');
+        $config = isset($config[$type]) ? $config[$type] : $config['wechat'];
+        if ($config['log']) {
+            $config['log'] = [
+                'file'  => LOG_PATH . '/epaylogs/' . $type . '-' . date("Y-m-d") . '.log',
+                'level' => 'debug'
+            ];
+        }
+        if (isset($config['cert_client']) && substr($config['cert_client'], 0, 6) == '/epay/') {
+            $config['cert_client'] = ADDON_PATH . $config['cert_client'];
+        }
+        if (isset($config['cert_key']) && substr($config['cert_key'], 0, 6) == '/epay/') {
+            $config['cert_key'] = ADDON_PATH . $config['cert_key'];
+        }
+
+        $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'];
+        return [$type => $config];
+    }
+
+}

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

@@ -0,0 +1,107 @@
+<?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);
+                    $openid = isset($token['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()
+    {
+        $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;
+    }
+}

+ 63 - 0
addons/epay/library/Yansongda/Pay/Contracts/GatewayInterface.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace Yansongda\Pay\Contracts;
+
+interface GatewayInterface
+{
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz);
+
+    /**
+     * refund a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array|string $config_biz
+     *
+     * @return array|bool
+     */
+    public function refund($config_biz);
+
+    /**
+     * close a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array|string $config_biz
+     *
+     * @return array|bool
+     */
+    public function close($config_biz);
+
+    /**
+     * find a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $out_trade_no
+     *
+     * @return array|bool
+     */
+    public function find($out_trade_no);
+
+    /**
+     * verify notify.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param mixed  $data
+     * @param string $sign
+     * @param bool   $sync
+     *
+     * @return array|bool
+     */
+    public function verify($data, $sign = null, $sync = false);
+}

+ 7 - 0
addons/epay/library/Yansongda/Pay/Exceptions/Exception.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Yansongda\Pay\Exceptions;
+
+class Exception extends \Exception
+{
+}

+ 28 - 0
addons/epay/library/Yansongda/Pay/Exceptions/GatewayException.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Yansongda\Pay\Exceptions;
+
+class GatewayException extends Exception
+{
+    /**
+     * error raw data.
+     *
+     * @var array
+     */
+    public $raw = [];
+
+    /**
+     * [__construct description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string     $message
+     * @param string|int $code
+     */
+    public function __construct($message, $code, $raw = [])
+    {
+        parent::__construct($message, intval($code));
+
+        $this->raw = $raw;
+    }
+}

+ 7 - 0
addons/epay/library/Yansongda/Pay/Exceptions/InvalidArgumentException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Yansongda\Pay\Exceptions;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}

+ 291 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php

@@ -0,0 +1,291 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+use Yansongda\Pay\Contracts\GatewayInterface;
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+use Yansongda\Pay\Support\Config;
+use Yansongda\Pay\Traits\HasHttpRequest;
+
+abstract class Alipay implements GatewayInterface
+{
+    use HasHttpRequest;
+
+    /**
+     * @var string
+     */
+    protected $gateway = 'https://openapi.alipay.com/gateway.do?charset=UTF-8';
+
+    /**
+     * alipay global config params.
+     *
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * user's config params.
+     *
+     * @var \Yansongda\Pay\Support\Config
+     */
+    protected $user_config;
+
+    /**
+     * [__construct description].
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config [description]
+     */
+    public function __construct(array $config)
+    {
+        $this->user_config = new Config($config);
+
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $this->config = [
+            'app_id'      => $this->user_config->get('app_id'),
+            'method'      => '',
+            'format'      => 'JSON',
+            'charset'     => 'UTF-8',
+            'sign_type'   => 'RSA2',
+            'version'     => '1.0',
+            'return_url'  => $this->user_config->get('return_url', ''),
+            'notify_url'  => $this->user_config->get('notify_url', ''),
+            'timestamp'   => date('Y-m-d H:i:s'),
+            'sign'        => '',
+            'biz_content' => '',
+        ];
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz)
+    {
+        $config_biz['product_code'] = $this->getProductCode();
+
+        $this->config['method'] = $this->getMethod();
+        $this->config['biz_content'] = json_encode($config_biz);
+        $this->config['sign'] = $this->getSign();
+    }
+
+    /**
+     * refund a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param mixed $config_biz
+     *
+     * @return array|bool
+     */
+    public function refund($config_biz, $refund_amount = null)
+    {
+        if (!is_array($config_biz)) {
+            $config_biz = [
+                'out_trade_no'  => $config_biz,
+                'refund_amount' => $refund_amount,
+            ];
+        }
+
+        return $this->getResult($config_biz, 'alipay.trade.refund');
+    }
+
+    /**
+     * close a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array|string $config_biz
+     *
+     * @return array|bool
+     */
+    public function close($config_biz)
+    {
+        if (!is_array($config_biz)) {
+            $config_biz = [
+                'out_trade_no' => $config_biz,
+            ];
+        }
+
+        return $this->getResult($config_biz, 'alipay.trade.close');
+    }
+
+    /**
+     * find a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $out_trade_no
+     *
+     * @return array|bool
+     */
+    public function find($out_trade_no = '')
+    {
+        $config_biz = [
+            'out_trade_no' => $out_trade_no,
+        ];
+
+        return $this->getResult($config_biz, 'alipay.trade.query');
+    }
+
+    /**
+     * verify the notify.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array  $data
+     * @param string $sign
+     * @param bool   $sync
+     *
+     * @return array|bool
+     */
+    public function verify($data, $sign = null, $sync = false)
+    {
+        if (is_null($this->user_config->get('ali_public_key'))) {
+            throw new InvalidArgumentException('Missing Config -- [ali_public_key]');
+        }
+
+        $sign = is_null($sign) ? $data['sign'] : $sign;
+
+        $res = "-----BEGIN PUBLIC KEY-----\n".
+                wordwrap($this->user_config->get('ali_public_key'), 64, "\n", true).
+                "\n-----END PUBLIC KEY-----";
+
+        $toVerify = $sync ? json_encode($data) : $this->getSignContent($data, true);
+
+        return openssl_verify($toVerify, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1 ? $data : false;
+    }
+
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    abstract protected function getMethod();
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    abstract protected function getProductCode();
+
+    /**
+     * build pay html.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function buildPayHtml()
+    {
+        $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->gateway."' method='POST'>";
+        foreach ($this->config as $key => $val) {
+            $val = str_replace("'", '&apos;', $val);
+            $sHtml .= "<input type='hidden' name='".$key."' value='".$val."'/>";
+        }
+        $sHtml .= "<input type='submit' value='ok' style='display:none;'></form>";
+        $sHtml .= "<script>document.forms['alipaysubmit'].submit();</script>";
+
+        return $sHtml;
+    }
+
+    /**
+     * get alipay api result.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array  $config_biz
+     * @param string $method
+     *
+     * @return array|bool
+     */
+    protected function getResult($config_biz, $method)
+    {
+        $this->config['biz_content'] = json_encode($config_biz);
+        $this->config['method'] = $method;
+        $this->config['sign'] = $this->getSign();
+
+        $this->config = array_filter($this->config, function ($value) {
+            return $value !== '' && !is_null($value);
+        });
+
+        $method = str_replace('.', '_', $method).'_response';
+
+        $data = json_decode($this->post($this->gateway, $this->config), true);
+
+        if (!isset($data[$method]['code']) || $data[$method]['code'] !== '10000') {
+            throw new GatewayException(
+                'get result error:'.$data[$method]['msg'].' - '.$data[$method]['sub_code'],
+                $data[$method]['code'],
+                $data);
+        }
+
+        return $this->verify($data[$method], $data['sign'], true);
+    }
+
+    /**
+     * get sign.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getSign()
+    {
+        if (is_null($this->user_config->get('private_key'))) {
+            throw new InvalidArgumentException('Missing Config -- [private_key]');
+        }
+
+        $res = "-----BEGIN RSA PRIVATE KEY-----\n".
+                wordwrap($this->user_config->get('private_key'), 64, "\n", true).
+                "\n-----END RSA PRIVATE KEY-----";
+
+        openssl_sign($this->getSignContent($this->config), $sign, $res, OPENSSL_ALGO_SHA256);
+
+        return base64_encode($sign);
+    }
+
+    /**
+     * get signContent that is to be signed.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $toBeSigned
+     * @param bool  $verify
+     *
+     * @return string
+     */
+    protected function getSignContent(array $toBeSigned, $verify = false)
+    {
+        ksort($toBeSigned);
+
+        $stringToBeSigned = '';
+        foreach ($toBeSigned as $k => $v) {
+            if ($verify && $k != 'sign' && $k != 'sign_type') {
+                $stringToBeSigned .= $k.'='.$v.'&';
+            }
+            if (!$verify && $v !== '' && !is_null($v) && $k != 'sign' && '@' != substr($v, 0, 1)) {
+                $stringToBeSigned .= $k.'='.$v.'&';
+            }
+        }
+        $stringToBeSigned = substr($stringToBeSigned, 0, -1);
+        unset($k, $v);
+
+        return $stringToBeSigned;
+    }
+}

+ 46 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/AppGateway.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class AppGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.app.pay';
+    }
+
+    /**
+     * get productCode method.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'QUICK_MSECURITY_PAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        parent::pay($config_biz);
+
+        return http_build_query($this->config);
+    }
+}

+ 47 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/PosGateway.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class PosGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.pay';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'FACE_TO_FACE_PAYMENT';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array  $config_biz
+     * @param string $scene
+     *
+     * @return array|bool
+     */
+    public function pay(array $config_biz = [], $scene = 'bar_code')
+    {
+        $config_biz['scene'] = $scene;
+
+        return $this->getResult($config_biz, $this->getMethod());
+    }
+}

+ 44 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/ScanGateway.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class ScanGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.precreate';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return '';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array|bool
+     */
+    public function pay(array $config_biz = [])
+    {
+        return $this->getResult($config_biz, $this->getMethod());
+    }
+}

+ 44 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/TransferGateway.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class TransferGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.fund.trans.toaccount.transfer';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return '';
+    }
+
+    /**
+     * transfer amount to account.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array|bool
+     */
+    public function pay(array $config_biz = [])
+    {
+        return $this->getResult($config_biz, $this->getMethod());
+    }
+}

+ 48 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/WapGateway.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class WapGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @version 2017-08-10
+     *
+     * @return string [description]
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.wap.pay';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'QUICK_WAP_WAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        parent::pay($config_biz);
+
+        return $this->buildPayHtml();
+    }
+}

+ 46 - 0
addons/epay/library/Yansongda/Pay/Gateways/Alipay/WebGateway.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Alipay;
+
+class WebGateway extends Alipay
+{
+    /**
+     * get method config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getMethod()
+    {
+        return 'alipay.trade.page.pay';
+    }
+
+    /**
+     * get productCode config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getProductCode()
+    {
+        return 'FAST_INSTANT_TRADE_PAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        parent::pay($config_biz);
+
+        return $this->buildPayHtml();
+    }
+}

+ 50 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/AppGateway.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class AppGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'APP';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('appid'))) {
+            throw new InvalidArgumentException('Missing Config -- [appid]');
+        }
+
+        $this->config['appid'] = $this->user_config->get('appid');
+
+        $payRequest = [
+            'appid'     => $this->user_config->get('appid'),
+            'partnerid' => $this->user_config->get('mch_id'),
+            'prepayid'  => $this->preOrder($config_biz)['prepay_id'],
+            'timestamp' => strval(time()),
+            'noncestr'  => $this->createNonceStr(),
+            'package'   => 'Sign=WXPay',
+        ];
+        $payRequest['sign'] = $this->getSign($payRequest);
+
+        return $payRequest;
+    }
+}

+ 82 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/GroupredpackGateway.php

@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * 发放裂变红包
+ * Class GroupredpackGateway
+ * Date: 2017/12/21
+ * Time: 19:23
+ * Com:萌点云科技(深圳)有限公司.
+ *
+ * Author:陈老司机
+ *
+ * Email:690712575@qq.com
+ */
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class GroupredpackGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_transfer = 'mmpaymkttransfers/sendgroupredpack';
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+        unset($this->config['sign_type']);
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+        unset($this->config['app_id']);
+        unset($this->config['appid']);
+        $this->config = array_merge($this->config, $config_biz);
+        $this->config['sign'] = $this->getSign($this->config);
+        $data = $this->fromXml($this->post(
+            $this->endpoint.$this->gateway_transfer,
+            $this->toXml($this->config),
+            [
+                'cert'    => $this->user_config->get('cert_client', ''),
+                'ssl_key' => $this->user_config->get('cert_key', ''),
+            ]
+        ));
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return '';
+    }
+}

+ 49 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/MiniappGateway.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class MiniappGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string [description]
+     */
+    protected function getTradeType()
+    {
+        return 'JSAPI';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('miniapp_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [miniapp_id]');
+        }
+
+        $this->config['appid'] = $this->user_config->get('miniapp_id');
+
+        $payRequest = [
+            'appId'     => $this->user_config->get('miniapp_id'),
+            'timeStamp' => strval(time()),
+            'nonceStr'  => $this->createNonceStr(),
+            'package'   => 'prepay_id='.$this->preOrder($config_biz)['prepay_id'],
+            'signType'  => 'MD5',
+        ];
+        $payRequest['paySign'] = $this->getSign($payRequest);
+
+        return $payRequest;
+    }
+}

+ 47 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/MpGateway.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class MpGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'JSAPI';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $payRequest = [
+            'appId'     => $this->user_config->get('app_id'),
+            'timeStamp' => strval(time()),
+            'nonceStr'  => $this->createNonceStr(),
+            'package'   => 'prepay_id='.$this->preOrder($config_biz)['prepay_id'],
+            'signType'  => 'MD5',
+        ];
+        $payRequest['paySign'] = $this->getSign($payRequest);
+
+        return $payRequest;
+    }
+}

+ 46 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/PosGateway.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class PosGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_order = 'pay/micropay';
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'MICROPAY';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+
+        return $this->preOrder($config_biz);
+    }
+}

+ 86 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/RedpackGateway.php

@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * 发放普通红包
+ * Class RedPackGateway
+ * Date: 2017/12/21
+ * Time: 19:23
+ * Com:萌点云科技(深圳)有限公司.
+ *
+ * Author:陈老司机
+ *
+ * Email:690712575@qq.com
+ */
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class RedpackGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_transfer = 'mmpaymkttransfers/sendredpack';
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+        unset($this->config['sign_type']);
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+        unset($this->config['app_id']);
+        unset($this->config['appid']);
+
+        $this->config = array_merge($this->config, $config_biz);
+
+        $this->config['sign'] = $this->getSign($this->config);
+
+        $data = $this->fromXml($this->post(
+            $this->endpoint.$this->gateway_transfer,
+            $this->toXml($this->config),
+            [
+                'cert'    => $this->user_config->get('cert_client', ''),
+                'ssl_key' => $this->user_config->get('cert_key', ''),
+            ]
+        ));
+
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return '';
+    }
+}

+ 38 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/ScanGateway.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class ScanGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'NATIVE';
+    }
+
+    /**
+     * pay a order using modelTWO.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        return $this->preOrder($config_biz)['code_url'];
+    }
+}

+ 78 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/TransferGateway.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class TransferGateway extends Wechat
+{
+    /**
+     * @var string
+     */
+    protected $gateway_transfer = 'mmpaymkttransfers/promotion/transfers';
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return '';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $config_biz['mch_appid'] = $this->config['appid'];
+        $config_biz['mchid'] = $this->config['mch_id'];
+
+        unset($this->config['appid']);
+        unset($this->config['mch_id']);
+        unset($this->config['sign_type']);
+        unset($this->config['trade_type']);
+        unset($this->config['notify_url']);
+
+        $this->config = array_merge($this->config, $config_biz);
+
+        $this->config['sign'] = $this->getSign($this->config);
+
+        $data = $this->fromXml($this->post(
+            $this->endpoint.$this->gateway_transfer,
+            $this->toXml($this->config),
+            [
+                'cert'    => $this->user_config->get('cert_client', ''),
+                'ssl_key' => $this->user_config->get('cert_key', ''),
+            ]
+        ));
+
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+}

+ 41 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/WapGateway.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class WapGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'MWEB';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $data = $this->preOrder($config_biz);
+
+        return is_null($this->user_config->get('return_url')) ? $data['mweb_url'] : $data['mweb_url'].
+                        '&redirect_url='.urlencode($this->user_config->get('return_url'));
+    }
+}

+ 69 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/WebGateway.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class WebGateway extends Wechat
+{
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function getTradeType()
+    {
+        return 'NATIVE';
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return string
+     */
+    public function pay(array $config_biz = [])
+    {
+        if (is_null($this->user_config->get('app_id'))) {
+            throw new InvalidArgumentException('Missing Config -- [app_id]');
+        }
+
+        $code_url = $this->preOrder($config_biz)['code_url'];
+        $params = [
+            'body'         => $config_biz['body'],
+            'code_url'     => $code_url,
+            'out_trade_no' => $config_biz['out_trade_no'],
+            'return_url'   => $this->user_config->get('return_url'),
+            'total_fee'    => $config_biz['total_fee'],
+        ];
+        $params['sign'] = md5(implode('', $params) . $this->user_config->get('app_id'));
+        $endpoint = addon_url("epay/api/wechat");
+
+        return $this->buildPayHtml($endpoint, $params);
+    }
+
+    /**
+     * build pay html.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    protected function buildPayHtml($endpoint, $params)
+    {
+        $sHtml = "<form id='alipaysubmit' name='wechatsubmit' action='" . $endpoint . "' method='POST'>";
+        foreach ($params as $key => $val) {
+            $val = str_replace("'", '&apos;', $val);
+            $sHtml .= "<input type='hidden' name='" . $key . "' value='" . $val . "'/>";
+        }
+        $sHtml .= "<input type='submit' value='ok' style='display:none;'></form>";
+        $sHtml .= "<script>document.forms['wechatsubmit'].submit();</script>";
+
+        return $sHtml;
+    }
+}

+ 354 - 0
addons/epay/library/Yansongda/Pay/Gateways/Wechat/Wechat.php

@@ -0,0 +1,354 @@
+<?php
+
+namespace Yansongda\Pay\Gateways\Wechat;
+
+use Yansongda\Pay\Contracts\GatewayInterface;
+use Yansongda\Pay\Exceptions\GatewayException;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+use Yansongda\Pay\Support\Config;
+use Yansongda\Pay\Traits\HasHttpRequest;
+
+abstract class Wechat implements GatewayInterface
+{
+    use HasHttpRequest;
+
+    /**
+     * @var string
+     */
+    protected $endpoint = 'https://api.mch.weixin.qq.com/';
+
+    /**
+     * @var string
+     */
+    protected $gateway_order = 'pay/unifiedorder';
+
+    /**
+     * @var string
+     */
+    protected $gateway_query = 'pay/orderquery';
+
+    /**
+     * @var string
+     */
+    protected $gateway_close = 'pay/closeorder';
+
+    /**
+     * @var string
+     */
+    protected $gateway_refund = 'secapi/pay/refund';
+
+    /**
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * @var \Yansongda\Pay\Support\Config
+     */
+    protected $user_config;
+
+    /**
+     * [__construct description].
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config
+     */
+    public function __construct(array $config)
+    {
+        $this->user_config = new Config($config);
+
+        $this->config = [
+            'appid'      => $this->user_config->get('app_id', ''),
+            'mch_id'     => $this->user_config->get('mch_id', ''),
+            'nonce_str'  => $this->createNonceStr(),
+            'sign_type'  => 'MD5',
+            'notify_url' => $this->user_config->get('notify_url', ''),
+            'trade_type' => $this->getTradeType(),
+        ];
+
+        if ($endpoint = $this->user_config->get('endpoint_url')) {
+            $this->endpoint = $endpoint;
+        }
+    }
+
+    /**
+     * pay a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return mixed
+     */
+    abstract public function pay(array $config_biz = []);
+
+    /**
+     * refund.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string|bool
+     */
+    public function refund($config_biz = [])
+    {
+        if (isset($config_biz['miniapp'])) {
+            $this->config['appid'] = $this->user_config->get('miniapp_id');
+            unset($config_biz['miniapp']);
+        }
+
+        $this->config = array_merge($this->config, $config_biz);
+
+        $this->unsetTradeTypeAndNotifyUrl();
+
+        return $this->getResult($this->gateway_refund, true);
+    }
+
+    /**
+     * close a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return array|bool
+     */
+    public function close($out_trade_no = '')
+    {
+        $this->config['out_trade_no'] = $out_trade_no;
+
+        $this->unsetTradeTypeAndNotifyUrl();
+
+        return $this->getResult($this->gateway_close);
+    }
+
+    /**
+     * find a order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $out_trade_no
+     *
+     * @return array|bool
+     */
+    public function find($out_trade_no = '')
+    {
+        $this->config['out_trade_no'] = $out_trade_no;
+
+        $this->unsetTradeTypeAndNotifyUrl();
+
+        return $this->getResult($this->gateway_query);
+    }
+
+    /**
+     * verify the notify.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $data
+     * @param string $sign
+     * @param bool   $sync
+     *
+     * @return array|bool
+     */
+    public function verify($data, $sign = null, $sync = false)
+    {
+        $data = $this->fromXml($data);
+
+        $sign = is_null($sign) ? $data['sign'] : $sign;
+
+        return $this->getSign($data) === $sign ? $data : false;
+    }
+
+    /**
+     * get trade type config.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return string
+     */
+    abstract protected function getTradeType();
+
+    /**
+     * pre order.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $config_biz
+     *
+     * @return array
+     */
+    protected function preOrder($config_biz = [])
+    {
+        $this->config = array_merge($this->config, $config_biz);
+
+        return $this->getResult($this->gateway_order);
+    }
+
+    /**
+     * get api result.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $path
+     * @param bool   $cert
+     *
+     * @return array
+     */
+    protected function getResult($path, $cert = false)
+    {
+        $this->config['sign'] = $this->getSign($this->config);
+
+        if ($cert) {
+            $data = $this->fromXml($this->post(
+                $this->endpoint.$path,
+                $this->toXml($this->config),
+                [
+                    'cert'    => $this->user_config->get('cert_client', ''),
+                    'ssl_key' => $this->user_config->get('cert_key', ''),
+                ]
+            ));
+        } else {
+            $data = $this->fromXml($this->post($this->endpoint.$path, $this->toXml($this->config)));
+        }
+
+        if (!isset($data['return_code']) || $data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
+            $error = 'getResult error:'.$data['return_msg'];
+            $error .= isset($data['err_code_des']) ? ' - '.$data['err_code_des'] : '';
+        }
+
+        if (!isset($error) && $this->getSign($data) !== $data['sign']) {
+            $error = 'getResult error: return data sign error';
+        }
+
+        if (isset($error)) {
+            throw new GatewayException(
+                $error,
+                20000,
+                $data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * sign.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $data
+     *
+     * @return string
+     */
+    protected function getSign($data)
+    {
+        if (is_null($this->user_config->get('key'))) {
+            throw new InvalidArgumentException('Missing Config -- [key]');
+        }
+
+        ksort($data);
+
+        $string = md5($this->getSignContent($data).'&key='.$this->user_config->get('key'));
+
+        return strtoupper($string);
+    }
+
+    /**
+     * get sign content.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $data
+     *
+     * @return string
+     */
+    protected function getSignContent($data)
+    {
+        $buff = '';
+
+        foreach ($data as $k => $v) {
+            $buff .= ($k != 'sign' && $v != '' && !is_array($v)) ? $k.'='.$v.'&' : '';
+        }
+
+        return trim($buff, '&');
+    }
+
+    /**
+     * create random string.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param int $length
+     *
+     * @return string
+     */
+    protected function createNonceStr($length = 16)
+    {
+        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+        $str = '';
+        for ($i = 0; $i < $length; $i++) {
+            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
+        }
+
+        return $str;
+    }
+
+    /**
+     * convert to xml.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param array $data
+     *
+     * @return string
+     */
+    protected function toXml($data)
+    {
+        if (!is_array($data) || count($data) <= 0) {
+            throw new InvalidArgumentException('convert to xml error!invalid array!');
+        }
+
+        $xml = '<xml>';
+        foreach ($data as $key => $val) {
+            $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
+                                       '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
+        }
+        $xml .= '</xml>';
+
+        return $xml;
+    }
+
+    /**
+     * convert to array.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $xml
+     *
+     * @return array
+     */
+    protected function fromXml($xml)
+    {
+        if (!$xml) {
+            throw new InvalidArgumentException('convert to array error !invalid xml');
+        }
+
+        libxml_disable_entity_loader(true);
+
+        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
+    }
+
+    /**
+     * delete trade_type and notify_url.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @return bool
+     */
+    protected function unsetTradeTypeAndNotifyUrl()
+    {
+        unset($this->config['notify_url']);
+        unset($this->config['trade_type']);
+
+        return true;
+    }
+}

+ 134 - 0
addons/epay/library/Yansongda/Pay/Pay.php

@@ -0,0 +1,134 @@
+<?php
+
+namespace Yansongda\Pay;
+
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+use Yansongda\Pay\Support\Config;
+
+class Pay
+{
+    /**
+     * @var \Yansongda\Pay\Support\Config
+     */
+    private $config;
+
+    /**
+     * @var string
+     */
+    private $drivers;
+
+    /**
+     * @var \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    private $gateways;
+
+    /**
+     * construct method.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config = new Config($config);
+    }
+
+    /**
+     * set pay's driver.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $driver
+     *
+     * @return Pay
+     */
+    public function driver($driver)
+    {
+        if (is_null($this->config->get($driver))) {
+            throw new InvalidArgumentException("Driver [$driver]'s Config is not defined.");
+        }
+
+        $this->drivers = $driver;
+
+        return $this;
+    }
+
+    /**
+     * set pay's gateway.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $gateway
+     *
+     * @return \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    public function gateway($gateway = 'web')
+    {
+        if (!isset($this->drivers)) {
+            throw new InvalidArgumentException('Driver is not defined.');
+        }
+
+        $this->gateways = $this->createGateway($gateway);
+
+        return $this->gateways;
+    }
+
+    /**
+     * create pay's gateway.
+     *
+     * @author yansongda <me@yansongda.cn>
+     *
+     * @param string $gateway
+     *
+     * @return \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    protected function createGateway($gateway)
+    {
+        if (!file_exists(__DIR__ . '/Gateways/' . ucfirst($this->drivers) . '/' . ucfirst($gateway) . 'Gateway.php')) {
+            throw new InvalidArgumentException("Gateway [$gateway] is not supported.");
+        }
+
+        $gateway = __NAMESPACE__ . '\\Gateways\\' . ucfirst($this->drivers) . '\\' . ucfirst($gateway) . 'Gateway';
+
+        return $this->build($gateway);
+    }
+
+    /**
+     * build pay's gateway.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $gateway
+     *
+     * @return \Yansongda\Pay\Contracts\GatewayInterface
+     */
+    protected function build($gateway)
+    {
+        return new $gateway($this->config->get($this->drivers));
+    }
+
+    public function verify()
+    {
+        if ($this->drivers == 'wechat') {
+            return $this->gateway()->verify(file_get_contents("php://input"));
+        } else {
+            $request = request();
+            $data = $request->get('app_id') && $request->get('out_trade_no') ? $request->get('', null, 'trim') : $request->post('', null, 'trim');
+            return $this->gateway()->verify($data);
+        }
+    }
+
+    public function success()
+    {
+        if ($this->drivers == 'wechat') {
+            echo '<xml>
+  <return_code><![CDATA[SUCCESS]]></return_code>
+  <return_msg><![CDATA[OK]]></return_msg>
+</xml>';
+        } else {
+            echo 'success';
+        }
+        return;
+    }
+}

+ 147 - 0
addons/epay/library/Yansongda/Pay/Support/Config.php

@@ -0,0 +1,147 @@
+<?php
+
+namespace Yansongda\Pay\Support;
+
+use ArrayAccess;
+use Yansongda\Pay\Exceptions\InvalidArgumentException;
+
+class Config implements ArrayAccess
+{
+    /**
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * Config constructor.
+     *
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * get a config.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $key
+     * @param string $default
+     *
+     * @return mixed
+     */
+    public function get($key = null, $default = null)
+    {
+        $config = $this->config;
+
+        if (is_null($key)) {
+            return $config;
+        }
+
+        if (isset($config[$key])) {
+            return $config[$key];
+        }
+
+        foreach (explode('.', $key) as $segment) {
+            if (!is_array($config) || !array_key_exists($segment, $config)) {
+                return $default;
+            }
+            $config = $config[$segment];
+        }
+
+        return $config;
+    }
+
+    /**
+     * set a config.
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $key
+     * @param array  $value
+     */
+    public function set(string $key, $value)
+    {
+        if ($key == '') {
+            throw new InvalidArgumentException('Invalid config key.');
+        }
+
+        // 只支持三维数组,多余无意义
+        $keys = explode('.', $key);
+        switch (count($keys)) {
+            case '1':
+                $this->config[$key] = $value;
+                break;
+            case '2':
+                $this->config[$keys[0]][$keys[1]] = $value;
+                break;
+            case '3':
+                $this->config[$keys[0]][$keys[1]][$keys[2]] = $value;
+                break;
+
+            default:
+                throw new InvalidArgumentException('Invalid config key.');
+        }
+
+        return $this->config;
+    }
+
+    /**
+     * [offsetExists description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     *
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        return array_key_exists($offset, $this->config);
+    }
+
+    /**
+     * [offsetGet description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     *
+     * @return mixed
+     */
+    public function offsetGet($offset)
+    {
+        return $this->get($offset);
+    }
+
+    /**
+     * [offsetSet description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     * @param string $value
+     *
+     * @return array
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->set($offset, $value);
+    }
+
+    /**
+     * [offsetUnset description].
+     *
+     * @author JasonYan <me@yansongda.cn>
+     *
+     * @param string $offset
+     *
+     * @return array
+     */
+    public function offsetUnset($offset)
+    {
+        $this->set($offset, null);
+    }
+}

+ 119 - 0
addons/epay/library/Yansongda/Pay/Traits/HasHttpRequest.php

@@ -0,0 +1,119 @@
+<?php
+
+/*
+ * (c) overtrue <i@overtrue.me>
+ *
+ * Modified By yansongda <me@yansongda.cn>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Yansongda\Pay\Traits;
+
+use GuzzleHttp\Client;
+use Psr\Http\Message\ResponseInterface;
+
+trait HasHttpRequest
+{
+    /**
+     * Make a get request.
+     *
+     * @param string $endpoint
+     * @param array  $query
+     * @param array  $headers
+     *
+     * @return array
+     */
+    protected function get($endpoint, $query = [], $headers = [])
+    {
+        return $this->request('get', $endpoint, [
+            'headers' => $headers,
+            'query'   => $query,
+        ]);
+    }
+
+    /**
+     * Make a post request.
+     *
+     * @param string $endpoint
+     * @param mixed  $params
+     * @param array  $options
+     *
+     * @return string
+     */
+    protected function post($endpoint, $params = [], ...$options)
+    {
+        $options = isset($options[0]) ? $options[0] : [];
+
+        if (!is_array($params)) {
+            $options['body'] = $params;
+        } else {
+            $options['form_params'] = $params;
+        }
+
+        return $this->request('post', $endpoint, $options);
+    }
+
+    /**
+     * Make a http request.
+     *
+     * @param string $method
+     * @param string $endpoint
+     * @param array  $options  http://docs.guzzlephp.org/en/latest/request-options.html
+     *
+     * @return array
+     */
+    protected function request($method, $endpoint, $options = [])
+    {
+        return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($endpoint, $options));
+    }
+
+    /**
+     * Return base Guzzle options.
+     *
+     * @return array
+     */
+    protected function getBaseOptions()
+    {
+        $options = [
+            'base_uri' => method_exists($this, 'getBaseUri') ? $this->getBaseUri() : '',
+            'timeout'  => property_exists($this, 'timeout') ? $this->timeout : 5.0,
+        ];
+
+        return $options;
+    }
+
+    /**
+     * Return http client.
+     *
+     * @param array $options
+     *
+     * @return \GuzzleHttp\Client
+     */
+    protected function getHttpClient(array $options = [])
+    {
+        return new Client($options);
+    }
+
+    /**
+     * Convert response contents to json.
+     *
+     * @param \Psr\Http\Message\ResponseInterface $response
+     *
+     * @return array
+     */
+    protected function unwrapResponse(ResponseInterface $response)
+    {
+        $contentType = $response->getHeaderLine('Content-Type');
+        $contents = $response->getBody()->getContents();
+
+        if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
+            return json_decode($contents, true);
+        } elseif (false !== stripos($contentType, 'xml')) {
+            return json_decode(json_encode(simplexml_load_string($contents)), true);
+        }
+
+        return $contents;
+    }
+}

+ 86 - 0
addons/epay/view/api/wechat.html

@@ -0,0 +1,86 @@
+<link rel="stylesheet" href="__ADDON__/css/wechat.css" />
+
+{if $type=='jsapi'}
+<div class="container">
+    <div class="row" style="margin-top:20px;">
+        <div class="col-xs-12">
+            <button type="button" class="btn btn-success btn-lg btn-block">正在发起微信支付</button>
+            <button type="button" class="btn btn-default btn-lg btn-block" onclick="location.href='{$orderData.returnurl}'">如果页面未自动跳转</button>
+        </div>
+    </div>
+</div>
+<script>
+    function onBridgeReady(){
+        WeixinJSBridge.invoke(
+            'getBrandWCPayRequest', {$payData|json_encode},
+            function(res){
+                if (res.err_msg == "get_brand_wcpay_request:ok") {
+                    layer.msg('支付成功!');
+                } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
+                    layer.msg('您取消了支付');
+                } else if (res.err_msg == "get_brand_wcpay_request:fail") {
+                    layer.msg('支付失败');
+                }
+                setTimeout(function () {
+                    location.href = '{$orderData.returnurl}';
+                }, 1500);
+            });
+    }
+    if (typeof WeixinJSBridge == "undefined"){
+        if( document.addEventListener ){
+            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
+        }else if (document.attachEvent){
+            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
+            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
+        }
+    }else{
+        onBridgeReady();
+    }
+</script>
+{elseif $type=='pc' /}
+<div class="container">
+    <div class="wechat">
+        <div class="row">
+            <div class="col-xs-12 col-sm-12">
+                <h2>
+                    <img src="__ADDON__/images/logo-wechat.png" alt="" height="32" class="pull-left" style="margin-right:5px;"> 微信支付
+                    <div class="wechat-time">
+                        请在 <span>60</span> 秒内完成支付
+                    </div>
+                </h2>
+
+                <div class="row">
+                    <div class="col-xs-12 col-sm-5">
+                        <div class="wechat-body">
+                            <div class="wechat-order clearfix">
+                                <p>订单标题:<em>{$data.body}</em></p>
+                                <p>订单编号:<em>{$data.out_trade_no}</em></p>
+                                <p>订单价格:<em class="wechat-price">¥{$data.total_fee/100}</em> 元</p>
+                            </div>
+                            <div class="wechat-qrcode">
+                                <img src="{:addon_url('epay/api/qrcode',[],false)}?text={$data.code_url}">
+                                <div class="expired hidden"></div>
+                                <div class="paid hidden"></div>
+                            </div>
+                            <div class="wechat-tips">
+                                <p>请使用微信扫一扫<br>扫描二维码支付</p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-sm-1"></div>
+                    <div class="col-sm-6 hidden-xs">
+                        <div class="wechat-scan">
+                            <img src="__ADDON__/images/tips.png" class="img-responsive" alt=""/>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
+
+<script>
+    var queryParams = {$data|json_encode};
+</script>
+{/if}

+ 212 - 0
addons/epay/view/index/index.html

@@ -0,0 +1,212 @@
+<!-- Header Carousel -->
+<header id="myCarousel" class="carousel slide">
+    <!-- Indicators -->
+    <ol class="carousel-indicators">
+        <li data-target="#myCarousel" data-slide-to="0" class="active"></li>
+        <li data-target="#myCarousel" data-slide-to="1"></li>
+        <li data-target="#myCarousel" data-slide-to="2"></li>
+        <li data-target="#myCarousel" data-slide-to="3"></li>
+    </ol>
+
+    <!-- Wrapper for slides -->
+    <div class="carousel-inner">
+        <div class="item active">
+            <a href="https://www.fastadmin.net/store/epay.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=9b59b6');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">微信支付宝整合插件</h1>
+                        <h2 class="display-4 text-white">打通微信、支付宝付款功能,支持CMS、余额充值、知识问答插件</h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/cms.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=3498db');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">CMS内容管理系统(含小程序)</h1>
+                        <h2 class="display-4 text-white">简单强大的内容管理系统,支持付费阅读、可自定义内容模型、标签、伪静态、区块、个性化模板等功能<br>包含完整的小程序CMS客户端,拥有完善的资讯模块、产品模块、评论模块、会员模块
+                        </h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/ask.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=3498db');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">知识问答系统</h1>
+                        <h2 class="display-4 text-white">简单强大的问答社区,拥有强大的问答模块、付费问答、话题归类、付费阅读</h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/blog.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=2ecc71');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">简单博客(含小程序)</h1>
+                        <h2 class="display-4 text-white">响应式博客插件,包含日志、评论、分类、归档,包含完善的后台管理和前台功能<br/>包含完整的小程序博客客户端,拥有博客日志列表、日志详情、评论等功能
+                        </h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+        <div class="item">
+            <a href="https://www.fastadmin.net/store/docs.html" target="_blank">
+                <div class="fill"
+                     style="background-image:url('https://bg.fastadmin.net?text=random&color=9c88ff');"></div>
+                <div class="carousel-body">
+                    <div class="container">
+                        <h1 class="display-1 text-white">Markdown文档生成插件</h1>
+                        <h2 class="display-4 text-white">将Github或本地Git环境中的Markdown文件解析并生成HTML,可在线浏览或导出为HTML离线浏览</h2>
+                    </div>
+                </div>
+            </a>
+        </div>
+    </div>
+</header>
+
+<!-- Page Content -->
+<div class="container">
+
+    <div class="row">
+        <div class="col-lg-12">
+            <h2 class="page-header">
+                开始接入
+            </h2>
+        </div>
+        <div class="col-md-4">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h4><i class="fa fa-fw fa-check"></i> 准备工作</h4>
+                </div>
+                <div class="panel-body">
+                    <p><a href="https://b.alipay.com/" target="_blank">申请支付宝相应的支付产品,并获取相关配置</a></p>
+                    <p><a href="https://pay.weixin.qq.com" target="_blank">申请微信相应的支付产品,并获取相关配置</a></p>
+                    <p>插件管理中配置相应的微信或支付宝参数</p>
+                    <a href="https://www.fastadmin.net/store/epay.html" target="_blank" class="btn btn-success"><i class="fa fa-download"></i> 下载插件</a>
+                </div>
+            </div>
+        </div>
+        <div class="col-md-4">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h4><i class="fa fa-fw fa-gift"></i> 开发工作</h4>
+                </div>
+                <div class="panel-body">
+                    <p>在你的PHP代码中调用相关代码进行支付,请参考控制器代码</p>
+                    <p>目前FastAdmin中已经支持
+                        <a href="https://www.fastadmin.net/store/cms.html" target="_blank">CMS内容管理系统</a>、
+                        <a href="https://www.fastadmin.net/store/recharge.html" target="_blank">会员充值插件</a>、
+                        <a href="https://www.fastadmin.net/store/ask.html" target="_blank">知识问答系统</a>
+                        支持无缝接入此插件</p>
+                    <a href="https://www.fastadmin.net/store/epay.html" target="_blank" class="btn btn-success"><i class="fa fa-list"></i> 查看文档</a>
+                </div>
+            </div>
+        </div>
+        <div class="col-md-4">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h4><i class="fa fa-fw fa-compass"></i> 立即体验</h4>
+                </div>
+                <div class="panel-body">
+                    <p>请选择对应的支付金额和支付方式</p>
+                    <p>
+                        <span class="input-group">
+                            <input type="number" name="amount" step="2" value="{:mt_rand(1, 99)/100}"
+                                   class="form-control" placeholder="请输入一个随机金额"/>
+                            <span class="input-group-addon" style="padding:0;width:100px;">
+                                <select class="form-control" name="method" id="method" style="border:none;height: 32px;">
+                                    <option value="web">PC网页支付</option>
+                                    <option value="wap">H5手机网页支付</option>
+                                    <option value="app">APP支付</option>
+                                    <option value="scan">扫码支付</option>
+                                    <option value="mp">公众号支付(不支持支付宝)</option>
+                                    <option value="miniapp">小程序支付(不支持支付宝)</option>
+                                </select>
+                            </span>
+                        </span>
+                    </p>
+                    <button data-type="alipay" class="btn btn-info btn-experience"><i class="fa fa-money"></i> 支付宝支付</button>
+                    <button data-type="wechat" class="btn btn-success btn-experience"><i class="fa fa-wechat"></i> 微信支付</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!-- /.row -->
+
+    <div class="row addonlist">
+        <div class="col-lg-12">
+            <h2 class="page-header">模块&插件</h2>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/blog.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/blog.png"
+                     alt="">
+                <p>简单博客(含小程序)</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/cms.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/cms.png"
+                     alt="">
+                <p>CMS内容管理系统(含小程序)</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/ask.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/ask.png" alt="">
+                <p>知识付费问答社区</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/docs.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/docs.png"
+                     alt="">
+                <p>文档生成模块</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/filemanager.html" target="_blank">
+                <img class="img-responsive img-addon img-hover"
+                     src="https://cdn.fastadmin.net/uploads/addons/filemanager.png" alt="">
+                <p>文件管理器插件</p>
+            </a>
+        </div>
+        <div class="col-md-4 col-sm-6">
+            <a href="https://www.fastadmin.net/store/qiniu.html" target="_blank">
+                <img class="img-responsive img-addon img-hover" src="https://cdn.fastadmin.net/uploads/addons/qiniu.png"
+                     alt="">
+                <p>七牛上传插件</p>
+            </a>
+        </div>
+    </div>
+    <!-- /.row -->
+
+    <hr>
+
+    <!-- Call to Action Section -->
+    <div class="well">
+        <div class="row">
+            <div class="col-md-8">
+                <p>感谢你对FastAdmin的支持!如果你在使用FastAdmin开发插件的过程中有任何疑问或需要寻求帮助,请前往FastAdmin交流社区与小伙伴们一起交流。</p>
+            </div>
+            <div class="col-md-4">
+                <a class="btn btn-lg btn-default btn-block" href="https://forum.fastadmin.net"
+                   target="_blank">立即前往社区</a>
+            </div>
+        </div>
+    </div>
+
+    <hr>
+
+</div>

+ 115 - 0
addons/epay/view/layout/default.html

@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <title>{$title} - FastAdmin</title>
+
+    <!-- Bootstrap Core CSS -->
+    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+
+    <!-- Custom CSS -->
+    <link href="__ADDON__/css/common.css" rel="stylesheet">
+
+    <!-- Plugin CSS -->
+    <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
+    <link href="https://cdn.staticfile.org/simple-line-icons/2.4.1/css/simple-line-icons.min.css" rel="stylesheet">
+
+    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+    <!--[if lt IE 9]>
+    <script src="https://cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
+    <script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
+    <![endif]-->
+
+</head>
+
+<body>
+
+<!-- Navigation -->
+<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+    <div class="container">
+        <!-- Brand and toggle get grouped for better mobile display -->
+        <div class="navbar-header">
+            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+                <span class="sr-only">Toggle navigation</span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </button>
+            <a class="navbar-brand" href="{:addon_url('epay/index/index')}">FastAdmin</a>
+        </div>
+        <!-- Collect the nav links, forms, and other content for toggling -->
+        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+            <ul class="nav navbar-nav navbar-right">
+                <li>
+                </li>
+                {if $user}
+                <li class="dropdown">
+                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">欢迎你! {$user.nickname}<b class="caret"></b></a>
+                    <ul class="dropdown-menu">
+                        <li>
+                            <a href="{:url('index/user/index')}">会员中心</a>
+                        </li>
+                        <li>
+                            <a href="{:url('index/user/profile')}">个人资料</a>
+                        </li>
+                        <li>
+                            <a href="{:url('index/user/logout')}">退出登录</a>
+                        </li>
+                    </ul>
+                </li>
+                {else /}
+                <li class="dropdown">
+                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">会员中心 <b class="caret"></b></a>
+                    <ul class="dropdown-menu">
+                        <li>
+                            <a href="{:url('index/user/login')}">登录</a>
+                        </li>
+                        <li>
+                            <a href="{:url('index/user/register')}">注册</a>
+                        </li>
+                    </ul>
+                </li>
+                {/if}
+            </ul>
+        </div>
+        <!-- /.navbar-collapse -->
+    </div>
+    <!-- /.container -->
+</nav>
+
+{__CONTENT__}
+
+<div class="container">
+    <!-- Footer -->
+    <footer>
+        <div class="row">
+            <div class="col-lg-12">
+                <hr>
+                <p>Copyright &copy; <a href="https://www.fastadmin.net">FastAdmin</a> 2017-1019</p>
+            </div>
+        </div>
+    </footer>
+
+</div>
+<!-- /.container -->
+
+<!-- jQuery -->
+<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
+
+<!-- Bootstrap Core JavaScript -->
+<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
+
+<script src="__CDN__/assets/libs/fastadmin-layer/dist/layer.js"></script>
+
+<script src="__ADDON__/js/common.js"></script>
+
+</body>
+
+</html>

+ 750 - 0
addons/unishop/Unishop.php

@@ -0,0 +1,750 @@
+<?php
+
+namespace addons\unishop;
+
+use app\common\library\Menu;
+use think\Addons;
+use think\Loader;
+
+/**
+ * 插件
+ */
+class unishop extends Addons
+{
+
+    /**
+     * 插件安装方法
+     * @return bool
+     */
+    public function install()
+    {
+        $menu = [
+            0 =>
+                [
+                    'name' => 'unishop',
+                    'title' => '喂喂商城',
+                    'icon' => 'fa fa-font-awesome',
+                    'remark' => '',
+                    'sublist' =>
+                        [
+                            0 =>
+                                [
+                                    'name' => 'unishop/ads',
+                                    'title' => '广告图管理',
+                                    'remark' => '',
+                                    'icon' => 'fa fa-buysellads',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/ads/index',
+                                                    'title' => '查看',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/ads/add',
+                                                    'title' => '添加',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            2 =>
+                                                [
+                                                    'name' => 'unishop/ads/edit',
+                                                    'title' => '编辑',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            3 =>
+                                                [
+                                                    'name' => 'unishop/ads/del',
+                                                    'title' => '删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            4 =>
+                                                [
+                                                    'name' => 'unishop/ads/multi',
+                                                    'title' => '批量更新',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                            1 =>
+                                [
+                                    'name' => 'unishop/category',
+                                    'title' => '分类管理',
+                                    'remark' => '注意:产品只支持二级分类。',
+                                    'icon' => 'fa fa-align-justify',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/category/index',
+                                                    'title' => '查看',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/category/del',
+                                                    'title' => '删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            2 =>
+                                                [
+                                                    'name' => 'unishop/category/edit',
+                                                    'title' => '修改',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            3 =>
+                                                [
+                                                    'name' => 'unishop/category/add',
+                                                    'title' => '添加',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            4 =>
+                                                [
+                                                    'name' => 'unishop/category/multi',
+                                                    'title' => '批量操作',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                            2 =>
+                                [
+                                    'name' => 'unishop/product',
+                                    'title' => '产品管理',
+                                    'remark' => '',
+                                    'icon' => 'fa fa-product-hunt',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/product/index',
+                                                    'title' => '查看',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/product/add',
+                                                    'title' => '添加',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            2 =>
+                                                [
+                                                    'name' => 'unishop/product/edit',
+                                                    'title' => '编辑',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            3 =>
+                                                [
+                                                    'name' => 'unishop/product/del',
+                                                    'title' => '删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            4 =>
+                                                [
+                                                    'name' => 'unishop/product/multi',
+                                                    'title' => '批量更新',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            5 =>
+                                                [
+                                                    'name' => 'unishop/product/recyclebin',
+                                                    'title' => '回收站',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            6 =>
+                                                [
+                                                    'name' => 'unishop/product/destroy',
+                                                    'title' => '真是删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            7 =>
+                                                [
+                                                    'name' => 'unishop/product/restore',
+                                                    'title' => '还原',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            8 =>
+                                                [
+                                                    'name' => 'unishop/product/copy',
+                                                    'title' => '复制',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                            3 =>
+                                [
+                                    'name' => 'unishop/delivery',
+                                    'title' => '运费模板',
+                                    'remark' => '',
+                                    'icon' => 'fa fa-delicious',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/delivery/index',
+                                                    'title' => '查看',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/delivery/add',
+                                                    'title' => '添加',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            2 =>
+                                                [
+                                                    'name' => 'unishop/delivery/edit',
+                                                    'title' => '编辑',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            3 =>
+                                                [
+                                                    'name' => 'unishop/delivery/del',
+                                                    'title' => '删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            4 =>
+                                                [
+                                                    'name' => 'unishop/delivery/multi',
+                                                    'title' => '批量更新',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                            4 =>
+                                [
+                                    'name' => 'unishop/config',
+                                    'title' => '系统配置',
+                                    'remark' => '更新配置缓存不会立即生效,如需立即生效请清空缓存。',
+                                    'icon' => 'fa fa-certificate',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/config/index',
+                                                    'title' => '查看',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/config/add',
+                                                    'title' => '添加',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            2 =>
+                                                [
+                                                    'name' => 'unishop/config/edit',
+                                                    'title' => '编辑',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            3 =>
+                                                [
+                                                    'name' => 'unishop/config/del',
+                                                    'title' => '删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            4 =>
+                                                [
+                                                    'name' => 'unishop/config/multi',
+                                                    'title' => '批量更新',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                            5 =>
+                                [
+                                    'name' => 'unishop/market',
+                                    'title' => '营销中心',
+                                    'remark' => '',
+                                    'icon' => 'fa fa-list',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/market/coupon',
+                                                    'title' => '优惠券管理',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-gratipay',
+                                                    'sublist' =>
+                                                        [
+                                                            0 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/index',
+                                                                    'title' => '查看',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            1 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/add',
+                                                                    'title' => '添加',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            2 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/edit',
+                                                                    'title' => '编辑',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            3 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/del',
+                                                                    'title' => '删除',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            4 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/multi',
+                                                                    'title' => '批量更新',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            5 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/recyclebin',
+                                                                    'title' => '回收站',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            6 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/destroy',
+                                                                    'title' => '真实删除',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            7 =>
+                                                                [
+                                                                    'name' => 'unishop/market/coupon/restore',
+                                                                    'title' => '还原',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/market/flash_sale',
+                                                    'title' => '秒杀管理',
+                                                    'remark' => '1,归档结束会把商品的真实售量和剩余数量同步到对应商品。
+2,已归档、已开始、上架状态的秒杀信息不能够修改。
+3,商品列表下架的商品也可以参与秒杀,建议复制一份商品专门提供给秒杀使用。
+4,秒杀进行中的商品可以单个下架。
+5,必须启动redis才能使用秒杀功能',
+                                                    'icon' => 'fa fa-flag',
+                                                    'sublist' =>
+                                                        [
+                                                            0 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/index',
+                                                                    'title' => '查看',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            1 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/add',
+                                                                    'title' => '添加',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            2 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/edit',
+                                                                    'title' => '编辑',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            3 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/del',
+                                                                    'title' => '删除',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            4 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/multi',
+                                                                    'title' => '批量更新',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            5 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/recyclebin',
+                                                                    'title' => '回收站',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            6 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/destroy',
+                                                                    'title' => '真实删除',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            7 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_sale/restroy',
+                                                                    'title' => '还原',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                            8 =>
+                                                                [
+                                                                    'name' => 'unishop/market/flash_product/multi',
+                                                                    'title' => '更新秒杀产品状态',
+                                                                    'icon' => 'fa fa-circle-o',
+                                                                ],
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                            6 =>
+                                [
+                                    'name' => 'unishop/order',
+                                    'title' => '订单管理',
+                                    'remark' => '1,货到付款默认支付状态为已支付,请留意发快递的时候选择收付模式。',
+                                    'icon' => 'fa fa-print',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/order/delivery',
+                                                    'title' => '物流管理',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/order/multi',
+                                                    'title' => '批量更新',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            2 =>
+                                                [
+                                                    'name' => 'unishop/order/restore',
+                                                    'title' => '还原',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            3 =>
+                                                [
+                                                    'name' => 'unishop/order/destroy',
+                                                    'title' => '真实删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            4 =>
+                                                [
+                                                    'name' => 'unishop/order/del',
+                                                    'title' => '删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            5 =>
+                                                [
+                                                    'name' => 'unishop/order/edit',
+                                                    'title' => '编辑',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            6 =>
+                                                [
+                                                    'name' => 'unishop/order/recyclebin',
+                                                    'title' => '回收站',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            7 =>
+                                                [
+                                                    'name' => 'unishop/order/index',
+                                                    'title' => '查看',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            8 =>
+                                                [
+                                                    'name' => 'unishop/order/product',
+                                                    'title' => '商品管理',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            9 =>
+                                                [
+                                                    'name' => 'unishop/order/refund',
+                                                    'title' => '退货',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                            7 =>
+                                [
+                                    'name' => 'unishop/evaluate',
+                                    'title' => '商品评价管理',
+                                    'remark' => '',
+                                    'icon' => 'fa fa-commenting',
+                                    'sublist' =>
+                                        [
+                                            0 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/index',
+                                                    'title' => '查看',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            1 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/recyclebin',
+                                                    'title' => '回收站',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            2 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/add',
+                                                    'title' => '添加',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            3 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/edit',
+                                                    'title' => '编辑',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            4 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/del',
+                                                    'title' => '删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            5 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/destroy',
+                                                    'title' => '真实删除',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            6 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/restore',
+                                                    'title' => '还原',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                            7 =>
+                                                [
+                                                    'name' => 'unishop/evaluate/multi',
+                                                    'title' => '批量更新',
+                                                    'remark' => '',
+                                                    'icon' => 'fa fa-circle-o',
+                                                    'sublist' =>
+                                                        [
+                                                        ],
+                                                ],
+                                        ],
+                                ],
+                        ],
+                ],
+        ];
+        Menu::create($menu);
+        return true;
+    }
+
+    /**
+     * 插件卸载方法
+     * @return bool
+     */
+    public function uninstall()
+    {
+        Menu::delete('unishop');
+        return true;
+    }
+
+    /**
+     * 插件启用方法
+     * @return bool
+     */
+    public function enable()
+    {
+        Menu::enable('unishop');
+        return true;
+    }
+
+    /**
+     * 插件禁用方法
+     * @return bool
+     */
+    public function disable()
+    {
+        Menu::disable('unishop');
+        return true;
+    }
+
+
+    public function appInit()
+    {
+        Loader::addNamespace('Hashids', ADDON_PATH . 'unishop' . DS . 'library' . DS . 'Hashids' . DS);
+        Loader::addNamespace('Godruoyi\Snowflake', ADDON_PATH . 'unishop' . DS . 'library' . DS . 'Godruoyi' . DS . 'Snowflake' . DS);
+        Loader::addNamespace('Yansongda', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS);
+    }
+}

+ 60 - 0
addons/unishop/application/admin/controller/unishop/Ads.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace app\admin\controller\unishop;
+
+use app\common\controller\Backend;
+
+/**
+ * 广告图管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Ads extends Backend
+{
+
+    /**
+     * Ads模型对象
+     * @var \app\admin\model\unishop\Ads
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\Ads;
+    }
+
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->where($where)
+                ->count();
+
+            $list = $this->model
+                ->with('product')
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $list = collection($list)->toArray();
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+}

+ 146 - 0
addons/unishop/application/admin/controller/unishop/Category.php

@@ -0,0 +1,146 @@
+<?php
+
+namespace app\admin\controller\unishop;
+
+use app\common\controller\Backend;
+use app\admin\model\unishop\Category as CategoryModel;
+use fast\Tree;
+
+/**
+ * 分类管理
+ *
+ * @icon fa fa-list
+ * @remark 用于统一管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加
+ */
+class Category extends Backend
+{
+
+    /**
+     * @var \app\common\model\Category
+     */
+    protected $model = null;
+    protected $categorylist = [];
+    protected $noNeedRight = ['selectpage'];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->request->filter(['strip_tags']);
+        $this->model = new CategoryModel();
+
+        $tree = Tree::instance();
+        $tree->init(collection($this->model->order('weigh asc,id desc')->select())->toArray(), 'pid');
+        $this->categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
+        $categorydata = [0 => ['type' => 'all', 'name' => __('None')]];
+        foreach ($this->categorylist as $k => $v) {
+            $categorydata[$v['id']] = $v;
+        }
+        $typeList = CategoryModel::getTypeList();
+        $this->view->assign("flagList", $this->model->getFlagList());
+        $this->view->assign("typeList", $typeList);
+        $this->view->assign("parentList", $categorydata);
+        $this->assignconfig('typeList', $typeList);
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $search = $this->request->request("search");
+            $type = $this->request->request("type");
+
+            //构造父类select列表选项数据
+            $list = [];
+
+            foreach ($this->categorylist as $k => $v) {
+                if ($search) {
+                    if ($v['type'] == $type && stripos($v['name'], $search) !== false || stripos($v['nickname'], $search) !== false) {
+                        if ($type == "all" || $type == null) {
+                            $list = $this->categorylist;
+                        } else {
+                            $list[] = $v;
+                        }
+                    }
+                } else {
+                    if ($type == "all" || $type == null) {
+                        $list = $this->categorylist;
+                    } elseif ($v['type'] == $type) {
+                        $list[] = $v;
+                    }
+                }
+            }
+
+            $total = count($list);
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+
+                if ($params['pid'] != $row['pid']) {
+                    $childrenIds = Tree::instance()->init(collection(\app\admin\model\unishop\Category::select())->toArray())->getChildrenIds($row['id']);
+                    if (in_array($params['pid'], $childrenIds)) {
+                        $this->error(__('Can not change the parent to child'));
+                    }
+                }
+
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
+                        $row->validate($validate);
+                    }
+                    $result = $row->allowField(true)->save($params);
+                    if ($result !== false) {
+                        $this->success();
+                    } else {
+                        $this->error($row->getError());
+                    }
+                } catch (\think\exception\PDOException $e) {
+                    $this->error($e->getMessage());
+                } catch (\think\Exception $e) {
+                    $this->error($e->getMessage());
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $this->view->assign("row", $row);
+
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * Selectpage搜索
+     *
+     * @internal
+     */
+    public function selectpage()
+    {
+        return parent::selectpage();
+    }
+}

+ 166 - 0
addons/unishop/application/admin/controller/unishop/Config.php

@@ -0,0 +1,166 @@
+<?php
+
+namespace app\admin\controller\unishop;
+
+use app\common\controller\Backend;
+use \app\admin\model\unishop\Config as ConfigModel;
+use think\Exception;
+/**
+ * 系统配置
+ *
+ * @icon fa fa-circle-o
+ */
+class Config extends Backend
+{
+
+    /**
+     * Config模型对象
+     * @var \app\admin\model\unishop\Config
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new ConfigModel;
+
+    }
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        $siteList = [];
+        $groupList = ConfigModel::getGroupList();
+        foreach ($groupList as $k => $v) {
+            $siteList[$k]['name'] = $k;
+            $siteList[$k]['title'] = $v;
+            $siteList[$k]['list'] = [];
+        }
+
+        foreach ($this->model->all() as $k => $v) {
+            if (!isset($siteList[$v['group']])) {
+                continue;
+            }
+            $value = $v->toArray();
+            $value['title'] = __($value['title']);
+            if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio'])) {
+                $value['value'] = explode(',', $value['value']);
+            }
+            $value['content'] = json_decode($value['content'], TRUE);
+            $value['tip'] = htmlspecialchars($value['tip']);
+            $siteList[$v['group']]['list'][] = $value;
+        }
+        $index = 0;
+        foreach ($siteList as $k => &$v) {
+            $v['active'] = !$index ? true : false;
+            $index++;
+        }
+        $this->view->assign('siteList', $siteList);
+        $this->view->assign('typeList', ConfigModel::getTypeList());
+        $this->view->assign('groupList', ConfigModel::getGroupList());
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                foreach ($params as $k => &$v) {
+                    $v = is_array($v) ? implode(',', $v) : $v;
+                }
+                try {
+                    if (in_array($params['type'], ['select', 'selects', 'checkbox', 'radio', 'array'])) {
+                        $params['content'] = json_encode(ConfigModel::decode($params['content']), JSON_UNESCAPED_UNICODE);
+                    } else {
+                        $params['content'] = '';
+                    }
+                    $result = $this->model->create($params);
+                    if ($result !== false) {
+                        $this->success();
+                    } else {
+                        $this->error($this->model->getError());
+                    }
+                } catch (Exception $e) {
+                    $this->error($e->getMessage());
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     * @param null $ids
+     */
+    public function edit($ids = NULL)
+    {
+        if ($this->request->isPost()) {
+            $row = $this->request->post("row/a");
+            if ($row) {
+                $configList = [];
+                foreach ($this->model->all() as $v) {
+                    if (isset($row[$v['name']])) {
+                        $value = $row[$v['name']];
+                        if (is_array($value) && isset($value['field'])) {
+                            $value = json_encode(ConfigModel::getArrayData($value), JSON_UNESCAPED_UNICODE);
+                        } else {
+                            $value = is_array($value) ? implode(',', $value) : $value;
+                        }
+                        $v['value'] = $value;
+                        $configList[] = $v->toArray();
+                    }
+                }
+                $this->model->allowField(true)->saveAll($configList);
+                $this->success();
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+    }
+
+    public function del($ids = "")
+    {
+        $name = $this->request->request('name');
+        $config = ConfigModel::getByName($name);
+        if ($config) {
+            try {
+                $config->delete();
+            } catch (Exception $e) {
+                $this->error($e->getMessage());
+            }
+            $this->success();
+        } else {
+            $this->error(__('Invalid parameters'));
+        }
+    }
+
+
+
+    /**
+     * 检测配置项是否存在
+     * @internal
+     */
+    public function check()
+    {
+        $params = $this->request->post("row/a");
+        if ($params) {
+
+            $config = $this->model->get($params);
+            if (!$config) {
+                return $this->success();
+            } else {
+                return $this->error(__('Name already exist'));
+            }
+        } else {
+            return $this->error(__('Invalid parameters'));
+        }
+    }
+
+
+
+}

+ 179 - 0
addons/unishop/application/admin/controller/unishop/Delivery.php

@@ -0,0 +1,179 @@
+<?php
+
+namespace app\admin\controller\unishop;
+
+use app\admin\model\unishop\DeliveryRule;
+use app\common\controller\Backend;
+use app\admin\model\unishop\Area;
+
+/**
+ * 运费模板
+ *
+ * @icon fa fa-circle-o
+ */
+class Delivery extends Backend
+{
+    /**
+     * Multi方法可批量修改的字段
+     */
+    protected $multiFields = 'switch';
+
+    /**
+     * Delivery模型对象
+     * @var \app\admin\model\unishop\Delivery
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\Delivery;
+        $this->view->assign("typeList", $this->model->getTypeList());
+
+        $areaData = json_encode(Area::getCacheTree());
+        $this->view->assign('areaData',$areaData);
+    }
+
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost())
+        {
+            $params = $this->request->post("row/a");
+            if ($params)
+            {
+                if(empty($params['area'])){
+                    $this->error('请添加可配送区域和运费');
+                }
+                if ($this->dataLimit && $this->dataLimitFieldAutoFill)
+                {
+                    $params[$this->dataLimitField] = $this->auth->id;
+                }
+                try
+                {
+                    //是否采用模型验证
+                    if ($this->modelValidate)
+                    {
+                        $name = basename(str_replace('\\', '/', get_class($this->model)));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : true) : $this->modelValidate;
+                        $this->model->validate($validate);
+                    }
+
+                    $result = $this->model::saveDelivery($params);
+                    if ($result !== false)
+                    {
+                        $this->success();
+                    }
+                    else
+                    {
+                        $this->error($this->model->getError());
+                    }
+
+                }
+                catch (\think\exception\PDOException $e)
+                {
+                    $this->error($e->getMessage());
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+
+
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * 删除
+     */
+    public function del($ids = "")
+    {
+        if ($ids)
+        {
+            $pk = $this->model->getPk();
+            $adminIds = $this->getDataLimitAdminIds();
+            if (is_array($adminIds))
+            {
+                $count = $this->model->where($this->dataLimitField, 'in', $adminIds);
+            }
+            $list = $this->model->where($pk, 'in', $ids)->select();
+            $count = 0;
+            foreach ($list as $k => $v)
+            {
+                (new DeliveryRule)->where('delivery_id',$v['id'])->delete();
+                $count += $v->delete();
+            }
+            if ($count)
+            {
+                $this->success();
+            }
+            else
+            {
+                $this->error(__('No rows were deleted'));
+            }
+        }
+        $this->error(__('Parameter %s can not be empty', 'ids'));
+    }
+
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = NULL)
+    {
+        $row = $this->model->detail($ids);
+        if (!$row)
+            $this->error(__('No Results were found'));
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds))
+        {
+            if (!in_array($row[$this->dataLimitField], $adminIds))
+            {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost())
+        {
+            $params = $this->request->post("row/a");
+            if ($params)
+            {
+                if(empty($params['area'])){
+                    $this->error('请添加可配送区域和运费');
+                }
+                try
+                {
+                    //是否采用模型验证
+                    if ($this->modelValidate)
+                    {
+                        $name = basename(str_replace('\\', '/', get_class($this->model)));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : true) : $this->modelValidate;
+                        $row->validate($validate);
+                    }
+                    $result = $this->model::editDelivery($params,$ids);
+                    if ($result !== false)
+                    {
+                        $this->success();
+                    }
+                    else
+                    {
+                        $this->error($row->getError());
+                    }
+                }
+                catch (\think\exception\PDOException $e)
+                {
+                    $this->error($e->getMessage());
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+
+
+        $this->view->assign("row", $row);
+        // 模板详情
+        return $this->view->fetch();
+    }
+
+}

+ 115 - 0
addons/unishop/application/admin/controller/unishop/Evaluate.php

@@ -0,0 +1,115 @@
+<?php
+
+namespace app\admin\controller\unishop;
+
+use app\common\controller\Backend;
+use think\Db;
+use think\Exception;
+use think\exception\PDOException;
+
+/**
+ * 商品评价管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Evaluate extends Backend
+{
+
+    /**
+     * Multi方法可批量修改的字段
+     */
+    protected $multiFields = 'toptime';
+
+    /**
+     * Evaluate模型对象
+     * @var \app\admin\model\unishop\Evaluate
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\Evaluate;
+        $this->view->assign("rateList", $this->model->getRateList());
+        $this->view->assign("anonymousList", $this->model->getAnonymousList());
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->where($where)
+                ->count();
+
+            $list = $this->model
+                ->with(['product'])
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $list = collection($list)->toArray();
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * 批量更新
+     */
+    public function multi($ids = "")
+    {
+        $ids = $ids ? $ids : $this->request->param("ids");
+        if ($ids) {
+            if ($this->request->has('params')) {
+                parse_str($this->request->post("params"), $values);
+                $values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields)));
+                if ($values || $this->auth->isSuperAdmin()) {
+                    $adminIds = $this->getDataLimitAdminIds();
+                    if (is_array($adminIds)) {
+                        $this->model->where($this->dataLimitField, 'in', $adminIds);
+                    }
+                    $count = 0;
+                    Db::startTrans();
+                    try {
+                        $list = $this->model->where($this->model->getPk(), 'in', $ids)->select();
+                        foreach ($list as $index => $item) {
+                            if ($values['toptime'] > 0) {
+                                $values['toptime'] = time();
+                            }
+                            $count += $item->allowField(true)->isUpdate(true)->save($values);
+                        }
+                        Db::commit();
+                    } catch (PDOException $e) {
+                        Db::rollback();
+                        $this->error($e->getMessage());
+                    } catch (Exception $e) {
+                        Db::rollback();
+                        $this->error($e->getMessage());
+                    }
+                    if ($count) {
+                        $this->success();
+                    } else {
+                        $this->error(__('No rows were updated'));
+                    }
+                } else {
+                    $this->error(__('You have no permission'));
+                }
+            }
+        }
+        $this->error(__('Parameter %s can not be empty', 'ids'));
+    }
+}

+ 509 - 0
addons/unishop/application/admin/controller/unishop/Order.php

@@ -0,0 +1,509 @@
+<?php
+
+namespace app\admin\controller\unishop;
+
+use app\admin\model\unishop\Area;
+use app\admin\model\unishop\OrderRefund;
+use app\common\controller\Backend;
+use think\Db;
+use think\Exception;
+use think\exception\PDOException;
+use think\exception\ValidateException;
+use think\Hook;
+
+/**
+ * 订单管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Order extends Backend
+{
+    /**
+     * 是否是关联查询
+     */
+    protected $relationSearch = true;
+
+    /**
+     * Order模型对象
+     * @var \app\admin\model\unishop\Order
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\Order;
+        $this->view->assign("payTypeList", $this->model->getPayTypeList());
+        $this->view->assign("statusList", $this->model->getStatusList());
+        $this->view->assign("refundStatusList", $this->model->getRefundStatusList());
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->alias('order')
+                ->join('user', 'user.id = order.user_id')
+                ->where($where)
+                ->count();
+
+            $list = $this->model
+                ->alias('order')
+                ->join('user', 'user.id = order.user_id')
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->field('order.*,user.username')
+                ->select();
+
+            $list = collection($list)->toArray();
+            foreach ($list as &$item) {
+                $item['id'] = (string)$item['id']; // 整形数字太大js会失准
+                $item['user'] = [];
+                $item['user']['username'] = $item['username'] ? $item['username'] : __('Tourist');
+
+                $item['have_paid_status'] = $item['have_paid'];
+                $item['have_delivered_status'] = $item['have_delivered'];
+                $item['have_received_status'] = $item['have_received'];
+                $item['have_commented_status'] = $item['have_commented'];
+            }
+            $result = array("total" => $total, "rows" => $list);
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * 生成查询所需要的条件,排序方式
+     * @param mixed $searchfields 快速查询的字段
+     * @param boolean $relationSearch 是否关联查询
+     * @return array
+     */
+    protected function buildparams($searchfields = null, $relationSearch = null)
+    {
+        $searchfields = is_null($searchfields) ? $this->searchFields : $searchfields;
+        $relationSearch = is_null($relationSearch) ? $this->relationSearch : $relationSearch;
+        $search = $this->request->get("search", '');
+        $filter = $this->request->get("filter", '');
+        $op = $this->request->get("op", '', 'trim');
+        $sort = $this->request->get("sort", "id");
+        $order = $this->request->get("order", "DESC");
+        $offset = $this->request->get("offset", 0);
+        $limit = $this->request->get("limit", 0);
+        $filter = (array)json_decode($filter, true);
+        $op = (array)json_decode($op, true);
+        $filter = $filter ? $filter : [];
+        $where = [];
+        $tableName = '';
+        if ($relationSearch) {
+            if (!empty($this->model)) {
+                $name = \think\Loader::parseName(basename(str_replace('\\', '/', get_class($this->model))));
+                $tableName = '' . $name . '.';
+            }
+            $sortArr = explode(',', $sort);
+            foreach ($sortArr as $index => & $item) {
+                $item = stripos($item, ".") === false ? $tableName . trim($item) : $item;
+            }
+            unset($item);
+            $sort = implode(',', $sortArr);
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            $where[] = [$tableName . $this->dataLimitField, 'in', $adminIds];
+        }
+        if ($search) {
+            $searcharr = is_array($searchfields) ? $searchfields : explode(',', $searchfields);
+            foreach ($searcharr as $k => &$v) {
+                $v = stripos($v, ".") === false ? $tableName . $v : $v;
+            }
+            unset($v);
+            $where[] = [implode("|", $searcharr), "LIKE", "%{$search}%"];
+        }
+        foreach ($filter as $k => $v) {
+            // 搜索订单状态
+            if (in_array($k, ['have_paid_status', 'have_delivered_status', 'have_received_status', 'have_commented_status'])) {
+                switch ($k) {
+                    case 'have_paid_status':
+                        $k = 'have_paid';
+                        break;
+                    case 'have_delivered_status':
+                        $k = 'have_delivered';
+                        break;
+                    case 'have_received_status':
+                        $k = 'have_received';
+                        break;
+                    case 'have_commented_status':
+                        $k = 'have_commented';
+                        break;
+                }
+                $v == 0 ? ($op[$k] = '=') : ($op[$k] = '>');
+                $v = 0;
+            }
+
+
+            $sym = isset($op[$k]) ? $op[$k] : '=';
+            if (stripos($k, ".") === false) {
+                $k = $tableName . $k;
+            }
+            $v = !is_array($v) ? trim($v) : $v;
+            $sym = strtoupper(isset($op[$k]) ? $op[$k] : $sym);
+            switch ($sym) {
+                case '=':
+                case '<>':
+                    $where[] = [$k, $sym, (string)$v];
+                    break;
+                case 'LIKE':
+                case 'NOT LIKE':
+                case 'LIKE %...%':
+                case 'NOT LIKE %...%':
+                    $where[] = [$k, trim(str_replace('%...%', '', $sym)), "%{$v}%"];
+                    break;
+                case '>':
+                case '>=':
+                case '<':
+                case '<=':
+                    $where[] = [$k, $sym, intval($v)];
+                    break;
+                case 'FINDIN':
+                case 'FINDINSET':
+                case 'FIND_IN_SET':
+                    $where[] = "FIND_IN_SET('{$v}', " . ($relationSearch ? $k : '`' . str_replace('.', '`.`', $k) . '`') . ")";
+                    break;
+                case 'IN':
+                case 'IN(...)':
+                case 'NOT IN':
+                case 'NOT IN(...)':
+                    $where[] = [$k, str_replace('(...)', '', $sym), is_array($v) ? $v : explode(',', $v)];
+                    break;
+                case 'BETWEEN':
+                case 'NOT BETWEEN':
+                    $arr = array_slice(explode(',', $v), 0, 2);
+                    if (stripos($v, ',') === false || !array_filter($arr)) {
+                        continue 2;
+                    }
+                    //当出现一边为空时改变操作符
+                    if ($arr[0] === '') {
+                        $sym = $sym == 'BETWEEN' ? '<=' : '>';
+                        $arr = $arr[1];
+                    } elseif ($arr[1] === '') {
+                        $sym = $sym == 'BETWEEN' ? '>=' : '<';
+                        $arr = $arr[0];
+                    }
+                    $where[] = [$k, $sym, $arr];
+                    break;
+                case 'RANGE':
+                case 'NOT RANGE':
+                    $v = str_replace(' - ', ',', $v);
+                    $arr = array_slice(explode(',', $v), 0, 2);
+                    if (stripos($v, ',') === false || !array_filter($arr)) {
+                        continue 2;
+                    }
+                    //当出现一边为空时改变操作符
+                    if ($arr[0] === '') {
+                        $sym = $sym == 'RANGE' ? '<=' : '>';
+                        $arr = $arr[1];
+                    } elseif ($arr[1] === '') {
+                        $sym = $sym == 'RANGE' ? '>=' : '<';
+                        $arr = $arr[0];
+                    }
+                    $where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' time', $arr];
+                    break;
+                case 'LIKE':
+                case 'LIKE %...%':
+                    $where[] = [$k, 'LIKE', "%{$v}%"];
+                    break;
+                case 'NULL':
+                case 'IS NULL':
+                case 'NOT NULL':
+                case 'IS NOT NULL':
+                    $where[] = [$k, strtolower(str_replace('IS ', '', $sym))];
+                    break;
+                default:
+                    break;
+            }
+        }
+        $where = function ($query) use ($where) {
+            foreach ($where as $k => $v) {
+                if (is_array($v)) {
+                    call_user_func_array([$query, 'where'], $v);
+                } else {
+                    $query->where($v);
+                }
+            }
+        };
+        return [$where, $sort, $order, $offset, $limit];
+    }
+
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
+                        $row->validateFailException(true)->validate($validate);
+                    }
+
+                    $updatetime = $this->request->post('updatetime');
+                    // 乐观锁
+                    $result = $this->model->allowField(true)->save($params, ['id' => $ids, 'updatetime' => $updatetime]);
+                    if (!$result) {
+                        throw new Exception(__('Data had been update before saved, close windows and do it again'));
+                    }
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were updated'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * 物流管理
+     */
+    public function delivery($ids = null)
+    {
+        $row = $this->model->get($ids, ['extend']);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost()) {
+            $result = false;
+            Db::startTrans();
+            try {
+                $express_number = $this->request->post('express_number');
+                $have_delivered = $express_number ? time() : 0;
+                $res1 = $row->allowField(true)->save(['have_delivered' => $have_delivered]);
+                $res2 = $row->extend->allowField(true)->save(['express_number' => $express_number]);
+                if ($res1 && $res2) {
+                    $result = true;
+                } else {
+                    throw new Exception(__('No rows were updated'));
+                }
+                Db::commit();
+            } catch (ValidateException $e) {
+                Db::rollback();
+                $this->error($e->getMessage());
+            } catch (PDOException $e) {
+                Db::rollback();
+                $this->error($e->getMessage());
+            } catch (Exception $e) {
+                Db::rollback();
+                $this->error($e->getMessage());
+            }
+            if ($result !== false) {
+                $this->success();
+            } else {
+                $this->error(__('No rows were updated'));
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $address = json_decode($row->extend->address_json,true);
+        if ($address) {
+            $area = (new Area)->whereIn('id',[$address['province_id'],$address['city_id'],$address['area_id']])->column('name', 'id');
+            $row['addressText'] = $area[$address['province_id']].$area[$address['city_id']].$area[$address['area_id']].' '.$address['address'];
+            $row['address'] = $address;
+        }
+
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 商品管理
+     */
+    public function product($ids = null)
+    {
+        if ($this->request->isPost()) {
+            $this->success();
+        }
+        $row = $this->model->get($ids, ['product','evaluate']);
+        $this->view->assign('product', $row->product);
+        $evaluate = [];
+        foreach ($row->evaluate as $key => $item) {
+            $evaluate[$item['product_id']] = $item;
+        }
+
+        $this->view->assign('order', $row);
+        $this->view->assign('evaluate', $evaluate);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 退货管理
+     */
+    public function refund($ids = null)
+    {
+        $row = $this->model->get($ids, ['refund']);
+        if ($row['status'] != \app\admin\model\unishop\Order::STATUS_REFUND) {
+            $this->error(__('This order is not returned'));
+        }
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+                $result = false;
+                Db::startTrans();
+                try {
+
+                    // 退款
+                    if($params['refund_action'] == 1) {
+                        $params['had_refund'] = time();
+                        Hook::add('order_refund', 'addons\\unishop\\behavior\\Order');
+                    }
+
+                    $updatetime = $this->request->post('updatetime');
+                    // 乐观锁
+                    $result = $this->model->allowField(true)->save($params, ['id' => $ids, 'updatetime' => $updatetime]);
+                    if (!$result) {
+                        throw new Exception(__('Data had been update before saved, close windows and do it again'));
+                    }
+
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    Hook::listen('order_refund', $row);
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were updated'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+
+        $products = $row->product;
+        $refundProducts = $row->refundProduct;
+        foreach ($products as &$product) {
+            $product['choose'] = 0;
+            foreach ($refundProducts as $refundProduct) {
+                if ($product['id'] == $refundProduct['order_product_id']) {
+                    $product['choose'] = 1;
+                }
+            }
+        }
+        if ($row->refund) {
+            $refund = $row->refund->append(['receiving_status_text', 'service_type_text'])->toArray();
+        } else {
+            $refund = [
+                'service_type' => 0,
+                'express_number' => -1,
+                'receiving_status_text' => -1,
+                'receiving_status' => -1,
+                'service_type_text' => -1,
+                'amount' => -1,
+                'reason_type' => -1,
+                'refund_explain' => -1,
+            ];
+        }
+        $this->view->assign('row', $row);
+        $this->view->assign('product', $products);
+        $this->view->assign('refund', $refund);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 回收站
+     */
+    public function recyclebin()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->onlyTrashed()
+                ->alias('order')
+                ->join('user', 'user.id = order.user_id')
+                ->where($where)
+                ->count();
+
+            $list = $this->model
+                ->onlyTrashed()
+                ->alias('order')
+                ->join('user', 'user.id = order.user_id')
+                ->where($where)
+                ->field('order.*,user.username')
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $list = collection($list)->toArray();
+            foreach ($list as &$item) {
+                $item['id'] = (string)$item['id'];
+                $item['user'] = [];
+                $item['user']['username'] = $item['username'] ? $item['username'] : __('Tourist');
+
+                $item['have_paid_status'] = $item['have_paid'];
+                $item['have_delivered_status'] = $item['have_delivered'];
+                $item['have_received_status'] = $item['have_received'];
+                $item['have_commented_status'] = $item['have_commented'];
+            }
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+}

+ 303 - 0
addons/unishop/application/admin/controller/unishop/Product.php

@@ -0,0 +1,303 @@
+<?php
+
+namespace app\admin\controller\unishop;
+
+use app\common\controller\Backend;
+use fast\Tree;
+use think\Db;
+use think\Exception;
+use think\exception\PDOException;
+use think\exception\ValidateException;
+
+/**
+ * 产品管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Product extends Backend
+{
+    /**
+     * 快速搜索时执行查找的字段
+     */
+    protected $searchFields = 'title';
+
+    /**
+     * Multi方法可批量修改的字段
+     */
+    protected $multiFields = 'switch';
+
+    /**
+     * product模型对象
+     * @var \app\admin\model\unishop\Product
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\Product;
+        $servers = \app\admin\model\unishop\Config::getByName('server');
+        $this->assign('servers',json_decode($servers['value']));
+    }
+
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+
+                if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
+                    $params[$this->dataLimitField] = $this->auth->id;
+                }
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
+                        $this->model->validateFailException(true)->validate($validate);
+                    }
+
+                    // 查看规格有没有添加
+                    if (!empty($params['use_spec']) && $params['use_spec'] == 1) {
+                        if (empty($params['specList']) || empty($params['specTableList']) || $params['specList'] == '""' || $params['specTableList'] == '""' || $params['specList'] == '[]' || $params['specTableList'] == '[]') {
+                            throw new Exception('规格不能为空');
+                        }
+                    }
+                    $params['server'] = implode(',',$params['server']);
+
+                    $result = $this->model->allowField(true)->save($params);
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were inserted'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->where($where)
+                ->count();
+
+            $list = $this->model
+                ->with([
+                    'category' => function($query) {
+                        $query->with('parent');
+                    },
+                    'delivery'
+                ])
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $list = collection($list)->toArray();
+            $result = array("total" => $total, "rows" => $list);
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+
+    public function selectpage(){
+        return parent::selectpage();
+    }
+
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
+                        $row->validateFailException(true)->validate($validate);
+                    }
+
+                    // 查看规格有没有添加
+                    if (!empty($params['use_spec']) && $params['use_spec'] == 1) {
+                        if (empty($params['specList']) || empty($params['specTableList']) || $params['specList'] == '""' || $params['specTableList'] == '""' || $params['specList'] == '[]' || $params['specTableList'] == '[]') {
+                            throw new Exception('规格不能为空');
+                        }
+                    }
+
+                    $params['server'] = implode(',',$params['server']);
+                    $result = $row->allowField(true)->save($params);
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were updated'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $this->view->assign("row", $row);
+
+        $this->view->assign('categoryList', $this->build_category_select('row[category_id]', 'product' ,$row->category_id));
+
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * 生成分类下拉列表框
+     * @param string $name
+     * @param string $type
+     * @param mixed $selected
+     * @param array $attr
+     * @param array $header
+     * @return string
+     */
+    protected function build_category_select($name, $type, $selected = null, $attr = [], $header = [])
+    {
+        $tree = Tree::instance();
+        $tree->init(\app\admin\model\unishop\Category::getCategoryArray($type), 'pid');
+        $categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
+        $categorydata = $header ? $header : [];
+        foreach ($categorylist as $k => $v) {
+            $categorydata[$v['id']] = $v['name'];
+        }
+        $attr = array_merge(['id' => "c-{$name}", 'class' => 'form-control selectpicker'], $attr);
+        return build_select($name, $categorydata, $selected, $attr);
+    }
+
+    /**
+     * 选择附件
+     */
+    public function select()
+    {
+        if ($this->request->isAjax()) {
+            return $this->index();
+        }
+        return $this->view->fetch();
+    }
+
+
+    /**
+     * 编辑
+     */
+    public function copy($ids = null)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
+                        $row->validateFailException(true)->validate($validate);
+                    }
+
+                    // 查看规格有没有添加
+                    if (!empty($params['use_spec']) && $params['use_spec'] == 1) {
+                        if (empty($params['specList']) || empty($params['specTableList']) || $params['specList'] == '""' || $params['specTableList'] == '""' || $params['specList'] == '[]' || $params['specTableList'] == '[]') {
+                            throw new Exception('规格不能为空');
+                        }
+                    }
+
+                    $params['server'] = implode(',',$params['server']);
+                    $result = $this->model->allowField(true)->save($params);
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were updated'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $this->view->assign("row", $row);
+
+        $this->view->assign('categoryList', $this->build_category_select('row[category_id]', 'product' ,$row->category_id));
+
+        return $this->view->fetch();
+    }
+}

+ 50 - 0
addons/unishop/application/admin/controller/unishop/market/Coupon.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace app\admin\controller\unishop\market;
+
+use app\common\controller\Backend;
+
+/**
+ * 优惠券管理
+ *
+ * @icon fa fa-circle-o
+ */
+class Coupon extends Backend
+{
+
+    /**
+     * 是否开启Validate验证
+     */
+    protected $modelValidate = true;
+
+    /**
+     * 是否开启模型场景验证
+     */
+    protected $modelSceneValidate = true;
+
+    /**
+     * Multi方法可批量修改的字段
+     */
+    protected $multiFields = 'switch';
+
+    /**
+     * Coupon模型对象
+     * @var \app\admin\model\unishop\Coupon
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\Coupon;
+
+    }
+
+    /**
+     * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
+     * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
+     * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
+     */
+
+
+}

+ 87 - 0
addons/unishop/application/admin/controller/unishop/market/FlashProduct.php

@@ -0,0 +1,87 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: zhengmingwei
+ * Date: 2020/3/1
+ * Time: 7:43 PM
+ */
+
+
+namespace app\admin\controller\unishop\market;
+
+
+use addons\unishop\extend\Redis;
+use app\common\controller\Backend;
+use think\Db;
+use think\Exception;
+use think\exception\PDOException;
+
+class FlashProduct extends Backend
+{
+    /**
+     * Multi方法可批量修改的字段
+     */
+    protected $multiFields = 'switch';
+
+    /**
+     * FlashSale模型对象
+     * @var \app\admin\model\unishop\FlashSale
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\FlashProduct;
+    }
+
+
+    /**
+     * 批量更新
+     */
+    public function multi($ids = "")
+    {
+
+        $ids = $ids ? $ids : $this->request->param("ids");
+        if ($ids) {
+            if ($this->request->has('params')) {
+                parse_str($this->request->post("params"), $values);
+                $values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields)));
+                if ($values || $this->auth->isSuperAdmin()) {
+                    $adminIds = $this->getDataLimitAdminIds();
+                    if (is_array($adminIds)) {
+                        $this->model->where($this->dataLimitField, 'in', $adminIds);
+                    }
+                    $count = 0;
+                    Db::startTrans();
+                    try {
+                        Redis::available();
+
+                        $list = $this->model->with(['product'])->where($this->model->getPk(), 'in', $ids)->select();
+
+                        $redis = new Redis();
+                        foreach ($list as $index => $item) {
+                            $redis->handler->hSet('flash_sale_' . $item['flash_id'] . '_' . $item['product_id'], 'switch', $values['switch']);
+                            $count += $item->allowField(true)->isUpdate(true)->save($values);
+                        }
+                        Db::commit();
+                    } catch (PDOException $e) {
+                        Db::rollback();
+                        $this->error($e->getMessage());
+                    } catch (Exception $e) {
+                        Db::rollback();
+                        $this->error($e->getMessage());
+                    }
+                    if ($count) {
+                        $this->success();
+                    } else {
+                        $this->error(__('No rows were updated'));
+                    }
+                } else {
+                    $this->error(__('You have no permission'));
+                }
+            }
+        }
+        $this->error(__('Parameter %s can not be empty', 'ids'));
+    }
+}

+ 503 - 0
addons/unishop/application/admin/controller/unishop/market/FlashSale.php

@@ -0,0 +1,503 @@
+<?php
+
+namespace app\admin\controller\unishop\market;
+
+use addons\unishop\extend\Redis;
+use app\admin\model\unishop\FlashProduct;
+use app\admin\model\unishop\OrderExtend;
+use app\admin\model\unishop\OrderProduct;
+use app\common\controller\Backend;
+use think\Db;
+use think\Exception;
+use think\exception\PDOException;
+use think\exception\ValidateException;
+use think\Hook;
+
+/**
+ * 秒杀管理
+ *
+ * @icon fa fa-circle-o
+ */
+class FlashSale extends Backend
+{
+    /**
+     * Multi方法可批量修改的字段
+     */
+    protected $multiFields = 'switch';
+
+    /**
+     * 是否开启Validate验证
+     */
+    protected $modelValidate = true;
+
+    /**
+     * 是否开启模型场景验证
+     */
+    protected $modelSceneValidate = true;
+
+    /**
+     * FlashSale模型对象
+     * @var \app\admin\model\unishop\FlashSale
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\unishop\FlashSale;
+        $this->view->assign("statusList", $this->model->getStatusList());
+    }
+
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $total = $this->model
+                ->where($where)
+                ->count();
+
+            $list = $this->model
+                ->with([
+                    'product'
+                ])
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            $list = collection($list)->toArray();
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+
+                if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
+                    $params[$this->dataLimitField] = $this->auth->id;
+                }
+
+                //Db::startTrans();
+                $this->model->startTrans();
+                try {
+
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
+                        $this->model->validateFailException(true)->validate($validate);
+                    }
+                    $params['starttime'] = strtotime($params['starttime']);
+                    $params['endtime'] = strtotime($params['endtime']);
+                    $this->model->allowField(true)->save($params);
+
+                    $products = [];
+                    if (isset($params['product'])) {
+                        $time = time();
+                        $redis = new Redis();
+                        foreach ($params['product'] as $item) {
+                            array_push($products, [
+                                'flash_id' => $this->model->id,
+                                'product_id' => $item['id'],
+                                'number' => $item['number'],
+                                'introduction' => $item['introduction'],
+                                'createtime' => $time,
+                                'updatetime' => $time,
+                            ]);
+
+                            // 是否上架
+                            if ($params['switch'] == \app\admin\model\unishop\FlashSale::SWITCH_YES) {
+                                $redis->handler->hMSet('flash_sale_' . $this->model->id . '_' . $item['id'], [
+                                    'flash_id' => $this->model->id,
+                                    'product_id' => $item['id'],
+                                    'id' => 0, // 新增的时候没有flash_product_id. 这个值无关紧要
+                                    'number' => $item['number'],
+                                    'sold' => 0, // 出售0个
+                                    'switch' => 1, // 默认全部上架
+                                    'starttime' => $params['starttime'],
+                                    'endtime' => $params['endtime'],
+                                ]);
+                            }
+
+                        }
+                    }
+                    if (empty($products)) {
+                        throw new ValidateException(__('Add at least one product'));
+                    }
+
+                    $flashProduct = new FlashProduct();
+                    $flashProduct->insertAll($products);
+
+                    //Db::commit();
+                    $this->model->commit();
+                    $this->success();
+                } catch (ValidateException $e) {
+                    //Db::rollback();
+                    $this->model->rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    //Db::rollback();
+                    $this->model->rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    //Db::rollback();
+                    $this->model->rollback();
+                    $this->error($e->getMessage());
+                }
+
+                $this->error(__('No rows were inserted'));
+
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        if ($this->request->isPost()) {
+
+            $params = $this->request->post("row/a");
+
+            if ($row['status'] == \app\admin\model\unishop\FlashSale::STATUS_YES) {
+                $this->error(__('Activity filed,can not change'));
+            }
+
+
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+                $result = false;
+                //Db::startTrans();
+                $this->model->startTrans();
+                try {
+                    if (isset($params['switch']) && $params['switch'] == \app\admin\model\unishop\FlashSale::SWITCH_YES) {
+                        Redis::available();
+                    }
+
+                    $row->checkItCanEdit();
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
+                        $row->validateFailException(true)->validate($validate);
+                    }
+                    $result = $row->allowField(true)->save($params);
+
+                    $products = [];
+                    if (isset($params['product'])) {
+                        $time = time();
+                        foreach ($params['product'] as $item) {
+                            array_push($products, [
+                                'flash_id' => $row['id'],
+                                'product_id' => $item['id'],
+                                'number' => $item['number'],
+                                'introduction' => $item['introduction'],
+                                'createtime' => $time,
+                                'updatetime' => $time,
+                            ]);
+                        }
+                    }
+                    if (empty($products)) {
+                        throw new ValidateException(__('Add at least one product'));
+                    }
+                    $flashProduct = new FlashProduct();
+                    $flashProduct->where(['flash_id' => $row['id']])->delete();
+                    $flashProduct->insertAll($products);
+
+                    //Db::commit();
+                    $this->model->commit();
+                } catch (ValidateException $e) {
+                    //Db::rollback();
+                    $this->model->rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    //Db::rollback();
+                    $this->model->rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    //Db::rollback();
+                    $this->model->rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were updated'));
+                }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 真实删除
+     */
+    public function destroy($ids = "")
+    {
+        $pk = $this->model->getPk();
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            $this->model->where($this->dataLimitField, 'in', $adminIds);
+        }
+        if ($ids) {
+            $this->model->where($pk, 'in', $ids);
+        }
+        $count = 0;
+        Db::startTrans();
+        try {
+            $list = $this->model->onlyTrashed()->select();
+            foreach ($list as $k => $v) {
+                FlashProduct::destroy(['flash_id' => $v->id]);
+                $count += $v->delete(true);
+            }
+            Db::commit();
+        } catch (PDOException $e) {
+            Db::rollback();
+            $this->error($e->getMessage());
+        } catch (Exception $e) {
+            Db::rollback();
+            $this->error($e->getMessage());
+        }
+        if ($count) {
+            $this->success();
+        } else {
+            $this->error(__('No rows were deleted'));
+        }
+        $this->error(__('Parameter %s can not be empty', 'ids'));
+    }
+
+    /**
+     * 归档结束
+     */
+    public function done($ids = null)
+    {
+        Db::startTrans();
+        try {
+
+            $row = $this->model->get($ids, [
+                'product' => function ($query) { // flashProduct
+                    // 悲观锁
+                    $query->lock(true)->with(['product']); //product
+                }
+            ]);
+            if (!$row) {
+                throw new Exception(__('No Results were found'));
+            }
+            if ($row['status'] == \app\admin\model\unishop\FlashSale::STATUS_YES) {
+                throw new Exception(__('Activity filed,can not change'));
+            }
+            $orderExtent = new OrderProduct();
+            $orders = $orderExtent->where(['flash_id' => $ids])
+                ->field('number,spec,product_id')->select();
+
+            if (!$orders) {
+                throw new Exception(__('No one buys'));
+            }
+
+            $productList = [];
+            foreach ($orders as $key => $order) {
+                if ($order['spec'] == '' || !$order['spec'] || empty($order['spec']) || is_null($order['spec'])) {
+                    // 没有规格的商品
+                    if (isset($productList[$order['product_id']])) {
+                        $productList[$order['product_id']] += $order['number'];
+                    } else {
+                        $productList[$order['product_id']] = $order['number'];
+                    }
+                } else {
+                    // 有规格的商品
+                    if (!isset($productList[$order['product_id']])) {
+                        $productList[$order['product_id']] = [];
+                    }
+
+                    if (!isset($productList[$order['product_id']][$order['spec']])) {
+                        $productList[$order['product_id']][$order['spec']] = $order['number'];
+                    } else {
+                        $productList[$order['product_id']][$order['spec']] += $order['number'];
+                    }
+
+                }
+            }
+
+            $products = $specNumber = [];
+            foreach ($productList as $product_id => $value) {
+                foreach ($row['product'] as $flashProduct) {
+                    if ($flashProduct['product_id'] == $product_id) {
+
+                        if (is_array($value)) {
+                            // 有规格 (这里循环的是同一个商品不同的规格)
+                            foreach ($value as $spec => $number) {
+                                $products[] = $flashProduct['product']->getData();
+                                $specNumber[$spec] = $number;
+                            }
+                        } else {
+                            // 无规格
+                            $products[] = $flashProduct['product']->getData();
+                            $specNumber[] = $value;
+                        }
+                    }
+                }
+            }
+
+            // 让秒杀下架
+            $row->status = \app\admin\model\unishop\FlashSale::STATUS_YES;
+            $row->switch = \app\admin\model\unishop\FlashSale::SWITCH_NO;
+            $row->save();
+
+            $this->model->activityFiled($products, $specNumber);
+
+            // 删除redis数据
+            $redis = new Redis();
+            foreach ($products as $product) {
+                $redis->handler->del('flash_sale_' . $ids . '_' . $product['id']);
+            }
+
+            Db::commit();
+            $this->success(__('Success'));
+        } catch (Exception $e) {
+            Db::rollback();
+            $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * 秒杀的产品列表
+     */
+    public function product()
+    {
+        $flashId = $this->request->request('flash_id');
+
+        $flashProductModel = new FlashProduct();
+
+        $products = $flashProductModel
+            ->with([
+                'product' => function ($query) {
+                    $query->with('category')->field(['id', 'title', 'category_id', 'image', 'stock']);
+                }
+            ])
+            ->where(['flash_id' => $flashId])
+            ->select();
+        $list = [];
+        foreach ($products as $key => $item) {
+            $list[$key]['flash_product_id'] = $item['id'];
+            $list[$key]['id'] = $item['product_id'];
+            $list[$key]['product_id'] = $item['product_id'];
+            $list[$key]['title'] = $item['product']['title'];
+            $list[$key]['image'] = $item['product']['image'];
+            $list[$key]['stock'] = $item['product']['stock'];
+            $list[$key]['sold'] = $item['sold'];
+            $list[$key]['switch'] = $item['switch'];
+            $list[$key]['category'] = $item['product']['category'];
+            $list[$key]['number'] = $item['number'];
+            $list[$key]['introduction'] = $item['introduction'];
+        }
+
+        $result = array("total" => count($list), "rows" => $list);
+
+        return json($result);
+    }
+
+
+    /**
+     * 批量更新
+     */
+    public function multi($ids = "")
+    {
+
+        $ids = $ids ? $ids : $this->request->param("ids");
+        if ($ids) {
+            if ($this->request->has('params')) {
+                parse_str($this->request->post("params"), $values);
+                $values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields)));
+                if ($values || $this->auth->isSuperAdmin()) {
+                    $adminIds = $this->getDataLimitAdminIds();
+                    if (is_array($adminIds)) {
+                        $this->model->where($this->dataLimitField, 'in', $adminIds);
+                    }
+                    $count = 0;
+                    Db::startTrans();
+                    try {
+                        Redis::available();
+
+                        $list = $this->model->with(['product'])->where($this->model->getPk(), 'in', $ids)->select();
+
+                        $redis = new Redis();
+                        foreach ($list as $index => $item) {
+                            if ($item['status'] == \app\admin\model\unishop\FlashSale::STATUS_YES) {
+                                throw new Exception(__('Activity filed,can not change'));
+                            }
+                            foreach ($item['product'] as $product) {
+                                // 上架
+                                if ($values['switch'] == \app\admin\model\unishop\FlashSale::SWITCH_YES) {
+                                    $redis->handler->hMSet('flash_sale_' . $product['flash_id'] . '_' . $product['product_id'], [
+                                        'flash_id' => $product['flash_id'],
+                                        'product_id' => $product['product_id'],
+                                        'id' => $product['id'],
+                                        'number' => $product['number'],
+                                        'sold' => $product['sold'],
+                                        'switch' => $product['switch'],
+                                        'starttime' => $item['starttime'],
+                                        'endtime' => $item['endtime'],
+                                    ]);
+                                } else {
+                                    // 下架
+                                    $redis->handler->del('flash_sale_' . $product['flash_id'] . '_' . $product['product_id']);
+                                }
+                            }
+                            $count += $item->allowField(true)->isUpdate(true)->save($values);
+                        }
+                        Db::commit();
+                    } catch (PDOException $e) {
+                        Db::rollback();
+                        $this->error($e->getMessage());
+                    } catch (Exception $e) {
+                        Db::rollback();
+                        $this->error($e->getMessage());
+                    }
+                    if ($count) {
+                        $this->success();
+                    } else {
+                        $this->error(__('No rows were updated'));
+                    }
+                } else {
+                    $this->error(__('You have no permission'));
+                }
+            }
+        }
+        $this->error(__('Parameter %s can not be empty', 'ids'));
+    }
+}

+ 13 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/ads.php

@@ -0,0 +1,13 @@
+<?php
+
+return [
+    'Image'         => '图片',
+    'product_id' => '产品id',
+    'Background'    => '背景色',
+    'Position'      => '位置',
+    'Status'        => '是否显现',
+    'Weigh'         => '权重',
+    'Createtime'    => '创建时间',
+    'Updatetime'    => '更新时间',
+    'Product Title'    => '产品名字',
+];

+ 19 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/category.php

@@ -0,0 +1,19 @@
+<?php
+
+return [
+    'Id'                                 => 'ID',
+    'Pid'                                => '父ID',
+    'Type'                               => '类型',
+    'All'                                => '全部',
+    'Image'                              => '图片',
+    'Keywords'                           => '关键字',
+    'Description'                        => '描述',
+    'Diyname'                            => '自定义名称',
+    'Createtime'                         => '创建时间',
+    'Updatetime'                         => '更新时间',
+    'Weigh'                              => '权重',
+    'Category warmtips'                  => '温馨提示:栏目类型请前往<b>常规管理</b>-><b>系统配置</b>-><b>字典配置</b>中进行管理',
+    'Can not change the parent to child' => '父组别不能是它的子组别',
+    'Status'                             => '状态',
+    'Product'                             => '产品',
+];

+ 62 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/config.php

@@ -0,0 +1,62 @@
+<?php
+
+return [
+    'Name'                        => '变量名',
+    'Tip'                         => '提示信息',
+    'Group'                       => '分组',
+    'Type'                        => '类型',
+    'Title'                       => '变量标题',
+    'Value'                       => '变量值',
+    'Basic'                       => '基础配置',
+    'Email'                       => '邮件配置',
+    'Attachment'                  => '附件配置',
+    'Dictionary'                  => '字典配置',
+    'User'                        => '会员配置',
+    'Example'                     => '示例分组',
+    'Extend'                      => '扩展属性',
+    'String'                      => '字符',
+    'Text'                        => '文本',
+    'Editor'                      => '编辑器',
+    'Number'                      => '数字',
+    'Date'                        => '日期',
+    'Time'                        => '时间',
+    'Datetime'                    => '日期时间',
+    'Image'                       => '图片',
+    'Images'                      => '图片(多)',
+    'File'                        => '文件',
+    'Files'                       => '文件(多)',
+    'Select'                      => '列表',
+    'Selects'                     => '列表(多选)',
+    'Switch'                      => '开关',
+    'Checkbox'                    => '复选',
+    'Radio'                       => '单选',
+    'Array'                       => '数组',
+    'Array key'                   => '键名',
+    'Array value'                 => '键值',
+    'Content'                     => '数据列表',
+    'Rule'                        => '校验规则',
+    'Site name'                   => '站点名称',
+    'Beian'                       => '备案号',
+    'Cdn url'                     => 'CDN地址',
+    'Version'                     => '版本号',
+    'Timezone'                    => '时区',
+    'Forbidden ip'                => '禁止IP',
+    'Languages'                   => '语言',
+    'Fixed page'                  => '后台固定页',
+    'Category type'               => '分类类型',
+    'Config group'                => '配置分组',
+    'Mail type'                   => '邮件发送方式',
+    'Mail smtp host'              => 'SMTP服务器',
+    'Mail smtp port'              => 'SMTP端口',
+    'Mail smtp user'              => 'SMTP用户名',
+    'Mail smtp password'          => 'SMTP密码',
+    'Mail vertify type'           => 'SMTP验证方式',
+    'Mail from'                   => '发件人邮箱',
+    'Name already exist'          => '变量名称已经存在',
+    'Send a test message'         => '发送测试邮件',
+    'This is a test mail content' => '这是一封来自FastAdmin校验邮件,用于校验邮件配置是否正常!',
+    'This is a test mail'         => '这是一封来自FastAdmin的邮件',
+    'Please input your email'     => '请输入测试接收者邮箱',
+    'Wechat'          => '微信配置',
+    'Ali'           => '支付宝配置',
+];

+ 19 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/delivery.php

@@ -0,0 +1,19 @@
+<?php
+
+return [
+    'User_id'    => '用户id',
+    'Name'       => '名字',
+    'Createtime' => '创建时间',
+    'Updatetime' => '更新时间',
+    'Quantity'                      => '数量',
+    'Weight'                        => '重量',
+    'First'                         => '首重(KG) / 首件(个)',
+    'First fee'                     => '运费(元)',
+    'Additional'                    => '续重(KG) / 续件(个)',
+    'Additional fee'                => '续费(元)',
+    'Type'                          => '计费方式',
+    'Delivery area'                 => '配送区域',
+    'Can delivery area'             => '可配送区域',
+    'Min buy'             => '至少购买量',
+    'Switch'             => '上架',
+];

+ 24 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/evaluate.php

@@ -0,0 +1,24 @@
+<?php
+
+return [
+    'Product_id'  => '产品id',
+    'User_id'     => '用户id',
+    'Comment'     => '评论',
+    'Rate'        => '评价级别',
+    'Rate 1'      => '极差',
+    'Rate 2'      => '差评',
+    'Rate 3'      => '中评',
+    'Rate 4'      => '好评',
+    'Rate 5'      => '极好',
+    'Order_id'    => '订单id',
+    'Spec'        => '商品规格/购买类型',
+    'Anonymous'   => '是否匿名',
+    'Anonymous 0' => '否',
+    'Anonymous 1' => '是',
+    'Toptime'     => '是否置顶',
+    'Top time'     => '置顶时间',
+    'Createtime'  => '创建时间',
+    'Updatetime'  => '更新时间',
+    'Deletetime'  => '删除时间',
+    'Product title'  => '商品标题',
+];

+ 14 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/market/coupon.php

@@ -0,0 +1,14 @@
+<?php
+
+return [
+    'Title'      => '优惠券名称',
+    'Switch'     => '是否上架',
+    'Least'      => '消费多少可用',
+    'Value'      => '优惠券金额',
+    'Starttime'  => '开始时间',
+    'Endtime'    => '结束时间',
+    'Createtime' => '创建时间',
+    'Updatetime' => '更新时间',
+    'Deletetime' => '删除时间',
+    'Weigh'      => '权重'
+];

+ 35 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/market/flash_sale.php

@@ -0,0 +1,35 @@
+<?php
+
+return [
+    'Title'       => '场景名',
+    'Introdution' => '场景介绍',
+    'Switch'      => '上架状态',
+    'Switch off'    => '下架',
+    'Switch on'    => '上架',
+    'Status'      => '归档结束',
+    'Status 0'    => '否',
+    'Status 1'    => '是',
+    'Starttime'   => '开始时间',
+    'Endtime'     => '结束时间',
+    'Createtime'  => '创建时间',
+    'Updatetime'  => '更新时间',
+    'Add Product' => '添加产品',
+    'Product id' => '产品ID',
+    'Product title' => '产品标题',
+    'Product image' => '主图',
+    'Product stock' => '库存',
+    'Category name' => '分类名称',
+    'Flash number' => '秒杀数量',
+    'introduction' => '营销短语',
+    'Add at least one product' => '至少添加一个产品',
+    'Product number' => '产品数量',
+    'Flash done' => '归档结束',
+    'Nothing' => '无',
+    'Sold' => '已售',
+    'Not started' => '未开始',
+    'On going' => '进行中',
+    'Has ended' => '已结束',
+    'Current state' => '当前状态',
+    'Activity filed,can not change' => '活动已归档,无法修改',
+    'No one buys' => '一个都没有卖出',
+];

+ 57 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/order.php

@@ -0,0 +1,57 @@
+<?php
+
+return [
+    'User_id'          => '用户id',
+    'Out_trade_no'     => '订单编号',
+    'Order_price'      => '订单金额',
+    'Discount_price'   => '优惠价格',
+    'Delivery_price'   => '运费价格',
+    'Total_price'      => '实付价格',
+    'Pay_type'         => '付款方式',
+    'Online'           => '在线支付',
+    'Offline'          => '到货付款',
+    'wxPay'            => '微信付款',
+    'aliPay'           => '支付宝付',
+    'Ip'               => '下单ip',
+    'Remark'           => '订单备注',
+    'Status'           => '订单状态',
+    'Refund'        => '退货',
+    'Cancel'         => '取消订单',
+    'Normal'         => '正常',
+    'Have_paid'        => '已支付',
+    'Yes'      => '是',
+    'No'      => '否',
+    'Have_delivered'   => '已发货',
+    'Have_commented'   => '已评论',
+    'Have_received'    => '已收货',
+    'Pay time'          => '支付时间',
+    'Time: %s'          => '时间: %s',
+    'Delivered time'          => '发货时间',
+    'Received time'          => '收货时间',
+    'Commented time'          => '评论时间',
+    'Createtime'       => '创建时间',
+    'Updatetime'       => '更新时间',
+    'Deletetime'       => '删除时间',
+    'Visitor'          => '游客',
+    'User name'          => '用户名',
+    'Delivere'          => '物流',
+    'Comment'          => '评论',
+    'Data had been update before saved, close windows and do it again' => '数据在保存之前有变动,请关闭窗口再试一次',
+    'Product'           => '商品',
+    'Delivery status'           => '发货状态',
+    'Express number'           => '快递单号',
+    'Confirm delivery'           => '确认发货',
+    'Consignee address'           => '收货人地址',
+    'Consignee name'           => '收货人名称',
+    'Consignee mobile'           => '收货人电话',
+    'Refund status'           => '售后状态',
+    'Apply'           => '申请中',
+    'Pass'           => '通过',
+    'Pass and left User delivery'           => '通过(让用户发货)',
+    'Waiting for shipment'           => '等待发货',
+    'Refuse'           => '拒绝',
+    'This order is not returned'           => '这个订单没有退货',
+    'Withdraw money'           => '退款',
+    'Not withdraw money'           => '不退款',
+    'Tourist'           => '游客',
+];

+ 37 - 0
addons/unishop/application/admin/lang/zh-cn/unishop/product.php

@@ -0,0 +1,37 @@
+<?php
+
+return [
+    'Category_id'    => '分类ID',
+    'Category name'    => '分类名称',
+    'Title'          => '产品标题',
+    'Image'          => '主图',
+    'Images'         => '组图',
+    'Desc'           => '详情',
+    'Sales'          => '虚拟销售量',
+    'Stock'          => '库存',
+    'Look'           => '虚拟浏览量',
+    'Speclist'       => '规格属性',
+    'Specchildlist'  => '规格属性值',
+    'Weigh'          => '权重',
+    'Delivery_id'    => '运费模板',
+    'Assign delivery'    => '指定运费模板',
+    'Switch'         => '是否上架',
+    'Server'         => '售后服务',
+    'Spec'         => '规格',
+    'Updatetime'     => '更新时间',
+    'use_spec'     => '选用规格',
+    'Createtime'     => '创建时间',
+    'Market price'     => '市场价',
+    'Sales price'     => '销售价',
+    'Lower sales price'     => '最低销售价',
+    'Lower market price'     => '最低市场价',
+    'Real sales'     => '真实销量',
+    'Real look'     => '真实浏览量',
+    'lower price'     => '最低价',
+    'Total stock'     => '总库存',
+    'Total sales'     => '总虚拟销量',
+    'Deletetime'     => '删除时间',
+    'No buy yet'     => '下单未支付量',
+    'Evaluate'     => '评价',
+    'Copy'     => '复制',
+];

+ 49 - 0
addons/unishop/application/admin/model/unishop/Ads.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+
+class Ads extends Model
+{
+
+
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_ads';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = false;
+
+    // 追加属性
+    protected $append = [
+
+    ];
+
+    protected static function init()
+    {
+        self::afterInsert(function ($row) {
+            $pk = $row->getPk();
+            $row->getQuery()->where($pk, $row[$pk])->update(['weigh' => $row[$pk]]);
+        });
+
+    }
+
+
+    public function product(){
+        return $this->belongsTo('product')->field('id,title');
+    }
+
+
+
+
+
+}

+ 116 - 0
addons/unishop/application/admin/model/unishop/Area.php

@@ -0,0 +1,116 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: zhengmingwei
+ * Date: 2019-07-14
+ * Time: 22:39
+ */
+
+namespace app\admin\model\unishop;
+
+use think\Cache;
+use think\Model;
+
+/**
+ * 地区模型
+ * Class Region
+ * @package app\common\model
+ */
+class Area extends Model
+{
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_area';
+
+    /**
+     * 根据id获取地区名称
+     * @param $id
+     * @return string
+     */
+    public static function getNameById($id)
+    {
+        $region = self::getCacheAll();
+        return $region[$id]['name'];
+    }
+
+    /**
+     * 根据名称获取地区id
+     * @param $name
+     * @param int $level
+     * @param int $pid
+     * @return unied
+     */
+    public static function getIdByName($name, $level = 0, $pid = 0)
+    {
+        return static::useGlobalScope(false)->where(compact('name', 'level', 'pid'))
+            ->value('id') ?: static::add($name, $level, $pid);
+    }
+
+    /**
+     * @param $name
+     * @param int $level
+     * @param int $pid
+     * @return unied
+     */
+    private static function add($name, $level = 0, $pid = 0)
+    {
+        $model = new static;
+        $model->save(compact('name', 'level', 'pid'));
+        Cache::rm('area');
+        return $model->getLastInsID();
+    }
+
+    /**
+     * 获取所有地区(树状结构)
+     * @return unied
+     */
+    public static function getCacheTree()
+    {
+        return self::regionCache()['tree'];
+    }
+
+    /**
+     * 获取所有地区
+     * @return unied
+     */
+    public static function getCacheAll()
+    {
+        return self::regionCache()['all'];
+    }
+
+    /**
+     * 获取地区缓存
+     * @return unied
+     */
+    private static function regionCache()
+    {
+        if (!Cache::get('area')) {
+            // 所有地区
+            $all = $allData = self::useGlobalScope(false)->column('id, pid, name, level', 'id');
+            // 格式化
+            $tree = [];
+            foreach ($allData as $pKey => $province) {
+                if ($province['level'] === 1) {    // 省份
+                    $tree[$province['id']] = $province;
+                    unset($allData[$pKey]);
+                    foreach ($allData as $cKey => $city) {
+                        if ($city['level'] === 2 && $city['pid'] === $province['id']) {    // 城市
+                            $tree[$province['id']]['city'][$city['id']] = $city;
+                            unset($allData[$cKey]);
+                            foreach ($allData as $rKey => $region) {
+                                if ($region['level'] === 3 && $region['pid'] === $city['id']) {    // 地区
+                                    $tree[$province['id']]['city'][$city['id']]['region'][$region['id']] = $region;
+                                    unset($allData[$rKey]);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            Cache::set('area', compact('all', 'tree'));
+        }
+        return Cache::get('area');
+    }
+
+}

+ 94 - 0
addons/unishop/application/admin/model/unishop/Category.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+/**
+ * 分类模型
+ */
+class Category extends Model
+{
+
+    // 表名
+    protected $name = 'unishop_category';
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    // 追加属性
+    protected $append = [
+        'type_text',
+        'flag_text',
+    ];
+
+    protected static function init()
+    {
+        self::afterInsert(function ($row) {
+            $row->save(['weigh' => $row['id']]);
+        });
+    }
+
+    public function setFlagAttr($value, $data)
+    {
+        return is_array($value) ? implode(',', $value) : $value;
+    }
+
+    /**
+     * 读取分类类型
+     * @return array
+     */
+    public static function getTypeList()
+    {
+        return ['product'=>__('Product')];
+    }
+
+    public function getTypeTextAttr($value, $data)
+    {
+        $value = $value ? $value : $data['type'];
+        $list = $this->getTypeList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    public function getFlagList()
+    {
+        return ['index' => __('Index')];
+    }
+
+    public function getFlagTextAttr($value, $data)
+    {
+        $value = $value ? $value : $data['flag'];
+        $valueArr = explode(',', $value);
+        $list = $this->getFlagList();
+        return implode(',', array_intersect_key($list, array_flip($valueArr)));
+    }
+
+    /**
+     * 读取分类列表
+     * @param string $type   指定类型
+     * @param string $status 指定状态
+     * @return array
+     */
+    public static function getCategoryArray($type = null, $status = null)
+    {
+        $list = collection(self::where(function ($query) use ($type, $status) {
+            if (!is_null($type)) {
+                $query->where('type', '=', $type);
+            }
+            if (!is_null($status)) {
+                $query->where('status', '=', $status);
+            }
+        })->order('weigh', 'desc')->select())->toArray();
+        return $list;
+    }
+
+    /**
+     * 关联上一级
+     * @return \think\model\relation\HasOne
+     */
+    public function parent()
+    {
+        return $this->hasOne('category', 'id', 'pid');
+    }
+}

+ 169 - 0
addons/unishop/application/admin/model/unishop/Config.php

@@ -0,0 +1,169 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Cache;
+use think\Model;
+
+
+class Config extends Model
+{
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_config';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = false;
+    // 定义时间戳字段名
+    protected $createTime = false;
+    protected $updateTime = false;
+    // 追加属性
+    protected $append = [
+    ];
+
+    /**
+     * 读取配置类型
+     * @return array
+     */
+    public static function getTypeList()
+    {
+        $typeList = [
+            'string'   => __('String'),
+            'text'     => __('Text'),
+            'editor'   => __('Editor'),
+            'number'   => __('Number'),
+            'date'     => __('Date'),
+            'time'     => __('Time'),
+            'datetime' => __('Datetime'),
+            'select'   => __('Select'),
+            'selects'  => __('Selects'),
+            'image'    => __('Image'),
+            'images'   => __('Images'),
+            'file'     => __('File'),
+            'files'    => __('Files'),
+            'switch'   => __('Switch'),
+            'checkbox' => __('Checkbox'),
+            'radio'    => __('Radio'),
+            'array'    => __('Array'),
+            'custom'   => __('Custom'),
+        ];
+        return $typeList;
+    }
+
+    public static function getRegexList()
+    {
+        $regexList = [
+            'required' => '必选',
+            'digits'   => '数字',
+            'letters'  => '字母',
+            'date'     => '日期',
+            'time'     => '时间',
+            'email'    => '邮箱',
+            'url'      => '网址',
+            'qq'       => 'QQ号',
+            'IDcard'   => '身份证',
+            'tel'      => '座机电话',
+            'mobile'   => '手机号',
+            'zipcode'  => '邮编',
+            'chinese'  => '中文',
+            'username' => '用户名',
+            'password' => '密码'
+        ];
+        return $regexList;
+    }
+
+    /**
+     * 读取分类分组列表
+     * @return array
+     */
+    public static function getGroupList()
+    {
+        $groupList = self::getByName('configgroup');
+        $groupList = json_decode($groupList['value'],true);
+        foreach ($groupList as $k => &$v) {
+            $v = __($v);
+        }
+        return $groupList;
+    }
+
+    public static function getArrayData($data)
+    {
+        if (!isset($data['value'])) {
+            $result = [];
+            foreach ($data as $index => $datum) {
+                $result['field'][$index] = $datum['key'];
+                $result['value'][$index] = $datum['value'];
+            }
+            $data = $result;
+        }
+        $fieldarr = $valuearr = [];
+        $field = isset($data['field']) ? $data['field'] : (isset($data['key']) ? $data['key'] : []);
+        $value = isset($data['value']) ? $data['value'] : [];
+        foreach ($field as $m => $n) {
+            if ($n != '') {
+                $fieldarr[] = $field[$m];
+                $valuearr[] = $value[$m];
+            }
+        }
+        return $fieldarr ? array_combine($fieldarr, $valuearr) : [];
+    }
+
+    /**
+     * 将字符串解析成键值数组
+     * @param string $text
+     * @return array
+     */
+    public static function decode($text, $split = "\r\n")
+    {
+        $content = explode($split, $text);
+        $arr = [];
+        foreach ($content as $k => $v) {
+            if (stripos($v, "|") !== false) {
+                $item = explode('|', $v);
+                $arr[$item[0]] = $item[1];
+            }
+        }
+        return $arr;
+    }
+
+    /**
+     * 将键值数组转换为字符串
+     * @param array $array
+     * @return string
+     */
+    public static function encode($array, $split = "\r\n")
+    {
+        $content = '';
+        if ($array && is_array($array)) {
+            $arr = [];
+            foreach ($array as $k => $v) {
+                $arr[] = "{$k}|{$v}";
+            }
+            $content = implode($split, $arr);
+        }
+        return $content;
+    }
+
+    /**
+     * 本地上传配置信息
+     * @return array
+     */
+    public static function upload()
+    {
+        $uploadcfg = config('upload');
+
+        $upload = [
+            'cdnurl'    => $uploadcfg['cdnurl'],
+            'uploadurl' => $uploadcfg['uploadurl'],
+            'bucket'    => 'local',
+            'maxsize'   => $uploadcfg['maxsize'],
+            'mimetype'  => $uploadcfg['mimetype'],
+            'multipart' => [],
+            'multiple'  => $uploadcfg['multiple'],
+        ];
+        return $upload;
+    }
+
+}

+ 72 - 0
addons/unishop/application/admin/model/unishop/Coupon.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class Coupon extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_coupon';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+        'starttime_text',
+        'endtime_text'
+    ];
+
+    // 是否上架?
+    const SWITCH_ON = 1; //是
+    const SWITCH_OFF = 0; //否
+
+    protected static function init()
+    {
+        self::afterInsert(function ($row) {
+            $pk = $row->getPk();
+            $row->getQuery()->where($pk, $row[$pk])->update(['weigh' => $row[$pk]]);
+        });
+    }
+
+
+
+
+
+    public function getStarttimeTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['starttime']) ? $data['starttime'] : '');
+        return is_numeric($value) ? date("Y-m-d", $value) : $value;
+    }
+
+
+    public function getEndtimeTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['endtime']) ? $data['endtime'] : '');
+        return is_numeric($value) ? date("Y-m-d", $value) : $value;
+    }
+
+    protected function setStarttimeAttr($value)
+    {
+        return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+    protected function setEndtimeAttr($value)
+    {
+        return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+
+}

+ 162 - 0
addons/unishop/application/admin/model/unishop/Delivery.php

@@ -0,0 +1,162 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+use think\Db;
+use think\Exception;
+use think\Request;
+
+class Delivery extends Model
+{
+
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_delivery';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = false;
+
+    // 是否上架
+    const SWITCH_YES = 1;
+    const SWITCH_NO = 0;
+
+    // 追加属性
+    protected $append = [
+        'type_text'
+    ];
+
+    public function getTypeList()
+    {
+        return ['quantity' => __('Quantity'), 'weight' => __('Weight')];
+    }
+
+    public function getTypeTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['type']) ? $data['type'] : '');
+        $list = $this->getTypeList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    /**
+     * 关联配送模板区域及运费
+     * @return \think\model\relation\HasMany
+     */
+    public function rule()
+    {
+        return $this->hasMany('DeliveryRule','delivery_id','id');
+    }
+
+    /**
+     * 计费方式
+     * @param $value
+     * @return array
+     */
+    public function getMethodAttr($value)
+    {
+        $method = ['quantity' => '按件数', 'weight' => '按重量'];
+        return ['text' => $method[$value], 'value' => $value];
+    }
+
+    /**
+     * 获取全部
+     * @return unied
+     */
+    public static function getAll()
+    {
+        $model = new static;
+        return $model->select();
+    }
+
+    /**
+     * 获取列表
+     * @return \think\Paginator
+     * @throws \think\exception\DbException
+     */
+    public function getList()
+    {
+        return $this->with(['rule'])
+            ->order(['sort' => 'asc'])
+            ->paginate(15, false, [
+                'query' => Request::instance()->request()
+            ]);
+    }
+
+    /**
+     * 运费模板详情
+     * @param $delivery_id
+     * @return null|static
+     * @throws \think\exception\DbException
+     */
+    public static function detail($delivery_id)
+    {
+        return self::get($delivery_id, ['rule']);
+    }
+
+    /**
+     * 保存运费模板
+     * @param array $params
+     * @return false|int|void
+     */
+    public static function saveDelivery($params){
+
+        Db::startTrans();
+        try{
+            $result = self::create($params,true);
+            $data['delivery_id'] = $result['id'];
+            foreach ($params['first'] as $k => $v){
+                $data['area'] = $params['area'][$k];
+                $data['first'] = $params['first'][$k];
+                $data['first_fee'] = $params['first_fee'][$k];
+                $data['additional'] = $params['additional'][$k];
+                $data['additional_fee'] = $params['additional_fee'][$k];
+                DeliveryRule::create($data,true);
+            }
+        }catch (Exception $e){
+            Db::rollback();
+            return false;
+        }
+        Db::commit();
+        return true;
+    }
+
+    /**
+     * 修改运费模板
+     * @param $params
+     * @param $ids
+     * @return bool
+     */
+    public static function editDelivery($params,$ids){
+        Db::startTrans();
+        try{
+            $model = new static;
+            $model->update($params,['id'=>$ids],true);
+            DeliveryRule::where('delivery_id',$ids)->delete();
+
+            $data['delivery_id'] = $ids;
+            foreach ($params['first'] as $k => $v){
+                $data['area'] = $params['area'][$k];
+                $data['first'] = $params['first'][$k];
+                $data['first_fee'] = $params['first_fee'][$k];
+                $data['additional'] = $params['additional'][$k];
+                $data['additional_fee'] = $params['additional_fee'][$k];
+                DeliveryRule::create($data,true);
+            }
+        }catch (Exception $e){
+            Db::rollback();
+            return false;
+        }
+        Db::commit();
+        return true;
+    }
+
+
+
+}

+ 69 - 0
addons/unishop/application/admin/model/unishop/DeliveryRule.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: zhengmingwei
+ * Date: 2019-07-14
+ * Time: 22:45
+ */
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+class DeliveryRule extends Model
+{
+
+    // 表名
+    protected $name = 'unishop_delivery_rule';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+
+
+    protected $append = ['area_content'];
+
+    static $regionAll;
+    static $regionTree;
+
+    /**
+     * 可配送区域
+     * @param $value
+     * @param $data
+     * @return string
+     */
+    public function getAreaContentAttr($value, $data)
+    {
+        // 当前区域记录转换为数组
+        $regionIds = explode(',', $data['area']);
+
+        if (count($regionIds) === 373) return '全国';
+
+        // 所有地区
+        if (empty(self::$regionAll)) {
+            self::$regionAll = Area::getCacheAll();
+            self::$regionTree = Area::getCacheTree();
+        }
+        // 将当前可配送区域格式化为树状结构
+        $alreadyTree = [];
+        foreach ($regionIds as $regionId)
+            $alreadyTree[self::$regionAll[$regionId]['pid']][] = $regionId;
+        $str = '';
+        foreach ($alreadyTree as $provinceId => $citys) {
+            $str .= self::$regionTree[$provinceId]['name'];
+            if (count($citys) !== count(self::$regionTree[$provinceId]['city'])) {
+                $cityStr = '';
+                foreach ($citys as $cityId)
+                    $cityStr .= self::$regionTree[$provinceId]['city'][$cityId]['name'];
+                $str .= ' (<span class="am-link-muted">' . mb_substr($cityStr, 0, -1, 'utf-8') . '</span>)';
+            }
+            $str .= '、';
+        }
+        return mb_substr($str, 0, -1, 'utf-8');
+    }
+
+
+}

+ 78 - 0
addons/unishop/application/admin/model/unishop/Evaluate.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class Evaluate extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_evaluate';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+        'rate_text',
+        'anonymous_text',
+        'toptime_text'
+    ];
+
+
+
+    public function getRateList()
+    {
+        return ['1' => __('Rate 1'), '2' => __('Rate 2'), '3' => __('Rate 3'), '4' => __('Rate 4'), '5' => __('Rate 5')];
+    }
+
+    public function getAnonymousList()
+    {
+        return ['0' => __('Anonymous 0'), '1' => __('Anonymous 1')];
+    }
+
+
+    public function getRateTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['rate']) ? $data['rate'] : '');
+        $list = $this->getRateList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+
+    public function getAnonymousTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['anonymous']) ? $data['anonymous'] : '');
+        $list = $this->getAnonymousList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+
+    public function getToptimeTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['toptime']) ? $data['toptime'] : '');
+        return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
+    }
+
+    protected function setToptimeAttr($value)
+    {
+        return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+    public function product()
+    {
+        return $this->hasOne('product', 'id', 'product_id');
+    }
+
+}

+ 46 - 0
addons/unishop/application/admin/model/unishop/FlashProduct.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+
+class FlashProduct extends Model
+{
+
+
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_flash_product';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = false;
+
+    // 追加属性
+    protected $append = [
+
+    ];
+
+
+    /**
+     * 关联到产品
+     */
+    public function product()
+    {
+        return $this->belongsTo('product', 'product_id', 'id');
+    }
+
+
+
+
+
+
+
+}

+ 163 - 0
addons/unishop/application/admin/model/unishop/FlashSale.php

@@ -0,0 +1,163 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use addons\unishop\model\Product;
+use think\Db;
+use think\Exception;
+use think\Model;
+use traits\model\SoftDelete;
+
+
+class FlashSale extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_flash_sale';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+        'status_text',
+        'starttime_text',
+        'endtime_text',
+        'current_state'
+    ];
+
+    // 已归档
+    const STATUS_YES = 1; // 是
+    const STATUS_NO = 0; // 否
+
+    // 已上架
+    const SWITCH_YES = 1; // 是
+    const SWITCH_NO = 0; // 否
+
+    /**
+     * 获取当前状态
+     */
+    public function getCurrentStateAttr($value, $data)
+    {
+        $time = time();
+        switch (true) {
+            case $data['starttime'] > $time:
+                $result = __('Not started');
+                break;
+            case $data['starttime'] <= $time && $time < $data['endtime']:
+                $result = __('On going');
+                break;
+            case $time >= $data['endtime']:
+                $result = __('Has ended');
+                break;
+            default:
+                $result = __('Nothing');
+        }
+        return $result;
+    }
+
+    public function getStatusList()
+    {
+        return ['0' => __('Status 0'), '1' => __('Status 1')];
+    }
+
+
+    public function getStatusTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['status']) ? $data['status'] : '');
+        $list = $this->getStatusList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+
+    public function getStarttimeTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['starttime']) ? $data['starttime'] : '');
+        return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
+    }
+
+
+    public function getEndtimeTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['endtime']) ? $data['endtime'] : '');
+        return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
+    }
+
+    protected function setStarttimeAttr($value)
+    {
+        return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+    protected function setEndtimeAttr($value)
+    {
+        return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+    /**
+     * 关联产品
+     * @return \think\model\relation\HasMany
+     */
+    public function product()
+    {
+        return $this->hasMany('flashProduct', 'flash_id', 'id');
+    }
+
+    /**
+     * 判断能不能修改
+     * 已归档、已开始、上架状态的秒杀不能够修改。
+     */
+    public function checkItCanEdit()
+    {
+        if ($this['switch'] == self::SWITCH_YES || $this['status'] == self::STATUS_YES || $this['starttime'] < time()) {
+            throw new Exception('已归档、已开始、上架状态的秒杀信息不能够修改。');
+        }
+        return true;
+    }
+
+    /**
+     * 归档减库存
+     */
+    public function activityFiled($params, $specNumber)
+    {
+        $productExtend = new \addons\unishop\extend\Product;
+        $key = 0;
+        $prefix = \think\Config::get('database.prefix');
+        foreach ($specNumber as $spec => $number) {
+            $result = 0;
+            if (is_numeric($spec) && $params[$key]['use_spec'] == Product::SPEC_OFF) {
+                $result = Db::execute("UPDATE fa_unishop_product SET stock = stock-{$number}, real_sales = real_sales+{$number} WHERE id = {$params[$key]['id']}");
+            } else if ($params[$key]['use_spec'] == Product::SPEC_ON) {
+                $info = $productExtend->getBaseData($params[$key], $spec);
+
+                // mysql<5.7.13时用
+                //if (mysql < 5.7.13) {
+                $spec = str_replace(',', '","', $spec);
+                $search = '"stock":"' . $info['stock'] . '","value":["' . $spec . '"]';
+                $stock = $info['stock'] - $number;
+                $replace = '"stock":\"' . $stock . '\","value":["' . $spec . '"]';
+                $sql = 'UPDATE ' . $prefix . "unishop_product SET stock = stock-{$number}, real_sales = real_sales+{$number}, `specTableList` = REPLACE(specTableList,'$search','$replace') WHERE id = {$params[$key]['id']}";
+                $result = Db::execute($sql);
+                //}
+
+                //下面语句直接操作JSON
+                //if (mysql >= 5.7.13) {
+                //$info['stock'] -= $number;
+                //$result = Db::execute("UPDATE fa_unishop_product SET stock = stock-{$number}, real_sales = real_sales+{$number}, specTableList = JSON_REPLACE(specTableList, '$[{$info['key']}].stock', {$info['stock']}) WHERE id = {$params[$key]['id']}");
+                //}
+            }
+            if ($result == 0) { // 锁生效
+                throw new Exception('失败');
+            }
+            $key++;
+        }
+    }
+}

+ 128 - 0
addons/unishop/application/admin/model/unishop/Order.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class Order extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_order';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 订单状态
+    const STATUS_NORMAL = 1; // 正常
+    const STATUS_CANCEL = 0; // 用户取消订单
+    const STATUS_REFUND = -1; // 申请售后
+
+    // 追加属性
+    protected $append = [
+        'pay_type_text',
+        'status_text'
+    ];
+
+    public function getPayTypeList()
+    {
+        return ['1' => __('Online'), '2' => __('Offline'), '3' => __('wxPay'), '4' => __('aliPay')];
+    }
+
+    public function getStatusList()
+    {
+        return ['-1' => __('Refund'), '0' => __('Cancel'), '1' => __('Normal')];
+    }
+
+    public function getRefundStatusList()
+    {
+        return ['0' => __('None'), '1' => __('Apply'),'2' => __('Pass and left User delivery') , '3' => __('Pass'), '4' => __('Refuse')];
+    }
+
+    public function getPayTypeTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['pay_type']) ? $data['pay_type'] : '');
+        $list = $this->getPayTypeList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    public function getStatusTextAttr($value, $data)
+    {
+        $value = $value ? $value : (isset($data['status']) ? $data['status'] : '');
+        $list = $this->getStatusList();
+        return isset($list[$value]) ? $list[$value] : '';
+    }
+
+    protected function setHavePaidAttr($value)
+    {
+        return $value === '' ? 0 : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+    protected function setHaveDeliveredAttr($value)
+    {
+        return $value === '' ? 0 : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+    protected function setHaveCommentedAttr($value)
+    {
+        return $value === '' ? 0 : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+    protected function setHaveReceivedAttr($value)
+    {
+        return $value === '' ? 0 : ($value && !is_numeric($value) ? strtotime($value) : $value);
+    }
+
+
+    /**
+     * 关联用户
+     */
+    public function user(){
+        return $this->hasOne('user', 'id', 'user_id');
+    }
+
+    /**
+     * 关联订单扩展
+     */
+    public function extend(){
+        return $this->belongsTo('orderExtend', 'id', 'order_id');
+    }
+
+    /**
+     * 关联订单商品
+     */
+    public function product(){
+        return $this->hasMany('orderProduct', 'order_id', 'id');
+    }
+
+    /**
+     * 关联评价信息
+     */
+    public function evaluate()
+    {
+        return $this->hasMany('evaluate', 'order_id', 'id');
+    }
+
+    /**
+     * 关联退货信息
+     */
+    public function refund()
+    {
+        return $this->hasOne('orderRefund', 'order_id', 'id');
+    }
+
+    public function refundProduct()
+    {
+        return $this->hasMany('orderRefundProduct', 'order_id', 'id');
+    }
+}

+ 31 - 0
addons/unishop/application/admin/model/unishop/OrderExtend.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+class OrderExtend extends Model
+{
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_order_extend';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+
+    /**
+     * 关联订单表
+     * @return Model|\think\model\relation\HasOne
+     */
+    public function order()
+    {
+        return $this->hasOne('order', 'id', 'order_id');
+    }
+
+}

+ 22 - 0
addons/unishop/application/admin/model/unishop/OrderProduct.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+class OrderProduct extends Model
+{
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_order_product';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+
+}

+ 30 - 0
addons/unishop/application/admin/model/unishop/OrderRefund.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+/**
+ * 分类模型
+ */
+class OrderRefund extends Model
+{
+    // 表名
+    protected $name = 'unishop_order_refund';
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+
+    public function getReceivingStatusTextAttr()
+    {
+        return ['0' => '未收到', '1' => '已收到'];
+    }
+
+    public function getServiceTypeTextAttr()
+    {
+        return ['0' => '我要退款(无需退货)', '1' => '我要退货退款', '2' => '换货'];
+    }
+
+}

+ 19 - 0
addons/unishop/application/admin/model/unishop/OrderRefundProduct.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+
+/**
+ * 分类模型
+ */
+class OrderRefundProduct extends Model
+{
+    // 表名
+    protected $name = 'unishop_order_refund_product';
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = false;
+}

+ 58 - 0
addons/unishop/application/admin/model/unishop/Product.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+/**
+ * 商品模型
+ * Class Product
+ * @package app\admin\model\unishop
+ */
+class Product extends Model
+{
+
+    use SoftDelete;
+
+    //数据库
+    protected $connection = 'database';
+    // 表名
+    protected $name = 'unishop_product';
+
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    protected static function init()
+    {
+        self::afterInsert(function ($row) {
+            $pk = $row->getPk();
+            $row->getQuery()->where($pk, $row[$pk])->update(['weigh' => $row[$pk]]);
+        });
+    }
+
+    /**
+     * 关联分类
+     * @return \think\model\relation\BelongsTo
+     */
+    public function category()
+    {
+        return $this->belongsTo('category');
+    }
+
+    /**
+     * 关联运费
+     * @return \think\model\relation\BelongsTo
+     */
+    public function delivery()
+    {
+        return $this->belongsTo('delivery');
+    }
+
+
+}

+ 27 - 0
addons/unishop/application/admin/model/unishop/User.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\model\unishop;
+
+use app\common\model\MoneyLog;
+use think\Model;
+
+class User extends Model
+{
+
+    // 表名
+    protected $name = 'user';
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+
+    /**
+     * 修改用户昵称
+     */
+    public function getUsernameAttr($value, $data)
+    {
+        return !empty($data['username']) ? $data['username'] : __('Visitor');
+    }
+
+}

+ 27 - 0
addons/unishop/application/admin/validate/unishop/Ads.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\validate\unishop;
+
+use think\Validate;
+
+class Ads extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+
+}

+ 27 - 0
addons/unishop/application/admin/validate/unishop/Config.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\validate\unishop;
+
+use think\Validate;
+
+class Config extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+
+}

+ 30 - 0
addons/unishop/application/admin/validate/unishop/Coupon.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace app\admin\validate\unishop;
+
+use think\Validate;
+
+class Coupon extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+        'value'=>'lt:least' // 小于  value:优惠券金额   least:消费多少可用
+    ];
+
+    /**
+     * 提示消息
+     */
+    protected $message = [
+        'value' => '优惠券金额 必须小于 消费金额'
+    ];
+
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => ['value'],
+        'edit' => ['value'],
+    ];
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov