PaySuccess.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2024 https://www.yiovo.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
  8. // +----------------------------------------------------------------------
  9. // | Author: 萤火科技 <admin@yiovo.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\api\service\order;
  13. use think\facade\Event;
  14. use app\api\model\User as UserModel;
  15. use app\api\model\Order as OrderModel;
  16. use app\api\model\PaymentTrade as PaymentTradeModel;
  17. use app\api\model\user\BalanceLog as BalanceLogModel;
  18. use app\api\service\order\source\Factory as OrderSourceFactory;
  19. use app\common\library\Log;
  20. use app\common\library\Lock;
  21. use app\common\service\BaseService;
  22. use app\common\service\Order as OrderService;
  23. use app\common\service\order\Refund as RefundService;
  24. use app\common\service\goods\source\Factory as StockFactory;
  25. use app\common\enum\OrderType as OrderTypeEnum;
  26. use app\common\enum\order\PayStatus as PayStatusEnum;
  27. use app\common\enum\user\balanceLog\Scene as SceneEnum;
  28. use app\common\enum\payment\Method as PaymentMethodEnum;
  29. use app\common\enum\order\OrderStatus as OrderStatusEnum;
  30. use cores\exception\BaseException;
  31. /**
  32. * 订单支付成功服务类
  33. * Class PaySuccess
  34. * @package app\api\service\order
  35. */
  36. class PaySuccess extends BaseService
  37. {
  38. // 当前订单信息
  39. public OrderModel $orderInfo;
  40. // 当前用户信息
  41. private UserModel $userInfo;
  42. // 当前订单号
  43. private string $orderNo;
  44. // 订单支付方式
  45. private string $method;
  46. // 第三方交易记录ID
  47. private ?int $tradeId = null;
  48. // 第三方支付成功返回的数据
  49. private array $paymentData = [];
  50. /**
  51. * 设置当前的订单号
  52. * @param string $orderNo
  53. * @return $this
  54. */
  55. public function setOrderNo(string $orderNo): PaySuccess
  56. {
  57. $this->orderNo = $orderNo;
  58. return $this;
  59. }
  60. /**
  61. * 设置订单支付方式
  62. * @param string $method
  63. * @return $this
  64. */
  65. public function setMethod(string $method): PaySuccess
  66. {
  67. $this->method = $method;
  68. return $this;
  69. }
  70. /**
  71. * 第三方支付交易记录ID
  72. * @param int|null $tradeId
  73. * @return $this
  74. */
  75. public function setTradeId(?int $tradeId = null): PaySuccess
  76. {
  77. $this->tradeId = $tradeId;
  78. return $this;
  79. }
  80. /**
  81. * 第三方支付成功返回的数据
  82. * @param array $paymentData
  83. * @return $this
  84. */
  85. public function setPaymentData(array $paymentData): PaySuccess
  86. {
  87. $this->paymentData = $paymentData;
  88. return $this;
  89. }
  90. /**
  91. * 订单支付成功业务处理
  92. * @return bool
  93. * @throws BaseException
  94. */
  95. public function handle(): bool
  96. {
  97. // 验证当前参数是否合法
  98. $this->verifyParameters();
  99. // 当前订单开启并发锁
  100. $this->lockUp();
  101. // 验证当前订单是否允许支付
  102. if ($this->checkOrderStatusOnPay()) {
  103. // 更新订单状态为已付款
  104. $this->updatePayStatus();
  105. // 订单支付成功事件 (处理订单来源相关业务)
  106. Event::trigger('OrderPaySuccess', [
  107. 'order' => $this->getOrderInfo(),
  108. 'orderType' => OrderTypeEnum::ORDER
  109. ]);
  110. }
  111. // 当前订单解除并发锁
  112. $this->unLock();
  113. return true;
  114. }
  115. /**
  116. * 验证当前参数是否合法
  117. * @throws BaseException
  118. */
  119. private function verifyParameters()
  120. {
  121. if (empty($this->orderNo)) {
  122. throwError('orderNo not found');
  123. }
  124. if (empty($this->method)) {
  125. throwError('method not found');
  126. }
  127. if ($this->tradeId) {
  128. empty($this->paymentData) && throwError('PaymentData not found');
  129. !isset($this->paymentData['tradeNo']) && throwError('PaymentData not found');
  130. }
  131. // 记录日志
  132. Log::append('PaySuccess', [
  133. 'orderNo' => $this->orderNo, 'method' => $this->method,
  134. 'tradeId' => $this->tradeId, 'paymentData' => $this->paymentData
  135. ]);
  136. }
  137. /**
  138. * 获取当前订单的详情信息
  139. * @return OrderModel|null
  140. * @throws BaseException
  141. */
  142. private function getOrderInfo(): ?OrderModel
  143. {
  144. // 获取订单详情 (待支付状态)
  145. if (empty($this->orderInfo)) {
  146. $this->orderInfo = OrderModel::getPayDetail($this->orderNo);
  147. }
  148. // 判断订单是否存在
  149. if (empty($this->orderInfo)) {
  150. throwError('未找到该订单信息');
  151. }
  152. return $this->orderInfo;
  153. }
  154. /**
  155. * 订单模型
  156. * @return OrderModel|null
  157. * @throws BaseException
  158. */
  159. private function orderModel(): ?OrderModel
  160. {
  161. return $this->getOrderInfo();
  162. }
  163. /**
  164. * 验证当前订单是否允许支付
  165. * @return bool
  166. * @throws BaseException
  167. */
  168. private function checkOrderStatusOnPay(): bool
  169. {
  170. // 当前订单信息
  171. $orderInfo = $this->getOrderInfo();
  172. // 验证余额支付时用户余额是否满足
  173. if ($this->method == PaymentMethodEnum::BALANCE) {
  174. if ($this->getUserInfo()['balance'] < $orderInfo['pay_price']) {
  175. throwError('账户余额不足,无法使用余额支付');
  176. }
  177. }
  178. // 检查订单状态是否为已支付
  179. if ($orderInfo['pay_status'] == PayStatusEnum::SUCCESS) {
  180. $this->onOrderPaid();
  181. return false;
  182. }
  183. // 检查订单是否允许支付
  184. $orderSource = OrderSourceFactory::getFactory($this->orderInfo['order_source']);
  185. if (!$orderSource->checkOrderStatusOnPay($this->orderInfo)) {
  186. // 记录日志
  187. Log::append('PaySuccess --checkOrderStatusOnPay', ['errorMsg' => $orderSource->getError()]);
  188. // 执行原路退款
  189. $this->originalRefund();
  190. // 取消和关闭订单
  191. $this->cancelOrder();
  192. return false;
  193. }
  194. return true;
  195. }
  196. /**
  197. * 处理订单已支付的情况
  198. * @throws BaseException
  199. */
  200. private function onOrderPaid()
  201. {
  202. // 记录日志
  203. Log::append('PaySuccess --onOrderPaid', ['title' => '处理订单已支付的情况']);
  204. // 当前订单信息
  205. $orderInfo = $this->getOrderInfo();
  206. // 余额支付直接返回错误信息
  207. if ($this->method == PaymentMethodEnum::BALANCE) {
  208. throwError('当前订单已支付,无需重复支付');
  209. }
  210. // 第三方支付判断是否为重复下单 (因异步回调可能存在网络延迟的原因,在并发的情况下会出现同时付款两次,这里需要容错)
  211. // 如果订单记录中已存在tradeId并且和当前支付的tradeId不一致, 那么判断为重复的订单, 需进行退款处理
  212. if ($this->tradeId > 0 && $orderInfo['trade_id'] != $this->tradeId) {
  213. // 执行原路退款
  214. $this->originalRefund();
  215. }
  216. }
  217. /**
  218. * 订单原路退款
  219. * @return void
  220. * @throws BaseException
  221. */
  222. private function originalRefund(): void
  223. {
  224. // 记录日志
  225. Log::append('PaySuccess --originalRefund', ['title' => '订单原路退款']);
  226. // 当前订单信息
  227. $orderInfo = $this->getOrderInfo();
  228. // 余额支付无需退款 (因为是同步执行)
  229. if ($this->method === PaymentMethodEnum::BALANCE) {
  230. return;
  231. }
  232. // 执行第三方支付原路退款
  233. try {
  234. $orderInfo['trade_id'] = $this->tradeId;
  235. $orderInfo['pay_method'] = $this->method;
  236. $status = (new RefundService)->handle($orderInfo);
  237. Log::append('PaySuccess --originalRefund', ['status' => $status ? 'true' : 'false']);
  238. } catch (\Throwable $e) {
  239. Log::append('PaySuccess --originalRefund', ['status' => 'false', 'errorMsg' => $e->getMessage()]);
  240. }
  241. }
  242. /**
  243. * 取消并关闭订单
  244. * @throws BaseException
  245. */
  246. private function cancelOrder(): void
  247. {
  248. // 记录日志
  249. Log::append('PaySuccess --cancelOrder', ['title' => '取消并关闭订单']);
  250. // 当前订单信息
  251. $orderInfo = $this->getOrderInfo();
  252. // 订单取消事件
  253. OrderService::cancelEvent($orderInfo);
  254. // 更新订单状态
  255. $this->orderModel()->save(['order_status' => OrderStatusEnum::CANCELLED]);
  256. }
  257. /**
  258. * 订单已付款事件
  259. * @return void
  260. * @throws BaseException
  261. */
  262. private function updatePayStatus(): void
  263. {
  264. // 记录日志
  265. Log::append('PaySuccess --updatePayStatus', ['title' => '订单已付款事件']);
  266. // 当前订单信息
  267. $orderInfo = $this->getOrderInfo();
  268. // 事务处理
  269. $this->orderModel()->transaction(function () use ($orderInfo) {
  270. // 更新订单状态
  271. $this->updateOrderStatus();
  272. // 累积用户总消费金额
  273. UserModel::setIncPayMoney($orderInfo['user_id'], (float)$orderInfo['pay_price']);
  274. // 记录订单支付信息
  275. $this->updatePayInfo();
  276. });
  277. }
  278. /**
  279. * 获取买家用户信息
  280. * @return UserModel|array|null
  281. * @throws BaseException
  282. */
  283. private function getUserInfo()
  284. {
  285. if (empty($this->userInfo)) {
  286. $this->userInfo = UserModel::detail($this->getOrderInfo()['user_id']);
  287. }
  288. if (empty($this->userInfo)) {
  289. throwError('未找到买家用户信息');
  290. }
  291. return $this->userInfo;
  292. }
  293. /**
  294. * 更新订单状态
  295. * @throws BaseException
  296. */
  297. private function updateOrderStatus(): void
  298. {
  299. // 当前订单信息
  300. $orderInfo = $this->getOrderInfo();
  301. // 更新商品库存、销量
  302. StockFactory::getFactory($orderInfo['order_source'])->updateStockSales($orderInfo['goods']);
  303. // 更新订单状态
  304. $this->orderModel()->save([
  305. 'pay_method' => $this->method,
  306. 'pay_status' => PayStatusEnum::SUCCESS,
  307. 'pay_time' => time(),
  308. 'trade_id' => $this->tradeId ?: 0,
  309. ]);
  310. }
  311. /**
  312. * 记录订单支付的信息
  313. * @throws BaseException
  314. */
  315. private function updatePayInfo()
  316. {
  317. // 当前订单信息
  318. $orderInfo = $this->getOrderInfo();
  319. // 余额支付
  320. if ($this->method == PaymentMethodEnum::BALANCE) {
  321. // 更新用户余额
  322. UserModel::setDecBalance((int)$orderInfo['user_id'], (float)$orderInfo['pay_price']);
  323. // 新增余额变动记录
  324. BalanceLogModel::add(SceneEnum::CONSUME, [
  325. 'user_id' => (int)$orderInfo['user_id'],
  326. 'money' => -$orderInfo['pay_price'],
  327. ], ['order_no' => $orderInfo['order_no']]);
  328. }
  329. // 将第三方交易记录更新为已支付状态
  330. if (in_array($this->method, [PaymentMethodEnum::WECHAT, PaymentMethodEnum::ALIPAY])) {
  331. $this->updateTradeRecord();
  332. }
  333. }
  334. /**
  335. * 将第三方交易记录更新为已支付状态
  336. */
  337. private function updateTradeRecord()
  338. {
  339. if ($this->tradeId && !empty($this->paymentData)) {
  340. PaymentTradeModel::updateToPaySuccess($this->tradeId, $this->paymentData['tradeNo']);
  341. }
  342. }
  343. /**
  344. * 订单锁:防止并发导致重复支付
  345. * @throws BaseException
  346. */
  347. private function lockUp()
  348. {
  349. $orderInfo = $this->getOrderInfo();
  350. Lock::lockUp("OrderPaySuccess_{$orderInfo['order_id']}");
  351. }
  352. /**
  353. * 订单锁:防止并发导致重复支付
  354. * @throws BaseException
  355. */
  356. private function unLock()
  357. {
  358. $orderInfo = $this->getOrderInfo();
  359. Lock::unLock("OrderPaySuccess_{$orderInfo['order_id']}");
  360. }
  361. }