RechargeClass.php 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028
  1. <?php
  2. namespace bizHd\recharge\classes;
  3. use biz\shop\classes\ShopClass;
  4. use bizGhs\shop\classes\ShopMoneyClass;
  5. use bizHd\balance\classes\BalanceChangeClass;
  6. use bizHd\balance\classes\BalanceGiveChangeClass;
  7. use bizHd\balance\classes\BalancePayChangeClass;
  8. use bizHd\custom\classes\CustomClass;
  9. use bizHd\custom\classes\HdClass;
  10. use bizHd\hb\classes\HbClass;
  11. use bizHd\hb\classes\HbManageClass;
  12. use bizHd\order\classes\OrderClass;
  13. use bizHd\order\classes\SettleClass;
  14. use bizHd\shop\classes\ShopExtClass;
  15. use bizHd\shop\classes\MainClass;
  16. use bizHd\user\classes\UserGrowthClass;
  17. use bizHd\user\classes\UserIntegralClass;
  18. use common\components\dateUtil;
  19. use common\components\dict;
  20. use common\components\lakala\Lakala;
  21. use common\components\noticeUtil;
  22. use common\components\orderSn;
  23. use common\components\util;
  24. use Yii;
  25. use bizHd\base\classes\BaseClass;
  26. use yii\db\Expression;
  27. class RechargeClass extends BaseClass
  28. {
  29. public static $baseFile = '\bizHd\recharge\models\Recharge';
  30. const STAT_CACHE_TTL = 300; //统计数据缓存时长
  31. //充值记录
  32. public static function getRechargeList($where, $channelKey = 'all', $io = null)
  33. {
  34. switch ($channelKey) {
  35. case 'system':
  36. $where['onlinePay'] = 2;
  37. break;
  38. case 'online_0':
  39. $where['onlinePay'] = 2;
  40. $where['payWay'] = 0;
  41. break;
  42. case 'online_1':
  43. $where['onlinePay'] = 2;
  44. $where['payWay'] = 1;
  45. break;
  46. case 'pay_0':
  47. $where['onlinePay!='] = 2;
  48. $where['payWay'] = 0;
  49. break;
  50. case 'pay_1':
  51. $where['onlinePay!='] = 2;
  52. $where['payWay'] = 1;
  53. break;
  54. case 'pay_4':
  55. $where['onlinePay!='] = 2;
  56. $where['payWay'] = 4;
  57. break;
  58. case 'pay_5':
  59. $where['onlinePay!='] = 2;
  60. $where['payWay'] = 5;
  61. break;
  62. case 'other':
  63. $where['onlinePay!='] = 2;
  64. $where['payWay'] = ['not in', [0, 1, 4, 5]];
  65. break;
  66. default:
  67. break;
  68. }
  69. if ($io === null || $io === '') {
  70. return self::getList('*', $where, 'addTime DESC,id DESC');
  71. }
  72. $model = self::getModel();
  73. $query = $model->conditionQuery($where)->select('*');
  74. if ((int)$io === 1) {
  75. $query->andWhere('IFNULL(amount, 0) + IFNULL(giveAmount, 0) > 0');
  76. } else {
  77. $query->andWhere(['isRefund' => 1]);
  78. }
  79. return self::getRechargePageList($query, 'addTime DESC,id DESC');
  80. }
  81. private static function getRechargePageList($query, $order = '')
  82. {
  83. $get = Yii::$app->request->get();
  84. $page = isset($get['page']) ? $get['page'] : 1;
  85. Yii::$app->params['page'] = $page;
  86. $pageSize = isset($get['pageSize']) && !empty($get['pageSize']) ? $get['pageSize'] : Yii::$app->params['pageSize'];
  87. $offset = ($page - 1) * $pageSize;
  88. $clone = clone $query;
  89. $count = $clone->count();
  90. $totalPage = ceil($count / $pageSize);
  91. $data['totalNum'] = $count;
  92. $data['totalPage'] = $totalPage;
  93. $data['moreData'] = $totalPage > $page ? 1 : 0;
  94. if (!empty($order)) {
  95. $query->orderBy($order);
  96. }
  97. $data['list'] = $query->offset($offset)->limit($pageSize)->asArray()->all();
  98. return $data;
  99. }
  100. public static function addRecharge($data)
  101. {
  102. //创建充值单时,要把过去的充值单和结账单取消掉
  103. $customId = $data['customId'] ?? 0;
  104. if (empty($customId)) {
  105. util::fail('没有客户信息');
  106. }
  107. $shopId = $data['shopId'] ?? 0;
  108. $hasList = self::getAllByCondition(['customId' => $customId, 'status' => 0], null, '*', null, true);
  109. if (!empty($hasList)) {
  110. $shop = \bizHd\merchant\classes\ShopClass::getById($shopId, true);
  111. if (empty($shop)) {
  112. util::fail('没有找到门店');
  113. }
  114. foreach ($hasList as $k => $recharge) {
  115. $payRequest = $recharge->payRequest ?? 0;
  116. if ($payRequest == 1 || $payRequest == 2) {
  117. $closeOrderSn = $recharge->orderSn;
  118. $merchantPrivateKeyPath = Yii::getAlias("@vendor/lakala") . '/production/api_private_key.pem';
  119. $lklCertificatePath = Yii::getAlias("@vendor/lakala") . '/production/lkl-apigw-v1.cer';
  120. $params = [
  121. 'appid' => 'OP00002119',
  122. 'serial_no' => '018b08cfddbd',
  123. 'merchant_no' => $shop->lklSjNo,
  124. 'term_no' => $shop->lklScanTermNo,
  125. 'merchantPrivateKeyPath' => $merchantPrivateKeyPath,
  126. 'lklCertificatePath' => $lklCertificatePath,
  127. ];
  128. $laResource = new Lakala($params);
  129. $closeParams = ['orderSn' => $closeOrderSn];
  130. if ($payRequest == 1) {
  131. //聚合支付
  132. $response = $laResource->close($closeParams);
  133. if (!isset($response['code']) || $response['code'] != 'BBS00000') {
  134. $msg = $response['msg'] ?? '';
  135. noticeUtil::push('充值换单,聚合支付,没有成功,单号:' . $closeOrderSn . ',关单没有成功,编号966558,' . $msg, '15280215347');
  136. //util::fail('老订单取消失败,请联系管理员');
  137. }
  138. } else {
  139. //收银台支付
  140. $response = $laResource->cashClose($closeParams);
  141. if (!isset($response['code']) || $response['code'] != '000000') {
  142. $msg = $response['msg'] ?? '';
  143. noticeUtil::push('充值换单,收银台的单号:' . $closeOrderSn . ',关单没有成功,编号66996,' . $msg, '15280215347');
  144. //util::fail('旧订单取消失败,请联系管理员');
  145. }
  146. }
  147. }
  148. $recharge->status = 2;
  149. $recharge->save();
  150. $settleId = $recharge->settleId ?? 0;
  151. $settle = SettleClass::getById($settleId, true);
  152. if (!empty($settle)) {
  153. $settle->status = 3;
  154. $settle->save();
  155. }
  156. }
  157. }
  158. return self::add($data, true);
  159. }
  160. //第三支付后流程 ssh 2021.4.27
  161. public static function thirdPay($payWay, $orderSn, $totalFee, $attach, $transactionId = '')
  162. {
  163. $recharge = RechargeClass::getByCondition(['orderSn' => $orderSn], true);
  164. if (empty($recharge)) {
  165. noticeUtil::push('充值付款成功,收到回调,但没有找到充值记录,orderSn:' . $orderSn, '15280215347');
  166. return false;
  167. }
  168. if ($totalFee != $recharge->amount) {
  169. noticeUtil::push("充值付款成功,收到回调,但金额不一致 {$totalFee} {$recharge->amount},orderSn:" . $orderSn, '15280215347');
  170. return false;
  171. }
  172. if ($recharge->payStatus == 1) {
  173. noticeUtil::push("充值付款成功,收到回调,但订单是已经支付过了,orderSn:" . $orderSn, '15280215347');
  174. return false;
  175. }
  176. $id = $recharge->id;
  177. $recharge = RechargeClass::getLockById($id);
  178. $params = [
  179. 'onlinePay' => 2,
  180. 'side' => 1,
  181. ];
  182. $recharge->returnCode = $transactionId;
  183. $recharge->save(false);
  184. self::complete($recharge, $payWay, $params);
  185. return $recharge;
  186. }
  187. //处理充值流程 ssh 20250321
  188. public static function complete($recharge, $payWay, $params)
  189. {
  190. $onlinePay = $params['onlinePay'];
  191. //0 花店端操作,1 客户端操作
  192. $side = $params['side'] ?? 0;
  193. $recharge->onlinePay = $onlinePay;
  194. $recharge->payWay = $payWay;
  195. $recharge->payStatus = 1;
  196. $recharge->status = 1;
  197. $recharge->save();
  198. $shopId = $recharge->shopId;
  199. $mainId = $recharge->mainId;
  200. $hdId = $recharge->hdId;
  201. $hdInfo = HdClass::getById($hdId, true);
  202. $hdName = $hdInfo->name ?? "";
  203. $amount = $recharge->amount;
  204. $staffId = $recharge->staffId;
  205. $payWay = $recharge->payWay;
  206. $staffName = $recharge->staffName;
  207. $customId = $recharge->customId;
  208. $customName = $recharge->customName;
  209. $capitalType = dict::getDict('capitalType', 'xhRecharge', 'id');
  210. $relateId = $recharge->id;
  211. $shOrderId = $recharge->shOrderId ?? 0;
  212. $shOrderSn = $recharge->shOrderSn ?? '';
  213. $custom = CustomClass::getLockById($customId);
  214. if (empty($custom)) {
  215. $msg = '充值回调失败,没有找到客户';
  216. util::fail($msg);
  217. }
  218. $hd = HdClass::getLockById($hdId);
  219. if (empty($hd)) {
  220. $msg = '充值回调失败,没有找到花店';
  221. util::fail($msg);
  222. }
  223. $shop = ShopClass::getById($shopId, true);
  224. $rechargeWeal = $shop->rechargeWeal ?? 0; //充值赠送: 0未设置 2充值送红包--红包非余额支付可用,3充值送红包--红包非余额支付不可用
  225. $balanceGive = 0;
  226. //如果客户有欠款、结账充值、售后充值、售后付款充值,不参与优惠
  227. if ($custom->balance >= 0 && $recharge->settleId == 0 && $recharge->shOrderId == 0 && $recharge->refundOrderId == 0) {
  228. if ($rechargeWeal == 1) {
  229. $weal = RechargeSqClass::getAllByCondition(['shopId' => $shopId], 'recharge ASC', '*');
  230. if (!empty($weal)) {
  231. //优惠方式2:充多少送多少
  232. foreach ($weal as $k => $v) {
  233. $thisRecharge = $v['recharge'] ?? 0;
  234. $thisGive = $v['give'] ?? 0;
  235. if ($amount >= $thisRecharge) {
  236. $recharge->giveAmount = $thisGive;
  237. $recharge->save();
  238. //增加赠送的金额
  239. $balanceGive = $thisGive;
  240. }
  241. }
  242. }
  243. } elseif ($rechargeWeal == 2 || $rechargeWeal == 3) {
  244. //充值送红包
  245. // 1.根据充值金额,查询符合条件的红包规则
  246. $maxHbRule = RechargeShbClass::getByCondition(['shopId' => $shopId, 'amount<=' => $amount], false, 'allHbAmount DESC');
  247. if (!empty($maxHbRule)) {
  248. $currentTime = time();
  249. $rulesJson = $maxHbRule['rules'] ?? '';
  250. $rulesArr = json_decode($rulesJson, true);
  251. if (is_array($rulesArr) && !empty($rulesArr)) {
  252. foreach ($rulesArr as $rule) {
  253. $hbAmount = $rule['hbAmount'];
  254. $hbNum = $rule['hbNum'];
  255. $miniCost = $rule['miniCost'];
  256. $duration = $rule['duration'];
  257. $effectiveType = $rule['effectiveType'] ?? 2;
  258. $effectiveDate = $rule['effectiveDate'] ?? '';
  259. $effectiveDays = $rule['effectiveDays'] ?? 0;
  260. if ($effectiveType == 1 && !empty($effectiveDate) && $effectiveDate != '0000-00-00') {
  261. $beginTime = strtotime($effectiveDate);
  262. if (!$beginTime) {
  263. $beginTime = $currentTime;
  264. }
  265. } else {
  266. $beginTime = $currentTime + 86400 * $effectiveDays;
  267. }
  268. //给用户生成红包
  269. $data = [
  270. 'mainId' => $mainId,
  271. 'shopId' => $shopId,
  272. 'userId' => $recharge->userId,
  273. 'customId' => $customId,
  274. 'amount' => $hbAmount,
  275. 'minConsume' => $miniCost,
  276. 'beginTime' => $beginTime,
  277. 'endTime' => $duration == 0 ? 4102416000 : ($beginTime + 86400 * $duration),
  278. 'willTime' => $duration == 0 ? 4102416000 : ($beginTime + 86400 * $duration),
  279. 'duration' => $duration,
  280. 'getType' => 2, //取得方式 0新人领取 1门店发放 2充值赠送
  281. 'remark' => '充值赠送红包',
  282. ];
  283. $manageData = [
  284. 'getType' => 2,
  285. 'getTargetId' => 0,
  286. 'createStaffId' => 0,
  287. 'createStaffName' => '',
  288. 'remark' => $data['remark'] ?? '',
  289. ];
  290. for ($i = 0; $i < $hbNum; $i++) {
  291. $hb = HbClass::add($data);
  292. $manageData['hbId'] = $hb['id'];
  293. HbManageClass::add($manageData);
  294. }
  295. }
  296. }
  297. }
  298. } else {
  299. //其他
  300. }
  301. }
  302. //是否开启充值满多少成为 VIP
  303. $shopExt = ShopExtClass::getByCondition(['shopId' => $shopId], false, 'reachVip');
  304. if (!empty($shopExt) && $shopExt['reachVip'] > 0.00 && bccomp($amount, $shopExt['reachVip'], 2) >= 0) {
  305. $custom->vip = 1;
  306. }
  307. $balancePay = $amount;
  308. if ($onlinePay == 2) {
  309. $event = $payWay == 0 ? '线上微信 充值' : '线上支付宝 充值';
  310. } else {
  311. $payWayName = dict::getDict('payWayName');
  312. $channel = $payWayName[$payWay] ?? '微信';
  313. $event = "线下{$channel} 充值";
  314. }
  315. $event .= floatval($balancePay);
  316. //赠送的余额
  317. if ($balanceGive > 0) {
  318. $event .= "(送" . floatval($balanceGive) . ")";
  319. $custom->balanceGive = bcadd($custom->balanceGive, $balanceGive, 2);
  320. $hd->balanceGive = bcadd($hd->balanceGive, $balanceGive, 2);
  321. }
  322. //充值的余额
  323. $beforeBalancePay = $custom->balancePay;
  324. $custom->balancePay = bcadd($custom->balancePay, $balancePay, 2);
  325. $hd->balancePay = bcadd($hd->balancePay, $balancePay, 2);
  326. //总余额
  327. $totalBalance = bcadd($balancePay, $balanceGive, 2);
  328. $custom->balance = bcadd($custom->balance, $totalBalance, 2);
  329. $hd->balance = bcadd($hd->balance, $totalBalance, 2);
  330. $changeAmount = bccomp($beforeBalancePay, 0, 2) === -1 ? $custom->balancePay : $balancePay; // 根据余额欠款,计算变化金额,用于计算积分与成长值
  331. $addIntegralAndGrowth = bccomp($changeAmount, 0, 2) === 1; // 如果变化金额大于0,则计算积分与成长值
  332. if ($addIntegralAndGrowth) {
  333. //积分与成长值
  334. $custom->integral = bcadd($custom->integral, $changeAmount, 2);
  335. $custom->growth = bcadd($custom->growth, $changeAmount, 2);
  336. // 由于没有更新 buyAmount,所以得额外添加更新等级
  337. $memberData = CustomClass::getCustomExpenseLevel($custom->growth, $mainId); // 设置等级多处需要同步修改的,请搜索:getCustomExpenseLevel
  338. CustomClass::updateById($customId, [
  339. 'member' => (int) ($memberData['level'] ?? 0),
  340. 'memberName' => (string) ($memberData['name'] ?? ''),
  341. 'discount' => bcdiv(floatval($memberData['discount']), 10, 2)
  342. ]);
  343. }
  344. $custom->save();
  345. $hd->save();
  346. if ($hd->balance != $custom->balance) {
  347. $msg = '充值金额不一致。编号:' . floatval($hd->balance) . ' / ' . floatval($custom->balance);
  348. Yii::info($msg);
  349. util::fail($msg);
  350. }
  351. if ($hd->balancePay != $custom->balancePay) {
  352. $msg = '充值金额有异常。编号:' . floatval($hd->balancePay) . ' / ' . floatval($custom->balancePay);
  353. Yii::info($msg);
  354. util::fail($msg);
  355. }
  356. if ($hd->balanceGive != $custom->balanceGive) {
  357. $msg = '充值金额异常。编号:' . floatval($hd->balanceGive) . ' / ' . floatval($custom->balanceGive);
  358. Yii::info($msg);
  359. util::fail($msg);
  360. }
  361. $addBalance = bcadd($hd->balanceGive, $hd->balancePay, 2);
  362. if (floatval($hd->balance) != floatval($addBalance)) {
  363. util::fail('充值金额有问题哦,编号:' . $customId);
  364. }
  365. $settleId = $recharge->settleId;
  366. $settleAmount = 0;
  367. $settleNo = '';
  368. $realClearAmount = 0;
  369. if (!empty($settleId)) {
  370. //如果有结账单,按结账单销账
  371. $set = SettleClass::getById($settleId, true);
  372. $res = SettleClass::clearSettle($set, $recharge);
  373. $realClearAmount = $res['totalClearAmount'] ?? 0;
  374. $settleAmount = $set->actPrice ?? 0;
  375. $settleNo = $set->orderSn ?? '';
  376. } else {
  377. //没有结账单,按顺序去销订单欠款
  378. $orderList = OrderClass::getAllByCondition(['customId' => $customId, 'debt' => 1], 'id asc', '*', null, true);
  379. if (!empty($orderList)) {
  380. $lastRemainDebtAmount = 0;
  381. $needClearIdMap = [];
  382. $hasOver = false;
  383. foreach ($orderList as $order) {
  384. $orderId = $order->id ?? 0;
  385. $orderSn = $order->orderSn ?? '';
  386. //销订单全部的欠款!!!
  387. $remainDebtPrice = $order->remainDebtPrice ?? 0;
  388. $currentTotalDebtAmount = bcadd($lastRemainDebtAmount, $remainDebtPrice, 2);
  389. if ($currentTotalDebtAmount > $totalBalance) {
  390. if (!$hasOver) {
  391. $lastClearOrderAmount = bcsub($totalBalance, $lastRemainDebtAmount, 2);
  392. if ($lastClearOrderAmount > 0) {
  393. //销订单部分的欠款!!!
  394. $lastClearOrderId = $orderId;
  395. $needClearIdMap[] = ['orderId' => $lastClearOrderId, 'clearAmount' => $lastClearOrderAmount, 'orderSn' => $orderSn];
  396. }
  397. }
  398. $hasOver = true;
  399. continue;
  400. }
  401. $lastRemainDebtAmount = bcadd($lastRemainDebtAmount, $remainDebtPrice, 2);
  402. $needClearIdMap[] = ['orderId' => $orderId, 'clearAmount' => $remainDebtPrice, 'orderSn' => $orderSn];
  403. }
  404. if (!empty($needClearIdMap)) {
  405. //创建结账单,并且销结账单
  406. $staffId = $recharge->staffId ?? 0;
  407. $staffName = $recharge->staffName ?? '';
  408. $settleParams = ['staffId' => $staffId, 'staffName' => $staffName];
  409. $set = SettleClass::addSettle($needClearIdMap, $shop, $custom, $hd, $settleParams);
  410. $settleId = $set->id;
  411. $settleNo = $set->orderSn ?? '';
  412. $settleAmount = $set->actPrice ?? 0;
  413. $res = SettleClass::clearSettle($set);
  414. $realClearAmount = $res['totalClearAmount'] ?? 0;
  415. }
  416. }
  417. }
  418. if ($settleId > 0) {
  419. if (floatval($totalBalance) != floatval($realClearAmount) && $hd->balance < 0) {
  420. $rechargeId = $recharge->id;
  421. noticeUtil::push("注意,充值单,结账金额不一致:getBalance:{$totalBalance} totalAmount:{$realClearAmount},recharge:{$rechargeId}", '15280215347');
  422. util::fail('充值和结账金额异常哈');
  423. }
  424. if (floatval($totalBalance) != floatval($settleAmount) && $hd->balance < 0) {
  425. $rechargeId = $recharge->id;
  426. noticeUtil::push("注意,充值单,结账单不一致:getBalance:{$totalBalance} settleAmount:{$settleAmount},settleId:{$settleId} recharge:{$rechargeId}", '15280215347');
  427. util::fail('充值和结账金额异常呢');
  428. }
  429. if (floatval($realClearAmount) != floatval($settleAmount)) {
  430. $rechargeId = $recharge->id;
  431. noticeUtil::push("注意,结账金额有问题,编号 settleId:{$settleId} recharge:{$rechargeId}", '15280215347');
  432. util::fail('充值结账金额异常呢');
  433. }
  434. }
  435. $recharge->settleId = $settleId;
  436. $recharge->settleAmount = $settleAmount;
  437. $recharge->settleNo = $settleNo;
  438. $recharge->save();
  439. $remark = $recharge->remark;
  440. $change = [
  441. 'customId' => $customId,
  442. 'customName' => $customName,
  443. 'relateId' => $relateId,
  444. 'onlinePay' => $onlinePay,
  445. 'capitalType' => $capitalType,
  446. 'giveAmount' => $balanceGive,
  447. 'amount' => $totalBalance,
  448. 'balance' => $hd->balance,
  449. 'io' => 1,
  450. 'side' => $side,
  451. 'payWay' => $payWay,
  452. 'event' => $event,
  453. 'staffId' => $staffId,
  454. 'staffName' => $staffName,
  455. 'shopId' => $shopId,
  456. 'mainId' => $mainId,
  457. 'settleId' => $settleId,
  458. 'settleNo' => $settleNo,
  459. 'settleAmount' => $settleAmount,
  460. 'remark' => $remark,
  461. 'shOrderId' => $shOrderId,
  462. 'shOrderSn' => $shOrderSn,
  463. 'hdId' => $hdId,
  464. 'hdName' => $hdName,
  465. ];
  466. $returnChange = BalanceChangeClass::add($change, true);
  467. $payChange = [
  468. 'customId' => $customId,
  469. 'customName' => $customName,
  470. 'relateId' => $relateId,
  471. 'onlinePay' => $onlinePay,
  472. 'capitalType' => $capitalType,
  473. 'amount' => $balancePay,
  474. 'balance' => $hd->balancePay,
  475. 'io' => 1,
  476. 'side' => $side,
  477. 'payWay' => $payWay,
  478. 'event' => $event,
  479. 'staffId' => $staffId,
  480. 'staffName' => $staffName,
  481. 'shopId' => $shopId,
  482. 'mainId' => $mainId,
  483. 'settleId' => $settleId,
  484. 'settleNo' => $settleNo,
  485. 'settleAmount' => $settleAmount,
  486. 'remark' => $remark,
  487. 'shOrderId' => $shOrderId,
  488. 'shOrderSn' => $shOrderSn,
  489. 'hdId' => $hdId,
  490. 'hdName' => $hdName,
  491. ];
  492. BalancePayChangeClass::add($payChange, true);
  493. if ($balanceGive > 0) {
  494. $givChange = [
  495. 'customId' => $customId,
  496. 'customName' => $customName,
  497. 'relateId' => $relateId,
  498. 'onlinePay' => $onlinePay,
  499. 'capitalType' => $capitalType,
  500. 'amount' => $balanceGive,
  501. 'balance' => $hd->balanceGive,
  502. 'io' => 1,
  503. 'side' => $side,
  504. 'payWay' => $payWay,
  505. 'event' => $event,
  506. 'staffId' => $staffId,
  507. 'staffName' => $staffName,
  508. 'shopId' => $shopId,
  509. 'mainId' => $mainId,
  510. 'remark' => $remark,
  511. 'hdId' => $hdId,
  512. 'hdName' => $hdName,
  513. ];
  514. BalanceGiveChangeClass::add($givChange, true);
  515. }
  516. if ($recharge->onlinePay == 2) {
  517. //如果线上充值,门店余额增加
  518. $shop = ShopClass::getLockById($shopId);
  519. $main = MainClass::getLockById($mainId);
  520. ShopClass::skRechargeAddBalance($main, $shop, $recharge);
  521. } else {
  522. if ($recharge->payWay == 4) {
  523. //现在现金充值要增加现金
  524. $main = MainClass::getLockById($mainId);
  525. $cashData = [];
  526. $cashData['mainId'] = $mainId;
  527. $cashData['sjId'] = 0;
  528. $cashData['amount'] = $amount;
  529. $cashData['staffId'] = $staffId;
  530. $cashData['staffName'] = $staffName;
  531. $cashData['customName'] = $customName;
  532. $cashData['ptStyle'] = dict::getDict('ptStyle', 'hd');
  533. ShopMoneyClass::skRecharge($cashData, $main);
  534. }
  535. }
  536. if ($addIntegralAndGrowth) {
  537. $createTime = date('Y-m-d H:i:s');
  538. //成长值变动记录
  539. $growthData = [
  540. 'shopId' => $shopId,
  541. 'userId' => $custom->userId,
  542. 'userName' => $custom->name,
  543. 'event' => $event,
  544. 'relateId' => $relateId,
  545. 'relateType' => 0,
  546. 'num' => $changeAmount,
  547. 'growth' => $custom->growth,
  548. 'staffId' => $staffId, //操作人(员工id)
  549. 'staffName' => $staffName, //员工名称
  550. 'createTime' => $createTime,
  551. ];
  552. UserGrowthClass::add($growthData);
  553. //积分变动记录
  554. $integralData = [
  555. 'shopId' => $shopId,
  556. 'userId' => $custom->userId,
  557. 'userName' => $custom->name,
  558. 'event' => $event,
  559. 'relateId' => $relateId,
  560. 'relateType' => 0,
  561. 'num' => $changeAmount,
  562. 'integral' => $custom->integral,
  563. 'staffId' => $staffId, //操作人(员工id)
  564. 'staffName' => $staffName, //员工名称
  565. 'createTime' => $createTime,
  566. ];
  567. UserIntegralClass::add($integralData);
  568. }
  569. return $returnChange;
  570. }
  571. /**
  572. * 花店后台:对已付款客户充值单退款。线上原路退,线下扣减客户余额;xhRecharge.isRefund 标记退款状态。
  573. */
  574. public static function refundPaidRecharge($rechargeId, $shop, $staff)
  575. {
  576. $rechargeId = intval($rechargeId);
  577. if ($rechargeId <= 0) {
  578. util::fail('参数错误');
  579. }
  580. $recharge = self::getLockById($rechargeId);
  581. if (empty($recharge)) {
  582. util::fail('没有找到充值记录');
  583. }
  584. if (intval($recharge->mainId ?? 0) !== intval($shop->mainId ?? 0) || intval($recharge->shopId ?? 0) !== intval($shop->id ?? 0)) {
  585. util::fail('没有权限操作该充值记录');
  586. }
  587. if (intval($recharge->payStatus ?? 0) !== 1 || intval($recharge->status ?? 0) !== 1) {
  588. util::fail('仅已付款的充值可退款');
  589. }
  590. if (self::isRechargeRefunded($recharge)) {
  591. util::fail('该充值已退款');
  592. }
  593. $amount = bcadd((string)($recharge->amount ?? '0'), '0', 2);
  594. if (bccomp($amount, '0', 2) <= 0) {
  595. util::fail('充值金额有误');
  596. }
  597. $customId = intval($recharge->customId ?? 0);
  598. $hdId = intval($recharge->hdId ?? 0);
  599. $custom = CustomClass::getLockById($customId);
  600. $hd = HdClass::getLockById($hdId);
  601. if (empty($custom) || empty($hd)) {
  602. util::fail('没有找到客户余额账户');
  603. }
  604. $isOnline = intval($recharge->onlinePay ?? 0) === 2;
  605. $payWay = intval($recharge->payWay ?? -1);
  606. $wxPay = intval(dict::getDict('payWay', 'wxPay'));
  607. $aliPay = intval(dict::getDict('payWay', 'alipay'));
  608. $cashPay = intval(dict::getDict('payWay', 'cash'));
  609. $onlineMain = null;
  610. if ($isOnline) {
  611. if ($payWay !== $wxPay && $payWay !== $aliPay) {
  612. util::fail('暂不支持该支付方式原路退款');
  613. }
  614. $returnCode = trim((string)($recharge->returnCode ?? ''));
  615. if ($returnCode === '') {
  616. util::fail('缺少支付流水号,无法原路退款');
  617. }
  618. $onlineMain = MainClass::getLockById($shop->mainId ?? 0);
  619. if (empty($onlineMain)) {
  620. util::fail('没有找到资产信息');
  621. }
  622. if (bccomp((string)($onlineMain->balance ?? '0'), $amount, 2) < 0) {
  623. util::fail('门店余额不足,无法退款');
  624. }
  625. self::refundOnlineByLakala($recharge, $shop, $amount, $returnCode);
  626. }
  627. $giveAmount = bcadd((string)($recharge->giveAmount ?? '0'), '0', 2);
  628. if (bccomp($giveAmount, '0', 2) < 0) {
  629. $giveAmount = '0.00';
  630. }
  631. $currentGive = bcadd((string)($hd->balanceGive ?? '0'), '0', 2);
  632. $deductGive = '0.00';
  633. if (bccomp($giveAmount, '0', 2) === 1 && bccomp($currentGive, '0', 2) === 1) {
  634. $deductGive = bccomp($currentGive, $giveAmount, 2) >= 0 ? $giveAmount : $currentGive;
  635. }
  636. $deductPay = bcadd($amount, bcsub($giveAmount, $deductGive, 2), 2);
  637. $totalDeduct = bcadd($deductPay, $deductGive, 2);
  638. $custom->balancePay = bcsub((string)($custom->balancePay ?? '0'), $deductPay, 2);
  639. $hd->balancePay = bcsub((string)($hd->balancePay ?? '0'), $deductPay, 2);
  640. if (bccomp($deductGive, '0', 2) === 1) {
  641. $custom->balanceGive = bcsub((string)($custom->balanceGive ?? '0'), $deductGive, 2);
  642. $hd->balanceGive = bcsub((string)($hd->balanceGive ?? '0'), $deductGive, 2);
  643. }
  644. $custom->balance = bcsub((string)($custom->balance ?? '0'), $totalDeduct, 2);
  645. $hd->balance = bcsub((string)($hd->balance ?? '0'), $totalDeduct, 2);
  646. $custom->save(false);
  647. $hd->save(false);
  648. if (floatval($custom->balance) != floatval($hd->balance) || floatval($custom->balancePay) != floatval($hd->balancePay) || floatval($custom->balanceGive) != floatval($hd->balanceGive)) {
  649. util::fail('账户余额有问题,请联系管理员');
  650. }
  651. $staffId = intval($staff->id ?? 0);
  652. $staffName = (string)($staff->name ?? '');
  653. $capitalType = dict::getDict('capitalType', 'customRechargeRefund', 'id');
  654. $event = '充值退款(操作人:' . $staffName . ')单号:' . ($recharge->orderSn ?? '');
  655. $changeBase = [
  656. 'hdId' => $hdId,
  657. 'hdName' => $hd->name ?? ($recharge->hdName ?? ''),
  658. 'customId' => $customId,
  659. 'customName' => $custom->name ?? ($recharge->customName ?? ''),
  660. 'relateId' => $rechargeId,
  661. 'onlinePay' => $recharge->onlinePay ?? 1,
  662. 'capitalType' => $capitalType,
  663. 'side' => 0,
  664. 'payWay' => $payWay,
  665. 'event' => $event,
  666. 'staffId' => $staffId,
  667. 'staffName' => $staffName,
  668. 'shopId' => $recharge->shopId ?? 0,
  669. 'mainId' => $recharge->mainId ?? 0,
  670. 'remark' => '',
  671. ];
  672. BalanceChangeClass::add(array_merge($changeBase, [
  673. 'amount' => $totalDeduct,
  674. 'balance' => $hd->balance,
  675. 'io' => 0,
  676. ]), true);
  677. BalancePayChangeClass::add(array_merge($changeBase, [
  678. 'amount' => $deductPay,
  679. 'balance' => $hd->balancePay,
  680. 'io' => 0,
  681. ]), true);
  682. if (bccomp($deductGive, '0', 2) === 1) {
  683. BalanceGiveChangeClass::add(array_merge($changeBase, [
  684. 'amount' => $deductGive,
  685. 'balance' => $hd->balanceGive,
  686. 'io' => 0,
  687. ]), true);
  688. }
  689. if ($isOnline) {
  690. $main = $onlineMain ?: MainClass::getLockById($shop->mainId ?? 0);
  691. if (empty($main)) {
  692. util::fail('没有找到资产信息');
  693. }
  694. ShopClass::customRechargeRefundReduceBalance($main, $shop, $recharge, dict::getDict('capitalType', 'xhRecharge', 'id'), $staffName);
  695. } elseif ($payWay === $cashPay) {
  696. self::cashRechargeRefundReduceMoney($recharge, $shop, $staffName, $amount);
  697. }
  698. self::markRechargeRefunded($recharge);
  699. $recharge->balance = $hd->balance;
  700. $recharge->save(false);
  701. return $recharge;
  702. }
  703. protected static function cashRechargeRefundReduceMoney($recharge, $shop, $staffName, $amount)
  704. {
  705. $main = MainClass::getLockById($shop->mainId ?? 0);
  706. if (empty($main)) {
  707. util::fail('没有找到现金账户');
  708. }
  709. if (bccomp((string)($main->money ?? '0'), $amount, 2) < 0) {
  710. util::fail('现金不足' . floatval($amount) . '元');
  711. }
  712. $main->money = bcsub((string)$main->money, $amount, 2);
  713. $main->save(false);
  714. ShopMoneyClass::addData([
  715. 'mainId' => $shop->mainId ?? 0,
  716. 'sjId' => 0,
  717. 'amount' => $amount,
  718. 'balance' => $main->money,
  719. 'io' => 0,
  720. 'staffId' => $recharge->staffId ?? 0,
  721. 'staffName' => $staffName,
  722. 'customName' => $recharge->customName ?? '',
  723. 'ptStyle' => dict::getDict('ptStyle', 'hd'),
  724. 'capitalType' => dict::getDict('capitalType', 'xhRecharge', 'id'),
  725. 'event' => ($recharge->customName ?? '客户') . '现金充值退款' . floatval($amount) . '元',
  726. 'remark' => '操作人:' . $staffName,
  727. ]);
  728. }
  729. protected static function refundOnlineByLakala($recharge, $shop, $amount, $returnCode)
  730. {
  731. $termNo = $shop->lklScanTermNo ?? '';
  732. if (intval($recharge->payWay ?? 0) === intval(dict::getDict('payWay', 'alipay'))) {
  733. $termNo = $shop->lklB2BTermNo ?? '';
  734. }
  735. if (empty($shop->lklSjNo) || empty($termNo)) {
  736. util::fail('门店未配置拉卡拉商户信息');
  737. }
  738. $merchantPrivateKeyPath = Yii::getAlias('@vendor/lakala') . '/production/api_private_key.pem';
  739. $lklCertificatePath = Yii::getAlias('@vendor/lakala') . '/production/lkl-apigw-v1.cer';
  740. $laResource = new Lakala([
  741. 'appid' => 'OP00002119',
  742. 'serial_no' => '018b08cfddbd',
  743. 'merchant_no' => $shop->lklSjNo,
  744. 'term_no' => $termNo,
  745. 'merchantPrivateKeyPath' => $merchantPrivateKeyPath,
  746. 'lklCertificatePath' => $lklCertificatePath,
  747. ]);
  748. $response = $laResource->refund([
  749. 'refundSn' => orderSn::getRechargeSn(),
  750. 'orderSn' => $recharge->orderSn ?? '',
  751. 'refundAmount' => bcmul($amount, 100),
  752. 'refundReason' => '充值退款',
  753. 'thirdNo' => $returnCode,
  754. ]);
  755. if (!isset($response['code']) || $response['code'] != 'BBS00000') {
  756. util::fail('原路退款失败:' . ($response['msg'] ?? '退款失败'));
  757. }
  758. }
  759. protected static function isRechargeRefunded($recharge)
  760. {
  761. if (empty($recharge)) {
  762. return true;
  763. }
  764. if (self::modelHasAttr($recharge, 'isRefund')) {
  765. return intval($recharge->isRefund ?? 0) === 1;
  766. }
  767. return false;
  768. }
  769. protected static function markRechargeRefunded($recharge)
  770. {
  771. if (empty($recharge) || !self::modelHasAttr($recharge, 'isRefund')) {
  772. return;
  773. }
  774. $recharge->isRefund = 1;
  775. $recharge->save(false, ['isRefund']);
  776. }
  777. protected static function modelHasAttr($model, $attr)
  778. {
  779. return is_object($model) && method_exists($model, 'hasAttribute') && $model->hasAttribute($attr);
  780. }
  781. /**
  782. * 售后付款单是否已经充值过了 ssh 20250801
  783. */
  784. public static function checkShOrderRecharge($order)
  785. {
  786. $orderId = $order->id;
  787. $has = self::getByCondition(['shOrderId' => $orderId, 'payStatus' => 1]);
  788. if (!empty($has)) {
  789. util::fail('已经充值过了,无需再次操作');
  790. }
  791. }
  792. public static function getStatProfile($shopId, $params = [])
  793. {
  794. $searchTime = $params['searchTime'] ?? 'today';
  795. $startTime = $params['startTime'] ?? '';
  796. $endTime = $params['endTime'] ?? '';
  797. $range = dateUtil::formatTime($searchTime, $startTime, $endTime);
  798. $start = $range['startTime'];
  799. $end = $range['endTime'];
  800. $cacheKey = 'hd_recharge_stat:' . $shopId . '_' . md5($start . '_' . $end);
  801. $cache = Yii::$app->redis->executeCommand('GET', [$cacheKey]);
  802. if (!empty($cache)) {
  803. $data = json_decode($cache, true);
  804. if (is_array($data)) {
  805. return $data;
  806. }
  807. }
  808. $summary = self::getRechargeStatSummary($shopId, $start, $end);
  809. $channelList = self::getRechargeStatChannelList($shopId, $start, $end);
  810. $customerList = self::getRechargeStatCustomerList($shopId, $start, $end);
  811. $data = [
  812. 'summary' => $summary,
  813. 'channelList' => $channelList,
  814. 'customerList' => $customerList,
  815. 'startTime' => $start,
  816. 'endTime' => $end,
  817. ];
  818. Yii::$app->redis->executeCommand('SETEX', [$cacheKey, self::STAT_CACHE_TTL, json_encode($data, JSON_UNESCAPED_UNICODE)]);
  819. return $data;
  820. }
  821. private static function getPaidQuery($shopId, $start, $end)
  822. {
  823. $model = self::getActiveRecord();
  824. return $model::find()
  825. ->where([
  826. 'shopId' => $shopId,
  827. 'payStatus' => 1,
  828. 'status' => 1,
  829. ])
  830. ->andWhere(['between', 'addTime', $start, $end]);
  831. }
  832. private static function getRechargeStatSummary($shopId, $start, $end)
  833. {
  834. $row = self::getPaidQuery($shopId, $start, $end)
  835. ->select([
  836. 'rechargeCount' => new Expression('SUM(CASE WHEN IFNULL(amount, 0) + IFNULL(giveAmount, 0) > 0 THEN 1 ELSE 0 END)'),
  837. 'reduceCount' => new Expression('SUM(CASE WHEN IFNULL(isRefund, 0) = 1 THEN 1 ELSE 0 END)'),
  838. 'realAmount' => new Expression('IFNULL(SUM(amount), 0)'),
  839. 'giveAmount' => new Expression('IFNULL(SUM(giveAmount), 0)'),
  840. 'reduceAmount' => new Expression('IFNULL(SUM(CASE WHEN IFNULL(isRefund, 0) = 1 THEN amount ELSE 0 END), 0)'),
  841. ])
  842. ->asArray()
  843. ->one();
  844. $realAmount = $row['realAmount'] ?? 0;
  845. $giveAmount = $row['giveAmount'] ?? 0;
  846. $reduceAmount = $row['reduceAmount'] ?? 0;
  847. return [
  848. 'rechargeAmount' => self::moneyAdd($realAmount, $giveAmount),
  849. 'rechargeCount' => intval($row['rechargeCount'] ?? 0),
  850. 'reduceAmount' => self::moneyValue($reduceAmount),
  851. 'reduceCount' => intval($row['reduceCount'] ?? 0),
  852. 'realAmount' => self::moneyValue($realAmount),
  853. ];
  854. }
  855. private static function getRechargeStatChannelList($shopId, $start, $end)
  856. {
  857. $rows = self::getPaidQuery($shopId, $start, $end)
  858. ->select([
  859. 'channelKey' => new Expression("IF(onlinePay = 2, CONCAT('online_', payWay), CONCAT('pay_', payWay))"),
  860. 'rechargeCount' => new Expression('SUM(CASE WHEN IFNULL(amount, 0) + IFNULL(giveAmount, 0) > 0 THEN 1 ELSE 0 END)'),
  861. 'reduceCount' => new Expression('SUM(CASE WHEN IFNULL(isRefund, 0) = 1 THEN 1 ELSE 0 END)'),
  862. 'realAmount' => new Expression('IFNULL(SUM(amount), 0)'),
  863. 'giveAmount' => new Expression('IFNULL(SUM(giveAmount), 0)'),
  864. 'reduceAmount' => new Expression('IFNULL(SUM(CASE WHEN IFNULL(isRefund, 0) = 1 THEN amount ELSE 0 END), 0)'),
  865. ])
  866. ->groupBy(new Expression("IF(onlinePay = 2, CONCAT('online_', payWay), CONCAT('pay_', payWay))"))
  867. ->asArray()
  868. ->all();
  869. $payWayName = dict::getDict('payWayName');
  870. $defaultList = [
  871. 'online_0' => '线上微信',
  872. 'online_1' => '线上支付宝',
  873. 'pay_0' => '线下微信',
  874. 'pay_1' => '线下支付宝',
  875. 'pay_4' => '现金',
  876. 'pay_5' => '银行卡',
  877. ];
  878. $map = [];
  879. foreach ($rows as $row) {
  880. $key = $row['channelKey'] ?? '';
  881. $payWay = intval(str_replace('pay_', '', $key));
  882. $map[$key] = self::buildStatRow(
  883. $defaultList[$key] ?? ($payWayName[$payWay] ?? '其它'),
  884. $row
  885. );
  886. }
  887. $list = [];
  888. foreach ($defaultList as $key => $name) {
  889. $list[] = $map[$key] ?? self::emptyStatRow($name);
  890. unset($map[$key]);
  891. }
  892. foreach ($map as $item) {
  893. $list[] = $item;
  894. }
  895. return $list;
  896. }
  897. private static function getRechargeStatCustomerList($shopId, $start, $end)
  898. {
  899. $rows = self::getPaidQuery($shopId, $start, $end)
  900. ->select([
  901. 'customId',
  902. 'customName',
  903. 'rechargeCount' => new Expression('SUM(CASE WHEN IFNULL(amount, 0) + IFNULL(giveAmount, 0) > 0 THEN 1 ELSE 0 END)'),
  904. 'reduceCount' => new Expression('SUM(CASE WHEN IFNULL(isRefund, 0) = 1 THEN 1 ELSE 0 END)'),
  905. 'realAmount' => new Expression('IFNULL(SUM(amount), 0)'),
  906. 'giveAmount' => new Expression('IFNULL(SUM(giveAmount), 0)'),
  907. 'reduceAmount' => new Expression('IFNULL(SUM(CASE WHEN IFNULL(isRefund, 0) = 1 THEN amount ELSE 0 END), 0)'),
  908. ])
  909. ->groupBy(['customId', 'customName'])
  910. ->orderBy(new Expression('IFNULL(SUM(amount), 0) + IFNULL(SUM(giveAmount), 0) DESC'))
  911. ->limit(100)
  912. ->asArray()
  913. ->all();
  914. $list = [];
  915. foreach ($rows as $row) {
  916. $list[] = self::buildStatRow($row['customName'] ?: '未命名', $row);
  917. }
  918. return $list;
  919. }
  920. private static function buildStatRow($name, $row)
  921. {
  922. $realAmount = $row['realAmount'] ?? 0;
  923. $giveAmount = $row['giveAmount'] ?? 0;
  924. return [
  925. 'customId' => $row['customId'] ?? 0,
  926. 'name' => $name,
  927. 'rechargeAmount' => self::moneyAdd($realAmount, $giveAmount),
  928. 'rechargeCount' => intval($row['rechargeCount'] ?? 0),
  929. 'reduceAmount' => self::moneyValue($row['reduceAmount'] ?? 0),
  930. 'reduceCount' => intval($row['reduceCount'] ?? 0),
  931. 'realAmount' => self::moneyValue($realAmount),
  932. ];
  933. }
  934. private static function emptyStatRow($name)
  935. {
  936. return [
  937. 'name' => $name,
  938. 'rechargeAmount' => '0.00',
  939. 'rechargeCount' => 0,
  940. 'reduceAmount' => '0.00',
  941. 'reduceCount' => 0,
  942. 'realAmount' => '0.00',
  943. ];
  944. }
  945. private static function moneyAdd($left, $right)
  946. {
  947. return self::moneyValue(bcadd((string)$left, (string)$right, 2));
  948. }
  949. private static function moneyValue($amount)
  950. {
  951. return number_format((float)$amount, 2, '.', '');
  952. }
  953. }