Laravel中Stripe关联账户多商家订单佣金扣除代码修改需求
Laravel多商家Stripe一次性支付实现方案
原代码的问题在于为每个商品单独创建Payment Intent,导致用户需要多次支付。下面修改为一次性完成支付,并自动向两个关联账户分账(扣除平台佣金):
修改后的代码
<?php namespace App\Http\Controllers\Stripe\MarketPlace\PaymentIntent; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Stripe\Stripe; use Stripe\Customer; use Stripe\Checkout\Session; use Stripe\Transfer; use Stripe\Exception\ApiErrorException; class MultiVendor extends Controller { /** * 处理多商家一次性支付请求 * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function __invoke(Request $request) { try { Stripe::setApiKey(config('services.stripe.secret')); $stripe = new \Stripe\StripeClient([ 'api_key' => config('services.stripe.secret'), ]); // 创建或获取已有客户(实际项目建议从请求中获取用户信息) $customer = Customer::create([ 'email' => 'mehdi@example.com', 'name' => 'Cavid Mehdi', 'address' => [ 'line1' => '123 Main St', 'city' => 'Anytown', 'state' => 'CA', 'postal_code' => '12345', 'country' => 'US', ], 'metadata' => [ 'order_id' => '1234', 'product_ids' => '5678,9012,3456' ] ]); // 定义购物车商品(实际应从请求中获取) $onceConnectedAccountId = 'acct_1OZWoOGbXQHnE0RA'; $secondConnectedAccountId = 'acct_1OZAXz2e37RpwEkH'; $items = [ [ 'name' => "Amazon Essentials Women's Skinny Jean", 'description' => 'Authentic five-pocket styling, zip fly, shank button closure, and skinny leg. Available in short, regular, and tall inseam lengths.', 'images' => ['https://m.media-amazon.com/images/I/7174TDiYiWL._AC_SY879_.jpg'], 'amount' => 10000, // 单位:分 'connected_account_id' => $onceConnectedAccountId ], [ 'name' => 'Under Armour Unisex-Adult Lockdown 6 Basketball Shoe', 'description' => 'Durable solid rubber outsole with herringbone traction pattern for ultimate on-court movement & control', 'images' => ['https://m.media-amazon.com/images/I/51f97K1qS7L._AC_SY695_.jpg'], 'amount' => 15000, 'connected_account_id' => $onceConnectedAccountId ], [ 'name' => "Amazon Essentials Women's Fleece Blouson Sleeve Crewneck Sweatshirt Dress", 'description' => 'COZY BRUSHED BACK FLEECE: Buttery soft and comfy midweight cotton blend fleece with brushed interior.', 'images' => ['https://m.media-amazon.com/images/I/714Io18547L._AC_SY879_.jpg'], 'amount' => 16200, 'connected_account_id' => $secondConnectedAccountId ], ]; // 按商家分组计算总金额,同时构建Checkout商品项 $vendorTotals = []; $totalAmount = 0; $checkoutLineItems = []; $applicationFeePercentage = 5; // 平台佣金比例5% foreach ($items as $item) { $totalAmount += $item['amount']; // 构建Checkout所需的商品项结构 $checkoutLineItems[] = [ 'price_data' => [ 'currency' => 'usd', 'product_data' => [ 'name' => $item['name'], 'description' => $item['description'], 'images' => $item['images'], ], 'unit_amount' => $item['amount'], ], 'quantity' => 1, ]; // 按商家ID累计订单金额 if (!isset($vendorTotals[$item['connected_account_id']])) { $vendorTotals[$item['connected_account_id']] = 0; } $vendorTotals[$item['connected_account_id']] += $item['amount']; } // 创建Stripe Checkout Session,实现一次性支付 $session = Session::create([ 'customer' => $customer->id, 'payment_method_types' => ['card'], 'line_items' => $checkoutLineItems, 'mode' => 'payment', 'success_url' => route('checkout.success', ['order_id' => '1234']), // 支付成功跳转地址 'cancel_url' => route('checkout.cancel'), // 支付取消跳转地址 'payment_intent_data' => [ 'transfer_group' => 'order_1234', // 标记转账组,用于后续分账 'metadata' => [ 'order_id' => '1234', 'vendor_totals' => json_encode($vendorTotals), 'commission_percentage' => $applicationFeePercentage ] ] ]); // 返回Checkout会话URL,前端跳转至此完成支付 return response()->json([ 'success' => true, 'checkout_url' => $session->url, 'session_id' => $session->id ]); } catch (ApiErrorException $e) { return response()->json([ 'success' => false, 'error' => $e->getMessage(), ], 500); } } /** * 处理Stripe支付成功Webhook(需配置Stripe Webhook指向此方法) * 用于完成向商家的分账操作 */ public function handleWebhook(Request $request) { try { Stripe::setApiKey(config('services.stripe.secret')); $stripe = new \Stripe\StripeClient([ 'api_key' => config('services.stripe.secret'), ]); // 验证Webhook签名,防止伪造请求 $payload = $request->getContent(); $sigHeader = $request->header('Stripe-Signature'); $webhookSecret = config('services.stripe.webhook_secret'); $event = \Stripe\Webhook::constructEvent( $payload, $sigHeader, $webhookSecret ); // 监听支付成功事件,执行分账 if ($event->type === 'payment_intent.succeeded') { $paymentIntent = $event->data->object; $metadata = $paymentIntent->metadata; $vendorTotals = json_decode($metadata['vendor_totals'], true); $commissionPercentage = $metadata['commission_percentage']; // 向每个商家转账扣除佣金后的金额 foreach ($vendorTotals as $accountId => $total) { $commission = $total * ($commissionPercentage / 100); $transferAmount = $total - $commission; Transfer::create([ 'amount' => $transferAmount, 'currency' => 'usd', 'destination' => $accountId, 'transfer_group' => $paymentIntent->transfer_group, ]); } } return response()->json(['status' => 'success']); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 400); } } }
关键修改说明
- 一次性支付:用Stripe Checkout Session替代循环创建Payment Intent,用户仅需完成一次支付流程
- 商家分组统计:提前按关联账户ID统计每个商家的订单总金额,为后续分账做准备
- 异步分账:通过Webhook监听支付成功事件,向每个商家转账扣除佣金后的金额,平台自动留存佣金
- 安全保障:添加Webhook签名验证,确保分账请求来自Stripe官方
注意事项
- 需在Stripe后台配置Webhook,监听
payment_intent.succeeded事件,指向handleWebhook方法的路由 - 实际项目中,商品数据、用户信息、订单ID等应从请求动态获取,避免硬编码
- 佣金比例可存入配置文件,提升灵活性
- 确保Stripe关联账户已完成平台对接(如Express账户类型)
内容的提问来源于stack exchange,提问作者user21471995
相关产品推荐
相关产品推荐

