| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744 |
- <?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;
- }
- }
|