Laravel Cashier Re-attempt Pending Invoice after Card Updates

864 views Asked by At

I am using Laravel 5.3 with Cashier. If a customer updates their card details, how can I check if there is a pending invoice and ask Stripe to re-attempt the charge on the new card? At the moment, I have set the settings for attempts in Stripe dashboard. But from what I understand, Stripe does not automatically attempt to charge the customer if they updated their card details and it waits for the next attempt date to try again. Thats why I want to manually attempt to charge the customer on pending invoice as soon as they update their card. I read the Cashier documentation and Github page but this case is not covered there.

$user->updateCard($token);
// Next charge customer if there is a pending invoice

Can someone help me out please.

1

There are 1 answers

1
Neel On BEST ANSWER

After testing and talking with Stripe support, I found out the problem with the current updateCard() method used in Laravel Cashier.

With the current updateCard() method, the card is added to the sources list and then sets the new card as the default_source. the result of this method has 2 outcomes:

  1. Multiple cards gets added to the list although the recent one is set as default_source

  2. When updating the card using this method, if there are any unpaid invoices (i.e. invoices in past_due state), they are not automatically charged.

In order for stripe to re-attempt charging customer on all invoices in past_due state, the source parameter needs to be passed. So I have created a new method something like this:

public function replaceCard($token)
    {
        $customer = $this->asStripeCustomer();
        $token = StripeToken::retrieve($token, ['api_key' => $this->getStripeKey()]);
        // If the given token already has the card as their default source, we can just
        // bail out of the method now. We don't need to keep adding the same card to
        // a model's account every time we go through this particular method call.
        if ($token->card->id === $customer->default_source) {
            return;
        }
        //  Just pass `source: tok_xxx` in order for the previous default source 
        // to be deleted and any unpaid invoices to be retried
        $customer->source = $token;
        $customer->save();
        // Next we will get the default source for this model so we can update the last
        // four digits and the card brand on the record in the database. This allows
        // us to display the information on the front-end when updating the cards.
        $source = $customer->default_source
                    ? $customer->sources->retrieve($customer->default_source)
                    : null;
        $this->fillCardDetails($source);
        $this->save();
    }

I have created a Pull request for this addition. Since editing the Billable file directly for any changes is not a good idea, if this doesnt get added to Cashier, then you can use the following in the Controller file to do it directly from there:

$user = Auth::User();

$customer = $user->asStripeCustomer();
$token = StripeToken::retrieve($token, ['api_key' => config('services.stripe.secret')]);

if (!($token->card->id === $customer->default_source)) {
  $customer->source = $token;
  $customer->save();
  // Next synchronise user's card details and update the database
  $user->updateCardFromStripe();
}