PiPiPen 工作任务确认业务逻辑分析文档
概述
本文档详细分析了 WorkTaskController::confrimStageWorkStatus 方法中 CNY 和非 CNY 两种不同货币的业务逻辑和资金流转过程。该方法处理工作任务完成时的资金结算,涉及多币种转换、平台费抵扣、以及复杂的钱包体系设计。
核心逻辑分支
CNY 收款分支($workTask->currency->isCNY):
- 使用 aliPayWallet 单钱包架构
- 直接以 CNY 币种进行所有计算
- $totalAmount = $workTask->price 已包含 CreditWallet 抵扣金额
- 艺术家收入 = $totalAmount - $payFee - $platFee
非 CNY 收款分支(! $workTask->currency->isCNY):
- 使用 stripeFullWallet + stripeRecipientWallet 双钱包架构
- 涉及汇率转换和零小数货币处理
- 需要单独补偿 CreditWallet 抵扣金额
- 复杂的多币种费用计算和补偿机制
核心业务场景
CNY 收款场景
当工作任务的货币是CNY时,系统采用支付宝(aliPay)生态进行资金处理,使用单一钱包架构。
非 CNY 收款场景
当工作任务的货币不是CNY时(如USD、JPY、EUR等),系统采用 Stripe 支付生态进行资金处理,使用双钱包架构。
关键代码位置
- 文件:
app/Http/Controllers/Api/User/WorkTaskController.php
- 方法:
confrimStageWorkStatus
- CNY 逻辑分支:
$workTask->currency->isCNY(第245-358行)
- 非 CNY 逻辑分支:
! $workTask->currency->isCNY(第359-474行)
重要安全特性
- 幂等性保护:第187-189行检查工作任务状态,防止重复执行
- 并发安全:使用分布式锁(第162-165行)和数据库锁(第182行)
- 异常处理:完善的 try-catch-finally 结构确保事务一致性和锁释放(第168-492行)
钱包体系设计
设计架构对比
CNY 收款架构:单一钱包系统
- 使用 aliPayWallet 进行所有资金处理
- 直接以 CNY 币种进行计算,无需汇率转换
- CreditWallet 抵扣已包含在
$totalAmount 中
非 CNY 收款架构:三钱包系统
- stripeFullWallet:记录 Stripe 实际到账净收入
- stripeRecipientWallet:补偿各种抵扣和费用
- platFeeWallet:平台费抵扣福利钱包
CNY 收款钱包设计
aliPayWallet(支付宝钱包)
- 币种:CNY(人民币)
- 用途:CNY 收款的唯一钱包,处理所有资金流转
- 资金来源:用户支付的完整订单金额
- 关键特点:
- $totalAmount = $workTask->price(已包含所有费用和抵扣)
- 不需要单独的 CreditWallet 补偿机制
- 艺术家最终收入 = $totalAmount - $payFee - $platFee
- 钱包类型:
WalletUsage::Deposit + WalletDepositType::Alipay
- 安全特性:使用
firstOrFail() 确保钱包存在(第246-251行)
核心设计原则(非 CNY 收款)
在修复 bug 过程中,我们确认了三钱包体系的精确设计原则:
1. stripeFullWallet(Stripe全功能钱包)
- 币种:与 workTask 币种一致(如 JPY、EUR 等)
- 数据存储:零小数货币以"分"为单位存储(如100日元存储为10000)
- 用途:记录从 Stripe Connect 账户实际到账的净收入
- 资金来源:
balance_transaction['net'](扣除 Stripe 手续费后的净额)
- 钱包类型:
WalletUsage::Deposit + WalletDepositType::StripeFull
- 安全特性:使用
firstOrFail() 确保钱包存在(第353-355行)
2. stripeRecipientWallet(补差钱包)
- 币种:固定为 USD(存储为美分)
- 用途:确保艺术家获得完整收入的补偿钱包
- 补偿内容:
- 用户使用 CreditWallet 抵现的金额(USD,不需要零小数货币转换)
- platFeeWallet 抵扣的平台费用(USD)
- 钱包类型:
WalletUsage::Deposit + WalletDepositType::StripeRecipient
- 安全特性:使用
firstOrFail() 确保钱包存在(第357-361行)
3. platFeeWallet(平台费抵扣钱包)
- 币种:固定为 USD(存储为美分)
- 性质:平台发放给艺术家的福利钱包
- 用途:专门用于抵扣平台收取的手续费
- 使用规则:只能用于抵扣,不能提现
- 钱包类型:
WalletUsage::PlatFee
- 重要逻辑:通过
PlatFeeWalletService 处理复杂的汇率转换和余额扣除
CNY 收款业务流程分析
业务场景举例(CNY 收款)
假设条件:
- 用户订单:1000 CNY
- 用户使用 CreditWallet 抵扣:200 CNY
- 用户实际支付:800 CNY
- 平台费率:10%
- 支付手续费:30 CNY
重要理解:
$totalAmount = $workTask->price = 1000 CNY(包含 CreditWallet 抵扣)
- 用户实际支付 + CreditWallet 抵扣 = 总订单金额
- 800 CNY(实际支付)+ 200 CNY(CreditWallet)= 1000 CNY(总订单)
第一步:获取 aliPayWallet 钱包(第246-251行)
$aliPayWallet = Wallet::where('user_id', $workTask->artist->user_id)
->where('currency_id', $currency->id) // CNY 货币
->where('usage', WalletUsage::Deposit) // 收款钱包
->where('deposit_type', WalletDepositType::Alipay) // 支付宝类型
->lockForUpdate()
->firstOrFail();
关键设计:
- 使用
firstOrFail() 确保钱包存在
- 数据库锁确保并发安全
- 单一钱包处理所有 CNY 资金流转
第二步:设置基础金额(第253-256行)
$totalAmount = $workTask->price; // 1000 CNY(包含 CreditWallet 抵扣)
$payFee = 0; // 支付手续费(从订单中累计)
$userDeductionAmount = 0; // CreditWallet 抵扣金额(需要记录)
关键理解:
$totalAmount 直接使用 $workTask->price,不是从 Stripe 获取的 balance_transaction['net']
- 这个金额已经包含了 CreditWallet 抵扣的部分
- 不需要像非 CNY 那样单独补偿 CreditWallet
第三步:处理订单费用信息(第258-282行)
$workTask->orders
->where('status', OrderStatus::Paid)
->each(function ($order) use (&$payFee, &$userDeductionAmount) {
// 验证货币一致性
if (strtoupper($order->payment_info->balance_transaction['currency']) !== Currency::BaseCurrency) {
abort(400, 'Currency not match');
}
// 累计支付手续费
$payFee += $order->fee->amount;
// 计算 CreditWallet 抵扣金额并转换为 CNY
$order->walletDeductionRecords
->where('status', WalletDeductionStatus::Pending)
->each(function ($record) use (&$userDeductionAmount, $order) {
// 将 USD 抵扣转换为 CNY(使用订单中的汇率)
$userDeductionAmount += $record->amount * $order->fee->exchange_rate;
});
// 更新抵扣记录状态
$order->walletDeductionRecords()
->where('status', WalletDeductionStatus::Pending)
->update(['status' => WalletDeductionStatus::WalletDeposited]);
});
处理结果:
$payFee = 30 CNY(支付手续费)
$userDeductionAmount = 200 CNY(CreditWallet 抵扣,用于记录)
- 抵扣记录状态更新为已处理
第四步:计算平台费(第283-305行)
// 计算平台费
$platFee = round($totalAmount * $platFeeRatio, 2); // 1000 * 0.1 = 100 CNY
$platFee = ceil($platFee); // 向上取整:100 CNY
$beforePlatFee = $platFee; // 记录原始平台费
// 处理平台费抵扣
$platFeeWalletService = new PlatFeeWalletService;
$platFeeProcessStage = $platFeeWalletService->minus($workTask->artist->user, Amount::new($platFee, $currency));
if ($platFeeProcessStage) {
$platFee = $platFeeProcessStage->after_amount->amount; // 抵扣后的平台费
// 更新 platFeeWallet 余额
$platFeeWallet = Wallet::find($platFeeProcessStage->wallet_id);
$platFeeWallet->balance = $platFeeProcessStage->wallet_balance_after->amount;
$platFeeWallet->save();
// 记录 platFeeWallet 交易
$platFeeWallet->transactions()->create([
'user_id' => $workTask->artist->user->id,
'currency_id' => $platFeeProcessStage->wallet_balance->currency->id,
'action' => WalletTransactionAction::Minus,
'biz_type' => WalletTransactionBizType::PlatFeeDeduct,
'status' => WalletTransactionStatus::Successed,
'before_balance' => $platFeeProcessStage->wallet_balance->amount,
'after_balance' => $platFeeProcessStage->wallet_balance_after->amount,
'amount' => $platFeeProcessStage->wallet_minus->amount,
]);
}
平台费处理场景:
- 如果 platFeeWallet 有足够余额:
$platFee 减少到 0 或更低
- 如果 platFeeWallet 余额不足:
$platFee 部分减少
- 如果 platFeeWallet 无余额:
$platFee 保持原值
第五步:计算艺术家收入(第307-310行)
// 艺术家收入计算:订单总额减去各种费用
$amount = $totalAmount - $payFee - $platFee;
$amount = ceil($amount); // 向上取整
计算示例:
- 假设 platFeeWallet 完全抵扣了平台费($platFee = 0):
$amount = 1000 - 30 - 0 = 970 CNY
- 假设 platFeeWallet 无余额($platFee = 100):
$amount = 1000 - 30 - 100 = 870 CNY
第六步:更新 aliPayWallet 余额(第311-358行)
// 安全的余额更新
$beforeBalance = $aliPayWallet->balance;
$afterBalance = $beforeBalance + $amount;
$aliPayWallet->increment('balance', $amount);
// 创建详细的交易记录
$detail = [
[
'type' => 'init',
'amount' => $totalAmount, // 1000 CNY
'biz_type' => WalletTransactionBizType::WorkTask->value,
'biz_info' => [
'work_task_id' => $workTask->id,
'user_deduction_amount' => $userDeductionAmount, // 200 CNY
],
],
[
'type' => 'pay_fee',
'action' => WalletTransactionAction::Minus,
'amount' => $payFee, // 30 CNY
],
[
'type' => 'plat_fee',
'action' => WalletTransactionAction::Minus,
'amount' => $beforePlatFee, // 100 CNY(原始平台费)
],
[
'type' => 'plat_fee',
'action' => WalletTransactionAction::Plus,
'amount' => $platFeeProcessStage ? $platFeeProcessStage->amount_minus->amount : 0,
],
[
'type' => 'result',
'amount' => $amount, // 970 CNY(最终收入)
],
];
// 创建交易记录
$aliPayWallet->transactions()->create([
'user_id' => $workTask->artist->user_id,
'currency_id' => $currency->id,
'action' => WalletTransactionAction::Plus,
'biz_type' => WalletTransactionBizType::WorkTask,
'status' => WalletTransactionStatus::Successed,
'before_balance' => $beforeBalance,
'after_balance' => $afterBalance,
'amount' => $amount,
'detail' => $detail,
]);
CNY 与非 CNY 业务逻辑对比
核心差异总结
| 特性 | CNY 收款 | 非 CNY 收款 |
|---|
| 钱包架构 | 单一 aliPayWallet | 三钱包系统 |
| 总金额来源 | $workTask->price | $order->payment_info->balance_transaction['net'] |
| CreditWallet 处理 | 已包含在 $totalAmount 中 | 需要单独补偿到 stripeRecipientWallet |
| 汇率转换 | 无需转换(直接 CNY) | 需要复杂的汇率转换 |
| 零小数货币 | 不涉及 | 需要特殊处理 |
| 费用补偿 | 无需补偿 | 需要补偿到 stripeRecipientWallet |
| 平台费抵扣 | 直接在 CNY 中计算 | 需要汇率转换 |
关键理解:CreditWallet 处理差异
CNY 收款:
$totalAmount = $workTask->price(1000 CNY)
- 这个金额已经包含了 CreditWallet 的抵扣部分
- 用户实际支付 800 CNY + CreditWallet 抵扣 200 CNY = 1000 CNY
- 艺术家收入基于完整的 1000 CNY 计算
- 不需要额外补偿
非 CNY 收款:
$totalAmount 来自 Stripe 的 balance_transaction['net']
- 这个金额是用户实际支付的净到账金额
- 不包含 CreditWallet 抵扣部分
- 需要单独补偿 CreditWallet 到 stripeRecipientWallet
- 需要额外补偿机制
业务逻辑完整性
CNY 收款的设计优势:
- 简化的单钱包架构
- 避免复杂的汇率转换
- CreditWallet 补偿天然内置
- 计算逻辑更加直观
非 CNY 收款的设计复杂性:
- 需要处理多币种和汇率
- 零小数货币的特殊处理
- 多钱包协调工作
- 复杂的补偿机制
这两种设计反映了不同支付生态(支付宝 vs Stripe)的特点和限制,CNY 收款利用了支付宝的简化支付流程,而非 CNY 收款则需要适应 Stripe Connect 的复杂架构。
详细业务流程分析(非 CNY 收款)
业务场景举例
假设条件:
- 用户支付:1000 JPY
- 平台费:100 JPY
- platFeeWallet 余额:50 USD
- 当前汇率:1 USD = 150 JPY
第一步:数据验证和安全检查(第362-385行)
$workTask->orders
->where('status', OrderStatus::Paid)
->each(function ($order) use (&$totalAmount, &$payFee, &$platFee, &$userDeductionAmount) {
// 获取 Stripe 实际到账金额(扣除手续费后)
$totalAmount += $order->payment_info->balance_transaction['net'];
// 解析费用结构
$fee_details = $order->payment_info->balance_transaction['fee_details'];
$payFee += collect($fee_details)->where('type', 'stripe_fee')->sum('amount');
$platFee += collect($fee_details)->where('type', 'application_fee')->sum('amount');
// 累计用户礼品卡抵扣金额(USD,不需要转换)
$order->walletDeductionRecords
->where('status', WalletDeductionStatus::Pending)
->each(function ($record) use (&$userDeductionAmount) {
$userDeductionAmount += $record->amount;
});
});
// 关键安全检查:防止负数金额
if ($totalAmount < 0 || $payFee < 0 || $platFee < 0) {
abort(400, 'Invalid amount data from Stripe');
}
重要发现:
$userDeductionAmount 始终为 USD 币种,因为来自 walletDeductionRecords
- 添加了负数验证,防止 Stripe 异常数据导致系统问题
解析结果:
$totalAmount:实际到账净额(JPY)
$payFee:Stripe 手续费(JPY)
$platFee:平台应用费(JPY)
$userDeductionAmount:用户礼品卡抵扣金额(JPY)
第二步:零小数货币处理(第387-392行)
// ## zero decimal currency
if ($workTask->currency->is_zero_decimal) {
$totalAmount = $totalAmount * 100;
$payFee = $payFee * 100;
$platFee = $platFee * 100;
// 注意:userDeductionAmount 不需要转换,因为它是 USD 币种
}
重要澄清:
- 零小数货币列表:JPY、KRW、VND、BIF、CLP等
- 数据存储规则:零小数货币在数据库中以"分"为单位存储(100日元存储为10000)
- 为什么 userDeductionAmount 不转换:它来自 USD 的
walletDeductionRecords,与 workTask 币种无关
第三步:钱包余额更新和交易记录(第394-421行)
// 安全的余额更新模式(修复了数据一致性问题)
$beforeBalance = $stripeFullWallet->balance;
$afterBalance = $beforeBalance + $totalAmount;
$stripeFullWallet->increment('balance', $totalAmount);
$stripeFullWallet->transactions()->create([
'before_balance' => $beforeBalance, // 正确:原始余额
'after_balance' => $afterBalance, // 正确:计算后的余额
'amount' => $totalAmount,
// ... 其他字段
]);
修复关键点:
- 在执行
increment() 前保存原始余额
- 确保交易记录中的
before_balance 和 after_balance 准确无误
结果:记录 Stripe 实际到账的净收入
第四步:补偿用户礼品卡抵扣(第409-421行)
// 同样使用安全的余额更新模式
$beforeBalance = $stripeRecipientWallet->balance;
$afterBalance = $beforeBalance + $userDeductionAmount;
$stripeRecipientWallet->increment('balance', $userDeductionAmount);
$stripeRecipientWallet->transactions()->create([
'before_balance' => $beforeBalance, // 正确:原始余额
'after_balance' => $afterBalance, // 正确:计算后的余额
'amount' => $userDeductionAmount, // USD 金额,不需要转换
// ... 其他字段
]);
结果:补偿用户使用礼品卡抵扣的金额(USD)
第五步:平台费抵扣与汇率计算(第424-456行)
重大修复:此步骤在原实现中有严重的逻辑错误,经过多次 bug 修复已完全纠正
5.1 调用 PlatFeeWalletService 处理
// 调用专门的服务处理平台费抵扣
$platFeeWalletService = new PlatFeeWalletService;
$platFeeProcessStage = $platFeeWalletService->minus($workTask->artist->user, Amount::new($platFee, $currency));
PlatFeeWalletService::minus 内部逻辑:
- 获取 platFeeWallet(USD钱包)余额
- 将 USD 余额转换为 workTask 币种进行比较
- 计算实际可抵扣金额
- 将抵扣金额转换回 USD 进行钱包操作
5.2 计算 platFeeWallet 等值金额
// 在 PlatFeeWalletService::minus 方法中
$wallet_exchange_balance = RateService::new()->amountExchange(
$wallet_balance, // 50 USD
$wallet_balance_currency->code, // "USD"
$exchange_currency->code, // "JPY"
'floor' // 向下取整,保护用户利益
);
计算结果:50 USD × 150 = 7500 JPY(向下取整)
5.3 判断抵扣逻辑
场景A:platFeeWallet 余额充足(7500 JPY > 100 JPY)
$amount_minus = $amount_before->amount; // 100 JPY(完全抵扣)
$amount_after->amount = 0; // 0 JPY(无需额外支付)
// 计算从 platFeeWallet 实际扣除的 USD 金额
$wallet_minus = RateService::new()->amountExchange(
$amount_minus, // 100 JPY
$exchange_currency->code, // "JPY"
$wallet_balance_currency->code, // "USD"
'ceil' // 向上取整,确保平台费完全覆盖
);
// 100 ÷ 150 = 0.67,向上取整 = 1 USD
场景B:platFeeWallet 余额不足
假设 platFeeWallet 只有 0.5 USD(等值75 JPY):
$amount_minus = $wallet_exchange_balance; // 75 JPY(部分抵扣)
$amount_after->amount = 25; // 25 JPY(艺术家承担)
$wallet_minus = $wallet_balance; // 0.5 USD(全部扣除)
5.4 更新 platFeeWallet(修复后的正确实现)
$platFeeWallet = Wallet::find($platFeeProcessStage->wallet_id);
// 关键修复:使用正确的余额更新模式
$beforeBalance = $platFeeWallet->balance;
$afterBalance = $beforeBalance - $platFeeProcessStage->wallet_minus->amount;
$platFeeWallet->decrement('balance', $platFeeProcessStage->wallet_minus->amount);
// 记录交易:正确的减少操作
$platFeeWallet->transactions()->create([
'user_id' => $workTask->artist->user->id,
'currency_id' => $platFeeProcessStage->wallet_balance->currency->id,
'action' => WalletTransactionAction::Minus,
'biz_type' => WalletTransactionBizType::PlatFeeDeduct,
'status' => WalletTransactionStatus::Successed,
'before_balance' => $beforeBalance, // 正确:原始余额
'after_balance' => $afterBalance, // 正确:扣除后余额
'amount' => $platFeeProcessStage->wallet_minus->amount, // 1 USD
]);
重要修复点:
- 之前的错误:使用
increment() 错误地增加 platFeeWallet 余额
- 修复后:使用
decrement() 正确地减少 platFeeWallet 余额
- 数据一致性:before_balance 和 after_balance 现在准确记录
5.5 stripeRecipientWallet 补偿平台费
// 补偿从 platFeeWallet 扣除的 USD 金额
$beforeBalance = $stripeRecipientWallet->balance;
$afterBalance = $beforeBalance + $platFeeProcessStage->wallet_minus->amount;
$stripeRecipientWallet->increment('balance', $platFeeProcessStage->wallet_minus->amount);
$stripeRecipientWallet->transactions()->create([
'user_id' => $workTask->artist->user->id,
'currency_id' => $stripeRecipientWallet->currency_id,
'action' => WalletTransactionAction::Plus,
'biz_type' => WalletTransactionBizType::WorkTask,
'status' => WalletTransactionStatus::Successed,
'before_balance' => $beforeBalance, // 正确:原始余额
'after_balance' => $afterBalance, // 正确:增加后余额
'amount' => $platFeeProcessStage->wallet_minus->amount, // 1 USD
]);
汇率处理的关键设计
汇率舍入策略
- 扣除时向上取整(ceil):确保平台费完全覆盖,保护平台利益
- 兑换时向下取整(floor):防止超额扣除用户钱包,保护用户利益
币种转换流程
- 输入:平台费以 workTask 币种计算(如 100 JPY)
- 转换:将 platFeeWallet 的 USD 余额转换为 workTask 币种进行比较
- 扣除:将需要抵扣的 workTask 币种金额转换为 USD 进行钱包操作
- 输出:两个 USD 钱包的余额变动
不同场景的处理结果
场景1:platFeeWallet 余额充足
- 输入:100 JPY 平台费,50 USD platFeeWallet
- 处理:
- platFeeWallet:50 USD → 49 USD(扣除1 USD)
- stripeRecipientWallet:+1 USD
- 结果:艺术家实际承担平台费:0 JPY
场景2:platFeeWallet 余额不足
- 输入:100 JPY 平台费,0.5 USD platFeeWallet
- 处理:
- 0.5 USD 等值:75 JPY
- platFeeWallet:0.5 USD → 0 USD(全部扣除)
- stripeRecipientWallet:+0.5 USD
- 结果:艺术家实际承担平台费:25 JPY
场景3:platFeeWallet 余额为0
- 输入:100 JPY 平台费,0 USD platFeeWallet
- 处理:不进行任何抵扣操作
- 结果:艺术家承担全部平台费:100 JPY
最终资金流向总结
以 1000 JPY 支付为例:
资金分配
- Stripe 手续费:自动扣除(如 30 JPY)
- 平台应用费:100 JPY
- 净到账金额:870 JPY
钱包变动
- stripeFullWallet:+870 JPY(实际到账金额)
- platFeeWallet:-1 USD(抵扣平台费)
- stripeRecipientWallet:+1 USD(补偿平台费)+ 其他补偿
艺术家收入
- 总收入:stripeFullWallet + stripeRecipientWallet(按汇率折算)
- 实际效果:获得完整的预期收入,平台费被 USD 钱包抵扣
安全特性与错误处理
1. 并发安全机制
- 分布式锁:使用 Redis 缓存锁防止同一工作任务的并发处理
- 数据库锁:
lockForUpdate() 确保数据一致性
- 锁超时设置:10秒超时防止死锁
$lockKey = "lock:confrimStageWorkStatus:{$request->id}";
$lock = Cache::lock($lockKey, 10);
if (!$lock->get()) {
abort(429, '系统繁忙,请稍后重试');
}
2. 幂等性保护
- 状态检查:防止重复执行已完成的工作任务
- 早期返回:已完成的任务直接返回成功
if ($workTask->status === WorkTask::StatusFinished) {
return ['ok' => true]; // 幂等性保护
}
3. 数据验证与安全检查
- 金额有效性验证:防止 Stripe 返回负数或异常数据
- 钱包存在性检查:使用
firstOrFail() 确保钱包存在
- 空值安全检查:防止关联对象为空导致的错误
// 金额验证
if ($totalAmount < 0 || $payFee < 0 || $platFee < 0) {
abort(400, 'Invalid amount data from Stripe');
}
// 钱包存在性检查
$stripeFullWallet = Wallet::where('user_id', $workTask->artist->user_id)
->where('currency_id', $currency->id)
->where('usage', WalletUsage::Deposit)
->where('deposit_type', WalletDepositType::StripeFull)
->lockForUpdate()
->firstOrFail(); // 使用 firstOrFail 而不是 first
4. 完善的异常处理
- 统一异常处理:try-catch-finally 结构
- 事务回滚:确保数据一致性
- 锁释放保证:finally 块确保锁被正确释放
- 详细错误日志:记录完整的错误信息和堆栈跟踪
try {
DB::beginTransaction();
// 业务逻辑
DB::commit();
} catch (\Exception $e) {
DB::rollback();
Log::error('WorkTask confirmation failed', [
'work_task_id' => $request->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
} finally {
$lock->release(); // 确保锁被释放
}
5. 数据一致性保证
- 正确的余额记录:before_balance 和 after_balance 准确记录
- 交易记录完整性:所有钱包操作都有对应的交易记录
- 操作原子性:所有相关操作在同一事务中完成
// 正确的余额更新模式
$beforeBalance = $wallet->balance;
$afterBalance = $beforeBalance + $amount;
$wallet->increment('balance', $amount);
$wallet->transactions()->create([
'before_balance' => $beforeBalance, // 正确记录原始余额
'after_balance' => $afterBalance, // 正确记录更新后余额
'amount' => $amount,
]);
技术特点与优势
1. 多币种支持
- 任意币种支持:支持所有 Stripe 支持的货币
- 智能汇率转换:自动处理汇率转换和舍入
- 零小数货币处理:正确处理 JPY、KRW 等货币的存储格式
2. 风险控制
- 多层并发保护:分布式锁 + 数据库锁
- 汇率舍入策略:向上取整保护平台,向下取整保护用户
- 完整的审计追踪:详细的交易记录确保可追溯性
- 输入验证:全面的数据验证和安全检查
3. 用户体验
- 平台费抵扣:减少艺术家负担的福利机制
- 透明的费用计算:清晰的费用分解和记录
- 灵活的补偿机制:确保艺术家获得完整收入
4. 业务灵活性
- 动态费用政策:通过调整 platFeeWallet 余额来调节费用政策
- 复杂业务场景支持:支持多币种、多阶段的复杂委托
- 模块化服务设计:便于维护和扩展
5. 系统稳定性
- 幂等性设计:防止重复操作的副作用
- 故障恢复:完善的异常处理和事务回滚
- 监控友好:详细的日志记录便于问题排查
相关代码文件
核心控制器
app/Http/Controllers/Api/User/WorkTaskController.php
服务类
app/Service/Wallet/PlatFeeWalletService.php
app/Service/RateService.php
app/Service/Stripe/Amount.php
模型类
app/Models/WorkTask.php
app/Models/Wallet.php
app/Models/Currency.php
app/Models/Order.php
枚举类
app/Enums/WalletUsage.php
app/Enums/WalletDepositType.php
app/Enums/WalletTransactionAction.php
app/Enums/WalletTransactionBizType.php
总结
这套业务逻辑设计巧妙地处理了 CNY 和非 CNY 两种不同收款环境下的复杂费用结算问题。通过在 bug 修复过程中的深入分析和多次迭代,我们完善了整个系统的设计,实现了:
1. 双架构设计精髓
CNY 收款架构:
- 单一钱包设计:aliPayWallet 处理所有资金流转
- 天然补偿机制:CreditWallet 抵扣已包含在 $totalAmount 中
- 简化计算流程:艺术家收入 = 订单总额 - 支付费用 - 平台费
- 直接费用处理:无需复杂的汇率转换和多钱包协调
非 CNY 收款架构:
- 三钱包协作:stripeFullWallet + stripeRecipientWallet + platFeeWallet
- 精确补偿机制:单独补偿 CreditWallet 和平台费抵扣
- 复杂汇率处理:多币种环境下的精确计算和舍入策略
- 零小数货币支持:特殊处理 JPY、KRW 等货币格式
2. 业务逻辑完整性
CNY 收款的核心优势:
- 利用支付宝生态的简化支付流程
- CreditWallet 补偿天然内置,无需额外处理
- 计算逻辑直观,维护成本低
- 适应中国市场的支付习惯
非 CNY 收款的技术复杂性:
- 适应 Stripe Connect 的复杂架构
- 处理全球多币种和汇率波动
- 零小数货币的特殊存储和计算
- 多钱包协调确保资金流转准确性
3. 完善的三钱包架构(非 CNY)
- 收入记录:stripeFullWallet 准确记录 Stripe 实际到账的净收入
- 费用抵扣:platFeeWallet 提供平台费抵扣的福利机制
- 收入补偿:stripeRecipientWallet 确保艺术家获得完整收入
4. 健壮的安全保障(通用)
- 并发安全:多层锁机制防止数据竞争
- 数据一致性:正确的余额记录和交易追踪
- 异常恢复:完善的错误处理和事务回滚
- 输入验证:全面的数据验证和安全检查
- 幂等性保护:防止重复操作的副作用
5. 精确的业务逻辑(通用)
- 汇率处理:合理的舍入策略保护各方利益
- 币种支持:正确处理零小数货币的特殊格式
- 费用计算:透明准确的费用分解和处理
- 状态管理:完整的任务状态流转控制
6. 关键技术特点对比
CNY 收款的技术特点:
- 单一数据源:$totalAmount 直接来自 $workTask->price
- 内置补偿:CreditWallet 抵扣天然包含在总金额中
- 简化交易记录:详细的 detail 数组记录所有费用分解
- 直接费用扣除:所有费用直接从 CNY 总额中扣除
非 CNY 收款的技术特点:
- 多数据源协调:从 Stripe balance_transaction 获取净收入
- 显式补偿机制:单独补偿 CreditWallet 和平台费抵扣
- 复杂汇率计算:多币种转换和精确舍入策略
- 多钱包同步:确保三个钱包的数据一致性
7. 关键修复成果
在 bug 修复过程中,我们解决了多个关键问题:
- 平台费抵扣逻辑:修复了 increment/decrement 操作方向错误
- 数据一致性:确保 before_balance 和 after_balance 的准确记录
- 零小数货币处理:澄清了 userDeductionAmount 的正确处理方式
- 异常处理:完善了锁释放和错误恢复机制
- 安全检查:增加了空值验证和金额有效性检查
8. 架构设计价值
统一的设计哲学:
- 不同支付生态采用最适合的架构设计
- CNY 利用支付宝的简化流程,非 CNY 适应 Stripe 的复杂架构
- 两种架构都确保艺术家获得完整的预期收入
- 平台费抵扣机制在两种架构中都得到了良好实现
业务灵活性:
- CNY 收款:适应中国市场的支付习惯和监管要求
- 非 CNY 收款:支持全球多币种的复杂国际支付场景
- 统一接口:对上层业务逻辑提供一致的处理接口
9. 生产就绪特性(通用)
- 可扩展性:模块化的服务设计支持功能扩展
- 可维护性:清晰的代码结构和详细的日志记录
- 可监控性:完整的交易记录支持业务监控和审计
- 国际化支持:多币种和多语言的完整支持
10. 核心业务价值
用户体验:
- CNY 用户享受简化的支付流程
- 非 CNY 用户获得全球化的支付体验
- 平台费抵扣减少艺术家负担
- 透明的费用计算和记录
业务扩展性:
- 支持中国和国际两个市场的不同支付生态
- 复杂的费用政策和抵扣机制
- 完整的合规性和审计追踪
- 灵活的汇率和费用管理
这个双架构系统在保证财务准确性和系统稳定性的同时,充分适应了不同市场的支付特点,提供了良好的用户体验和业务灵活性。CNY 收款的简化设计和非 CNY 收款的复杂架构,都是针对具体业务场景的最优解决方案。通过多次 bug 修复和优化,两套代码都已达到生产可用的高质量标准。
文档创建时间:2025-01-11
最后更新时间:2025-01-11
版本:3.0 - 已整合 CNY 和非 CNY 收款业务逻辑分析
状态:已完成双架构业务逻辑分析和技术特性文档化