PayOper.php 15 KB

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