// +---------------------------------------------------------------------- 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\Server\ServerOrder; 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\library\Lock; use app\common\library\Log; use app\common\service\BaseService; use cores\exception\BaseException; /** * 余额充值订单支付成功服务类 * Class PaySuccess * @package app\api\service\order */ class PaySuccess extends BaseService { // 当前订单信息 public ServerOrder $orderInfo; // 当前用户信息 private UserModel $userInfo; // 当前订单号 private string $orderNo; // 当前订单ID private string $orderId; // 订单支付方式 private string $method; // 第三方交易记录ID private ?int $tradeId = null; // 第三方支付成功返回的数据 private array $paymentData = []; /** * 设置支付的订单ID * @param int $orderId 订单ID */ public function setOrderId(int $orderId): \app\api\service\Server\PaySuccess { $this->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(); } // 当前订单解除并发锁 $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(); }); } /** * 记录订单支付的信息 * @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' => 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("OrderPaySuccess_{$orderInfo['order_id']}"); } /** * 订单锁:防止并发导致重复支付 * @throws BaseException */ private function unLock() { $orderInfo = $this->getOrderInfo(); Lock::unLock("OrderPaySuccess_{$orderInfo['order_id']}"); } }