PayOper.php 15 KB

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