You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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官方

注意事项

  1. 需在Stripe后台配置Webhook,监听payment_intent.succeeded事件,指向handleWebhook方法的路由
  2. 实际项目中,商品数据、用户信息、订单ID等应从请求动态获取,避免硬编码
  3. 佣金比例可存入配置文件,提升灵活性
  4. 确保Stripe关联账户已完成平台对接(如Express账户类型)

内容的提问来源于stack exchange,提问作者user21471995

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:35:55