Reputation: 905
I have setup my Stripe subscriptions to be automatically cancelled after 3 failed payment attempts and I have a customer.subscription.deleted
webhook to record the cancelled subscription.
Is there a way to detect in the customer.subscription.deleted
webhook if the subscription is cancelled by Stripe because of failed payment attempts OR manually cancelled through the Stripe dashboard OR cancelled because of an API request made from our application?
Upvotes: 17
Views: 9087
Reputation: 441
You can now differentiate the two cases (manually cancelled vs payment failed) using the cancellation_details.reason
field on the subscription object sent by the customer.subscription.deleted
webhook.
As of 9/13/2023 there are 3 possible reasons: cancellation_requested
, payment_disputed
, and payment_failed
.
Stripe documentation - https://stripe.com/docs/api/subscriptions/object#subscription_object-cancellation_details-reason
Upvotes: 3
Reputation: 872
I came here with the same questions, Based on the answers found here I was able to create this snippet of code, it covers all the cases on the original question:
try {
\Stripe::setApiKey(config('services.stripe.secret'));
\Stripe::setApiVersion(config('services.stripe.version'));
$endpoint_secret = config('services.stripe.invoice_webhook_secret');
$request = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? null;
$event = \Stripe\Webhook::constructEvent(
$request,
$signature,
$endpoint_secret
);
//$event->type is: "customer.subscription.deleted"
//Keep in mind that you can change the settings in stripe
//so failed payments cause subscriptions to be left as unpaid instead
//of cancelled, if those are your settings this event will not trigger
$subscription = $event->data->object;
if ( !empty($event->request->id)) {
//the request was made by a human
}elseif ( !empty($subscription->cancel_at_period_end)) {
//the request was empty but the subscription set to cancel
//at period end, which means it was set to cancel by a human
}else{
//the request was empty and
//NOT set to cancel at period end
//which means it was cancelled by stripe by lack of payment
}
} catch ( \Exception $e ) {
exit($e->getMessage());
}
Hope this helps someone else looking for this since this is the first result that comes up in google.
Upvotes: 0
Reputation: 17503
You can't differentiate between the last two cases, as the dashboard itself uses the API.
However, you can differentiate between automatic and manual cancelations. Simply look at the request
attribute in the customer.subscription.deleted
event's body.
If the subscription was canceled automatically after too many failed payments, then request
will have a null value.
Otherwise, if the subscription was canceled through the API or the dashboard, request
will have a non-null value: the request ID ("req_..."
) of the subscription cancelation request.
EDIT: as Yoni Rabinovitch pointed out, the above is true if the subscription was canceled with at_period_end=false
(or no at_period_end
parameter, as false
is the default value).
If the subscription was canceled with at_period_end=true
, then a customer.subscription.updated
event would be fired immediately (to reflect the fact that the subscription's cancel_at_period_end
attribute is now true), and that event's request
would have the request ID of the subscription cancelation request.
However, the customer.subscription.deleted
event that would be sent when the subscription is actually canceled at the end of the billing period would have request=null
, just like an automatic cancelation after too many failed payements.
Upvotes: 18
Reputation: 126
If you instead cancel a subscription at the end of the billing period, a customer.subscription.updated event is immediately triggered. That event reflects the change in the subscription’s cancel_at_period_end value. When the subscription is actually canceled at the end of the period, a customer.subscription.deleted event then occurs.
Upvotes: 5