where('id', $orderId)->lock(true)->find(); if (!$order) { return false; } if (!$this->isQualified($order)) { return false; } $exists = RewardModel::where('order_id', $orderId)->where('order_id', '>', 0)->find(); if ($exists) { return true; } $buyerId = intval($order['user_id']); // 确保双向保活已更新(即使钩子丢失或补偿场景也能准确刷新) $paidTime = !empty($order['paid_time']) ? intval($order['paid_time']) : time(); $this->touchOrderTimes($buyerId, $paidTime); $active = new ActiveService(); $chainService = new ChainService(); $parentChain = $chainService->collect($buyerId, false); $selfChain = $chainService->collect($buyerId, true); // 动态解析商品独立分成规则与全局默认配置 $orderRules = PartnerConfig::getOrderEffectiveRules($order); $parent = $parentChain[0] ?? null; (new Push3Reward())->run($order, $buyerId, $parent, $active, $orderRules); $diff = (new MakerDiffReward())->run($order, $buyerId, $parentChain, $active, $orderRules); (new MakerPeerReward())->run($order, $buyerId, $parentChain, $diff, $active, $orderRules); $team = (new TeamSplitReward())->run($order, $buyerId, $selfChain, $active, $orderRules); (new PeerShareReward())->run($order, $buyerId, $selfChain, $team, $active, $orderRules); (new VoucherService())->grant($order, $buyerId, $orderRules); // 清理可能存在的历史重试失败标记 $ext = is_array($order->ext) ? $order->ext : (json_decode($order->ext ?? '', true) ?: []); if (!empty($ext['reward_settle_failed'])) { unset($ext['reward_settle_failed'], $ext['reward_settle_error'], $ext['reward_settle_failed_time'], $ext['reward_settle_attempts']); $order->ext = $ext; $order->save(); } return true; }); } public function refund($orderId) { return Db::transaction(function () use ($orderId) { $rewards = RewardModel::where('order_id', $orderId) ->where('order_id', '>', 0) ->where('status', 1) ->whereIn('reward_type', array_merge(PartnerConfig::instantTypes(), ['voucher_grant'])) ->lock(true) ->select(); $order = OrderModel::where('id', $orderId)->find(); if ($order) { (new VoucherService())->refundConsume($order); } if (!$rewards || count($rewards) == 0) { (new VoucherService())->closeByOrder($orderId); return true; } foreach ($rewards as $reward) { if (in_array($reward->reward_type, PartnerConfig::instantTypes()) && (float) $reward->amount > 0 && $reward->user_id > 0) { $walletLog = \app\admin\model\shopro\user\WalletLog::where('user_id', $reward->user_id) ->where('ext', 'like', '%"partner_reward_id":' . intval($reward->id) . '%') ->order('id', 'desc') ->find(); $targetWallet = ($walletLog && $walletLog->type === 'commission') ? 'commission' : PartnerConfig::rewardWalletType(); Wallet::change($reward->user_id, $targetWallet, -$reward->amount, 'reward_back', [ 'partner_reward_id' => $reward->id, 'order_id' => $orderId, ], '收益中心奖励退回'); AgentModel::where('user_id', $reward->user_id)->setDec('total_income', $reward->amount); } if ($reward->reward_type === 'push3' && $reward->user_id > 0) { (new Push3Reward())->rollbackSeq($reward->user_id); } $reward->status = -2; $reward->save(); } (new VoucherService())->closeByOrder($orderId); return true; }); } /** * 部分退款处理:仅等比缩减兑换券额度,不全额撤销分销商奖励 */ public function refundPartial($order, $item) { return Db::transaction(function () use ($order, $item) { $orderId = intval(is_array($order) ? ($order['id'] ?? 0) : ($order->id ?? 0)); $itemPayFee = floatval(is_array($item) ? ($item['pay_fee'] ?? 0) : ($item->pay_fee ?? 0)); $goodsId = intval(is_array($item) ? ($item['goods_id'] ?? 0) : ($item->goods_id ?? 0)); $ext = is_array($item['ext'] ?? null) ? $item['ext'] : (json_decode($item['ext'] ?? '', true) ?: []); $isCommissionItem = true; if (isset($ext['is_commission']) && !$ext['is_commission']) { $isCommissionItem = false; } elseif ($goodsId > 0) { $cg = CommissionGoods::where('goods_id', $goodsId)->where('status', CommissionGoods::GOODS_COMMISSION_STATUS_ON)->find(); if (!$cg) { $isCommissionItem = false; } } else { $isCommissionItem = false; } $voucherService = new VoucherService(); if ($itemPayFee > 0 && $isCommissionItem) { $voucherService->reduceByRefund($orderId, $itemPayFee); } PartnerRewardService::write([ 'order_id' => $orderId, 'buyer_id' => intval($order['user_id'] ?? 0), 'user_id' => intval($order['user_id'] ?? 0), 'role' => 'taster', 'reward_type' => 'voucher_grant', 'amount' => 0, 'base_amount' => $itemPayFee, 'ext' => [ 'formula' => $isCommissionItem ? "计奖商品退款 {$itemPayFee},缩减兑换券总额" : "非计奖商品退款 {$itemPayFee},无需缩减兑换券", 'item_id' => $item['id'] ?? 0, 'partial_refund' => 1 ], 'status' => -2, ]); return true; }); } /** * 获取订单中参与计奖的商品实付金额之和(排除非计奖商品及活动不参与项) */ public function getQualifiedPayFee($order) { if (!$order || ($order['type'] ?? '') === 'score') { return 0.0; } $items = $order['items'] ?? []; if (empty($items) && !empty($order['id'])) { $fresh = OrderModel::with(['items'])->where('id', $order['id'])->find(); if ($fresh) { $order = $fresh; $items = $fresh['items'] ?? []; } } $total = 0.0; foreach ($items as $item) { $ext = is_array($item['ext'] ?? null) ? $item['ext'] : (json_decode($item['ext'] ?? '', true) ?: []); if (isset($ext['is_commission']) && !$ext['is_commission']) { continue; } $goods = CommissionGoods::where('goods_id', $item['goods_id']) ->where('status', CommissionGoods::GOODS_COMMISSION_STATUS_ON) ->find(); if ($goods) { $itemPay = floatval($item['pay_fee'] ?? 0); $total = function_exists('bcadd') ? (float) bcadd((string) $total, (string) $itemPay, 2) : round($total + $itemPay, 2); } } // 计奖实付金额上限不得超过整单实际现金支付金额(防止使用兑换券/整单折扣抵扣后对券抵扣部分重复计奖) $orderPayFee = max(0, floatval($order['pay_fee'] ?? 0)); $total = min($total, $orderPayFee); return (float) $total; } public function isQualified($order) { if (!$order || ($order['type'] ?? '') === 'score') { return false; } if ((float) ($order['pay_fee'] ?? 0) <= 0) { return false; } $status = $order['status'] ?? ''; if (!in_array($status, [OrderModel::STATUS_PAID, OrderModel::STATUS_COMPLETED])) { return false; } $qualifiedPayFee = $this->getQualifiedPayFee($order); $minAmount = (float) PartnerConfig::get('partner_order_min_amount', 0); if ($minAmount > 0) { return $qualifiedPayFee >= $minAmount; } return $qualifiedPayFee > 0; } /** * 刷新有效订单时间(自购及直推订单双向保活) * 1. 买家本人(自购) * 2. 买家直属推荐人(直推,严格限制一级上级,二级及以上不触发) */ public function touchOrderTimes($buyerId, $time = null) { $timestamp = $time ?: time(); if (!$buyerId) { return; } // 1. 买家本人自购保活 $this->updateAgentOrderTime($buyerId, $timestamp); // 2. 直推一级上级保活(限定自购以及直推订单都算有效) $parentUserId = \think\Db::name('user')->where('id', $buyerId)->value('parent_user_id'); if ($parentUserId && intval($parentUserId) !== intval($buyerId)) { $this->updateAgentOrderTime($parentUserId, $timestamp); } } protected function updateAgentOrderTime($userId, $timestamp) { if (!$userId) { return; } $agent = AgentModel::where('user_id', $userId)->find(); if (!$agent) { try { $agentService = new \addons\shopro\service\commission\Agent($userId); $agentService->createNewAgent(); } catch (\Throwable $e) {} $agent = AgentModel::where('user_id', $userId)->find(); } if ($agent && is_object($agent)) { $current = intval($agent->last_self_order_time ?? 0); if ($timestamp >= $current) { $agent->last_self_order_time = $timestamp; $agent->save(); } } else { // 合伙人模式下首购未建档时自愈建立品酒师档案 try { \think\Db::name('shopro_commission_agent')->insert([ 'user_id' => $userId, 'level' => 1, 'status' => 'normal', 'last_self_order_time' => $timestamp, 'createtime' => time() ]); } catch (\Throwable $e) {} } } public function touchSelfOrder($userId, $time = null) { $this->updateAgentOrderTime($userId, $time ?: time()); } public static function dailyOrderId($settleDate) { return -intval(date('Ymd', strtotime($settleDate))); } public static $lastWriteCreated = false; public static function write($data) { self::$lastWriteCreated = false; $now = time(); $payload = array_merge([ 'order_id' => 0, 'buyer_id' => 0, 'user_id' => 0, 'role' => '', 'reward_type' => '', 'amount' => 0, 'madow' => 0, 'base_amount' => 0, 'rate' => 0, 'ext' => [], 'settle_date' => null, 'status' => 0, 'createtime' => $now, 'updatetime' => $now, ], $data); try { $row = RewardModel::create($payload); self::$lastWriteCreated = true; if ($row) { $row->_isNewCreated = true; } return $row; } catch (\Exception $e) { $msg = $e->getMessage(); if (stripos($msg, 'Duplicate') !== false || stripos($msg, '1062') !== false) { $existingQuery = RewardModel::where('order_id', $payload['order_id']) ->where('user_id', $payload['user_id']) ->where('reward_type', $payload['reward_type']); if (!empty($payload['settle_date'])) { $existingQuery->where('settle_date', $payload['settle_date']); } $existing = $existingQuery->find(); if ($existing) { $existing->_isNewCreated = false; } return $existing; } throw $e; } } public static function writeSkip($order, $buyerId, $userId, $type, $role, $reason, $formula = '') { $orderId = is_array($order) || $order instanceof \think\Model ? ($order['id'] ?? 0) : intval($order); return self::write([ 'order_id' => $orderId, 'buyer_id' => $buyerId, 'user_id' => $userId, 'role' => $role, 'reward_type' => $type, 'amount' => 0, 'base_amount' => 0, 'ext' => [ 'skip_reason' => $reason, 'formula' => $formula ?: PartnerConfig::skipReasonText($reason), ], 'status' => -1, ]); } public static function account($order, $buyerId, $userId, $type, $role, $amount, $base, $ext = []) { $orderId = is_array($order) || $order instanceof \think\Model ? ($order['id'] ?? 0) : intval($order); $walletType = PartnerConfig::rewardWalletType(); if (!isset($ext['wallet_type'])) { $ext['wallet_type'] = $walletType; } $row = self::write([ 'order_id' => $orderId, 'buyer_id' => $buyerId, 'user_id' => $userId, 'role' => $role, 'reward_type' => $type, 'amount' => $amount, 'base_amount' => $base, 'ext' => $ext, 'status' => 1, ]); $isNew = ($row && !empty($row->_isNewCreated)) || self::$lastWriteCreated; if ($row && $isNew && (float) $amount > 0 && $userId > 0 && $row->status == 1 && in_array($type, PartnerConfig::instantTypes())) { Wallet::change($userId, $walletType, $amount, $type, [ 'partner_reward_id' => $row->id, 'order_id' => $orderId, ]); AgentModel::where('user_id', $userId)->setInc('total_income', $amount); } return $row; } }