// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\service\identity; use app\api\model\PaymentTrade as PaymentTradeModel; use app\api\model\recharge\Order as OrderModel; use app\api\model\User as UserModel; use app\api\model\user\BalanceLog as BalanceLogModel; use app\api\model\user\IdentityOrder; use app\common\enum\order\PayStatus; use app\common\enum\payment\Method as PaymentMethodEnum; use app\common\enum\recharge\order\PayStatus as PayStatusEnum; use app\common\enum\user\balanceLog\Scene as SceneEnum; use app\common\enum\user\IdentityEnum; use app\common\enum\user\UserTypeEnum; use app\common\library\Lock; use app\common\library\Log; use app\common\service\BaseService; use app\store\model\dealer\User; use cores\exception\BaseException; /** * 余额充值订单支付成功服务类 * Class PaySuccess * @package app\api\service\order */ class PaySuccess extends BaseService { // 当前订单信息 public IdentityOrder $orderInfo; // 当前用户信息 private UserModel $userInfo; // 当前订单号 private string $orderNo; // 当前订单ID private int $orderId; // 订单支付方式 private string $method; // 第三方交易记录ID private ?int $tradeId = null; // 第三方支付成功返回的数据 private array $paymentData = []; private int $type; /** * 设置支付的订单ID * @param int $orderId 订单ID */ public function setOrderId(int $orderId): PaySuccess { $this->orderId = $orderId; return $this; } public function setType($type): PaySuccess { $this->type = $type; 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(); } // 当前订单解除并发锁 $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 IdentityOrder|null * @throws BaseException */ private function orderModel(): ?IdentityOrder { 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(); //更改会员角色 $this->activate(); }); } /** * 记录订单支付的信息 * @throws BaseException */ private function updatePayInfo() { // 当前订单信息 $orderInfo = $this->getOrderInfo(); // 余额支付 if ($this->method == PaymentMethodEnum::BALANCE) { // 更新用户余额 UserModel::setDecBalance((int)$orderInfo['user_id'], (float)$orderInfo['pay_price']); // 新增余额变动记录 $type = SceneEnum::MEMBER; if ($this->type == IdentityEnum::DEALER) { $type = SceneEnum::DEALER; } BalanceLogModel::add($type, [ 'user_id' => (int)$orderInfo['user_id'], 'money' => -$orderInfo['pay_price'], ], ['order_no' => $orderInfo['order_no']]); } // 将第三方交易记录更新为已支付状态 if (in_array($this->method, [PaymentMethodEnum::WECHAT, PaymentMethodEnum::ALIPAY, PaymentMethodEnum::HUIFU])) { $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' => PayStatus::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(): ?IdentityOrder { // 获取订单详情 (待支付状态) if (empty($this->orderInfo)) { $this->orderInfo = IdentityOrder::getPayDetail($this->orderNo); } // 判断订单是否存在 if (empty($this->orderInfo)) { throwError('未找到该订单信息'); } return $this->orderInfo; } /** * 订单锁:防止并发导致重复支付 * @throws BaseException */ private function lockUp() { $orderInfo = $this->getOrderInfo(); Lock::lockUp("IdentityOrderPaySuccess_{$orderInfo['order_id']}"); } /** * 订单锁:防止并发导致重复支付 * @throws BaseException */ private function unLock() { $orderInfo = $this->getOrderInfo(); Lock::unLock("IdentityOrderPaySuccess_{$orderInfo['order_id']}"); } /** * @notes:修改会员身份信息 * @throws BaseException * @author: wanghousheng */ private function activate(): void { $orderInfo = $this->getOrderInfo(); $userInfo = $this->getUserInfo(); //判断当前用户角色 $orderType = $orderInfo['order_type']; $userType = $userInfo['user_type']; if ($userType == UserTypeEnum::STORE) { return; } $userModel = new UserModel(); $up = []; $time = date('Y-m-d'); //已经是会员或者未开通会员 if ($orderType == IdentityEnum::MEMBER && $userType != UserTypeEnum::DEALER) { $up['user_type'] = UserTypeEnum::MEMBER; //已经是会员 if ($userType == UserTypeEnum::MEMBER) { //是否到期 if (!empty($userInfo['effective_time']) && strtotime($userInfo['effective_time']) > strtotime(date('Y-m-d'))) { $time = $userInfo['effective_time']; } } $up['effective_time'] = date("Y-m-d", strtotime("+{$orderInfo['month']} months", strtotime($time))); } else { $up['user_type'] = UserTypeEnum::DEALER; //已经是分销商 if ($userType == UserTypeEnum::DEALER) { //是否到期 if (!empty($userInfo['fx_effective_time']) && strtotime($userInfo['fx_effective_time']) > strtotime(date('Y-m-d'))) { $time = $userInfo['fx_effective_time']; } } if (!User::isDealerUser($userInfo['user_id'])) { // 新增分销商用户 $model = new UserModel(); $mobile = $model->where(['user_id' => $userInfo['user_id']])->value('mobile'); User::add($userInfo['user_id'], [ 'real_name' => $mobile, 'mobile' => $mobile, 'store_id' => $userInfo['store_id'] ?? 0, ]); } else { //更新分销用户 User::update(['is_delete' => 0], ['user_id' => $userInfo['user_id']]); } $up['fx_effective_time'] = date("Y-m-d", strtotime("+{$orderInfo['month']} months", strtotime($time))); } $userModel->where(['user_id' => $userInfo['user_id']])->save($up); } }