From 88f1002f9c60ecfb242ac02abbab810f0390e451 Mon Sep 17 00:00:00 2001 From: wanghousheng Date: Sun, 28 Jan 2024 00:56:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/controller/Server.php | 198 ++++++++++++-- app/api/model/Server/ServerOrder.php | 115 ++++++-- app/api/model/UserCoupon.php | 37 ++- app/api/service/Notify.php | 16 +- app/api/service/Server/PaySuccess.php | 326 ++++++++++++++++++++++ app/api/service/Server/ServerPayment.php | 327 +++++++++++++++++++++++ app/api/service/User.php | 10 +- app/common/model/UserCoupon.php | 6 +- app/common/service/server/Order.php | 274 ++++++++++++++++++- 9 files changed, 1245 insertions(+), 64 deletions(-) create mode 100644 app/api/service/Server/PaySuccess.php create mode 100644 app/api/service/Server/ServerPayment.php diff --git a/app/api/controller/Server.php b/app/api/controller/Server.php index b0354358..5339a2a1 100644 --- a/app/api/controller/Server.php +++ b/app/api/controller/Server.php @@ -7,7 +7,9 @@ namespace app\api\controller; use app\api\model\Server\ServerCategory; use app\api\model\Server\ServerOrder; +use app\api\service\Server\ServerPayment; use app\common\enum\ServerEnum; +use app\common\service\server\Order as ServerServiceOrder; use cores\exception\BaseException; use think\db\exception\DataNotFoundException; use think\db\exception\DbException; @@ -94,50 +96,198 @@ class Server extends Controller $where['order_status'] = $order_status; } $model = new ServerOrder($where); - $list = $model->userOrders($where); + $list = $model->orderList($where); $data['list'] = $list->items(); $data['total'] = $list->total(); if (!$list->isEmpty()) { - if (!$list->isEmpty()) { - foreach ($data['list'] as $key => $value) { - unset($data['list'][$key]['image']); + foreach ($data['list'] as $key => $value) { + $data['list'][$key]['is_cancel'] = ServerServiceOrder::checkCancel($value); + $data['list'][$key]['is_dispatch'] = ServerServiceOrder::checkDispatch($value); + $data['list'][$key]['is_pay'] = ServerServiceOrder::checkPay($value); + unset($data['list'][$key]['image']); + if (!empty($data['list'][$key]['dealer'])) { unset($data['list'][$key]['dealer']); } + if (!empty($data['list'][$key]['user'])) { + unset($data['list'][$key]['user']); + } } } return $this->renderSuccess($data); } /** - * @notes: + * @notes:确认订单 * @return Json * @throws BaseException * @throws DbException * @author: wanghousheng */ - public function dealerOrderList(): Json + public function checkOrder(): Json { - $order_no = $this->request->post('order_no'); - $order_status = intval($this->request->post('order_status')); - $where = []; - if (!empty($order_no)) { - $where['order_no'] = $order_no; + $serverId = intval($this->request->post('server_id')); + $couponId = intval($this->request->post('coupon_id')); + if (!$serverId) { + return $this->renderError('非法请求'); } - if ($order_status) { - $where['order_status'] = $order_status; + $orderService = new ServerServiceOrder(); + $data = $orderService->onCheck($serverId, $couponId); + return $this->renderSuccess($data); + } + + /** + * @notes:生成订单 + * @return Json + * @throws BaseException + * @throws DbException + * @author: wanghousheng + */ + public function createOrder(): Json + { + $serverId = intval($this->request->post('server_id')); + $couponId = intval($this->request->post('coupon_id')); + $remake = $this->request->post('remake'); + if (!$serverId) { + return $this->renderError('非法请求'); } - $model = new ServerOrder($where); - $list = $model->dealerOrders($where); - $data['list'] = $list->items(); - $data['total'] = $list->total(); - if (!$list->isEmpty()) { - if (!$list->isEmpty()) { - foreach ($data['list'] as $key => $value) { - unset($data['list'][$key]['image']); - unset($data['list'][$key]['dealer']); - } - } + $orderService = new ServerServiceOrder(); + $result = $orderService->createOrder($serverId, $couponId, $remake); + if ($result) { + return $this->renderSuccess($result); + } + return $this->renderError('创建订单失败'); + } + + /** + * @notes:取消订单 + * @return Json + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function cancelOrder(): Json + { + $orderId = intval($this->request->post('order_id')); + if (!$orderId) { + return $this->renderError('非法请求'); + } + if (ServerServiceOrder::userCancelOrder($orderId)) { + return $this->renderSuccess('取消成功'); + } + return $this->renderError('操作失败'); + } + + /** + * @notes:派单人员列表 + * @return Json + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function getEngineer(): Json + { + $list = ServerServiceOrder::getEngineer(); + return $this->renderSuccess(compact('list')); + } + + /** + * @notes:派单 + * @return Json + * @throws BaseException + * @author: wanghousheng + */ + public function dispatchOrders(): Json + { + $orderId = intval($this->request->post('order_id')); + if (!$orderId) { + return $this->renderError('非法请求'); + } + $dealerId = intval($this->request->post('dealer_id')); + if (!$dealerId) { + return $this->renderError('分配人员不能为空'); + } + if (ServerServiceOrder::userDispatchOrders($orderId, $dealerId)) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError('操作失败'); + } + + /** + * @notes:获取支付订单的信息 + * @return Json + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function orderInfo(): Json + { + $orderId = intval($this->request->post('order_id')); + if (!$orderId) { + return $this->renderError('非法请求'); } + $client = $this->request->post('client'); + if (!$client) { + return $this->renderError('客户端不能为空'); + } + $paymentService = new ServerPayment(); + $data = $paymentService->setOrderId($orderId)->setClient($client)->orderInfo(); return $this->renderSuccess($data); } + + /** + * @notes:确认订单支付事件 + * @return array + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function orderPay(): array + { + $orderId = intval($this->request->post('order_id')); + if (!$orderId) { + return $this->renderError('非法请求'); + } + $method = $this->request->post('method'); + if (!$method) { + return $this->renderError('支付方式不能为空'); + } + $client = $this->request->post('client'); + if (!$client) { + return $this->renderError('客户端不能为空'); + } + $paymentService = new ServerPayment(); + $data = $paymentService->setOrderId($orderId) + ->setMethod($method) + ->setClient($client) + ->orderPay(); + return $this->renderSuccess($data, $paymentService->getMessage() ?: '下单成功'); + } + + /** + * @notes:交易查询 + * @param string $outTradeNo + * @param string $method + * @param string $client + * @return Json + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function tradeQuery(string $outTradeNo, string $method, string $client): Json + { + $paymentService = new ServerPayment; + $result = $paymentService->setMethod($method)->setClient($client)->tradeQuery($outTradeNo); + $message = $result ? '恭喜您,订单已付款成功' : ($paymentService->getError() ?: '很抱歉,订单未支付,请重新发起'); + return $this->renderSuccess(['isPay' => $result], $message); + } } \ No newline at end of file diff --git a/app/api/model/Server/ServerOrder.php b/app/api/model/Server/ServerOrder.php index 547ea119..16c41169 100644 --- a/app/api/model/Server/ServerOrder.php +++ b/app/api/model/Server/ServerOrder.php @@ -2,11 +2,15 @@ namespace app\api\model\Server; - +use app\api\model\Setting as SettingModel; use app\api\service\User as UserService; +use app\common\enum\order\PayStatus; +use app\common\enum\ServerEnum; use app\common\model\server\Order; use cores\exception\BaseException; +use think\db\exception\DataNotFoundException; use think\db\exception\DbException; +use think\db\exception\ModelNotFoundException; use think\Paginator; class ServerOrder extends Order @@ -33,38 +37,103 @@ class ServerOrder extends Order * @throws DbException * @author: wanghousheng */ - public function userOrders($where, int $listRows = 15): Paginator + public function orderList($where, int $listRows = 15): Paginator { // 当前用户ID $userId = UserService::getCurrentLoginUserId(); - // 查询列表数据 - return $this->with(['image', 'dealer']) - ->where($where) - ->where('user_id', '=', $userId) - ->where('is_delete', '=', 0) - ->order(['create_time' => 'desc']) - ->paginate($listRows); + //判断当前用户身份 + if (UserService::isDealerEngineer()) { + //分销商工程师 + return $this->with(['image', 'user']) + ->where($where) + ->where('dealer_id', '=', $userId) + ->where('is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->paginate($listRows); + } elseif (UserService::isStore()) { + // 店主 + return $this->with(['image', 'user', 'dealer']) + ->where($where) + ->where('is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->paginate($listRows); + } else { + return $this->with(['image', 'dealer']) + ->where($where) + ->where('user_id', '=', $userId) + ->where('is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->paginate($listRows); + } + } /** - * @notes:分销员订单列表 - * @param $where - * @param int $listRows - * @return Paginator + * 获取用户订单详情(仅订单记录) + * @param int $orderId + * @param array $with + * @param bool $onlyCurrentUser 只查询当前登录用户的记录 + * @return array|Order|null * @throws BaseException + */ + public static function getDetail(int $orderId, array $with = [], bool $onlyCurrentUser = true) + { + // 查询条件 + $where = ['order_id' => $orderId]; + $onlyCurrentUser && $where['user_id'] = UserService::getCurrentLoginUserId(); + // 查询订单记录 + $order = static::detail($where, $with); + empty($order) && throwError('订单不存在'); + return $order; + } + + /** + * 待支付订单详情 + * @param string $orderNo 订单号 + * @return null|static + */ + public static function getPayDetail(string $orderNo): ?Order + { + $where = ['order_no' => $orderNo, 'is_delete' => 0]; + return self::detail($where, ['user']); + } + + /** + * @notes:获取未支付的订单详情(用于订单支付) + * @param int $orderId 订单ID + * @return array + * @throws BaseException + * @throws DataNotFoundException * @throws DbException + * @throws ModelNotFoundException * @author: wanghousheng */ - public function dealerOrders($where, int $listRows = 15): Paginator + public static function getUnpaidOrderDetail(int $orderId): array { - // 当前用户ID - $userId = UserService::getCurrentLoginUserId(); - // 查询列表数据 - return $this->with(['image', 'user']) - ->where($where) - ->where('dealer_id', '=', $userId) - ->where('is_delete', '=', 0) - ->order(['create_time' => 'desc']) - ->paginate($listRows); + // 获取订单详情 + $orderInfo = static::getDetail($orderId); + // 验证订单状态 + if ($orderInfo['order_status'] != ServerEnum::APPLYPAY || $orderInfo['pay_status'] = PayStatus::SUCCESS) { + throwError('当前订单状态不允许支付'); + } + // 未支付订单的过期时间 + $orderCloseTime = SettingModel::getOrderCloseTime() * 60 * 60; + // 订单超时截止时间 + $expirationTime = $orderInfo->getData('create_time') + $orderCloseTime; + if ($orderCloseTime > 0 && $expirationTime <= time()) { + throwError('当前订单支付已超时,请重新下单'); + } + // 仅返回需要的数据 + return [ + 'orderId' => $orderInfo['order_id'], + 'order_no' => $orderInfo['order_no'], + 'pay_price' => $orderInfo['pay_price'], + 'pay_status' => $orderInfo['pay_status'], + 'order_status' => $orderInfo['order_status'], + 'create_time' => $orderInfo['create_time'], + 'showExpiration' => $orderCloseTime > 0, + 'expirationTime' => format_time($expirationTime), + ]; } + } \ No newline at end of file diff --git a/app/api/model/UserCoupon.php b/app/api/model/UserCoupon.php index a5668dd0..51616e57 100644 --- a/app/api/model/UserCoupon.php +++ b/app/api/model/UserCoupon.php @@ -12,12 +12,13 @@ declare (strict_types=1); namespace app\api\model; -use app\api\service\User as UserService; use app\api\model\Coupon as CouponModel; -use app\common\model\UserCoupon as UserCouponModel; -use app\common\enum\coupon\CouponType as CouponTypeEnum; +use app\api\service\User as UserService; use app\common\enum\coupon\ApplyRange as ApplyRangeEnum; +use app\common\enum\coupon\CouponType as CouponTypeEnum; +use app\common\enum\coupon\TypeCase; use app\common\library\helper; +use app\common\model\UserCoupon as UserCouponModel; use cores\exception\BaseException; /** @@ -169,14 +170,14 @@ class UserCoupon extends UserCouponModel * @return array * @throws \think\db\exception\DbException */ - public static function getUserCouponList(int $userId, float $orderPayPrice): array + public static function getUserCouponList(int $userId, float $orderPayPrice, $couponType = TypeCase::SHOP): array { // 判断订单商品总金额不能为1分 if ($orderPayPrice <= 0.01) { return []; } // 获取用户可用的优惠券列表 - $list = (new static)->getList($userId, ['dataType' => 'isUsable', 'amount' => $orderPayPrice]); + $list = (new static)->getList($userId, ['dataType' => 'isUsable', 'amount' => $orderPayPrice, 'coupon_case' => $couponType]); $data = $list->isEmpty() ? [] : $list->toArray()['data']; foreach ($data as &$item) { // 计算最大能折扣的金额 @@ -217,4 +218,30 @@ class UserCoupon extends UserCouponModel } return $couponList; } + + /** + * @notes:判断当前优惠券是否满足服务订单使用条件 + * @param array $couponList + * @param array $serverIds + * @return array + * @author: wanghousheng + */ + public static function couponListApplyRangeServer(array $couponList, array $serverIds): array + { + // 名词解释(is_apply):允许用于抵扣当前订单 + foreach ($couponList as &$item) { + if ($item['apply_range'] == ApplyRangeEnum::ALL) { + // 1. 全部服务 + $item['is_apply'] = true; + } elseif ($item['apply_range'] == ApplyRangeEnum::SOME) { + // 2. 指定服务, 判断订单是否存在可用 + if (!empty($item['apply_range_config']['applyServerIds'])) { + $applyServerIds = array_intersect($item['apply_range_config']['applyServerIds'], $serverIds); + $item['is_apply'] = !empty($applyServerIds); + } + } + !$item['is_apply'] && $item['not_apply_info'] = '该优惠券不支持当前服务'; + } + return $couponList; + } } diff --git a/app/api/service/Notify.php b/app/api/service/Notify.php index 6ddf0237..8a77e6fd 100644 --- a/app/api/service/Notify.php +++ b/app/api/service/Notify.php @@ -13,16 +13,17 @@ declare (strict_types=1); namespace app\api\service; use app\api\model\Payment as PaymentModel; -use app\api\model\PaymentTrade as PaymentTradeModel; use app\api\model\PaymentTemplate as PaymentTemplateModel; +use app\api\model\PaymentTrade as PaymentTradeModel; use app\api\service\order\PaySuccess as OrderPaySuccesService; use app\api\service\recharge\PaySuccess as RechargePaySuccesService; +use app\api\service\Server\PaySuccess; use app\common\enum\OrderType as OrderTypeEnum; use app\common\enum\payment\Method as PaymentMethodEnum; -use app\common\library\Log; use app\common\library\helper; -use app\common\library\payment\gateway\Driver; +use app\common\library\Log; use app\common\library\payment\Facade as PaymentFacade; +use app\common\library\payment\gateway\Driver; use app\common\library\payment\gateway\driver\wechat\V3 as WechatPaymentV3; use cores\exception\BaseException; @@ -154,6 +155,15 @@ class Notify ->setPaymentData($paymentData) ->handle(); } + // 订单支付成功业务处理 (服务订单) + if ($tradeInfo['order_type'] == OrderTypeEnum::SERVER) { + $service = new PaySuccess(); + $service->setOrderNo($tradeInfo['order_no']) + ->setMethod($tradeInfo['pay_method']) + ->setTradeId($tradeInfo['trade_id']) + ->setPaymentData($paymentData) + ->handle(); + } Log::append('Notify-orderPaySucces', ['message' => '订单支付成功']); } catch (\Throwable $e) { // 记录错误日志 diff --git a/app/api/service/Server/PaySuccess.php b/app/api/service/Server/PaySuccess.php new file mode 100644 index 00000000..01cae855 --- /dev/null +++ b/app/api/service/Server/PaySuccess.php @@ -0,0 +1,326 @@ +orderId = $orderId; + return $this; + } + + /** + * 设置当前的订单号 + * @param string $orderNo + * @return $this + */ + public function setOrderNo(string $orderNo): PaySuccess + { + $this->orderNo = $orderNo; + return $this; + } + + /** + * 设置订单支付方式 + * @param string $method + * @return $this + */ + public function setMethod(string $method): PaySuccess + { + $this->method = $method; + return $this; + } + + /** + * 第三方支付交易记录ID + * @param int|null $tradeId + * @return $this + */ + public function setTradeId(?int $tradeId = null): PaySuccess + { + $this->tradeId = $tradeId; + return $this; + } + + /** + * 第三方支付成功返回的数据 + * @param array $paymentData + * @return $this + */ + public function setPaymentData(array $paymentData): PaySuccess + { + $this->paymentData = $paymentData; + return $this; + } + + /** + * 订单支付成功业务处理 + * @return bool + * @throws BaseException + */ + public function handle(): bool + { + // 验证当前参数是否合法 + $this->verifyParameters(); + // 当前订单开启并发锁 + $this->lockUp(); + // 验证当前订单是否允许支付 + if ($this->checkOrderStatusOnPay()) { + // 更新订单状态为已付款 + $this->updatePayStatus(); + // 订单支付成功事件 (处理订单来源相关业务) + Event::trigger('OrderPaySuccess', [ + 'order' => $this->getOrderInfo(), + 'orderType' => OrderTypeEnum::SERVER + ]); + } + // 当前订单解除并发锁 + $this->unLock(); + return true; + } + + /** + * 验证当前参数是否合法 + * @throws BaseException + */ + private function verifyParameters() + { + if (empty($this->orderNo)) { + throwError('orderNo not found'); + } + if (empty($this->method)) { + throwError('method not found'); + } + if ($this->tradeId) { + empty($this->paymentData) && throwError('PaymentData not found'); + !isset($this->paymentData['tradeNo']) && throwError('PaymentData not found'); + } + // 记录日志 + Log::append('PaySuccess', [ + 'orderNo' => $this->orderNo, 'method' => $this->method, + 'tradeId' => $this->tradeId, 'paymentData' => $this->paymentData + ]); + } + + /** + * 订单模型 + * @return OrderModel|null + * @throws BaseException + */ + private function orderModel(): ?OrderModel + { + return $this->getOrderInfo(); + } + + /** + * 订单已付款事件 + * @return void + * @throws BaseException + */ + private function updatePayStatus(): void + { + // 记录日志 + Log::append('PaySuccess --updatePayStatus', ['title' => '订单已付款事件']); + // 当前订单信息 + $orderInfo = $this->getOrderInfo(); + // 事务处理 + $this->orderModel()->transaction(function () use ($orderInfo) { + // 更新订单状态 + $this->updateOrderStatus(); + // 累积用户总消费金额 + UserModel::setIncPayMoney($orderInfo['user_id'], (float)$orderInfo['pay_price']); + // 记录订单支付信息 + $this->updatePayInfo(); + }); + } + + /** + * 记录订单支付的信息 + * @throws BaseException + */ + private function updatePayInfo() + { + // 当前订单信息 + $orderInfo = $this->getOrderInfo(); + // 余额支付 + if ($this->method == PaymentMethodEnum::BALANCE) { + // 更新用户余额 + UserModel::setDecBalance((int)$orderInfo['user_id'], (float)$orderInfo['pay_price']); + // 新增余额变动记录 + BalanceLogModel::add(SceneEnum::CONSUME, [ + 'user_id' => (int)$orderInfo['user_id'], + 'money' => -$orderInfo['pay_price'], + ], ['order_no' => $orderInfo['order_no']]); + } + // 将第三方交易记录更新为已支付状态 + if (in_array($this->method, [PaymentMethodEnum::WECHAT, PaymentMethodEnum::ALIPAY])) { + $this->updateTradeRecord(); + } + } + + /** + * 将第三方交易记录更新为已支付状态 + */ + private function updateTradeRecord() + { + if ($this->tradeId && !empty($this->paymentData)) { + PaymentTradeModel::updateToPaySuccess($this->tradeId, $this->paymentData['tradeNo']); + } + } + + /** + * 更新订单状态 + * @throws BaseException + */ + private function updateOrderStatus(): void + { + // 更新订单状态 + $this->orderModel()->save([ + 'pay_method' => $this->method, + 'pay_status' => PayStatusEnum::SUCCESS, + 'pay_time' => time(), + 'trade_id' => $this->tradeId ?: 0, + ]); + } + + /** + * 获取买家用户信息 + * @return UserModel|array|null + * @throws BaseException + */ + private function getUserInfo() + { + if (empty($this->userInfo)) { + $this->userInfo = UserModel::detail($this->getOrderInfo()['user_id']); + } + if (empty($this->userInfo)) { + throwError('未找到买家用户信息'); + } + return $this->userInfo; + } + + /** + * 验证当前订单是否允许支付 + * @return bool + * @throws BaseException + */ + private function checkOrderStatusOnPay(): bool + { + // 当前订单信息 + $orderInfo = $this->getOrderInfo(); + // 验证余额支付时用户余额是否满足 + if ($this->method == PaymentMethodEnum::BALANCE) { + if ($this->getUserInfo()['balance'] < $orderInfo['pay_price']) { + throwError('账户余额不足,无法使用余额支付'); + } + } + // 检查订单状态是否为已支付 + if ($orderInfo['pay_status'] == PayStatusEnum::SUCCESS) { + $this->onOrderPaid(); + return false; + } + return true; + } + + /** + * 处理订单已支付的情况 + * @throws BaseException + */ + private function onOrderPaid() + { + // 记录日志 + Log::append('PaySuccess --onOrderPaid', ['title' => '处理订单已支付的情况']); + // 当前订单信息 + $orderInfo = $this->getOrderInfo(); + // 余额支付直接返回错误信息 + if ($this->method == PaymentMethodEnum::BALANCE) { + throwError('当前订单已支付,无需重复支付'); + } + // 第三方支付判断是否为重复下单 (因异步回调可能存在网络延迟的原因,在并发的情况下会出现同时付款两次,这里需要容错) + // 如果订单记录中已存在tradeId并且和当前支付的tradeId不一致, 那么判断为重复的订单, 需进行退款处理 + if ($this->tradeId > 0 && $orderInfo['trade_id'] != $this->tradeId) { + // 执行原路退款 + throwError('当前订单异常'); + } + } + + + /** + * 获取当前订单的详情信息 + * @return OrderModel|null + * @throws BaseException + */ + private function getOrderInfo(): ?OrderModel + { + // 获取订单详情 (待支付状态) + if (empty($this->orderInfo)) { + $this->orderInfo = ServerOrder::getPayDetail($this->orderNo); + } + // 判断订单是否存在 + if (empty($this->orderInfo)) { + throwError('未找到该订单信息'); + } + return $this->orderInfo; + } + + /** + * 订单锁:防止并发导致重复支付 + * @throws BaseException + */ + private function lockUp() + { + $orderInfo = $this->getOrderInfo(); + Lock::lockUp("OrderPaySuccess_{$orderInfo['order_id']}"); + } + + /** + * 订单锁:防止并发导致重复支付 + * @throws BaseException + */ + private function unLock() + { + $orderInfo = $this->getOrderInfo(); + Lock::unLock("OrderPaySuccess_{$orderInfo['order_id']}"); + } +} \ No newline at end of file diff --git a/app/api/service/Server/ServerPayment.php b/app/api/service/Server/ServerPayment.php new file mode 100644 index 00000000..760a7b50 --- /dev/null +++ b/app/api/service/Server/ServerPayment.php @@ -0,0 +1,327 @@ +orderId = $orderId; + return $this; + } + + /** + * 设置当前支付方式 + * @param string $method 支付方式 + * @return $this + */ + public function setMethod(string $method): ServerPayment + { + $this->method = $method; + return $this; + } + + /** + * 设置下单的客户端 + * @param string $client 客户端 + * @return $this + */ + public function setClient(string $client): ServerPayment + { + $this->client = $client; + return $this; + } + + /** + * 返回消息提示 + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @notes:获取支付订单的信息 + * @return array + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function orderInfo(): array + { + // 当期用户信息 + $userInfo = UserService::getCurrentLoginUser(true); + // 根据指定客户端获取可用的支付方式 + $PaymentModel = new PaymentModel; + $methods = $PaymentModel->getMethodsByClient($this->client); + // 获取结算订单信息 + $OrderModel = new ServerOrder(); + $orderInfo = $OrderModel->getUnpaidOrderDetail($this->orderId); + return [ + 'order' => $orderInfo, + 'personal' => $userInfo, + 'paymentMethods' => $methods + ]; + } + + /** + * @notes:确认订单支付事件 + * @param array $extra 附加数据 + * @return array + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function orderPay(array $extra = []): array + { + // 获取订单信息 + $this->orderInfo = ServerOrder::getDetail($this->orderId); + // 订单支付事件 + $this->orderPayEvent(); + // 构建第三方支付请求的参数 + $payment = $this->unifiedorder($extra); + // 记录第三方交易信息 + $this->recordPaymentTrade($payment); + // 返回结果 + return compact('payment'); + } + + /** + * 订单支付事件 + * @return void + * @throws BaseException + * @author: wanghousheng + */ + private function orderPayEvent(): void + { + // 余额支付 + if ($this->method == PaymentMethodEnum::BALANCE) { + $this->orderPaySuccess($this->orderInfo['order_no']); + } + } + + /** + * 获取支付方式的配置信息 + * @return mixed + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + private function getPaymentConfig() + { + $PaymentModel = new PaymentModel; + $templateInfo = $PaymentModel->getPaymentInfo($this->method, $this->client, $this->getStoreId()); + return $templateInfo['template']['config'][$this->method]; + } + + /** + * 获取支付宝端的用户buyerId(仅支付宝小程序端) + * @return null + * @throws BaseException + * @author: wanghousheng + */ + private function getAlipayBuyerId() + { + if ($this->client == ClientEnum::MP_ALIPAY) { + // 当前登录用户信息 + $useInfo = UserService::getCurrentLoginUser(true); + if (!$useInfo['currentOauth'] || empty($useInfo['currentOauth']['oauth_id'])) { + throwError('很抱歉,您当前不存在buyerId 无法发起支付宝支付'); + } + // 当前第三方用户标识 + return $useInfo['currentOauth']['oauth_id']; + } + return null; + } + + /** + * 整理下单接口所需的附加数据 + * @param array $extra + * @return array + * @throws BaseException + * @author: wanghousheng + */ + private function extraAsUnify(array $extra = []): array + { + // 微信支付时需要的附加数据 + if ($this->method === PaymentMethodEnum::WECHAT) { + // 微信小程序端和微信公众号端需要openid + if (in_array($this->client, [ClientEnum::WXOFFICIAL, ClientEnum::MP_WEIXIN])) { + $extra['openid'] = $this->getWechatOpenid(); + } + } + // 支付宝支付时需要的附加数据 + if ($this->method === PaymentMethodEnum::ALIPAY) { + // 支付宝小程序端需要buyerId + if ($this->client == ClientEnum::MP_ALIPAY) { + $extra['buyerId'] = $this->getAlipayBuyerId(); + } + } + return $extra; + } + + /** + * 获取微信端的用户openid(仅微信小程序和微信公众号) + * @return null + * @throws BaseException + * @author: wanghousheng + */ + private function getWechatOpenid() + { + if (in_array($this->client, [ClientEnum::MP_WEIXIN, ClientEnum::WXOFFICIAL])) { + // 当前登录用户信息 + $useInfo = UserService::getCurrentLoginUser(true); + if (!$useInfo['currentOauth'] || empty($useInfo['currentOauth']['oauth_id'])) { + throwError('很抱歉,您当前不存在openid 无法发起微信支付'); + } + // 当前第三方用户标识 + return $useInfo['currentOauth']['oauth_id']; + } + return null; + } + + /** + * 查询订单是否支付成功 (仅限第三方支付订单) + * @param string $outTradeNo 商户订单号 + * @return bool + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public function tradeQuery(string $outTradeNo): bool + { + // 判断支付方式是否合法 + if (!in_array($this->method, [PaymentMethodEnum::WECHAT, PaymentMethodEnum::ALIPAY])) { + return false; + } + // 获取支付方式的配置信息 + $options = $this->getPaymentConfig(); + // 构建支付模块 + $Payment = PaymentFacade::store($this->method)->setOptions($options, $this->client); + // 执行第三方支付查询API + $result = $Payment->tradeQuery($outTradeNo); + // 订单支付成功事件 + if (!empty($result) && $result['paySuccess']) { + // 获取第三方交易记录信息 + $tradeInfo = PaymentTradeModel::detailByOutTradeNo($outTradeNo); + // 订单支付成功事件 + $this->orderPaySuccess($tradeInfo['order_no'], $tradeInfo['trade_id'], $result); + } + // 返回订单状态 + return $result ? $result['paySuccess'] : false; + } + + /** + * 构建第三方支付请求的参数 + * @param array $extra 附加数据 + * @return array + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + private function unifiedorder(array $extra = []): array + { + // 生成第三方交易订单号 (并非主订单号) + $outTradeNo = OrderService::createOrderNo(); + // 获取支付方式的配置信息 + $options = $this->getPaymentConfig(); + // 整理下单接口所需的附加数据 + $extra = $this->extraAsUnify($extra); + // 构建支付模块 + $Payment = PaymentFacade::store($this->method)->setOptions($options, $this->client); + // 执行第三方支付下单API + if (!$Payment->unify($outTradeNo, (string)$this->orderInfo['pay_price'], $extra)) { + throwError('第三方支付下单API调用失败'); + } + // 返回客户端需要的支付参数 + return $Payment->getUnifyResult(); + } + + /** + * 记录第三方交易信息 + * @param array $payment 第三方支付数据 + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + private function recordPaymentTrade(array $payment): void + { + if ($this->method != PaymentMethodEnum::BALANCE) { + PaymentTradeModel::record( + $this->orderInfo, + $this->method, + $this->client, + OrderTypeEnum::SERVER, + $payment + ); + } + } + + /** + * 订单支付成功事件 + * @param string $orderNo 当前订单号 + * @param int|null $tradeId 第三方交易记录ID + * @param array $paymentData 第三方支付成功返回的数据 + * @return void + * @throws BaseException + * @author: wanghousheng + */ + private function orderPaySuccess(string $orderNo, ?int $tradeId = null, array $paymentData = []): void + { + // 获取订单详情 + $service = new PaySuccess(); + // 订单支付成功业务处理 + $service->setOrderNo($orderNo)->setMethod($this->method)->setTradeId($tradeId)->setPaymentData($paymentData); + if (!$service->handle()) { + throwError($service->getError() ?: '订单支付失败'); + } + $this->message = '恭喜您,订单支付成功'; + } +} \ No newline at end of file diff --git a/app/api/service/User.php b/app/api/service/User.php index 7fbe2615..546ef20b 100644 --- a/app/api/service/User.php +++ b/app/api/service/User.php @@ -86,7 +86,7 @@ class User extends UserService * @throws BaseException * @author: wanghousheng */ - public function isDealerMember(): bool + public static function isDealerMember(): bool { $userType = static::getCurrentLoginUserType(); if ($userType && $userType == UserTypeEnum::DEALER) { @@ -106,7 +106,7 @@ class User extends UserService * @throws BaseException * @author: wanghousheng */ - public function isDealerEngineer(): bool + public static function isDealerEngineer(): bool { $userType = static::getCurrentLoginUserType(); if ($userType && $userType == UserTypeEnum::DEALER) { @@ -126,7 +126,7 @@ class User extends UserService * @throws BaseException * @author: wanghousheng */ - public function isStore(): bool + public static function isStore(): bool { $userType = static::getCurrentLoginUserType(); if ($userType && $userType == UserTypeEnum::STORE) { @@ -134,7 +134,7 @@ class User extends UserService //商家表里有没有 $model = new \app\api\model\Store(); $store_id = $model->where(['user_id' => $userId])->value('store_id'); - if ($store_id && $store_id == $this->getStoreId()) { + if ($store_id && $store_id == (new self())->getStoreId()) { return $userId; } } @@ -147,7 +147,7 @@ class User extends UserService * @throws BaseException * @author: wanghousheng */ - public function isNormalMember(): bool + public static function isNormalMember(): bool { $userType = static::getCurrentLoginUserType(); if ($userType && $userType == UserTypeEnum::NORMAL) { diff --git a/app/common/model/UserCoupon.php b/app/common/model/UserCoupon.php index d4f83a40..594de0e9 100644 --- a/app/common/model/UserCoupon.php +++ b/app/common/model/UserCoupon.php @@ -12,10 +12,11 @@ declare (strict_types=1); namespace app\common\model; -use cores\BaseModel; +use app\common\enum\coupon\ExpireType as ExpireTypeEnum; +use app\common\enum\coupon\TypeCase; use app\common\library\helper; use app\common\model\Coupon as CouponModel; -use app\common\enum\coupon\ExpireType as ExpireTypeEnum; +use cores\BaseModel; use think\model\relation\BelongsTo; /** @@ -176,6 +177,7 @@ class UserCoupon extends BaseModel // 整理领取记录 $data = [ 'coupon_id' => $couponInfo['coupon_id'], + 'coupon_case' => !empty($couponInfo['coupon_case']) ?: TypeCase::SHOP, 'name' => $couponInfo['name'], 'coupon_type' => $couponInfo['coupon_type'], 'reduce_price' => $couponInfo['reduce_price'], diff --git a/app/common/service/server/Order.php b/app/common/service/server/Order.php index e30fbac5..4de0f3a2 100644 --- a/app/common/service/server/Order.php +++ b/app/common/service/server/Order.php @@ -4,12 +4,21 @@ declare (strict_types=1); namespace app\common\service\server; use app\api\model\dealer\User as DealerUserModel; +use app\api\model\Server\ServerOrder; +use app\api\model\UserCoupon; +use app\api\service\User as UserService; +use app\common\enum\coupon\CouponType as CouponTypeEnum; +use app\common\enum\coupon\TypeCase; use app\common\enum\dealer\DealerUserEnum; use app\common\enum\order\PayStatus; use app\common\enum\ServerEnum; +use app\common\library\helper; +use app\common\model\server\Server; use app\common\model\UserCoupon as UserCouponModel; use app\common\service\BaseService; +use app\common\service\Order as OrderService; use app\common\service\order\Refund as RefundService; +use app\store\model\dealer\User as UserModel; use cores\exception\BaseException; use think\db\exception\DataNotFoundException; use think\db\exception\DbException; @@ -17,13 +26,29 @@ use think\db\exception\ModelNotFoundException; class Order extends BaseService { + /** + * 订单结算的规则 + * @var array + */ + private array $checkoutRule = [ + 'isUserGrade' => true, // 会员等级折扣 + 'isCoupon' => true, // 优惠券抵扣 + 'isUsePoints' => true, // 是否使用积分抵扣 + 'isDealer' => true, // 是否开启分销 + ]; + + private array $serverInfo; + + private int $serverId; + private int $couponId; + /** * 生成订单号 * @return string */ - public static function createOrderNo(): string + private function orderNo(): string { - return 'SE' . date('Ymd') . substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13)))), 0, 8); + return OrderService::createOrderNo(); } /** @@ -62,6 +87,51 @@ class Order extends BaseService return false; } + /** + * @notes:是否可以取消 + * @param array $order + * @return bool + * @author: wanghousheng + */ + public static function checkCancel(array $order): bool + { + if (!empty($order['order_status']) && $order['order_status'] != ServerEnum::COMPLETED || $order['order_status'] != ServerEnum::CANCELLED) { + return true; + } + return false; + } + + /** + * @notes:是否可以派单 + * @param array $order + * @return bool + * @throws BaseException + * @author: wanghousheng + */ + public static function checkDispatch(array $order): bool + { + if (!empty($order) && $order['order_status'] == ServerEnum::APPLYDISPATCH && UserService::isStore()) { + return true; + } + return false; + } + + /** + * @notes:是否可以支付 + * @param array $order + * @return bool + * @throws BaseException + * @author: wanghousheng + */ + public static function checkPay(array $order): bool + { + $userId = UserService::getCurrentLoginUserId(); + if (!empty($order) && $order['pay_status'] == PayStatus::PENDING && $order['user_id'] == $userId) { + return true; + } + return false; + } + /** * @notes:派单 * @param array $order @@ -88,4 +158,204 @@ class Order extends BaseService } return false; } + + /** + * @notes:生成订单 + * @param int $serverId + * @param int $couponId + * @param string $buyer_remark + * @return array + * @throws BaseException + * @throws DbException + * @author: wanghousheng + */ + public function createOrder(int $serverId, int $couponId, string $buyer_remark = ''): array + { + $userId = UserService::getCurrentLoginUserId(); + $data = $this->onCheck($serverId, $couponId); + // 设置优惠券使用状态 + $couponId > 0 && UserCoupon::setIsUse($couponId); + //封装数据 + $order_data = [ + 'server_id' => $this->serverInfo['server_id'], + 'order_no' => $this->orderNo(), + 'server_image_id' => $this->serverInfo['image_id'], + 'total_price' => $this->serverInfo['server_price'], + 'store_id' => $this->storeId, + 'user_id' => $userId, + 'coupon_id' => $data['coupon_id'], + 'coupon_money' => $data['coupon_money'], + 'pay_price' => $data['pay_price'], + 'buyer_remark' => $buyer_remark, + ]; + $model = new ServerOrder(); + $order_id = $model->insertGetId($order_data); + if ($order_id) { + return ['order_no' => $order_data['order_no'], 'order_id' => $order_id]; + } + return []; + } + + /** + * @notes:订单确认-结算台 + * @param int $serverId + * @param int $couponId + * @return array + * @throws BaseException + * @throws DbException + * @author: wanghousheng + */ + public function onCheck(int $serverId, int $couponId): array + { + $this->serverId = $serverId; + $this->couponId = $couponId; + return $this->checkOut(); + } + + /** + * @notes:订单结算台 + * @return array + * @throws BaseException + * @throws DbException + * @author: wanghousheng + */ + private function checkOut(): array + { + $info = Server::detail(['server_id' => $this->serverId, 'status' => 1], ['image', 'category']); + if ($info->isEmpty()) { + throwError('未找到服务信息'); + } + $this->serverInfo = $info->toArray(); + $couponList = $this->getUserCouponList(); + $couponMoney = 0; + if ($couponList && $this->couponId) { + $couponMoney = $this->getCouponMoney($this->couponId, $couponList); + } + return [ + 'info' => $this->serverInfo, + 'server_id' => $this->serverId, + 'coupon_list' => $couponList, + 'coupon_money' => $couponMoney, + 'coupon_id' => $this->couponId, + 'pay_price' => helper::number2($this->serverInfo['server_price'] - $couponMoney), + ]; + } + + /** + * 当前用户可用的优惠券列表 + * @return array + * @throws DbException + */ + private function getUserCouponList(): array + { + // 是否开启优惠券折扣 + if (!$this->checkoutRule['isCoupon']) { + return []; + } + // 当前用户可用的优惠券列表 + $couponList = UserCoupon::getUserCouponList($this->user['user_id'], $this->serverInfo['server_price'], TypeCase::SERVER); + // 判断当前优惠券是否满足订单使用条件 (优惠券适用范围) + return UserCoupon::couponListApplyRangeServer($couponList, [$this->serverId]); + } + + /** + * 查找指定的优惠券信息 + * @param array $couponList 优惠券列表 + * @return false|mixed + */ + private function getCouponInfo(int $userCouponId, array $couponList) + { + return helper::getArrayItemByColumn($couponList, 'user_coupon_id', $userCouponId); + } + + /** + * @notes:获取优惠金额 + * @param int $userCouponId + * @param array $couponList + * @return float|int|mixed + * @author: wanghousheng + */ + private function getCouponMoney(int $userCouponId, array $couponList) + { + // 获取优惠券信息 + $couponInfo = $this->getCouponInfo($userCouponId, $couponList); + $reducedMoney = $this->serverInfo['server_price']; + // 计算打折金额 + if ($couponInfo['coupon_type'] == CouponTypeEnum::DISCOUNT) { + $reducePrice = $reducedMoney - ($reducedMoney * ($couponInfo['discount'] / 10)); + } else { + $reducePrice = $couponInfo['reduce_price']; + } + $reducePrice = helper::number2($reducePrice); + // 优惠券最大允许抵扣到一分钱,所以此处判断抵扣金额大于等于订单金额时,减去一分钱 + return ($reducePrice >= $reducedMoney) ? $reducedMoney - 1 : $reducePrice; + } + + /** + * @notes:用户取消订单 + * @param int $orderId + * @return bool + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public static function userCancelOrder(int $orderId): bool + { + $userId = UserService::getCurrentLoginUserId(); + $where['order_id'] = $orderId; + if (UserService::isDealerMember()) { + return false; + } + if (UserService::isPlusMember() || UserService::isNormalMember()) { + $where['user_id'] = $userId; + } + $orderInfo = ServerOrder::detail($where); + if ($orderInfo->isEmpty()) { + throwError('订单信息不存在'); + } + return self::cancelOrder($orderInfo->toArray()); + } + + /** + * @notes:派单 + * @param int $orderId + * @param int $dealerId + * @return bool + * @throws BaseException + * @author: wanghousheng + */ + public static function userDispatchOrders(int $orderId, int $dealerId): bool + { + if (!UserService::isStore()) { + return false; + } + $orderInfo = ServerOrder::detail(['order_id' => $orderId]); + if ($orderInfo->isEmpty()) { + throwError('订单信息不存在'); + } + return self::dispatchOrders($orderInfo->toArray(), $dealerId); + } + + /** + * @notes:分销商工程师-派单人员列表 + * @return array + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @author: wanghousheng + */ + public static function getEngineer(): array + { + if (UserService::isStore()) { + $list = UserModel::getEngineer(); + if (!$list->isEmpty()) { + return $list->toArray(); + } + } + return []; + } + } \ No newline at end of file