feat: 收益全额直入商城余额、存量结转与奖项名称全局规范化对齐

这个提交包含在:
chen
2026-09-19 11:49:57 +08:00
父节点 85947e43ae
当前提交 aca0d3c671
共有 83 个文件被更改,包括 4146 次插入 和 443 次删除

查看文件

@@ -36,7 +36,7 @@ class Pay extends Common
$payment = $this->request->post('payment');
$openid = $this->request->post('openid', '');
$money = $this->request->post('money', 0);
$money = $money > 0 ? $money : 0;
$money = floatval($money) > 0 ? round(floatval($money), 2) : 0;
$platform = $this->request->header('platform');
list($order, $order_type) = $this->getOrderInstance($order_sn);
@@ -65,30 +65,62 @@ class Pay extends Common
// pay 实例
$payOper = new PayOper();
if ($money && $order_type == 'order') {
// 余额混合支付
$order = Db::transaction(function () use ($payOper, $order, $order_type, $money) {
$mixedPayRecord = null;
if ($money > 0 && $order_type == 'order') {
// 余额混合支付(行锁串行化保障并发资金安全)
$order = Db::transaction(function () use ($order, $order_type, $money, $user, &$mixedPayRecord) {
// 加锁读订单
$order = $order->lock(true)->find($order->id);
if (in_array($order->status, [$order::STATUS_PAID, $order::STATUS_COMPLETED])) {
$this->error('订单已支付');
}
// 余额支付
$order = $payOper->money($order, $money, $order_type);
// 事务内行级排他锁查验用户最新可用余额,杜绝并发快照双花
$lockedUser = \app\admin\model\shopro\user\User::where('id', $user->id)->lock(true)->find();
if (bccomp((string)$money, (string)$lockedUser->money, 2) === 1) {
$this->error('账户可用余额不足以进行本次抵扣');
}
$deductMoney = min($money, floatval($order->remain_pay_fee));
if ($deductMoney <= 0) {
return $order;
}
// 使用锁定的实时用户对象执行余额扣减
$lockedPayOper = new PayOper($lockedUser);
$order = $lockedPayOper->money($order, $deductMoney, $order_type);
return $order;
});
// 获取刚才产生的余额支付记录,用于异常情况冲正
$mixedPayRecord = PayModel::where('order_id', $order->id)
->where('order_type', $order_type)
->where('pay_type', 'money')
->order('id', 'desc')
->find();
if (in_array($order->status, [$order::STATUS_PAID, $order::STATUS_COMPLETED])) {
$this->success('订单支付成功', $order);
}
}
if ($payment == 'money' && $order_type == 'order') {
// 余额支付
$order = Db::transaction(function () use ($payOper, $order, $order_type) {
// 纯余额支付
$order = Db::transaction(function () use ($order, $order_type, $user) {
// 加锁读订单
$order = $order->lock(true)->find($order->id);
if (in_array($order->status, [$order::STATUS_PAID, $order::STATUS_COMPLETED])) {
$this->error('订单已支付');
}
$order = $payOper->money($order, $order->remain_pay_fee, $order_type);
$lockedUser = \app\admin\model\shopro\user\User::where('id', $user->id)->lock(true)->find();
if (bccomp((string)$order->remain_pay_fee, (string)$lockedUser->money, 2) === 1) {
$this->error('账户可用余额不足以付清该订单');
}
$lockedPayOper = new PayOper($lockedUser);
$order = $lockedPayOper->money($order, $order->remain_pay_fee, $order_type);
return $order;
});
@@ -146,7 +178,10 @@ class Pay extends Common
}
if (empty($order_data['payer']['openid'])) {
// 缺少 openid
// 缺少 openid 时,若前面预扣了余额则冲正
if ($mixedPayRecord) {
$this->rollbackMixedMoney($order, $mixedPayRecord, $user, $order_type);
}
$this->error('miss_openid', -1);
}
}
@@ -160,11 +195,18 @@ class Pay extends Common
try {
$result = $payService->pay($order_data);
} catch (\Yansongda\Pay\Exception\Exception $e) {
$this->error('支付失败' . (config('app_debug') ? ":" . $e->getMessage() : ',请重试'));
} catch (HttpResponseException $e) {
$data = $e->getResponse()->getData();
$message = $data ? ($data['msg'] ?? '') : $e->getMessage();
} catch (\Exception $e) {
// 第三方预支付异常,安全冲正本次混合支付预扣款
if ($mixedPayRecord) {
$this->rollbackMixedMoney($order, $mixedPayRecord, $user, $order_type);
}
if ($e instanceof HttpResponseException) {
$data = $e->getResponse()->getData();
$message = $data ? ($data['msg'] ?? '') : $e->getMessage();
} else {
$message = $e->getMessage();
}
$this->error('支付失败' . (config('app_debug') ? ":" . $message : ',请重试'));
}
@@ -183,6 +225,39 @@ class Pay extends Common
]);
}
/**
* 混合支付预下单异常时,冲正退回预扣余额
*
* @param \think\Model $order
* @param \think\Model $mixedPayRecord
* @param \think\Model $user
* @param string $order_type
* @return void
*/
protected function rollbackMixedMoney($order, $mixedPayRecord, $user, $order_type)
{
try {
Db::transaction(function () use ($order, $mixedPayRecord, $user, $order_type) {
\addons\shopro\service\Wallet::change($user, 'money', $mixedPayRecord->pay_fee, 'order_refund', [
'order_id' => $order->id,
'order_sn' => $order->order_sn,
'order_type' => $order_type,
'remark' => '混合支付预下单未完成,安全冲正退回'
]);
$mixedPayRecord->status = PayModel::PAY_STATUS_REFUND;
$mixedPayRecord->refund_fee = $mixedPayRecord->pay_fee;
$mixedPayRecord->save();
$order = $order->lock(true)->find($order->id);
$order->remain_pay_fee = bcadd((string)$order->remain_pay_fee, (string)$mixedPayRecord->pay_fee, 2);
$order->save();
});
} catch (\Exception $e) {
Log::write('混合支付预扣款冲正异常: ' . $e->getMessage(), 'error');
}
}
/**

查看文件

@@ -303,16 +303,18 @@ class Agent extends Commission
->column('id');
if (empty($teamUserIds)) {
$selfAgent = AgentModel::where('user_id', $agentId)->find();
$selfUser = UserModel::where('id', $agentId)->find();
return [
'total_count' => 0,
'active_count' => 0,
'expiring_count' => 0,
'inactive_count' => 0,
'partner_count' => 0,
'team_total_consume' => '0.00',
'team_total_consume' => \addons\shopro\service\partner\IdentityService::formatMoney($selfUser->total_consume ?? 0),
'team_total_reward' => '0.00',
'team_month_reward' => '0.00',
'team_order_count' => 0,
'team_order_count' => intval($selfAgent->child_order_count_0 ?? 0),
];
}
@@ -348,8 +350,17 @@ class Agent extends Commission
$inactiveCount += $missingAgentCount;
}
// 1. 团队累计自购总业绩
$teamTotalConsume = UserModel::whereIn('id', $teamUserIds)->sum('total_consume');
// 1. 团队累计自购总业绩与团队订单总数(统一使用包含本人自购的分销团队全口径)
$selfAgent = AgentModel::where('user_id', $agentId)->find();
$selfUser = UserModel::where('id', $agentId)->find();
$teamTotalConsume = isset($selfAgent->child_order_money_all) && floatval($selfAgent->child_order_money_all) > 0
? floatval($selfAgent->child_order_money_all)
: (UserModel::whereIn('id', $teamUserIds)->sum('total_consume') + floatval($selfUser->total_consume ?? 0));
$teamOrderCount = isset($selfAgent->child_order_count_all) && intval($selfAgent->child_order_count_all) > 0
? intval($selfAgent->child_order_count_all)
: ($teamOrderCount + intval($selfAgent->child_order_count_0 ?? 0));
// 2. 团队成员为当前用户贡献的累计收益与本月收益
$monthStart = strtotime(date('Y-m-01 00:00:00'));

查看文件

@@ -28,8 +28,10 @@ class Index extends Commission
->where('createtime', '>=', $monthStart)
->count();
$agent = IdentityService::attachAgent($this->service->agent);
$totalIncome = $agent['total_income'] ?? '0.00';
$this->success('ok', [
'total_income' => $totalIncome,
'month_income' => IdentityService::formatMoney($monthIncome),
'skip_count' => intval($skipCount),
'active_30d' => $agent['active_30d'] ?? true,
@@ -92,10 +94,21 @@ class Index extends Commission
}
}
$monthStart = strtotime(date('Y-m-01 00:00:00'));
$monthIncome = RewardModel::where('user_id', $userId)
->where('status', 1)
->where('createtime', '>=', $monthStart)
->whereIn('reward_type', array_merge(PartnerConfig::instantTypes(), ['venue_share', 'county_share']))
->sum('amount');
$agent = IdentityService::attachAgent($this->service->agent);
$totalIncome = $agent['total_income'] ?? '0.00';
$this->success('ok', [
'total' => $list->total(),
'per_page' => $list->listRows(),
'current_page' => $list->currentPage(),
'total_income' => $totalIncome,
'month_income' => IdentityService::formatMoney($monthIncome),
'data' => $rows,
'buyer_info' => $buyerInfo,
]);
@@ -127,6 +140,7 @@ class Index extends Commission
'madow' => $agent['madow'] ?? '0.00000000',
'madow_rate' => $agent['madow_rate'] ?? IdentityService::formatRate(null),
'madow_value' => $agent['madow_value'] ?? '0.00',
'show_madow_rate' => PartnerConfig::showMadowRate(),
'list' => $list,
]);
}

查看文件

@@ -84,7 +84,20 @@ class User extends Common
$agent = \addons\shopro\service\partner\IdentityService::findAgent($user->id);
$partner = \addons\shopro\service\partner\IdentityService::attachProfile($agent);
if (empty($partner['total_income']) || $partner['total_income'] === '0.00') {
$userRewardCash = \app\admin\model\shopro\partner\Reward::where('user_id', $user->id)
->where('status', 1)
->where('amount', '>', 0)
->sum('amount');
if ($userRewardCash > 0) {
$formatted = \addons\shopro\service\partner\IdentityService::formatMoney($userRewardCash);
$partner['total_income'] = $formatted;
$partner['income_total_cash'] = $formatted;
}
}
$data = array_merge($user->toArray(), $partner);
// 对齐用户余额字段:统一映射商城余额,避免前端不同字段读取出现口径偏差
$data['balance'] = $user->money;
$this->success('个人详情', $data);
}

查看文件

@@ -19,20 +19,102 @@ class WalletLog extends Common
$list_rows = $this->request->param('list_rows', 10);
$date = $this->request->param('date/a');
$user = auth_user();
$where['user_id'] = $user->id;
$rewardEvents = [
'push3', 'maker_diff', 'maker_peer', 'maker_overtaken',
'team_venue', 'team_county', 'venue_peer', 'county_peer',
'venue_share', 'county_share', 'reward_income', 'transfer_by_commission', 'transfer_to_money'
];
$baseQuery = function () use ($user, $type, $rewardEvents) {
$model = UserWalletLogModel::where('user_id', $user->id);
if ($type === 'commission') {
// 收益记录:既包含历史 commission 流水,也对齐直入 money 的收益分成流水
$model->where(function ($query) use ($rewardEvents) {
$query->where('type', 'commission')
->whereOr(function ($sub) use ($rewardEvents) {
$sub->where('type', 'money')->whereIn('event', $rewardEvents);
});
});
} else {
$model->where('type', $type);
}
return $model;
};
$filterQuery = $baseQuery();
if (!empty($date) && is_array($date) && count($date) === 2) {
$filterQuery->whereTime('createtime', 'between', $date);
}
switch ($tab) {
case 'reward':
$filterQuery->where('amount', '>', 0)->whereIn('event', $rewardEvents);
break;
case 'income':
$where['amount'] = ['>', 0];
$filterQuery->where('amount', '>', 0);
break;
case 'expense':
$where['amount'] = ['<', 0];
$filterQuery->where('amount', '<', 0);
break;
}
$income = UserWalletLogModel::where('user_id', $user->id)->{$type}()->where('amount', '>', 0)->whereTime('createtime', 'between', $date)->sum('amount');
$expense = UserWalletLogModel::where('user_id', $user->id)->{$type}()->where('amount', '<', 0)->whereTime('createtime', 'between', $date)->sum('amount');
$logs = UserWalletLogModel::where($where)->{$type}()->whereTime('createtime', 'between', $date)->order('createtime', 'desc')->paginate($list_rows);
$this->success('获取成功', ['list' => $logs, 'income' => $income, 'expense' => $expense]);
// 统计收入与支出(统一统计口径,避免内部互转重叠统计造成误导)
$incomeQuery = $baseQuery();
$expenseQuery = $baseQuery();
$rewardQuery = $baseQuery();
if (!empty($date) && is_array($date) && count($date) === 2) {
$incomeQuery->whereTime('createtime', 'between', $date);
$expenseQuery->whereTime('createtime', 'between', $date);
$rewardQuery->whereTime('createtime', 'between', $date);
}
$actualRewardEvents = [
'push3', 'maker_diff', 'maker_peer', 'maker_overtaken',
'team_venue', 'team_county', 'venue_peer', 'county_peer',
'venue_share', 'county_share', 'reward_income'
];
if ($type === 'commission') {
// 收益流水统计口径对齐:
// 1. 真实收益(总收入):排除内部互转入账事件 transfer_by_commission,避免佣金转入余额时重复计入收益
// 2. 真实扣减(总支出):排除内部结转转出事件 transfer_to_money 与 commission_transfer,避免误导用户认为产生了消费支出
$income = (float) $incomeQuery->where('amount', '>', 0)
->where(function ($q) use ($actualRewardEvents) {
$q->where(function ($sub) use ($actualRewardEvents) {
$sub->where('type', 'money')->whereIn('event', $actualRewardEvents);
})->whereOr(function ($sub) {
$sub->where('type', 'commission')->whereNotIn('event', ['transfer_by_commission']);
});
})->sum('amount');
$expense = (float) $expenseQuery->where('amount', '<', 0)
->whereNotIn('event', ['transfer_to_money', 'commission_transfer'])
->sum('amount');
$rewardIncome = (float) $rewardQuery->where('amount', '>', 0)
->where(function ($q) use ($actualRewardEvents) {
$q->where(function ($sub) use ($actualRewardEvents) {
$sub->where('type', 'money')->whereIn('event', $actualRewardEvents);
})->whereOr(function ($sub) {
$sub->where('type', 'commission')->whereNotIn('event', ['transfer_by_commission']);
});
})->sum('amount');
} else {
$income = (float) $incomeQuery->where('amount', '>', 0)->sum('amount');
$expense = (float) $expenseQuery->where('amount', '<', 0)->sum('amount');
$rewardIncome = (float) $rewardQuery->where('amount', '>', 0)->whereIn('event', $actualRewardEvents)->sum('amount');
}
$logs = $filterQuery->order('id', 'desc')->paginate($list_rows);
$this->success('获取成功', [
'list' => $logs,
'income' => $income,
'expense' => $expense,
'reward_income' => $rewardIncome,
]);
}
}