فهرست منبع

第一次大修改

PC-7\Administrator 9 ماه پیش
والد
کامیت
82cc70d1dc

+ 2 - 0
app/admin/controller/package/Package.php

@@ -335,6 +335,7 @@ class Package extends Backend
         $unitValue = $this->request->post('unit_value', 0);
         $pointsValue = $this->request->post('points_value', 0);
         $description = $this->request->post('description', '');
+        $image = $this->request->post('image', '');
         $sort = $this->request->post('sort', 0);
 
         if (empty($packageId) || empty($specName)) {
@@ -382,6 +383,7 @@ class Package extends Backend
             'price' => $price,
             'stock' => $stock,
             'type' => $type,
+            'image' => $image,
             'card_type' => $cardType,
             'unit_value' => $unitValue,
             'points_value' => $pointsValue,

+ 225 - 11
app/api/controller/User.php

@@ -2,6 +2,9 @@
 
 namespace app\api\controller;
 
+use app\common\service\CommissionService;
+use app\common\service\InvitationService;
+use think\facade\Db;
 use Throwable;
 use ba\Captcha;
 use ba\ClickCaptcha;
@@ -12,11 +15,18 @@ use app\api\validate\User as UserValidate;
 
 class User extends Frontend
 {
-    protected array $noNeedLogin = ['checkIn', 'logout'];
+    protected array $noNeedLogin = ['*'];
+
+    protected $invitationService;
+    protected $commissionService;
 
     public function initialize(): void
     {
         parent::initialize();
+
+        $this->invitationService = new InvitationService();
+        $this->commissionService = new CommissionService();
+
     }
 
     /**
@@ -40,16 +50,21 @@ class User extends Frontend
         $userLoginCaptchaSwitch = Config::get('buildadmin.user_login_captcha');
 
         if ($this->request->isPost()) {
-            $params = $this->request->post(['tab', 'email', 'mobile', 'username', 'password', 'keep', 'captcha', 'captchaId', 'captchaInfo', 'registerType']);
+            $params = $this->request->post();
 
-            // 提前检查 tab ,然后将以 tab 值作为数据验证场景
-            if (!in_array($params['tab'] ?? '', ['login', 'register'])) {
+            // 检查必填的 tab 字段
+            if (!isset($params['tab']) || !in_array($params['tab'], ['login', 'register'])) {
                 $this->error(__('Unknown operation'));
             }
-
             $validate = new UserValidate();
             try {
-                $validate->scene($params['tab'])->check($params);
+                if ($params['tab'] == 'register') {
+                    $registerType = $params['registerType'] ?? 'email';
+
+                    $validate->scene('register_' . $registerType)->check($params);
+                } else {
+                    $validate->scene($params['tab'])->check($params);
+                }
             } catch (Throwable $e) {
                 $this->error($e->getMessage());
             }
@@ -57,23 +72,59 @@ class User extends Frontend
             if ($params['tab'] == 'login') {
                 if ($userLoginCaptchaSwitch) {
                     $captchaObj = new ClickCaptcha();
-                    if (!$captchaObj->check($params['captchaId'], $params['captchaInfo'])) {
+                    if (!isset($params['captchaId']) || !isset($params['captchaInfo']) ||
+                        !$captchaObj->check($params['captchaId'], $params['captchaInfo'])) {
                         $this->error(__('Captcha error'));
                     }
                 }
-                $res = $this->auth->login($params['username'], $params['password'], !empty($params['keep']));
+                $username = $params['username'] ?? '';
+                $password = $params['password'] ?? '';
+                $keep = !empty($params['keep']);
+
+                if (empty($username) || empty($password)) {
+                    $this->error(__('Please enter username and password'));
+                }
+
+                $res = $this->auth->login($username, $password, $keep);
             } elseif ($params['tab'] == 'register') {
+                $registerType = $params['registerType'] ?? 'email';
+                // 验证码验证
                 $captchaObj = new Captcha();
-                if (!$captchaObj->check($params['captcha'], $params[$params['registerType']] . 'user_register')) {
+                $captchaKey = $params[$registerType] ?? '';
+                if (empty($captchaKey) || !$captchaObj->check($params['captcha'] ?? '', $captchaKey . 'user_register')) {
                     $this->error(__('Please enter the correct verification code'));
                 }
-                $res = $this->auth->register($params['username'], $params['password'], $params['mobile'], $params['email']);
+
+                $username = $params['username'] ?? '';
+                $password = $params['password'] ?? '';
+                $mobile = $params['mobile'] ?? '';
+                $email = $params['email'] ?? '';
+                $invitationCode = $params['inviteCode'] ?? ''; // 用户输入的邀请码(别人的)
+                // 根据注册类型验证
+                if ($registerType == 'mobile' && empty($mobile)) {
+                    $this->error(__('Please enter mobile number'));
+                } elseif ($registerType == 'email' && empty($email)) {
+                    $this->error(__('Please enter email address'));
+                }
+
+                // 正常注册
+                $group = 0;
+                $res = $this->auth->register($username, $password, $mobile, $email, $group);
+
+                // 注册成功后处理邀请关系,并为用户生成自己的邀请码
+                if ($res === true) {
+                    $userId = $this->auth->id;
+                    if ($userId) {
+                        // 处理邀请关系(传入用户输入的邀请码)
+                        $this->invitationService->processRegistration($userId, $invitationCode);
+                    }
+                }
             }
 
             if (isset($res) && $res === true) {
                 $this->success(__('Login succeeded!'), [
                     'userInfo'  => $this->auth->getUserInfo(),
-                    'routePath' => '/user'
+                    'routePath' => '/'
                 ]);
             } else {
                 $msg = $this->auth->getError();
@@ -88,6 +139,169 @@ class User extends Frontend
         ]);
     }
 
+    /**
+     * 我的主页数据
+     */
+    public function dashboard(): void
+    {
+        // 确保用户已登录
+        if (!$this->auth->isLogin()) {
+            $this->error('请先登录');
+        }
+
+        $userId = $this->auth->id;
+        if (!$userId) {
+            $this->error('用户ID获取失败');
+        }
+
+        try {
+            // 1. 获取用户基本信息
+            $user = Db::table('ba_user')
+                ->where('id', $userId)
+                ->field('id, username, nickname, email, mobile, avatar, 
+                    last_login_time, join_time, join_ip, create_time')
+                ->find();
+
+            if (!$user) {
+                $this->error('用户信息不存在');
+            }
+
+            // 2. 获取邀请统计
+            $invitationService = new InvitationService();
+            $invitationStats = $invitationService->getUserInvitationStats($userId);
+
+            // 3. 获取佣金信息
+            $commissionService = new CommissionService();
+            $commissionDetail = $commissionService->getUserCommissionDetail($userId);
+
+            // 4. 格式化时间
+            $formatTime = function($timestamp) {
+                if (!$timestamp) return '';
+                if (is_numeric($timestamp)) {
+                    return date('Y-m-d H:i:s', $timestamp);
+                }
+                return $timestamp;
+            };
+
+            $response = [
+                // 用户基本信息
+                'user_info' => [
+                    'email' => $user['email'],
+                    'username' => $user['username'],
+                    'nickname' => $user['nickname'],
+                    'mobile' => $user['mobile'],
+                    'avatar' => $user['avatar'],
+                    'join_time' => $formatTime($user['join_time']),
+                    'create_time' => $formatTime($user['create_time']),
+                ],
+
+                // 登录信息
+                'login_info' => [
+                    'last_login_time' => $formatTime($user['last_login_time']),
+                    'current_login_time' => date('Y-m-d H:i:s'),
+                    'join_ip' => $user['join_ip'],
+                ],
+
+                // 推广信息
+                'promotion_info' => [
+                    'invitation_code' => $invitationStats['invitation_code'],
+                    'promotion_link' => $commissionDetail['commission']['promotion_link'],
+                    'invitation_count' => $invitationStats['invitation_count'],
+                    'total_invitation' => $invitationStats['commission']['total_invitation'] ?? 0,
+                ],
+
+                // 佣金信息
+                'commission_info' => [
+                    'current_commission' => $commissionDetail['commission']['current_commission'],
+                    'total_commission' => $commissionDetail['commission']['total_commission'],
+                    'withdrawn_commission' => $commissionDetail['commission']['withdrawn_commission'],
+                    'frozen_commission' => $commissionDetail['commission']['frozen_commission'],
+                    'today_income' => $commissionDetail['stats']['today_income'] ?? 0,
+                    'month_income' => $commissionDetail['stats']['month_income'] ?? 0,
+                ],
+
+                // 快速统计
+//                'quick_stats' => [
+//                    'total_orders' => $this->getUserOrderCount($userId),
+//                    'pending_withdrawals' => $this->getPendingWithdrawalCount($userId),
+//                ]
+            ];
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('获取我的主页数据失败: ' . $e->getMessage());
+            $this->error('获取数据失败');
+        }
+        $this->success('', $response);
+    }
+
+
+    /**
+     * 获取用户邀请统计
+     */
+    public function invitationStats(): void
+    {
+        $userId = $this->auth->id;
+
+        $stats = $this->invitationService->getUserInvitationStats($userId);
+
+        $this->success('', $stats);
+    }
+
+    /**
+     * 获取下级用户列表
+     */
+    public function subordinateList(): void
+    {
+        $userId = $this->auth->id;
+        $page = $this->request->get('page', 1);
+        $pageSize = $this->request->get('pageSize', 10);
+
+        $result = $this->invitationService->getSubordinateList($userId, $page, $pageSize);
+
+        $this->success('', $result);
+    }
+
+    /**
+     * 获取佣金详情
+     */
+    public function commissionDetail(): void
+    {
+        $userId = $this->auth->id;
+        $detail = $this->commissionService->getUserCommissionDetail($userId);
+
+        $this->success('', $detail);
+    }
+
+    /**
+     * 获取佣金流水
+     */
+    public function commissionFlow(): void
+    {
+        $userId = $this->auth->id;
+        $page = $this->request->get('page', 1);
+        $pageSize = $this->request->get('pageSize', 20);
+
+        try {
+            $list = \think\facade\Db::name('commission_flow')
+                ->where('user_id', $userId)
+                ->order('create_time', 'desc')
+                ->paginate([
+                    'page' => $page,
+                    'list_rows' => $pageSize
+                ]);
+        } catch (\Throwable $e) {
+            $this->error('获取佣金流水失败');
+        }
+
+        $this->success('', [
+            'list' => $list->items(),
+            'total' => $list->total(),
+            'page' => $list->currentPage(),
+            'page_size' => $list->listRows(),
+            'page_count' => $list->lastPage()
+        ]);
+    }
+
     public function logout(): void
     {
         if ($this->request->isPost()) {

+ 235 - 0
app/api/controller/invitation/Allocation.php

@@ -0,0 +1,235 @@
+<?php
+namespace app\api\controller\invitation;
+
+use app\common\controller\Frontend;
+use app\common\service\CompanyPackageService;
+use think\facade\Db;
+
+class Allocation extends Frontend
+{
+    protected array $noNeedPermission = ['allocationList', 'assign', 'cancel'];
+    protected $companyPackageService;
+
+    public function initialize(): void
+    {
+        parent::initialize();
+        $this->companyPackageService = new CompanyPackageService();
+    }
+
+    /**
+     * 获取可分配的企业套餐列表(区分类型)
+     */
+    public function allocationList()
+    {
+        $userId = $this->auth->id;
+
+        // 获取用户所在企业
+        $companyService = new CompanyPackageService();
+        $companyInfo = $companyService->getUserCompany($userId);
+        if (!$companyInfo) {
+            $this->error('您不属于任何企业');
+        }
+
+        $result = $this->companyPackageService->getAvailableCompanyPackages(
+            $companyInfo['id'],
+            $userId
+        );
+
+        $this->success('', $result);
+    }
+
+    /**
+     * 分配套餐给企业成员(统一处理所有卡类型)
+     */
+    public function assign()
+    {
+        if (!$this->request->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $userId = $this->auth->id;
+        $seatsId = $this->request->post('seats_id', 0);
+        $assignedToUserId = $this->request->post('assigned_to_user_id', 0);
+        $remark = $this->request->post('remark', '');
+
+        if ($seatsId <= 0 || $assignedToUserId <= 0) {
+            $this->error('参数错误');
+        }
+
+        try {
+            // 获取席位信息以确定卡类型
+            $seats = Db::table('ba_company_package_seats')
+                ->where('id', $seatsId)
+                ->find();
+
+            if (!$seats) {
+                $this->error('企业套餐不存在');
+            }
+
+            if ($seats['card_type'] >= 3) {
+                // 日期卡:使用原有的分配逻辑
+                $result = $this->companyPackageService->allocatePackage(
+                    $seatsId,
+                    $assignedToUserId,
+                    $userId,
+                    $remark
+                );
+            } else {
+                // 次卡/积分卡:使用新的权限分配逻辑
+                $result = $this->companyPackageService->assignPackagePermission(
+                    $seatsId,
+                    $assignedToUserId,
+                    $userId,
+                    $remark
+                );
+            }
+
+        } catch (\Throwable $e) {
+            $this->error($e->getMessage());
+        }
+
+        if ($result['success'] ?? $result) {
+            $this->success('分配成功');
+        } else {
+            $this->error('分配失败');
+        }
+    }
+
+    /**
+     * 取消分配
+     */
+    public function cancel()
+    {
+        if (!$this->request->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $userId = $this->auth->id;
+        $allocationId = $this->request->post('allocation_id', 0);
+        $remark = $this->request->post('remark', '');
+
+        if ($allocationId <= 0) {
+            $this->error('参数错误');
+        }
+
+        try {
+            // 获取分配记录以确定卡类型
+            $allocation = Db::table('ba_company_package_allocation')
+                ->alias('cpa')
+                ->leftJoin('ba_package_price pp', 'pp.id = cpa.package_price_id')
+                ->where('cpa.id', $allocationId)
+                ->field('cpa.*, pp.card_type')
+                ->find();
+;
+            if (!$allocation) {
+                $this->error('分配记录不存在');
+            }
+
+            if ($allocation['card_type'] >= 3) {
+                // 日期卡:使用原有的取消分配逻辑
+                $result = $this->companyPackageService->cancelAllocation(
+                    $allocationId,
+                    $userId,
+                    $remark
+                );
+            } else {
+                // 次卡/积分卡:使用新的权限取消逻辑
+                $result = $this->companyPackageService->cancelPackagePermission(
+                    $allocationId,
+                    $userId,
+                    $remark
+                );
+            }
+
+
+        } catch (\Throwable $e) {
+            $this->error($e->getMessage());
+        }
+        if ($result) {
+            $this->success('取消分配成功');
+        } else {
+            $this->error('取消分配失败');
+        }
+    }
+
+    /**
+     * 获取企业成员已分配的套餐
+     */
+    public function myAssignedPackages()
+    {
+        $userId = $this->auth->id;
+
+            $allocations = Db::name('ba_company_package_allocation')
+                ->alias('cpa')
+                ->leftJoin('ba_product p', 'p.id = cpa.product_id')
+                ->leftJoin('ba_package_price pp', 'pp.id = cpa.package_price_id')
+                ->leftJoin('ba_user u', 'u.id = cpa.assigned_by_user_id')
+                ->where('cpa.assigned_to_user_id', $userId)
+                ->where('cpa.allocation_status', 1) // 已分配
+                ->field('cpa.*, 
+                    p.name as product_name, p.type as product_type, p.link,
+                    pp.spec_name, pp.type as package_type, pp.card_type,
+                    u.nickname as assigned_by_name')
+                ->select()
+                ->toArray();
+
+            $result = [];
+            foreach ($allocations as $allocation) {
+                $result[] = [
+                    'allocation_id' => $allocation['id'],
+                    'product_name' => $allocation['product_name'],
+                    'product_type' => $allocation['product_type'],
+                    'product_link' => $allocation['link'],
+                    'spec_name' => $allocation['spec_name'],
+                    'package_type' => $allocation['package_type'],
+                    'card_type' => $allocation['card_type'],
+                    'card_type_text' => $this->companyPackageService->getCardTypeText($allocation['card_type']),
+                    'end_time' => $allocation['end_time'],
+                    'remaining_times' => $allocation['remaining_times'],
+                    'remaining_points' => $allocation['remaining_points'],
+                    'assigned_by' => $allocation['assigned_by_name'],
+                    'allocation_time' => $allocation['allocation_time']
+                ];
+            }
+
+            $this->success('', $result);
+    }
+
+
+    /**
+     * 使用企业次卡/积分卡
+     */
+    public function useSharedPackage()
+    {
+        if (!$this->request->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $userId = $this->auth->id;
+        $packagePriceId = $this->request->post('package_price_id', 0);
+        $quantity = $this->request->post('quantity', 1);
+        $remark = $this->request->post('remark', '');
+
+        if ($packagePriceId <= 0 || $quantity <= 0) {
+            $this->error('参数错误');
+        }
+
+            $result = $this->companyPackageService->useSharedCompanyPackage(
+                $userId,
+                $packagePriceId,
+                $quantity,
+                null,
+                $remark
+            );
+
+            if ($result['success']) {
+                $this->success($result['message'], [
+                    'remaining_times' => $result['remaining_times'],
+                    'remaining_points' => $result['remaining_points']
+                ]);
+            } else {
+                $this->error('使用失败');
+            }
+
+    }
+}

+ 597 - 0
app/api/controller/invitation/Company.php

@@ -0,0 +1,597 @@
+<?php
+
+namespace app\api\controller\invitation;
+
+use app\common\controller\Frontend;
+use think\facade\Db;
+
+class Company extends Frontend
+{
+    protected array $noNeedPermission = ['myCompanies', 'members', 'addMember', 'removeMember', 'createCompany', 'updateCompany', 'deleteCompany','searchEmail','checkCompany'];
+
+
+    /**
+     * 检查当前用户是否已有企业
+     */
+    public function checkCompany(): void
+    {
+            $userId = $this->auth->id;
+            if (!$userId) {
+                $this->error('用户未登录');
+            }
+
+            $hasCompany = $this->hasCompany($userId);
+            $companyInfo = $hasCompany ? $this->getUserCompany($userId) : null;
+
+            $this->success('', [
+                'has_company' => $hasCompany,
+                'company' => $companyInfo,
+                'message' => $hasCompany
+                    ? '您已' . ($companyInfo['role'] == 'creator' ? '创建' : '加入') . '企业《' . ($companyInfo['name'] ?? '') . '》'
+                    : '您尚未创建或加入任何企业'
+            ]);
+    }
+
+    /**
+     * 检查用户是否已有企业(创建或加入)
+     * @return bool 返回true表示已有企业,false表示没有企业
+     */
+    private function hasCompany($userId): bool
+    {
+        try {
+            // 1. 检查是否创建了企业
+            $createdCompany = Db::table('ba_company')
+                ->where('creator_id', $userId)
+                ->where('status', 1) // 只检查正常状态的企业
+                ->count();
+
+            if ($createdCompany > 0) {
+                return true;
+            }
+
+            // 2. 检查是否加入了企业(作为成员)
+            $joinedCompany = Db::table('ba_company_member')
+                ->alias('cm')
+                ->leftJoin('ba_company c', 'c.id = cm.company_id')
+                ->where('cm.user_id', $userId)
+                ->where('cm.status', 1) // 成员状态正常
+                ->where('c.status', 1) // 企业状态正常
+                ->count();
+
+            return $joinedCompany > 0;
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('检查用户是否已有企业失败: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+
+    /**
+     * 获取用户的唯一企业信息(如果有)
+     * @return array|null 返回企业信息,没有则返回null
+     */
+    private function getUserCompany($userId): ?array
+    {
+        try {
+            // 先检查是否是创建人
+            $createdCompany = Db::table('ba_company')
+                ->where('creator_id', $userId)
+                ->where('status', 1)
+                ->field('id, name, creator_id, creator_name, status')
+                ->find();
+
+            if ($createdCompany) {
+                $createdCompany['role'] = 'creator'; // 角色:创建人
+                return $createdCompany;
+            }
+
+            // 再检查是否是成员
+            $joinedCompany = Db::table('ba_company_member')
+                ->alias('cm')
+                ->leftJoin('ba_company c', 'c.id = cm.company_id')
+                ->where('cm.user_id', $userId)
+                ->where('cm.status', 1)
+                ->where('c.status', 1)
+                ->field('c.id, c.name, cm.role, cm.name as member_name, cm.email as member_email, cm.join_time')
+                ->find();
+
+            if ($joinedCompany) {
+                $joinedCompany['role'] = $joinedCompany['role'] == 1 ? 'admin' : 'member'; // 管理员或普通成员
+                return $joinedCompany;
+            }
+
+            return null;
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('获取用户企业信息失败: ' . $e->getMessage());
+            return null;
+        }
+    }
+
+
+    /**
+     * 获取我的企业列表(我创建的企业)
+     */
+    public function myCompanies(): void
+    {
+
+            $userId = $this->auth->id;
+            if (!$userId) {
+                $this->error('用户未登录');
+            }
+
+            // 获取用户的唯一企业
+            $companyInfo = $this->getUserCompany($userId);
+
+            if (!$companyInfo) {
+                $this->success('您尚未创建或加入任何企业', [
+                    'has_company' => false,
+                    'company' => null,
+                    'message' => '您可以创建一个新企业,或者请其他企业管理员邀请您加入'
+                ]);
+            }
+
+            // 获取企业成员数量
+            $memberCount = Db::table('ba_company_member')
+                ->where('company_id', $companyInfo['id'])
+                ->where('status', 1)
+                ->count();
+
+            $companyInfo['member_count'] = $memberCount;
+            $companyInfo['has_company'] = true;
+
+            // 格式化角色文本
+            if (isset($companyInfo['role'])) {
+                switch ($companyInfo['role']) {
+                    case 'creator':
+                        $companyInfo['role_text'] = '企业创建人';
+                        break;
+                    case 'admin':
+                        $companyInfo['role_text'] = '管理员';
+                        break;
+                    case 'member':
+                        $companyInfo['role_text'] = '普通成员';
+                        break;
+                    default:
+                        $companyInfo['role_text'] = '未知角色';
+                }
+            }
+
+            // 格式化时间
+            if (isset($companyInfo['join_time'])) {
+                $companyInfo['join_time_formatted'] = date('Y-m-d H:i', strtotime($companyInfo['join_time']));
+            }
+
+            if (isset($companyInfo['create_time'])) {
+                $companyInfo['create_time_formatted'] = date('Y-m-d H:i', strtotime($companyInfo['create_time']));
+            }
+
+            $this->success('', $companyInfo);
+
+
+
+    }
+
+    /**
+     * 获取企业成员列表
+     */
+    public function members(): void
+    {
+            $userId = $this->auth->id;
+            $companyId = $this->request->get('company_id', 0);
+            $page = $this->request->get('page', 1);
+            $pageSize = $this->request->get('pageSize', 10);
+
+            if ($companyId <= 0) {
+                $this->error('请选择企业');
+            }
+
+            // 验证用户是否有权限查看该企业
+            $company = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->field('id, creator_id')
+                ->find();
+
+            if (!$company) {
+                $this->error('企业不存在');
+            }
+            // 检查用户是否是企业创建人或成员
+            $isCreator = $company['creator_id'] == $userId;
+            $isMember = Db::table('ba_company_member')
+                ->where('company_id', $companyId)
+                ->where('user_id', $userId)
+                ->find();
+            if (!$isCreator && !$isMember) {
+                $this->error('无权查看该企业');
+            }
+
+            // 查询成员列表
+            $query = Db::table('ba_company_member')
+                ->where('company_id', $companyId);
+
+            $total = $query->count();
+            $members = $query->field('id, name, email, role, status, join_time, create_time')
+                ->order('role', 'asc')
+                ->order('join_time', 'asc')
+                ->page($page, $pageSize)
+                ->select()
+                ->toArray();
+
+            // 处理成员数据
+            foreach ($members as &$member) {
+                $member['role_text'] = $member['role'] == 1 ? '创建人/管理员' : '普通成员';
+                $member['status_text'] = $member['status'] == 1 ? '正常' : '禁用';
+
+                // 是否可以操作(只有创建人可以操作)
+                $member['can_operate'] = $isCreator && $member['role'] != 1; // 创建人不能操作自己
+            }
+
+            $this->success('', [
+                'list' => $members,
+                'total' => $total,
+                'page' => $page,
+                'page_size' => $pageSize,
+                'is_creator' => $isCreator // 前端根据这个判断是否显示操作按钮
+            ]);
+
+    }
+
+    /**
+     * 通过邮箱添加成员
+     */
+    public function addMember(): void
+    {
+
+            if (!$this->request->isPost()) {
+                $this->error('非法请求');
+            }
+
+            $userId = $this->auth->id;
+            $companyId = $this->request->post('company_id', 0);
+            $email = $this->request->post('email', '');
+            $name = $this->request->post('name', '');
+
+            if ($companyId <= 0) {
+                $this->error('请选择企业');
+            }
+
+            if (empty($email) || empty($name)) {
+                $this->error('邮箱和姓名不能为空');
+            }
+
+            // 验证邮箱格式
+            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+                $this->error('邮箱格式不正确');
+            }
+
+            // 验证用户是否有权限添加成员(必须是企业创建人)
+            $company = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->where('creator_id', $userId)
+                ->find();
+
+            if (!$company) {
+                $this->error('无权添加成员或企业不存在');
+            }
+
+            // 检查邮箱是否已经是成员
+            $exists = Db::table('ba_company_member')
+                ->where('company_id', $companyId)
+                ->where('email', $email)
+                ->find();
+
+            if ($exists) {
+                $this->error('该邮箱已经是企业成员');
+            }
+
+            // 查询邮箱对应的用户ID(如果用户已注册)
+            $user = Db::table('ba_user')
+                ->where('email', $email)
+                ->field('id')
+                ->find();
+
+            $userIdForMember = $user ? $user['id'] : null;
+
+            // 添加成员
+            $memberData = [
+                'company_id' => $companyId,
+                'user_id' => $userIdForMember,
+                'email' => $email,
+                'name' => $name,
+                'role' => 2, // 普通成员
+                'status' => 1,
+                'join_time' => date('Y-m-d H:i:s'),
+                'create_time' => date('Y-m-d H:i:s'),
+                'update_time' => date('Y-m-d H:i:s')
+            ];
+
+            $result = Db::table('ba_company_member')->insert($memberData);
+
+            if ($result) {
+                $this->success('添加成员成功');
+            } else {
+                $this->error('添加成员失败');
+            }
+
+    }
+
+    /**
+     * 移除企业成员
+     */
+    public function removeMember(): void
+    {
+
+            if (!$this->request->isPost()) {
+                $this->error('非法请求');
+            }
+
+            $userId = $this->auth->id;
+            $memberId = $this->request->post('member_id', 0);
+
+            if ($memberId <= 0) {
+                $this->error('成员ID错误');
+            }
+
+            // 查询成员信息
+            $member = Db::table('ba_company_member')
+                ->where('id', $memberId)
+                ->find();
+
+            if (!$member) {
+                $this->error('成员不存在');
+            }
+
+            // 验证用户是否有权限移除成员(必须是企业创建人)
+            $company = Db::table('ba_company')
+                ->where('id', $member['company_id'])
+                ->where('creator_id', $userId)
+                ->find();
+
+            if (!$company) {
+                $this->error('无权移除成员');
+            }
+
+            // 不能移除创建人自己
+            if ($member['role'] == 1) {
+                $this->error('不能移除创建人');
+            }
+
+            // 移除成员
+            $result = Db::table('ba_company_member')
+                ->where('id', $memberId)
+                ->delete();
+
+            if ($result) {
+                $this->success('移除成员成功');
+            } else {
+                $this->error('移除成员失败');
+            }
+
+    }
+
+    /**
+     * 创建企业
+     */
+    public function createCompany(): void
+    {
+
+            if (!$this->request->isPost()) {
+                $this->error('非法请求');
+            }
+
+            $userId = $this->auth->id;
+            $name = $this->request->post('name', '');
+
+            if (empty($name)) {
+                $this->error('企业名称不能为空');
+            }
+
+
+        if ($this->hasCompany($userId)) {
+            $companyInfo = $this->getUserCompany($userId);
+            $role = isset($companyInfo['role']) ? $companyInfo['role'] : '';
+            $companyName = isset($companyInfo['name']) ? $companyInfo['name'] : '';
+
+            $message = "您已经";
+            if ($role == 'creator') {
+                $message .= "创建了企业《{$companyName}》";
+            } elseif ($role == 'admin') {
+                $message .= "加入了企业《{$companyName}》并拥有管理员权限";
+            } elseif ($role == 'member') {
+                $message .= "加入了企业《{$companyName}》";
+            }
+            $message .= ",无法再创建新企业。";
+
+            $this->error($message);
+        }
+
+            // 检查企业名称是否已存在
+            $exists = Db::table('ba_company')
+                ->where('name', $name)
+                ->find();
+
+            if ($exists) {
+                $this->error('企业名称已存在');
+            }
+
+            // 获取当前用户信息
+            $user = Db::table('ba_user')
+                ->where('id', $userId)
+                ->field('username, nickname')
+                ->find();
+
+            $creatorName = $user['nickname'] ?: $user['username'];
+
+            // 创建企业
+            $companyData = [
+                'name' => $name,
+                'creator_id' => $userId,
+                'creator_name' => $creatorName,
+                'status' => 1,
+                'create_time' => date('Y-m-d H:i:s'),
+                'update_time' => date('Y-m-d H:i:s')
+            ];
+
+            $companyId = Db::table('ba_company')->insertGetId($companyData);
+
+            if ($companyId) {
+                // 将创建人自己添加到企业成员表中
+                $memberData = [
+                    'company_id' => $companyId,
+                    'user_id' => $userId,
+                    'email' => $this->auth->email ?: '',
+                    'name' => $creatorName,
+                    'role' => 1, // 创建人/管理员
+                    'status' => 1,
+                    'join_time' => date('Y-m-d H:i:s'),
+                    'create_time' => date('Y-m-d H:i:s'),
+                    'update_time' => date('Y-m-d H:i:s')
+                ];
+
+                Db::table('ba_company_member')->insert($memberData);
+
+                $this->success('创建企业成功', [
+                    'company_id' => $companyId,
+                    'name' => $name
+                ]);
+            } else {
+                $this->error('创建企业失败');
+            }
+
+    }
+
+    /**
+     * 更新企业信息
+     */
+    public function updateCompany(): void
+    {
+
+            if (!$this->request->isPost()) {
+                $this->error('非法请求');
+            }
+
+            $userId = $this->auth->id;
+            $companyId = $this->request->post('company_id', 0);
+            $name = $this->request->post('name', '');
+
+            if ($companyId <= 0) {
+                $this->error('企业ID错误');
+            }
+
+            if (empty($name)) {
+                $this->error('企业名称不能为空');
+            }
+
+            // 验证用户是否有权限修改(必须是企业创建人)
+            $company = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->where('creator_id', $userId)
+                ->find();
+
+            if (!$company) {
+                $this->error('无权修改企业信息或企业不存在');
+            }
+
+            // 检查企业名称是否与其他企业重名(排除自己)
+            $exists = Db::table('ba_company')
+                ->where('name', $name)
+                ->where('id', '<>', $companyId)
+                ->find();
+
+            if ($exists) {
+                $this->error('企业名称已存在');
+            }
+
+            // 更新企业信息
+            $result = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->update([
+                    'name' => $name,
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+
+            if ($result !== false) {
+                $this->success('更新企业信息成功');
+            } else {
+                $this->error('更新企业信息失败');
+            }
+
+    }
+
+    /**
+     * 删除企业
+     */
+    public function deleteCompany(): void
+    {
+        try {
+            if (!$this->request->isPost()) {
+                $this->error('非法请求');
+            }
+
+            $userId = $this->auth->id;
+            $companyId = $this->request->post('company_id', 0);
+
+            if ($companyId <= 0) {
+                $this->error('企业ID错误');
+            }
+
+            // 验证用户是否有权限删除(必须是企业创建人)
+            $company = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->where('creator_id', $userId)
+                ->find();
+
+            if (!$company) {
+                $this->error('无权删除企业或企业不存在');
+            }
+
+            // 删除企业(会自动删除关联的成员,因为有外键约束)
+            $result = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->delete();
+
+            if ($result) {
+                $this->success('删除企业成功');
+            } else {
+                $this->error('删除企业失败');
+            }
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('删除企业失败: ' . $e->getMessage());
+            $this->error('删除企业失败');
+        }
+    }
+
+    /**
+     * 搜索邮箱用户(用于添加成员时搜索)
+     */
+    public function searchEmail(): void
+    {
+
+            $keyword = $this->request->get('keyword', '');
+
+            if (empty($keyword)) {
+                $this->success('', []);
+            }
+
+            // 搜索用户表,查找邮箱匹配的用户
+            $users = Db::table('ba_user')
+                ->whereLike('email', "%{$keyword}%")
+                ->field('id, username, nickname, email')
+                ->limit(10)
+                ->select()
+                ->toArray();
+
+            // 格式化返回数据
+            $result = [];
+            foreach ($users as $user) {
+                $result[] = [
+                    'id' => $user['id'],
+                    'name' => $user['nickname'] ?: $user['username'],
+                    'email' => $user['email']
+                ];
+            }
+
+            $this->success('', $result);
+
+    }
+}

+ 395 - 0
app/api/controller/invitation/Order.php

@@ -0,0 +1,395 @@
+<?php
+
+namespace app\api\controller\invitation;
+
+use app\common\controller\Frontend;
+use app\common\service\CommissionService;
+use think\facade\Db;
+
+class Order extends Frontend
+{
+    protected array $noNeedPermission = ['purchaseRecordsSimple','orderDetail'];
+    protected $commissionService;
+
+    public function initialize(): void
+    {
+        parent::initialize();
+        $this->commissionService = new CommissionService();
+    }
+
+
+    /**
+     * 获取用户购买记录(简化版)
+     */
+    public function purchaseRecordsSimple(): void
+    {
+        // 确保用户已登录
+        if (!$this->auth->isLogin()) {
+            $this->error('请先登录');
+        }
+
+        $userId = $this->auth->id;
+        if (!$userId) {
+            $this->error('用户ID获取失败');
+        }
+
+//        try {
+            // 获取查询参数
+            $page = $this->request->get('page', 1);
+            $pageSize = $this->request->get('pageSize', 10);
+            $status = $this->request->get('status', 'all'); // all, paid, unpaid
+
+            // 构建查询条件
+            $query = Db::table('ba_order')
+                ->where('user_id', $userId);
+
+            // 根据状态筛选
+            if ($status === 'paid') {
+                $query->where('pay_status', 1); // 已支付
+            } elseif ($status === 'unpaid') {
+                $query->where('pay_status', 0); // 未支付
+            }
+
+            // 获取订单总数
+            $total = $query->count();
+
+            // 获取订单列表(只取必要字段)
+            $orders = $query->field('order_no, pay_amount, pay_method, pay_status, order_time, pay_time')
+                ->order('order_time', 'desc')
+                ->page($page, $pageSize)
+                ->select()
+                ->toArray();
+            // 处理每个订单的商品信息
+            $orderNos = array_column($orders, 'order_no');
+
+            if (!empty($orderNos)) {
+                // 批量查询订单商品
+                $orderItems = \think\facade\Db::table('ba_order_item')
+                    ->where('order_id', 'IN', Db::table('ba_order')
+                        ->where('order_no', 'IN', $orderNos)
+                        ->column('id'))
+                    ->field('order_id, item_name, quantity, unit_price, total_price')
+                    ->select()
+                    ->toArray();
+
+                // 按order_id分组商品
+                $itemsByOrderId = [];
+                foreach ($orderItems as $item) {
+                    $itemsByOrderId[$item['order_id']][] = $item;
+                }
+
+                // 获取订单ID映射
+                $orderIdMap = \think\facade\Db::table('ba_order')
+                    ->where('order_no', 'IN', $orderNos)
+                    ->column('id', 'order_no');
+
+                // 合并商品信息到订单
+                foreach ($orders as &$order) {
+                    // 注意:现在 $orderIdMap 的键是 order_no,值是对应的 id
+                    $orderId = $orderIdMap[$order['order_no']] ?? 0;
+
+                    // 取第一个商品作为主商品信息(根据您的截图,一单一个商品)
+                    $mainItem = $itemsByOrderId[$orderId][0] ?? [];
+
+                    // 商品名称(如果有多个商品,显示第一个)
+                    $order['product_name'] = $mainItem['item_name'] ?? '商品';
+
+                    // 数量
+                    $order['quantity'] = $mainItem['quantity'] ?? 1;
+
+                    // 格式化显示字段
+                    $order['amount'] = '¥' . number_format($order['pay_amount'], 2);
+                    $order['pay_method_text'] = $this->getSimplePayMethodText($order['pay_method']);
+                    $order['pay_status_text'] = $order['pay_status'] == 1 ? '已支付' : '未支付';
+                    $order['order_time_formatted'] = $order['order_time'] ? date('Y-m-d H:i:s', strtotime($order['order_time'])) : '';
+                    $order['pay_time_formatted'] = $order['pay_time'] ? date('Y-m-d H:i:s', strtotime($order['pay_time'])) : '';
+
+                    // 移除不需要的原始字段
+                    unset($order['pay_amount'], $order['pay_method'], $order['pay_status'],
+                        $order['order_time'], $order['pay_time']);
+                }
+            }
+
+            // 获取简单的统计信息
+            $stats = [
+                'total_orders' => $total,
+                'paid_orders' =>Db::table('ba_order')
+                    ->where('user_id', $userId)
+                    ->where('pay_status', 1)
+                    ->count(),
+                'unpaid_orders' =>Db::table('ba_order')
+                    ->where('user_id', $userId)
+                    ->where('pay_status', 0)
+                    ->count(),
+            ];
+
+//        } catch (\Throwable $e) {
+//            \think\facade\Log::error('获取购买记录失败: ' . $e->getMessage());
+//            $this->error('获取购买记录失败');
+//        }
+        $this->success('', [
+            'list' => $orders,
+            'total' => $total,
+            'page' => $page,
+            'page_size' => $pageSize,
+            'stats' => $stats
+        ]);
+    }
+
+    /**
+     * 简化支付方式文本
+     */
+    private function getSimplePayMethodText(string $method): string
+    {
+        $methodMap = [
+            'wechat' => '微信',
+            'alipay' => '支付宝',
+            'bank' => '银行卡'
+        ];
+
+        return $methodMap[$method] ?? '其他';
+    }
+
+    /**
+     * 获取购买统计信息
+     */
+    private function getPurchaseStats(int $userId, string $status = 'all', string $startDate = '', string $endDate = ''): array
+    {
+        try {
+            // 全部订单统计
+            $totalQuery = \think\facade\Db::table('ba_order')
+                ->where('user_id', $userId);
+
+            // 已支付订单统计
+            $paidQuery = \think\facade\Db::table('ba_order')
+                ->where('user_id', $userId)
+                ->where('pay_status', 1);
+
+            // 时间范围筛选
+            if (!empty($startDate)) {
+                $totalQuery->where('order_time', '>=', $startDate);
+                $paidQuery->where('order_time', '>=', $startDate);
+            }
+            if (!empty($endDate)) {
+                $totalQuery->where('order_time', '<=', $endDate . ' 23:59:59');
+                $paidQuery->where('order_time', '<=', $endDate . ' 23:59:59');
+            }
+
+            $totalCount = $totalQuery->count();
+            $paidCount = $paidQuery->count();
+            $unpaidCount = $totalCount - $paidCount;
+
+            // 支付总金额
+            $totalAmount = $paidQuery->sum('pay_amount');
+            $totalAmount = $totalAmount ?: 0;
+
+            // 本月统计
+            $monthStart = date('Y-m-01');
+            $monthPaidAmount = \think\facade\Db::table('ba_order')
+                ->where('user_id', $userId)
+                ->where('pay_status', 1)
+                ->where('order_time', '>=', $monthStart)
+                ->sum('pay_amount');
+
+            $monthPaidAmount = $monthPaidAmount ?: 0;
+
+            return [
+                'total_orders' => $totalCount,
+                'paid_orders' => $paidCount,
+                'unpaid_orders' => $unpaidCount,
+                'total_amount' => $totalAmount,
+                'month_amount' => $monthPaidAmount
+            ];
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('获取购买统计失败: ' . $e->getMessage());
+            return [
+                'total_orders' => 0,
+                'paid_orders' => 0,
+                'unpaid_orders' => 0,
+                'total_amount' => 0,
+                'month_amount' => 0
+            ];
+        }
+    }
+
+    /**
+     * 获取支付状态文本
+     */
+    private function getPayStatusText(int $status): string
+    {
+        $statusMap = [
+            0 => '待支付',
+            1 => '已支付',
+            2 => '支付失败'
+        ];
+
+        return $statusMap[$status] ?? '未知状态';
+    }
+
+    /**
+     * 获取订单状态文本
+     */
+    private function getOrderStatusText(int $status): string
+    {
+        $statusMap = [
+            0 => '待处理',
+            1 => '已确认',
+            2 => '已发货',
+            3 => '已完成',
+            4 => '已取消'
+        ];
+
+        return $statusMap[$status] ?? '未知状态';
+    }
+
+    /**
+     * 获取支付方式文本
+     */
+    private function getPayMethodText(string $method): string
+    {
+        $methodMap = [
+            'wechat' => '微信支付',
+            'alipay' => '支付宝',
+            'bank' => '银行卡',
+            'balance' => '余额支付'
+        ];
+
+        return $methodMap[$method] ?? '其他支付';
+    }
+
+    /**
+     * 获取订单详情(单个订单)
+     */
+    public function orderDetail(): void
+    {
+        if (!$this->auth->isLogin()) {
+            $this->error('请先登录');
+        }
+
+        $userId = $this->auth->id;
+        $orderId = $this->request->get('order_id', 0);
+        $orderNo = $this->request->get('order_no', '');
+
+        if (empty($orderId) && empty($orderNo)) {
+            $this->error('参数错误');
+        }
+        try {
+            // 查询订单
+            if ($orderId) {
+                $order = \think\facade\Db::table('ba_order')
+                    ->where('id', $orderId)
+                    ->where('user_id', $userId) // 只能查看自己的订单
+                    ->find();
+            } else {
+                $order = \think\facade\Db::table('ba_order')
+                    ->where('order_no', $orderNo)
+                    ->where('user_id', $userId) // 只能查看自己的订单
+                    ->find();
+            }
+
+            if (!$order) {
+                $this->error('订单不存在或无权查看');
+            }
+
+            // 获取订单商品项
+            $orderItems = \think\facade\Db::table('ba_order_item')
+                ->where('order_id', $order['id'])
+                ->select()
+                ->toArray();
+
+            // 格式化显示数据
+            $order['pay_status_text'] = $this->getPayStatusText($order['pay_status']);
+            $order['order_status_text'] = $this->getOrderStatusText($order['order_status']);
+            $order['pay_method_text'] = $this->getPayMethodText($order['pay_method']);
+
+            // 格式化时间
+            $order['order_time_formatted'] = $order['order_time'] ? date('Y-m-d H:i:s', strtotime($order['order_time'])) : '';
+            $order['pay_time_formatted'] = $order['pay_time'] ? date('Y-m-d H:i:s', strtotime($order['pay_time'])) : '';
+            $order['complete_time_formatted'] = $order['complete_time'] ? date('Y-m-d H:i:s', strtotime($order['complete_time'])) : '';
+            $order['create_time_formatted'] = $order['create_time'] ? date('Y-m-d H:i:s', strtotime($order['create_time'])) : '';
+
+
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('获取订单详情失败: ' . $e->getMessage());
+            $this->error('获取订单详情失败');
+        }
+        $this->success('', [
+            'order' => $order,
+            'items' => $orderItems
+        ]);
+    }
+
+
+
+
+
+    /**
+     * 订单支付成功回调(处理佣金)
+     */
+    public function paymentCallback(): void
+    {
+        if (!$this->request->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $orderId = $this->request->post('order_id', 0);
+        $orderNo = $this->request->post('order_no', '');
+
+        if (empty($orderId) && empty($orderNo)) {
+            $this->error('参数错误');
+        }
+
+        try {
+            // 查找订单
+            if (!empty($orderId)) {
+                $order = \think\facade\Db::table('ba_order')
+                    ->where('id', $orderId)
+                    ->find();
+            } else {
+                $order = \think\facade\Db::table('ba_order')
+                    ->where('order_no', $orderNo)
+                    ->find();
+            }
+
+            if (!$order) {
+                $this->error('订单不存在');
+            }
+
+            // 处理订单佣金
+            $result = $this->commissionService->processOrderCommission($order['id']);
+
+            if ($result) {
+                $this->success('佣金处理成功');
+            } else {
+                $this->error('佣金处理失败');
+            }
+        } catch (\Throwable $e) {
+            $this->error('处理失败:' . $e->getMessage());
+        }
+    }
+
+    /**
+     * 手动触发订单佣金处理(管理用)
+     */
+    public function processCommission(): void
+    {
+        if (!$this->auth->isLogin() || !$this->auth->isAdmin()) {
+            $this->error('权限不足');
+        }
+
+        $orderId = $this->request->post('order_id', 0);
+
+        if ($orderId <= 0) {
+            $this->error('订单ID错误');
+        }
+
+        $result = $this->commissionService->processOrderCommission($orderId);
+
+        if ($result) {
+            $this->success('处理成功');
+        } else {
+            $this->error('处理失败');
+        }
+    }
+}

+ 164 - 0
app/api/controller/invitation/Product.php

@@ -0,0 +1,164 @@
+<?php
+namespace app\api\controller\invitation;
+
+use app\common\controller\Frontend;
+use app\common\service\UserProductService;
+use think\facade\Db;
+
+class Product extends Frontend
+{
+    protected array $noNeedPermission = ['purchasedList', 'detail', 'myProducts'];
+    protected $userProductService;
+
+    public function initialize(): void
+    {
+        parent::initialize();
+        $this->userProductService = new UserProductService();
+    }
+
+    /**
+     * 获取我的产品列表(新版本)
+     */
+    public function myProducts(): void
+    {
+
+            $userId = $this->auth->id;
+            if (!$userId) {
+                $this->error('用户未登录');
+            }
+
+            // 使用新的服务类获取产品列表
+            $products = $this->userProductService->getUserProducts($userId);
+            // 获取用户所在企业信息(用于显示企业套餐管理入口)
+            $companyInfo = null;
+            $companyService = new \app\common\service\CompanyPackageService();
+            $companyData = $companyService->getUserCompany($userId);
+
+            if ($companyData) {
+                $companyInfo = [
+                    'company_id' => $companyData['id'],
+                    'company_name' => $companyData['name'],
+                    'is_creator' => $companyData['creator_id'] == $userId
+                ];
+            }
+        $this->success('', [
+            'products' => $products,
+            'company_info' => $companyInfo,
+            'total' => count($products)
+        ]);
+
+    }
+
+    /**
+     * 获取用户已购买的产品列表(简化版,兼容旧接口)
+     * @deprecated 建议使用myProducts接口
+     */
+    public function purchasedList(): void
+    {
+        // 调用新接口
+        $this->myProducts();
+    }
+
+    /**
+     * 获取产品详情(包含购买记录)
+     */
+    public function detail(): void
+    {
+        $userId = $this->auth->id;
+        $productId = $this->request->get('id', 0);
+
+        if ($productId <= 0) {
+            $this->error('产品ID错误');
+        }
+
+        try {
+            // 1. 获取产品基本信息
+            $product = Db::table('ba_product')
+                ->where('id', $productId)
+                ->where('status', 1)
+                ->find();
+
+            if (!$product) {
+                $this->error('产品不存在');
+            }
+
+            // 2. 获取用户对该产品的使用记录
+            $usageRecords = Db::name('user_product_usage')
+                ->alias('upu')
+                ->leftJoin('ba_order o', 'o.id = upu.order_id')
+                ->leftJoin('ba_package_price pp', 'pp.id = upu.package_price_id')
+                ->where('upu.user_id', $userId)
+                ->where('upu.product_id', $productId)
+                ->where('upu.status', 1) // 有效记录
+                ->field('upu.*, o.order_no, o.pay_time, o.order_status, o.pay_amount,
+                    pp.spec_name, pp.card_type')
+                ->order('upu.create_time', 'desc')
+                ->select()
+                ->toArray();
+
+            // 3. 获取企业套餐的分配记录(如果有)
+            $allocationRecords = [];
+            foreach ($usageRecords as &$record) {
+                if ($record['package_type'] == 2 && $record['allocation_id']) {
+                    $allocation = Db::name('company_package_allocation')
+                        ->alias('cpa')
+                        ->leftJoin('ba_user u', 'u.id = cpa.assigned_by_user_id')
+                        ->where('cpa.id', $record['allocation_id'])
+                        ->field('cpa.*, u.nickname as assigned_by_name')
+                        ->find();
+
+                    if ($allocation) {
+                        $record['allocation_info'] = $allocation;
+                    }
+                }
+
+                // 格式化显示信息
+                $record['display_info'] = $this->getDisplayInfo($record);
+                $record['card_type_text'] = $this->getCardTypeText($record['card_type']);
+                $record['package_type_text'] = $record['package_type'] == 1 ? '个人套餐' : '企业套餐';
+            }
+
+            $this->success('', [
+                'product' => $product,
+                'usage_records' => $usageRecords,
+                'total_records' => count($usageRecords)
+            ]);
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('获取产品详情失败: ' . $e->getMessage());
+            $this->error('获取产品详情失败');
+        }
+    }
+
+    /**
+     * 获取显示信息
+     */
+    private function getDisplayInfo($record)
+    {
+        if ($record['card_type'] >= 3) { // 日期卡
+            return $record['end_time'] ? '到期: ' . date('Y-m-d', strtotime($record['end_time'])) : '永久有效';
+        } elseif ($record['card_type'] == 1) { // 次卡
+            return '剩余次数: ' . $record['remaining_times'];
+        } elseif ($record['card_type'] == 2) { // 积分卡
+            return '剩余积分: ' . $record['remaining_points'];
+        }
+        return '';
+    }
+
+    /**
+     * 获取卡类型文本
+     */
+    private function getCardTypeText($type)
+    {
+        $map = [
+            1 => '次卡',
+            2 => '积分卡',
+            3 => '日卡',
+            4 => '周卡',
+            5 => '季卡',
+            6 => '月卡',
+            7 => '年卡'
+        ];
+        return $map[$type] ?? '其他';
+    }
+}

+ 161 - 0
app/api/controller/invitation/Withdrawal.php

@@ -0,0 +1,161 @@
+<?php
+
+namespace app\api\controller\invitation;
+
+use think\facade\Db;
+use app\common\controller\Frontend;
+use app\common\service\CommissionService;
+
+class Withdrawal extends Frontend
+{
+    protected $commissionService;
+
+    public function initialize(): void
+    {
+        parent::initialize();
+        $this->commissionService = new CommissionService();
+    }
+
+    /**
+     * 申请提现
+     */
+    public function apply(): void
+    {
+        if (!$this->request->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $userId = $this->auth->id;
+        $amount = $this->request->post('amount', 0);
+        $withdrawalMethod = $this->request->post('withdrawal_method', 'wechat');
+        $accountInfo = $this->request->post('account_info', '');
+        $remark = $this->request->post('remark', '');
+
+        if ($amount <= 0) {
+            $this->error('提现金额必须大于0');
+        }
+
+        if (!in_array($withdrawalMethod, ['wechat', 'alipay', 'bank'])) {
+            $this->error('提现方式错误');
+        }
+
+        try {
+            // 检查用户佣金是否足够
+            $commission = Db::name('ba_user_commission')
+                ->where('user_id', $userId)
+                ->find();
+
+            if (!$commission || $commission['current_commission'] < $amount) {
+                $this->error('佣金余额不足');
+            }
+
+            // 生成提现单号
+            $withdrawalNo = 'TX' . date('YmdHis') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
+
+            // 创建提现记录
+            $data = [
+                'withdrawal_no' => $withdrawalNo,
+                'user_id' => $userId,
+                'amount' => $amount,
+                'withdrawal_method' => $withdrawalMethod,
+                'account_info' => $accountInfo,
+                'current_balance' => $commission['current_commission'],
+                'balance_after' => $commission['current_commission'] - $amount,
+                'status' => 0, // 待审核
+                'remark' => $remark,
+                'create_time' => date('Y-m-d H:i:s'),
+                'update_time' => date('Y-m-d H:i:s')
+            ];
+
+            $result = Db::name('ba_withdrawal_record')->insert($data);
+
+            if ($result) {
+                $this->success('提现申请提交成功,等待审核', [
+                    'withdrawal_no' => $withdrawalNo
+                ]);
+            } else {
+                $this->error('提现申请失败');
+            }
+        } catch (\Throwable $e) {
+            $this->error('申请失败:' . $e->getMessage());
+        }
+    }
+
+    /**
+     * 获取提现记录
+     */
+    public function records(): void
+    {
+        $userId = $this->auth->id;
+        $page = $this->request->get('page', 1);
+        $pageSize = $this->request->get('pageSize', 10);
+
+        try {
+            $list = Db::name('ba_withdrawal_record')
+                ->where('user_id', $userId)
+                ->order('create_time', 'desc')
+                ->paginate([
+                    'page' => $page,
+                    'list_rows' => $pageSize
+                ]);
+
+            $this->success('', [
+                'list' => $list->items(),
+                'total' => $list->total(),
+                'page' => $list->currentPage(),
+                'page_size' => $list->listRows()
+            ]);
+        } catch (\Throwable $e) {
+            $this->error('获取提现记录失败');
+        }
+    }
+
+    /**
+     * 审核提现(管理员用)
+     */
+    public function audit(): void
+    {
+        if (!$this->auth->isLogin() || !$this->auth->isAdmin()) {
+            $this->error('权限不足');
+        }
+
+        $withdrawalId = $this->request->post('withdrawal_id', 0);
+        $status = $this->request->post('status', 0);
+        $auditRemark = $this->request->post('audit_remark', '');
+
+        if ($withdrawalId <= 0) {
+            $this->error('参数错误');
+        }
+
+        if (!in_array($status, [1, 2])) { // 1=通过,2=拒绝
+            $this->error('状态错误');
+        }
+
+        try {
+            if ($status == 1) {
+                // 审核通过,处理提现
+                $result = $this->commissionService->processWithdrawal($withdrawalId);
+            } else {
+                // 审核拒绝
+                $result = Db::name('ba_withdrawal_record')
+                    ->where('id', $withdrawalId)
+                    ->update([
+                        'status' => 2, // 审核拒绝
+                        'audit_time' => date('Y-m-d H:i:s'),
+                        'audit_remark' => $auditRemark,
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+
+                $result = $result !== false;
+            }
+
+            if ($result) {
+                $this->success('审核操作成功');
+            } else {
+                $this->error('审核操作失败');
+            }
+        } catch (\Throwable $e) {
+            $this->error('审核失败:' . $e->getMessage());
+        }
+    }
+}

+ 161 - 0
app/api/controller/product/OrderStatus.php

@@ -0,0 +1,161 @@
+<?php
+
+namespace app\api\controller\product;
+
+use app\common\controller\Frontend;
+use think\facade\Db;
+
+class OrderStatus extends Frontend
+{
+    protected array $noNeedPermission = ['check'];
+    protected array $noNeedLogin = ['check']; // 允许未登录用户查询(通过订单号)
+
+    /**
+     * 查询订单状态(前端轮询调用)
+     * GET /api/product/orderStatus/check?order_no=BUV202601161721166683
+     */
+    public function check()
+    {
+
+            // 1. 获取订单号
+            $orderNo = $this->request->get('order_no', '');
+
+            if (empty($orderNo)) {
+                return json([
+                    'code' => 400,
+                    'msg' => '订单号不能为空',
+                    'data' => null
+                ]);
+            }
+
+            // 2. 查询订单信息
+            $order = Db::name('order')
+                ->where('order_no', $orderNo)
+                ->field('id, order_no, user_id, pay_amount, pay_method, 
+                    pay_status, order_status, pay_time, create_time')
+                ->find();
+
+            if (!$order) {
+                return json([
+                    'code' => 404,
+                    'msg' => '订单不存在',
+                    'data' => null
+                ]);
+            }
+
+            // 3. 查询支付记录(如果有)
+            $paymentRecord = Db::name('payment_record')
+                ->where('order_no', $orderNo)
+                ->field('id, trade_no, status, pay_time as payment_pay_time')
+                ->find();
+
+            // 4. 查询订单商品信息
+            $orderItem = Db::name('order_item')
+                ->where('order_id', $order['id'])
+                ->field('item_name, spec_name, quantity, unit_price')
+                ->find();
+
+            // 5. 格式化返回数据
+            $result = [
+                'order_no' => $order['order_no'],
+                'pay_status' => $order['pay_status'], // 0=待支付, 1=已支付, 2=支付失败
+                'order_status' => $order['order_status'], // 0=待处理, 1=已确认, 2=已发货, 3=已完成, 4=已取消
+                'pay_method' => $order['pay_method'],
+                'pay_amount' => $order['pay_amount'],
+                'pay_time' => $order['pay_time'],
+                'trade_no' => $paymentRecord['trade_no'] ?? '',
+                'product_name' => $orderItem['item_name'] ?? '商品',
+                'product_spec' => $orderItem['spec_name'] ?? '',
+                'quantity' => $orderItem['quantity'] ?? 1,
+                'status_text' => $this->getStatusText($order['pay_status'], $order['order_status'])
+            ];
+
+
+
+        return json([
+            'code' => 1,
+            'msg' => 'success',
+            'data' => $result
+        ]);
+    }
+
+    /**
+     * 获取状态文本
+     */
+    private function getStatusText($payStatus, $orderStatus)
+    {
+        // 支付状态文本
+        $payStatusMap = [
+            0 => '待支付',
+            1 => '已支付',
+            2 => '支付失败'
+        ];
+
+        // 订单状态文本
+        $orderStatusMap = [
+            0 => '待处理',
+            1 => '已确认',
+            2 => '已发货',
+            3 => '已完成',
+            4 => '已取消'
+        ];
+
+        $text = $payStatusMap[$payStatus] ?? '未知支付状态';
+
+        // 如果已支付,显示订单状态
+        if ($payStatus == 1) {
+            $text .= '/' . ($orderStatusMap[$orderStatus] ?? '未知订单状态');
+        }
+
+        return $text;
+    }
+
+    /**
+     * 手动查询微信订单状态(用于调试)
+     */
+    public function queryWechat()
+    {
+        if (!$this->auth->isLogin() || !$this->auth->isAdmin()) {
+            $this->error('权限不足');
+        }
+
+        $orderNo = $this->request->get('order_no', '');
+
+        if (empty($orderNo)) {
+            $this->error('订单号不能为空');
+        }
+
+        try {
+            // 查询支付记录
+            $paymentRecord = Db::name('ba_payment_record')
+                ->where('order_no', $orderNo)
+                ->find();
+
+            if (!$paymentRecord) {
+                $this->error('支付记录不存在');
+            }
+
+            // 查询支付配置
+            $payConfig = Db::name('ba_pay_config')
+                ->where('id', $paymentRecord['pay_config_id'])
+                ->find();
+
+            if (!$payConfig) {
+                $this->error('支付配置不存在');
+            }
+
+            // 调用微信查询接口
+            $paymentService = \app\common\service\PaymentFactory::create(
+                $paymentRecord['pay_method'],
+                $payConfig['id']
+            );
+
+            $result = $paymentService->queryOrder($orderNo);
+
+            $this->success('查询成功', $result);
+
+        } catch (\Exception $e) {
+            $this->error('查询失败: ' . $e->getMessage());
+        }
+    }
+}

+ 309 - 0
app/api/controller/product/Package.php

@@ -0,0 +1,309 @@
+<?php
+
+namespace app\api\controller\product;
+
+use app\common\controller\Frontend;
+use think\facade\Db;
+
+class Package extends Frontend
+{
+    protected array $noNeedPermission = ['product_list','list', 'detail'];
+    protected array $noNeedLogin = ['*'];
+
+    /**
+     * 获取产品列表(简化版)
+     */
+    public function product_list(): void
+    {
+
+            // 查询所有上架的产品
+            $products = Db::table('ba_product')
+                ->where('status', 1)
+                ->where('stock_status', 1) // 库存充足
+                ->order('sort', 'desc')
+                ->order('id', 'desc')
+                ->field('id, name, image, description, package_id')
+                ->select()
+                ->toArray();
+
+//            // 查询每个产品的套餐信息
+//            foreach ($products as &$product) {
+//                // 获取产品对应的所有套餐规格
+//                $product['packages'] = $this->getProductPackages($product['id'], $product['package_id']);
+//
+//                // 获取主图
+//                $product['main_image'] = $this->getMainImage($product['image']);
+//            }
+
+            $this->success('', $products);
+
+    }
+
+    /**
+     * 获取产品对应的套餐
+     */
+    private function getProductPackages($productId, $packageId, $detail = false)
+    {
+        // 查询套餐规格
+        $query = Db::table('ba_package_price')
+            ->alias('pp')
+            ->leftJoin('ba_package p', 'p.id = pp.package_id')
+            ->where('p.status', 1)
+            ->where('pp.price', '>', 0);
+
+        // 如果产品有关联的package_id,使用它
+        if ($packageId) {
+            $query->where('pp.package_id', $packageId);
+        } else {
+            // 如果没有,可能需要其他关联逻辑
+            // 这里简化处理,返回空
+            return [];
+        }
+
+        // 如果是详情页,获取更多字段
+        if ($detail) {
+            $packages = $query->field('
+                pp.id,
+                pp.spec_name,
+                pp.price,
+                pp.type,
+                pp.card_type,
+                pp.unit_value,
+                pp.points_value,
+                pp.stock,
+                pp.sold_count,
+                pp.description,
+                p.name as package_name
+            ')->select()->toArray();
+        } else {
+            // 列表页只需要基本信息
+            $packages = $query->field('
+                pp.id,
+                pp.spec_name,
+                pp.price,
+                pp.type,
+                pp.card_type,
+                pp.unit_value,
+                pp.points_value,
+                pp.stock,
+                pp.sold_count
+            ')->select()->toArray();
+        }
+
+        // 格式化数据
+        $formattedPackages = [];
+        foreach ($packages as $package) {
+            $formattedPackage = [
+                'id' => $package['id'],
+                'name' => $package['spec_name'],
+                'type' => $package['type'],
+                'type_text' => $package['type'] == 1 ? '个人' : '企业',
+                'price' => floatval($package['price']),
+                'price_text' => '¥' . number_format($package['price'], 2),
+                'stock' => intval($package['stock']),
+                'stock_text' => $this->getStockText($package['stock']),
+                'sold_count' => intval($package['sold_count']),
+                'card_type' => $package['card_type'],
+                'card_info' => $this->getCardInfo($package['card_type'], $package['unit_value'], $package['points_value'])
+            ];
+
+            // 如果是详情页,添加更多信息
+            if ($detail) {
+                $formattedPackage['description'] = $package['description'] ?? '商品说明';
+                $formattedPackage['package_name'] = $package['package_name'] ?? '';
+            }
+
+            $formattedPackages[] = $formattedPackage;
+        }
+
+        return $formattedPackages;
+    }
+
+    /**
+     * 获取套餐列表
+     */
+    public function list(): void
+    {
+
+            // 获取参数(可根据产品筛选)
+            $productId = $this->request->get('product_id', 0);
+
+            // 构建查询
+            $query = Db::table('ba_package_price')
+                ->alias('pp')
+                ->leftJoin('ba_package p', 'p.id = pp.package_id')
+                ->where('p.status', 1)
+                ->where('pp.price', '>', 0)
+                ->order('pp.type', 'asc') // 个人在前,企业在后
+                ->order('pp.price', 'asc'); // 价格从低到高
+
+            // 如果有产品ID,筛选对应的套餐
+            if ($productId > 0) {
+                $product = Db::table('ba_product')
+                    ->where('id', $productId)
+                    ->field('package_id')
+                    ->find();
+                if ($product && $product['package_id']) {
+                    $query->where('pp.package_id', $product['package_id']);
+                }
+            }
+
+            // 获取套餐数据
+            $packages = $query->field('
+                pp.id,
+                pp.spec_name,
+                pp.image,
+                pp.price,
+                pp.type,
+                pp.card_type,
+                pp.unit_value,
+                pp.points_value,
+                pp.stock,
+                pp.sold_count,
+                pp.description
+            ')->select()->toArray();
+            // 格式化返回数据(严格按照截图格式)
+            $formattedPackages = [];
+            foreach ($packages as $package) {
+                $formattedPackage = [
+                    'id' => $package['id'],
+                    'name' => $package['spec_name'], // 套餐名称
+                    'type' => $package['type'], // 1个人,2企业
+                    'type_text' => $package['type'] == 1 ? '个人' : '企业',
+                    'price' => floatval($package['price']),
+                    'price_text' => '¥' . number_format($package['price'], 2),
+                    'description' => $package['description'] ?? '商品说明',
+                    'stock' => intval($package['stock']),
+                    'stock_text' => $this->getStockText($package['stock']),
+                    'sold_count' => intval($package['sold_count']),
+                    'card_type' => $package['card_type'],
+                    'image' => $package['image'],
+                    'card_info' => $this->getCardInfo($package['card_type'], $package['unit_value'], $package['points_value'])
+                ];
+
+                $formattedPackages[] = $formattedPackage;
+            }
+
+            $this->success('', $formattedPackages);
+
+    }
+
+    /**
+     * 获取套餐详情(用于购买页面)
+     */
+    public function detail(): void
+    {
+            $packageId = $this->request->get('id', 0);
+
+            if ($packageId <= 0) {
+                $this->error('套餐ID错误');
+            }
+
+            // 查询套餐详情
+            $package = Db::table('ba_package_price')
+                ->alias('pp')
+                ->leftJoin('ba_package p', 'p.id = pp.package_id')
+                ->where('pp.id', $packageId)
+                ->field('
+                    pp.id,
+                    pp.image,
+                    pp.spec_name,
+                    pp.price,
+                    pp.type,
+                    pp.card_type,
+                    pp.unit_value,
+                    pp.points_value,
+                    pp.stock,
+                    pp.sold_count,
+                    pp.description,
+                    p.name as package_name,
+                    p.description as package_description
+                ')
+                ->find();
+
+            if (!$package) {
+                $this->error('套餐不存在');
+            }
+
+            // 查询关联的产品信息
+            $product = Db::table('ba_product')
+                ->where('package_id', $package['package_id'] ?? 0)
+                ->field('id, name, image')
+                ->find();
+
+            // 格式化详情数据
+            $formattedDetail = [
+                'id' => $package['id'],
+                'name' => $package['spec_name'],
+                'type' => $package['type'],
+                'type_text' => $package['type'] == 1 ? '个人' : '企业',
+                'price' => floatval($package['price']),
+                'price_text' => '¥' . number_format($package['price'], 2),
+                'description' => $package['description'] ?? ($package['package_description'] ?? '商品说明'),
+                'stock' => intval($package['stock']),
+                'stock_text' => $this->getStockText($package['stock']),
+                'sold_count' => intval($package['sold_count']),
+                'card_type' => $package['card_type'],
+                'card_info' => $this->getCardInfo($package['card_type'], $package['unit_value'], $package['points_value']),
+                'package_name' => $package['package_name'],
+                'image' => $package['image'],
+                'product' => $product ? [
+                    'id' => $product['id'],
+                    'name' => $product['name'],
+                    'image' => $this->getMainImage($product['image'])
+                ] : null
+            ];
+
+            $this->success('', $formattedDetail);
+
+    }
+
+    /**
+     * 获取库存状态文本
+     */
+    private function getStockText($stock): string
+    {
+        if ($stock <= 0) {
+            return '售罄';
+        } elseif ($stock <= 10) {
+            return '即将售罄';
+        } else {
+            return '充足';
+        }
+    }
+
+    /**
+     * 获取卡类型信息
+     */
+    private function getCardInfo($cardType, $unitValue, $pointsValue): array
+    {
+        $cardTypes = [
+            1 => ['name' => '次卡', 'show_value' => $unitValue . '次'],
+            2 => ['name' => '积分卡', 'show_value' => $pointsValue . '积分'],
+            3 => ['name' => '日卡', 'show_value' => '1天'],
+            4 => ['name' => '周卡', 'show_value' => '7天'],
+            5 => ['name' => '季卡', 'show_value' => '90天'],
+            6 => ['name' => '月卡', 'show_value' => '30天'],
+            7 => ['name' => '年卡', 'show_value' => '365天']
+        ];
+
+        return $cardTypes[$cardType] ?? ['name' => '其他', 'show_value' => ''];
+    }
+
+    /**
+     * 获取主图
+     */
+    private function getMainImage($imageJson)
+    {
+        if (empty($imageJson)) {
+            return '';
+        }
+
+        $images = json_decode($imageJson, true);
+        if (is_array($images) && !empty($images)) {
+            return $images[0];
+        }
+
+        return '';
+    }
+}

+ 744 - 0
app/api/controller/product/Payment.php

@@ -0,0 +1,744 @@
+<?php
+
+namespace app\api\controller\product;
+
+use app\common\controller\Frontend;
+use app\common\service\PaymentFactory;
+use think\facade\Db;
+use think\facade\Log;
+
+class Payment extends Frontend
+{
+    protected array $noNeedPermission = ['createQrCode', 'queryStatus', 'wechatNotify', 'alipayNotify', 'paymentSuccess'];
+    protected array $noNeedLogin = ['*'];
+    /**
+     * 创建支付二维码
+     */
+    public function createQrCode()
+    {
+        $userId = $this->auth->id;
+        $orderId = $this->request->post('order_id', 0);
+        $payMethod = $this->request->post('pay_method', 'wechat');
+        if (!$userId || $orderId <= 0) {
+            $this->error('参数错误');
+        }
+
+        // 验证支付方式
+        if (!in_array($payMethod, ['wechat', 'alipay'])) {
+            $this->error('不支持的支付方式');
+        }
+
+        // 查询订单信息
+        $order = Db::table('ba_order')
+            ->where('id', $orderId)
+            ->where('user_id', $userId)
+            ->where('pay_status', 0) // 待支付
+            ->find();
+        if (!$order) {
+            $this->error('订单不存在或已支付');
+        }
+
+        // 更新订单支付方式
+        Db::table('ba_order')
+            ->where('id', $orderId)
+            ->update([
+                'pay_method' => $payMethod,
+                'update_time' => date('Y-m-d H:i:s')
+            ]);
+
+        // 检查是否已有支付记录
+        $paymentRecord = Db::table('ba_payment_record')
+            ->where('order_id', $orderId)
+            ->where('status', 0) // 待支付
+            ->find();
+        if ($paymentRecord) {
+            // 检查支付记录是否过期(15分钟)
+            $createTime = strtotime($paymentRecord['create_time']);
+            if (time() - $createTime < 900) { // 15分钟
+                $this->success('', [
+                    'order_no' => $order['order_no'],
+                    'pay_amount' => $order['pay_amount'],
+                    'qr_code_url' => $paymentRecord['qr_code_url'],
+                    'qr_code_content' => $paymentRecord['qr_code_content'],
+                    'payment_id' => $paymentRecord['id'],
+                ]);
+            }
+        }
+
+        // 查询订单商品信息
+        $orderItem = Db::table('ba_order_item')
+            ->where('order_id', $orderId)
+            ->find();
+
+        $subject = $orderItem['item_name'] ?? '商品购买';
+        $body = $orderItem['spec_name'] ?? '';
+
+        // 创建支付服务实例
+        $paymentService = PaymentFactory::create($payMethod);
+        // 生成支付二维码
+        $qrCodeResult = $paymentService->createQrCode(
+            $order['order_no'],
+            $order['pay_amount'],
+            $subject,
+            $body
+        );
+        // 创建支付记录
+        $payConfig = $paymentService->getPayConfig($payMethod); // 需要添加这个方法到BasePaymentService
+
+        $paymentData = [
+            'order_id' => $orderId,
+            'order_no' => $order['order_no'],
+            'pay_config_id' => $payConfig['id'],
+            'pay_method' => $payMethod,
+            'pay_amount' => $order['pay_amount'],
+            'qr_code_url' => $qrCodeResult['qr_code_url'],
+            'qr_code_content' => $qrCodeResult['qr_code_content'],
+            'status' => 0,
+            'create_time' => date('Y-m-d H:i:s'),
+            'update_time' => date('Y-m-d H:i:s'),
+        ];
+
+        if ($paymentRecord) {
+            // 更新现有记录
+            Db::table('ba_payment_record')
+                ->where('id', $paymentRecord['id'])
+                ->update($paymentData);
+            $paymentId = $paymentRecord['id'];
+        } else {
+            // 创建新记录
+            $paymentId = Db::table('ba_payment_record')->insertGetId($paymentData);
+        }
+
+        $this->success('创建成功', [
+            'order_no' => $order['order_no'],
+            'pay_amount' => $order['pay_amount'],
+            'qr_code_url' => $qrCodeResult['qr_code_url'],
+            'qr_code_content' => $qrCodeResult['qr_code_content'],
+            'payment_id' => $paymentId,
+            'expire_time' => 900, // 15分钟过期
+        ]);
+    }
+
+    /**
+     * 查询支付状态
+     */
+    public function queryStatus()
+    {
+        try {
+            $orderNo = $this->request->get('order_no', '');
+
+            if (empty($orderNo)) {
+                $this->error('订单号错误');
+            }
+
+            // 查询支付记录
+            $paymentRecord = Db::table('ba_payment_record')
+                ->where('order_no', $orderNo)
+                ->find();
+
+            if (!$paymentRecord) {
+                $this->error('支付记录不存在');
+            }
+
+            // 如果已支付,直接返回
+            if ($paymentRecord['status'] == 1) {
+                $this->success('', [
+                    'pay_status' => 1,
+                    'pay_time' => $paymentRecord['pay_time'],
+                    'trade_no' => $paymentRecord['trade_no'],
+                    'message' => '支付成功',
+                ]);
+            }
+
+            // 检查是否过期
+            $createTime = strtotime($paymentRecord['create_time']);
+            if (time() - $createTime > 900) { // 15分钟
+                // 更新为已关闭
+                Db::table('ba_payment_record')
+                    ->where('id', $paymentRecord['id'])
+                    ->update([
+                        'status' => 3,
+                        'update_time' => date('Y-m-d H:i:s'),
+                    ]);
+
+                $this->success('', [
+                    'pay_status' => 3,
+                    'message' => '支付已超时',
+                ]);
+            }
+
+            // 查询支付配置
+            $payConfig = Db::table('ba_pay_config')
+                ->where('id', $paymentRecord['pay_config_id'])
+                ->find();
+
+            if (!$payConfig) {
+                $this->error('支付配置不存在');
+            }
+
+            // 查询支付平台
+            $paymentService = PaymentFactory::create($paymentRecord['pay_method'], $payConfig['id']);
+            $queryResult = $paymentService->queryOrder($orderNo);
+
+            // 如果支付成功,处理支付成功逻辑
+            if ($queryResult['pay_status'] == 1) {
+                $this->processPaymentSuccess(
+                    $orderNo,
+                    $queryResult['trade_no'],
+                    $queryResult['pay_amount'],
+                    $queryResult['pay_time']
+                );
+            }
+
+            $this->success('', [
+                'pay_status' => $queryResult['pay_status'],
+                'pay_time' => $queryResult['pay_time'] ?? null,
+                'trade_no' => $queryResult['trade_no'] ?? null,
+                'message' => $this->getStatusMessage($queryResult['pay_status']),
+            ]);
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('查询支付状态失败: ' . $e->getMessage());
+            $this->error('查询支付状态失败: ' . $e->getMessage());
+        }
+    }
+
+    /**
+     * 微信支付回调
+     */
+    public function wechatNotify()
+    {
+        try {
+            // 获取原始输入
+            $input = file_get_contents('php://input');
+
+            if (empty($input)) {
+                throw new \Exception('回调数据为空');
+            }
+
+            Log::info('微信支付回调数据: ' . $input);
+
+            // 解析XML
+            $data = $this->xmlToArray($input);
+            $orderNo = $data['out_trade_no'] ?? '';
+
+            if (empty($orderNo)) {
+                throw new \Exception('未找到订单号');
+            }
+
+            // 使用事务和锁处理回调
+            Db::startTrans();
+            try {
+                // 使用行锁检查支付记录
+                $paymentRecord = Db::table('ba_payment_record')
+                    ->where('order_no', $orderNo)
+                    ->lock(true)
+                    ->find();
+
+                if (!$paymentRecord) {
+                    throw new \Exception('支付记录不存在: ' . $orderNo);
+                }
+
+                // 如果已处理,直接返回成功响应(幂等性)
+                if ($paymentRecord['status'] == 1) {
+                    Db::rollback();
+                    Log::info('微信支付回调幂等性检查:订单已处理,直接返回成功', [
+                        'order_no' => $orderNo,
+                        'payment_record_id' => $paymentRecord['id']
+                    ]);
+
+                    $response = [
+                        'return_code' => 'SUCCESS',
+                        'return_msg' => 'OK',
+                    ];
+                    echo $this->arrayToXml($response);
+                    return;
+                }
+
+                // 查询支付配置
+                $payConfig = Db::table('ba_pay_config')
+                    ->where('id', $paymentRecord['pay_config_id'])
+                    ->find();
+
+                if (!$payConfig) {
+                    throw new \Exception('支付配置不存在');
+                }
+
+                // 处理回调
+                $paymentService = PaymentFactory::create('wechat', $payConfig['id']);
+                $result = $paymentService->handleNotify($input);
+
+                // 处理支付成功
+                $this->processPaymentSuccess(
+                    $result['order_no'],
+                    $result['trade_no'],
+                    $result['pay_amount'],
+                    $result['pay_time']
+                );
+
+                Db::commit();
+
+                // 返回成功响应给微信
+                $response = [
+                    'return_code' => 'SUCCESS',
+                    'return_msg' => 'OK',
+                ];
+
+                echo $this->arrayToXml($response);
+
+            } catch (\Throwable $e) {
+                Db::rollback();
+                throw $e;
+            }
+
+        } catch (\Throwable $e) {
+            Log::error('微信支付回调处理失败: ' . $e->getMessage());
+
+            $response = [
+                'return_code' => 'FAIL',
+                'return_msg' => $e->getMessage(),
+            ];
+            echo $this->arrayToXml($response);
+        }
+    }
+
+    /**
+     * 支付宝支付回调
+     */
+    public function alipayNotify()
+    {
+        try {
+            $input = $_POST;
+
+            if (empty($input)) {
+                throw new \Exception('回调数据为空');
+            }
+
+            // 记录回调日志
+            \think\facade\Log::info('支付宝支付回调数据: ' . json_encode($input));
+
+            $orderNo = $input['out_trade_no'] ?? '';
+
+            if (empty($orderNo)) {
+                throw new \Exception('未找到订单号');
+            }
+
+            // 幂等性检查:检查订单是否已处理
+            $processedOrder = Db::table('ba_payment_record')
+                ->where('order_no', $orderNo)
+                ->where('status', 1) // 已支付
+                ->find();
+
+            if ($processedOrder) {
+                // 如果已处理,直接返回成功响应
+                \think\facade\Log::info('支付宝支付回调幂等性检查:订单已处理,直接返回成功', [
+                    'order_no' => $orderNo,
+                    'payment_record_id' => $processedOrder['id']
+                ]);
+
+                echo 'success';
+                return;
+            }
+
+            // 查询支付记录
+            $paymentRecord = Db::table('ba_payment_record')
+                ->where('order_no', $orderNo)
+                ->find();
+
+            if (!$paymentRecord) {
+                throw new \Exception('支付记录不存在: ' . $orderNo);
+            }
+
+            // 查询支付配置
+            $payConfig = Db::table('ba_pay_config')
+                ->where('id', $paymentRecord['pay_config_id'])
+                ->find();
+
+            if (!$payConfig) {
+                throw new \Exception('支付配置不存在');
+            }
+
+            // 处理回调
+            $paymentService = PaymentFactory::create('alipay', $payConfig['id']);
+            $result = $paymentService->handleNotify($input);
+
+            // 处理支付成功
+            $this->processPaymentSuccess(
+                $result['order_no'],
+                $result['trade_no'],
+                $result['pay_amount'],
+                $result['pay_time']
+            );
+
+            echo 'success';
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('支付宝支付回调处理失败: ' . $e->getMessage());
+            echo 'fail';
+        }
+    }
+
+    /**
+     * 处理支付成功
+     */
+    private function processPaymentSuccess($orderNo, $tradeNo, $payAmount, $payTime)
+    {
+        // 使用数据库事务和锁来确保幂等性
+        Db::startTrans();
+
+        try {
+            // 1. 使用行锁检查订单是否已处理
+            $order = Db::table('ba_order')
+                ->where('order_no', $orderNo)
+                ->lock(true) // 行锁
+                ->find();
+
+            if (!$order) {
+                Db::rollback();
+                Log::error('订单不存在: ' . $orderNo);
+                throw new \Exception('订单不存在');
+            }
+
+            // 2. 如果订单已支付,直接返回(幂等性)
+            if ($order['pay_status'] == 1) {
+                Db::rollback();
+                Log::info('订单已支付,跳过处理(幂等性)', ['order_no' => $orderNo]);
+                return;
+            }
+
+            // 3. 更新支付记录
+            Db::table('ba_payment_record')
+                ->where('order_no', $orderNo)
+                ->where('status', 0) // 只更新待支付的记录
+                ->update([
+                    'status' => 1,
+                    'trade_no' => $tradeNo,
+                    'pay_time' => $payTime,
+                    'notify_time' => date('Y-m-d H:i:s'),
+                    'update_time' => date('Y-m-d H:i:s'),
+                ]);
+
+            // 4. 更新订单状态
+            Db::table('ba_order')
+                ->where('order_no', $orderNo)
+                ->where('pay_status', 0) // 只更新未支付的订单
+                ->update([
+                    'pay_status' => 1,
+                    'pay_time' => $payTime,
+                    'order_status' => 1,
+                    'update_time' => date('Y-m-d H:i:s'),
+                ]);
+
+            // 5. 获取更新后的订单
+            $order = Db::table('ba_order')
+                ->where('order_no', $orderNo)
+                ->find();
+
+            // 6. 获取订单商品
+            $orderItems = Db::table('ba_order_item')
+                ->where('order_id', $order['id'])
+                ->select();
+
+            // 7. 创建用户产品服务实例
+            $userProductService = new \app\common\service\UserProductService();
+            $companyPackageService = new \app\common\service\CompanyPackageService();
+
+            foreach ($orderItems as $item) {
+                // 8. 使用事务和锁检查是否已存在使用记录
+                $existingUsage = Db::table('ba_user_product_usage')
+                    ->where('order_item_id', $item['id'])
+                    ->where('user_id', $order['user_id'])
+                    ->lock(true)
+                    ->find();
+
+                if ($existingUsage) {
+                    Log::info('订单商品已处理,跳过', [
+                        'order_item_id' => $item['id'],
+                        'order_id' => $order['id']
+                    ]);
+                    continue;
+                }
+
+                // 9. 获取套餐信息
+                $package = Db::table('ba_package_price')
+                    ->where('id', $item['package_price_id'])
+                    ->find();
+
+                if (!$package) {
+                    throw new \Exception('套餐不存在: ' . $item['package_price_id']);
+                }
+
+                Log::info('处理订单商品', [
+                    'order_item_id' => $item['id'],
+                    'product_id' => $item['product_id'],
+                    'package_id' => $item['package_price_id'],
+                    'package_type' => $package['type'],
+                    'card_type' => $package['card_type'],
+                    'quantity' => $item['quantity']
+                ]);
+
+                if ($package['type'] == 1) {
+                    // 个人套餐:立即创建使用记录
+                    $userProductService->createUserProductRecord($order['user_id'], $item, $package);
+                } else {
+                    // 企业套餐:创建席位记录
+                    // 首先验证企业购买
+                    $companyInfo = $companyPackageService->getUserCompany($order['user_id']);
+                    if (!$companyInfo) {
+                        throw new \Exception('企业套餐购买失败:用户不属于任何企业');
+                    }
+
+                    // 创建企业套餐席位记录
+                    $companyPackageService->processCompanyPackagePurchase(
+                        $order['id'],
+                        $companyInfo['id'],
+                        $package['id'],
+                        $item['quantity'],
+                        $package['card_type'],
+                        $package['unit_value'],
+                        $package['points_value']
+                    );
+
+                    // 企业套餐购买后,自动分配一个席位给购买者
+                    $companyPackageService->autoAllocateToBuyer(
+                        $order['user_id'],
+                        $companyInfo['id'],
+                        $package['id'],
+                        $order['id']
+                    );
+
+                    // 注意:这里不再重复创建用户产品记录!
+                    // 因为 autoAllocateToBuyer 中已经会创建
+                    // 只有次卡/积分卡需要在购买时也创建使用记录
+                    if ($package['card_type'] == 1 || $package['card_type'] == 2) {
+                        // 使用事务安全的方式创建
+                        $this->createUserProductRecordSafe($order['user_id'], $item, $package);
+                    }
+                }
+
+                // 10. 扣减库存
+                $this->deductStockAfterPayment($order['id']);
+            }
+
+            // 11. 处理分销佣金
+            if ($order['is_commission'] == 0 && $order['distributor_id'] && $order['commission_amount'] > 0) {
+                $this->processCommission($order);
+            }
+
+            Db::commit();
+            Log::info('支付成功处理完成', ['order_no' => $orderNo]);
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error('处理支付成功失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 安全创建用户产品记录(防止重复)
+     */
+    private function createUserProductRecordSafe($userId, $orderItem, $package)
+    {
+        try {
+            // 检查是否已存在
+            $existing = Db::table('ba_user_product_usage')
+                ->where('order_item_id', $orderItem['id'])
+                ->where('user_id', $userId)
+                ->find();
+
+            if ($existing) {
+                Log::info('用户产品记录已存在,跳过创建', [
+                    'order_item_id' => $orderItem['id'],
+                    'user_id' => $userId
+                ]);
+                return $existing['id'];
+            }
+
+            // 创建用户产品服务实例
+            $userProductService = new \app\common\service\UserProductService();
+            return $userProductService->createUserProductRecord($userId, $orderItem, $package);
+
+        } catch (\Throwable $e) {
+            Log::error('创建用户产品记录失败: ' . $e->getMessage());
+            // 不抛出异常,避免影响主流程
+            return false;
+        }
+    }
+
+    /**
+     * 支付成功后扣减库存
+     */
+    private function deductStockAfterPayment($orderId)
+    {
+        try {
+            // 查询订单商品
+            $orderItem = Db::table('ba_order_item')
+                ->where('order_id', $orderId)
+                ->find();
+
+            if (!$orderItem) {
+                return;
+            }
+
+            // 扣减套餐库存
+            if ($orderItem['package_price_id']) {
+                $result = Db::table('ba_package_price')
+                    ->where('id', $orderItem['package_price_id'])
+                    ->where('stock', '>=', $orderItem['quantity']) // 再次检查库存
+                    ->dec('stock', $orderItem['quantity'])
+                    ->inc('sold_count', $orderItem['quantity'])
+                    ->update();
+
+                if (!$result) {
+                    // 库存不足,需要处理异常(退款或补货)
+                    \think\facade\Log::error('支付成功后库存不足,订单ID:' . $orderId);
+                    // 这里可以触发退款逻辑或人工处理
+                }
+            }
+
+            // 如果有产品库存,也可以在这里扣减
+            if ($orderItem['product_id']) {
+                // 如果产品表有库存字段,在这里扣减
+                // Db::table('ba_product')...
+            }
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('扣减库存失败: ' . $e->getMessage());
+        }
+    }
+
+    /**
+     * 处理分销佣金
+     */
+    private function processCommission($order)
+    {
+        try {
+            // 调用佣金服务
+            $commissionService = new \app\common\service\CommissionService();
+
+            $result = $commissionService->updateDistributionRecord($order['id'], 2, $order['pay_time']);
+
+            if ($result) {
+                // 2. 更新分销人的佣金统计
+                $commissionService->updateUserCommission($order['distributor_id']);
+
+                // 3. 标记订单佣金已处理
+                Db::table('ba_order')
+                    ->where('id', $order['id'])
+                    ->update([
+                        'is_commission' => 1,
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+            }
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('处理分销佣金失败: ' . $e->getMessage());
+        }
+    }
+
+    /**
+     * 确认订单库存
+     */
+    private function confirmOrderStock($orderId)
+    {
+        try {
+            // 查询订单商品
+            $orderItems = Db::table('ba_order_item')
+                ->where('order_id', $orderId)
+                ->select();
+
+            foreach ($orderItems as $item) {
+                if ($item['package_price_id']) {
+                    // 可以在这里记录库存确认,或者更新相关状态
+                    // 由于创建订单时已经扣减库存,这里主要做确认处理
+                }
+            }
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('确认订单库存失败: ' . $e->getMessage());
+        }
+    }
+
+    /**
+     * 发送支付成功通知
+     */
+    private function sendPaymentSuccessNotify($order)
+    {
+        // 这里可以实现发送微信模板消息、短信、站内信等通知
+        // 根据业务需求实现
+
+        \think\facade\Log::info('支付成功通知: 订单号=' . $order['order_no'] . ', 用户ID=' . $order['user_id']);
+    }
+
+    /**
+     * 获取状态消息
+     */
+    private function getStatusMessage($status)
+    {
+        $messages = [
+            0 => '等待支付',
+            1 => '支付成功',
+            2 => '支付失败',
+            3 => '支付已关闭',
+        ];
+
+        return $messages[$status] ?? '未知状态';
+    }
+
+    /**
+     * XML转数组
+     */
+    private function xmlToArray($xml)
+    {
+        if (empty($xml)) {
+            return [];
+        }
+
+        try {
+            // 使用DOMDocument方式,兼容PHP 8.0+
+            $dom = new \DOMDocument();
+
+            if (version_compare(PHP_VERSION, '8.0.0', '>=')) {
+                // PHP 8.0+ 直接加载
+                $dom->loadXML($xml, LIBXML_NOENT | LIBXML_NOCDATA);
+            } else {
+                // PHP 8.0以下需要禁用实体加载器
+                $previous = libxml_disable_entity_loader(true);
+                $dom->loadXML($xml, LIBXML_NOENT | LIBXML_NOCDATA);
+                libxml_disable_entity_loader($previous);
+            }
+
+            // 简单的转换方法
+            $data = [];
+            foreach ($dom->documentElement->childNodes as $node) {
+                if ($node->nodeType == XML_ELEMENT_NODE) {
+                    $data[$node->nodeName] = $node->nodeValue;
+                }
+            }
+
+            return $data;
+
+        } catch (\Exception $e) {
+            \think\facade\Log::error('XML解析失败: ' . $e->getMessage());
+            return [];
+        }
+    }
+
+    /**
+     * 数组转XML
+     */
+    private function arrayToXml($arr)
+    {
+        $xml = '<xml>';
+        foreach ($arr as $key => $val) {
+            if (is_numeric($val)) {
+                $xml .= "<{$key}>{$val}</{$key}>";
+            } else {
+                $xml .= "<{$key}><![CDATA[{$val}]]></{$key}>";
+            }
+        }
+        $xml .= '</xml>';
+        return $xml;
+    }
+}

+ 290 - 0
app/api/controller/product/Purchase.php

@@ -0,0 +1,290 @@
+<?php
+
+namespace app\api\controller\product;
+
+use app\common\controller\Frontend;
+use app\common\service\CompanyPackageService;
+use think\facade\Db;
+
+class Purchase extends Frontend
+{
+    protected array $noNeedPermission = ['createOrder', 'orderDetail'];
+
+    /**
+     * 创建购买订单
+     */
+    public function createOrder(): void
+    {
+
+            if (!$this->request->isPost()) {
+                $this->error('非法请求');
+            }
+
+            $userId = $this->auth->id;
+            if (!$userId) {
+                $this->error('请先登录');
+            }
+            // 获取参数
+            $packageId = $this->request->post('package_id', 0);
+            $quantity = $this->request->post('quantity', 1);
+            $distributorId = $this->request->post('distributor_id', 0); // 分销人ID
+
+            if ($packageId <= 0) {
+                $this->error('请选择套餐');
+            }
+
+            if ($quantity < 1) {
+                $this->error('购买数量错误');
+            }
+
+            // 查询套餐详情
+            $package = Db::table('ba_package_price')
+                ->alias('pp')
+                ->leftJoin('ba_package p', 'p.id = pp.package_id')
+                ->where('pp.id', $packageId)
+                ->field('
+                    pp.*,
+                    p.name as package_name
+                ')
+                ->find();
+
+            if (!$package) {
+                $this->error('套餐不存在');
+            }
+
+            // 检查库存
+            if ($package['stock'] < $quantity) {
+                $this->error('库存不足');
+            }
+
+        // 在原有验证后,添加企业套餐验证
+        if ($package['type'] == 2) { // 企业套餐
+            $companyService = new CompanyPackageService();
+            $validationResult = $companyService->validateCompanyPurchase($userId, $packageId, $quantity);
+
+            if (!$validationResult['valid']) {
+                $this->error('企业套餐购买验证失败');
+            }
+
+            $companyId = $validationResult['company_id'];
+        }
+
+            // 查找关联的产品
+            $product = Db::table('ba_product')
+                ->where('package_id', $package['package_id'])
+                ->field('id, name')
+                ->find();
+            if (!$product) {
+                $this->error('关联产品不存在');
+            }
+
+            // 计算金额
+            $totalAmount = $package['price'] * $quantity;
+
+        // 计算分销佣金
+        $commissionService = new \app\common\service\CommissionService();
+        $commissionResult = $commissionService->calculateCommission($totalAmount, $distributorId);
+        $commissionAmount = $commissionResult['commission_amount'];
+        $commissionRate = $commissionResult['commission_rate'];
+        $hasCommission = $commissionResult['has_commission'];
+
+            // 生成订单号
+            $orderNo = 'BUY' . date('YmdHis') . mt_rand(1000, 9999);
+            Db::startTrans();
+
+            try {
+                // 1. 创建订单
+                $orderData = [
+                    'order_no' => $orderNo,
+                    'user_id' => $userId,
+                    'company_id' => $companyId,
+                    'total_amount' => $totalAmount,
+                    'discount_amount' => 0.00,
+                    'pay_amount' => $totalAmount,
+                    'pay_method' => null, // 支付方式待选择
+                    'pay_status' => 0, // 0=待支付
+                    'order_status' => 0, // 0=待处理
+                    'is_commission' => $hasCommission ? 0 : 0, // 0=未处理佣金,1=已处理
+                    'distributor_id' => $hasCommission ? $distributorId : null,
+                    'commission_amount' => $commissionAmount,
+                    'order_time' => date('Y-m-d H:i:s'),
+                    'create_time' => date('Y-m-d H:i:s'),
+                    'update_time' => date('Y-m-d H:i:s')
+                ];
+                $orderId = Db::table('ba_order')->insertGetId($orderData);
+
+                // 2. 创建订单商品
+                $itemData = [
+                    'order_id' => $orderId,
+                    'product_id' => $product['id'],
+                    'package_id' => $package['package_id'],
+                    'package_price_id' => $packageId,
+                    'company_id' => $companyId,
+                    'item_type' => 2, // 套餐
+                    'item_name' => $product['name'] . ' - ' . $package['spec_name'],
+                    'spec_name' => $package['spec_name'],
+                    'quantity' => $quantity,
+                    'unit_price' => $package['price'],
+                    'total_price' => $totalAmount,
+                    'create_time' => date('Y-m-d H:i:s')
+                ];
+
+                Db::table('ba_order_item')->insert($itemData);
+
+                // 3. 如果存在分销,创建分销记录
+                if ($hasCommission && $distributorId > 0) {
+                    $commissionService->createDistributionRecord([
+                        'order_id' => $orderId,
+                        'product_id' => $product['id'],
+                        'buyer_id' => $userId,
+                        'distributor_id' => $distributorId,
+                        'commission_rate' => $commissionRate,
+                        'commission_amount' => $commissionAmount
+                    ]);
+                }
+                // 如果是企业套餐,处理企业套餐相关逻辑
+//                if ($package['type'] == 2) {
+//                    // 在企业购买配额表中记录
+//                    $companyService->processCompanyPackagePurchase(
+//                        $orderId,
+//                        $companyId,
+//                        $packageId,
+//                        $quantity,
+//                        $package['card_type'],
+//                        $package['unit_value'],
+//                        $package['points_value']
+//                    );
+//
+//                    $orderData['company_id'] = $companyId;
+//                }
+
+                Db::commit();
+            } catch (\Throwable $e) {
+                Db::rollback();
+                throw $e;
+            }
+
+        $this->success('订单创建成功', [
+            'order_id' => $orderId,
+            'order_no' => $orderNo,
+            'product_name' => $product['name'],
+            'package_name' => $package['spec_name'],
+            'total_amount' => $totalAmount,
+            'quantity' => $quantity,
+            'has_commission' => $hasCommission,
+            'commission_amount' => $commissionAmount
+        ]);
+    }
+
+    /**
+     * 获取订单详情
+     */
+    public function orderDetail(): void
+    {
+        try {
+            $userId = $this->auth->id;
+            $orderId = $this->request->get('order_id', 0);
+            $orderNo = $this->request->get('order_no', '');
+
+            if (empty($orderId) && empty($orderNo)) {
+                $this->error('参数错误');
+            }
+
+            // 查询订单
+            if ($orderId) {
+                $order = Db::table('ba_order')
+                    ->where('id', $orderId)
+                    ->where('user_id', $userId)
+                    ->find();
+            } else {
+                $order = Db::table('ba_order')
+                    ->where('order_no', $orderNo)
+                    ->where('user_id', $userId)
+                    ->find();
+            }
+
+            if (!$order) {
+                $this->error('订单不存在');
+            }
+
+            // 查询订单商品
+            $orderItem = Db::table('ba_order_item')
+                ->where('order_id', $order['id'])
+                ->find();
+
+            if ($orderItem) {
+                // 查询套餐详情
+                $package = Db::table('ba_package_price')
+                    ->where('id', $orderItem['package_price_id'])
+                    ->find();
+
+                if ($package) {
+                    $orderItem['package_info'] = [
+                        'name' => $package['spec_name'],
+                        'price' => $package['price'],
+                        'type' => $package['type'],
+                        'type_text' => $package['type'] == 1 ? '个人' : '企业'
+                    ];
+                }
+
+                // 查询产品信息
+                $product = Db::table('ba_product')
+                    ->where('id', $orderItem['product_id'])
+                    ->field('id, name')
+                    ->find();
+
+                if ($product) {
+                    $orderItem['product_info'] = $product;
+                }
+
+                $order['item'] = $orderItem;
+            }
+
+            // 格式化状态
+            $order['pay_status_text'] = $this->getPayStatusText($order['pay_status']);
+            $order['order_status_text'] = $this->getOrderStatusText($order['order_status']);
+
+            // 格式化时间
+            $order['order_time_formatted'] = $order['order_time']
+                ? date('m-d H:i', strtotime($order['order_time']))
+                : '';
+            $order['pay_time_formatted'] = $order['pay_time']
+                ? date('m-d H:i', strtotime($order['pay_time']))
+                : '';
+
+            $this->success('', $order);
+
+        } catch (\Throwable $e) {
+            \think\facade\Log::error('获取订单详情失败: ' . $e->getMessage());
+            $this->error('获取订单详情失败');
+        }
+    }
+
+    /**
+     * 获取支付状态文本
+     */
+    private function getPayStatusText($status): string
+    {
+        $texts = [
+            0 => '待支付',
+            1 => '已支付',
+            2 => '支付失败'
+        ];
+        return $texts[$status] ?? '未知';
+    }
+
+    /**
+     * 获取订单状态文本
+     */
+    private function getOrderStatusText($status): string
+    {
+        $texts = [
+            0 => '待处理',
+            1 => '已确认',
+            2 => '已发货',
+            3 => '已完成',
+            4 => '已取消'
+        ];
+        return $texts[$status] ?? '未知';
+    }
+}

+ 1 - 1
app/api/validate/User.php

@@ -10,7 +10,7 @@ class User extends Validate
     protected $failException = true;
 
     protected $rule = [
-        'username'     => 'require|regex:^[a-zA-Z][a-zA-Z0-9_]{2,15}$|unique:user',
+        'username'     => 'require|regex:/^[\s\S]{2,15}$/|unique:user',
         'password'     => 'require|regex:^(?!.*[&<>"\'\n\r]).{6,32}$',
         'registerType' => 'require|in:email,mobile',
         'email'        => 'email|unique:user|requireIf:registerType,email',

+ 1 - 1
app/common/library/Auth.php

@@ -181,7 +181,7 @@ class Auth extends \ba\Auth
         $validate = Validate::rule([
             'email|' . __('Email')       => 'email|unique:user',
             'mobile|' . __('Mobile')     => 'mobile|unique:user',
-            'username|' . __('Username') => 'require|regex:^[a-zA-Z][a-zA-Z0-9_]{2,15}$|unique:user',
+            'username|' . __('Username') => 'require|regex:/^[\s\S]{2,15}$/|unique:user',
             'password|' . __('Password') => 'regex:^(?!.*[&<>"\'\n\r]).{6,32}$',
         ]);
         $params   = [

+ 314 - 0
app/common/service/AlipayPaymentService.php

@@ -0,0 +1,314 @@
+<?php
+
+namespace app\common\service;
+
+class AlipayPaymentService extends BasePaymentService
+{
+    // 支付宝API地址
+    protected $apiUrl = 'https://openapi.alipay.com/gateway.do';
+
+    // 配置参数
+    protected $config = [];
+
+    /**
+     * 初始化支付宝配置
+     */
+    protected function initConfig()
+    {
+        $this->config = [
+            'app_id' => $this->payConfig['app_id'],
+            'merchant_private_key' => $this->getPrivateKey(),
+            'alipay_public_key' => $this->getPublicKey(),
+            'notify_url' => $this->payConfig['notify_url'] ?: url('/api/invitation/payment/alipayNotify', [], true, true)->build(),
+            'charset' => 'UTF-8',
+            'sign_type' => 'RSA2',
+            'gateway_url' => $this->apiUrl,
+        ];
+
+        if (empty($this->config['app_id']) || empty($this->config['merchant_private_key'])) {
+            throw new \Exception('支付宝支付配置不完整');
+        }
+    }
+
+    /**
+     * 获取私钥
+     */
+    protected function getPrivateKey()
+    {
+        // 从文件路径读取或直接使用配置中的私钥
+        if (!empty($this->payConfig['private_key_path']) && file_exists($this->payConfig['private_key_path'])) {
+            return file_get_contents($this->payConfig['private_key_path']);
+        }
+
+        // 如果没有文件路径,可能私钥直接存储在配置中
+        return $this->payConfig['key'] ?? '';
+    }
+
+    /**
+     * 获取公钥
+     */
+    protected function getPublicKey()
+    {
+        if (!empty($this->payConfig['public_key_path']) && file_exists($this->payConfig['public_key_path'])) {
+            return file_get_contents($this->payConfig['public_key_path']);
+        }
+
+        return '';
+    }
+
+    /**
+     * 生成支付二维码
+     */
+    public function createQrCode($orderNo, $amount, $subject, $body = '')
+    {
+        try {
+            // 业务参数
+            $bizContent = [
+                'out_trade_no' => $orderNo,
+                'total_amount' => number_format($amount, 2, '.', ''),
+                'subject' => $subject,
+                'body' => $body ?: $subject,
+                'product_code' => 'FAST_INSTANT_TRADE_PAY',
+            ];
+
+            // 公共参数
+            $params = [
+                'app_id' => $this->config['app_id'],
+                'method' => 'alipay.trade.precreate',
+                'charset' => $this->config['charset'],
+                'sign_type' => $this->config['sign_type'],
+                'timestamp' => date('Y-m-d H:i:s'),
+                'version' => '1.0',
+                'notify_url' => $this->config['notify_url'],
+                'biz_content' => json_encode($bizContent, JSON_UNESCAPED_UNICODE),
+            ];
+
+            // 生成签名
+            $params['sign'] = $this->generateSign($params);
+
+            // 发送请求
+            $result = $this->execute($params);
+
+            $response = $result['alipay_trade_precreate_response'];
+
+            if ($response['code'] == '10000') {
+                return [
+                    'qr_code_url' => $response['qr_code'],
+                    'qr_code_content' => $response['qr_code'],
+                    'out_request_no' => $response['out_trade_no'],
+                ];
+            } else {
+                $errorMsg = $response['sub_msg'] ?? $response['msg'] ?? '支付宝下单失败';
+                throw new \Exception($errorMsg);
+            }
+
+        } catch (\Throwable $e) {
+            $this->log('生成支付宝支付二维码失败', [
+                'order_no' => $orderNo,
+                'error' => $e->getMessage()
+            ], 'error');
+            throw $e;
+        }
+    }
+
+    /**
+     * 处理支付回调
+     */
+    public function handleNotify($input)
+    {
+        try {
+            // 验证签名
+            if (!$this->verifySign($input)) {
+                throw new \Exception('签名验证失败');
+            }
+
+            // 检查交易状态
+            $tradeStatus = $input['trade_status'];
+            if (!in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
+                throw new \Exception('支付未成功: ' . $tradeStatus);
+            }
+
+            return [
+                'order_no' => $input['out_trade_no'],
+                'trade_no' => $input['trade_no'],
+                'pay_amount' => $input['total_amount'],
+                'pay_time' => $input['gmt_payment'] ?? date('Y-m-d H:i:s'),
+                'raw_data' => $input,
+            ];
+
+        } catch (\Throwable $e) {
+            $this->log('处理支付宝支付回调失败', [
+                'input' => $input,
+                'error' => $e->getMessage()
+            ], 'error');
+            throw $e;
+        }
+    }
+
+    /**
+     * 查询支付订单
+     */
+    public function queryOrder($orderNo)
+    {
+        try {
+            // 业务参数
+            $bizContent = [
+                'out_trade_no' => $orderNo,
+            ];
+
+            // 公共参数
+            $params = [
+                'app_id' => $this->config['app_id'],
+                'method' => 'alipay.trade.query',
+                'charset' => $this->config['charset'],
+                'sign_type' => $this->config['sign_type'],
+                'timestamp' => date('Y-m-d H:i:s'),
+                'version' => '1.0',
+                'biz_content' => json_encode($bizContent, JSON_UNESCAPED_UNICODE),
+            ];
+
+            $params['sign'] = $this->generateSign($params);
+
+            $result = $this->execute($params);
+            $response = $result['alipay_trade_query_response'];
+
+            if ($response['code'] == '10000') {
+                $tradeStatus = $response['trade_status'];
+                $payStatus = 0; // 待支付
+
+                if (in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
+                    $payStatus = 1; // 已支付
+                } elseif ($tradeStatus == 'TRADE_CLOSED') {
+                    $payStatus = 2; // 支付失败
+                }
+
+                return [
+                    'order_no' => $response['out_trade_no'],
+                    'trade_no' => $response['trade_no'],
+                    'pay_status' => $payStatus,
+                    'pay_amount' => $response['total_amount'],
+                    'pay_time' => $response['send_pay_date'] ?? null,
+                    'trade_status' => $tradeStatus,
+                ];
+            } elseif ($response['code'] == '40004') {
+                // 订单不存在,可能是未支付
+                return [
+                    'order_no' => $orderNo,
+                    'pay_status' => 0,
+                    'trade_status' => 'WAIT_BUYER_PAY',
+                ];
+            }
+
+            throw new \Exception('查询订单失败: ' . ($response['sub_msg'] ?? $response['msg'] ?? '未知错误'));
+
+        } catch (\Throwable $e) {
+            $this->log('查询支付宝支付订单失败', [
+                'order_no' => $orderNo,
+                'error' => $e->getMessage()
+            ], 'error');
+            throw $e;
+        }
+    }
+
+    /**
+     * 验证签名
+     */
+    protected function verifySign($data)
+    {
+        $sign = $data['sign'];
+        $signType = $data['sign_type'];
+
+        // 移除sign和sign_type参数
+        unset($data['sign'], $data['sign_type']);
+
+        // 参数排序
+        ksort($data);
+
+        // 拼接待签名字符串
+        $signContent = '';
+        foreach ($data as $key => $value) {
+            if ($value !== '' && $value !== null && $key != 'sign') {
+                $signContent .= $key . '=' . $value . '&';
+            }
+        }
+        $signContent = rtrim($signContent, '&');
+
+        // 验证签名
+        $publicKey = $this->config['alipay_public_key'];
+        $res = openssl_get_publickey($publicKey);
+        $result = openssl_verify($signContent, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
+        openssl_free_key($res);
+
+        return $result === 1;
+    }
+
+    /**
+     * 生成签名
+     */
+    protected function generateSign($data)
+    {
+        // 参数排序
+        ksort($data);
+
+        // 拼接待签名字符串
+        $signContent = '';
+        foreach ($data as $key => $value) {
+            if ($value !== '' && $value !== null && $key != 'sign') {
+                $signContent .= $key . '=' . $value . '&';
+            }
+        }
+        $signContent = rtrim($signContent, '&');
+
+        // 使用私钥生成签名
+        $privateKey = $this->config['merchant_private_key'];
+        $res = openssl_get_privatekey($privateKey);
+
+        if (!$res) {
+            throw new \Exception('私钥格式错误');
+        }
+
+        if ($this->config['sign_type'] == 'RSA2') {
+            openssl_sign($signContent, $sign, $res, OPENSSL_ALGO_SHA256);
+        } else {
+            openssl_sign($signContent, $sign, $res);
+        }
+
+        openssl_free_key($res);
+
+        return base64_encode($sign);
+    }
+
+    /**
+     * 执行请求
+     */
+    private function execute($params)
+    {
+        $url = $this->config['gateway_url'];
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+
+        $response = curl_exec($ch);
+
+        if (curl_errno($ch)) {
+            $error = curl_error($ch);
+            curl_close($ch);
+            throw new \Exception('CURL错误: ' . $error);
+        }
+
+        curl_close($ch);
+
+        $result = json_decode($response, true);
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            throw new \Exception('JSON解析失败: ' . json_last_error_msg());
+        }
+
+        return $result;
+    }
+}

+ 96 - 0
app/common/service/BasePaymentService.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace app\common\service;
+
+use think\facade\Db;
+use think\facade\Log;
+
+abstract class BasePaymentService
+{
+    // 支付配置
+    protected $config;
+
+    // 支付方式
+    protected $payMethod;
+
+    // 支付平台配置
+    protected $payConfig;
+
+    /**
+     * 初始化支付服务
+     */
+    public function __construct($payMethod, $payConfigId = 0)
+    {
+        $this->payMethod = $payMethod;
+
+        // 获取支付配置
+        $this->payConfig = $this->getPayConfig($payMethod, $payConfigId);
+        if (!$this->payConfig) {
+            throw new \Exception('支付配置不存在或已禁用');
+        }
+
+        // 初始化支付平台配置
+        $this->initConfig();
+    }
+
+    /**
+     * 获取支付配置
+     */
+    public function getPayConfig($payMethod, $payConfigId = 0)
+    {
+        $query = Db::table('ba_pay_config')
+            ->where('type', $payMethod == 'wechat' ? 1 : 2)
+            ->where('status', 1);
+
+        if ($payConfigId > 0) {
+            $query->where('id', $payConfigId);
+        }
+
+        return $query->order('id', 'desc')->find();
+    }
+
+    /**
+     * 初始化配置(子类实现)
+     */
+    abstract protected function initConfig();
+
+    /**
+     * 生成支付二维码
+     */
+    abstract public function createQrCode($orderNo, $amount, $subject, $body = '');
+
+    /**
+     * 处理支付回调
+     */
+    abstract public function handleNotify($input);
+
+    /**
+     * 查询支付订单
+     */
+    abstract public function queryOrder($orderNo);
+
+    /**
+     * 验证签名
+     */
+    abstract protected function verifySign($data, $sign);
+
+    /**
+     * 生成签名
+     */
+    abstract protected function generateSign($data);
+
+    /**
+     * 记录支付日志
+     */
+    protected function log($message, $data = [], $level = 'info')
+    {
+        $logData = [
+            'message' => $message,
+            'data' => $data,
+            'pay_method' => $this->payMethod,
+            'time' => date('Y-m-d H:i:s')
+        ];
+
+        Log::write("支付服务: " . json_encode($logData), $level);
+    }
+}

+ 504 - 0
app/common/service/CommissionService.php

@@ -0,0 +1,504 @@
+<?php
+
+namespace app\common\service;
+
+use think\facade\Db;
+use think\facade\Log;
+
+class CommissionService
+{
+
+
+    /**
+     * 获取分销设置
+     */
+    public function getDistributionSetting()
+    {
+        $setting = Db::table('ba_distribution_setting')
+            ->order('id', 'desc')
+            ->find();
+        // 如果没有设置,返回默认值
+        if (!$setting) {
+            $setting = [
+                'default_rate' => 0.10, // 10%
+                'min_withdrawal' => 10.00,
+                'max_withdrawal' => 10000.00,
+                'withdrawal_fee_rate' => 0.01, // 1%
+            ];
+        }
+
+        return $setting;
+    }
+
+    /**
+     * 计算佣金
+     */
+    public function calculateCommission($orderAmount, $distributorId = 0)
+    {
+        if ($distributorId <= 0) {
+            return [
+                'has_commission' => false,
+                'commission_rate' => 0,
+                'commission_amount' => 0
+            ];
+        }
+
+        $setting = $this->getDistributionSetting();
+        $commissionRate = ($setting['default_rate']/100) ?? 0.10;
+
+        // 计算佣金金额
+        $commissionAmount = bcmul($orderAmount, $commissionRate, 2);
+
+        return [
+            'has_commission' => true,
+            'commission_rate' => $commissionRate,
+            'commission_amount' => $commissionAmount,
+            'setting' => $setting
+        ];
+    }
+
+
+    /**
+     * 创建分销记录
+     */
+    public function createDistributionRecord($data)
+    {
+        $recordData = [
+            'order_id' => $data['order_id'] ?? 0,
+            'product_id' => $data['product_id'] ?? 0,
+            'buyer_id' => $data['buyer_id'] ?? 0,
+            'distributor_id' => $data['distributor_id'] ?? 0,
+            'commission_rate' => $data['commission_rate'] ?? 0,
+            'commission_amount' => $data['commission_amount'] ?? 0,
+            'status' => 1, // 1=待结算
+            'create_time' => date('Y-m-d H:i:s')
+        ];
+
+        return Db::table('ba_distribution_record')->insertGetId($recordData);
+    }
+
+    /**
+     * 更新分销记录状态
+     */
+    public function updateDistributionRecord($orderId, $status, $settleTime = null)
+    {
+        $updateData = [
+            'status' => $status,
+            'update_time' => date('Y-m-d H:i:s')
+        ];
+
+        if ($settleTime) {
+            $updateData['settle_time'] = $settleTime;
+        }
+
+        return Db::table('ba_distribution_record')
+            ->where('order_id', $orderId)
+            ->update($updateData);
+    }
+
+    /**
+     * 获取用户佣金统计
+     */
+    public function getUserCommissionSummary($userId)
+    {
+        $summary = Db::table('ba_distribution_record')
+            ->where('distributor_id', $userId)
+            ->field('
+                COUNT(*) as total_orders,
+                SUM(CASE WHEN status = 1 THEN commission_amount ELSE 0 END) as pending_amount,
+                SUM(CASE WHEN status = 2 THEN commission_amount ELSE 0 END) as settled_amount,
+                SUM(CASE WHEN status = 3 THEN commission_amount ELSE 0 END) as canceled_amount
+            ')
+            ->find();
+
+        return $summary ?: [
+            'total_orders' => 0,
+            'pending_amount' => 0,
+            'settled_amount' => 0,
+            'canceled_amount' => 0
+        ];
+    }
+
+    /**
+     * 更新用户佣金表
+     */
+    public function updateUserCommission($userId)
+    {
+        $summary = $this->getUserCommissionSummary($userId);
+
+        // 已结算的佣金可以作为当前佣金(可提现)
+        $currentCommission = $summary['settled_amount'] ?? 0;
+        $totalCommission = $summary['settled_amount'] + $summary['pending_amount'];
+
+        // 检查用户佣金表是否有记录
+        $userCommission = Db::table('ba_user_commission')
+            ->where('user_id', $userId)
+            ->find();
+
+        $data = [
+            'total_commission' => $totalCommission,
+            'current_commission' => $currentCommission,
+            'update_time' => date('Y-m-d H:i:s')
+        ];
+
+        if ($userCommission) {
+            // 更新
+            Db::table('ba_user_commission')
+                ->where('user_id', $userId)
+                ->update($data);
+        } else {
+            // 创建
+            $data['user_id'] = $userId;
+            $data['promotion_link'] = $this->generatePromotionLink($userId);
+            Db::table('ba_user_commission')->insert($data);
+        }
+    }
+
+
+
+    /**
+     * 处理订单佣金
+     */
+    public function processOrderCommission(int $orderId): bool
+    {
+        try {
+            // 获取订单信息
+            $order = Db::name('order')
+                ->where('id', $orderId)
+                ->where('pay_status', 1) // 已支付
+                ->where('order_status', 'IN', [1, 3]) // 已确认或已完成
+                ->where('is_commission', 0) // 未处理佣金
+                ->find();
+
+            if (!$order) {
+                Log::info("订单不满足佣金处理条件: {$orderId}");
+                return false;
+            }
+
+            // 如果有分销人,处理佣金
+            if (!empty($order['distributor_id']) && $order['commission_amount'] > 0) {
+                $distributorId = $order['distributor_id'];
+                $commissionAmount = $order['commission_amount'];
+
+                // 更新分销人佣金
+                $this->addCommission($distributorId, $commissionAmount, [
+                    'type' => 2, // 订单佣金
+                    'order_id' => $orderId,
+                    'from_user_id' => $order['user_id'],
+                    'remark' => '订单佣金:' . $order['order_no'] . ',金额:¥' . $commissionAmount
+                ]);
+
+                // 标记订单佣金已处理
+                Db::name('order')
+                    ->where('id', $orderId)
+                    ->update([
+                        'is_commission' => 1,
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+
+                Log::info("订单佣金处理成功: 订单ID={$orderId}, 分销人ID={$distributorId}, 佣金={$commissionAmount}");
+            }
+
+            return true;
+        } catch (\Throwable $e) {
+            Log::error("处理订单佣金失败: " . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 增加佣金
+     */
+    public function addCommission(int $userId, float $amount, array $extra = []): bool
+    {
+        try {
+            if ($amount <= 0) {
+                return false;
+            }
+
+            Db::startTrans();
+
+            // 获取当前佣金
+            $commission = Db::name('user_commission')
+                ->where('user_id', $userId)
+                ->lock(true)
+                ->find();
+
+            $balanceBefore = $commission ? $commission['current_commission'] : 0;
+            $balanceAfter = $balanceBefore + $amount;
+
+            if ($commission) {
+                // 更新现有记录
+                Db::name('user_commission')
+                    ->where('user_id', $userId)
+                    ->update([
+                        'total_commission' => Db::raw('total_commission + ' . $amount),
+                        'current_commission' => Db::raw('current_commission + ' . $amount),
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+            } else {
+                // 创建新记录
+                $promotionLink = $this->generatePromotionLink($userId);
+
+                Db::name('user_commission')->insert([
+                    'user_id' => $userId,
+                    'total_commission' => $amount,
+                    'current_commission' => $amount,
+                    'withdrawn_commission' => 0.00,
+                    'frozen_commission' => 0.00,
+                    'promotion_link' => $promotionLink,
+                    'invitation_count' => 0,
+                    'total_invitation' => 0,
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+            }
+
+            // 记录佣金流水
+            Db::name('commission_flow')->insert([
+                'user_id' => $userId,
+                'order_id' => $extra['order_id'] ?? null,
+                'from_user_id' => $extra['from_user_id'] ?? null,
+                'type' => $extra['type'] ?? 1,
+                'amount' => $amount,
+                'balance_before' => $balanceBefore,
+                'balance_after' => $balanceAfter,
+                'remark' => $extra['remark'] ?? ('佣金增加:¥' . $amount),
+                'create_time' => date('Y-m-d H:i:s')
+            ]);
+
+            Db::commit();
+            return true;
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error("增加佣金失败: " . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 处理提现
+     */
+    public function processWithdrawal(int $withdrawalId): bool
+    {
+        try {
+            $withdrawal = Db::name('withdrawal_record')
+                ->where('id', $withdrawalId)
+                ->where('status', 0) // 待审核
+                ->lock(true)
+                ->find();
+
+            if (!$withdrawal) {
+                Log::warning("提现记录不存在或状态不符: {$withdrawalId}");
+                return false;
+            }
+
+            $userId = $withdrawal['user_id'];
+            $amount = $withdrawal['amount'];
+
+            // 检查用户佣金是否足够
+            $commission = Db::name('user_commission')
+                ->where('user_id', $userId)
+                ->find();
+
+            if (!$commission || $commission['current_commission'] < $amount) {
+                Log::warning("用户佣金不足: 用户ID={$userId}, 需要={$amount}, 当前={$commission['current_commission']}");
+                return false;
+            }
+
+            Db::startTrans();
+
+            // 扣除佣金
+            $newCurrent = $commission['current_commission'] - $amount;
+            $newWithdrawn = $commission['withdrawn_commission'] + $amount;
+
+            Db::name('user_commission')
+                ->where('user_id', $userId)
+                ->update([
+                    'current_commission' => $newCurrent,
+                    'withdrawn_commission' => $newWithdrawn,
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+
+            // 记录佣金流水
+            Db::name('commission_flow')->insert([
+                'user_id' => $userId,
+                'type' => 3, // 提现扣除
+                'amount' => -$amount,
+                'balance_before' => $commission['current_commission'],
+                'balance_after' => $newCurrent,
+                'remark' => '提现扣除:' . $withdrawal['withdrawal_no'] . ',金额:¥' . $amount,
+                'create_time' => date('Y-m-d H:i:s')
+            ]);
+
+            // 更新提现记录状态为审核通过(实际打款需要另外处理)
+            Db::name('withdrawal_record')
+                ->where('id', $withdrawalId)
+                ->update([
+                    'status' => 1, // 审核通过
+                    'audit_time' => date('Y-m-d H:i:s'),
+                    'current_balance' => $commission['current_commission'],
+                    'balance_after' => $newCurrent,
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+
+            Db::commit();
+
+            Log::info("提现处理成功: 提现ID={$withdrawalId}, 用户ID={$userId}, 金额={$amount}");
+
+            return true;
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error("处理提现失败: " . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 获取用户佣金详情
+     */
+    public function getUserCommissionDetail(int $userId): array
+    {
+        try {
+            // 佣金信息
+            $commission = Db::name('user_commission')
+                ->where('user_id', $userId)
+                ->find();
+
+            if (!$commission) {
+                $commission = [
+                    'total_commission' => 0.00,
+                    'current_commission' => 0.00,
+                    'withdrawn_commission' => 0.00,
+                    'frozen_commission' => 0.00,
+                    'promotion_link' => $this->generatePromotionLink($userId),
+                    'invitation_count' => 0,
+                    'total_invitation' => 0
+                ];
+            }
+
+            // 最近佣金流水
+            $flows = Db::name('commission_flow')
+                ->where('user_id', $userId)
+                ->order('create_time', 'desc')
+                ->limit(50)
+                ->select()
+                ->toArray();
+
+            // 统计今日/本月佣金
+            $todayStart = date('Y-m-d 00:00:00');
+            $monthStart = date('Y-m-01 00:00:00');
+
+            $todayIncome = Db::name('commission_flow')
+                ->where('user_id', $userId)
+                ->where('amount', '>', 0)
+                ->where('create_time', '>=', $todayStart)
+                ->sum('amount');
+
+            $monthIncome = Db::name('commission_flow')
+                ->where('user_id', $userId)
+                ->where('amount', '>', 0)
+                ->where('create_time', '>=', $monthStart)
+                ->sum('amount');
+
+            return [
+                'commission' => $commission,
+                'flows' => $flows,
+                'stats' => [
+                    'today_income' => $todayIncome ?: 0,
+                    'month_income' => $monthIncome ?: 0
+                ]
+            ];
+        } catch (\Throwable $e) {
+            Log::error("获取用户佣金详情失败: " . $e->getMessage());
+            return [
+                'commission' => [
+                    'total_commission' => 0.00,
+                    'current_commission' => 0.00,
+                    'withdrawn_commission' => 0.00,
+                    'frozen_commission' => 0.00,
+                    'promotion_link' => '',
+                    'invitation_count' => 0,
+                    'total_invitation' => 0
+                ],
+                'flows' => [],
+                'stats' => [
+                    'today_income' => 0,
+                    'month_income' => 0
+                ]
+            ];
+        }
+    }
+
+
+    /**
+     * 生成唯一邀请码
+     */
+    public function generateInvitationCode(): string
+    {
+        $maxAttempts = 10;
+        $attempts = 0;
+
+        while ($attempts < $maxAttempts) {
+            // 方法1:使用数字+字母组合
+            $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 去掉容易混淆的字符
+            $code = '';
+            for ($i = 0; $i < 6; $i++) {
+                $code .= $chars[mt_rand(0, strlen($chars) - 1)];
+            }
+
+            // 方法2:或者使用更短的邀请码
+            // $code = strtoupper(substr(md5(uniqid()), 0, 6));
+
+            // 检查邀请码是否已存在
+            $exists = Db::table('ba_user')
+                ->where('invitation_code', $code)
+                ->count();
+
+            if (!$exists) {
+                return $code;
+            }
+
+            $attempts++;
+
+            // 如果冲突,尝试不同的算法
+            if ($attempts >= 5) {
+                $code = 'INV' . mt_rand(1000, 9999);
+                $exists = Db::table('ba_user')
+                    ->where('invitation_code', $code)
+                    ->count();
+
+                if (!$exists) {
+                    return $code;
+                }
+            }
+        }
+
+        // 如果还是冲突,使用时间戳+随机数
+        return 'INV' . time() . mt_rand(10, 99);
+    }
+
+    /**
+     * 生成推广链接
+     */
+    private function generatePromotionLink(int $userId): string
+    {
+        // 获取用户的邀请码
+        $user = Db::table('ba_user')
+            ->where('id', $userId)
+            ->field('invitation_code')
+            ->find();
+
+        $invitationCode = $user['invitation_code'] ?? '';
+
+        if (empty($invitationCode)) {
+            // 如果用户还没有邀请码,先生成一个
+            $invitationCode = $this->generateInvitationCode();
+            Db::table('ba_user')
+                ->where('id', $userId)
+                ->update(['invitation_code' => $invitationCode]);
+        }
+
+        $domain = request()->domain();
+        return $domain . '/#/user/register?invite=' . $invitationCode;
+    }
+}

+ 1414 - 0
app/common/service/CompanyPackageService.php

@@ -0,0 +1,1414 @@
+<?php
+namespace app\common\service;
+
+use think\facade\Db;
+use think\facade\Log;
+
+class CompanyPackageService
+{
+    /**
+     * 验证企业套餐购买资格
+     */
+    public function validateCompanyPurchase($userId, $packagePriceId, $quantity)
+    {
+        try {
+            // 1. 获取套餐信息
+            $package = Db::table('ba_package_price')
+                ->alias('pp')
+                ->leftJoin('ba_package p', 'p.id = pp.package_id')
+                ->where('pp.id', $packagePriceId)
+                ->field('pp.*, p.name as package_name')
+                ->find();
+
+            if (!$package) {
+                throw new \Exception('套餐不存在');
+            }
+
+            // 2. 如果是企业套餐,检查用户是否属于企业
+            if ($package['type'] == 2) { // 企业套餐
+                $companyInfo = $this->getUserCompany($userId);
+                if (!$companyInfo) {
+                    throw new \Exception('只有企业成员才能购买企业套餐');
+                }
+
+                // 3. 检查日期卡的最低购买数量
+                if ($package['is_date_card'] == 1 && $quantity < $package['min_quantity']) {
+                    throw new \Exception('企业日期卡至少需要购买' . $package['min_quantity'] . '份');
+                }
+
+                return [
+                    'valid' => true,
+                    'company_id' => $companyInfo['id'],
+                    'company_name' => $companyInfo['name'],
+                    'min_quantity' => $package['min_quantity']
+                ];
+            }
+
+            return ['valid' => true, 'company_id' => null];
+
+        } catch (\Throwable $e) {
+            Log::error('验证企业套餐购买资格失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取用户所在企业
+     */
+    public function getUserCompany($userId)
+    {
+        // 检查用户是否是企业创建人或成员
+        $company = Db::table('ba_company')
+            ->where('creator_id', $userId)
+            ->where('status', 1)
+            ->field('id, name,creator_id')
+            ->find();
+        if ($company) {
+            return $company;
+        }
+
+        // 检查是否是成员
+        $member = Db::table('ba_company_member')
+            ->alias('cm')
+            ->leftJoin('ba_company c', 'c.id = cm.company_id')
+            ->where('cm.user_id', $userId)
+            ->where('cm.status', 1)
+            ->where('c.status', 1)
+            ->field('c.id, c.name')
+            ->find();
+
+        return $member;
+    }
+
+    /**
+     * 处理企业套餐购买(创建席位记录)
+     */
+    public function processCompanyPackagePurchase($orderId, $companyId, $packagePriceId, $quantity, $cardType, $unitValue, $pointsValue)
+    {
+        Db::startTrans();
+        try {
+            // 1. 获取或创建企业套餐席位记录
+            $seatsRecord = Db::table('ba_company_package_seats')
+                ->where('company_id', $companyId)
+                ->where('package_price_id', $packagePriceId)
+                ->find();
+
+            if ($seatsRecord) {
+                // 更新现有记录
+                $updateData = [
+                    'total_seats' => Db::raw('total_seats + ' . $quantity),
+                    'available_seats' => Db::raw('available_seats + ' . $quantity),
+                    'is_shared' => $cardType < 3 ? 1 : 0, // 次卡/积分卡为共享资源
+                    'update_time' => date('Y-m-d H:i:s')
+                ];
+
+                // 如果是次卡,累加次数
+                if ($cardType == 1) {
+                    $updateData['total_times'] = Db::raw('total_times + ' . ($unitValue * $quantity));
+                    $updateData['remaining_times'] = Db::raw('remaining_times + ' . ($unitValue * $quantity));
+                }
+
+                // 如果是积分卡,累加积分
+                if ($cardType == 2) {
+                    $updateData['total_points'] = Db::raw('total_points + ' . ($pointsValue * $quantity));
+                    $updateData['remaining_points'] = Db::raw('remaining_points + ' . ($pointsValue * $quantity));
+                }
+
+                Db::table('ba_company_package_seats')
+                    ->where('id', $seatsRecord['id'])
+                    ->update($updateData);
+                $seatsId = $seatsRecord['id'];
+            } else {
+                // 创建新记录
+                $seatsData = [
+                    'company_id' => $companyId,
+                    'package_price_id' => $packagePriceId,
+                    'product_id' => $this->getProductIdByPackage($packagePriceId),
+                    'total_seats' => $quantity,
+                    'assigned_seats' => 0,
+                    'available_seats' => $quantity,
+                    'is_shared' => $cardType < 3 ? 1 : 0, // 次卡/积分卡为共享资源
+                    'card_type' => $cardType,
+                    'total_times' => $cardType == 1 ? $unitValue * $quantity : 0,
+                    'remaining_times' => $cardType == 1 ? $unitValue * $quantity : 0,
+                    'total_points' => $cardType == 2 ? $pointsValue * $quantity : 0,
+                    'remaining_points' => $cardType == 2 ? $pointsValue * $quantity : 0,
+                    'status' => 1,
+                    'create_time' => date('Y-m-d H:i:s'),
+                    'update_time' => date('Y-m-d H:i:s')
+                ];
+
+                $seatsId = Db::table('ba_company_package_seats')->insertGetId($seatsData);
+            }
+
+            // 2. 获取购买者ID
+            $buyerId = $this->getOrderBuyerId($orderId);
+
+            // 3. 查询订单商品
+            $orderItems = Db::table('ba_order_item')
+                ->where('order_id', $orderId)
+                ->where('package_price_id', $packagePriceId)
+                ->select();
+
+            foreach ($orderItems as $item) {
+                if ($cardType >= 3) {
+                    // 日期卡:为每个购买的数量创建独立的分配记录
+                    for ($i = 0; $i < $quantity; $i++) {
+                        $allocationData = [
+                            'order_item_id' => $item['id'],
+                            'order_id' => $orderId,
+                            'package_price_id' => $packagePriceId,
+                            'product_id' => $item['product_id'],
+                            'buyer_id' => $buyerId,
+                            'company_id' => $companyId,
+                            'assigned_to_user_id' => null, // 初始不分配
+                            'assigned_by_user_id' => $buyerId,
+                            'record_type' => 2, // 购买记录
+                            'is_general' => 0,
+                            'seat_index' => $i + 1, // 席位索引
+                            'allocation_status' => 0, // 未分配
+                            'allocation_time' => null,
+                            'start_time' => null,
+                            'end_time' => null,
+                            'expire_time' => $this->calculateEndTime($cardType, date('Y-m-d H:i:s')), // 冗余字段
+                            'remaining_times' => 0, // 日期卡没有次数
+                            'remaining_points' => 0, // 日期卡没有积分
+                            'is_used' => 0,
+                            'create_time' => date('Y-m-d H:i:s'),
+                            'update_time' => date('Y-m-d H:i:s')
+                        ];
+
+                        $allocationId = Db::table('ba_company_package_allocation')->insertGetId($allocationData);
+                    }
+                } else {
+                    // 次卡和积分卡:
+                    // 1. 创建通用记录(用于管理总资源)
+                    $generalAllocationData = [
+                        'order_item_id' => $item['id'],
+                        'order_id' => $orderId,
+                        'package_price_id' => $packagePriceId,
+                        'product_id' => $item['product_id'],
+                        'buyer_id' => $buyerId,
+                        'company_id' => $companyId,
+                        'assigned_to_user_id' => null, // 通用记录不分配
+                        'assigned_by_user_id' => $buyerId,
+                        'record_type' => 0, // 通用记录
+                        'is_general' => 1, // 是通用记录
+                        'seat_index' => 0,
+                        'allocation_status' => 0, // 状态:通用记录
+                        'allocation_time' => null,
+                        'start_time' => null,
+                        'end_time' => null,
+                        'expire_time' => null,
+                        'remaining_times' => $cardType == 1 ? $unitValue * $quantity : 0,
+                        'remaining_points' => $cardType == 2 ? $pointsValue * $quantity : 0,
+                        'is_used' => 0,
+                        'create_time' => date('Y-m-d H:i:s'),
+                        'update_time' => date('Y-m-d H:i:s')
+                    ];
+
+                    $generalAllocationId = Db::table('ba_company_package_allocation')->insertGetId($generalAllocationData);
+
+                    // 2. 为购买者创建独立的分配记录
+                    $buyerAllocationData = [
+                        'order_item_id' => $item['id'],
+                        'order_id' => $orderId,
+                        'package_price_id' => $packagePriceId,
+                        'product_id' => $item['product_id'],
+                        'buyer_id' => $buyerId,
+                        'company_id' => $companyId,
+                        'assigned_to_user_id' => $buyerId,
+                        'assigned_by_user_id' => $buyerId,
+                        'record_type' => 1, // 用户分配记录
+                        'is_general' => 0,
+                        'seat_index' => 0,
+                        'parent_id' => $generalAllocationId, // 关联通用记录
+                        'allocation_status' => 1, // 已分配
+                        'allocation_time' => date('Y-m-d H:i:s'),
+                        'start_time' => date('Y-m-d H:i:s'),
+                        'end_time' => null,
+                        'expire_time' => null,
+                        'remaining_times' => $cardType == 1 ? $unitValue : 0,
+                        'remaining_points' => $cardType == 2 ? $pointsValue : 0,
+                        'is_used' => 0,
+                        'create_time' => date('Y-m-d H:i:s'),
+                        'update_time' => date('Y-m-d H:i:s')
+                    ];
+
+                    $buyerAllocationId = Db::table('ba_company_package_allocation')->insertGetId($buyerAllocationData);
+
+                    // 3. 将购买者添加到授权列表
+                    $seats = Db::table('ba_company_package_seats')
+                        ->where('id', $seatsId)
+                        ->find();
+
+                    $authorizedUsers = json_decode($seats['authorized_users'] ?? '[]', true);
+
+                    if (!in_array($buyerId, $authorizedUsers)) {
+                        $authorizedUsers[] = $buyerId;
+
+                        Db::table('ba_company_package_seats')
+                            ->where('id', $seatsId)
+                            ->update([
+                                'authorized_users' => json_encode($authorizedUsers),
+                                'assigned_member_count' => Db::raw('assigned_member_count + 1')
+                            ]);
+                    }
+                }
+
+                // 更新订单商品的分配记录ID(对于日期卡,记录第一个分配记录的ID)
+                if ($cardType >= 3) {
+                    // 获取第一个分配记录ID
+                    $firstAllocation = Db::table('ba_company_package_allocation')
+                        ->where('order_item_id', $item['id'])
+                        ->where('record_type', 2)
+                        ->order('seat_index', 'asc')
+                        ->find();
+
+                    if ($firstAllocation) {
+                        Db::table('ba_order_item')
+                            ->where('id', $item['id'])
+                            ->update(['allocation_id' => $firstAllocation['id']]);
+                    }
+                } else {
+                    // 次卡/积分卡使用购买者分配记录ID
+                    Db::table('ba_order_item')
+                        ->where('id', $item['id'])
+                        ->update(['allocation_id' => $buyerAllocationId]);
+                }
+            }
+
+            Db::commit();
+            return true;
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error('处理企业套餐购买失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+
+    /**
+     * 获取可分配的企业套餐列表(区分类型)
+     */
+    public function getAvailableCompanyPackages($companyId, $operatorId)
+    {
+        try {
+            // 获取用户角色(创建者或成员)
+            $isCreator = Db::table('ba_company')
+                    ->where('id', $companyId)
+                    ->where('creator_id', $operatorId)
+                    ->count() > 0;
+
+            // 获取企业所有套餐席位
+            $seats = Db::table('ba_company_package_seats')
+                ->alias('cs')
+                ->leftJoin('ba_package_price pp', 'pp.id = cs.package_price_id')
+                ->leftJoin('ba_product p', 'p.id = cs.product_id')
+                ->where('cs.company_id', $companyId)
+                ->where('cs.available_seats', '>', 0)
+                ->where('cs.status', 1)
+                ->field('cs.*, pp.spec_name, pp.card_type, pp.type as package_type, p.name as product_name, p.type as product_type, p.link,pp.image')
+                ->select();
+
+            // 获取企业所有成员(包括创建者)
+            $allMembers = $this->getAllCompanyMembers($companyId);
+            // 处理数据
+            $result = [];
+            foreach ($seats as $seat) {
+                // 获取该套餐已分配的成员
+                $allocatedMembers = $this->getAllocatedMembersForPackage($companyId, $seat['package_price_id']);
+
+                // 获取该套餐已分配的成员ID列表
+                $allocatedMemberIds = array_column($allocatedMembers, 'user_id');
+
+                // 筛选未分配的成员
+                $unallocatedMembers = [];
+                foreach ($allMembers as $member) {
+                    if (!in_array($member['user_id'], $allocatedMemberIds)) {
+                        $unallocatedMembers[] = $member;
+                    }
+                }
+
+                // 组织返回数据
+                $seatItem = [
+                    'seats_id' => $seat['id'],
+                    'product_id' => $seat['product_id'],
+                    'product_name' => $seat['product_name'],
+                    'product_type' => $seat['product_type'], // 1:网页,2:软件
+                    'product_link' => $seat['link'],
+                    'product_image' => $seat['image'],
+                    'package_price_id' => $seat['package_price_id'],
+                    'spec_name' => $seat['spec_name'],
+                    'card_type' => $seat['card_type'],
+                    'card_type_text' => $this->getCardTypeText($seat['card_type']),
+                    'package_type' => $seat['package_type'], // 1个人,2企业
+                    'total_seats' => $seat['total_seats'],
+                    'assigned_seats' => $seat['assigned_seats'],
+                    'available_seats' => $seat['available_seats'],
+                    // 次卡和积分卡相关
+                    'total_times' => $seat['total_times'],
+                    'remaining_times' => $seat['remaining_times'],
+                    'total_points' => $seat['total_points'],
+                    'remaining_points' => $seat['remaining_points'],
+                    'end_time' => $seat['end_time'],
+                    // 已分配成员信息
+                    'allocated_members' => $allocatedMembers,
+                    // 未分配成员信息
+                    'unallocated_members' => $unallocatedMembers,
+                    // 统计信息
+                    'allocated_count' => count($allocatedMembers),
+                    'unallocated_count' => count($unallocatedMembers),
+                    'total_members' => count($allMembers)
+                ];
+
+                $result[] = $seatItem;
+            }
+
+            return [
+                'packages' => $result,
+                'is_creator' => $isCreator,
+                'total_members' => count($allMembers)
+            ];
+
+        } catch (\Throwable $e) {
+            Log::error('获取可分配企业套餐失败: ' . $e->getMessage());
+            return [
+                'packages' => [],
+                'is_creator' => false,
+                'total_members' => 0
+            ];
+        }
+    }
+
+    /**
+     * 获取企业所有成员(包括创建者)
+     */
+    private function getAllCompanyMembers($companyId)
+    {
+        $members = [];
+
+        // 1. 获取企业创建者
+        $company = Db::table('ba_company')
+            ->where('id', $companyId)
+            ->field('id, name as company_name, creator_id')
+            ->find();
+
+        if ($company && $company['creator_id']) {
+            // 获取创建者用户信息
+            $creator = Db::table('ba_user')
+                ->where('id', $company['creator_id'])
+                ->field('id as user_id, nickname as member_name, avatar, email, mobile')
+                ->find();
+
+            if ($creator) {
+                $creator['is_creator'] = 1;
+                $creator['role'] = 1; // 创建者/管理员
+                $members[] = $creator;
+            }
+        }
+
+        // 2. 获取企业成员表中的成员
+        $companyMembers = Db::table('ba_company_member')
+            ->alias('cm')
+            ->leftJoin('ba_user u', 'u.id = cm.user_id')
+            ->where('cm.company_id', $companyId)
+            ->where('cm.status', 1)
+            ->field('
+            cm.id as member_id, 
+            cm.user_id, 
+            COALESCE(cm.name, u.nickname) as member_name,
+            COALESCE(cm.email, u.email) as email,
+            u.avatar,
+            u.mobile,
+            cm.role
+        ')
+            ->select()
+            ->toArray();
+
+        // 过滤掉重复的成员(创建者可能也在成员表中)
+        foreach ($companyMembers as $member) {
+            // 检查是否已经是创建者
+            $isCreator = false;
+            foreach ($members as $existingMember) {
+                if ($existingMember['user_id'] == $member['user_id']) {
+                    $isCreator = true;
+                    break;
+                }
+            }
+
+            if (!$isCreator) {
+                $member['is_creator'] = 0;
+                $members[] = $member;
+            }
+        }
+
+        return $members;
+    }
+
+    /**
+     * 获取指定套餐已分配的成员
+     */
+    private function getAllocatedMembersForPackage($companyId, $packagePriceId)
+    {
+
+        // 获取套餐信息,判断卡类型
+        $package = Db::table('ba_package_price')
+            ->where('id', $packagePriceId)
+            ->field('card_type')
+            ->find();
+
+        if (!$package) {
+            return [];
+        }
+
+        $cardType = $package['card_type'];
+
+        if ($cardType >= 3) {
+            // 日期卡:从分配记录中获取已分配成员
+            return Db::table('ba_company_package_allocation')
+                ->alias('cpa')
+                ->leftJoin('ba_user u', 'u.id = cpa.assigned_to_user_id')
+                ->leftJoin('ba_company_member cm', 'cm.user_id = cpa.assigned_to_user_id AND cm.company_id = cpa.company_id')
+                ->where('cpa.company_id', $companyId)
+                ->where('cpa.package_price_id', $packagePriceId)
+                ->where('cpa.allocation_status', 1) // 已分配
+                ->whereNotNull('cpa.assigned_to_user_id')
+                ->field('
+                cpa.assigned_to_user_id as user_id,
+                COALESCE(cm.name, u.nickname) as member_name,
+                COALESCE(cm.email, u.email) as email,
+                u.avatar,
+                u.mobile,
+                cpa.allocation_time,
+                cpa.start_time,
+                cpa.end_time,
+                cpa.remaining_times,
+                cpa.remaining_points
+            ')
+                ->select()
+                ->toArray();
+        } else {
+            // 次卡/积分卡:从席位表的授权用户列表中获取
+            $seats = Db::table('ba_company_package_seats')
+                ->where('company_id', $companyId)
+                ->where('package_price_id', $packagePriceId)
+                ->field('authorized_users')
+                ->find();
+
+            if (!$seats || empty($seats['authorized_users'])) {
+                return [];
+            }
+
+            $authorizedUserIds = json_decode($seats['authorized_users'], true);
+            if (empty($authorizedUserIds)) {
+                return [];
+            }
+
+            // 获取授权用户的详细信息
+
+           $user=Db::table('ba_user')
+            ->alias('u')
+            // 使用字符串条件替代闭包
+            ->leftJoin('ba_company_member cm', 'cm.user_id = u.id AND cm.company_id = ' . $companyId)
+            ->whereIn('u.id', $authorizedUserIds)
+            ->field('
+                u.id as user_id,
+                COALESCE(cm.name, u.nickname) as member_name,
+                COALESCE(cm.email, u.email) as email,
+                u.avatar,
+                u.mobile,
+                cm.role
+            ')
+            ->select()
+            ->toArray();
+            return $user;
+        }
+    }
+
+    /**
+     * 分配套餐给企业成员
+     */
+    public function allocatePackage($seatsId, $assignedToUserId, $operatorId, $remark = '')
+    {
+        Db::startTrans();
+        try {
+            // 1. 获取席位信息
+            $seats = Db::table('ba_company_package_seats')
+                ->where('id', $seatsId)
+                ->lock(true)
+                ->find();
+
+            if (!$seats || $seats['available_seats'] <= 0) {
+                throw new \Exception('没有可用席位');
+            }
+            // 2. 检查是否是日期卡
+            if ($seats['card_type'] < 3) {
+                throw new \Exception('非日期卡类型,请使用assignPackagePermission方法');
+            }
+            // 3. 检查权限(只有创建者可以分配其他人的,成员只能分配自己的)
+            $isCreator = Db::table('ba_company')
+                    ->where('id', $seats['company_id'])
+                    ->where('creator_id', $operatorId)
+                    ->count() > 0;
+            // 4. 获取一个未分配的购买记录
+            $availableAllocation = Db::table('ba_company_package_allocation')
+                ->where('company_id', $seats['company_id'])
+                ->where('package_price_id', $seats['package_price_id'])
+                ->where('record_type', 2) // 购买记录
+                ->where('allocation_status', 0) // 未分配
+                ->where('assigned_to_user_id', null) // 未指定用户
+                ->lock(true)
+                ->order('seat_index', 'asc') // 按席位索引排序
+                ->find();
+            if (!$availableAllocation) {
+                throw new \Exception('没有可分配的套餐记录');
+            }
+
+            // 如果不是创建者,检查是否是购买者
+            if (!$isCreator && $availableAllocation['buyer_id'] != $operatorId) {
+                throw new \Exception('您只能分配自己购买的套餐');
+            }
+
+            // 5. 检查成员是否属于该企业
+            $isMember = Db::table('ba_company_member')
+                    ->where('company_id', $seats['company_id'])
+                    ->where('user_id', $assignedToUserId)
+                    ->where('status', 1)
+                    ->count() > 0;
+
+            if (!$isMember) {
+                throw new \Exception('该用户不是企业成员');
+            }
+
+            // 6. 检查该成员是否已分配此套餐(日期卡只能分配给一个成员)
+            if ($seats['card_type'] >= 3) { // 日期卡
+                $alreadyAllocated = Db::table('ba_company_package_allocation')
+                    ->where('company_id', $seats['company_id'])
+                    ->where('package_price_id', $seats['package_price_id'])
+                    ->where('assigned_to_user_id', $assignedToUserId)
+                    ->where('allocation_status', 1)
+                    ->count();
+
+                if ($alreadyAllocated > 0) {
+                    throw new \Exception('该成员已分配此套餐,如需再次分配请重新购买');
+                }
+            }
+
+            // 7. 执行分配
+            $now = date('Y-m-d H:i:s');
+            // 计算到期时间
+            $endTime = $this->calculateEndTime($seats['card_type'], $now);
+            // 更新分配记录
+            Db::table('ba_company_package_allocation')
+                ->where('id', $availableAllocation['id'])
+                ->update([
+                    'assigned_to_user_id' => $assignedToUserId,
+                    'assigned_by_user_id' => $operatorId,
+                    'allocation_status' => 1,
+                    'allocation_time' => $now,
+                    'start_time' => $now,
+                    'end_time' => $endTime,
+                    'expire_time' => $endTime, // 更新冗余字段
+                    'update_time' => $now
+                ]);
+
+            // 8.更新席位表
+            Db::table('ba_company_package_seats')
+                ->where('id', $seatsId)
+                ->update([
+                    'assigned_seats' => Db::raw('assigned_seats + 1'),
+                    'available_seats' => Db::raw('available_seats - 1'),
+                    'update_time' => $now
+                ]);
+
+            // 9. 检查是否所有席位都已分配
+            $unallocatedCount = Db::table('ba_company_package_allocation')
+                ->where('order_item_id', $availableAllocation['order_item_id'])
+                ->where('allocation_status', 0)
+                ->where('record_type', 2)
+                ->count();
+
+            if ($unallocatedCount == 0) {
+                // 所有席位都已分配,更新订单商品
+                Db::table('ba_order_item')
+                    ->where('id', $availableAllocation['order_item_id'])
+                    ->update([
+                        'is_allocated' => 1,
+                        'company_id' => $seats['company_id']
+                    ]);
+            }
+            // 10. 为用户创建产品使用记录
+            $userProductService = new UserProductService();
+            $userProductService->createCompanyProductUsage($availableAllocation, $assignedToUserId, $seats['card_type']);
+
+            // 11. 记录分配日志
+            Db::table('ba_allocation_log')->insert([
+                'allocation_id' => $availableAllocation['id'],
+                'action' => 1, // 分配
+                'operator_id' => $operatorId,
+                'to_user_id' => $assignedToUserId,
+                'remark' => $remark,
+                'create_time' => $now
+            ]);
+
+            Db::commit();
+            return [
+                'success' => true,
+                'allocation_id' => $availableAllocation['id'],
+                'message' => '分配成功'
+            ];
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error('分配套餐失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 取消分配(日期卡独占席位模式)
+     */
+    public function cancelAllocation($allocationId, $operatorId, $remark = '')
+    {
+        Db::startTrans();
+        try {
+            // 1. 获取分配记录
+            $allocation = Db::table('ba_company_package_allocation')
+                ->where('id', $allocationId)
+                ->where('record_type', 2) // 购买记录
+                ->where('allocation_status', 1) // 已分配
+                ->lock(true)
+                ->find();
+
+            if (!$allocation) {
+                throw new \Exception('分配记录不存在或未分配');
+            }
+
+            // 2. 检查权限
+            $isCreator = Db::table('ba_company')
+                    ->where('id', $allocation['company_id'])
+                    ->where('creator_id', $operatorId)
+                    ->count() > 0;
+
+            if (!$isCreator && $allocation['assigned_by_user_id'] != $operatorId) {
+                throw new \Exception('您没有取消分配的权限');
+            }
+
+            // 3. 获取席位信息
+            $seats = Db::table('ba_company_package_seats')
+                ->where('company_id', $allocation['company_id'])
+                ->where('package_price_id', $allocation['package_price_id'])
+                ->find();
+
+            if (!$seats) {
+                throw new \Exception('企业套餐席位不存在');
+            }
+            // 4. 执行取消分配
+            $now = date('Y-m-d H:i:s');
+            // 更新分配记录状态
+            Db::table('ba_company_package_allocation')
+                ->where('id', $allocationId)
+                ->update([
+                    'assigned_to_user_id' => null,
+                    'allocation_status' => 2,
+                    'cancel_allocation_time' => $now,
+                    'update_time' => $now
+                ]);
+
+            // 5. 更新席位表
+            Db::table('ba_company_package_seats')
+                ->where('id', $seats['id'])
+                ->update([
+                    'assigned_seats' => Db::raw('assigned_seats - 1'),
+                    'available_seats' => Db::raw('available_seats + 1'),
+                    'update_time' => $now
+                ]);
+
+            // 6. 检查是否还有已分配的记录
+            $allocatedCount = Db::table('ba_company_package_allocation')
+                ->where('order_item_id', $allocation['order_item_id'])
+                ->where('allocation_status', 1)
+                ->count();
+
+            if ($allocatedCount == 0) {
+                // 没有已分配的记录,更新订单商品
+                Db::table('ba_order_item')
+                    ->where('id', $allocation['order_item_id'])
+                    ->update([
+                        'is_allocated' => 0,
+                        'allocation_id' => null
+                    ]);
+            }
+
+            // 7. 删除对应的用户产品使用记录
+            Db::table('ba_user_product_usage')
+                ->where('user_id', $allocation['assigned_to_user_id'])
+                ->where('package_price_id', $allocation['package_price_id'])
+                ->where('company_id', $allocation['company_id'])
+                ->where('package_type', 2) // 企业套餐
+                ->delete();
+
+            // 8. 记录取消分配日志
+            Db::table('ba_allocation_log')->insert([
+                'allocation_id' => $allocationId,
+                'action' => 2, // 取消分配
+                'operator_id' => $operatorId,
+                'from_user_id' => $allocation['assigned_to_user_id'],
+                'remark' => $remark,
+                'create_time' => $now
+            ]);
+
+            Db::commit();
+            return true;
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error('取消分配失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 分配次卡/积分卡使用权限给成员(授权访问共享资源池)
+     */
+    public function assignPackagePermission($seatsId, $memberUserId, $allocatorId, $remark = '')
+    {
+        Db::startTrans();
+        try {
+            // 1. 获取席位信息
+            $seats = Db::table('ba_company_package_seats')
+                ->where('id', $seatsId)
+                ->lock(true)
+                ->find();
+
+            if (!$seats) {
+                throw new \Exception('企业套餐不存在');
+            }
+
+            // 2. 检查权限(只有企业创建者或购买者可以分配)
+            $canAssign = $this->checkAssignPermission($allocatorId, $seats['company_id'], $seatsId);
+            if (!$canAssign) {
+                throw new \Exception('没有分配权限');
+            }
+
+            // 3. 检查成员是否属于企业
+            if (!$this->isCompanyMember($memberUserId, $seats['company_id'])) {
+                throw new \Exception('该用户不是企业成员');
+            }
+
+            // 4. 检查是否已经是次卡或积分卡
+            if (!in_array($seats['card_type'], [1, 2])) {
+                throw new \Exception('只有次卡和积分卡支持权限分配');
+            }
+
+            // 5. 检查该成员是否已被授权
+            $authorizedUsers = json_decode($seats['authorized_users'] ?? '[]', true);
+
+            if (in_array($memberUserId, $authorizedUsers)) {
+                throw new \Exception('该成员已被授权使用此套餐');
+            }
+
+            // 6. 查找通用记录
+            $generalRecord = Db::table('ba_company_package_allocation')
+                ->where('company_id', $seats['company_id'])
+                ->where('package_price_id', $seats['package_price_id'])
+                ->where('record_type', 0) // 通用记录
+                ->where('is_general', 1) // 是通用记录
+                ->order('id', 'asc')
+                ->find();
+            if (!$generalRecord) {
+                throw new \Exception('未找到套餐购买记录');
+            }
+            // 7. 将成员添加到授权列表
+            $authorizedUsers[] = $memberUserId;
+
+            Db::table('ba_company_package_seats')
+                ->where('id', $seatsId)
+                ->update([
+                    'authorized_users' => json_encode($authorizedUsers),
+                    'assigned_member_count' => Db::raw('assigned_member_count + 1'),
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+
+            // 8. 创建新的分配记录
+            $now = date('Y-m-d H:i:s');
+            $newAllocationData = [
+                'order_item_id' => $generalRecord['order_item_id'],
+                'order_id' => $generalRecord['order_id'],
+                'package_price_id' => $seats['package_price_id'],
+                'product_id' => $seats['product_id'],
+                'buyer_id' => $generalRecord['buyer_id'],
+                'company_id' => $seats['company_id'],
+                'assigned_to_user_id' => $memberUserId,
+                'assigned_by_user_id' => $allocatorId,
+                'record_type' => 1, // 用户分配记录
+                'is_general' => 0,
+                'seat_index' => 0,
+                'parent_id' => $generalRecord['id'], // 关联通用记录
+                'allocation_status' => 1, // 已分配
+                'allocation_time' => $now,
+                'start_time' => $now,
+                'end_time' => null,
+                'expire_time' => null,
+                'remaining_times' => $seats['remaining_times'],
+                'remaining_points' => $seats['remaining_points'],
+                'is_used' => 0,
+                'create_time' => $now,
+                'update_time' => $now
+            ];
+
+            $allocationId = Db::table('ba_company_package_allocation')->insertGetId($newAllocationData);
+
+            // 9. 为用户创建产品使用记录
+            $userProductService = new UserProductService();
+            $orderItem = Db::table('ba_order_item')->where('id', $generalRecord['order_item_id'])->find();
+
+            if ($orderItem) {
+                $package = Db::table('ba_package_price')->where('id', $seats['package_price_id'])->find();
+                if ($package) {
+                    $userProductService->createUserProductRecord($memberUserId, $orderItem, $package);
+                }
+            }
+            // 10. 记录分配日志
+            Db::table('ba_allocation_log')->insert([
+                'allocation_id' => $allocationId,
+                'action' => 1, // 分配
+                'operator_id' => $allocatorId,
+                'to_user_id' => $memberUserId,
+                'remark' => $remark ?: '分配次卡/积分卡使用权限',
+                'create_time' => $now
+            ]);
+
+
+            Db::commit();
+
+            return [
+                'success' => true,
+                'allocation_id' => $allocationId,
+                'message' => '分配成功'
+            ];
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            throw $e;
+        }
+    }
+
+    /**
+     * 检查分配权限
+     */
+    private function checkAssignPermission($userId, $companyId, $seatsId = null)
+    {
+        // 1. 检查是否是公司创建者
+        $isCreator = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->where('creator_id', $userId)
+                ->count() > 0;
+
+        if ($isCreator) {
+            return true;
+        }
+
+        // 2. 检查是否是购买者(对于该套餐)
+        if ($seatsId) {
+            $isBuyer = Db::table('ba_company_package_allocation')
+                    ->where('seats_id', $seatsId)
+                    ->where('buyer_id', $userId)
+                    ->count() > 0;
+
+            if ($isBuyer) {
+                return true;
+            }
+        }
+
+        // 3. 检查是否是管理员(根据企业成员表)
+        $isAdmin = Db::table('ba_company_member')
+                ->where('company_id', $companyId)
+                ->where('user_id', $userId)
+                ->where('role', 1) // 1=管理员
+                ->where('status', 1)
+                ->count() > 0;
+
+        return $isAdmin;
+    }
+
+    /**
+     * 检查用户是否是企业成员
+     */
+    private function isCompanyMember($userId, $companyId)
+    {
+        // 检查是否是创建者
+        $isCreator = Db::table('ba_company')
+                ->where('id', $companyId)
+                ->where('creator_id', $userId)
+                ->count() > 0;
+
+        if ($isCreator) {
+            return true;
+        }
+
+        // 检查是否是成员
+        $isMember = Db::table('ba_company_member')
+                ->where('company_id', $companyId)
+                ->where('user_id', $userId)
+                ->where('status', 1)
+                ->count() > 0;
+
+        return $isMember;
+    }
+    /**
+     * 自动分配席位给购买者
+     */
+    public function autoAllocateToBuyer($buyerId, $companyId, $packagePriceId, $orderId)
+    {
+        try {
+            Log::info('开始自动分配席位给购买者', [
+                'buyer_id' => $buyerId,
+                'company_id' => $companyId,
+                'package_price_id' => $packagePriceId,
+                'order_id' => $orderId
+            ]);
+            // 1. 检查购买者是否已分配此套餐
+            $alreadyAllocated = Db::table('ba_company_package_allocation')
+                ->where('company_id', $companyId)
+                ->where('package_price_id', $packagePriceId)
+                ->where('assigned_to_user_id', $buyerId)
+                ->where('allocation_status', 1) // 已分配
+                ->count();
+            Log::info('检查购买者是否已分配此套餐结果', [
+                'already_allocated' => $alreadyAllocated
+            ]);
+            if ($alreadyAllocated > 0) {
+                Log::info('购买者已分配此套餐,跳过自动分配', [
+                    'buyer_id' => $buyerId,
+                    'company_id' => $companyId,
+                    'package_price_id' => $packagePriceId
+                ]);
+                return false; // 已分配,跳过
+            }
+
+            // 2. 获取可用的席位ID(从席位表中获取)
+            $seats = Db::table('ba_company_package_seats')
+                ->where('company_id', $companyId)
+                ->where('package_price_id', $packagePriceId)
+                ->where('available_seats', '>', 0)
+                ->find();
+            Log::info('查询可用席位结果', [
+                'seats' => $seats ? $seats['id'] : null,
+                'available_seats' => $seats ? $seats['available_seats'] : 0
+            ]);
+            if (!$seats) {
+                Log::error('没有可用席位进行自动分配', [
+                    'company_id' => $companyId,
+                    'package_price_id' => $packagePriceId
+                ]);
+                return false;
+            }
+
+            // 3. 获取一个未分配的分配记录
+            $allocation = Db::table('ba_company_package_allocation')
+                ->where('order_id', $orderId)
+                ->where('company_id', $companyId)
+                ->where('package_price_id', $packagePriceId)
+                ->where('allocation_status', 0) // 未分配
+                ->order('id', 'asc')
+                ->find();
+            Log::info('查询未分配记录结果', [
+                'allocation' => $allocation ? $allocation['id'] : null,
+                'order_id' => $orderId
+            ]);
+
+            if (!$allocation) {
+                // 尝试查找任何属于该订单的未分配记录
+                $allocation = Db::table('ba_company_package_allocation')
+                    ->where('order_id', $orderId)
+                    ->where('allocation_status', 0)
+                    ->order('id', 'asc')
+                    ->find();
+
+                Log::info('二次查询未分配记录结果', [
+                    'allocation' => $allocation ? $allocation['id'] : null
+                ]);
+
+                if (!$allocation) {
+                    Log::error('没有找到未分配的分配记录', [
+                        'order_id' => $orderId,
+                        'company_id' => $companyId,
+                        'package_price_id' => $packagePriceId
+                    ]);
+                    return false;
+                }
+            }
+
+            // 4. 获取套餐信息(用于卡类型判断)
+            $package = Db::table('ba_package_price')
+                ->where('id', $packagePriceId)
+                ->find();
+
+            if (!$package) {
+                Log::error('套餐信息不存在', ['package_price_id' => $packagePriceId]);
+                return false;
+            }
+
+            Db::startTrans();
+            try {
+                // 5. 执行分配
+                $now = date('Y-m-d H:i:s');
+
+                // 更新分配记录
+                Db::table('ba_company_package_allocation')
+                    ->where('id', $allocation['id'])
+                    ->update([
+                        'assigned_to_user_id' => $buyerId,
+                        'assigned_by_user_id' => $buyerId,
+                        'allocation_status' => 1,
+                        'allocation_time' => $now,
+                        'start_time' => $now,
+                        'end_time' => $this->calculateEndTime($package['card_type'], $now),
+                        'update_time' => $now
+                    ]);
+
+                // 更新席位表
+                Db::table('ba_company_package_seats')
+                    ->where('id', $seats['id'])
+                    ->update([
+                        'assigned_seats' => Db::raw('assigned_seats + 1'),
+                        'available_seats' => Db::raw('available_seats - 1'),
+                        'update_time' => $now
+                    ]);
+
+                // 更新订单商品
+                Db::table('ba_order_item')
+                    ->where('id', $allocation['order_item_id'])
+                    ->update([
+                        'is_allocated' => 1,
+                        'company_id' => $companyId
+                    ]);
+
+                // 6. 记录分配日志
+                Db::table('ba_allocation_log')->insert([
+                    'allocation_id' => $allocation['id'],
+                    'action' => 1, // 分配
+                    'operator_id' => $buyerId,
+                    'to_user_id' => $buyerId,
+                    'remark' => '购买后自动分配给购买者',
+                    'create_time' => $now
+                ]);
+
+                // 7. 根据卡类型创建用户产品记录
+                $userProductService = new UserProductService();
+
+                // 所有卡类型都需要创建使用记录
+                if ($package['card_type'] >= 3) { // 日期卡
+                // 日期卡分配时创建使用记录
+                $userProductService->createCompanyProductUsage($allocation, $buyerId, $package['card_type']);
+                } else { // 次卡(1)和积分卡(2)
+                    // 获取订单商品信息
+                    $orderItem = Db::table('ba_order_item')
+                        ->where('id', $allocation['order_item_id'])
+                        ->find();
+
+                    if ($orderItem) {
+                        // 创建用户产品记录(次卡/积分卡)
+                        $userProductService->createUserProductRecord($buyerId, $orderItem, $package);
+                    }
+                }
+
+                Db::commit();
+
+                Log::info('企业套餐自动分配给购买者成功', [
+                    'buyer_id' => $buyerId,
+                    'company_id' => $companyId,
+                    'package_price_id' => $packagePriceId,
+                    'allocation_id' => $allocation['id']
+                ]);
+
+                return true;
+
+            } catch (\Throwable $e) {
+                Db::rollback();
+                Log::error('企业套餐自动分配给购买者失败: ' . $e->getMessage());
+                throw $e;
+            }
+
+        } catch (\Throwable $e) {
+            Log::error('自动分配企业套餐给购买者失败: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 计算到期时间
+     */
+    private function calculateEndTime($cardType, $startTime)
+    {
+        $start = strtotime($startTime);
+
+        switch ($cardType) {
+            case 3: // 日卡
+                return date('Y-m-d 23:59:59', strtotime('+1 day', $start));
+            case 4: // 周卡
+                return date('Y-m-d 23:59:59', strtotime('+7 days', $start));
+            case 5: // 季卡
+                return date('Y-m-d 23:59:59', strtotime('+90 days', $start));
+            case 6: // 月卡
+                return date('Y-m-d 23:59:59', strtotime('+30 days', $start));
+            case 7: // 年卡
+                return date('Y-m-d 23:59:59', strtotime('+365 days', $start));
+            default:
+                return null; // 次卡和积分卡没有固定到期时间
+        }
+    }
+
+    /**
+     * 获取卡类型文本
+     */
+    private function getCardTypeText($type)
+    {
+        $map = [
+            1 => '次卡',
+            2 => '积分卡',
+            3 => '日卡',
+            4 => '周卡',
+            5 => '季卡',
+            6 => '月卡',
+            7 => '年卡'
+        ];
+        return $map[$type] ?? '其他';
+    }
+
+    private function getProductIdByPackage($packagePriceId) {
+        // 实现获取产品ID的逻辑
+        $package = Db::table('ba_package_price')->where('id', $packagePriceId)->find();
+        $product = Db::table('ba_product')->where('package_id', $package['package_id'])->find();
+        return $product ? $product['id'] : 0;
+    }
+
+    private function getOrderBuyerId($orderId) {
+        $order = Db::table('ba_order')->where('id', $orderId)->find();
+        return $order ? $order['user_id'] : 0;
+    }
+
+    /**
+     * 取消次卡/积分卡权限分配
+     */
+    public function cancelPackagePermission($allocationId, $operatorId, $remark = '')
+    {
+        Db::startTrans();
+        try {
+            // 1. 获取分配记录
+            $allocation = Db::table('ba_company_package_allocation')
+                ->where('id', $allocationId)
+                ->where('record_type', 1) // 用户分配记录
+                ->where('allocation_status', 1) // 已分配
+                ->lock(true)
+                ->find();
+
+            if (!$allocation) {
+                throw new \Exception('分配记录不存在或未分配');
+            }
+
+            // 2. 检查权限
+            $canCancel = $this->checkAssignPermission($operatorId, $allocation['company_id']);
+            if (!$canCancel) {
+                throw new \Exception('您没有取消分配的权限');
+            }
+
+            // 3. 获取席位信息
+            $seats = Db::table('ba_company_package_seats')
+                ->where('company_id', $allocation['company_id'])
+                ->where('package_price_id', $allocation['package_price_id'])
+                ->find();
+
+            if (!$seats) {
+                throw new \Exception('企业套餐席位不存在');
+            }
+
+            // 4. 从授权列表中移除用户
+            $authorizedUsers = json_decode($seats['authorized_users'] ?? '[]', true);
+            $key = array_search($allocation['assigned_to_user_id'], $authorizedUsers);
+
+            if ($key !== false) {
+                unset($authorizedUsers[$key]);
+                $authorizedUsers = array_values($authorizedUsers); // 重新索引数组
+
+                // 更新席位表
+                Db::table('ba_company_package_seats')
+                    ->where('id', $seats['id'])
+                    ->update([
+                        'authorized_users' => json_encode($authorizedUsers),
+                        'assigned_member_count' => Db::raw('assigned_member_count - 1'),
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+            }
+
+            // 5. 更新分配记录状态
+            $now = date('Y-m-d H:i:s');
+            Db::table('ba_company_package_allocation')
+                ->where('id', $allocationId)
+                ->update([
+                    'allocation_status' => 2, // 已取消
+                    'cancel_allocation_time' => $now,
+                    'update_time' => $now
+                ]);
+            // 6. 删除对应的用户产品使用记录
+            Db::table('ba_user_product_usage')
+                ->where('user_id', $allocation['assigned_to_user_id'])
+                ->where('package_price_id', $allocation['package_price_id'])
+                ->where('company_id', $allocation['company_id'])
+                ->where('package_type', 2) // 企业套餐
+                ->delete();
+
+            // 7. 记录取消分配日志
+            Db::table('ba_allocation_log')->insert([
+                'allocation_id' => $allocationId,
+                'action' => 2, // 取消分配
+                'operator_id' => $operatorId,
+                'from_user_id' => $allocation['assigned_to_user_id'],
+                'remark' => $remark ?: '取消次卡/积分卡使用权限',
+                'create_time' => $now
+            ]);
+
+            Db::commit();
+            return true;
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error('取消次卡/积分卡权限失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+    /**
+     * 使用企业次卡/积分卡(从共享资源池扣除)
+     */
+    public function useSharedCompanyPackage($userId, $packagePriceId, $useQuantity, $productId = null, $remark = '')
+    {
+        Db::startTrans();
+        try {
+            // 1. 获取用户所在企业
+            $companyInfo = $this->getUserCompany($userId);
+            if (!$companyInfo) {
+                throw new \Exception('您不属于任何企业');
+            }
+
+            // 2. 获取套餐信息
+            $package = Db::table('ba_package_price')
+                ->where('id', $packagePriceId)
+                ->find();
+
+            if (!$package) {
+                throw new \Exception('套餐不存在');
+            }
+
+            // 3. 检查卡类型是否为次卡或积分卡
+            if (!in_array($package['card_type'], [1, 2])) {
+                throw new \Exception('只有次卡和积分卡支持此使用方式');
+            }
+
+            // 4. 获取企业套餐席位信息,检查用户是否被授权
+            $seats = Db::table('ba_company_package_seats')
+                ->where('company_id', $companyInfo['id'])
+                ->where('package_price_id', $packagePriceId)
+                ->lock(true)
+                ->find();
+
+            if (!$seats) {
+                throw new \Exception('企业套餐不存在');
+            }
+
+            // 检查用户是否在授权列表中
+            $authorizedUsers = json_decode($seats['authorized_users'] ?? '[]', true);
+            if (!in_array($userId, $authorizedUsers)) {
+                throw new \Exception('您未被授权使用此套餐');
+            }
+
+            // 5. 检查共享资源池是否足够
+            if ($package['card_type'] == 1) { // 次卡
+                if ($seats['remaining_times'] < $useQuantity) {
+                    throw new \Exception('剩余次数不足,剩余:' . $seats['remaining_times'] . '次');
+                }
+            } elseif ($package['card_type'] == 2) { // 积分卡
+                if ($seats['remaining_points'] < $useQuantity) {
+                    throw new \Exception('剩余积分不足,剩余:' . $seats['remaining_points'] . '积分');
+                }
+            }
+
+            // 6. 获取用户的分配记录
+            $userAllocation = Db::table('ba_company_package_allocation')
+                ->where('company_id', $companyInfo['id'])
+                ->where('package_price_id', $packagePriceId)
+                ->where('assigned_to_user_id', $userId)
+                ->where('allocation_status', 1)
+                ->order('id', 'asc')
+                ->find();
+
+            if (!$userAllocation) {
+                throw new \Exception('未找到您的分配记录');
+            }
+
+            // 7. 扣减共享池资源
+            if ($package['card_type'] == 1) { // 次卡
+                Db::table('ba_company_package_seats')
+                    ->where('id', $seats['id'])
+                    ->dec('remaining_times', $useQuantity)
+                    ->update();
+
+                $remainingTimesAfter = $seats['remaining_times'] - $useQuantity;
+                $remainingPointsAfter = $seats['remaining_points'];
+            } else { // 积分卡
+                Db::table('ba_company_package_seats')
+                    ->where('id', $seats['id'])
+                    ->dec('remaining_points', $useQuantity)
+                    ->update();
+
+                $remainingTimesAfter = $seats['remaining_times'];
+                $remainingPointsAfter = $seats['remaining_points'] - $useQuantity;
+            }
+
+            // 8. 更新分配记录的使用状态
+            Db::table('ba_company_package_allocation')
+                ->where('id', $userAllocation['id'])
+                ->update([
+                    'is_used' => 1,
+                    'usage_count' => Db::raw('usage_count + ' . $useQuantity),
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+
+            // 9. 记录使用明细
+            $usageData = [
+                'allocation_id' => $userAllocation['id'],
+                'company_id' => $companyInfo['id'],
+                'package_price_id' => $packagePriceId,
+                'user_id' => $userId,
+                'card_type' => $package['card_type'],
+                'used_quantity' => $useQuantity,
+                'remaining_times_after' => $remainingTimesAfter,
+                'remaining_points_after' => $remainingPointsAfter,
+                'product_id' => $productId,
+                'usage_time' => date('Y-m-d H:i:s'),
+                'remark' => $remark,
+                'create_time' => date('Y-m-d H:i:s')
+            ];
+
+            $usageId = Db::table('ba_company_package_usage')->insertGetId($usageData);
+
+            Db::commit();
+
+            return [
+                'success' => true,
+                'usage_id' => $usageId,
+                'remaining_times' => $remainingTimesAfter,
+                'remaining_points' => $remainingPointsAfter,
+                'message' => '使用成功'
+            ];
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error('使用企业次卡/积分卡失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+}

+ 342 - 0
app/common/service/InvitationService.php

@@ -0,0 +1,342 @@
+<?php
+namespace app\common\service;
+
+use think\facade\Db;
+use think\facade\Log;
+
+class InvitationService
+{
+    /**
+     * 生成唯一邀请码
+     */
+    public function generateInvitationCode(): string
+    {
+        $maxAttempts = 10;
+        $attempts = 0;
+
+        while ($attempts < $maxAttempts) {
+            // 方法1:使用数字+字母组合
+            $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 去掉容易混淆的字符
+            $code = '';
+            for ($i = 0; $i < 6; $i++) {
+                $code .= $chars[mt_rand(0, strlen($chars) - 1)];
+            }
+
+            // 方法2:或者使用更短的邀请码
+            // $code = strtoupper(substr(md5(uniqid()), 0, 6));
+
+            // 检查邀请码是否已存在
+            $exists = Db::table('ba_user')
+                ->where('invitation_code', $code)
+                ->count();
+
+            if (!$exists) {
+                return $code;
+            }
+
+            $attempts++;
+
+            // 如果冲突,尝试不同的算法
+            if ($attempts >= 5) {
+                $code = 'INV' . mt_rand(1000, 9999);
+                $exists = Db::table('ba_user')
+                    ->where('invitation_code', $code)
+                    ->count();
+
+                if (!$exists) {
+                    return $code;
+                }
+            }
+        }
+
+        // 如果还是冲突,使用时间戳+随机数
+        return 'INV' . time() . mt_rand(10, 99);
+    }
+
+    /**
+     * 处理用户注册邀请关系(完整版)
+     */
+    public function processRegistration(int $newUserId, string $invitationCode = ''): bool
+    {
+        try {
+            // 1. 为注册用户生成自己的邀请码
+            $userInvitationCode = $this->generateInvitationCode();
+
+            // 2. 将邀请码保存到用户表
+            Db::table('ba_user')
+                ->where('id', $newUserId)
+                ->update([
+                    'invitation_code' => $userInvitationCode,
+                    'update_time' => time()
+                ]);
+            // 3. 处理邀请关系(如果提供了邀请码)
+            if (!empty($invitationCode)) {
+                $this->processInvitationRelationship($newUserId, $invitationCode);
+            }
+
+            // 4. 初始化用户的佣金记录
+            $this->initUserCommission($newUserId);
+
+            Log::info("用户邀请码生成成功: 用户ID={$newUserId}, 邀请码={$userInvitationCode}");
+
+            return true;
+        } catch (\Throwable $e) {
+            Log::error("处理用户注册邀请关系失败: " . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 处理邀请关系(新用户使用别人的邀请码注册)
+     */
+    private function processInvitationRelationship(int $newUserId, string $invitationCode): void
+    {
+        try {
+            // 查找邀请人(通过邀请码)
+            $inviter = Db::table('ba_user')
+                ->where('invitation_code', $invitationCode)
+                ->field('id, username')
+                ->find();
+
+            if (!$inviter) {
+                Log::warning("邀请码不存在: {$invitationCode}");
+                return;
+            }
+
+            // 检查是否已存在邀请关系
+            $exists = Db::table('ba_user_invitation')
+                ->where('invitee_id', $newUserId)
+                ->find();
+
+            if ($exists) {
+                return; // 已有邀请关系,直接返回
+            }
+
+            // 创建邀请关系记录
+            Db::table('ba_user_invitation')->insert([
+                'inviter_id' => $inviter['id'],
+                'invitee_id' => $newUserId,
+                'invitation_code' => $invitationCode,
+                'create_time' => date('Y-m-d H:i:s')
+            ]);
+
+            // 更新邀请人统计
+            $this->updateInviterStats($inviter['id']);
+
+            Log::info("邀请关系创建成功: 邀请人ID={$inviter['id']}, 被邀请人ID={$newUserId}");
+
+        } catch (\Throwable $e) {
+            Log::error("处理邀请关系失败: " . $e->getMessage());
+        }
+    }
+
+    /**
+     * 更新邀请人统计
+     */
+    private function updateInviterStats(int $inviterId): void
+    {
+        try {
+            // 统计邀请人数
+            $invitationCount = Db::table('ba_user_invitation')
+                ->where('inviter_id', $inviterId)
+                ->count();
+
+            // 获取当前佣金记录
+            $commission = Db::table('ba_user_commission')
+                ->where('user_id', $inviterId)
+                ->find();
+
+            if ($commission) {
+                // 更新现有记录
+                Db::table('ba_user_commission')
+                    ->where('user_id', $inviterId)
+                    ->update([
+                        'invitation_count' => $invitationCount,
+                        'total_invitation' => $invitationCount,
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+            } else {
+                // 创建新记录(如果邀请人还没有佣金记录)
+                $promotionLink = $this->generatePromotionLink($inviterId);
+
+                Db::table('ba_user_commission')->insert([
+                    'user_id' => $inviterId,
+                    'total_commission' => 0.00,
+                    'current_commission' => 0.00,
+                    'withdrawn_commission' => 0.00,
+                    'frozen_commission' => 0.00,
+                    'promotion_link' => $promotionLink,
+                    'invitation_count' => $invitationCount,
+                    'total_invitation' => $invitationCount,
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+            }
+        } catch (\Throwable $e) {
+            Log::error("更新邀请人统计失败: " . $e->getMessage());
+        }
+    }
+
+    /**
+     * 初始化用户佣金记录
+     */
+    private function initUserCommission(int $userId): void
+    {
+        try {
+            $exists = Db::table('ba_user_commission')
+                ->where('user_id', $userId)
+                ->find();
+
+            if (!$exists) {
+                $promotionLink = $this->generatePromotionLink($userId);
+
+                Db::table('ba_user_commission')->insert([
+                    'user_id' => $userId,
+                    'total_commission' => 0.00,
+                    'current_commission' => 0.00,
+                    'withdrawn_commission' => 0.00,
+                    'frozen_commission' => 0.00,
+                    'promotion_link' => $promotionLink,
+                    'invitation_count' => 0,
+                    'total_invitation' => 0,
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+            }
+        } catch (\Throwable $e) {
+            Log::error("初始化用户佣金失败: " . $e->getMessage());
+        }
+    }
+
+    /**
+     * 生成推广链接
+     */
+    private function generatePromotionLink(int $userId): string
+    {
+        // 获取用户的邀请码
+        $user = Db::table('ba_user')
+            ->where('id', $userId)
+            ->field('invitation_code')
+            ->find();
+
+        $invitationCode = $user['invitation_code'] ?? '';
+
+        if (empty($invitationCode)) {
+            // 如果用户还没有邀请码,先生成一个
+            $invitationCode = $this->generateInvitationCode();
+            Db::table('ba_user')
+                ->where('id', $userId)
+                ->update(['invitation_code' => $invitationCode]);
+        }
+
+        $domain = request()->domain();
+        return $domain . '/#/user/register?invite=' . $invitationCode;
+    }
+
+    /**
+     * 获取用户邀请统计
+     */
+    public function getUserInvitationStats(int $userId): array
+    {
+        try {
+            // 获取用户的邀请码
+            $user = Db::table('ba_user')
+                ->where('id', $userId)
+                ->field('invitation_code')
+                ->find();
+
+            // 获取邀请关系
+            $invitations = Db::table('ba_user_invitation')
+                ->alias('ui')
+                ->leftJoin('ba_user u', 'u.id = ui.invitee_id')
+                ->where('ui.inviter_id', $userId)
+                ->field('u.id, u.username, u.email, u.mobile, u.create_time, ui.create_time as invite_time')
+                ->order('ui.create_time', 'desc')
+                ->select()
+                ->toArray();
+
+            // 获取佣金信息
+            $commission = Db::table('ba_user_commission')
+                ->where('user_id', $userId)
+                ->find();
+
+            // 获取佣金流水
+            $flows = Db::table('ba_commission_flow')
+                ->where('user_id', $userId)
+                ->order('create_time', 'desc')
+                ->limit(20)
+                ->select()
+                ->toArray();
+
+            return [
+                'invitation_code' => $user['invitation_code'] ?? '', // 用户的邀请码
+                'invitations' => $invitations,
+                'invitation_count' => count($invitations),
+                'commission' => $commission ?: [
+                    'total_commission' => 0,
+                    'current_commission' => 0,
+                    'withdrawn_commission' => 0,
+                    'frozen_commission' => 0,
+                    'promotion_link' => $this->generatePromotionLink($userId),
+                    'invitation_count' => 0,
+                    'total_invitation' => 0
+                ],
+                'flows' => $flows
+            ];
+        } catch (\Throwable $e) {
+            Log::error("获取用户邀请统计失败: " . $e->getMessage());
+            return [
+                'invitation_code' => '',
+                'invitations' => [],
+                'invitation_count' => 0,
+                'commission' => [
+                    'total_commission' => 0,
+                    'current_commission' => 0,
+                    'withdrawn_commission' => 0,
+                    'frozen_commission' => 0,
+                    'promotion_link' => $this->generatePromotionLink($userId),
+                    'invitation_count' => 0,
+                    'total_invitation' => 0
+                ],
+                'flows' => []
+            ];
+        }
+    }
+
+    /**
+     * 获取下级用户列表
+     */
+    public function getSubordinateList(int $userId, int $page = 1, int $pageSize = 10): array
+    {
+        try {
+            $query = Db::table('ba_user_invitation')
+                ->alias('ui')
+                ->leftJoin('ba_user u', 'u.id = ui.invitee_id')
+                ->where('ui.inviter_id', $userId);
+
+            $total = $query->count();
+
+            $list = $query->field('u.id, u.username, u.nickname, u.email, u.mobile, u.create_time, 
+                    u.last_login_time, u.status, ui.create_time as invite_time')
+                ->page($page, $pageSize)
+                ->order('ui.create_time', 'desc')
+                ->select()
+                ->toArray();
+
+            return [
+                'list' => $list,
+                'total' => $total,
+                'page' => $page,
+                'page_size' => $pageSize,
+                'page_count' => ceil($total / $pageSize)
+            ];
+        } catch (\Throwable $e) {
+            Log::error("获取下级用户列表失败: " . $e->getMessage());
+            return [
+                'list' => [],
+                'total' => 0,
+                'page' => $page,
+                'page_size' => $pageSize,
+                'page_count' => 0
+            ];
+        }
+    }
+}

+ 21 - 0
app/common/service/PaymentFactory.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace app\common\service;
+
+class PaymentFactory
+{
+    /**
+     * 创建支付服务实例
+     */
+    public static function create($payMethod, $payConfigId = 0)
+    {
+        switch ($payMethod) {
+            case 'wechat':
+                return new WechatPaymentService($payMethod, $payConfigId);
+            case 'alipay':
+                return new AlipayPaymentService($payMethod, $payConfigId);
+            default:
+                throw new \Exception('不支持的支付方式');
+        }
+    }
+}

+ 390 - 0
app/common/service/UserProductService.php

@@ -0,0 +1,390 @@
+<?php
+namespace app\common\service;
+
+use think\facade\Db;
+use think\facade\Log;
+
+class UserProductService
+{
+    /**
+     * 创建用户产品使用记录(支付成功后调用)
+     */
+    public function createUserProductRecord($userId, $orderItem, $package)
+    {
+        Db::startTrans();
+        try {
+            // 1. 首先检查是否已存在相同的记录
+            $existing = Db::table('ba_user_product_usage')
+                ->where('order_item_id', $orderItem['id'])
+                ->where('user_id', $userId)
+                ->lock(true)
+                ->find();
+
+            if ($existing) {
+                Db::commit();
+                Log::info('用户产品记录已存在,跳过创建', [
+                    'order_item_id' => $orderItem['id'],
+                    'user_id' => $userId,
+                    'existing_id' => $existing['id']
+                ]);
+                return $existing['id'];
+            }
+
+            // 2. 创建使用记录(原有逻辑)
+            $usageData = [
+                'user_id' => $userId,
+                'order_id' => $orderItem['order_id'],
+                'order_item_id' => $orderItem['id'],
+                'product_id' => $orderItem['product_id'],
+                'package_price_id' => $orderItem['package_price_id'],
+                'package_type' => $package['type'],
+                'card_type' => $package['card_type'],
+                'start_time' => date('Y-m-d H:i:s'),
+                'status' => 1,
+                'create_time' => date('Y-m-d H:i:s'),
+                'update_time' => date('Y-m-d H:i:s')
+            ];
+
+            // 3. 根据卡类型和购买数量计算初始值
+            $quantity = $orderItem['quantity'] ?? 1;
+            switch ($package['card_type']) {
+                case 1: // 次卡
+                    $unitValue = $package['unit_value'] ?? 0;
+                    $usageData['remaining_times'] = $unitValue * $quantity;
+                    break;
+                case 2: // 积分卡
+                    $pointsValue = $package['points_value'] ?? 0;
+                    $usageData['remaining_points'] = $pointsValue * $quantity;
+                    break;
+                case 3: // 日卡
+                    $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+1 day'));
+                    break;
+                case 4: // 周卡
+                    $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+7 days'));
+                    break;
+                case 5: // 季卡
+                    $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+90 days'));
+                    break;
+                case 6: // 月卡
+                    $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+30 days'));
+                    break;
+                case 7: // 年卡
+                    $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+365 days'));
+                    break;
+            }
+
+            $usageId = Db::table('ba_user_product_usage')->insertGetId($usageData);
+
+            // 4. 如果是个人套餐,同时更新ba_user_product表
+            if ($package['type'] == 1) {
+                $this->updateUserProductTable($userId, $orderItem, $package, $usageData);
+            }
+
+            Db::commit();
+            Log::info('用户产品记录创建成功', [
+                'usage_id' => $usageId,
+                'user_id' => $userId,
+                'order_item_id' => $orderItem['id']
+            ]);
+
+            return $usageId;
+
+        } catch (\Throwable $e) {
+            Db::rollback();
+            Log::error('创建用户产品记录失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 更新用户产品表(兼容原有表)
+     */
+    private function updateUserProductTable($userId, $orderItem, $package, $usageData)
+    {
+        try {
+        // 检查是否已有记录
+        $userProduct = Db::table('ba_user_product')
+            ->where('user_id', $userId)
+            ->where('product_id', $orderItem['product_id'])
+            ->where('package_price_id', $orderItem['package_price_id'])
+            ->find();
+
+        if ($userProduct) {
+            // 更新现有记录
+            $updateData = [
+                'update_time' => date('Y-m-d H:i:s')
+            ];
+
+            // 根据卡类型累加
+            if ($package['card_type'] == 1) { // 次卡
+                $updateData['remaining_times'] = Db::raw('remaining_times + ' . $usageData['remaining_times']);
+            } elseif ($package['card_type'] == 2) { // 积分卡
+                $updateData['remaining_points'] = Db::raw('remaining_points + ' . $usageData['remaining_points']);
+            } elseif ($package['card_type'] >= 3) { // 日期卡
+                // 日期卡取最新的结束时间
+                if (empty($userProduct['expire_time']) ||
+                    strtotime($usageData['end_time']) > strtotime($userProduct['expire_time'])) {
+                    $updateData['expire_time'] = $usageData['end_time'];
+                }
+            }
+
+            Db::table('ba_user_product')
+                ->where('id', $userProduct['id'])
+                ->update($updateData);
+        } else {
+            // 创建新记录
+            $newData = [
+                'user_id' => $userId,
+                'product_id' => $orderItem['product_id'],
+                'package_price_id' => $orderItem['package_price_id'],
+                'remaining_points' => $usageData['remaining_points'] ?? 0,
+                'remaining_times' => $usageData['remaining_times'] ?? 0,
+                'expire_time' => $usageData['end_time'] ?? null,
+                'create_time' => date('Y-m-d H:i:s')
+            ];
+
+            Db::table('ba_user_product')->insert($newData);
+        }
+        } catch (\Throwable $e) {
+            Log::error('更新用户产品表失败: ' . $e->getMessage());
+            // 这里不抛出异常,因为这是辅助功能,主功能不受影响
+        }
+    }
+
+    /**
+     * 企业套餐分配时创建用户产品记录
+     */
+    public function createCompanyProductUsage($allocationRecord, $assignedToUserId,$card_type)
+    {
+
+        try {
+        // 从分配记录创建用户产品使用记录
+        $usageData = [
+            'user_id' => $assignedToUserId,
+            'order_id' => $allocationRecord['order_id'],
+            'order_item_id' => $allocationRecord['order_item_id'],
+            'product_id' => $allocationRecord['product_id'],
+            'package_price_id' => $allocationRecord['package_price_id'],
+            'package_type' => 2, // 企业套餐
+            'card_type' => $card_type,
+            'company_id' => $allocationRecord['company_id'],
+            'allocation_id' => $allocationRecord['id'],
+            'is_allocated' => 1,
+            'start_time' => date('Y-m-d H:i:s'),
+            'status' => 1,
+            'create_time' => date('Y-m-d H:i:s'),
+            'update_time' => date('Y-m-d H:i:s')
+        ];
+
+        // 设置有效期和剩余量
+        if ($card_type >= 3) { // 日期卡
+            $usageData['end_time'] = $this->calculateEndTime($card_type);
+        } elseif ($card_type == 1) { // 次卡
+            $usageData['remaining_times'] = $allocationRecord['remaining_times'];
+        } elseif ($card_type == 2) { // 积分卡
+            $usageData['remaining_points'] = $allocationRecord['remaining_points'];
+        }
+
+        return Db::table('ba_user_product_usage')->insertGetId($usageData);
+        } catch (\Throwable $e) {
+            Log::error('创建企业产品使用记录失败: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取我的产品列表(整合个人套餐和企业套餐)
+     */
+    public function getUserProducts($userId)
+    {
+        try {
+            // 1. 查询个人套餐(直接从使用记录表)
+            $personalProducts = Db::table('ba_user_product_usage')
+                ->alias('upu')
+                ->leftJoin('ba_product p', 'p.id = upu.product_id')
+                ->leftJoin('ba_package_price pp', 'pp.id = upu.package_price_id')
+                ->where('upu.user_id', $userId)
+                ->where('upu.package_type', 1) // 个人套餐
+                ->where('upu.status', 1) // 有效
+                ->field('upu.*, 
+                    p.name as product_name, p.type as product_type, p.link,
+                    pp.spec_name, pp.type as package_type, pp.card_type,pp.image')
+                ->select()
+                ->toArray();
+            // 2. 查询已分配给用户的企业套餐
+            $companyProducts = Db::table('ba_user_product_usage')
+                ->alias('upu')
+                ->leftJoin('ba_product p', 'p.id = upu.product_id')
+                ->leftJoin('ba_package_price pp', 'pp.id = upu.package_price_id')
+                ->leftJoin('ba_company_package_allocation cpa', 'cpa.id = upu.allocation_id')
+                ->where('upu.user_id', $userId)
+                ->where('upu.package_type', 2) // 企业套餐
+                ->where('upu.status', 1) // 有效
+                ->where('upu.is_allocated', 1) // 已分配
+                ->field('upu.*, 
+                    p.name as product_name, p.type as product_type, p.link,
+                    pp.spec_name, pp.type as package_type, pp.card_type,
+                    cpa.assigned_by_user_id,pp.image')
+                ->select()
+                ->toArray();
+            // 3. 合并结果
+            $allProducts = array_merge($personalProducts, $companyProducts);
+            // 4. 按产品ID和套餐规格ID分组,合并相同产品(只合并次卡和积分卡)
+            $groupedProducts = [];
+            foreach ($allProducts as $product) {
+                $key = $product['product_id'] . '_' . $product['package_price_id'];
+
+                // 日期卡不合并,每次分配单独显示
+                if ($product['card_type'] >= 3) {
+                    $groupedProducts[] = $this->formatProductItem($product, false);
+                    continue;
+                }
+
+                // 次卡和积分卡合并
+                if (!isset($groupedProducts[$key])) {
+                    $groupedProducts[$key] = $this->formatProductItem($product, true);
+                } else {
+                    // 累加剩余次数/积分
+                    $groupedProducts[$key]['remaining_times'] += $product['remaining_times'];
+                    $groupedProducts[$key]['remaining_points'] += $product['remaining_points'];
+                    $groupedProducts[$key]['total_quantity'] += 1;
+                }
+            }
+
+            // 5. 处理合并后的数组
+            $result = [];
+            foreach ($groupedProducts as $product) {
+                $result[] = $product;
+            }
+
+            return $result;
+
+        } catch (\Throwable $e) {
+            Log::error('获取用户产品列表失败: ' . $e->getMessage());
+            return [];
+        }
+    }
+
+    /**
+     * 格式化产品项
+     */
+    private function formatProductItem($product, $isMerged = false)
+    {
+        $item = [
+            'product_id' => $product['product_id'],
+            'product_name' => $product['product_name'],
+            'product_type' => $product['product_type'] ?? 1, // 默认1:网页
+            'product_link' => $product['link'] ?? '',
+            'image' => $product['image'] ?? '',
+            'package_spec' => $product['spec_name'],
+            'package_type' => $product['package_type'],
+            'package_type_text' => $product['package_type'] == 1 ? '个人套餐' : '企业套餐',
+            'card_type' => $product['card_type'],
+            'card_type_text' => $this->getCardTypeText($product['card_type']),
+            'total_quantity' => $isMerged ? 1 : 1,
+            'effective_end_time' => $product['end_time'] ?? null,
+            'remaining_times' => $product['remaining_times'] ?? 0,
+            'remaining_points' => $product['remaining_points'] ?? 0,
+            'display_info' => $this->getDisplayInfo($product),
+            'usage_id' => $product['id'],
+            'start_time' => $product['start_time'] ?? null,
+            'last_use_time' => $product['last_use_time'] ?? null,
+            'status' => $product['status'] ?? 1,
+            'buy_time' => $product['create_time']
+        ];
+        // 企业套餐特有字段
+        if ($product['package_type'] == 2) {
+            $item['is_allocated'] = $product['is_allocated'] ?? 0;
+            $item['company_id'] = $product['company_id'] ?? null;
+            $item['allocation_id'] = $product['allocation_id'] ?? null;
+            // 获取企业套餐的席位信息(如果有)
+            if ($item['company_id']) {
+
+                $seatsInfo = Db::name('company_package_seats')
+                    ->where('company_id', $item['company_id'])
+                    ->where('package_price_id', $product['package_price_id'])
+                    ->find();
+
+                if ($seatsInfo) {
+                    $item['total_seats'] = $seatsInfo['total_seats'];
+                    $item['assigned_seats'] = $seatsInfo['assigned_seats'];
+                    $item['available_seats'] = $seatsInfo['available_seats'];
+                } else {
+                    $item['total_seats'] = 0;
+                    $item['assigned_seats'] = 0;
+                    $item['available_seats'] = 0;
+                }
+            } else {
+                $item['total_seats'] = null;
+                $item['assigned_seats'] = null;
+                $item['available_seats'] = null;
+            }
+        } else {
+            // 个人套餐没有席位信息
+            $item['is_allocated'] = 0;
+            $item['company_id'] = null;
+            $item['allocation_id'] = null;
+            $item['total_seats'] = null;
+            $item['assigned_seats'] = null;
+            $item['available_seats'] = null;
+        }
+        return $item;
+    }
+
+    /**
+     * 获取显示信息
+     */
+    private function getDisplayInfo($product)
+    {
+        if ($product['card_type'] >= 3) { // 日期卡
+            $endTime = $product['end_time'] ?? null;
+            return $endTime ? '到期: ' . date('Y-m-d', strtotime($endTime)) : '永久有效';
+        } elseif ($product['card_type'] == 1) { // 次卡
+            $remainingTimes = $product['remaining_times'] ?? 0;
+            return '剩余次数: ' . $remainingTimes;
+        } elseif ($product['card_type'] == 2) { // 积分卡
+            $remainingPoints = $product['remaining_points'] ?? 0;
+            return '剩余积分: ' . $remainingPoints;
+        }
+        return '';
+    }
+
+    /**
+     * 获取卡类型文本
+     */
+    private function getCardTypeText($type)
+    {
+        $map = [
+            1 => '次卡',
+            2 => '积分卡',
+            3 => '日卡',
+            4 => '周卡',
+            5 => '季卡',
+            6 => '月卡',
+            7 => '年卡'
+        ];
+        return $map[$type] ?? '其他';
+    }
+
+    /**
+     * 计算到期时间
+     */
+    private function calculateEndTime($cardType)
+    {
+        $start = time();
+        switch ($cardType) {
+            case 3: // 日卡
+                return date('Y-m-d 23:59:59', strtotime('+1 day', $start));
+            case 4: // 周卡
+                return date('Y-m-d 23:59:59', strtotime('+7 days', $start));
+            case 5: // 季卡
+                return date('Y-m-d 23:59:59', strtotime('+90 days', $start));
+            case 6: // 月卡
+                return date('Y-m-d 23:59:59', strtotime('+30 days', $start));
+            case 7: // 年卡
+                return date('Y-m-d 23:59:59', strtotime('+365 days', $start));
+            default:
+                return null;
+        }
+    }
+}

+ 418 - 0
app/common/service/WechatPaymentService.php

@@ -0,0 +1,418 @@
+<?php
+
+namespace app\common\service;
+
+class WechatPaymentService extends BasePaymentService
+{
+    // 微信支付API地址
+    protected $apiUrl = 'https://api.mch.weixin.qq.com';
+
+    // 配置参数
+    protected $config = [];
+
+    /**
+     * 初始化微信支付配置
+     */
+    protected function initConfig()
+    {
+        $this->config = [
+            'appid' => $this->payConfig['app_id'],
+            'mch_id' => $this->payConfig['mch_id'],
+            'key' => $this->payConfig['key'],
+            'notify_url' => $this->payConfig['notify_url'] ?: url('/api/invitation/payment/wechatNotify', [], true, true)->build(),
+            'cert_path' => $this->getCertPath('cert'),
+            'key_path' => $this->getCertPath('key'),
+        ];
+
+        if (empty($this->config['appid']) || empty($this->config['mch_id']) || empty($this->config['key'])) {
+            throw new \Exception('微信支付配置不完整');
+        }
+    }
+
+    /**
+     * 获取证书路径
+     */
+    protected function getCertPath($type)
+    {
+        // 可以从数据库或文件系统获取证书
+        // 这里简化处理,实际需要根据业务获取
+        return '';
+    }
+
+    /**
+     * 生成支付二维码
+     */
+    public function createQrCode($orderNo, $amount, $subject, $body = '')
+    {
+        try {
+            // 构建请求参数
+            $params = [
+                'appid' => $this->config['appid'],
+                'mch_id' => $this->config['mch_id'],
+                'nonce_str' => $this->generateNonceStr(),
+                'body' => mb_substr($subject, 0, 127, 'UTF-8'), // 商品描述,128字节限制
+                'out_trade_no' => $orderNo,
+                'total_fee' => intval($amount * 100), // 单位:分
+                'spbill_create_ip' => request()->ip(),
+                'notify_url' => $this->config['notify_url'],
+                'trade_type' => 'NATIVE', // 扫码支付
+                'product_id' => $orderNo,
+            ];
+
+            // 生成签名
+            $params['sign'] = $this->generateSign($params);
+
+            // 调用微信统一下单接口
+            $result = $this->postXmlCurl($this->apiUrl . '/pay/unifiedorder', $this->arrayToXml($params));
+
+            // 解析结果
+            $response = $this->xmlToArray($result);
+            if ($response['return_code'] == 'SUCCESS' && $response['result_code'] == 'SUCCESS') {
+                return [
+                    'qr_code_url' => $response['code_url'],
+                    'qr_code_content' => $response['code_url'],
+                    'prepay_id' => $response['prepay_id'],
+                ];
+            } else {
+                $errorMsg = $response['return_msg'] ?? $response['err_code_des'] ?? '微信支付下单失败';
+                throw new \Exception($errorMsg);
+            }
+
+        } catch (\Throwable $e) {
+            $this->log('生成微信支付二维码失败', [
+                'order_no' => $orderNo,
+                'error' => $e->getMessage()
+            ], 'error');
+            throw $e;
+        }
+    }
+
+    /**
+     * 处理支付回调
+     */
+    public function handleNotify($input)
+    {
+        try {
+            // 解析XML数据
+            $data = $this->xmlToArray($input);
+
+            // 验证签名
+            if (!$this->verifySign($data, $data['sign'])) {
+                throw new \Exception('签名验证失败');
+            }
+
+            // 检查支付状态
+            if ($data['return_code'] != 'SUCCESS' || $data['result_code'] != 'SUCCESS') {
+                throw new \Exception('支付失败: ' . ($data['return_msg'] ?? '未知错误'));
+            }
+
+            return [
+                'order_no' => $data['out_trade_no'],
+                'trade_no' => $data['transaction_id'],
+                'pay_amount' => $data['total_fee'] / 100,
+                'pay_time' => $this->formatWxTime($data['time_end']),
+                'raw_data' => $data,
+            ];
+
+        } catch (\Throwable $e) {
+            $this->log('处理微信支付回调失败', [
+                'input' => $input,
+                'error' => $e->getMessage()
+            ], 'error');
+            throw $e;
+        }
+    }
+
+    /**
+     * 查询支付订单
+     */
+    public function queryOrder($orderNo)
+    {
+        try {
+            $params = [
+                'appid' => $this->config['appid'],
+                'mch_id' => $this->config['mch_id'],
+                'out_trade_no' => $orderNo,
+                'nonce_str' => $this->generateNonceStr(),
+            ];
+
+            $params['sign'] = $this->generateSign($params);
+
+            $result = $this->postXmlCurl($this->apiUrl . '/pay/orderquery', $this->arrayToXml($params));
+            $response = $this->xmlToArray($result);
+
+            if ($response['return_code'] == 'SUCCESS' && $response['result_code'] == 'SUCCESS') {
+                $status = $response['trade_state'];
+                $payStatus = 0; // 待支付
+
+                if ($status == 'SUCCESS') {
+                    $payStatus = 1; // 已支付
+                } elseif (in_array($status, ['CLOSED', 'REVOKED', 'PAYERROR', 'USERPAYING'])) {
+                    $payStatus = 2; // 支付失败
+                }
+
+                return [
+                    'order_no' => $response['out_trade_no'],
+                    'trade_no' => $response['transaction_id'],
+                    'pay_status' => $payStatus,
+                    'pay_amount' => $response['total_fee'] / 100,
+                    'pay_time' => isset($response['time_end']) ? $this->formatWxTime($response['time_end']) : null,
+                    'trade_state' => $status,
+                ];
+            }
+
+            throw new \Exception('查询订单失败: ' . ($response['return_msg'] ?? '未知错误'));
+
+        } catch (\Throwable $e) {
+            $this->log('查询微信支付订单失败', [
+                'order_no' => $orderNo,
+                'error' => $e->getMessage()
+            ], 'error');
+            throw $e;
+        }
+    }
+
+    /**
+     * 验证签名
+     */
+    protected function verifySign($data, $sign)
+    {
+        $localSign = $this->generateSign($data);
+        return $localSign === $sign;
+    }
+
+    /**
+     * 生成签名
+     */
+    protected function generateSign($data)
+    {
+        // 过滤空值和sign参数
+        $filterData = array_filter($data, function($value, $key) {
+            return $value !== '' && $value !== null && $key !== 'sign';
+        }, ARRAY_FILTER_USE_BOTH);
+
+        // 按参数名ASCII码从小到大排序
+        ksort($filterData);
+
+        // 拼接字符串
+        $string = '';
+        foreach ($filterData as $key => $value) {
+            $string .= $key . '=' . $value . '&';
+        }
+        $string .= 'key=' . $this->config['key'];
+
+        // MD5加密并转为大写
+        return strtoupper(md5($string));
+    }
+
+    /**
+     * 生成随机字符串
+     */
+    protected function generateNonceStr($length = 32)
+    {
+        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+        $str = '';
+        for ($i = 0; $i < $length; $i++) {
+            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
+        }
+        return $str;
+    }
+
+    /**
+     * 格式化微信支付时间
+     */
+    protected function formatWxTime($timeStr)
+    {
+        if (empty($timeStr)) return null;
+
+        // 微信时间格式:yyyyMMddHHmmss
+        $year = substr($timeStr, 0, 4);
+        $month = substr($timeStr, 4, 2);
+        $day = substr($timeStr, 6, 2);
+        $hour = substr($timeStr, 8, 2);
+        $minute = substr($timeStr, 10, 2);
+        $second = substr($timeStr, 12, 2);
+
+        return "{$year}-{$month}-{$day} {$hour}:{$minute}:{$second}";
+    }
+
+    /**
+     * 发送XML请求
+     */
+    private function postXmlCurl($url, $xml, $useCert = false, $timeout = 30)
+    {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+
+        // 设置SSL
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+
+        // 如果需要证书
+        if ($useCert) {
+            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
+            curl_setopt($ch, CURLOPT_SSLCERT, $this->config['cert_path']);
+            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
+            curl_setopt($ch, CURLOPT_SSLKEY, $this->config['key_path']);
+        }
+
+        $response = curl_exec($ch);
+
+        if (curl_errno($ch)) {
+            curl_close($ch);
+            throw new \Exception('CURL错误: ' . curl_error($ch));
+        }
+
+        curl_close($ch);
+        return $response;
+    }
+
+    /**
+     * 数组转XML
+     */
+    private function arrayToXml($arr)
+    {
+        $xml = '<xml>';
+        foreach ($arr as $key => $val) {
+            if (is_numeric($val)) {
+                $xml .= "<{$key}>{$val}</{$key}>";
+            } else {
+                $xml .= "<{$key}><![CDATA[{$val}]]></{$key}>";
+            }
+        }
+        $xml .= '</xml>';
+        return $xml;
+    }
+
+    /**
+     * XML转数组(兼容PHP 8.0+)
+     */
+    private function xmlToArray($xml)
+    {
+        if (empty($xml)) {
+            return [];
+        }
+
+        try {
+            // 方法1:使用DOMDocument(推荐,兼容性好)
+            $dom = new \DOMDocument();
+
+            // 针对PHP版本的兼容处理
+            if (version_compare(PHP_VERSION, '8.0.0', '>=')) {
+                // PHP 8.0+ 不需要禁用实体加载器,直接加载
+                $dom->loadXML($xml, LIBXML_NOENT | LIBXML_NOCDATA);
+            } else {
+                // PHP 8.0以下需要禁用实体加载器
+                $previous = libxml_disable_entity_loader(true);
+                $dom->loadXML($xml, LIBXML_NOENT | LIBXML_NOCDATA);
+                libxml_disable_entity_loader($previous);
+            }
+
+            // 转换为数组
+            $data = $this->domToArray($dom->documentElement);
+
+            return $data ?: [];
+
+        } catch (\Exception $e) {
+            $this->log('XML解析失败', [
+                'xml' => substr($xml, 0, 500), // 只记录前500字符
+                'error' => $e->getMessage()
+            ], 'error');
+
+            // 备用解析方法
+            return $this->fallbackXmlToArray($xml);
+        }
+    }
+
+    /**
+     * DOMElement转换为数组
+     */
+    private function domToArray($node)
+    {
+        $output = [];
+
+        switch ($node->nodeType) {
+            case XML_CDATA_SECTION_NODE:
+            case XML_TEXT_NODE:
+                $output = trim($node->textContent);
+                break;
+
+            case XML_ELEMENT_NODE:
+                for ($i = 0; $i < $node->childNodes->length; $i++) {
+                    $child = $node->childNodes->item($i);
+                    $value = $this->domToArray($child);
+
+                    if (isset($child->tagName)) {
+                        $tag = $child->tagName;
+
+                        if (!isset($output[$tag])) {
+                            $output[$tag] = [];
+                        }
+
+                        if (is_array($value) && isset($value['@value'])) {
+                            $output[$tag][] = $value['@value'];
+                        } else {
+                            $output[$tag][] = $value;
+                        }
+
+                        // 如果是单一元素,不要数组形式
+                        if (count($output[$tag]) === 1 && !is_array($output[$tag][0])) {
+                            $output[$tag] = $output[$tag][0];
+                        }
+                    } elseif ($value !== '') {
+                        $output = $value;
+                    }
+                }
+
+                if ($node->attributes->length) {
+                    $attributes = [];
+                    foreach ($node->attributes as $attrName => $attrNode) {
+                        $attributes[$attrName] = (string)$attrNode->value;
+                    }
+                    if (!empty($output)) {
+                        $output['@attributes'] = $attributes;
+                    } else {
+                        $output = ['@value' => '', '@attributes' => $attributes];
+                    }
+                }
+                break;
+        }
+
+        // 如果是数组且只有一个元素,直接返回那个元素
+        if (is_array($output) && count($output) == 1 && isset($output[0])) {
+            $output = $output[0];
+        }
+
+        return $output;
+    }
+
+    /**
+     * 备用XML解析方法(原来的方法,带版本检查)
+     */
+    private function fallbackXmlToArray($xml)
+    {
+        try {
+            // 版本检查,PHP 8.0以上不调用已废弃函数
+            if (version_compare(PHP_VERSION, '8.0.0', '<')) {
+                libxml_disable_entity_loader(true);
+            }
+
+            $data = json_decode(json_encode(simplexml_load_string(
+                $xml,
+                'SimpleXMLElement',
+                LIBXML_NOCDATA | LIBXML_NOENT
+            )), true);
+
+            return $data ?: [];
+        } catch (\Exception $e) {
+            $this->log('备用XML解析失败', [
+                'error' => $e->getMessage()
+            ], 'error');
+            return [];
+        }
+    }
+}