382 行
15 KiB
PHP
382 行
15 KiB
PHP
<?php
|
|
|
|
namespace addons\shopro\service\partner;
|
|
|
|
use think\Db;
|
|
use addons\shopro\service\Wallet;
|
|
use app\admin\model\shopro\partner\Reward as RewardModel;
|
|
use app\admin\model\shopro\order\Order as OrderModel;
|
|
use app\admin\model\shopro\commission\CommissionGoods;
|
|
use app\admin\model\shopro\commission\Agent as AgentModel;
|
|
|
|
/**
|
|
* 订单即时奖入口 + 退款回滚
|
|
*/
|
|
class PartnerRewardService
|
|
{
|
|
public function settleOrder($orderId)
|
|
{
|
|
if (!PartnerConfig::isEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
return Db::transaction(function () use ($orderId) {
|
|
$order = OrderModel::with(['items'])->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;
|
|
}
|
|
}
|