WalletService.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. <?php
  2. namespace app\api\service;
  3. use addons\fastchat\library\Chat;
  4. use app\api\model\deposit\Record;
  5. use app\api\model\order\Order;
  6. use app\api\model\order\Progress;
  7. use app\api\model\ThirdPayLog;
  8. use app\api\model\User;
  9. use app\api\model\user\Bill;
  10. use app\api\model\user\Voucher;
  11. use app\api\model\user\Wallet;
  12. use redis\RedLock;
  13. use think\Db;
  14. use think\Exception;
  15. use think\Log;
  16. class WalletService extends BaseService
  17. {
  18. private $userModel;
  19. private $userVoucherModel;
  20. private $userWalletModel;
  21. private $userBillModel;
  22. private $orderModel;
  23. private $thirdPayService;
  24. private $thirdPayLogModel;
  25. public function __construct()
  26. {
  27. $this->userModel = new User();
  28. $this->userWalletModel = new Wallet();
  29. $this->userBillModel = new Bill();
  30. $this->orderModel = new Order();
  31. $this->thirdPayService = new ThirdPayService();
  32. $this->thirdPayLogModel = new ThirdPayLog();
  33. $this->userVoucherModel = new Voucher();
  34. }
  35. public function fetchRechargeOption()
  36. {
  37. $cfg = config("site.balance_recharge");
  38. return array_map(function ($data) use ($cfg) {
  39. return [
  40. "amount" => (float)$data,
  41. "give" => (float)$cfg[$data]
  42. ];
  43. }, array_keys($cfg));
  44. }
  45. public function fetchVoucher($user_id, $status, $page = 1, $size = 10)
  46. {
  47. $this->userVoucherModel->passVoucher($user_id);
  48. $paginate = $this->userVoucherModel->fetchVoucher($user_id, $status, $page, $size);
  49. return [
  50. $paginate->items(),
  51. $paginate->total()
  52. ];
  53. }
  54. // 余额充值
  55. public function rechargeBalance($user_id, $payment_type, $amount, $platform): \SResult
  56. {
  57. $user = $this->userModel->findById($user_id);
  58. if (!$user)
  59. return $this->fail("用户不存在!");
  60. $walletLock = new RedLock();
  61. $wLock = $walletLock->lock(Wallet::UWKey($user_id));
  62. if (!is_array($wLock))
  63. return $this->fail("请稍后再试!");
  64. try {
  65. $give = 0;
  66. foreach ($this->fetchRechargeOption() as $item) {
  67. if ($amount >= $item["amount"])
  68. $give = $item["give"];
  69. }
  70. $no = "RBU" . time() . rand(10000, 99999);
  71. $log = [
  72. "no" => $no,
  73. "user_id" => $user_id,
  74. "platform" => $payment_type,
  75. "type" => null,
  76. "value" => json_encode([
  77. "user_id" => $user_id,
  78. "amount" => $amount,
  79. "give" => $give
  80. ]),
  81. "reason" => null,
  82. "status" => \E_BASE_STATUS::Default,
  83. "relation_no" => $no,
  84. "createtime" => time(),
  85. "updatetime" => time()
  86. ];
  87. if (\E_ORDER_PAY_TYPE::Wechat === $payment_type) {
  88. $openid = null;
  89. if ($platform == "app")
  90. $openid = $user["app_openid"];
  91. if ($platform == "web")
  92. $openid = $user["web_openid"];
  93. if ($platform == "applet")
  94. $openid = $user["applet_openid"];
  95. if (is_null($openid) || mb_strlen($openid) === 0)
  96. throw new Exception("请先绑定微信再进行支付!");
  97. $user = $this->userModel->findById($user_id);
  98. if (!$user)
  99. throw new Exception("用户异常!");
  100. $res = $this->thirdPayService->payWechat($platform, $openid, $log["no"], $amount, "充值余额");
  101. $log["type"] = \E_USER_BILL_CHANGE_TYPE::BalanceWxRecharge[0];
  102. $log["reason"] = \E_USER_BILL_CHANGE_TYPE::BalanceWxRecharge[1];
  103. } else {
  104. $log["type"] = \E_USER_BILL_CHANGE_TYPE::BalanceAliRecharge[0];
  105. $log["reason"] = \E_USER_BILL_CHANGE_TYPE::BalanceAliRecharge[1];
  106. $res = $this->thirdPayService->payAli([]);
  107. }
  108. if ($res->code())
  109. $this->thirdPayLogModel->save($log);
  110. return $res;
  111. } catch (Exception $e) {
  112. return $this->fail($e->getMessage());
  113. } finally {
  114. $walletLock->unlock($wLock);
  115. }
  116. }
  117. // 余额支付
  118. public function payBalance($order)
  119. {
  120. $walletLock = new RedLock();
  121. $wLock = $walletLock->lock(Wallet::UWKey($order->user_id));
  122. if (!is_array($wLock))
  123. return $this->fail("请稍后再试");
  124. $massager = (new \app\api\model\massager\Massager())->findById($order["massager_id"] ?? -1);
  125. if ($order["massager_id"] > 0) {
  126. if (!$massager)
  127. return $this->fail("助教不存在!");
  128. }
  129. $wallet = $this->userWalletModel->getUserWallet($order->user_id);
  130. $change = $order->total_real_amount + $order->membership_amount;
  131. $w_money = $wallet->money;
  132. $w_g_money = $wallet->give_money;
  133. $w_total_money = $w_money + $w_g_money;
  134. $membership_no = "RBM" . time() . rand(10000, 99999);
  135. if ($order["membership_config_id"] > 0 && $order["membership_amount"] > 0) {
  136. $res = $this->thirdPayLogModel->save([
  137. "no" => $membership_no,
  138. "user_id" => $order["user_id"],
  139. "platform" => $order["pay_platform"],
  140. "type" => \E_USER_BILL_CHANGE_TYPE::MemberCharge[0],
  141. "reason" => \E_USER_BILL_CHANGE_TYPE::MemberCharge[1],
  142. "value" => null,
  143. "membership_config_id" => $order["membership_config_id"],
  144. "amount" => $order["membership_amount"],
  145. "status" => \E_BASE_STATUS::Default,
  146. "relation_no" => $order["no"],
  147. "createtime" => time(),
  148. "updatetime" => time()
  149. ]);
  150. if (!$res) {
  151. return $this->fail("会员订单保存异常");
  152. }
  153. }
  154. if ($w_total_money - $change < 0)
  155. return $this->fail("账户余额不足");
  156. Db::startTrans();
  157. try {
  158. if ($order["membership_config_id"] > 0 && $order["membership_amount"] > 0) {
  159. $r = (new CallbackService())->rechargeMembershipSuccess($membership_no);
  160. if (0 === $r->code()) {
  161. throw new Exception($r->msg());
  162. }
  163. }
  164. $bill = [
  165. "user_id" => $order->user_id,
  166. "currency_type" => \E_USER_BILL_CURRENCY_TYPE::Money,
  167. "change_type" => \E_USER_BILL_CHANGE_TYPE::BalanceOrderConsumption[0],
  168. "change" => -$change,
  169. "before" => $w_total_money,
  170. "after" => $w_total_money - $change,
  171. "reason" => \E_USER_BILL_CHANGE_TYPE::BalanceOrderConsumption[1],
  172. "money" => 0,
  173. "give_money" => 0,
  174. "relation_no" => $order->no,
  175. "createtime" => time()
  176. ];
  177. $update = [
  178. "score" => $wallet["score"] + $change,
  179. "updatetime" => time()
  180. ];
  181. // 优先减扣充值的钱
  182. if ($w_money - $change > 0) {
  183. $update["money"] = fixed2Float($w_money - $change);
  184. $bill["money"] = -$change;
  185. } else {
  186. $update["money"] = 0;
  187. $update["give_money"] = $w_g_money - ($change - $w_money);
  188. $bill["money"] = -$w_money;
  189. $bill["give_money"] = -($change - $w_money);
  190. }
  191. $this->userWalletModel->update($update, ["id" => $wallet->id]);
  192. $this->orderModel->update([
  193. "status" => \E_ORDER_STATUS::Purchase,
  194. "balance_deduction" => 0,
  195. "pay_time" => time(),
  196. "updatetime" => time()
  197. ], ["id" => $order->id]);
  198. $this->userBillModel->insertAll([$bill, [
  199. "user_id" => $order->user_id,
  200. "currency_type" => \E_USER_BILL_CURRENCY_TYPE::Score,
  201. "change_type" => \E_USER_BILL_CHANGE_TYPE::ScoreOrderGive[0],
  202. "change" => $change,
  203. "before" => $wallet["score"],
  204. "after" => $wallet["score"] + $change,
  205. "reason" => \E_USER_BILL_CHANGE_TYPE::ScoreOrderGive[1],
  206. "money" => 0,
  207. "give_money" => 0,
  208. "relation_no" => $order->no,
  209. "createtime" => time()
  210. ]]);
  211. $progress = [];
  212. $types = $order["store_id"] > 0 ? [
  213. \E_ORDER_PROGRESS_TYPE::Start,
  214. \E_ORDER_PROGRESS_TYPE::Over,
  215. ] : ALL_ORDER_PROGRESS_TYPE;
  216. if ($order["reorder"] == 1) {
  217. $types = [
  218. \E_ORDER_PROGRESS_TYPE::Take,
  219. \E_ORDER_PROGRESS_TYPE::Over,
  220. ];
  221. }
  222. foreach ($types as $key => $value) {
  223. $progress[] = [
  224. "order_id" => $order->id,
  225. "order_no" => $order->no,
  226. "type" => $value,
  227. "clock_in" => 0,
  228. "index" => $key + 1,
  229. "clock_in_time" => null,
  230. "lng" => null,
  231. "lat" => null,
  232. "card_image" => null,
  233. "createtime" => time(),
  234. "updatetime" => time()
  235. ];
  236. }
  237. (new Progress())->insertAll($progress);
  238. if ($order["massager_id"] > 0 && $massager["mobile"]) {
  239. $r = TencentCloudService::tencent_cloud_vms_send($massager["mobile"], "1478969", [$order["no"]]);
  240. Log::custom_log("Vms", [$r->code(), $r->msg(), $r->data()], "Vms");
  241. }
  242. if ($order["voucher_id"] > 0) {
  243. $this->userVoucherModel->update([
  244. "status" => "use"
  245. ], ["id" => $order["voucher_id"]]);
  246. }
  247. if ($order["store_id"] > 0) {
  248. StoreService::inform($order["no"], $order["store_id"], $order["city_code"], $order["service_start_date"]);
  249. }
  250. if ($massager) {
  251. Chat::init(1)
  252. ->massager([$massager["id"]])
  253. ->send("您有新订单,请及时处理");
  254. }
  255. Db::commit();
  256. return $this->ok(true);
  257. } catch (Exception $e) {
  258. Db::rollback();
  259. return $this->fail($e->getMessage());
  260. } finally {
  261. UserService::updateGrouping($order["user_id"]);
  262. $walletLock->unlock($wLock);
  263. }
  264. }
  265. // 退款
  266. public function refundByBalance($order, $order_status = \E_ORDER_STATUS::Reject): \SResult
  267. {
  268. if (!in_array($order["status"], [\E_ORDER_STATUS::Purchase, \E_ORDER_STATUS::Proceed, \E_ORDER_STATUS::WaitFeedback])) {
  269. return $this->fail("订单的状态不支持退款!");
  270. }
  271. $wallet = $this->userWalletModel->getUserWallet($order->user_id);
  272. if (!$wallet)
  273. return $this->fail("钱包数据异常,无法取消订单!");
  274. $walletLock = new RedLock();
  275. $wLock = $walletLock->lock(Wallet::UWKey($order->user_id));
  276. if (!is_array($wLock))
  277. return $this->fail("请稍后再试");
  278. $bill = $this->userBillModel->getBill($order->user_id, \E_USER_BILL_CURRENCY_TYPE::Money, \E_USER_BILL_CHANGE_TYPE::BalanceOrderConsumption[0], $order->no);
  279. if (!$bill || (0 == $bill->money && 0 == $bill->give_money))
  280. return $this->fail("账单数据异常,无法取消订单!");
  281. $trip_amount = 0;
  282. if (\E_ORDER_STATUS::Purchase !== $order["status"]) {
  283. $trip_amount = $order["trip_amount"];
  284. }
  285. // 订单实际支付总额 - 出行费 出行费不退
  286. $change = abs($bill->change) - $trip_amount;
  287. $money = abs($bill->money);
  288. $give_money = abs($bill->give_money);
  289. if ($money - $trip_amount > 0) {
  290. $money -= $trip_amount;
  291. } else {
  292. $money = 0;
  293. $give_money -= ($money + $trip_amount);
  294. }
  295. Db::startTrans();
  296. try {
  297. $save_bill = [
  298. "user_id" => $order->user_id,
  299. "currency_type" => \E_USER_BILL_CURRENCY_TYPE::Money,
  300. "change_type" => \E_USER_BILL_CHANGE_TYPE::BalanceOrderRefund[0],
  301. "change" => $change,
  302. "before" => ($wallet->money + $wallet->give_money),
  303. "after" => ($wallet->money + $wallet->give_money) + $change,
  304. "reason" => \E_USER_BILL_CHANGE_TYPE::BalanceOrderRefund[1],
  305. "money" => $money,
  306. "give_money" => $give_money,
  307. "relation_no" => $order->no,
  308. "createtime" => time()
  309. ];
  310. $this->orderModel->update([
  311. "status" => $order_status,
  312. "cancel_time" => time(),
  313. "updatetime" => time()
  314. ], ["id" => $order->id]);
  315. $this->userWalletModel->update([
  316. "money" => $wallet->money + $money,
  317. "give_money" => $wallet->give_money + $give_money,
  318. "updatetime" => time()
  319. ], ["id" => $wallet->id]);
  320. if ($order["massager_id"] > 0 && $trip_amount > 0) {
  321. $mWalletModel = new \app\api\model\massager\Wallet();
  322. // 出行费到账助教账户
  323. $mWallet = $mWalletModel->getWallet($order["massager_id"]);
  324. $mWalletModel->where("id", $mWallet->id)->setInc("profit_amount", $trip_amount);
  325. (new \app\api\model\massager\Bill())->insert([
  326. "massager_id" => $order["massager_id"],
  327. "currency_type" => \E_USER_BILL_CURRENCY_TYPE::Money,
  328. "change_type" => \E_M_BILL_CHANGE_TYPE::Travel,
  329. "change" => $trip_amount,
  330. "before" => fixed2Float($mWallet->profit_amount),
  331. "after" => fixed2Float($mWallet->profit_amount + $trip_amount),
  332. "reason" => "出行费用",
  333. "relation_no" => $order->no,
  334. "createtime" => time()
  335. ]);
  336. }
  337. // 优惠券|积分 退还
  338. if ($order["voucher_id"] > 0) {
  339. $this->userVoucherModel->update(["status" => \E_BASE_STATUS::Normal], ["id" => $order["voucher_id"]]);
  340. }
  341. $this->userWalletModel->where("id", $wallet["id"])->setDec("score", $order["total_real_amount"]);
  342. $this->userBillModel->saveAll([
  343. $save_bill,
  344. [
  345. "user_id" => $order->user_id,
  346. "currency_type" => \E_USER_BILL_CURRENCY_TYPE::Score,
  347. "change_type" => \E_USER_BILL_CHANGE_TYPE::ScoreOrderRefund[0],
  348. "change" => -$order["total_real_amount"],
  349. "before" => $wallet["score"],
  350. "after" => $wallet["score"] - $order["total_real_amount"],
  351. "reason" => \E_USER_BILL_CHANGE_TYPE::ScoreOrderRefund[1],
  352. "money" => 0,
  353. "give_money" => 0,
  354. "relation_no" => $order->no,
  355. "createtime" => time()
  356. ]]);
  357. Db::commit();
  358. return $this->ok(true);
  359. } catch (Exception $e) {
  360. Db::rollback();
  361. return $this->fail($e->getMessage());
  362. } finally {
  363. UserService::updateGrouping($order["user_id"]);
  364. $walletLock->unlock($wLock);
  365. }
  366. }
  367. // 退单
  368. public function chargeback($order, $identity_type, $chargeback_type)
  369. {
  370. if (!in_array($order["status"], [\E_ORDER_STATUS::Purchase, \E_ORDER_STATUS::Proceed, \E_ORDER_STATUS::WaitFeedback])) {
  371. return $this->fail("订单的状态不支持退款!");
  372. }
  373. }
  374. // 获取账单
  375. public function fetchBill($user_id, $currency_type, $change_types, $page = 1, $size = 10)
  376. {
  377. $paginate = $this->userBillModel->fetchBill($user_id, $currency_type, $change_types, $page, $size);
  378. return [
  379. $paginate->items(),
  380. $paginate->total()
  381. ];
  382. }
  383. public function delBills($user_id, array $bill_ids)
  384. {
  385. $this->userBillModel
  386. ->where("user_id", $user_id)
  387. ->where("id", "in", $bill_ids)
  388. ->update([
  389. "is_del" => 1,
  390. ]);
  391. return $this->ok(true);
  392. }
  393. /**
  394. * 兑换优惠券
  395. * @param $user_id
  396. * @param $voucher_id
  397. */
  398. public function exchangeVoucher($user_id, $voucher_id)
  399. {
  400. $systemVoucher = (new \app\api\model\Voucher)->findById($voucher_id);
  401. if (!$systemVoucher || $systemVoucher->exchange_score <= 0)
  402. return $this->fail("该优惠券不可兑换");
  403. $walletLock = new RedLock();
  404. $wLock = $walletLock->lock(Wallet::UWKey($user_id));
  405. if (!is_array($wLock))
  406. return $this->fail("请稍后再试");
  407. $wallet = $this->userWalletModel->getUserWallet($user_id);
  408. if ($wallet->score - $systemVoucher->exchange_score < 0)
  409. return $this->fail("账户积分余额不足");
  410. Db::startTrans();
  411. try {
  412. $this->userWalletModel->where("id", $wallet->id)->setDec("score", $systemVoucher->exchange_score);
  413. $this->userBillModel->save([
  414. "user_id" => $user_id,
  415. "currency_type" => \E_USER_BILL_CURRENCY_TYPE::Score,
  416. "change_type" => \E_USER_BILL_CHANGE_TYPE::ScoreExchangeVoucher[0],
  417. "change" => -$systemVoucher->exchange_score,
  418. "before" => $wallet->score,
  419. "after" => $wallet->score - $systemVoucher->exchange_score,
  420. "reason" => \E_USER_BILL_CHANGE_TYPE::ScoreExchangeVoucher[1],
  421. "createtime" => time()
  422. ]);
  423. $this->userVoucherModel->save([
  424. "user_id" => $user_id,
  425. "voucher_id" => $voucher_id,
  426. "takeAmount" => $systemVoucher->takeAmount,
  427. "fullAmount" => $systemVoucher->fullAmount,
  428. "status" => \E_BASE_STATUS::Normal,
  429. "expiretime" => time() + (24 * 60 * 60) * $systemVoucher->indate_day,
  430. "createtime" => time(),
  431. "updatetime" => time()
  432. ]);
  433. Db::commit();
  434. return $this->ok(true);
  435. } catch (Exception $e) {
  436. Db::rollback();
  437. return $this->fail($e->getMessage());
  438. } finally {
  439. $walletLock->unlock($wLock);
  440. }
  441. }
  442. public function deposit($user_id, $platform, $amount)
  443. {
  444. $user = $this->userModel->get($user_id);
  445. if (!$user)
  446. return $this->fail("用户不存在");
  447. if ($platform == \E_ORDER_PAY_TYPE::Wechat)
  448. return $this->fail("微信提现功能暂未审核通过!");
  449. if ($platform == \E_ORDER_PAY_TYPE::Wechat) {
  450. if (null == $user["applet_openid"])
  451. return $this->fail("未绑定微信,无法发起提现!", 10001);
  452. } else if ($platform == \E_ORDER_PAY_TYPE::Bank) {
  453. if (mb_strlen($user["opening_bank_name"] ?? '') == 0
  454. || mb_strlen($user["bank_real_name"] ?? '') == 0
  455. || mb_strlen($user["bank_no"] ?? '') == 0
  456. )
  457. return $this->fail("未绑定银行卡信息,无法发起提现", 10002);
  458. }
  459. $wallet = $this->userWalletModel->getUserWallet($user_id);
  460. $deposit_min_amount = config("site.user_deposit_min_amount");
  461. if ($wallet["money"] < $amount)
  462. return $this->fail("数额不足,无法发起提现");
  463. if ($amount < $deposit_min_amount)
  464. return $this->fail("提现最小金额为:{$deposit_min_amount}");
  465. $record = (new Record())->where([
  466. "apply_status" => \E_BASE_STATUS::Default,
  467. "deposit_status" => \E_BASE_STATUS::Default,
  468. "identity_type" => \E_IDENTITY_TYPE::User,
  469. "user_id" => $user_id
  470. ])->find();
  471. if ($record) {
  472. return $this->fail("您上一笔提现未通过审核,等待上一笔提现后再次发起");
  473. }
  474. (new Record())->save([
  475. "no" => "TXU" . $user_id . time(),
  476. "platform" => $platform,
  477. "identity_type" => \E_IDENTITY_TYPE::User,
  478. "user_id" => $user_id,
  479. "deposit_amount" => $amount,
  480. "service_charge_rate" => config("site.service_charge_rate"),
  481. "apply_status" => \E_BASE_STATUS::Default,
  482. "deposit_status" => \E_BASE_STATUS::Default,
  483. "operation_id" => $user_id,
  484. "createtime" => time(),
  485. "updatetime" => time(),
  486. "opening_bank_name" => $user["opening_bank_name"],
  487. "bank_real_name" => $user["bank_real_name"],
  488. "bank_no" => $user["bank_no"],
  489. ]);
  490. return $this->ok(true, "申请提现成功,请耐心等待管理员审核!");
  491. }
  492. }