= amount,天然防并发超扣。 // +---------------------------------------------------------------------- declare (strict_types=1); namespace app\common\service; use app\common\enum\partners\balanceLog\Scene as SceneEnum; use think\facade\Db; class PartnersBalance { /** * 原子扣减合作商余额(余额不足返回 false,不做任何变更) * @param int $partnersId 合作商ID * @param float $amount 扣减金额(元) * @param string $orderNo 业务单号(写入流水描述) * @param int $orderId 关联订单ID * @param int $storeId 商城ID * @return int|false 成功返回流水ID(供建单后回填 order_id),余额不足返回 false */ public static function deduct(int $partnersId, float $amount, string $orderNo, int $orderId = 0, int $storeId = 0) { if ($amount <= 0) { return false; } $affected = Db::name('partners') ->where('partners_id', '=', $partnersId) ->where('balance', '>=', $amount) ->update(['balance' => Db::raw('`balance` - ' . self::money($amount))]); if (!$affected) { return false; } return self::log(SceneEnum::CONSUME, $partnersId, -$amount, $orderNo, $orderId, $storeId); } /** * 回填流水的关联订单ID */ public static function bindOrder($logId, int $orderId): void { if ($logId && $orderId) { Db::name('partners_balance_log')->where('log_id', '=', (int)$logId)->update(['order_id' => $orderId]); } } /** * 回补合作商余额(发放失败 / 撤单) */ public static function refund(int $partnersId, float $amount, string $orderNo, int $orderId = 0, int $storeId = 0): void { if ($amount <= 0) { return; } Db::name('partners') ->where('partners_id', '=', $partnersId) ->update(['balance' => Db::raw('`balance` + ' . self::money($amount))]); self::log(SceneEnum::REFUND, $partnersId, $amount, $orderNo, $orderId, $storeId); } /** * 查询当前余额 */ public static function balance(int $partnersId): float { return (float) Db::name('partners')->where('partners_id', '=', $partnersId)->value('balance'); } /** * 写余额变动流水(失败不影响主流程),返回流水ID * @return int */ protected static function log(int $scene, int $partnersId, float $money, string $orderNo, int $orderId, int $storeId): int { try { return (int) Db::name('partners_balance_log')->insertGetId([ 'partners_id' => $partnersId, 'scene' => $scene, 'money' => round($money, 2), 'describe' => vsprintf(SceneEnum::data()[$scene]['describe'], [$orderNo]), 'remark' => '', 'order_id' => $orderId, 'store_id' => $storeId, 'create_time' => time(), ]); } catch (\Throwable $e) { log_record(['name' => '合作商余额流水写入失败', 'partners_id' => $partnersId, 'msg' => $e->getMessage()]); return 0; } } /** * 金额安全格式化(仅允许数字与小数点,防拼接注入) */ protected static function money(float $amount): string { return number_format(round($amount, 2), 2, '.', ''); } }