HdRefundService.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. /**
  3. * HD 零售售后服务
  4. * 用途:有原单当天/跨天售后、无原单退款;隔天写 nextRefundNum/nextDayTkPrice,对齐资金三态。
  5. */
  6. namespace bizHd\refund\services;
  7. use biz\product\classes\ProductClass;
  8. use biz\shop\classes\MainClass;
  9. use biz\shop\classes\ShopCapitalClass;
  10. use biz\stat\classes\StatOutClass;
  11. use biz\stat\classes\StatRefundClass;
  12. use biz\stock\classes\GoodsStockRecordClass;
  13. use bizHd\base\services\BaseService;
  14. use bizHd\custom\classes\CustomClass;
  15. use bizHd\goods\classes\GoodsClass;
  16. use bizHd\merchant\classes\ShopClass;
  17. use bizHd\order\classes\OrderClass;
  18. use bizHd\order\classes\OrderGoodsClass;
  19. use bizHd\order\classes\OrderItemClass;
  20. use bizHd\refund\classes\HdNextDayRefundClass;
  21. use bizHd\refund\classes\HdRefundClass;
  22. use bizHd\refund\classes\HdRefundGoodsClass;
  23. use bizHd\refund\classes\HdRefundItemClass;
  24. use bizGhs\shop\classes\ShopMoneyClass;
  25. use bizHd\wx\classes\WxOpenClass;
  26. use common\components\dict;
  27. use common\components\noticeUtil;
  28. use common\components\orderSn;
  29. use common\components\util;
  30. use Yii;
  31. use common\components\lakala\Lakala;
  32. class HdRefundService extends BaseService
  33. {
  34. public static $baseFile = '\bizHd\refund\classes\HdRefundClass';
  35. /**
  36. * 有原单退款(当天或隔天)
  37. * 隔天:加 nextRefundNum/nextDayTkPrice,不减 actPrice;资金按快照三态落地。
  38. * @param array $post
  39. * @param object $order
  40. * @param object|null $existRefund 商城待审核行,非空则复用该记录而不是新插
  41. * @return object|\bizHd\refund\models\HdRefund
  42. */
  43. public static function addRefund($post, $order, $existRefund = null)
  44. {
  45. if (!empty($existRefund) && (int)$existRefund->status !== HdRefundClass::STATUS_PENDING) {
  46. util::fail('仅待审核申请可通过');
  47. }
  48. $refundPrice = bcadd((string)($post['price'] ?? '0'), '0', 2);
  49. if (bccomp($refundPrice, '0', 2) < 0) {
  50. util::fail('请填写退款金额');
  51. }
  52. // 红包全额抵扣的 0 元单允许 0 元售后(退货/退红包);其它 0 元仍拦截
  53. if (bccomp($refundPrice, '0', 2) === 0 && (int)($order->hbId ?? 0) <= 0) {
  54. util::fail('请填写退款金额');
  55. }
  56. HdNextDayRefundClass::assertCanRefund($order);
  57. $sameDay = intval($post['sameDay'] ?? HdRefundClass::SAME_DAY_YES);
  58. if (HdNextDayRefundClass::mustUseNextDay($order)) {
  59. $sameDay = HdRefundClass::SAME_DAY_NO;
  60. }
  61. // 可退上限与商城申请 remainRefundCash 对齐:用 orderPrice(实付合计),
  62. // 不要只卡 mainPay——组合付 / 会员折后 mainPay 与 orderPrice 不一致时,申请能过、审核会误报「可退款金额不足」
  63. $alreadyTk = bcadd((string)($order->tkPrice ?? '0'), '0', 2);
  64. $alreadyNext = bcadd((string)($order->nextDayTkPrice ?? '0'), '0', 2);
  65. $payCeiling = self::resolveRefundPayCeiling($order);
  66. $totalRefunded = bcadd(bcadd($alreadyTk, $alreadyNext, 2), $refundPrice, 2);
  67. if (bccomp($totalRefunded, $payCeiling, 2) > 0) {
  68. util::fail('可退款金额不足');
  69. }
  70. if ($sameDay === HdRefundClass::SAME_DAY_YES) {
  71. $order->tkPrice = bcadd($alreadyTk, $refundPrice, 2);
  72. $order->refund = OrderClass::REFUND_YES;
  73. // 当天售后扣减 actPrice;若历史数据 actPrice 略小于应退额,钳到 0(上限已用 orderPrice 校验过)
  74. $currentActPrice = bcsub((string)($order->actPrice ?? '0'), $refundPrice, 2);
  75. if (bccomp($currentActPrice, '0', 2) < 0) {
  76. $currentActPrice = '0.00';
  77. }
  78. $order->actPrice = $currentActPrice;
  79. $order->realPrice = $currentActPrice;
  80. $order->save();
  81. } else {
  82. // 隔天金额在通过资金段 applyHdAmountAndFund 写入 nextDayTkPrice
  83. $order->refund = OrderClass::REFUND_YES;
  84. $order->save(false, ['refund']);
  85. }
  86. $refundType = $post['refundType'] ?? 1;
  87. $post['orderId'] = $order->id ?? 0;
  88. $data = [];
  89. $data['status'] = HdRefundClass::STATUS_COMPLETE;
  90. // 审核通过复用申请时已生成的 refundSn,明细行才能挂到同一单
  91. if (!empty($existRefund) && !empty($existRefund->refundSn)) {
  92. $refundSn = $existRefund->refundSn;
  93. } else {
  94. $refundSn = orderSn::getHdRefundSn();
  95. }
  96. $data['refundSn'] = $refundSn;
  97. $data['orderSn'] = $order->orderSn ?? '';
  98. $data['orderId'] = $order->id ?? 0;
  99. $sjId = $post['sjId'] ?? 0;
  100. $shopId = $post['shopId'] ?? 0;
  101. $mainId = $post['mainId'] ?? 0;
  102. $myOrderSn = $order->orderSn ?? '';
  103. $thirdNo = $order->thirdNo ?? '';
  104. $data['sjId'] = $sjId;
  105. $data['shopId'] = $shopId;
  106. $data['mainId'] = $mainId;
  107. $data['shopAdminId'] = $post['shopAdminId'] ?? 0;
  108. $data['shopAdminName'] = $post['shopAdminName'] ?? '';
  109. $data['passTime'] = date('Y-m-d H:i:s');
  110. $postProduct = $post['product'] ?? '';
  111. if ($refundType == 1) {
  112. //商品花材结构 property 0商品 1花材
  113. if (empty($postProduct)) {
  114. util::fail('没有退款商品');
  115. }
  116. $goodsInfoList = [];
  117. $itemInfoList = [];
  118. $calcAmount = 0;
  119. foreach ($postProduct as $current) {
  120. $property = $current['property'] ?? 0;
  121. if ($property == 0) {
  122. $goodsInfoList[] = $current;
  123. } else {
  124. $itemInfoList[] = $current;
  125. }
  126. $calcNum = $current['num'] ?? 0;
  127. $calcUnitPrice = $current['unitPrice'] ?? 0;
  128. $calcPrice = bcmul($calcNum, $calcUnitPrice, 2);
  129. $calcAmount = bcadd($calcAmount, $calcPrice, 2);
  130. }
  131. if (!empty($itemInfoList)) {
  132. $ids = array_column($itemInfoList, 'productId');
  133. $productInfo = ProductClass::getByids($ids, null, 'id');
  134. $orderItemData = OrderItemClass::getAllByCondition(['orderSn' => $myOrderSn], null, '*', 'itemId', true);
  135. if (empty($orderItemData)) {
  136. util::fail('没有找到商品');
  137. }
  138. foreach ($itemInfoList as $currentProduct) {
  139. $id = $currentProduct['productId'] ?? 0;
  140. $unitPrice = $currentProduct['unitPrice'] ?? 0;
  141. $unitType = $currentProduct['unitType'] ?? 0;
  142. $unitName = $currentProduct['unitName'] ?? '';
  143. $currentRefundNum = $currentProduct['num'] ?? 0;
  144. $price = bcmul($currentRefundNum, $unitPrice, 2);
  145. $name = $productInfo[$id]['name'] ?? '';
  146. $ptItemId = $productInfo[$id]['itemId'] ?? 0;
  147. $cover = $productInfo[$id]['cover'] ?? '';
  148. $ratio = $productInfo[$id]['ratio'] ?? '';
  149. $bigUnit = $productInfo[$id]['bigUnit'] ?? '';
  150. $smallUnit = $productInfo[$id]['smallUnit'] ?? '';
  151. $currentOrderItem = $orderItemData[$id] ?? null;
  152. if (empty($currentOrderItem)) {
  153. util::fail('没有找到退款的花材');
  154. }
  155. $orderItemNum = $currentOrderItem->num ?? 0;
  156. $hasRefundNum = $currentOrderItem->refundNum ?? 0;
  157. $remainNum = bcsub($orderItemNum, $hasRefundNum);
  158. if ($currentRefundNum > $remainNum) {
  159. util::fail("{$name}超过可退数量");
  160. }
  161. // refundNum=总已退;隔天另记 nextRefundNum
  162. $currentOrderItem->refundNum = bcadd($hasRefundNum, $currentRefundNum);
  163. if ($sameDay === HdRefundClass::SAME_DAY_NO) {
  164. $currentOrderItem->nextRefundNum = bcadd($currentOrderItem->nextRefundNum ?? 0, $currentRefundNum);
  165. }
  166. $currentOrderItem->save();
  167. $thisItemData = [
  168. 'refundSn' => $refundSn,
  169. 'mainId' => $mainId,
  170. 'sjId' => $sjId,
  171. 'shopId' => $shopId,
  172. 'name' => $name,
  173. 'itemId' => $id,
  174. 'ptItemId' => $ptItemId,
  175. 'num' => $currentRefundNum,
  176. 'unitName' => $unitName,
  177. 'unitType' => $unitType,
  178. 'cover' => $cover,
  179. 'ratio' => $ratio,
  180. 'bigUnit' => $bigUnit,
  181. 'smallUnit' => $smallUnit,
  182. 'unitPrice' => $unitPrice,
  183. 'price' => $price,
  184. ];
  185. HdRefundItemClass::addData($thisItemData, $refundSn, $post);
  186. }
  187. }
  188. if (!empty($goodsInfoList)) {
  189. $ids = array_column($goodsInfoList, 'productId');
  190. $goodsData = GoodsClass::getByIds($ids, null, 'id');
  191. $orderGoodsData = OrderGoodsClass::getAllByCondition(['orderSn' => $myOrderSn], null, '*', 'goodsId', true);
  192. foreach ($goodsInfoList as $currentGoods) {
  193. $goodsId = $currentGoods['productId'] ?? 0;
  194. $unitPrice = $currentGoods['unitPrice'] ?? 0;
  195. $name = $goodsData[$goodsId]['name'] ?? '';
  196. $currentRefundNum = $currentGoods['num'] ?? 0;
  197. $price = bcmul($currentRefundNum, $unitPrice, 2);
  198. $currentOrderGoods = $orderGoodsData[$goodsId] ?? [];
  199. if (empty($currentOrderGoods)) {
  200. util::fail('没有找到商品呢');
  201. }
  202. $totalNum = $currentOrderGoods->num ?? 0;
  203. $hasRefundNum = $currentOrderGoods->refundNum ?? 0;
  204. $remainNum = bcsub($totalNum, $hasRefundNum);
  205. if ($currentRefundNum > $remainNum) {
  206. util::fail("{$name}超过可退数量");
  207. }
  208. $currentOrderGoods->refundNum = bcadd($hasRefundNum, $currentRefundNum);
  209. if ($sameDay === HdRefundClass::SAME_DAY_NO) {
  210. $currentOrderGoods->nextRefundNum = bcadd($currentOrderGoods->nextRefundNum ?? 0, $currentRefundNum);
  211. }
  212. $currentOrderGoods->save();
  213. $cover = $goodsData[$goodsId]['cover'] ?? '';
  214. $num = $currentGoods['num'] ?? 0;
  215. $saveGoods = [
  216. 'refundSn' => $refundSn,
  217. 'mainId' => $mainId,
  218. 'sjId' => $sjId,
  219. 'shopId' => $shopId,
  220. 'name' => $name,
  221. 'cover' => $cover,
  222. 'goodsId' => $goodsId,
  223. 'num' => $num,
  224. 'unitPrice' => $unitPrice,
  225. 'price' => $price,
  226. ];
  227. HdRefundGoodsClass::addData($saveGoods, $refundSn, $post);
  228. }
  229. }
  230. }
  231. $snap = HdNextDayRefundClass::buildRefundPaySnapshot(
  232. $order,
  233. true,
  234. ['payWay' => $post['payWay'] ?? null]
  235. );
  236. $data['customId'] = $order->customId ?? 0;
  237. $data['customName'] = $order->customName ?? '';
  238. $data['remark'] = $post['remark'] ?? '';
  239. $data['refundPrice'] = $refundPrice;
  240. $data['refundType'] = $post['refundType'] ?? 0;
  241. $data['sameDay'] = $sameDay;
  242. $data['onlinePay'] = $snap['onlinePay'];
  243. $data['payWay'] = $snap['payWay'];
  244. $data['hasReturn'] = $snap['hasReturn'];
  245. $data['couldReturn'] = $snap['couldReturn'];
  246. $data['returnBalance'] = $snap['returnBalance'];
  247. if (!empty($existRefund)) {
  248. foreach ($data as $key => $val) {
  249. $existRefund->$key = $val;
  250. }
  251. if (!$existRefund->save()) {
  252. throw new \Exception('更新退款单失败');
  253. }
  254. $refund = $existRefund;
  255. } else {
  256. $refund = HdRefundClass::add($data, true);
  257. }
  258. // 实付 0 仅退红包:不走拉卡拉/资金流水,避免 0 元通道失败;红包在 mall 审核通过后 hbBackIfNotExpired
  259. if (bccomp($refundPrice, '0', 2) <= 0) {
  260. HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
  261. return HdRefundClass::getById($refund->id, true);
  262. }
  263. $payWay = $order->payWay;
  264. $shopId = $order->shopId;
  265. $onlinePay = $order->onlinePay ?? 1;
  266. $main = MainClass::getLockById($mainId);
  267. $shop = ShopClass::getLockById($shopId);
  268. if (empty($main)) {
  269. util::fail('main信息缺失呢');
  270. }
  271. if (empty($shop)) {
  272. util::fail('没有找到门店,编号15860');
  273. }
  274. //总支出增加
  275. $currentTotalExpend = bcadd($main->totalExpend, $refundPrice, 2);
  276. $main->totalExpend = $currentTotalExpend;
  277. $totalRefund = bcadd($main->totalRefund, $refundPrice, 2);
  278. $main->totalRefund = $totalRefund;
  279. $main->save();
  280. $thisType = dict::getDict('capitalType', 'hdOrderRefund', 'id');
  281. $sjId = $order->sjId ?? 0;
  282. $capitalData = [
  283. 'capitalType' => $thisType,
  284. 'io' => 0,
  285. 'totalExpend' => $currentTotalExpend,
  286. 'payWay' => 0,
  287. 'amount' => $refundPrice,
  288. 'sjId' => $sjId,
  289. 'shopId' => $shopId,
  290. 'event' => '销售退款',
  291. 'mainId' => $mainId,
  292. ];
  293. ShopCapitalClass::addCapital($capitalData);
  294. // 隔天:金额与余额/挂账原路;线下现金等留给成功页
  295. if ($sameDay === HdRefundClass::SAME_DAY_NO) {
  296. HdNextDayRefundClass::applyHdAmountAndFund($refund, $order);
  297. // 线上隔天:仍尝试原路退回通道
  298. if (HdNextDayRefundClass::isOnlinePayOrder($refund) || HdNextDayRefundClass::isOnlinePayOrder($order)) {
  299. self::doOnlineLakalaRefund($shop, $refund, $refundSn, $myOrderSn, $thirdNo, $refundPrice, $main);
  300. HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
  301. }
  302. return HdRefundClass::getById($refund->id, true);
  303. }
  304. // —— 当天售后资金 ——
  305. if ($onlinePay == dict::getDict('onlinePay', 'yes')) {
  306. if ($payWay == dict::getDict('payWay', 'wxPay') || $payWay == dict::getDict('payWay', 'alipay')) {
  307. self::doOnlineLakalaRefund($shop, $refund, $refundSn, $myOrderSn, $thirdNo, $refundPrice, $main);
  308. HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
  309. }
  310. } else {
  311. $customId = $order->customId ?? 0;
  312. $custom = CustomClass::getLockById($customId);
  313. if (empty($custom)) {
  314. util::fail('没有找到客户');
  315. }
  316. if ($payWay == dict::getDict('payWay', 'debtPay')) {
  317. $currentRemain = bcsub($order->remainDebtPrice, $refundPrice, 2);
  318. if ($currentRemain < 0) {
  319. util::fail('剩余欠款不足售后金额');
  320. }
  321. $order->remainDebtPrice = $currentRemain;
  322. if ($currentRemain <= 0) {
  323. $order->debt = 0;
  324. }
  325. $order->save();
  326. CustomClass::skRefundDebtAmountReduce($custom, $refundPrice, $refund, $payWay);
  327. HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
  328. }
  329. if ($payWay == dict::getDict('payWay', 'cash')) {
  330. $moneyData = [
  331. 'shopId' => $shopId,
  332. 'amount' => $refundPrice,
  333. 'orderSn' => $order->orderSn,
  334. 'mainId' => $mainId,
  335. ];
  336. ShopMoneyClass::hdRefundSkOutAmount($moneyData, $main);
  337. // 现金:需人工确认原路(couldReturn=1),成功页 markHasReturn
  338. }
  339. if ($payWay == dict::getDict('payWay', 'balancePay')) {
  340. CustomClass::skRefundDebtAmountReduce($custom, $refundPrice, $refund, $payWay);
  341. HdNextDayRefundClass::syncRefundFundFlags($refund, ['hasReturn' => HdNextDayRefundClass::FLAG_YES]);
  342. }
  343. }
  344. return HdRefundClass::getById($refund->id, true);
  345. }
  346. /**
  347. * 售后可退金额上限(与商城申请 remainRefundCash 同一口径)
  348. * 优先 orderPrice;为空时回退 mainPay+cash,避免组合付/折后主付字段与实付合计不一致
  349. * @param object $order
  350. * @return string 两位小数字符串
  351. */
  352. protected static function resolveRefundPayCeiling($order)
  353. {
  354. $orderPrice = bcadd((string)($order->orderPrice ?? '0'), '0', 2);
  355. if (bccomp($orderPrice, '0', 2) > 0) {
  356. return $orderPrice;
  357. }
  358. $mainPay = bcadd((string)($order->mainPay ?? '0'), '0', 2);
  359. $cash = bcadd((string)($order->cash ?? '0'), '0', 2);
  360. $sum = bcadd($mainPay, $cash, 2);
  361. if (bccomp($sum, '0', 2) > 0) {
  362. return $sum;
  363. }
  364. // 再回退 actPrice + 已退(还原下单实付)
  365. $act = bcadd((string)($order->actPrice ?? '0'), '0', 2);
  366. $tk = bcadd((string)($order->tkPrice ?? '0'), '0', 2);
  367. $next = bcadd((string)($order->nextDayTkPrice ?? '0'), '0', 2);
  368. return bcadd(bcadd($act, $tk, 2), $next, 2);
  369. }
  370. /**
  371. * 拉卡拉线上原路退款
  372. */
  373. protected static function doOnlineLakalaRefund($shop, $refund, $refundSn, $myOrderSn, $thirdNo, $refundPrice, $main)
  374. {
  375. \biz\shop\classes\ShopClass::hdSaleRefundReduceBalance($main, $shop, $refund, $refundPrice);
  376. $refundFee = bcmul($refundPrice, 100);
  377. $merchantPrivateKeyPath = Yii::getAlias("@vendor/lakala") . '/production/api_private_key.pem';
  378. $lklCertificatePath = Yii::getAlias("@vendor/lakala") . '/production/lkl-apigw-v1.cer';
  379. $params = [
  380. 'appid' => 'OP00002119',
  381. 'serial_no' => '018b08cfddbd',
  382. 'merchant_no' => $shop->lklSjNo,
  383. 'term_no' => $shop->lklScanTermNo,
  384. 'merchantPrivateKeyPath' => $merchantPrivateKeyPath,
  385. 'lklCertificatePath' => $lklCertificatePath,
  386. ];
  387. $laResource = new Lakala($params);
  388. $refundParams = [
  389. 'refundSn' => $refundSn,
  390. 'orderSn' => $myOrderSn,
  391. 'refundAmount' => $refundFee,
  392. 'refundReason' => '',
  393. 'thirdNo' => $thirdNo,
  394. ];
  395. $response = $laResource->refund($refundParams);
  396. if (!isset($response['code']) || $response['code'] != 'BBS00000') {
  397. $errMsg = $response['msg'] ?? '';
  398. noticeUtil::push('散客订单,支付宝,退款失败,原因:' . $errMsg . ' refundSn:' . $refundSn . ' orderSn:' . $myOrderSn, '15280215347');
  399. util::fail('退款失败');
  400. }
  401. $wxRefundId = isset($response['resp_data']['trade_no']) ? $response['resp_data']['trade_no'] : '';
  402. $refund->thirdRefundNo = $wxRefundId;
  403. $refund->save();
  404. }
  405. /**
  406. * 无原单退款(工作台「退款」):以 orderId=0、空 orderSn 标识无原单;
  407. * sameDay 写 0 表示走跨天售后资金路径(字段本身不表示无原单),立即完成,资金留给成功页。
  408. * @param array $post
  409. * @return array
  410. */
  411. public static function createFreeRefund($post)
  412. {
  413. $customId = intval($post['customId'] ?? 0);
  414. if ($customId <= 0) {
  415. util::fail('请选择客户');
  416. }
  417. $custom = CustomClass::getLockById($customId);
  418. if (empty($custom)) {
  419. util::fail('没有找到客户');
  420. }
  421. $mainId = intval($post['mainId'] ?? 0);
  422. if ($mainId > 0 && intval($custom->mainId ?? 0) > 0 && intval($custom->mainId) !== $mainId) {
  423. // 部分客户用 mainId 归属;无字段则不拦
  424. }
  425. $refundPrice = bcadd((string)($post['price'] ?? '0'), '0', 2);
  426. if (bccomp($refundPrice, '0', 2) <= 0) {
  427. util::fail('请填写退款金额');
  428. }
  429. $returnWay = intval($post['returnWay'] ?? -1);
  430. if ($returnWay === 0) {
  431. $refundType = HdRefundClass::REFUND_TYPE_MONEY;
  432. } elseif ($returnWay === 1) {
  433. $refundType = HdRefundClass::REFUND_TYPE_MONEY_GOOD;
  434. } else {
  435. $refundType = intval($post['refundType'] ?? HdRefundClass::REFUND_TYPE_MONEY_GOOD);
  436. }
  437. $snap = HdNextDayRefundClass::buildRefundPaySnapshot(null, false, [
  438. 'payWay' => $post['payWay'] ?? null,
  439. ]);
  440. $sjId = intval($post['sjId'] ?? 0);
  441. $shopId = intval($post['shopId'] ?? 0);
  442. $refundSn = orderSn::getHdRefundSn();
  443. $remark = trim((string)($post['remark'] ?? ''));
  444. $data = [
  445. 'status' => HdRefundClass::STATUS_COMPLETE,
  446. 'refundSn' => $refundSn,
  447. 'orderSn' => '',
  448. 'orderId' => 0,
  449. 'sjId' => $sjId,
  450. 'shopId' => $shopId,
  451. 'mainId' => $mainId,
  452. 'shopAdminId' => $post['shopAdminId'] ?? 0,
  453. 'shopAdminName' => $post['shopAdminName'] ?? '',
  454. 'customId' => $customId,
  455. 'customName' => $custom->name ?? '',
  456. 'remark' => $remark,
  457. 'refundPrice' => $refundPrice,
  458. 'refundType' => $refundType,
  459. // 无原单复用跨天售后资金路径,故 sameDay=0;是否无原单以 orderId=0 为准
  460. 'sameDay' => HdRefundClass::SAME_DAY_NO,
  461. 'onlinePay' => $snap['onlinePay'],
  462. 'payWay' => $snap['payWay'],
  463. 'hasReturn' => $snap['hasReturn'],
  464. 'couldReturn' => $snap['couldReturn'],
  465. 'returnBalance' => $snap['returnBalance'],
  466. 'passTime' => date('Y-m-d H:i:s'),
  467. ];
  468. $postProduct = $post['product'] ?? [];
  469. if ($refundType === HdRefundClass::REFUND_TYPE_MONEY_GOOD) {
  470. if (empty($postProduct) || !is_array($postProduct)) {
  471. util::fail('请选择花材');
  472. }
  473. }
  474. $post['refundType'] = $refundType;
  475. $post['orderId'] = 0;
  476. $refund = HdRefundClass::add($data, true);
  477. // 有花材则写售后行;是否回库由 refundType 决定(HdRefundItemClass::addData)
  478. if (!empty($postProduct) && is_array($postProduct)) {
  479. $itemInfoList = [];
  480. $goodsInfoList = [];
  481. foreach ($postProduct as $current) {
  482. $property = intval($current['property'] ?? 1);
  483. if ($property === 0) {
  484. $goodsInfoList[] = $current;
  485. } else {
  486. $itemInfoList[] = $current;
  487. }
  488. }
  489. if (!empty($itemInfoList)) {
  490. $ids = array_column($itemInfoList, 'productId');
  491. $productInfo = ProductClass::getByids($ids, null, 'id');
  492. foreach ($itemInfoList as $currentProduct) {
  493. $id = intval($currentProduct['productId'] ?? 0);
  494. if ($id <= 0 || empty($productInfo[$id])) {
  495. util::fail('花材参数错误');
  496. }
  497. $num = bcadd((string)($currentProduct['num'] ?? '0'), '0', 2);
  498. if (bccomp($num, '0', 2) <= 0) {
  499. util::fail(($productInfo[$id]['name'] ?? '花材') . '数量必须大于0');
  500. }
  501. $unitPrice = bcadd((string)($currentProduct['unitPrice'] ?? '0'), '0', 2);
  502. $unitType = intval($currentProduct['unitType'] ?? 0);
  503. $unitName = $currentProduct['unitName'] ?? '';
  504. if ($unitName === '') {
  505. $unitName = $unitType === 1
  506. ? ($productInfo[$id]['smallUnit'] ?? '支')
  507. : ($productInfo[$id]['bigUnit'] ?? '扎');
  508. }
  509. $thisItemData = [
  510. 'refundSn' => $refundSn,
  511. 'mainId' => $mainId,
  512. 'sjId' => $sjId,
  513. 'shopId' => $shopId,
  514. 'name' => $productInfo[$id]['name'] ?? '',
  515. 'itemId' => $id,
  516. 'ptItemId' => $productInfo[$id]['itemId'] ?? 0,
  517. 'num' => $num,
  518. 'unitName' => $unitName,
  519. 'unitType' => $unitType,
  520. 'cover' => $productInfo[$id]['cover'] ?? '',
  521. 'ratio' => $productInfo[$id]['ratio'] ?? '',
  522. 'bigUnit' => $productInfo[$id]['bigUnit'] ?? '',
  523. 'smallUnit' => $productInfo[$id]['smallUnit'] ?? '',
  524. 'unitPrice' => $unitPrice,
  525. 'price' => bcmul($num, $unitPrice, 2),
  526. ];
  527. HdRefundItemClass::addData($thisItemData, $refundSn, $post);
  528. }
  529. }
  530. if (!empty($goodsInfoList)) {
  531. $ids = array_column($goodsInfoList, 'productId');
  532. $goodsData = GoodsClass::getByIds($ids, null, 'id');
  533. foreach ($goodsInfoList as $currentGoods) {
  534. $goodsId = intval($currentGoods['productId'] ?? 0);
  535. if ($goodsId <= 0 || empty($goodsData[$goodsId])) {
  536. util::fail('商品参数错误');
  537. }
  538. $num = bcadd((string)($currentGoods['num'] ?? '0'), '0', 2);
  539. if (bccomp($num, '0', 2) <= 0) {
  540. util::fail(($goodsData[$goodsId]['name'] ?? '商品') . '数量必须大于0');
  541. }
  542. $unitPrice = bcadd((string)($currentGoods['unitPrice'] ?? '0'), '0', 2);
  543. $saveGoods = [
  544. 'refundSn' => $refundSn,
  545. 'mainId' => $mainId,
  546. 'sjId' => $sjId,
  547. 'shopId' => $shopId,
  548. 'name' => $goodsData[$goodsId]['name'] ?? '',
  549. 'cover' => $goodsData[$goodsId]['cover'] ?? '',
  550. 'goodsId' => $goodsId,
  551. 'num' => $num,
  552. 'unitPrice' => $unitPrice,
  553. 'price' => bcmul($num, $unitPrice, 2),
  554. ];
  555. HdRefundGoodsClass::addData($saveGoods, $refundSn, $post);
  556. }
  557. }
  558. }
  559. HdNextDayRefundClass::addMainExpendForRefund($mainId, $shopId, $sjId, $refundPrice, '无原单退款');
  560. HdNextDayRefundClass::applyFreeFund($refund);
  561. $refund = HdRefundClass::getById($refund->id, true);
  562. $custom = CustomClass::getById($customId, true);
  563. return HdNextDayRefundClass::buildFundResultPayload($refund, $custom, [
  564. 'nextDayTkPrice' => '0.00',
  565. 'needFundAction' => 1,
  566. ]);
  567. }
  568. }