@henryeti What is it you’re actually trying to do? Because specifying an account ID and application fee is when you’re using Stripe Checkout and creating checkout sessions for a third party’s Stripe account and not your own.
laravel cashier subscription object
I am trying to set up subscription for my platform using laravel cashier stripe checkout.
I read the stripe checkout session create api documentation and it is possible to add add application fee and also stripe connected account id.
My question is that is it possible to add this two mentioned above in laravel cashier checkout below and if it is possible how do i do this
$request->user()
->newSubscription('default', 'price_monthly')
->checkout([
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
@martinbean remember last week we discussed two market application, usually using stripe checkout session api you do this
$response = app('stripe')->checkout->sessions->create([
'mode' => 'payment',
'line_items' => [
[
'price_data' => [
'currency' => 'usd',
'unit_amount' => $product->price->getAmount(),
'product_data' => [
'name' => $product->title,
]
],
'quantity' => 1
]
],
'payment_intent_data' => [
'application_fee_amount' => $product->applicationFeeAmount()->getAmount(),
],
'success_url' => route('subdomain.products.checkout.success', [$user->subdomain, $product->slug]),
'cancel_url' => route('subdomain.products.show', [$user->subdomain, $product->slug]),
'metadata' => [
'product_id' => $product->id,
]
], [
'stripe_account' => $product->user->stripe_account_id,
]);
return redirect($response->url);
so i was thinking of using laravel cashier to handle it for me is it possible to have payment_intent_data in the checkout method or maybe stick to using stripe checkout session instead of laravel cashier??
@henryeti Cashier is just a wrapper around parts of the Stripe SDK. Anything you pass to a checkout session, you can pass using Cashier. So if you want to create a checkout session for a merchant to bill a customer, then you’d need to pass that in the payment_intent_data when creating your session:
$items = [
[
'price_data' => [
'currency' => 'gbp',
'product_data' => [
'name' => 'Widget',
'description' => 'A widget from ACME Corp.',
],
'unit_amount' => 1250,
],
'quantity' => 1,
],
];
$user->checkout($items, [
'cancel_url' => url('/'), // FIXME: Your actual cancel URL
'payment_intent_data' => [
'application_fee_amount' => 250,
'on_behalf_of' => $merchant->stripe_account_id,
],
'success_url' => url('/'), // FIXME: Your actual success URL
]);
Note: when customers are checking out, they will see the merchant’s name and branding on the Stripe Checkout page, and not your application’s. The payment will go the merchant’s Stripe account, minus the application fee amount, which will go to your Stripe account.
Cheers @martinbean You are amazing. :)
@henryeti No problem! Worked with Stripe (and Stripe Connect) for probably near 10 years now, so happy to answer questions around it when I see them pop up 🙂
@martinbean Hi Martin sorry to mention you again here but i have another quick question actually the mode is subscription.
So there's a main stripe account and that main account have connect account in it and obviously the connect account can set up subscription for readers to pay for either monthly/yearly.
My question iis that how do platform/main account take charges from the stripe connect account if the session checkout mode is subscription and not payment.
I have this code
Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
// Create a Checkout Session for the connected account's subscription fee
$session = Session::create([
'mode' => 'subscription'
'payment_method_types' => ['card'],
'subscription_data' => [
'items' => [
[
'price' => 'price_id', // The ID of the price for the subscription
],
],
'metadata' => [
'connected_account_id' => 'connected_account_id_here', // ID of the connected account
],
],
'success_url' => 'https://yourwebsite.com/success',
'cancel_url' => 'https://yourwebsite.com/cancel',
]);
i saw this subscription_data.application_fee_percent in stripe session checkout create documentation but i am not sure if that's it.
Also i hope you don't mind me keeping this question open and mark as completed later.
Cheers pal
@henryeti It gets complicated with subscriptions, as the subscription product and price needs to exist in the connected Stripe account, and not yours. But you’re right in that, for subscriptions, you’d set the subscription_data.application_fee_percent parameter to take a cut of the subscription revenue.
There’s a Stripe docs page on creating subscriptions using Stripe Connect here: https://stripe.com/docs/connect/subscriptions#collecting-fees-on-subscriptions
@martinbean Oh yeah the subscription product do exist on the stripe connected account, i have looked at the link you provided and i have this
Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
// Create a Checkout Session for the connected account's subscription fee
$session = Session::create([
'mode' => 'subscription'
'payment_method_types' => ['card'],
'subscription_data' => [
'items' => [
[
'price' => 'price_id', // The ID of the price for the subscription
],
'application_fee_percent' => 10.0, // Application fee percentage
],
'metadata' => [
'connected_account_id' => 'connected_account_id_here', // ID of the connected account
],
],
'success_url' => 'https://yourwebsite.com/success',
'cancel_url' => 'https://yourwebsite.com/cancel',
]);
nice one
@henryeti As an aside, you should avoid using the env helper in your code, as if you cache your configuration then that will start returning null instead of your actual environment variable value.
I map the Stripe key and secret to configuration values in my config/services.php file:
return [
'stripe' => [
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],
];
And then access those using config('services.stripe.secret').
You could also create a StripeServiceProvider to automatically set the secret key for the Stripe SDK:
namespace App\Providers;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Stripe\Stripe;
use Stripe\StripeClient;
use Stripe\StripeClientInterface;
class StripeServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register(): void
{
$this->app->singleton(StripeClient::class, fn () => new StripeClient([
'api_key' => $this->app['config']['services.stripe.secret'],
]));
$this->app->alias(StripeClient::class, StripeClientInterface::class);
}
public function boot(): void
{
Stripe::setApiKey($this->app['config']['services.stripe.secret']);
}
public function provides(): array
{
return [
StripeClient::class,
StripeClientInterface::class,
];
}
}
Register that service provider in your config/app.php file and you never need to manually specify your Stripe API key again 🙂
@martinbean thanks for this, using subscription mode and direct charges in standard account is kind of complicated.
I just run a subscription payment and the payment goes to platform account and not connected account.
in the stripe connected account i see the payment but in the balance i see negative balance
All this is on test mode
@martinbean another quick question for you i was digging in laravel cashier subscription code and i saw how they hand customer.subscription.created webhook and i tried doing the same thing for my project but i noticed the price, plan and product id is different from the one the stripe connect account created. But what i did was to add a metadatato to subscription_data in stripe session checkout and pass the right product id, price id and so on.
Also to check if user has subscribed to a post i use
Blade::if('subscribed', function ($user, $post) {
return $user && ($post->author_id === $user->id || $user->subscribed('month') || $user->subscribed('yearly'));
});
Stripe session checkout
$response = app('stripe')->checkout->sessions->create([
'payment_method_types' => ['card'],
'mode' => 'subscription',
'line_items' => [
[
'price_data' => [
'currency' => 'usd',
'unit_amount' => $latestMonthlyPlan->price->getAmount(),
'product_data' => [
'name' => $productName,
],
'recurring' => ['interval' => $latestMonthlyPlan->abbreviation]
],
'quantity' => 1
]
],
'subscription_data' => [
'application_fee_percent' => 10,
'metadata' => [
'post_id' => $latestPost->id,
'user_id' => $blog->user->id,
'user_email' => $blog->user->email,
'name' => $latestMonthlyPlan->abbreviation,
'reader_id' => Auth::user()->id,
'price_id' => $latestMonthlyPlan->stripe_price_id,
'product_id' => $latestMonthlyPlan->stripe_product_id,
]
],
'client_reference_id' => Auth::user()->id,
'success_url' => route('blog.checkout.success', ['blog' => $blog->name, 'plan' => $latestMonthlyPlan->slug]),
'cancel_url' => route('blog.post.show', [$blog->name, 'post' => $latestPost->slug]),
'metadata' => [
'post_id' => $latestPost->id,
'user_id' => Auth::user()->id,
'name' => $latestMonthlyPlan->abbreviation
],
'customer_email' => Auth::user()->email,
], [
'stripe_account' => $stripeAccount,
]);
My question is that is this the right way to do this??
@martinbean don't mean to stress you but how do you handle listing user subscriptions and cancellation.
also how do you check if user is subscribed and if they are resume the subscription ??
@henryeti By using Cashier.
Things like checking if a user is subscribed and resuming subscriptions are covered in the Cashier docs.
@martinbean Oh yeah i was able to do that.. remember user can have subscription on multiple blogs, so what i did was to have blog_id as foreign key on subscription table loop through all subscriptions and check the if the authenticated user is subscribed to the blog they are viewing.
@php
$hasAccess = false;
foreach ($user->subscriptions as $subscription) {
if ($subscription->blog_id === $post->blog->id && $subscription->name === 'month' || $subscription->name === 'year') {
$hasAccess = true;
break;
}
}
@endphp
@if ($hasAccess)
{{-- Show the article --}}
@else
<div class="subscription-message">
<p>Please subscribe to access this premium content.</p>
<a href="{{ route('blogs.subscribe', $article->blog) }}">Subscribe Now</a>
</div>
Not sure if that's the right way but it works
The mistake i made while doing this was checking for subscription on a single blog using $user->subscribed('default')
But in general you have been really helpful mate :)
@henryeti But don’t you have different subscription products in Stripe for each “blog”? In which case you would check the user is subscribed to the product and price related to a given blog.
Mate @martinbean i'm a bit confused do you mind giving me an example. I have different subscription products for each blog yes
So for example blog 1 has
1st Product Detail
Product Id: Prod_kkkkk
Product name: month
2nd Product Detail
Product Id: Prod_zzz
Product name: year
When i create the product it goes inside my Plan database table and this is my migration
$table->id();
$table->string('name');
$table->string('user_id')->nullable();
$table->string('blog_id')->nullable();
$table->string('slug');
$table->string('stripe_name');
$table->string('stripe_product_id');
$table->string('stripe_price_id');
$table->bigInteger('price');
$table->string('abbreviation');
$table->timestamps();
This is my route to view post of a blog
Route::domain('{blog:name}.' . config('app.url'))->name('blog.')->group(function () {
Route::get('/{post:slug}', [PostController::class, 'show'])->name('post.show');
My Postcontrolloer show
public function show(Blog $blog, Post $post)
{
return view('blog.post.show', [
'blog' => $blog,
'post' => $post
]);
}
post.show blade
<h1>{{ $post->title }}</h1>
@if($post->isPremium())
{!! $post->body !!}
@else
{!! $post->body !!}
@endif
with what you mentioned previously how do i check if the user has subscribed in and allow them view the premium post??
@henryeti If each blog has a corresponding Stripe product and price, then check they’re subscribed to the corresponding product for that blog:
if ($user->subscribedToProduct('prod_blogone')) {
// User is subscribed to product corresponding to 'blogone'
}
You don’t need to store the blog ID next to subscriptions if a subscription belongs to a product, and a product belongs to a specific blog.
Docs: https://laravel.com/docs/10.x/billing#checking-subscription-status
@martinbean I ended up doing this since a blog has two plans (yearly and monthly) always
public function show(Blog $blog, Post $post)
{
$user = Auth::user();
$isBlogOwner = $user && $user->id === $blog->user_id;
$subscribed = $isBlogOwner || $blog->plans->contains(function ($plan) use ($user) {
return $user && $user->subscribedToProduct($plan->stripe_product_id, $plan->stripe_name);
});
return view('blog.post.show', [
'blog' => $blog,
'post' => $post,
'subscribed' => $subscribed
]);
}
Then in my blade
<h1>{{ $post->title }}</h1>
@if($post->isPremium() && $subscribed)
{!! $post->body !!} <!-- Display the premium content -->
@elseif(!$post->isPremium())
{!! $post->body !!} <!-- Display the non-premium content -->
@else
<a href="{{ route('blog.post.plans', [$blog->name, $post->slug]) }}">choose plan</a>
@endif
Please or to participate in this conversation.