PayOperService.php 15 KB

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