Laravel Cashier - Cancel a subscription using the stripe id - laravel

I'm using Laravel Cashier with stripe payment. One user can have multiple subscriptions. User should able to cancel particular subscription. Is there anyway to cancel subscription by stripe id or plan id?

You can use the PHP Stripe library to do it.
To cancel immediately
$sub = Stripe\Subscription::retrieve($subscription_id);
$sub->cancel();
To cancel after current period end
$sub = Stripe\Subscription::update($subscription_id, [
'cancel_at_period_end' => true
]);

You can use it with Laravel Cashier using subscription id .
In Laravel
You can find the subscription id in the subscriptions table in your database.(column name is stripe_id)
$subscription = \Stripe\Subscription::retrieve($subscription_id);
If you pass the correct subscription id , you will get the subscription details
To cancel the subscription
$sub->cancel();
Update your subscriptions table for the particular subscription id (again column name is stripe_id). In my
\Stripe\Subscription::where('stripe_id', $sub->id)
->update([
'trial_ends_at' => Carbon::now()->toDateTimeString(),
]);

I'm sure this is documented somewhere, but for the life of me I can't find it, but, if you pass the stripe subscription id in as the 2nd parameter you can cancel any subscription the Laravel way...
$user->subscription('default', 'price_abcdefghi12345')->cancel();

You can just call the cancel() method directly on your subscription:
$subscription->cancel();
As you can see here (Github Reference), the method $this->subscription called from your User model just gets a Subscription instance filtering by name:
/**
* Get a subscription instance by name.
*
* #param string $name
* #return \Laravel\Cashier\Subscription|null
*/
public function subscription($name = 'default')
{
return $this->subscriptions->where('name', $name)->first();
}
And, as you can see (Github Reference), the Subscription model has its own cancel method:
/**
* Cancel the subscription at the end of the billing period.
*
* #return $this
*/
public function cancel()
{
// ...
}
So, you can always call it directly on the Subscription instance.

You don't need to have the id to cancel.
You can just call
$subscription->cancel();
and it will work. If you have multiple subscriptions which you would like to cancel at the same time. You can do this. Let's find all the active subscriptions. You can find all the scopes here
$subscriptions = $user->subscriptions()->active()->get(); // getting all the active subscriptions
$subscriptions->map(function($subscription) {
$subscription->cancel(); // cancelling each of the active subscription
});

You can do this by using the cashier Subscription model as like Laravel model
Cancel Subscription
$subscription = Subscription::where('name', 'default')->where('user_id', auth()->id())->first();
$subscription->cancel();
Resume Subscription
$subscription = Subscription::where('name', 'default')->where('user_id', auth()->id())->first();
$subscription->resume();

Related

Website doesn't reflect Stripe customer/subscription changes

I have a website built with the Laravel framework that uses Stripe to process subscription payments. When a user subscribes to a plan through my website, this row appears in the "subscriptions" database table:
When a subscription is canceled through my website, the ends_at column is populated with a date the current subscription period ends and will not be renewed:
This is the desired behavior, however, when a payment method is invalid, Stripe automatically cancels the subscription outside of the website. Since the subscription was canceled automatically on Stripe's end, my website doesn't reflect the changes and the ends_at database column will indefinitely have a value of NULL, allowing the user to continue using premium features of the website for free.
This is how I currently check if a user has an active subscription plan on my website:
public function index()
{
$user = auth()->user();
if (!$user->subscribed('plan') && $user->posts->count() > 0) {
foreach ($user->posts as $post) {
$post->update(['status' => 'unpublished']);
}
}
return view('dashboard');
}
This is how a user cancels their subscription on my website:
public function cancel()
{
$user = auth()->user();
$user->subscription('plan')->cancel();
return redirect()->back()->with("success", "Subscription canceled.");
}
How do I fix this issue so my website will automatically check Stripe for a customer's subscription status?

way to insert a new record, with cashier subscription?

I am searching the web today to see if I can somehow be able to add a new record to some tables whenever the user memberships renew.
let's say we have an app have (users, notifications, subscriptions ) tabes.
when he subscribe the first time, I can add a new record in notification table within the same controller, but what if I want every time he renews his subscription a new instance can be added in the notification table?
Eloquent Model Event
is the way to go, I believe.
Or if the logic is really simple, you can define it in your model class.
/**
* The "booting" method of the model.
*
* #param Builder
*/
protected static function boot()
{
parent::boot();
static::updated(function (Subscription $subscription) {
// your subscription logic here...
});
}

How to Cancel the Braintree Subscription by Braintree_id or subscription_id using Laravel Cashier

We are using Laravel Cashier (Braintree) with Laravel version 5.8. We have a case where a user is subscribed to same plan with same name multiple times for different orders.
We want to give the ability to user to cancel their subscription.
we tried below statement to cancel the subscription with subscription name as suggested by manual here https://laravel.com/docs/5.8/braintree#cancelling-subscriptions.
$user->subscription('main')->cancel();
$user->subscription('main')->cancelNow();
We are passing the subscription name. It works fine as expected and also updating the date in "ends_at" column of subscription table.
The problem here is that as we have same name for the subscriptions where user is subscribed to. So in our case it returns the last subscribed order here and cancel that. It's fine as what it is suppose to do.
But we want to cancel the subscription based on braintree_id stored in subscriptions table. Can we do that ?
As of now we tried it like below:-
use Braintree\Subscription;
$subcriptionObj = Subscription::find($subscription); //where $subscription is braintree_id from subscriptions table.
if ($subcriptionObj->status == 'Canceled')
abort(400, 'Subscription Not Active');
Subscription::cancel($subscription);
This however cancel the subscription at Braintree but not updating the column "ends_at" in subscriptions table.
Can anyone suggests a workaround for this ? Any help would be appreciated.
Since the Laravel Braintree Cashier module is internally using Braintree Subscription library. So I thought to use the same directly into my controller.
I used namespace into my controller for subscription to call the Braintree subscription class directly. the below is the code to cancel the subscription by subscription ID.
use Braintree\Subscription;
public function cancelsubscription(User $user, $subscriptionId)
{
$subcriptionObj = Subscription::find($subscriptionId);
if(is_null($subcriptionObj)){
abort(400, 'Subscription is not found.');
}
if ($subcriptionObj->status == 'Canceled')
abort(400, 'Subscription is not Active.');
// In below line we are finding the Subscription DB Obj using cashier module here to update the ends_at date column
$subsDbObj = $user->subscriptions->filter(function($sub) use ($user,$subscriptionId){
return $sub->braintree_id == $subscriptionId && $sub->user_id == $user->id;})->values();
Subscription::cancel($subscriptionId);
if(! is_null($subsDbObj[0])){
//Internally cashier module doing the same to update the subscription table
$subsDbObj[0]->ends_at = Carbon::now();
$subsDbObj[0]->save();
}
return 'Cancelled';
}

Get next billing date from Laravel Cashier

I have subscribed a user to a subscription plan through Laravel's Cashier package. I now want to display the date the user will next be billed, however this doesn't appear to be an available through the Billable trait.
How do I get the next billing date?
Thanks!
The solution is to use the asStripeCustomer method:
// Retrieve the timestamp from Stripe
$timestamp = $user->asStripeCustomer()["subscriptions"]->data[0]["current_period_end"];
// Cast to Carbon instance and return
return \Carbon\Carbon::createFromTimeStamp($timestamp)->toFormattedDateString();
Note that I've only tested this with a user who has a single subscription - data[0].
You may need to alter this code for multiple subscriptions or if the user has cancelled and started another subscription.
Building on previous answers, here's what's working for me:
private function getSubscriptionRenewDate($plan)
{
$sub = Auth::user()->subscription($plan)->asStripeSubscription();
return Carbon::createFromTimeStamp($sub->current_period_end)->format('F jS, Y');
}
Subscriptions also have a ->asStripeSubscription() method that gives you access to the values just for that subscription. So you could do:
// Retrieve the timestamp from Stripe
$timestamp = $subscription->current_period_end;
// Cast to Carbon instance and return
return \Carbon\Carbon::createFromTimeStamp($timestamp)->toFormattedDateString();

Laravel cashier 6 renew subscription after cancellation

I've just updated Laravel cashier package from 5 version to the latest 6 version. It supports multiple subscriptions and it's really cool. But I've got one problem with renewing subscription after subscription cancellation.
I'm removing subscription manually from stripe dashboard and customer.subscription.deleted event is firing.
Cashier method is catching this event:
\Laravel\Cashier\Http\Controllers\WebhookController#handleWebhook
And $subscription->markAsCancelled(); is firing.
From that moment subscription cannot be renewed. I've tried to use resume() function, but subscription can be resumed only(!) on grace period.
In previous version of cashier I was using swap() method to resume subscription. Now it returns:
Stripe\Error\InvalidRequest: Customer cus_*** does not have a subscription with ID sub_***** in /**/vendor/stripe/stripe-php/lib/ApiRequestor.php:103 from API request 'req_****'
Creating new customer and subscription is not very efficient way. What your thoughts about this issue?
My solution at this moment:
public function resume()
{
$user = Auth::user();
$subscription = $user->subscription(ServicePackageRepository::SUBSCRIPTION_NAME);
if ($subscription->cancelled() && $subscription->onGracePeriod()) {
//if it was cancelled by user in grace period
$subscription->resume();
return $this->respondWithSaved([]);
} else { //if cancelled by payment failure or smth else...
if($user->subscription(ServicePackageRepository::SUBSCRIPTION_NAME)) {
$user->newSubscription(ServicePackageRepository::SUBSCRIPTION_NAME,
$user->subscription(ServicePackageRepository::SUBSCRIPTION_NAME)->stripe_plan)
->create();
return $this->respondWithSaved([]);
} else {
return $this->respondWithError([]);
}
}
}

Resources