فهرست منبع

【程序目录】优化程序

吴昊天 3 سال پیش
والد
کامیت
75174a99df
42فایلهای تغییر یافته به همراه1395 افزوده شده و 121 حذف شده
  1. 1 1
      crmeb/app/adminapi/controller/Common.php
  2. 1 1
      crmeb/app/adminapi/controller/v1/marketing/integral/StoreIntegralOrder.php
  3. 1 1
      crmeb/app/adminapi/controller/v1/order/StoreOrder.php
  4. 28 0
      crmeb/app/adminapi/controller/v1/product/StoreProduct.php
  5. 14 7
      crmeb/app/adminapi/controller/v1/setting/LangCountry.php
  6. 18 2
      crmeb/app/adminapi/controller/v1/setting/SystemAdmin.php
  7. 14 5
      crmeb/app/adminapi/controller/v1/setting/SystemConfig.php
  8. 5 0
      crmeb/app/adminapi/route/product.php
  9. 8 4
      crmeb/app/adminapi/route/setting.php
  10. 5 1
      crmeb/app/adminapi/route/system.php
  11. 7 2
      crmeb/app/api/controller/v1/PayController.php
  12. 1 1
      crmeb/app/api/controller/v1/order/StoreIntegralOrderController.php
  13. 2 2
      crmeb/app/api/controller/v1/order/StoreOrderController.php
  14. 5 0
      crmeb/app/api/controller/v1/wechat/WechatController.php
  15. 1 0
      crmeb/app/api/route/v1.php
  16. 2 2
      crmeb/app/dao/product/product/StoreProductDao.php
  17. 6 1
      crmeb/app/services/order/StoreOrderRefundServices.php
  18. 1 0
      crmeb/app/services/order/StoreOrderServices.php
  19. 5 1
      crmeb/app/services/pay/PayServices.php
  20. 95 0
      crmeb/app/services/product/product/StoreProductServices.php
  21. 2 2
      crmeb/app/services/shipping/ExpressServices.php
  22. 23 3
      crmeb/app/services/system/admin/SystemAdminServices.php
  23. 13 1
      crmeb/app/services/system/config/SystemConfigServices.php
  24. 2 2
      crmeb/app/services/system/lang/LangCodeServices.php
  25. 27 18
      crmeb/app/services/system/lang/LangCountryServices.php
  26. 16 0
      crmeb/app/services/wechat/WechatServices.php
  27. 7 7
      crmeb/crmeb/services/app/WechatService.php
  28. 8 1
      crmeb/crmeb/services/easywechat/Application.php
  29. 17 17
      crmeb/crmeb/services/easywechat/miniPayment/WeChatClient.php
  30. 280 0
      crmeb/crmeb/services/easywechat/v3pay/BaseClient.php
  31. 68 0
      crmeb/crmeb/services/easywechat/v3pay/Certficates.php
  32. 400 0
      crmeb/crmeb/services/easywechat/v3pay/PayClient.php
  33. 36 0
      crmeb/crmeb/services/easywechat/v3pay/ServiceProvider.php
  34. 4 3
      crmeb/crmeb/services/express/storage/Express.php
  35. 2 0
      crmeb/crmeb/services/pay/Pay.php
  36. 1 1
      crmeb/crmeb/services/pay/PayInterface.php
  37. 216 0
      crmeb/crmeb/services/pay/storage/V3WechatPay.php
  38. 5 7
      crmeb/crmeb/services/pay/storage/WechatPay.php
  39. 40 16
      crmeb/public/install/crmeb.sql
  40. 6 6
      crmeb/vendor/composer/installed.php
  41. 1 5
      crmeb/vendor/overtrue/wechat/src/Foundation/Application.php
  42. 1 1
      crmeb/vendor/services.php

+ 1 - 1
crmeb/app/adminapi/controller/Common.php

@@ -111,7 +111,7 @@ class Common extends AuthController
                 } catch (\Throwable $e) {
                     return app('json')->fail(400330);
                 }
-                return app('json')->success(['status' => 1, 'copyright' => $this->copyright(), 'authCode' => $authCode, 'day' => 0, 'force_reminder' => $upgradeStatus['force_reminder'] ?? 0]);
+                return app('json')->success(['status' => 1, 'copyright' => $res['data']['copyright'], 'authCode' => $authCode, 'day' => 0, 'force_reminder' => $upgradeStatus['force_reminder'] ?? 0]);
             default:
                 return app('json')->success(['status' => -9, 'force_reminder' => $upgradeStatus['force_reminder'] ?? 0]);
         }

+ 1 - 1
crmeb/app/adminapi/controller/v1/marketing/integral/StoreIntegralOrder.php

@@ -216,7 +216,7 @@ class StoreIntegralOrder extends AuthController
 
         $data['delivery_name'] = $orderInfo['delivery_name'];
         $data['delivery_id'] = $orderInfo['delivery_id'];
-        $data['result'] = $services->query($cacheName, $orderInfo['delivery_id'], $orderInfo['delivery_code'] ?? null);
+        $data['result'] = $services->query($cacheName, $orderInfo['delivery_id'], $orderInfo['delivery_code'] ?? null, $orderInfo['user_phone']);
         return app('json')->success($data);
     }
 

+ 1 - 1
crmeb/app/adminapi/controller/v1/order/StoreOrder.php

@@ -510,7 +510,7 @@ class StoreOrder extends AuthController
 
         $data['delivery_name'] = $orderInfo['delivery_name'];
         $data['delivery_id'] = $orderInfo['delivery_id'];
-        $data['result'] = $services->query($cacheName, $orderInfo['delivery_id'], $orderInfo['delivery_code'] ?? null);
+        $data['result'] = $services->query($cacheName, $orderInfo['delivery_id'], $orderInfo['delivery_code'] ?? null, $orderInfo['user_phone']);
         return app('json')->success($data);
     }
 

+ 28 - 0
crmeb/app/adminapi/controller/v1/product/StoreProduct.php

@@ -359,6 +359,11 @@ class StoreProduct extends AuthController
         return app('json')->success(100002);
     }
 
+    /**
+     * 导入卡密
+     * @return mixed
+     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
+     */
     public function import_card()
     {
         $data = $this->request->getMore([
@@ -371,4 +376,27 @@ class StoreProduct extends AuthController
         $cardData = $readExcelService->readExcel($file);
         return app('json')->success($cardData);
     }
+
+    /**
+     * 商品批量设置
+     * @return mixed
+     */
+    public function batchSetting()
+    {
+        $data = $this->request->postMore([
+            ['ids', []],
+            ['cate_id', []],
+            ['logistics', []],
+            ['freight', 2],
+            ['postage', 0],
+            ['temp_id', 1],
+            ['give_integral', 0],
+            ['coupon_ids', []],
+            ['label_id', []],
+            ['recommend', []],
+            ['type', 0]
+        ]);
+        $this->service->batchSetting($data);
+        return app('json')->success(100014);
+    }
 }

+ 14 - 7
crmeb/app/adminapi/controller/v1/setting/LangCountry.php

@@ -50,22 +50,29 @@ class LangCountry extends AuthController
      * @throws \think\db\exception\DbException
      * @throws \think\db\exception\ModelNotFoundException
      */
-    public function langCountryTypeForm($id)
+    public function langCountryForm($id)
     {
-        return app('json')->success($this->services->LangCountryTypeForm($id));
+        return app('json')->success($this->services->langCountryForm($id));
     }
 
     /**
-     * 国家语言修改
+     * 地区语言修改
      * @param $id
      * @return mixed
      */
     public function langCountrySave($id)
     {
-        [$typeId] = $this->request->postMore([
-            ['type', 0],
-        ], true);
-        $this->services->langCountrySave($id, $typeId);
+        $data = $this->request->postMore([
+            ['name', ''],
+            ['code', ''],
+        ]);
+        $this->services->langCountrySave($id, $data);
         return app('json')->success(100000);
     }
+
+    public function langCountryDel($id)
+    {
+        $this->services->langCountryDel($id);
+        return app('json')->success(100002);
+    }
 }

+ 18 - 2
crmeb/app/adminapi/controller/v1/setting/SystemAdmin.php

@@ -168,8 +168,6 @@ class SystemAdmin extends AuthController
             ['pwd', ''],
             ['new_pwd', ''],
             ['conf_pwd', ''],
-            ['file_pwd', ''],
-            ['conf_file_pwd', ''],
         ]);
         if(!preg_match('/^(?![^a-zA-Z]+$)(?!\D+$).{6,}$/',$data['new_pwd'])){
             return app('json')->fail(400183);
@@ -179,6 +177,24 @@ class SystemAdmin extends AuthController
         else
             return app('json')->fail(100007);
     }
+    /**
+     * 修改当前登陆admin的文件管理密码
+     * @return mixed
+     */
+    public function set_file_password()
+    {
+        $data = $this->request->postMore([
+            ['file_pwd', ''],
+            ['conf_file_pwd', ''],
+        ]);
+        if(!preg_match('/^(?![^a-zA-Z]+$)(?!\D+$).{6,}$/',$data['file_pwd'])){
+            return app('json')->fail(400183);
+        }
+        if ($this->services->setFilePassword($this->adminId, $data))
+            return app('json')->success(100001);
+        else
+            return app('json')->fail(100007);
+    }
 
     /**
      * 退出登陆

+ 14 - 5
crmeb/app/adminapi/controller/v1/setting/SystemConfig.php

@@ -12,6 +12,7 @@ namespace app\adminapi\controller\v1\setting;
 
 use app\adminapi\controller\AuthController;
 use app\Request;
+use app\services\order\StoreOrderServices;
 use app\services\system\config\SystemConfigServices;
 use app\services\system\config\SystemConfigTabServices;
 use think\facade\App;
@@ -301,13 +302,21 @@ class SystemConfig extends AuthController
         if (isset($post['day_brokerage_price_upper']) && $post['day_brokerage_price_upper'] < -1) {
             return app('json')->fail(400757);
         }
-        if( isset($post['pay_new_weixin_open']) && (bool)$post['pay_new_weixin_open'])
-        {
-            if(empty($post['pay_new_weixin_mchid']))
-            {
-                return  app('json')->fail(400763);
+        if (isset($post['pay_new_weixin_open']) && (bool)$post['pay_new_weixin_open']) {
+            if (empty($post['pay_new_weixin_mchid'])) {
+                return app('json')->fail(400763);
             }
         }
+
+        //支付接口类型选择,如果有订单就不能再进行切换
+        if (isset($post['pay_wechat_type'])) {
+            /** @var StoreOrderServices $orderServices */
+            $orderServices = app()->make(StoreOrderServices::class);
+            if ($post['pay_wechat_type'] != -1 && $orderServices->count()) {
+                return app('json')->fail('支付接口类型已经选择,不能再次进行切换,切换后会导致无法退款等问题。');
+            }
+        }
+
         if (isset($post['weixin_ckeck_file'])) {
             $from = public_path() . $post['weixin_ckeck_file'];
             $to = public_path() . array_reverse(explode('/', $post['weixin_ckeck_file']))[0];

+ 5 - 0
crmeb/app/adminapi/route/product.php

@@ -93,6 +93,11 @@ Route::group('product', function () {
     Route::get('product/check_activity/:id', 'v1.product.StoreProduct/check_activity')->option(['real_name' => '检测是商品否有活动开启']);
     //导入虚拟商品卡密
     Route::get('product/import_card', 'v1.product.StoreProduct/import_card')->option(['real_name' => '导入虚拟商品卡密']);
+
+    /** 商品批量操作 */
+    Route::post('batch/setting', 'v1.product.StoreProduct/batchSetting')->option(['real_name' => '商品批量设置']);
+
+
 })->middleware([
     \app\http\middleware\AllowOriginMiddleware::class,
     \app\adminapi\middleware\AdminAuthTokenMiddleware::class,

+ 8 - 4
crmeb/app/adminapi/route/setting.php

@@ -25,6 +25,8 @@ Route::group('setting', function () {
     Route::get('info', 'v1.setting.SystemAdmin/info')->name('SystemAdminInfo')->option(['real_name' => '获取当前管理员信息']);
     //修改当前管理员信息
     Route::put('update_admin', 'v1.setting.SystemAdmin/update_admin')->name('SystemAdminUpdateAdmin')->option(['real_name' => '修改当前管理员信息']);
+    //设置文件管理密码
+    Route::put('set_file_password', 'v1.setting.SystemAdmin/set_file_password')->name('SystemAdminSetFilePassword')->option(['real_name' => '设置当前文件管理密码']);
     //权限菜单资源路由
     Route::resource('menus', 'v1.setting.SystemMenus')->option(['real_name' => '权限菜单']);
     //未添加的权限规则列表
@@ -169,10 +171,12 @@ Route::group('setting', function () {
     /** 多语言 */
     //语言国家列表
     Route::get('lang_country/list', 'v1.setting.LangCountry/langCountryList')->option(['real_name' => '语言国家列表']);
-    //设置国家语言类型表单
-    Route::get('lang_country/type_form/:id', 'v1.setting.LangCountry/langCountryTypeForm')->option(['real_name' => '设置国家语言类型表单']);
-    //语言类型列表
-    Route::post('lang_country/save/:id', 'v1.setting.LangCountry/langCountrySave')->option(['real_name' => '语言国家修改']);
+    //添加语言地区表单
+    Route::get('lang_country/form/:id', 'v1.setting.LangCountry/langCountryForm')->option(['real_name' => '添加语言地区表单']);
+    //保存语言地区
+    Route::post('lang_country/save/:id', 'v1.setting.LangCountry/langCountrySave')->option(['real_name' => '保存语言地区']);
+    //删除语言地区
+    Route::delete('lang_country/del/:id', 'v1.setting.LangCountry/langCountryDel')->option(['real_name' => '删除语言地区']);
     //语言类型列表
     Route::get('lang_type/list', 'v1.setting.LangType/langTypeList')->option(['real_name' => '语言类型列表']);
     //新增修改语言类型表单

+ 5 - 1
crmeb/app/adminapi/route/system.php

@@ -94,7 +94,7 @@ Route::group('system', function () {
     Route::get('upgrade_log/list', 'UpgradeController/upgradeLogList')->option(['real_name' => '升级记录']);
     //导出备份项目
     Route::get('upgrade_export/:id/:type', 'UpgradeController/export')->option(['real_name' => '导出备份']);
-
+    //文件管理登录
     Route::post('file/login', 'v1.system.SystemFile/login')->option(['real_name' => '文件管理登录']);
 })->middleware([
     \app\http\middleware\AllowOriginMiddleware::class,
@@ -111,9 +111,13 @@ Route::group('system', function () {
     Route::get('file/openfile', 'v1.system.SystemFile/openfile')->option(['real_name' => '读取文件']);
     //保存文件
     Route::post('file/savefile', 'v1.system.SystemFile/savefile')->option(['real_name' => '保存文件']);
+    //创建文件夹
     Route::get('file/createFolder', 'v1.system.SystemFile/createFolder')->option(['real_name' => '创建文件夹']);
+    //创建文件
     Route::get('file/createFile', 'v1.system.SystemFile/createFile')->option(['real_name' => '创建文件']);
+    //删除文件夹或者文件
     Route::get('file/delFolder', 'v1.system.SystemFile/delFolder')->option(['real_name' => '删除文件夹']);
+    //重命名文件
     Route::get('file/rename', 'v1.system.SystemFile/rename')->option(['real_name' => '重命名文件夹']);
 })->middleware([
     \app\http\middleware\AllowOriginMiddleware::class,

+ 7 - 2
crmeb/app/api/controller/v1/PayController.php

@@ -38,8 +38,13 @@ class PayController
                 return $pay->handleNotify();
             case 'wechat':
             case 'routine':
-                /** @var Pay $pay */
-                $pay = app()->make(Pay::class);
+                if (sys_config('pay_wechat_type')) {
+                    /** @var Pay $pay */
+                    $pay = app()->make(Pay::class, ['v3_wechat_pay']);
+                } else {
+                    /** @var Pay $pay */
+                    $pay = app()->make(Pay::class);
+                }
                 return $pay->handleNotify()->getContent();
         }
     }

+ 1 - 1
crmeb/app/api/controller/v1/order/StoreIntegralOrderController.php

@@ -142,7 +142,7 @@ class StoreIntegralOrderController
         return app('json')->success([
             'order' => $order,
             'express' => [
-                'result' => ['list' => $expressServices->query($cacheName, $order['delivery_id'], $order['delivery_code'])
+                'result' => ['list' => $expressServices->query($cacheName, $order['delivery_id'], $order['delivery_code'], $order['user_phone'])
                 ]
             ]
         ]);

+ 2 - 2
crmeb/app/api/controller/v1/order/StoreOrderController.php

@@ -409,7 +409,7 @@ class StoreOrderController
                 case 'routine':
                     if ($type == 1 || in_array($order['is_channel'], [0, 2, 3, 4])) {
                         $order['order_id'] = app()->make(StoreOrderCreateServices::class)->getNewOrderId('cp');
-                        $this->services->update($order['id'],['order_id'=>$order['order_id']],'id');
+                        $this->services->update($order['id'], ['order_id' => $order['order_id']], 'id');
                     }
                     break;
                 case 'app':
@@ -690,7 +690,7 @@ class StoreOrderController
         return app('json')->success([
             'order' => $orderInfo,
             'express' => [
-                'result' => ['list' => $expressServices->query($cacheName, $orderInfo['delivery_id'], $orderInfo['delivery_code'])
+                'result' => ['list' => $expressServices->query($cacheName, $orderInfo['delivery_id'], $orderInfo['delivery_code'], $order['user_phone'])
                 ]
             ]
         ]);

+ 5 - 0
crmeb/app/api/controller/v1/wechat/WechatController.php

@@ -51,6 +51,11 @@ class WechatController
         return $this->services->notify();
     }
 
+    public function v3notify()
+    {
+        return $this->services->v3notify();
+    }
+
     /**
      * 公众号权限配置信息获取
      * @param Request $request

+ 1 - 0
crmeb/app/api/route/v1.php

@@ -15,6 +15,7 @@ use think\Response;
 
 Route::any('wechat/serve', 'v1.wechat.WechatController/serve');//公众号服务
 Route::any('wechat/notify', 'v1.wechat.WechatController/notify');//公众号支付回调
+Route::any('wechat/v3notify', 'v1.wechat.WechatController/v3notify');//公众号支付回调
 Route::any('routine/notify', 'v1.wechat.AuthController/notify');//小程序支付回调
 Route::any('pay/notify/:type', 'v1.PayController/notify');//支付回调
 Route::get('get_script', 'v1.PublicController/getScript');//获取统计代码

+ 2 - 2
crmeb/app/dao/product/product/StoreProductDao.php

@@ -160,7 +160,7 @@ class StoreProductDao extends BaseDao
             }
         })->when(!$page && $limit, function ($query) use ($limit) {
             $query->limit($limit);
-        })->field($field)->select()->toArray();
+        })->order('sort desc')->field($field)->select()->toArray();
     }
 
     /**商品列表
@@ -225,7 +225,7 @@ class StoreProductDao extends BaseDao
             ->when($limit, function ($query) use ($limit) {
                 $query->limit($limit);
             })
-            ->order((in_array($field, ['is_hot','is_best']) ? 'sales DESC' : 'sort DESC') . ', id DESC')->select()->toArray();
+            ->order(in_array($field, ['is_hot', 'is_best']) ? 'sales DESC,sort DESC, id desc' : 'sort DESC, id desc')->select()->toArray();
 
     }
 

+ 6 - 1
crmeb/app/services/order/StoreOrderRefundServices.php

@@ -188,8 +188,13 @@ class StoreOrderRefundServices extends BaseServices
                             $no = $refundOrder['trade_no'];
                             $refundData['type'] = 'trade_no';
                         }
+                        if (sys_config('pay_wechat_type')) {
+                            $drivers = 'v3_wechat_pay';
+                        } else {
+                            $drivers = 'wechat_pay';
+                        }
                         /** @var Pay $pay */
-                        $pay = app()->make(Pay::class);
+                        $pay = app()->make(Pay::class, [$drivers]);
                         if ($refundOrder['is_channel'] == 1) {
                             $refundData['trade_no'] = $refundOrder['trade_no'];
                             $refundData['pay_new_weixin_open'] = sys_config('pay_new_weixin_open');

+ 1 - 0
crmeb/app/services/order/StoreOrderServices.php

@@ -1581,6 +1581,7 @@ HTML;
         /** @var UserBillServices $userBillServices */
         $userBillServices = app()->make(UserBillServices::class);
         $data['usable_integral'] = bcsub((string)$user['integral'], (string)$userBillServices->getBillSum(['uid' => $user['uid'], 'is_frozen' => 1]), 0);
+        $data['integral_open'] = sys_config('integral_ratio', 0) > 0;
         return $data;
     }
 

+ 5 - 1
crmeb/app/services/pay/PayServices.php

@@ -76,6 +76,10 @@ class PayServices
             //这些全都是微信支付
             if (in_array($payType, ['routine', 'weixinh5', 'weixin', 'pc', 'store'])) {
                 $payType = 'wechat_pay';
+                //判断是否使用v3
+                if (sys_config('pay_wechat_type') == 1) {
+                    $payType = 'v3_wechat_pay';
+                }
             }
 
             if ($payType == 'alipay') {
@@ -85,7 +89,7 @@ class PayServices
             /** @var Pay $pay */
             $pay = app()->make(Pay::class, [$payType]);
 
-            return $pay->create($orderId, $price, $successAction, $body, '', ['openid' => $openid, 'isCode' => $isCode,'pay_new_weixin_open' => (bool)sys_config('pay_new_weixin_open')]);
+            return $pay->create($orderId, $price, $successAction, $body, '', ['openid' => $openid, 'isCode' => $isCode, 'pay_new_weixin_open' => (bool)sys_config('pay_new_weixin_open')]);
 
         } catch (\Exception $e) {
             throw new ApiException($e->getMessage());

+ 95 - 0
crmeb/app/services/product/product/StoreProductServices.php

@@ -1954,4 +1954,99 @@ class StoreProductServices extends BaseServices
 
         }
     }
+
+    /**
+     * 商品批量设置
+     * @param $data
+     * @return bool
+     * @throws \Exception
+     */
+    public function batchSetting($data)
+    {
+        $ids = $data['ids'];
+        $batchData = [];
+        if (!count($ids)) throw new AdminException(400337);
+        switch ($data['type']) {
+            case 1: // 修改分类
+                $cate_id = $data['cate_id'];
+                if (!count($cate_id)) throw new AdminException(410107);
+                /** @var StoreCategoryServices $storeCategoryServices */
+                $storeCategoryServices = app()->make(StoreCategoryServices::class);
+                $cateGory = $storeCategoryServices->getColumn([['id', 'IN', $cate_id]], 'id,pid', 'id');
+                if (!$cateGory) throw new AdminException(410096);
+                $time = time();
+                $cateData = [];
+                foreach ($cate_id as $cid) {
+                    if ($cid && isset($cateGory[$cid]['pid'])) {
+                        foreach ($ids as $product_id) {
+                            $cateData[$product_id][] = ['product_id' => $product_id, 'cate_id' => $cid, 'cate_pid' => $cateGory[$cid]['pid'], 'status' => 1, 'add_time' => $time];
+                        }
+                    }
+                }
+                /** @var StoreProductCateServices $storeProductCateServices */
+                $storeProductCateServices = app()->make(StoreProductCateServices::class);
+                foreach ($ids as $product_id) {
+                    $storeProductCateServices->change($product_id, $cateData[$product_id]);
+                    $this->dao->update($product_id, ['cate_id' => implode(',', $cate_id)]);
+                }
+                break;
+            case 2:
+                foreach ($ids as $product_id) {
+                    $batchData[] = [
+                        'id' => $product_id,
+                        'logistics' => implode(',', $data['logistics']),
+                        'freight' => $data['freight'],
+                        'postage' => $data['freight'] == 2 ? $data['postage'] : 0,
+                        'temp_id' => $data['freight'] == 3 ? $data['temp_id'] : 0
+                    ];
+                }
+                if (count($batchData)) $this->dao->saveAll($batchData);
+                break;
+            case 3:
+                foreach ($ids as $product_id) {
+                    $batchData[] = [
+                        'id' => $product_id,
+                        'give_integral' => $data['give_integral']
+                    ];
+                }
+                if (count($batchData)) $this->dao->saveAll($batchData);
+                break;
+            case 4:
+                /** @var StoreProductCouponServices $storeProductCouponServices */
+                $storeProductCouponServices = app()->make(StoreProductCouponServices::class);
+                foreach ($ids as $product_id) {
+                    if (!empty($data['coupon_ids'])) {
+                        $storeProductCouponServices->setCoupon($product_id, $data['coupon_ids']);
+                    } else {
+                        $storeProductCouponServices->delete(['product_id' => $product_id]);
+                    }
+                }
+                break;
+            case 5:
+                foreach ($ids as $product_id) {
+                    $batchData[] = [
+                        'id' => $product_id,
+                        'label_id' => implode(',', $data['label_id'])
+                    ];
+                }
+                if (count($batchData)) $this->dao->saveAll($batchData);
+                break;
+            case 6:
+                foreach ($ids as $product_id) {
+                    $batchData[] = [
+                        'id' => $product_id,
+                        'is_hot' => in_array('is_hot',$data['recommend']) ? 1 : 0,
+                        'is_benefit' => in_array('is_benefit',$data['recommend']) ? 1 : 0,
+                        'is_new' => in_array('is_new',$data['recommend']) ? 1 : 0,
+                        'is_good' => in_array('is_good',$data['recommend']) ? 1 : 0,
+                        'is_best' => in_array('is_best',$data['recommend']) ? 1 : 0
+                    ];
+                }
+                if (count($batchData)) $this->dao->saveAll($batchData);
+                break;
+            default:
+                return true;
+        }
+        return true;
+    }
 }

+ 2 - 2
crmeb/app/services/shipping/ExpressServices.php

@@ -190,7 +190,7 @@ class ExpressServices extends BaseServices
      * @param string|null $com
      * @return array
      */
-    public function query(string $cacheName, string $expressNum, string $com = null)
+    public function query(string $cacheName, string $expressNum, string $com = null, $phone = '')
     {
         $resultData = CacheService::get($cacheName, null);
         if ($resultData === null || !is_array($resultData)) {
@@ -200,7 +200,7 @@ class ExpressServices extends BaseServices
                 case 1:
                     /** @var ServeServices $services */
                     $services = app()->make(ServeServices::class);
-                    $result = $services->express()->query($expressNum, $com);
+                    $result = $services->express()->query($expressNum, $com, $phone);
                     if (isset($result['ischeck']) && $result['ischeck'] == 1) {
                         $cacheTime = 0;
                     } else {

+ 23 - 3
crmeb/app/services/system/admin/SystemAdminServices.php

@@ -138,6 +138,7 @@ class SystemAdminServices extends BaseServices
                 'id' => $adminInfo->getData('id'),
                 'account' => $adminInfo->getData('account'),
                 'head_pic' => $adminInfo->getData('head_pic'),
+                'level' => $adminInfo->getData('level'),
             ],
             'logo' => sys_config('site_logo'),
             'logo_square' => sys_config('site_logo_square'),
@@ -358,6 +359,28 @@ class SystemAdminServices extends BaseServices
                 throw new AdminException(400264);
             $adminInfo->pwd = $this->passwordHash($data['new_pwd']);
         }
+
+        $adminInfo->real_name = $data['real_name'];
+        $adminInfo->head_pic = $data['head_pic'];
+        if ($adminInfo->save())
+            return true;
+        else
+            return false;
+    }
+    /**
+     * 设置当前管理员文件管理密码
+     * @param int $id
+     * @param array $data
+     * @return bool
+     */
+    public function setFilePassword(int $id, array $data)
+    {
+        $adminInfo = $this->dao->get($id);
+        if (!$adminInfo)
+            throw new AdminException(400451);
+        if ($adminInfo->is_del) {
+            throw new AdminException(400452);
+        }
         if ($data['file_pwd']) {
             if ($adminInfo->level != 0) throw new AdminException(400611);
             if (!$data['conf_file_pwd'])
@@ -366,9 +389,6 @@ class SystemAdminServices extends BaseServices
                 throw new AdminException(400264);
             $adminInfo->file_pwd = $this->passwordHash($data['file_pwd']);
         }
-
-        $adminInfo->real_name = $data['real_name'];
-        $adminInfo->head_pic = $data['head_pic'];
         if ($adminInfo->save())
             return true;
         else

+ 13 - 1
crmeb/app/services/system/config/SystemConfigServices.php

@@ -139,7 +139,19 @@ class SystemConfigServices extends BaseServices
         'pay_weixin_open' => [
             'son_type' => [
                 'pay_weixin_mchid' => '',
-                'pay_weixin_key' => '',
+                'pay_wechat_type' => [
+                    'son_type' => [
+                        'pay_weixin_key' => '',
+                    ],
+                    'show_value' => 0
+                ],
+                'pay_wechat_type@' => [
+                    'son_type' => [
+                        'pay_weixin_serial_no' => '',
+                        'pay_weixin_key_v3' => ''
+                    ],
+                    'show_value' => 1
+                ],
                 'pay_weixin_client_cert' => '',
                 'pay_weixin_client_key' => '',
                 'paydir' => '',

+ 2 - 2
crmeb/app/services/system/lang/LangCodeServices.php

@@ -34,8 +34,8 @@ class LangCodeServices extends BaseServices
         $typeList = $langTypeServices->getColumn([['status', '=', 1], ['is_del', '=', 0]], 'language_name,file_name,id', 'id');
         $langType = [
             'isAdmin' => [
-                ['title' => '用户端', 'value' => 0],
-                ['title' => '管理端', 'value' => 1]
+                ['title' => '用户端页面', 'value' => 0],
+                ['title' => '后端接口', 'value' => 1]
             ]
         ];
         foreach ($typeList as $value) {

+ 27 - 18
crmeb/app/services/system/lang/LangCountryServices.php

@@ -19,7 +19,7 @@ class LangCountryServices extends BaseServices
     }
 
     /**
-     * 国家语言列表
+     * 地区语言列表
      * @param array $where
      * @return array
      * @throws \think\db\exception\DataNotFoundException
@@ -35,7 +35,7 @@ class LangCountryServices extends BaseServices
     }
 
     /**
-     * 设置国家语言类型表单
+     * 添加语言地区表单
      * @param $id
      * @return array
      * @throws \FormBuilder\Exception\FormBuilderException
@@ -43,32 +43,41 @@ class LangCountryServices extends BaseServices
      * @throws \think\db\exception\DbException
      * @throws \think\db\exception\ModelNotFoundException
      */
-    public function LangCountryTypeForm($id)
+    public function langCountryForm($id)
     {
-        if (!$id) throw new AdminException(100100);
-        $info = $this->dao->get($id);
-        if (!$info) throw new AdminException(100026);
-        /** @var LangTypeServices $typeServices */
-        $typeServices = app()->make(LangTypeServices::class);
-        $typeList = $typeServices->getColumn(['status' => 1, 'is_del' => 0], 'language_name,file_name', 'id');
-        $options[] = ['value' => 0, 'label' => '未定义'];
-        foreach ($typeList as $item) {
-            $options[] = ['value' => $item['id'], 'label' => $item['language_name'] . '(' . $item['file_name'] . ')'];
-        }
-        $field[] = Form::select('type', '选择语言类型', $info->type_id)->setOptions($options)->filterable(1);
-        return create_form('设置语言类型', $field, Url::buildUrl('/setting/lang_country/save/' . $id), 'POST');
+        if ($id) $info = $this->dao->get($id);
+        $field = [];
+        $field[] = Form::input('name', '所属地区', $info['name'] ?? '')->required('请填写所属地区');
+        $field[] = Form::input('code', '语言码', $info['code'] ?? '')->required('请填写语言码');
+        return create_form($id ? '修改语言地区' : '新增语言地区', $field, Url::buildUrl('/setting/lang_country/save/' . $id), 'POST');
     }
 
     /**
-     * 国家语言修改
+     * 保存语言地区
      * @param $id
      * @param $typeId
      * @return bool
      */
-    public function LangCountrySave($id, $typeId)
+    public function LangCountrySave($id, $data)
     {
-        $res = $this->dao->update(['id' => $id], ['type_id' => $typeId]);
+        if ($id) {
+            $res = $this->dao->update(['id' => $id], $data);
+        } else {
+            $res = $this->dao->save($data);
+        }
         if (!$res) throw new AdminException(100007);
         return true;
     }
+
+    /**
+     * 删除语言地区
+     * @param $id
+     * @return bool
+     */
+    public function langCountryDel($id)
+    {
+        $res = $this->dao->delete($id);
+        if (!$res) throw new AdminException(100008);
+        return true;
+    }
 }

+ 16 - 0
crmeb/app/services/wechat/WechatServices.php

@@ -21,6 +21,7 @@ use crmeb\services\CacheService;
 use crmeb\services\CacheService as Cache;
 use crmeb\services\app\WechatService as WechatAuthService;
 use crmeb\services\oauth\OAuth;
+use crmeb\services\pay\Pay;
 use crmeb\utils\Canvas;
 
 /**
@@ -60,6 +61,21 @@ class WechatServices extends BaseServices
         return WechatAuthService::handleNotify()->getContent();
     }
 
+    /**
+     * v3支付回调
+     * @return string
+     * @throws \EasyWeChat\Core\Exceptions\FaultException
+     * @author 等风来
+     * @email 136327134@qq.com
+     * @date 2022/9/22
+     */
+    public function v3notify()
+    {
+        /** @var Pay $pay */
+        $pay = app()->make(Pay::class, ['v3_wechat_pay']);
+        return $pay->handleNotify()->getContent();
+    }
+
     /**
      * 公众号权限配置信息获取
      * @param $url

+ 7 - 7
crmeb/crmeb/services/app/WechatService.php

@@ -146,9 +146,9 @@ class WechatService
                             $response = $messageService->wechatEventView($message);
                             break;
                         case 'funds_order_pay':
-                            $prefix = substr($message['order_info']['trade_no'],0,2);
+                            $prefix = substr($message['order_info']['trade_no'], 0, 2);
                             //处理一下参数
-                            switch ($prefix){
+                            switch ($prefix) {
                                 case 'cp':
                                     $data['attach'] = 'Product';
                                     break;
@@ -162,14 +162,12 @@ class WechatService
                             $data['out_trade_no'] = $message['order_info']['trade_no'];
                             $data['transaction_id'] = $message['order_info']['transaction_id'];
                             $data['opneid'] = $message['FromUserName'];
-                            if(Event::until('pay.notify', [$data]))
-                            {
+                            if (Event::until('pay.notify', [$data])) {
                                 $response = 'success';
-                            }else
-                            {
+                            } else {
                                 $response = 'faild';
                             }
-                            Log::error(['data'=>$data,'res'=>$response,'message'=>$message]);
+                            Log::error(['data' => $data, 'res' => $response, 'message' => $message]);
                             break;
                     }
                     break;
@@ -446,6 +444,7 @@ class WechatService
             }
         }
     }
+
     /**
      * 获得下单ID 新小程序支付
      * @param $openid
@@ -500,6 +499,7 @@ class WechatService
         $paymentPrepare = self::paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
         return self::paymentService()->configForJSSDKPayment($paymentPrepare->prepay_id);
     }
+
     /**
      * 获得jsSdk支付参数  新小程序支付
      * @param $openid

+ 8 - 1
crmeb/crmeb/services/easywechat/Application.php

@@ -14,7 +14,10 @@ namespace crmeb\services\easywechat;
 
 use crmeb\services\easywechat\miniPayment\ServiceProvider;
 use crmeb\services\easywechat\oauth2\wechat\WechatOauth2Provider;
+use crmeb\services\easywechat\subscribe\ProgramProvider;
+use crmeb\services\easywechat\v3pay\PayClient;
 use crmeb\services\easywechat\wechatlive\ProgramProvider as LiveProgramProvider;
+use crmeb\services\easywechat\v3pay\ServiceProvider as V3PayServiceProvider;
 
 
 /**
@@ -22,6 +25,7 @@ use crmeb\services\easywechat\wechatlive\ProgramProvider as LiveProgramProvider;
  * @package crmeb\services\easywechat
  * @property LiveProgramProvider $wechat_live
  * @property WechatOauth2Provider $oauth2
+ * @property PayClient $v3pay
  */
 class Application extends \EasyWeChat\Foundation\Application
 {
@@ -32,7 +36,10 @@ class Application extends \EasyWeChat\Foundation\Application
     protected $providersNew = [
         LiveProgramProvider::class,
         WechatOauth2Provider::class,
-        ServiceProvider::class
+        ServiceProvider::class,
+        ProgramProvider::class,
+        V3PayServiceProvider::class,
+        \crmeb\services\easywechat\Open3rd\ProgramProvider::class,
     ];
 
     /**

+ 17 - 17
crmeb/crmeb/services/easywechat/miniPayment/WeChatClient.php

@@ -49,7 +49,7 @@ class WeChatClient extends AbstractAPI
 
     /**
      * 支付
-     * @param array $params[
+     * @param array $params [
      *                      'openid'=>'支付者的openid',
      *                      'out_trade_no'=>'商家合单支付总交易单号',
      *                      'total_fee'=>'支付金额',
@@ -64,15 +64,15 @@ class WeChatClient extends AbstractAPI
     public function createorder($order)
     {
         $params = [
-            'openid'=>$order['openid'],    // 支付者的openid
-            'combine_trade_no'=>$order['out_trade_no'],  // 商家合单支付总交易单号
-            'expire_time'=>time()+$this->expire_time,
-            'sub_orders'=>[
+            'openid' => $order['openid'],    // 支付者的openid
+            'combine_trade_no' => $order['out_trade_no'],  // 商家合单支付总交易单号
+            'expire_time' => time() + $this->expire_time,
+            'sub_orders' => [
                 [
-                    'mchid'=>$this->merchant->merchant_id,
-                    'amount'=>(int)$order['total_fee'],
-                    'trade_no'=>$order['out_trade_no'],
-                    'description'=>$order['body']
+                    'mchid' => $this->merchant->merchant_id,
+                    'amount' => (int)$order['total_fee'],
+                    'trade_no' => $order['out_trade_no'],
+                    'description' => $order['body']
                 ]
             ]
         ];
@@ -81,7 +81,7 @@ class WeChatClient extends AbstractAPI
 
     /**
      * 退款
-     * @param array $params[
+     * @param array $params [
      *                      'openid'=>'退款者的openid',
      *                      'trade_no'=>'商家交易单号',
      *                      'transaction_id'=>'支付单号',
@@ -95,13 +95,13 @@ class WeChatClient extends AbstractAPI
     public function refundorder(array $order)
     {
         $params = [
-            'openid'=>$order['openid'],
-            'mchid'=>$this->merchant->merchant_id,
-            'trade_no'=>$order['trade_no'],
-            'transaction_id'=>$order['transaction_id'],
-            'refund_no'=>$order['refund_no'],
-            'total_amount'=>$order['total_amount'],
-            'refund_amount'=>$order['refund_amount'],
+            'openid' => $order['openid'],
+            'mchid' => $this->merchant->merchant_id,
+            'trade_no' => $order['trade_no'],
+            'transaction_id' => $order['transaction_id'],
+            'refund_no' => $order['refund_no'],
+            'total_amount' => $order['total_amount'],
+            'refund_amount' => $order['refund_amount'],
         ];
         return $this->parseJSON('post', [self::API_SET_REFUND_ORDER, json_encode($params)]);
     }

+ 280 - 0
crmeb/crmeb/services/easywechat/v3pay/BaseClient.php

@@ -0,0 +1,280 @@
+<?php
+/**
+ *  +----------------------------------------------------------------------
+ *  | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
+ *  +----------------------------------------------------------------------
+ *  | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
+ *  +----------------------------------------------------------------------
+ *  | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
+ *  +----------------------------------------------------------------------
+ *  | Author: CRMEB Team <admin@crmeb.com>
+ *  +----------------------------------------------------------------------
+ */
+
+namespace crmeb\services\easywechat\v3pay;
+
+
+use crmeb\exceptions\PayException;
+use crmeb\services\easywechat\Application;
+use EasyWeChat\Core\AbstractAPI;
+use EasyWeChat\Core\AccessToken;
+use EasyWeChat\Core\Exceptions\HttpException;
+use EasyWeChat\Core\Exceptions\InvalidConfigException;
+use EasyWeChat\Core\Http;
+use EasyWeChat\Encryption\EncryptionException;
+use think\exception\InvalidArgumentException;
+
+
+class BaseClient extends AbstractAPI
+{
+
+    use Certficates;
+
+    /**
+     * @var Application
+     */
+    protected $app;
+
+    const BASE_URL = 'https://api.mch.weixin.qq.com/';
+
+    const KEY_LENGTH_BYTE = 32;
+
+    const AUTH_TAG_LENGTH_BYTE = 16;
+
+    /**
+     * BaseClient constructor.
+     * @param AccessToken $accessToken
+     * @param $app
+     */
+    public function __construct(AccessToken $accessToken, $app)
+    {
+        parent::__construct($accessToken);
+        $this->app = $app;
+    }
+
+    /**
+     * request.
+     *
+     * @param string $endpoint
+     * @param string $method
+     * @param array $options
+     * @param bool $returnResponse
+     */
+    public function request(string $endpoint, string $method = 'POST', array $options = [], $serial = true)
+    {
+        $body = $options['body'] ?? '';
+
+        if (isset($options['json'])) {
+            $body = json_encode($options['json']);
+            $options['body'] = $body;
+            unset($options['json']);
+        }
+
+        $headers = [
+            'Content-Type' => 'application/json',
+            'User-Agent' => 'curl',
+            'Accept' => 'application/json',
+            'Authorization' => $this->getAuthorization($endpoint, $method, $body),
+        ];
+
+        $options['headers'] = array_merge($headers, ($options['headers'] ?? []));
+
+        if ($serial) {
+            $options['headers']['Wechatpay-Serial'] = $this->getCertficatescAttr('serial_no');
+        }
+
+        return $this->_doRequestCurl($method, self::BASE_URL . $endpoint, $options);
+    }
+
+    /**
+     * @param $method
+     * @param $location
+     * @param array $options
+     * @return mixed
+     */
+    private function _doRequestCurl($method, $location, $options = [])
+    {
+        $curl = curl_init();
+        // POST数据设置
+        if (strtolower($method) === 'post') {
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $options['data'] ?? $options['body'] ?? '');
+        }
+        // CURL头信息设置
+        if (!empty($options['headers'])) {
+            $headers = [];
+            foreach ($options['headers'] as $k => $v) {
+                $headers[] = "$k: $v";
+            }
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+        }
+        curl_setopt($curl, CURLOPT_URL, $location);
+        curl_setopt($curl, CURLOPT_HEADER, true);
+        curl_setopt($curl, CURLOPT_TIMEOUT, 60);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+        $content = curl_exec($curl);
+        $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
+        curl_close($curl);
+        return json_decode(substr($content, $headerSize), true);
+    }
+
+    /**
+     * To id card, mobile phone number and other fields sensitive information encryption.
+     *
+     * @param string $string
+     *
+     * @return string
+     */
+    protected function encryptSensitiveInformation(string $string)
+    {
+        $certificates = $this->app->certficates->get()['certificates'];
+        if (null === $certificates) {
+            throw new InvalidConfigException('config certificate connot be empty.');
+        }
+        $encrypted = '';
+        if (openssl_public_encrypt($string, $encrypted, $certificates, OPENSSL_PKCS1_OAEP_PADDING)) {
+            //base64编码
+            $sign = base64_encode($encrypted);
+        } else {
+            throw new EncryptionException('Encryption of sensitive information failed');
+        }
+        return $sign;
+    }
+
+
+    /**
+     * @param string $url
+     * @param string $method
+     * @param string $body
+     * @return string
+     */
+    protected function getAuthorization(string $url, string $method, string $body)
+    {
+        $nonceStr = uniqid();
+        $timestamp = time();
+        $message = $method . "\n" .
+            '/' . $url . "\n" .
+            $timestamp . "\n" .
+            $nonceStr . "\n" .
+            $body . "\n";
+        openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
+        $sign = base64_encode($raw_sign);
+        $schema = 'WECHATPAY2-SHA256-RSA2048 ';
+        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
+            $this->app['config']['v3_payment']['mchid'], $nonceStr, $timestamp, $this->app['config']['v3_payment']['serial_no'], $sign);
+        return $schema . $token;
+    }
+
+    /**
+     * 获取商户私钥
+     * @return bool|resource
+     */
+    protected function getPrivateKey()
+    {
+        $key_path = $this->app['config']['v3_payment']['key_path'];
+        if (!file_exists($key_path)) {
+            throw new \InvalidArgumentException(
+                "SSL certificate not found: {$key_path}"
+            );
+        }
+        return openssl_pkey_get_private(file_get_contents($key_path));
+    }
+
+    /**
+     * 获取商户公钥
+     * @return bool|resource
+     */
+    protected function getPublicKey()
+    {
+        $key_path = $this->app['config']['v3_payment']['cert_path'];
+        if (!file_exists($key_path)) {
+            throw new \InvalidArgumentException(
+                "SSL certificate not found: {$key_path}"
+            );
+        }
+        return openssl_pkey_get_public(file_get_contents($key_path));
+    }
+
+    /**
+     * 替换url
+     * @param string $url
+     * @param $search
+     * @param $replace
+     * @return array|string|string[]
+     */
+    public function getApiUrl(string $url, $search, $replace)
+    {
+        $newSearch = [];
+        foreach ($search as $key) {
+            $newSearch[] = '{' . $key . '}';
+        }
+        return str_replace($newSearch, $replace, $url);
+    }
+
+    /**
+     * @param int $padding
+     */
+    private static function paddingModeLimitedCheck(int $padding): void
+    {
+        if (!($padding === OPENSSL_PKCS1_OAEP_PADDING || $padding === OPENSSL_PKCS1_PADDING)) {
+            throw new PayException(sprintf("Doesn't supported padding mode(%d), here only support OPENSSL_PKCS1_OAEP_PADDING or OPENSSL_PKCS1_PADDING.", $padding));
+        }
+    }
+
+    /**
+     * 加密数据
+     * @param string $plaintext
+     * @param int $padding
+     * @return string
+     */
+    public function encryptor(string $plaintext, int $padding = OPENSSL_PKCS1_OAEP_PADDING)
+    {
+        self::paddingModeLimitedCheck($padding);
+
+        if (!openssl_public_encrypt($plaintext, $encrypted, $this->getPublicKey(), $padding)) {
+            throw new PayException('Encrypting the input $plaintext failed, please checking your $publicKey whether or nor correct.');
+        }
+
+        return base64_encode($encrypted);
+    }
+
+    /**
+     * decrypt ciphertext.
+     *
+     * @param array $encryptCertificate
+     *
+     * @return string
+     */
+    public function decrypt(array $encryptCertificate)
+    {
+        $ciphertext = base64_decode($encryptCertificate['ciphertext'], true);
+        $associatedData = $encryptCertificate['associated_data'];
+        $nonceStr = $encryptCertificate['nonce'];
+        $aesKey = $this->app['config']['v3_payment']['key'];
+
+        try {
+            // ext-sodium (default installed on >= PHP 7.2)
+            if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
+                return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
+            }
+            // ext-libsodium (need install libsodium-php 1.x via pecl)
+            if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
+                return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
+            }
+            // openssl (PHP >= 7.1 support AEAD)
+            if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
+                $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
+                $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
+                return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData);
+            }
+        } catch (\Exception $exception) {
+            throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
+        } catch (\SodiumException $exception) {
+            throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
+        }
+        throw new InvalidArgumentException('AEAD_AES_256_GCM 需要 PHP 7.1 以上或者安装 libsodium-php');
+    }
+}
+

+ 68 - 0
crmeb/crmeb/services/easywechat/v3pay/Certficates.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ *  +----------------------------------------------------------------------
+ *  | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
+ *  +----------------------------------------------------------------------
+ *  | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
+ *  +----------------------------------------------------------------------
+ *  | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
+ *  +----------------------------------------------------------------------
+ *  | Author: CRMEB Team <admin@crmeb.com>
+ *  +----------------------------------------------------------------------
+ */
+
+namespace crmeb\services\easywechat\v3pay;
+
+
+use crmeb\exceptions\PayException;
+use think\facade\Cache;
+
+/**
+ * Class Certficates
+ * @package crmeb\services\easywechat\v3pay
+ */
+trait Certficates
+{
+
+    /**
+     * @param string|null $key
+     * @return array|mixed|null
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function getCertficatescAttr(string $key = null)
+    {
+        $driver = Cache::store('file');
+        $cacheKey = '_wx_v3' . $this->app['config']['v3_payment']['serial_no'];
+        if ($driver->has($cacheKey)) {
+            $res = $driver->get($cacheKey);
+            if ($key && $res) {
+                return $res[$key] ?? null;
+            } else {
+                return $res;
+            }
+        }
+        $certficates = $this->getCertficates();
+        $driver->set($cacheKey, $certficates, 3600 * 24 * 30);
+        if ($key && $certficates) {
+            return $certficates[$key] ?? null;
+        }
+        return $certficates;
+    }
+
+    /**
+     * get certficates.
+     *
+     * @return array
+     */
+    public function getCertficates()
+    {
+        $response = $this->request('v3/certificates', 'GET', [], false);
+        if (isset($response['code'])) {
+            throw new PayException($response['message']);
+        }
+        $certificates = $response['data'][0];
+        $certificates['certificates'] = $this->decrypt($certificates['encrypt_certificate']);
+        unset($certificates['encrypt_certificate']);
+        return $certificates;
+    }
+}

+ 400 - 0
crmeb/crmeb/services/easywechat/v3pay/PayClient.php

@@ -0,0 +1,400 @@
+<?php
+/**
+ *  +----------------------------------------------------------------------
+ *  | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
+ *  +----------------------------------------------------------------------
+ *  | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
+ *  +----------------------------------------------------------------------
+ *  | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
+ *  +----------------------------------------------------------------------
+ *  | Author: CRMEB Team <admin@crmeb.com>
+ *  +----------------------------------------------------------------------
+ */
+
+namespace crmeb\services\easywechat\v3pay;
+
+
+use crmeb\exceptions\PayException;
+
+/**
+ * v3支付
+ * Class PayClient
+ * @package crmeb\services\easywechat\v3pay
+ */
+class PayClient extends BaseClient
+{
+    //app支付
+    const API_APP_APY_URL = 'v3/pay/transactions/app';
+    //二维码支付截图
+    const API_NATIVE_URL = 'v3/pay/transactions/native';
+    //h5支付接口
+    const API_H5_URL = 'v3/pay/transactions/h5';
+    //jsapi支付接口
+    const API_JSAPI_URL = 'v3/pay/transactions/jsapi';
+    //发起商家转账API
+    const API_BATCHES_URL = 'v3/transfer/batches';
+    //退款
+    const API_REFUND_URL = 'v3/refund/domestic/refunds';
+    //退款查询接口
+    const API_REFUND_QUERY_URL = 'v3/refund/domestic/refunds/{out_refund_no}';
+
+    /**
+     * 公众号jsapi支付下单
+     * @param string $outTradeNo
+     * @param string $total
+     * @param string $description
+     * @param string $attach
+     * @return mixed
+     */
+    public function jsapiPay(string $openid, string $outTradeNo, string $total, string $description, string $attach)
+    {
+        $appId = $this->app['config']['wechat']['appid'];
+        $res = $this->pay('jsapi', $appId, $outTradeNo, $total, $description, $attach, ['openid' => $openid]);
+        return $this->configForJSSDKPayment($appId, $res['prepay_id']);
+    }
+
+    /**
+     * 小程序支付
+     * @param string $outTradeNo
+     * @param string $total
+     * @param string $description
+     * @param string $attach
+     * @return array|false|string
+     */
+    public function miniprogPay(string $openid, string $outTradeNo, string $total, string $description, string $attach)
+    {
+        $appId = $this->app['config']['miniprog']['appid'];
+        $res = $this->pay('jsapi', $appId, $outTradeNo, $total, $description, $attach, ['openid' => $openid]);
+        return $this->configForJSSDKPayment($appId, $res['prepay_id']);
+    }
+
+    /**
+     * APP支付下单
+     * @param string $outTradeNo
+     * @param string $total
+     * @param string $description
+     * @param string $attach
+     * @return mixed
+     */
+    public function appPay(string $outTradeNo, string $total, string $description, string $attach)
+    {
+        $res = $this->pay('app', $this->app['config']['app']['appid'], $outTradeNo, $total, $description, $attach);
+        return $this->configForAppPayment($res['prepay_id']);
+    }
+
+    /**
+     * native支付下单
+     * @param string $outTradeNo
+     * @param string $total
+     * @param string $description
+     * @param string $attach
+     * @return mixed
+     */
+    public function nativePay(string $outTradeNo, string $total, string $description, string $attach)
+    {
+        return $this->pay('native', $this->app['config']['web']['appid'], $outTradeNo, $total, $description, $attach);
+    }
+
+    /**
+     * h5支付下单
+     * @param string $outTradeNo
+     * @param string $total
+     * @param string $description
+     * @param string $attach
+     * @return mixed
+     */
+    public function h5Pay(string $outTradeNo, string $total, string $description, string $attach)
+    {
+        return $this->pay('h5', $this->app['config']['wechat']['appid'], $outTradeNo, $total, $description, $attach);
+    }
+
+    /**
+     * 下单
+     * @param string $type
+     * @param string $appid
+     * @param string $outTradeNo
+     * @param string $total
+     * @param string $description
+     * @param string $attach
+     * @param array $payer
+     * @return mixed
+     */
+    public function pay(string $type, string $appid, string $outTradeNo, string $total, string $description, string $attach, array $payer = [])
+    {
+        $totalFee = (int)bcmul($total, '100');
+
+        $data = [
+            'appid' => $appid,
+            'mchid' => $this->app['config']['v3_payment']['mchid'],
+            'out_trade_no' => $outTradeNo,
+            'attach' => $attach,
+            'description' => $description,
+            'notify_url' => $this->app['config']['v3_payment']['notify_url'],
+            'amount' => [
+                'total' => $totalFee,
+                'currency' => 'CNY'
+            ],
+        ];
+
+        if ($payer) {
+            $data['payer'] = $payer;
+        }
+
+        $url = '';
+        switch ($type) {
+            case 'h5':
+                $url = self::API_H5_URL;
+                $data['scene_info'] = [
+                    'payer_client_ip' => request()->ip(),
+                    'h5_info' => [
+                        'type' => 'Wap'
+                    ]
+                ];
+                break;
+            case 'native':
+                $url = self::API_NATIVE_URL;
+                break;
+            case 'app':
+                $url = self::API_APP_APY_URL;
+                break;
+            case 'jsapi':
+                $url = self::API_JSAPI_URL;
+                break;
+        }
+
+        if (!$url) {
+            throw new PayException('缺少请求地址');
+        }
+
+        $res = $this->request($url, 'POST', ['json' => $data]);
+
+        if (!$res) {
+            throw new PayException('微信支付:下单失败');
+        }
+        if (isset($res['code']) && isset($res['message'])) {
+            throw new PayException($res['message']);
+        }
+
+        return $res;
+    }
+
+    /**
+     * 发起商家转账API
+     * @param string $outBatchNo
+     * @param string $amount
+     * @param string $batchName
+     * @param string $remark
+     * @param array $transferDetailList
+     * @return mixed
+     */
+    public function batches(string $outBatchNo, string $amount, string $batchName, string $remark, array $transferDetailList)
+    {
+        $totalFee = '0';
+
+        foreach ($transferDetailList as &$item) {
+            if ($item['transfer_amount'] >= 2000 && !empty($item['user_name'])) {
+                throw new PayException('明细金额大于等于2000时,收款人姓名必须填写');
+            }
+            $totalFee = bcadd($totalFee, $item['transfer_amount'], 2);
+            $item['transfer_amount'] = (int)bcmul($item['transfer_amount'], 100, 0);
+            if (isset($item['user_name'])) {
+                $item['user_name'] = $this->encryptor($item['user_name']);
+            }
+        }
+
+        if ($totalFee !== $amount) {
+            throw new PayException('转账明细金额总和和转账总金额不一致');
+        }
+
+        $amount = (int)bcmul($amount, 100, 0);
+
+        $data = [
+            'appid' => $this->app['config']['wechat']['appid'],
+            'out_batch_no' => $outBatchNo,
+            'batch_name' => $batchName,
+            'batch_remark' => $remark,
+            'total_amount' => $amount,
+            'total_num' => count($transferDetailList),
+            'transfer_detail_list' => $transferDetailList
+        ];
+
+        $res = $this->request(self::API_BATCHES_URL, 'POST', ['json' => $data]);
+
+        if (!$res) {
+            throw new PayException('微信支付:发起商家转账失败');
+        }
+
+        if (isset($res['code']) && isset($res['message'])) {
+            throw new PayException($res['message']);
+        }
+
+        return $res;
+
+    }
+
+    /**
+     * 退款
+     * @param string $outTradeNo
+     * @param array $options
+     * @return mixed
+     */
+    public function refund(string $outTradeNo, array $options = [])
+    {
+        if (!isset($options['pay_price'])) {
+            throw new PayException(400730);
+        }
+        $totalFee = floatval(bcmul($options['pay_price'], 100, 0));
+        $refundFee = isset($options['refund_price']) ? floatval(bcmul($options['refund_price'], 100, 0)) : null;
+        $refundReason = $options['desc'] ?? '';
+        $refundNo = $options['refund_id'] ?? $outTradeNo;
+        /*仅针对老资金流商户使用
+        REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)
+        REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款
+        */
+        $refundAccount = $opt['refund_account'] ?? 'AVAILABLE';
+
+        $data = [
+            'transaction_id' => $outTradeNo,
+            'out_refund_no' => $refundNo,
+            'amount' => [
+                'refund' => (int)$refundFee,
+                'currency' => 'CNY',
+                'total' => (int)$totalFee
+            ],
+            'funds_account' => $refundAccount
+        ];
+
+        if ($refundReason) {
+            $data['reason'] = $refundReason;
+        }
+
+        $res = $this->request(self::API_REFUND_URL, 'POST', ['json' => $data]);
+
+        if (!$res) {
+            throw new PayException('微信支付:发起退款失败');
+        }
+
+        if (isset($res['code']) && isset($res['message'])) {
+            throw new PayException($res['message']);
+        }
+
+        return $res;
+    }
+
+    /**
+     * 查询退款
+     * @param string $outRefundNo
+     * @return mixed
+     */
+    public function queryRefund(string $outRefundNo)
+    {
+        $res = $this->request($this->getApiUrl(self::API_REFUND_QUERY_URL, ['out_refund_no'], [$outRefundNo]), 'GET');
+
+        if (!$res) {
+            throw new PayException(50001);
+        }
+
+        if (isset($res['code']) && isset($res['message'])) {
+            throw new PayException($res['message']);
+        }
+
+        return $res;
+    }
+
+    /**
+     * jsapi支付
+     * @param string $appid
+     * @param string $prepayId
+     * @param bool $json
+     * @return array|false|string
+     */
+    public function configForPayment(string $appid, string $prepayId, bool $json = true)
+    {
+        $params = [
+            'appId' => $appid,
+            'timeStamp' => strval(time()),
+            'nonceStr' => uniqid(),
+            'package' => "prepay_id=$prepayId",
+            'signType' => 'RSA',
+        ];
+        $message = $params['appId'] . "\n" .
+            $params['timeStamp'] . "\n" .
+            $params['nonceStr'] . "\n" .
+            $params['package'] . "\n";
+        openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
+        $sign = base64_encode($raw_sign);
+
+        $params['paySign'] = $sign;
+
+        return $json ? json_encode($params) : $params;
+    }
+
+    /**
+     * Generate app payment parameters.
+     * @param string $prepayId
+     * @return array
+     */
+    public function configForAppPayment(string $prepayId): array
+    {
+        $params = [
+            'appid' => $this->app['config']['app']['appid'],
+            'partnerid' => $this->app['config']['v3_payment']['mchid'],
+            'prepayid' => $prepayId,
+            'noncestr' => uniqid(),
+            'timestamp' => time(),
+            'package' => 'Sign=WXPay',
+        ];
+        $message = $params['appid'] . "\n" .
+            $params['timestamp'] . "\n" .
+            $params['noncestr'] . "\n" .
+            $params['prepayid'] . "\n";
+        openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
+        $sign = base64_encode($raw_sign);
+
+        $params['sign'] = $sign;
+
+        return $params;
+    }
+
+    /**
+     * 小程序支付
+     * @param string $appid
+     * @param string $prepayId
+     * @return array|false|string
+     */
+    public function configForJSSDKPayment(string $appid, string $prepayId)
+    {
+        $config = $this->configForPayment($appid, $prepayId, false);
+
+        $config['timestamp'] = $config['timeStamp'];
+        unset($config['timeStamp']);
+
+        return $config;
+    }
+
+    /**
+     * @param $callback
+     * @return \think\Response
+     */
+    public function handleNotify($callback)
+    {
+        $request = request();
+        $success = $request->post('event_type') === 'TRANSACTION.SUCCESS';
+        $data = $this->decrypt($request->post('resource', []));
+
+        $handleResult = call_user_func_array($callback, [json_decode($data), $success]);
+        if (is_bool($handleResult) && $handleResult) {
+            $response = [
+                'code' => 'SUCCESS',
+                'message' => 'OK',
+            ];
+        } else {
+            $response = [
+                'code' => 'FAIL',
+                'message' => $handleResult,
+            ];
+        }
+
+        return response($response, 200, [], 'json');
+    }
+}

+ 36 - 0
crmeb/crmeb/services/easywechat/v3pay/ServiceProvider.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ *  +----------------------------------------------------------------------
+ *  | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
+ *  +----------------------------------------------------------------------
+ *  | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
+ *  +----------------------------------------------------------------------
+ *  | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
+ *  +----------------------------------------------------------------------
+ *  | Author: CRMEB Team <admin@crmeb.com>
+ *  +----------------------------------------------------------------------
+ */
+
+namespace crmeb\services\easywechat\v3pay;
+
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider
+ * @package crmeb\services\easywechat\v3pay
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+
+    /**
+     * @param Container $pimple
+     */
+    public function register(Container $pimple)
+    {
+        $pimple['v3pay'] = function ($pimple) {
+            return new PayClient($pimple['access_token'], $pimple);
+        };
+    }
+}

+ 4 - 3
crmeb/crmeb/services/express/storage/Express.php

@@ -110,11 +110,12 @@ class Express extends BaseExpress
      * @return 物流状态:status 0在途,1揽收,2疑难,3签收,4退签,5派件,6退回,7转单,10待清关,11清关中,12已清关,13清关异常,14收件人拒签
      * @return 物流详情 content
      */
-    public function query(string $num, string $com = '')
+    public function query(string $num, string $com = '', $phone = '')
     {
         $param = [
             'com' => $com,
-            'num' => $num
+            'num' => $num,
+            'phone' => $phone
         ];
         if ($com === null) {
             unset($param['com']);
@@ -156,7 +157,7 @@ class Express extends BaseExpress
         }
         /** @var ExpressServices $expressServices */
         $expressServices = app()->make(ExpressServices::class);
-        $expressData = $expressServices->getOneByWhere(['code'=>$param['com']])->toArray();
+        $expressData = $expressServices->getOneByWhere(['code' => $param['com']])->toArray();
         if (isset($data['cargo'])) $param['cargo'] = $data['cargo'];
         if ($expressData['partner_id'] == 1) $param['partner_id'] = $expressData['account'];
         if ($expressData['partner_key'] == 1) $param['partner_key'] = $expressData['key'];

+ 2 - 0
crmeb/crmeb/services/pay/Pay.php

@@ -14,6 +14,7 @@ namespace crmeb\services\pay;
 
 use crmeb\basic\BaseManager;
 use crmeb\services\pay\storage\AliPay;
+use crmeb\services\pay\storage\V3WechatPay;
 use crmeb\services\pay\storage\WechatPay;
 use think\facade\Config;
 
@@ -23,6 +24,7 @@ use think\facade\Config;
  * @package crmeb\services\pay
  * @mixin AliPay
  * @mixin WechatPay
+ * @mixin V3WechatPay
  */
 class Pay extends BaseManager
 {

+ 1 - 1
crmeb/crmeb/services/pay/PayInterface.php

@@ -72,6 +72,6 @@ interface PayInterface
      * 支付回调
      * @return mixed
      */
-    public static function handleNotify();
+    public function handleNotify();
 
 }

+ 216 - 0
crmeb/crmeb/services/pay/storage/V3WechatPay.php

@@ -0,0 +1,216 @@
+<?php
+/**
+ *  +----------------------------------------------------------------------
+ *  | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
+ *  +----------------------------------------------------------------------
+ *  | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
+ *  +----------------------------------------------------------------------
+ *  | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
+ *  +----------------------------------------------------------------------
+ *  | Author: CRMEB Team <admin@crmeb.com>
+ *  +----------------------------------------------------------------------
+ */
+
+namespace crmeb\services\pay\storage;
+
+use app\services\pay\PayNotifyServices;
+use app\services\wechat\WechatMessageServices;
+use crmeb\exceptions\PayException;
+use crmeb\services\app\MiniProgramService;
+use crmeb\services\easywechat\Application;
+use crmeb\services\pay\BasePay;
+use crmeb\services\pay\PayInterface;
+use crmeb\utils\Hook;
+use EasyWeChat\Payment\Order;
+use think\facade\Log;
+
+/**
+ * Class 微信支付v3
+ * @author 等风来
+ * @email 136327134@qq.com
+ * @date 2022/9/22
+ * @package crmeb\services\pay\storage
+ */
+class V3WechatPay extends BasePay implements PayInterface
+{
+
+    /**
+     * @var Application
+     */
+    protected $instance;
+
+    /**
+     * @param array $config
+     * @return mixed|void
+     * @author 等风来
+     * @email 136327134@qq.com
+     * @date 2022/9/22
+     */
+    protected function initialize(array $config = [])
+    {
+        $wechatAppid = sys_config('wechat_appid');
+        $config = [
+            'app' => [
+                'appid' => sys_config('wechat_app_appid'),
+            ],
+            'wechat' => [
+                'appid' => $wechatAppid,
+            ],
+            'miniprog' => [
+                'appid' => sys_config('routine_appId'),
+            ],
+            'web' => [
+                'appid' => sys_config('wechat_open_app_id', $wechatAppid),
+            ],
+            'v3_payment' => [
+                'mchid' => sys_config('pay_weixin_mchid'),
+                'key' => sys_config('pay_weixin_key_v3'),
+                'serial_no' => sys_config('pay_weixin_serial_no'),
+                'cert_path' => public_path() . $this->getPemPath(sys_config('pay_weixin_client_cert')),
+                'key_path' => public_path() . $this->getPemPath(sys_config('pay_weixin_client_key')),
+                'notify_url' => trim(sys_config('site_url')) . '/api/wechat/v3notify',
+            ]
+        ];
+        $this->instance = new Application($config);
+    }
+
+    /**
+     * 获取证书不带域名的路径
+     * @param string $path
+     * @return mixed|string
+     * @author 等风来
+     * @email 136327134@qq.com
+     * @date 2022/9/22
+     */
+    public function getPemPath(string $path)
+    {
+        if (strstr($path, 'http://') === false && strstr($path, 'https://') === false) {
+            return $path;
+        } else {
+            $res = parse_url($path);
+            return $res['path'] ?? '';
+        }
+    }
+
+    /**
+     * 创建订单返回支付参数
+     * @param string $orderId
+     * @param string $totalFee
+     * @param string $attach
+     * @param string $body
+     * @param string $detail
+     * @param array $options
+     * @return array|false|mixed|string
+     * @author 等风来
+     * @email 136327134@qq.com
+     * @date 2022/9/22
+     */
+    public function create(string $orderId, string $totalFee, string $attach, string $body, string $detail, array $options = [])
+    {
+        $this->authSetPayType();
+
+        switch ($this->payType) {
+            case Order::NATIVE:
+                $res = $this->instance->v3pay->nativePay($orderId, $totalFee, $body, $attach);
+                $res['invalid'] = time() + 60;
+                $res['logo'] = sys_config('wap_login_logo');
+                return $res;
+            case Order::APP:
+                return $this->instance->v3pay->appPay($orderId, $totalFee, $body, $attach);
+            case Order::JSAPI:
+                if (empty($options['openid'])) {
+                    throw new PayException('缺少openid');
+                }
+                if (request()->isRoutine()) {
+                    // 获取配置  判断是否为新支付
+                    if ($options['pay_new_weixin_open']) {
+                        return MiniProgramService::newJsPay($options['openid'], $orderId, $totalFee, $attach, $body, $detail, $options);
+                    }
+                    return $this->instance->v3pay->miniprogPay($options['openid'], $orderId, $totalFee, $body, $attach);
+                }
+                return $this->instance->v3pay->jsapiPay($options['openid'], $orderId, $totalFee, $body, $attach);
+            case 'h5':
+                return $this->instance->v3pay->h5Pay($orderId, $totalFee, $body, $attach);
+            default:
+                throw new PayException('微信支付:支付类型错误');
+        }
+    }
+
+    /**
+     * @param string $openid
+     * @param string $orderId
+     * @param string $amount
+     * @param array $options
+     * @return mixed
+     * @author 等风来
+     * @email 136327134@qq.com
+     * @date 2022/9/22
+     */
+    public function merchantPay(string $openid, string $orderId, string $amount, array $options = [])
+    {
+        $res = $this->instance->v3pay->batches(
+            $orderId,
+            $amount,
+            $options['batch_name'],
+            $options['batch_remark'],
+            [
+                [
+                    'out_detail_no' => $orderId,
+                    'transfer_amount' => $amount,
+                    'transfer_remark' => $options['batch_remark'],
+                    'openid' => $openid
+                ]
+            ]
+        );
+
+        return $res;
+    }
+
+    /**
+     * 发起退款
+     * @param string $outTradeNo
+     * @param array $options
+     * @return mixed
+     */
+    public function refund(string $outTradeNo, array $options = [])
+    {
+        return $this->instance->v3pay->refund($outTradeNo, $options);
+    }
+
+    /**
+     * 查询退款
+     * @param string $outTradeNo
+     * @param string|null $outRequestNo
+     * @param array $other
+     * @return mixed
+     */
+    public function queryRefund(string $outTradeNo, string $outRequestNo = null, array $other = [])
+    {
+        return $this->instance->v3pay->queryRefund($outTradeNo);
+    }
+
+    /**
+     * @return mixed|\think\Response
+     * @author 等风来
+     * @email 136327134@qq.com
+     * @date 2022/9/22
+     */
+    public function handleNotify()
+    {
+        return $this->instance->v3pay->handleNotify(function ($notify, $successful) {
+            Log::error('支付回调:' . json_encode($notify));
+            if ($successful && isset($notify->out_trade_no)) {
+                if (isset($notify->attach) && $notify->attach) {
+                    if (($count = strpos($notify->out_trade_no, '_')) !== false) {
+                        $notify->out_trade_no = substr($notify->out_trade_no, $count + 1);
+                    }
+                    return (new Hook(PayNotifyServices::class, 'wechat'))->listen($notify->attach, $notify->out_trade_no, $notify->transaction_id);
+                }
+                /** @var WechatMessageServices $wechatMessageService */
+                $wechatMessageService = app()->make(WechatMessageServices::class);
+                $wechatMessageService->setOnceMessage($notify, $notify->openid, 'payment_success', $notify->out_trade_no);
+                return false;
+            }
+        });
+    }
+}

+ 5 - 7
crmeb/crmeb/services/pay/storage/WechatPay.php

@@ -64,9 +64,8 @@ class WechatPay extends BasePay implements PayInterface
                 }
                 if (request()->isRoutine()) {
                     // 获取配置  判断是否为新支付
-                    if($options['pay_new_weixin_open'])
-                    {
-                        return MiniProgramService::newJsPay($options['openid'], $orderId, $totalFee, $attach, $body, $detail,$options);
+                    if ($options['pay_new_weixin_open']) {
+                        return MiniProgramService::newJsPay($options['openid'], $orderId, $totalFee, $attach, $body, $detail, $options);
                     }
                     return MiniProgramService::jsPay($options['openid'], $orderId, $totalFee, $attach, $body, $detail);
                 }
@@ -114,10 +113,9 @@ class WechatPay extends BasePay implements PayInterface
         if (isset($opt['wechat'])) {
             return WechatService::refund($outTradeNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundReason, $type, $refundAccount);
         } else {
-            if($opt['pay_new_weixin_open'])
-            {
+            if ($opt['pay_new_weixin_open']) {
                 return MiniProgramService::miniRefund($outTradeNo, $totalFee, $refundFee, $opt);
-            }else{
+            } else {
                 return MiniProgramService::refund($outTradeNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundReason, $type, $refundAccount);
             }
         }
@@ -140,7 +138,7 @@ class WechatPay extends BasePay implements PayInterface
      * @return mixed|\Symfony\Component\HttpFoundation\Response
      * @throws \EasyWeChat\Core\Exceptions\FaultException
      */
-    public static function handleNotify()
+    public function handleNotify()
     {
         return WechatService::handleNotify();
     }

+ 40 - 16
crmeb/public/install/crmeb.sql

@@ -15057,7 +15057,7 @@ INSERT INTO `eb_lang_code` (`id`, `type_id`, `code`, `remarks`, `lang_explain`,
 (13530, 1, '账户充值', '账户充值', '账户充值', 0),
 (13531, 1, '佣金转入', '佣金转入', '佣金转入', 0),
 (13532, 1, '周榜', '周榜', '周榜', 0),
-(13533, 1, '月榜', '月榜', '榜', 0),
+(13533, 1, '月榜', '月榜', '榜', 0),
 (13534, 1, '周排行', '周排行', '周排行', 0),
 (13535, 1, '月排行', '月排行', '月排行', 0),
 (13536, 1, '语言切换', '语言切换', '语言切换', 0),
@@ -17418,7 +17418,7 @@ INSERT INTO `eb_lang_code` (`id`, `type_id`, `code`, `remarks`, `lang_explain`,
 (15888, 3, '账户充值', '账户充值', '賬戶充值', 0),
 (15889, 3, '佣金转入', '佣金转入', '佣金轉入', 0),
 (15890, 3, '周榜', '周榜', '周榜', 0),
-(15891, 3, '月榜', '月榜', '榜', 0),
+(15891, 3, '月榜', '月榜', '榜', 0),
 (15892, 3, '周排行', '周排行', '週排行', 0),
 (15893, 3, '月排行', '月排行', '月排行', 0),
 (15894, 3, '语言切换', '语言切换', '語言切換', 0),
@@ -25755,7 +25755,27 @@ INSERT INTO `eb_lang_code` (`id`, `type_id`, `code`, `remarks`, `lang_explain`,
 (24217, 10, '提醒付款通知', '提醒付款通知', 'Ghi nhớ thông báo thanh toán', 0),
 (24218, 10, '短信验证码', '短信验证码', 'Mã kiểm tra SMS', 0),
 (24219, 10, '到店自提', '到店自提', 'Tự rút lui', 0),
-(24220, 10, '快递配送', '快递配送', 'Name', 0);
+(24220, 10, '快递配送', '快递配送', 'Name', 0),
+(24221, 1, '退款发起查询失败', '退款发起失败', '微信支付:发起退款查询失败', 0),
+(24222, 2, '退款发起查询失败', '退款发起失败', 'WeChat payment: Failed to launch refund query', 0),
+(24223, 3, '退款发起查询失败', '退款发起失败', '微信支付:發起退款査詢失敗', 0),
+(24224, 4, '退款发起查询失败', '退款发起失败', 'Paiement Wechat: impossible dinitier la requête de remboursement', 0),
+(24225, 5, '退款发起查询失败', '退款发起失败', 'Pagamento WeChat: avvio della richiesta di rimborso non riuscito', 0),
+(24226, 6, '退款发起查询失败', '退款发起失败', 'WeChat Pay:返金照会の開始に失敗しました', 0),
+(24227, 7, '退款发起查询失败', '退款发起失败', '위챗페이: 환불 조회 실패', 0),
+(24228, 8, '退款发起查询失败', '退款发起失败', '', 0),
+(24229, 9, '退款发起查询失败', '退款发起失败', 'ล้มเหลวในการเริ่มต้นการสอบถามเกี่ยวกับการคืนเงิน', 0),
+(24230, 10, '退款发起查询失败', '退款发起失败', 'Dịch vụ WeChat: không thể khởi chạy yêu cầu hoàn to àn', 0),
+(24231, 1, '500000', '发起退款查询失败', '微信支付:发起退款查询失败', 1),
+(24232, 2, '500000', '发起退款查询失败', 'WeChat payment: Failed to launch refund query', 1),
+(24233, 3, '500000', '发起退款查询失败', '微信支付:發起退款査詢失敗', 1),
+(24234, 4, '500000', '发起退款查询失败', 'Paiement Wechat: impossible dinitier la requête de remboursement', 1),
+(24235, 5, '500000', '发起退款查询失败', 'Pagamento WeChat: avvio della richiesta di rimborso non riuscito', 1),
+(24236, 6, '500000', '发起退款查询失败', 'WeChat Pay:返金照会の開始に失敗しました', 1),
+(24237, 7, '500000', '发起退款查询失败', '위챗페이: 환불 조회 실패', 1),
+(24238, 8, '500000', '发起退款查询失败', '', 1),
+(24239, 9, '500000', '发起退款查询失败', 'ล้มเหลวในการเริ่มต้นการสอบถามเกี่ยวกับการคืนเงิน', 1),
+(24240, 10, '500000', '发起退款查询失败', 'Dịch vụ WeChat: không thể khởi chạy yêu cầu hoàn to àn', 1);
 
 -- --------------------------------------------------------
 
@@ -26140,24 +26160,25 @@ CREATE TABLE IF NOT EXISTS `eb_lang_type` (
   `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1启用0禁用',
   `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否默认语言',
   `is_del` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
-  PRIMARY KEY (`id`)
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `id` (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='语言类型';
 
 --
 -- 转存表中的数据 `eb_lang_type`
 --
 
-INSERT INTO `eb_lang_type` (`id`, `language_name`, `file_name`, `status`, `is_default`) VALUES
-(1, '中文', 'zh-CN', 1, 1),
-(2, '英文', 'en-US', 1, 0),
-(3, '繁体中文', 'zh-Hant', 1, 0),
-(4, '法语', 'fr-FR', 1, 0),
-(5, '意大利语', 'it-IT', 1, 0),
-(6, '日语', 'ja-JP', 1, 0),
-(7, '韩语', 'ko-KR', 1, 0),
-(8, '蒙古语', 'mn-MN', 1, 0),
-(9, '泰语', 'th-TH', 1, 0),
-(10, '越南语', 'vi-VN', 1, 0);
+INSERT INTO `eb_lang_type` (`id`, `language_name`, `file_name`, `status`, `is_default`, `is_del`) VALUES
+(1, '中文', 'zh-CN', 1, 1, 0),
+(2, '英文', 'en-US', 1, 0, 0),
+(3, '繁体中文', 'zh-Hant', 1, 0, 0),
+(4, '法语', 'fr-FR', 1, 0, 0),
+(5, '意大利语', 'it-IT', 1, 0, 0),
+(6, '日语', 'ja-JP', 1, 0, 0),
+(7, '韩语', 'ko-KR', 1, 0, 0),
+(8, '蒙古语', 'mn-MN', 1, 0, 0),
+(9, '泰语', 'th-TH', 1, 0, 0),
+(10, '越南语', 'vi-VN', 1, 0, 0);
 
 -- --------------------------------------------------------
 
@@ -32353,7 +32374,10 @@ INSERT INTO `eb_system_config` (`id`, `menu_name`, `type`, `input_type`, `config
 (409, 'pay_new_weixin_open', 'radio', 'input', 4, '0=>关闭\n1=>打开', 1, '', 0, 0, '0', '小程序支付管理', '小程序开发后台有支付管理的请打开此开关', 0, 1),
 (410, 'pay_new_weixin_mchid', 'text', 'input', 4, '', 1, '', 0, 0, '\"\"', '小程序支付商户号', '小程序开通支付管理生成的商户号', 0, 1),
 (411, 'system_comment_time', 'text', 'number', 27, '', 1, '', 100, 0, '\"0\"', '自动评价时间', '自动评价时间(天),0为不开启自动评价', 0, 1),
-(412, 'comment_content', 'text', 'input', 27, '', 1, '', 100, 0, '\"\\u6b64\\u7528\\u6237\\u672a\\u505a\\u8bc4\\u4ef7\"', '自动评价文字', '自动评价显示的评价文字', 0, 1);
+(412, 'comment_content', 'text', 'input', 27, '', 1, '', 100, 0, '\"\\u6b64\\u7528\\u6237\\u672a\\u505a\\u8bc4\\u4ef7\"', '自动评价文字', '自动评价显示的评价文字', 0, 1),
+(413, 'pay_wechat_type', 'radio', 'input', 4, '0=>v2\n1=>v3', 1, '', 0, 0, '0', '支付接口类型', '支付接口类型v2对应微信支付旧版v2支付。v3对应微信支付v3支付接口。支付证书可以通用一个。支付秘钥和v2旧版支付有区别', 10, 1),
+(414, 'pay_weixin_serial_no', 'text', 'input', 4, '', 1, '', 100, 0, '\"\"', '证书序列号', '「商户API证书」的「证书序列号」,可以在证书管理里面查看', 0, 1),
+(415, 'pay_weixin_key_v3', 'text', 'input', 4, '', 1, '', 100, 0, '\"\"', 'V3支付Key', 'V3支付秘钥', 0, 1);
 
 -- --------------------------------------------------------
 

+ 6 - 6
crmeb/vendor/composer/installed.php

@@ -1,9 +1,9 @@
 <?php return array(
     'root' => array(
         'name' => 'topthink/think',
-        'pretty_version' => 'dev-master',
-        'version' => 'dev-master',
-        'reference' => '13adf9a931c339ffb7910da803bf28c4ed4d1d9b',
+        'pretty_version' => '4.6.0.x-dev',
+        'version' => '4.6.0.9999999-dev',
+        'reference' => '7894ca3eaf91b7c54ffb20443b7b0d51541546d3',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -764,9 +764,9 @@
             'dev_requirement' => false,
         ),
         'topthink/think' => array(
-            'pretty_version' => 'dev-master',
-            'version' => 'dev-master',
-            'reference' => '13adf9a931c339ffb7910da803bf28c4ed4d1d9b',
+            'pretty_version' => '4.6.0.x-dev',
+            'version' => '4.6.0.9999999-dev',
+            'reference' => '7894ca3eaf91b7c54ffb20443b7b0d51541546d3',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),

+ 1 - 5
crmeb/vendor/overtrue/wechat/src/Foundation/Application.php

@@ -105,11 +105,7 @@ class Application extends Container
         ServiceProviders\ShakeAroundServiceProvider::class,
         ServiceProviders\OpenPlatformServiceProvider::class,
         ServiceProviders\MiniProgramServiceProvider::class,
-        ServiceProviders\CommentServiceProvider::class,
-        \crmeb\services\easywechat\subscribe\ProgramProvider::class,
-        \crmeb\services\easywechat\wechatlive\ProgramProvider::class,
-        \crmeb\services\easywechat\Open3rd\ProgramProvider::class,
-        \crmeb\services\easywechat\miniPayment\ServiceProvider::class
+        ServiceProviders\CommentServiceProvider::class
     ];
 
     /**

+ 1 - 1
crmeb/vendor/services.php

@@ -1,5 +1,5 @@
 <?php 
-// This file is automatically generated at:2022-09-15 10:07:34
+// This file is automatically generated at:2022-09-20 16:14:20
 declare (strict_types = 1);
 return array (
   0 => 'think\\captcha\\CaptchaService',