how to get the subscription status with stripe.subscriptions - stripe-payments

I am trying to get a customer's subscription status. I already have their customer id. I am trying to determine from their status if they have paid or have cancelled, unpaid, ended, etc. I am in php.
this is how i get their customer id
$customersResponse = $stripe->customers->all(['email' => $_SESSION['userid']]);
$custid1 = $customersResponse->data[0]->id;
Now I want to get their status so I use
$subscriptionsResponse = $stripe->subscriptions->all(['customer' => $custid1]
$status=$subscriptionsResponse->data[0] -> status; but status is always null
In my testing I am using a customer who has currently cancelled. I am finding this data[0] to be terribly hard to debug. How can I get the $status set correctly?

Stripe's API reference doc shows how to get subscription list and filter by status.
Stripe won't return 'canceled' subscriptions unless you specify 'status' => 'canceled' or 'status' => 'all' in the request. If status is omitted from the request, the response will only include non-canceled subscriptions.
You also need to pass a value for the customer parameter if you want to list a specific customer's subscription(s) only.
In short, you need to pass status and customer query parameters in your request to receive all subscriptions of specific customer.

Related

Stripe update payment on subscription to make it use the default on the customer

So we have some Stripe customers with subscriptions that are setup with payment methods and they work just fine.
The issue is, when the subscription were setup, They were explicitly given the payment method id, instead of letting it default to the one on the customer ( dun... dun.. the same payment method. ).
So what this means is, if a customer updates their payment method, we have to explicitly update each subscription to use it, since the subscription has its own payment method ( the old default ).
The question is, without rebuilding the subscription, how do we tell the subscription to forget about the payment method it has, and start using the default source on the Customer?
The docs on the Subscription talk here about setting the default value, but not how do you clear it?
I attempted to null it out by running this node.js snippet:
const subscription = await stripe.subscriptions.update(
subscriptionId,
{
default_payment_method: undefined
}
);
and it succeeded, but did nothing.
Thanks in advance.
You should be able to send an empty string to clear the value of default_payment_method:
const subscription = await stripe.subscriptions.update(
subscriptionId,
{
default_payment_method: "",
}
);
Then, if default_source isn't set on the subscription, the customer's invoice_settings.default_payment_method or default_source will be used.

How to prevent creating duplicate subscription in stripe when trial period is set on subscription

I am encoutering a problem with stripe. let me explain my working
scenario.my requirement is do not charge user for 14 days with card up front
1)user enter card details
2)sca popup appear
3)regardless of user complete the authentication or not a subscription is created in stripe because i set trial_end_date=>now()+14 days
4)user payment fails in some reason and attempt again, another subscription created
i am worried about the duplicate subscription as the stripe will attempt to pay after the 14 days for both of these subscription as it send a Stripe-hosted link for cardholders for both of these subscription
let me give a snapshot of what i have so far
$data['customer']='customerId';
$data['items']=[];
$data['items'][0]['plan']='stripe_plan_id'
$data['default_payment_method']='pm_xxxx'
$data['trial_end']= strtotime('+14 days');
$data['prorate']=true;
$data['tax_percent']=env('VAT_PERCENTAGE');
$data['expand']= ['latest_invoice.payment_intent', 'pending_setup_intent'];
try {
$subscription=\Stripe\Subscription::create($data);
}
catch(Exception $e) {
return response()->json(['success' => false,'message'=>$e->getMessage()]);
}
what i am missing? how to prevent the duplicate subscription scenario.please expain with the correct example which is i am missing.thanks in advance
I think the problem is in your payment flow, there is not a stripe api that explicitly detects duplicate subscription. You are after all allowed to assign more then 1 subscription per customer id. You can create idempotent keys, but that isn't for the same thing you're talking about, idempotent keys are for accidently hitting the submit button twice within the same timeframe.
The solution would be to attach a payment method to your stripe customer id before your subscription trial is over. For example if you are using stripe elements you would call
Node/JS Example below :
const result = await stripe.createPaymentMethod({
type: 'card',
card: card
})
then pass that result to your backend
const result = await stripe.paymentMethods.attach(paymentMethodId, {customerId})
You do not need to create a new subscription, as one has already been created for that user. Credit cards are assigned to customer Ids, and not to subscriptions. Stripe will do the rest.
You do also need to update the customer with the default payment method as follows :
const customer_update = await stripe.customers.update(stripeCustomerId,{invoice_settings: {default_payment_method:paymentMethodId}});
Now when you visit your dashboard you will see a default card assigned to your customer. Once the subscriptions falls out of the trail period, the default card will be charged.
So in this case there wont be a duplicate subscription created, as you are not calling stripe.subscriptions.create again.

Stripe Session Getting Customer Name

I would also like to retrieve the name of the person who paid. How can I get the name value from the stripe session? I have tried this:
const sessions = await stripe.checkout.sessions.list({
limit: 1,
});
console.log(sessions.data[0].customer)
const customer = await stripe.customers.retrieve(sessions.data[0].customer);
console.log(customer)
which gets me the email, but the name is null.
UPDATE: Customers created through Checkout have a null name because it is not collected/set. The "name on card" field on the Checkout page is for the cardholder name for the payment method, not the name of the customer (see https://stripe.com/docs/api/payment_methods/object#payment_method_object-billing_details-name). If you want a Customer created through Checkout to not have a null name, you'll have to update the Customer after the session is completed.
Stripe has a dashboard setting (https://dashboard.stripe.com/settings/emails) you can enable that automatically sends receipts to customers for successful payments completed in live mode. Payments completed in test mode will not automatically send an email, but you can trigger one manually through the dashboard. You can read more about sending receipts here: https://stripe.com/docs/receipts#automatically-send-receipts-when-payments-are-successful.
If you want to grab the customer’s email and send a receipt yourself it’ll be a bit more complicated.
Wait for the Checkout Session to finish by listening for checkout_session.completed through a webhook endpoint (https://stripe.com/docs/payments/checkout/fulfill-orders#handle-the-checkoutsessioncompleted-event).
Grab the customer ID from the Checkout Session object that comes in with the checkout_session.completed event.
Using the customer ID from the previous step, retrieve the Customer object (https://stripe.com/docs/api/customers/retrieve) and get the customer’s email.

What to do when Strong Customer Authentication fails?

I am trying to implement a Subscription signup and payment flow with Stripe.js V3 and Strong Customer Authentication (SCA) like this:
var stripe = Stripe('pk_test_9hA8gecxBFTY3O6kUm7hl16j');
var paymentIntentSecret = 'pi_91_secret_W9';
stripe.handleCardPayment(
paymentIntentSecret
).then(function (result) {
if (result.error) {
// Display error.message in your UI.
} else {
// The payment has succeeded. Display a success message.
}
})
Everything works great when the payment succeeds.
But what am I supposed to do when it fails?
Should I redirect the user to the initial payment screen, so s/he can start over?
When I do that I get this error:
You cannot confirm this PaymentIntent because it has a status of canceled. Only a PaymentIntent with one of the following statuses may be confirmed: requires_confirmation, requires_action.
Or should I delete everything, including the previously created stripe_customer and stripe_subscription, and then start over?
Thanks for any help.
If the payment fails entirely(maybe it was declined, or 3D Secure was attempted but not completed successfully) , the PaymentIntent from the first invoice should be in the requires_payment_method state and the subscription is incomplete.
You can choose to attempt to collect new payment information from the user and use that to complete the invoice payment and activate the subscription. You can re-use the same PaymentIntent throughout this and try as many times as you wish. For example, if you had a payment form with a Card Element for collecting details, you can have the user enter a new card and call this again :
stripe.handleCardPayment(cardElement,
paymentIntentSecret
).then(function(res){...})
Alternatively you can choose to cancel the subscription entirely if you wish. Otherwise if you do nothing, or the customer isn't able to provide a payment method that works, after 24 hours, Stripe effectively cancels the subscription for you.
Your error message seems to indicate the PaymentIntent was cancelled, which might mean you cancelled the subscription, or you're trying this more than 24 hours after the initial payment, I'm not sure.
This link goes into more detail:
https://stripe.com/docs/billing/lifecycle#incomplete

Detect if subscription is cancelled automatically

I have setup my Stripe subscriptions to be automatically cancelled after 3 failed payment attempts and I have customer.subscription.deleted webhook to record the cancelled subscription.
Is there a way to detect in customer.subscription.deleted webhook if subscription is cancelled by stripe because of failed payment attempts OR manually cancelled through Stripe Dashboard OR cancelled because of an API request made from our application?
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.
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.
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.

Resources