| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- <?php
- namespace bizGhs\custom\services;
- use biz\ghs\classes\GhsClass as BizGhsClass;
- use bizGhs\custom\classes\AccountMoneyClass;
- use bizGhs\custom\classes\CustomBalanceChangeClass;
- use bizGhs\custom\classes\CustomClass;
- use bizGhs\ghs\classes\GhsBalanceChangeClass;
- use bizGhs\order\classes\OrderClearClass;
- use bizGhs\order\classes\OrderClass;
- use common\components\dict;
- use common\components\noticeUtil;
- use common\components\util;
- /**
- * 批发净余额模型:来款、销挂账、流水(对齐 hd RechargeClass::complete)
- *
- * 【核心概念】
- * - 净 balance:正数=客户有余额,负数=还欠供货商钱(历史挂账已并入 balance)。
- * - 资金池 pool:本次可用于销账的金额(线上充值=实付金额;商家帮充=充值额;用余额销账=当前正余额)。
- * - FIFO:按订单 id 从小到大,依次减少 remainDebtPrice,池子用完即停。
- *
- * 【线上充值销账 onlinePayFifoClear 五步】
- * 1. lockAccountPair — 加锁客户/供货商,校验两端 balance 一致
- * 2. voidAwaitPayClearBills — 作废该客户「待付款」的旧结账单,避免重复结
- * 3. fifoSettleWithPool — 用本次来款作资金池,FIFO 销挂账并生成 xhClear
- * 4. bindRechargeToClear — 把结账单信息回写到充值单、余额变动流水
- * 5. 返回 allocate(含 clear、实销金额等),无挂账则 clear 为 null
- *
- * 其他入口:merchantRechargeWithAutoClear / confirmClearBillWithIncoming / consumePositiveBalanceFifo
- */
- class GhsRechargeSettleService
- {
- /** 结账单 payWay=2 表示「余额支付」(掺杂历史正余额销账时用,见 dict balancePay) */
- const CLEAR_PAY_WAY_BALANCE = 2;
- /** 商家帮充 + 按来款 FIFO 销账(CustomController) */
- public static function merchantRechargeWithAutoClear($custom, $amount, $shop, $staff, $payWay, $params = [])
- {
- $pair = self::lockAccountPair($custom, null);
- $custom = $pair['custom'];
- $ghs = $pair['ghs'];
- $pool = self::money($amount);
- self::voidAwaitPayClearBills($custom->id ?? 0);
- // 入账前净余额:决定结账单线上/线下及 payWay
- $balanceBeforeRecharge = bcadd((string)($custom->balance ?? '0'), '0', 2);
- $respond = CustomClass::rechargeBalance($custom, $pool, $shop, $staff, $payWay, $params);
- $custom = $respond['custom'] ?? $custom;
- $ghs = $respond['ghs'] ?? $ghs;
- $customRecharge = $respond['customRecharge'] ?? null;
- $allocate = self::fifoSettleWithPool($pair, $shop, $staff, $pool, [
- 'payWay' => $payWay,
- 'balanceBeforeRecharge' => $balanceBeforeRecharge,
- 'rechargeSource' => 'merchant',
- 'deduct_balance' => false,
- ]);
- if (!empty($customRecharge) && !empty($allocate['clear'])) {
- self::bindRechargeToClear($customRecharge, $respond['ghsRecharge'] ?? null, $allocate, $custom, $ghs);
- }
- return $respond;
- }
- /**
- * 线上支付回调入账后的自动销账(CustomRechargeClass::thirdPay 调用)。
- *
- * 场景:花店 App 向供货商线上充值,钱已加到 balance;若客户还有欠款订单,用本次来款按 FIFO 销账。
- * 注意:入账(balance += amount)在调用本方法之前已完成,本方法只负责「分配来款销单」,不再扣 balance。
- *
- * @param object $custom 客户(可未加锁,内部会 lockAccountPair)
- * @param object $ghs 供货商
- * @param object $shop 收款供货商门店
- * @param string|float $amount 本次线上实付金额(= 资金池大小)
- * @param object $customRecharge 客户侧充值单 xhCustomRecharge
- * @param object|null $ghsRecharge 供货商侧充值单 xhGhsRecharge
- * @param string|null $balanceBeforeRecharge 入账前客户净 balance,用于判断结账单标「线上付」还是「余额付」
- * @return array 销账结果:clear 结账单、realClearAmount 实销总额、clearId/clearSn 等;无欠款时 clear=null
- */
- public static function onlinePayFifoClear($custom, $ghs, $shop, $amount, $customRecharge, $ghsRecharge = null, $balanceBeforeRecharge = null)
- {
- // ① 加锁并校验客户/供货商 balance 一致,同时 ensure 历史挂账已并入净余额
- $pair = self::lockAccountPair($custom, $ghs);
- // ② 资金池 = 本次充值金额(两位小数)
- $pool = self::money($amount);
- // ③ 作废该客户未付款的待结账单,防止与本次自动销账冲突
- self::voidAwaitPayClearBills($pair['custom']->id ?? 0);
- // ④ 从充值单取支付方式、第三方流水号,供结账单标记
- $payWay = intval(is_object($customRecharge) ? ($customRecharge->payWay ?? dict::getDict('payWay', 'unPay')) : dict::getDict('payWay', 'unPay'));
- $thirdNo = is_object($customRecharge) ? (string)($customRecharge->returnCode ?? '') : '';
- // ⑤ 核心:用资金池 FIFO 销挂账,生成 xhClear;deduct_balance=false 表示不再次扣 balance
- $allocate = self::fifoSettleWithPool($pair, $shop, null, $pool, [
- 'payWay' => $payWay,
- 'thirdNo' => $thirdNo,
- 'balanceBeforeRecharge' => $balanceBeforeRecharge,
- 'rechargeSource' => 'online', // 标识 hdApp 线上充值,影响结账单 onlinePay 判定
- 'deduct_balance' => false,
- ]);
- // ⑥ 若确实销了账,把结账单 id/单号/金额写回充值单与余额流水,便于对账
- if (!empty($customRecharge) && !empty($allocate['clear'])) {
- self::bindRechargeToClear($customRecharge, $ghsRecharge, $allocate, $pair['custom'], $pair['ghs']);
- }
- return $allocate;
- }
- /** 待付结账单 — 客户已转账:来款入账 + 按本单销订单(OrderClearController) */
- public static function confirmClearBillWithIncoming($custom, $ghs, $shop, $staff, $clear, $payWay, $customRecharge = null)
- {
- $pair = self::lockAccountPair($custom, $ghs);
- $custom = $pair['custom'];
- $ghs = $pair['ghs'];
- $pool = self::money($clear->actPrice ?? 0);
- if (bccomp($pool, '0', 2) <= 0) {
- util::fail('结账单金额有误');
- }
- $clearId = intval($clear->id ?? 0);
- $clearSn = $clear->orderSn ?? '';
- $clearAudit = [
- 'clearId' => $clearId,
- 'clearSn' => $clearSn,
- 'clearAmount' => $pool,
- ];
- $ghsRecharge = null;
- if (empty($customRecharge)) {
- $respond = CustomClass::rechargeBalance($custom, $pool, $shop, $staff, $payWay, [
- 'remark' => $clear->remark ?? '',
- 'clearAudit' => $clearAudit,
- ]);
- $custom = $respond['custom'];
- $ghs = $respond['ghs'];
- $customRecharge = $respond['customRecharge'] ?? null;
- $ghsRecharge = $respond['ghsRecharge'] ?? null;
- }
- // 商家「线下已转账」确认:微信/支付宝亦为线下,不得被 confirmClear 兜底标为线上
- OrderClearClass::applyNetBalanceClear($clear, $payWay, [
- 'skipCashMoney' => true,
- 'onlinePay' => intval(dict::getDict('onlinePay', 'not')),
- ]);
- $settled = self::money(OrderClearClass::sumClearAmountByClearId($clearId));
- if (bccomp($settled, $pool, 2) > 0) {
- $settled = $pool;
- }
- $clearAudit['clearAmount'] = $settled;
- self::refreshDebtFlags($custom, $ghs);
- $allocate = [
- 'clear' => $clear,
- 'realClearAmount' => $settled,
- 'clearId' => $clearId,
- 'clearSn' => $clearSn,
- 'clearAmount' => $settled,
- ];
- if (!empty($customRecharge)) {
- self::bindRechargeToClear($customRecharge, $ghsRecharge, $allocate, $custom, $ghs);
- } else {
- self::bindClearToLatestIncomeRow($custom, $ghs, $clear, $settled);
- }
- return [
- 'custom' => $custom,
- 'ghs' => $ghs,
- 'customRecharge' => $customRecharge,
- 'allocate' => $allocate,
- ];
- }
- /** 用当前正余额 FIFO 销挂账(原 useBalanceClear) */
- public static function consumePositiveBalanceFifo($custom, $ghs, $shop, $staff)
- {
- $pair = self::lockAccountPair($custom, $ghs);
- $pool = self::money($pair['custom']->balance ?? 0);
- if (bccomp($pool, '0', 2) <= 0) {
- return true;
- }
- $balanceBeforeRecharge = self::money($pair['custom']->balance ?? 0);
- $allocate = self::fifoSettleWithPool($pair, $shop, $staff, $pool, [
- 'deduct_balance' => true,
- 'balanceBeforeRecharge' => $balanceBeforeRecharge,
- 'rechargeSource' => 'merchant',
- ]);
- return true;
- }
- /**
- * 制定 FIFO 销账计划:不真正写库,只算出「资金池能销哪些订单、各销多少」。
- *
- * 规则:查 customId 下 debt=1 的订单,按 id 升序;每笔取 min(订单剩余欠款, 池子剩余),直到池子用完。
- *
- * @param int $customId 客户 id
- * @param string|float $poolAmount 可用资金池金额
- * @return array<int, array{orderId:int, clearAmount:string, orderSn:string}> 计划列表,空数组表示无挂账或池子为 0
- */
- public static function planFifoByPool($customId, $poolAmount)
- {
- $pool = self::money($poolAmount);
- if (bccomp($pool, '0', 2) <= 0) {
- return [];
- }
- // 只处理仍标记为欠款(debt=1)的订单,id 小=下单早,先销
- $orderList = OrderClass::getAllByCondition(['customId' => $customId, 'debt' => 1], 'id asc', '*', null, true);
- if (empty($orderList)) {
- return [];
- }
- $plan = [];
- $used = '0.00'; // 已分配进计划的金额
- foreach ($orderList as $order) {
- $left = bcsub($pool, $used, 2);
- if (bccomp($left, '0', 2) <= 0) {
- break; // 资金池已分完
- }
- $remain = self::money($order->remainDebtPrice ?? 0);
- if (bccomp($remain, '0', 2) <= 0) {
- continue; // 该单已无剩余欠款,跳过
- }
- // 本单销账额 = min(本单欠款, 池子剩余)
- $clearAmount = bccomp($remain, $left, 2) <= 0 ? $remain : $left;
- $plan[] = [
- 'orderId' => (int)($order->id ?? 0),
- 'clearAmount' => $clearAmount,
- 'orderSn' => $order->orderSn ?? '',
- ];
- $used = bcadd($used, $clearAmount, 2);
- }
- return $plan;
- }
- /**
- * 用资金池执行 FIFO 销账并生成结账单(merchant/online/余额销账共用内核)。
- *
- * 流程:planFifoByPool → 无计划则直接返回 → 创建 xhClear → applyNetBalanceClear 更新订单欠款 →
- * 可选 deduct_balance 从 balance 扣实销额 → refreshDebtFlags 刷新 isDebt。
- *
- * @param array{custom:object, ghs:object} $pair lockAccountPair 返回的客户/供货商
- * @param object $shop 操作门店
- * @param object|null $staff 操作员工(线上回调无员工传 null)
- * @param string|float $poolAmount 资金池
- * @param array $options payWay/thirdNo/balanceBeforeRecharge/rechargeSource/deduct_balance/onlinePay 等
- * @return array clear、realClearAmount、clearId、clearSn、custom、ghs
- */
- protected static function fifoSettleWithPool(array $pair, $shop, $staff, $poolAmount, $options = [])
- {
- $custom = $pair['custom'];
- $ghs = $pair['ghs'];
- $customId = $custom->id ?? 0;
- $pool = self::money($poolAmount);
- $plan = self::planFifoByPool($customId, $pool);
- if (empty($plan)) {
- // 没有可销订单:只刷新欠款标记,不生成结账单
- self::refreshDebtFlags($custom, $ghs);
- return self::emptyAllocateResult($custom, $ghs);
- }
- $planTotal = self::sumPlan($plan);
- if (bccomp($planTotal, $pool, 2) > 0) {
- util::fail('销账计划超过本次可用金额,请联系技术员');
- }
- self::voidAwaitPayClearBills($customId);
- $staffId = $staff->id ?? 0;
- $staffName = $staff->name ?? '';
- $sjId = $shop->sjId ?? 0;
- $shopId = $shop->id ?? 0;
- $actualPayWay = isset($options['payWay']) && is_numeric($options['payWay'])
- ? intval($options['payWay'])
- : intval(dict::getDict('payWay', 'unPay'));
- // 决定结账单上的 onlinePay、payWay(线上纯来款 vs 掺杂历史余额 vs 纯余额销账)
- if (array_key_exists('balanceBeforeRecharge', $options)) {
- $payMeta = self::resolveClearPayMetaForRechargeFifo(
- $options['balanceBeforeRecharge'],
- $planTotal,
- $pool,
- $actualPayWay,
- $options['rechargeSource'] ?? 'merchant',
- !empty($options['deduct_balance'])
- );
- $payWay = $payMeta['payWay'];
- $onlinePay = $payMeta['onlinePay'];
- } elseif (isset($options['onlinePay']) && is_numeric($options['onlinePay'])) {
- $payWay = $actualPayWay;
- $onlinePay = intval($options['onlinePay']);
- } else {
- $payWay = $actualPayWay;
- $onlinePay = self::resolveOnlinePayByPayWay($payWay);
- }
- // 按 plan 创建结账单 xhClear,并关联各订单销账明细
- $clear = OrderClearClass::clearWithAmountMap([
- 'customId' => $customId,
- 'ghsShopAdminId' => $staffId,
- 'ghsShopId' => $shopId,
- 'ghsShopAdminName' => $staffName,
- 'modifyPrice' => $planTotal,
- 'payWay' => $payWay,
- 'onlinePay' => $onlinePay,
- 'complete' => 0,
- 'remark' => '',
- ], $plan, $sjId, $shopId);
- // 更新订单 remainDebtPrice、净 balance 等(充值链路 skipCashMoney,不再动门店现金)
- OrderClearClass::applyNetBalanceClear($clear, $payWay, [
- 'skipCashMoney' => true,
- 'onlinePay' => $onlinePay,
- ]);
- // 线上支付:把拉卡拉/微信流水号写入结账单 thirdNo
- $clearId = intval($clear->id ?? 0);
- if ($clearId > 0 && !empty($options['thirdNo'])) {
- $clear = OrderClearClass::getLockById($clearId);
- if (!empty($clear) && self::modelHasAttr($clear, 'thirdNo')) {
- $clear->thirdNo = (string)$options['thirdNo'];
- $clear->save(false, ['thirdNo']);
- }
- }
- // 校验:库内实销合计必须与计划一致,且不能超过资金池
- $settled = self::money(OrderClearClass::sumClearAmountByClearId($clear->id ?? 0));
- if (bccomp($settled, $planTotal, 2) !== 0) {
- util::fail('销账结果与计划不一致,请检查订单待结或联系技术员');
- }
- if (bccomp($settled, $pool, 2) > 0) {
- util::fail('销账金额超过本次可用金额' . floatval($pool) . '元,请检查订单待结或联系技术员');
- }
- // 「用账面正余额销账」场景:FIFO 完成后要从 balance 扣掉实销额(线上充值场景 deduct_balance=false 不扣)
- if (!empty($options['deduct_balance']) && bccomp($settled, '0', 2) > 0) {
- $custom = CustomClass::getLockById($custom->id ?? 0);
- $ghs = BizGhsClass::getLockById($ghs->id ?? 0);
- CustomClass::clearConsumeBalance($settled, $custom, $ghs, $shop, $staff, $clear);
- }
- self::refreshDebtFlags($custom, $ghs);
- return [
- 'clear' => $clear,
- 'realClearAmount' => $settled,
- 'clearId' => $clear->id ?? 0,
- 'clearSn' => $clear->orderSn ?? '',
- 'clearAmount' => $settled,
- 'custom' => $custom,
- 'ghs' => $ghs,
- ];
- }
- /**
- * 加锁客户与供货商账户,并校验两端 balance 必须相等。
- *
- * 副作用:ensureCustomMoneyReady / ensureGhsMoneyReady 可能把历史挂账并入净 balance(幂等)。
- *
- * @param object|array $custom 客户或含 id 的数组
- * @param object|null $ghs 供货商,空则按 custom.ghsId 查
- * @return array{custom: object, ghs: object}
- */
- public static function lockAccountPair($custom, $ghs = null)
- {
- if (empty($custom)) {
- util::fail('没有客户信息');
- }
- $customId = is_object($custom) ? ($custom->id ?? 0) : ($custom['id'] ?? 0);
- $custom = CustomClass::getLockById($customId);
- if (empty($custom)) {
- util::fail('没有找到客户');
- }
- AccountMoneyClass::ensureCustomMoneyReady($custom, true);
- $ghsId = $custom->ghsId ?? 0;
- if (empty($ghs)) {
- $ghs = BizGhsClass::getLockById($ghsId);
- }
- if (empty($ghs)) {
- util::fail('没有找到供货商');
- }
- AccountMoneyClass::ensureGhsMoneyReady($ghs, true);
- if (bccomp(self::money($custom->balance), self::money($ghs->balance), 2) !== 0) {
- noticeUtil::push("客户的余额,和供货商端不一致,客户 ID:" . $custom->id . " 余额:" . $custom->balance . " 供货商 ID:" . $ghsId . ' 余额:' . $ghs->balance, '15280215347');
- util::fail('余额有问题,请联系管理员');
- }
- return ['custom' => $custom, 'ghs' => $ghs];
- }
- /**
- * 作废客户名下「待付款」状态的结账单。
- * 为什么:新来款/新销账前清掉旧草稿单,避免客户重复确认或数据打架。
- */
- protected static function voidAwaitPayClearBills($customId)
- {
- OrderClearClass::expireAwaitPayClears(intval($customId));
- }
- /** 汇总 FIFO 计划里各订单 clearAmount 之和 */
- protected static function sumPlan(array $plan)
- {
- $total = '0.00';
- foreach ($plan as $row) {
- $total = bcadd($total, self::money($row['clearAmount'] ?? 0), 2);
- }
- return $total;
- }
- /**
- * 按当前净 balance 刷新客户 isDebt、供货商 debt 标记。
- * balance < 0 → 仍欠款;balance >= 0 → 不欠款。
- */
- protected static function refreshDebtFlags($custom, $ghs)
- {
- $bal = self::money($custom->balance ?? 0);
- $custom->isDebt = bccomp($bal, '0', 2) < 0 ? CustomClass::IS_DEBT_YES : CustomClass::IS_DEBT_NO;
- $custom->save(false, ['isDebt']);
- if (!empty($ghs)) {
- $ghs->debt = bccomp($bal, '0', 2) < 0 ? 2 : 1;
- $ghs->save(false, ['debt']);
- }
- }
- /** 无挂账可销时的空结果,clear=null、金额为 0 */
- protected static function emptyAllocateResult($custom, $ghs)
- {
- return [
- 'clear' => null,
- 'realClearAmount' => '0.00',
- 'clearId' => 0,
- 'clearSn' => '',
- 'clearAmount' => '0.00',
- 'custom' => $custom,
- 'ghs' => $ghs,
- ];
- }
- /**
- * 充值销账成功后,把结账单信息回写到充值单 + 余额变动流水。
- *
- * 写哪些表:
- * - xhCustomRecharge / xhGhsRecharge:clearId、clearSn、clearAmount、balance
- * - xhCustomBalanceChange / xhGhsBalanceChange:同 relateId 的入账流水补 clear 字段
- */
- protected static function bindRechargeToClear($customRecharge, $ghsRecharge, $allocate, $custom = null, $ghs = null)
- {
- $clearId = $allocate['clearId'] ?? 0;
- $clearSn = $allocate['clearSn'] ?? '';
- $clearAmount = $allocate['clearAmount'] ?? '0.00';
- $finalCustomBalance = is_object($custom) ? ($custom->balance ?? null) : null;
- $finalGhsBalance = is_object($ghs) ? ($ghs->balance ?? null) : null;
- // 充值单上记录「这笔钱销了哪张结账单、销了多少」
- if (!empty($customRecharge) && self::modelHasAttr($customRecharge, 'clearId')) {
- $customRecharge->clearId = $clearId;
- $customRecharge->clearSn = $clearSn;
- $customRecharge->clearAmount = $clearAmount;
- if ($finalCustomBalance !== null) {
- $customRecharge->balance = $finalCustomBalance;
- }
- $customRecharge->save(false);
- }
- $relateId = $customRecharge->id ?? 0;
- $customId = is_object($custom) ? intval($custom->id ?? 0) : 0;
- if ($customId <= 0 && is_object($customRecharge)) {
- $customId = intval($customRecharge->customId ?? 0);
- }
- // 客户侧余额流水补结账单关联
- CustomBalanceChangeClass::patchClearAuditByRechargeId($customId, $relateId, $clearId, $clearSn, $clearAmount, $finalCustomBalance);
- self::patchBalanceChangeRow(CustomBalanceChangeClass::class, $relateId, $clearId, $clearSn, $clearAmount, [
- 'customId' => $customId,
- ], $finalCustomBalance);
- if (!empty($ghsRecharge)) {
- $gRelateId = intval($ghsRecharge->id ?? 0);
- CustomBalanceChangeClass::patchClearAuditByGhsRechargeId(
- $gRelateId,
- $clearId,
- $clearSn,
- $clearAmount,
- $finalGhsBalance
- );
- self::patchBalanceChangeRow(GhsBalanceChangeClass::class, $gRelateId, $clearId, $clearSn, $clearAmount, [], $finalGhsBalance);
- $balanceGhsId = intval($ghsRecharge->ghsId ?? 0);
- if ($balanceGhsId > 0) {
- // 供货商侧可能有多条同金额入账流水,按 ghsId+amount 再 patch 一条
- self::patchBalanceChangeRow(GhsBalanceChangeClass::class, 0, $clearId, $clearSn, $clearAmount, [
- 'ghsId' => $balanceGhsId,
- 'amount' => self::money($ghsRecharge->amount ?? 0),
- ], $finalGhsBalance);
- }
- if ($finalGhsBalance !== null) {
- $ghsRecharge->balance = $finalGhsBalance;
- $ghsRecharge->save(false, ['balance']);
- }
- }
- }
- /**
- * 无充值单、仅确认结账单时:把 clear 信息 patch 到最近一条入账流水(客户+供货商各一条)。
- */
- protected static function bindClearToLatestIncomeRow($custom, $ghs, $clear, $clearAmount)
- {
- $clearId = $clear->id ?? 0;
- $clearSn = $clear->orderSn ?? '';
- self::patchBalanceChangeRow(CustomBalanceChangeClass::class, 0, $clearId, $clearSn, $clearAmount, [
- 'customId' => $custom->id ?? 0,
- 'io' => 1,
- ]);
- self::patchBalanceChangeRow(GhsBalanceChangeClass::class, 0, $clearId, $clearSn, $clearAmount, [
- 'ghsId' => $ghs->id ?? 0,
- 'io' => 1,
- ]);
- }
- /**
- * 找最近一条匹配的余额变动流水,补上 clearId/clearSn/clearAmount。
- *
- * @param string $class CustomBalanceChangeClass 或 GhsBalanceChangeClass
- * @param int $relateId 充值单 id,0 表示不按 relateId 筛
- * @param array $extraWhere 额外条件如 customId、ghsId、amount
- */
- protected static function patchBalanceChangeRow($class, $relateId, $clearId, $clearSn, $clearAmount, $extraWhere = [], $finalBalance = null)
- {
- $where = array_merge(['io' => 1], $extraWhere); // io=1 只 patch 入账流水
- if ($relateId > 0) {
- $where['relateId'] = $relateId;
- }
- $row = $class::getByCondition($where, true, null, 'id DESC');
- if (empty($row) || !self::modelHasAttr($row, 'clearId')) {
- return;
- }
- $row->clearId = $clearId;
- $row->clearSn = $clearSn;
- $row->clearAmount = $clearAmount;
- if (self::modelHasAttr($row, 'giveAmount')) {
- $row->giveAmount = '0.00';
- }
- if ($finalBalance !== null && self::modelHasAttr($row, 'balance')) {
- $row->balance = $finalBalance;
- }
- $row->save(false);
- }
- /** 模型是否有某字段(兼容未迁移库) */
- protected static function modelHasAttr($row, $attr)
- {
- return is_object($row) && method_exists($row, 'hasAttribute') && $row->hasAttribute($attr);
- }
- /** 金额统一保留两位小数的字符串,避免 float 精度问题 */
- protected static function money($value)
- {
- return bcadd((string)$value, '0', 2);
- }
- /**
- * 按支付方式推断结账单是否线上付:仅微信/支付宝为线上。
- * 用于非充值 FIFO 场景的兜底,充值链路走 resolveClearPayMetaForRechargeFifo。
- */
- protected static function resolveOnlinePayByPayWay($payWay)
- {
- $payWay = intval($payWay);
- $wxPay = intval(dict::getDict('payWay', 'wxPay'));
- $aliPay = intval(dict::getDict('payWay', 'alipay'));
- if (in_array($payWay, [$wxPay, $aliPay], true)) {
- return intval(dict::getDict('onlinePay', 'yes'));
- }
- return intval(dict::getDict('onlinePay', 'not'));
- }
- /**
- * 充值 FIFO 销账时,决定 xhClear 上的 onlinePay 与 payWay。
- *
- * 业务规则(简化理解):
- * | 场景 | onlinePay | payWay |
- * |------|-----------|--------|
- * | 用账面余额销账 deduct_balance | 线下 | 2 余额 |
- * | 充值前已有正余额(掺杂历史余额) | 线下 | 2 余额 |
- * | hdApp 线上充,充前无正余额,实销≤来款 | 线上 | 微信/支付宝等 |
- * | ghsApp 商家手动充 | 线下 | 所选方式 |
- *
- * @param string|null $balanceBeforeRecharge 入账前净 balance
- * @param string $settledAmount 计划/实销总额
- * @param string $poolAmount 本次资金池(来款额)
- * @param int $actualPayWay 充值单上的支付方式
- * @param string $rechargeSource 'online' | 'merchant'
- * @param bool $deductBalance 是否「纯余额销账」
- * @return array{onlinePay:int, payWay:int}
- */
- protected static function resolveClearPayMetaForRechargeFifo(
- $balanceBeforeRecharge,
- $settledAmount,
- $poolAmount,
- $actualPayWay,
- $rechargeSource,
- $deductBalance = false
- )
- {
- $notOnline = intval(dict::getDict('onlinePay', 'not'));
- $yesOnline = intval(dict::getDict('onlinePay', 'yes'));
- // 纯余额销账:一律线下 + payWay=余额
- if ($deductBalance) {
- return [
- 'onlinePay' => $notOnline,
- 'payWay' => self::CLEAR_PAY_WAY_BALANCE,
- ];
- }
- $before = bcadd((string)$balanceBeforeRecharge, '0', 2);
- $settled = bcadd((string)$settledAmount, '0', 2);
- $pool = bcadd((string)$poolAmount, '0', 2);
- $actualPayWay = intval($actualPayWay);
- // 充值前客户已有正余额:本次来款+旧余额一起销,结账单标「余额支付」非纯线上
- if (bccomp($before, '0', 2) > 0) {
- return [
- 'onlinePay' => $notOnline,
- 'payWay' => self::CLEAR_PAY_WAY_BALANCE,
- ];
- }
- // 无销账或销账超过来款:保守标线下+实付方式
- if (bccomp($settled, '0', 2) <= 0 || bccomp($settled, $pool, 2) > 0) {
- return [
- 'onlinePay' => $notOnline,
- 'payWay' => $actualPayWay,
- ];
- }
- // hdApp 线上充值且充前无正余额:微信/支付宝可标线上付
- if ($rechargeSource === 'online') {
- $wxPay = intval(dict::getDict('payWay', 'wxPay'));
- $aliPay = intval(dict::getDict('payWay', 'alipay'));
- if (in_array($actualPayWay, [$wxPay, $aliPay], true)) {
- return [
- 'onlinePay' => $yesOnline,
- 'payWay' => $actualPayWay,
- ];
- }
- return [
- 'onlinePay' => $notOnline,
- 'payWay' => $actualPayWay,
- ];
- }
- // ghsApp 商家柜台帮充:线下 + 所选支付方式
- return [
- 'onlinePay' => $notOnline,
- 'payWay' => $actualPayWay,
- ];
- }
- }
|