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_balanceafter_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 内部逻辑

  1. 获取 platFeeWallet(USD钱包)余额
  2. 将 USD 余额转换为 workTask 币种进行比较
  3. 计算实际可抵扣金额
  4. 将抵扣金额转换回 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):防止超额扣除用户钱包,保护用户利益

币种转换流程

  1. 输入:平台费以 workTask 币种计算(如 100 JPY)
  2. 转换:将 platFeeWallet 的 USD 余额转换为 workTask 币种进行比较
  3. 扣除:将需要抵扣的 workTask 币种金额转换为 USD 进行钱包操作
  4. 输出:两个 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 收款业务逻辑分析
状态:已完成双架构业务逻辑分析和技术特性文档化