Dan E
Dan E

Reputation: 177

Laravel Cashier new subscription with Stripe throws "Could not determine which URL to request:" error

I'm using Laravel 6 and Cashier 10.7 to handle subscriptions on my platform. One off payments to Stripe are working fine and so is switching from one subscription to another, but when I try to initiate a new subscription for a customer with no stored card I get an error

Could not determine which URL to request: Stripe\PaymentMethod instance has invalid ID:

My payment form looks like this;

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-12 col-md-6 mt-5 mb-2">

            @if (session('status'))
                <div class="alert alert-success" role="alert">
                    {{ session('status') }}
                </div>
            @endif

            @if ($errors->any())
                <div class="alert alert-danger">
                    <strong>Whoops!</strong> There was a problem.
                    <ul>
                        @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            @endif

            <div class="h2 text-center">
                {{ __('Please enter your card details') }}
            </div>

            <div class="card">
                <div class="card-body">
                    <form method="post" action="{{ route('startSubscription') }}" id="payment-form">
                        @csrf

                        <div class="group">
                            <label class="stripe-label">
                                <span class="mr-1 p-2">{{ __('Cardholder Name') }}</span>
                                <input id="card-holder-name" class="field" placeholder="Jordan Jones" />
                            </label>
                        </div>

                        <!-- Stripe Elements Placeholder -->
                        <div class="group">
                          <label class="stripe-label">
                            <span class="mr-1 p-2">{{ __('Card') }}</span>
                            <div id="card-element" class="field p-3"></div>
                          </label>
                        </div>

                        <!-- Used to display form errors. -->
                        <div id="card-errors" role="alert"></div>
                        <button id="card-button" class="btn btn-lg btn-block btn-success" data-secret="{{ $intent->client_secret }}">
                            <i class="fas fa-credit-card mr-1"></i>{{ __('Add Payment Method') }}
                        </button>
                    </form>
                </div>
            </div>

        </div>
    </div>
</div>
@endsection

@section('javascript')

<script>
    const stripe = Stripe('{{ env("STRIPE_KEY") }}');
    const elements = stripe.elements();
    const cardElement = elements.create('card', {hidePostalCode: true});

    cardElement.mount('#card-element');

    const cardHolderName = document.getElementById('card-holder-name');
    const cardButton = document.getElementById('card-button');
    const clientSecret = cardButton.dataset.secret;

    cardButton.addEventListener('click', async (e) => {
        const { setupIntent, error } = await stripe.confirmCardSetup(
            clientSecret, {
                payment_method: {
                    card: cardElement,
                    billing_details: { name: cardHolderName.value }
                }
            }
        );

        if (error) {
            var errorElement = document.getElementById('card-errors');
            errorElement.textContent = error.message;
        } else {
          // Submit the form with the token ID.
           function stripePaymentHandler(setupIntent) {
           // Insert the token ID into the form so it gets submitted to the server
           var form = document.getElementById('payment-form');
           var hiddenInput = document.createElement('input');
           hiddenInput.setAttribute('type', 'hidden');
           hiddenInput.setAttribute('name', 'payment_method');
           hiddenInput.setAttribute('value', setupIntent.payment_method);
           form.appendChild(hiddenInput);

           // Submit the form
           form.submit();
          }
        }
        });

</script>
@endsection

My controller like this;

public function subscriptionPayment(Request $request) {
  $user = Auth::user();
  $paymentMethod = $request->payment_method;

  try {
    if ($user->hasPaymentMethod()) {
      $user->updateDefaultPaymentMethod($paymentMethod);
    } else {
      $user->addPaymentMethod($paymentMethod);
    }

$newSubscription = $user->newSubscription('User', 'plan_HGvJkewD0eaY6G')->trialDays(60)->create($paymentMethod, ['email' => $user->email]);

  } catch ( IncompletePayment $exception ){
    return redirect()->route('payment',
                [$exception->payment->id, 'redirect' => route('home')]
    );
  }

  return redirect()->route('home')
    ->with('status', 'Your payment has been processed and your subscription is now active.');
  }

and the Stripe event log shows this;

{ "object": { "id": "seti_*******", "object": "setup_intent", "application": null,

"cancellation_reason": null, "client_secret": "seti_*********", "created": 1591367952, "customer": null, "description": null, "last_setup_error": null, "livemode": false, "mandate": null, "metadata": { }, "next_action": null, "on_behalf_of": null, "payment_method": null, "payment_method_options": { "card": { "request_three_d_secure": "automatic" } }, "payment_method_types": [ "card" ], "single_use_mandate": null, "status": "requires_payment_method", "usage": "off_session" } }

Can Anyone help with what I'm doing wrong, i just can't work it out.

Upvotes: 0

Views: 1343

Answers (1)

Dan E
Dan E

Reputation: 177

I tracked this down to 'defer' being present in the js script tag in the headers, which meant the event listeners weren't working and the form was submitting without waiting for the call and response from Stripe. Fixed with a simple

<script src="{{ asset('js/app.js') }}"></script>

after 2 days 🤷‍♂️

Upvotes: 1

Related Questions