phplaravelstripe-payments

Pay with Stripe Connect


I am using Laravel 10 and I have implemented the following controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\MarketplaceItem;
use Stripe\Stripe;
use Stripe\Checkout\Session;
use Illuminate\Support\Facades\Log;

class PaymentController extends Controller
{
    /**
     * Create a new Stripe Checkout Session for the specified item.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $itemId  The ID of the item being purchased.
     * @return \Illuminate\Http\JsonResponse
     */
    public function createCheckoutSession(Request $request, $itemId)
    {
        try {
            // Fetch the specified item from the database.
            $item = MarketplaceItem::findOrFail($itemId);
            
            // Fetch the seller's Stripe account ID.
            $sellerStripeAccountId = $item->user->stripe_account_id;
            
            // Calculate the total amount for the item in cents.
            $amount = (int)($item->price * 100);
            
            // Calculate the application's commission (20% of the total amount).
            $applicationFee = (int)($amount * 0.20);

            // Set the secret API key for Stripe.
            Stripe::setApiKey(config('stripe.secret'));

            // Set the Stripe-Account header
            $options = ['stripe_account' => $sellerStripeAccountId];

            // Create a new Stripe Checkout Session.
            $session = Session::create([
                'payment_method_types' => ['card'],  // Allow card payments only.
                'line_items' => [[
                    'price_data' => [
                        'currency' => 'usd',  // Set the currency.
                        'product_data' => [
                            'name' => $item->prompt_name,  // Set the item name.
                        ],
                        'unit_amount' => $amount,  // Set the unit amount.
                    ],
                    'quantity' => 1,  // Set the quantity.
                ]],
                'mode' => 'payment',  // Set the payment mode.
                'success_url' => route('payment.success'),  // Set the success URL.
                'cancel_url' => route('payment.cancel'),  // Set the cancel URL.
                'payment_intent_data' => [
                    'application_fee_amount' => $applicationFee,  // Set the application fee amount.
                    'transfer_data' => [
                        'destination' => $sellerStripeAccountId,  // Set the destination Stripe account.
                    ],
                ],
            ], $options);  // Pass the options array here

            // Return the session ID as a JSON response.
            return response()->json(['session_id' => $session->id]);
        } catch (\Exception $e) {
            // Log the exception
            Log::error('PaymentController@createCheckoutSession: ' . $e->getMessage());

            // Optionally, you could return a generic error message to the frontend
            return response()->json(['error' => 'An error occurred while processing your payment.'], 500);
        }
    }


    /**
     * Display the payment success page.
     *
     * @return \Illuminate\View\View
     */
    public function success()
    {
        return view('payment.success');
    }

    /**
     * Display the payment cancellation page.
     *
     * @return \Illuminate\View\View
     */
    public function cancel()
    {
        return view('payment.cancel');
    }
}

My user has a $sellerStripeAccountId however I get the error:

"The 'payment_intent_data[transfer_data][destination]' param cannot be set to your own account."

I do not understand the error, because when a user registers I give the user a stripe_account_id like the following in my RegisterController:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use App\Models\Country;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Stripe\Stripe;
use Stripe\Account;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    public function showRegistrationForm()
    {
        $countries = Country::orderBy('name')->get();
        return view('auth.register', compact('countries'));
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
            'country_of_origin' => ['required', 'string', 'max:255'],
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\Models\User
     */
    protected function create(array $data)
    {
        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'country_of_origin' => $data['country_of_origin'],
        ]);

        $this->createStripeAccount($user);

        return $user;
    }

    /**
     * Create a new Stripe Account for the specified user.
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    protected function createStripeAccount(User $user)
    {
        // Set your secret key. Remember to switch to your live secret key in production!
        // See your keys here: https://dashboard.stripe.com/account/apikeys
        Stripe::setApiKey(config('stripe.secret'));

        $account = Account::create([
            'type'         => 'express',
            'email'        => $user->email,
            'country'      => $user->country_of_origin,
            'capabilities' => [
                'card_payments' => ['requested' => true],
                'transfers'     => ['requested' => true],
            ],
            'settings'     => [
                'payouts' => [
                    'schedule' => [
                        'interval' => 'manual',
                    ],
                ],
            ],
        ]);

        $user->stripe_account_id = $account->id;
        $user->save();
    }
}

What I am doing wrong?


Solution

  • I believe there's some confusion here. In your code, you're setting connected account ID as Stripe-Account header as well as transfer_data.destination.

    What sort of connect charge flow are you trying to integrate? Direct charges or Destination charges? You only need one or the other based on the charges. Not both.

    The way your code is right now, it sets the connected account ID as Stripe-Account header and the same connected account ID as destination which means you're making a request on that account to create a destination charge to itself which isn't possible.

    You'd likely want to refer to Stripe docs as they explain this in detail:

    Direct charges: https://stripe.com/docs/connect/direct-charges

    Destination charges: https://stripe.com/docs/connect/destination-charges