PayOperService.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <?php
  2. namespace app\common\Service;
  3. use think\Db;
  4. use think\Exception;
  5. use app\common\model\User;
  6. /**
  7. * 支付服务类
  8. * 封装订单创建相关逻辑
  9. */
  10. class PayOperService
  11. {
  12. /**
  13. *
  14. * @ 订单结算
  15. * @param string $order_sn 订单号
  16. * @param float $payamount 支付金额
  17. * @param string $transactionid 流水号
  18. * @return bool
  19. */
  20. public static function settle($order_sn, $payamount, $transactionid = '')
  21. {
  22. $order = self::with(['orderGoods'])->where('order_sn', $order_sn)->find();
  23. if (!$order || $order->paystate == 1) {
  24. return false;
  25. }
  26. if ($payamount != $order->saleamount) {
  27. \think\Log::write("[shop][pay][{$order_sn}][订单支付金额不一致]");
  28. return false;
  29. }
  30. // 启动事务
  31. Db::startTrans();
  32. try {
  33. $order->paystate = 1;
  34. $order->transactionid = $transactionid;
  35. $order->payamount = $payamount;
  36. $order->paytime = time();
  37. $order->paytype = !$order->paytype ? 'system' : $order->paytype;
  38. $order->method = !$order->method ? 'system' : $order->method;
  39. $order->save();
  40. if ($order->payamount == $order->saleamount) {
  41. //支付完成后,商品销量+1
  42. foreach ($order->order_goods as $item) {
  43. $goods = $item->goods;
  44. $sku = $item->sku;
  45. if ($goods) {
  46. $goods->setInc('sales', $item->nums);
  47. }
  48. if ($sku) {
  49. $sku->setInc('sales', $item->nums);
  50. }
  51. }
  52. }
  53. // 提交事务
  54. Db::commit();
  55. } catch (\Exception $e) {
  56. // 回滚事务
  57. Db::rollback();
  58. return false;
  59. }
  60. //记录操作
  61. OrderAction::push($order_sn, '系统', '订单支付成功');
  62. return true;
  63. }
  64. protected $user = null;
  65. /**
  66. * 实例化
  67. *
  68. * @param mixed $user
  69. */
  70. public function __construct($user = null)
  71. {
  72. // 优先使用传入的用户
  73. $this->user = $user ? (is_numeric($user) ? User::get($user) : $user) : auth_user();
  74. }
  75. /**
  76. * 微信预付款
  77. *
  78. * @param think\Model $order
  79. * @param float $money
  80. * @param string $order_type
  81. * @return think\Model
  82. */
  83. public function wechat($order, $money, $order_type = 'order')
  84. {
  85. $pay = $this->addPay($order, [
  86. 'order_type' => $order_type,
  87. 'pay_type' => 'wechat',
  88. 'pay_fee' => $money,
  89. 'real_fee' => $money,
  90. 'transaction_id' => null,
  91. 'payment_json' => [],
  92. 'status' => PayModel::PAY_STATUS_UNPAID
  93. ]);
  94. return $pay;
  95. }
  96. /**
  97. * 支付宝预付款
  98. *
  99. * @param think\Model $order
  100. * @param float $money
  101. * @param string $order_type
  102. * @return think\Model
  103. */
  104. public function alipay($order, $money, $order_type = 'order')
  105. {
  106. $pay = $this->addPay($order, [
  107. 'order_type' => $order_type,
  108. 'pay_type' => 'alipay',
  109. 'pay_fee' => $money,
  110. 'real_fee' => $money,
  111. 'transaction_id' => null,
  112. 'payment_json' => [],
  113. 'status' => PayModel::PAY_STATUS_UNPAID
  114. ]);
  115. return $pay;
  116. }
  117. /**
  118. * 余额付款
  119. *
  120. * @param think\Model $order
  121. * @param float $money
  122. * @param string $order_type
  123. * @return think\Model
  124. */
  125. public function money($order, $money, $order_type = 'order')
  126. {
  127. // 余额支付金额,传入金额和剩余支付金额最大值
  128. $money = $order->remain_pay_fee > $money ? $money : $order->remain_pay_fee; // 混合支付不能超过订单应支付总金额
  129. // 扣除用户余额
  130. WalletService::change($this->user, 'money', -$money, 'order_pay', [
  131. 'order_id' => $order->id,
  132. 'order_sn' => $order->order_sn,
  133. 'order_type' => $order_type,
  134. ]);
  135. // 添加支付记录
  136. $pay = $this->addPay($order, [
  137. 'order_type' => $order_type,
  138. 'pay_type' => 'money',
  139. 'pay_fee' => $money,
  140. 'real_fee' => $money,
  141. 'transaction_id' => null,
  142. 'payment_json' => [],
  143. 'status' => PayModel::PAY_STATUS_PAID
  144. ]);
  145. // 余额直接支付成功,更新订单剩余应付款金额,并检测订单状态
  146. return $this->checkAndPaid($order, $order_type);
  147. }
  148. /**
  149. * 积分支付
  150. *
  151. * @param think\Model $order
  152. * @param float $money
  153. * @param string $order_type
  154. * @return think\Model
  155. */
  156. public function score($order, $score, $order_type = 'order')
  157. {
  158. if ($order_type == 'order') {
  159. if ($order['type'] == 'score') {
  160. $log_type = 'score_shop_pay';
  161. $real_fee = $score; // 积分商城真实抵扣,就是积分
  162. } else {
  163. $log_type = 'order_pay';
  164. // $real_fee = ; // 积分商城真实抵扣,就是积分
  165. error_stop('缺少积分抵扣金额'); // 支持积分抵扣时补全
  166. }
  167. }
  168. WalletService::change($this->user, 'score', -$score, $log_type, [
  169. 'order_id' => $order->id,
  170. 'order_sn' => $order->order_sn,
  171. 'order_type' => $order_type,
  172. ]);
  173. // 添加支付记录
  174. $pay = $this->addPay($order, [
  175. 'order_type' => $order_type,
  176. 'pay_type' => 'score',
  177. 'pay_fee' => $score,
  178. 'real_fee' => $real_fee,
  179. 'transaction_id' => null,
  180. 'payment_json' => [],
  181. 'status' => PayModel::PAY_STATUS_PAID
  182. ]);
  183. // 积分直接支付成功,更新订单剩余应付款金额,并检测订单状态
  184. return $this->checkAndPaid($order, $order_type);
  185. }
  186. /**
  187. * 线下支付(货到付款)
  188. *
  189. * @param think\Model $order
  190. * @param float $money
  191. * @param string $order_type
  192. * @return think\Model
  193. */
  194. public function offline($order, $money, $order_type = 'order')
  195. {
  196. // 添加支付记录
  197. $pay = $this->addPay($order, [
  198. 'order_type' => $order_type,
  199. 'pay_type' => 'offline',
  200. 'pay_fee' => $money,
  201. 'real_fee' => $money,
  202. 'transaction_id' => null,
  203. 'payment_json' => [],
  204. 'status' => PayModel::PAY_STATUS_PAID
  205. ]);
  206. // 更新订单剩余应付款金额,并检测订单状态
  207. return $this->checkAndPaid($order, $order_type, 'offline');
  208. }
  209. /**
  210. * 微信支付宝支付回调通用方法
  211. *
  212. * @param \think\Model $pay
  213. * @param array $notify
  214. * @return void
  215. */
  216. public function notify($pay, $notify)
  217. {
  218. $pay->status = PayModel::PAY_STATUS_PAID;
  219. $pay->transaction_id = $notify['transaction_id'];
  220. $pay->buyer_info = $notify['buyer_info'];
  221. $pay->payment_json = $notify['payment_json'];
  222. $pay->paid_time = time();
  223. $pay->save();
  224. $orderModel = $this->getOrderModel($pay->order_type);
  225. $order = new $orderModel();
  226. $order = $order->where('id', $pay->order_id)->find();
  227. if (!$order) {
  228. // 订单未找到,非正常情况,这里记录日志
  229. Log::write('pay-notify-error:order notfound;pay:' . json_encode($pay) . ';notify:' . json_encode($notify));
  230. return false;
  231. }
  232. if ($order->status == $order::STATUS_UNPAID) { // 未支付,检测支付状态
  233. $order = $this->checkAndPaid($order, $pay->order_type);
  234. }
  235. return $order;
  236. }
  237. /**
  238. * 更新订单剩余应支付金额,并且检测订单状态
  239. *
  240. * @param think\Model $order
  241. * @param string $order_type
  242. * @return think\Model
  243. */
  244. public function checkAndPaid($order, $order_type, $pay_mode = 'online')
  245. {
  246. // 获取订单已支付金额
  247. $payed_fee = $this->getPayedFee($order, $order_type);
  248. $remain_pay_fee = bcsub($order->pay_fee, (string)$payed_fee, 2);
  249. $order->remain_pay_fee = $remain_pay_fee;
  250. if ($remain_pay_fee <= 0) {
  251. $order->remain_pay_fee = 0;
  252. $order->paid_time = time();
  253. $order->status = $order::STATUS_PAID;
  254. } else {
  255. if ($pay_mode == 'offline') {
  256. // 订单未支付成功,并且是线下支付(货到付款),将订单状态改为 pending
  257. $order->status = $order::STATUS_PENDING;
  258. $order->ext = array_merge($order->ext, ['pending_time' => time()]); // 货到付款下单时间
  259. $order->pay_mode = 'offline';
  260. }
  261. }
  262. $order->save();
  263. if ($order->status == $order::STATUS_PAID) {
  264. // 订单支付完成
  265. $user = User::where('id', $order->user_id)->find();
  266. if ($order_type == 'order') {
  267. if ($pay_mode == 'offline') {
  268. Action::add($order, null, auth_admin(), 'admin', '管理员操作自动货到付款支付成功');
  269. // 在控制器执行后续内容,这里不再处理
  270. return $order;
  271. } else {
  272. Action::add($order, null, $user, 'user', '用户支付成功');
  273. // 支付成功后续使用异步队列处理
  274. \think\Queue::push('\addons\shopro\job\OrderPaid@paid', ['order' => $order, 'user' => $user], 'shopro-high');
  275. }
  276. } else if ($order_type == 'trade_order') {
  277. // 支付成功后续使用异步队列处理
  278. \think\Queue::push('\addons\shopro\job\trade\OrderPaid@paid', ['order' => $order, 'user' => $user], 'shopro-high');
  279. }
  280. } else if ($order->status == $order::STATUS_PENDING) {
  281. // 货到付款,添加货到付款队列(后续也需要处理拼团, 减库存等等)
  282. $user = User::where('id', $order->user_id)->find();
  283. if ($order_type == 'order') {
  284. Action::add($order, null, $user, 'user', '用户货到付款下单成功');
  285. // 支付成功后续使用异步队列处理
  286. \think\Queue::push('\addons\shopro\job\OrderPaid@offline', ['order' => $order, 'user' => $user], 'shopro-high');
  287. }
  288. }
  289. return $order;
  290. }
  291. /**
  292. * 获取订单已支付金额,商城订单 计算 积分抵扣金额
  293. *
  294. * @param \think\Model $order
  295. * @param string $order_type
  296. * @return string
  297. */
  298. public function getPayedFee($order, $order_type)
  299. {
  300. // 锁定读取所有已支付的记录,判断已支付金额
  301. $pays = PayModel::{'type' . Str::studly($order_type)}()->paid()->where('order_id', $order->id)->lock(true)->select();
  302. // 商城或者积分商城订单
  303. $payed_fee = '0';
  304. foreach ($pays as $key => $pay) {
  305. if ($pay->pay_type == 'score') {
  306. if ($order_type == 'order' && $order['type'] == 'goods') {
  307. // 商城类型订单,并且不是积分商城订单,加上积分抵扣真实金额
  308. $payed_fee = bcadd($payed_fee, $pay->real_fee, 2);
  309. } else {
  310. // 其他类型,需要计算积分抵扣的金额时
  311. }
  312. } else {
  313. $payed_fee = bcadd($payed_fee, $pay->real_fee, 2);
  314. }
  315. }
  316. return $payed_fee;
  317. }
  318. /**
  319. * 获取剩余可退款的pays 记录(不含积分抵扣)
  320. *
  321. * @param integer $order_id
  322. * @param string $sort 排序:money=优先退回余额支付的钱
  323. * @return \think\Collection
  324. */
  325. public function getCanRefundPays($order_id, $sort = 'money')
  326. {
  327. // 商城订单,已支付的 pay 记录, 这里只查 钱的支付记录,不查积分
  328. $pays = PayModel::typeOrder()->paid()->isMoney()->where('order_id', $order_id)->lock(true)->order('id', 'asc')->select();
  329. $pays = collection($pays);
  330. if ($sort == 'money') {
  331. // 对 pays 进行排序,优先退 money 的钱
  332. $pays = $pays->sort(function ($a, $b) {
  333. if ($a['pay_type'] == 'money' && $b['pay_type'] == 'money') {
  334. return 0;
  335. } else if ($a['pay_type'] == 'money' && $b['pay_type'] != 'money') {
  336. return -1;
  337. } else if ($a['pay_type'] != 'money' && $b['pay_type'] == 'money') {
  338. return 1;
  339. } else {
  340. return 0;
  341. }
  342. });
  343. $pays = $pays->values();
  344. }
  345. return $pays;
  346. }
  347. /**
  348. * 获取剩余可退款金额,不含积分相关支付
  349. *
  350. * @param mixed $pays
  351. * @return string
  352. */
  353. public function getRemainRefundMoney($pays)
  354. {
  355. // 拿到 所有可退款的支付记录
  356. $pays = ($pays instanceof \think\Collection) ? $pays : $this->getCanRefundPays($pays);
  357. // 支付金额,除了已经退完款的金额 (这里不退积分)
  358. $payed_money = (string)array_sum($pays->column('pay_fee'));
  359. // 已经退款金额 (这里不退积分)
  360. $refunded_money = (string)array_sum($pays->column('refund_fee'));
  361. // 当前剩余的最大可退款金额,支付金额 - 已退款金额
  362. $remain_max_refund_money = bcsub($payed_money, $refunded_money, 2);
  363. return $remain_max_refund_money;
  364. }
  365. /**
  366. * 添加 pay 记录
  367. *
  368. * @param think\Model $order
  369. * @param array $params
  370. * @return think\Model
  371. */
  372. public function addPay($order, $params)
  373. {
  374. $payModel = new PayModel();
  375. $payModel->order_type = $params['order_type'];
  376. $payModel->order_id = $order->id;
  377. $payModel->pay_sn = get_sn($this->user->id, 'P');
  378. $payModel->user_id = $this->user->id;
  379. $payModel->pay_type = $params['pay_type'];
  380. $payModel->pay_fee = $params['pay_fee'];
  381. $payModel->real_fee = $params['real_fee'];
  382. $payModel->transaction_id = $params['transaction_id'];
  383. $payModel->payment_json = $params['payment_json'];
  384. $payModel->paid_time = $params['status'] == PayModel::PAY_STATUS_PAID ? time() : null;
  385. $payModel->status = $params['status'];
  386. $payModel->refund_fee = 0;
  387. $payModel->save();
  388. return $payModel;
  389. }
  390. public function getOrderModel($order_type)
  391. {
  392. switch ($order_type) {
  393. case 'trade_order':
  394. $orderModel = '';
  395. break;
  396. case 'order':
  397. $orderModel = '\\app\\common\\model\\order';
  398. break;
  399. default:
  400. $orderModel = '\\app\\common\\model\\order';
  401. break;
  402. }
  403. return $orderModel;
  404. }
  405. }