0
votes

when I try to create a new subscription I get this error (This customer has no attached payment source or default payment method. ) so I checked the PaymentController with dd($paymentMethod) which returned null

so I don't know why the variable $paymentMethod in store method is returning NULL from the $request but the request, for the price is returning the price_id. Please any help is appreciated

but when console.log() setupIntent.payment_method it returned the payment_method in the console

Here is my PaymentController

         public function index()
{
    $availablePlans = [
        'price_1HnIiLLzAo4pwMcyh2aGaznB'  => 'Monthly',
        'price_1HnJ2vLzAo4pwMcygQT66juk'  => 'Yearly',
        'price_1HnIhILzAo4pwMcy9iH3j30L'  => 'Free Membership'
    ];

    $user = auth()->user();
    $data = [
        'intent' => $user->createSetupIntent(),
        'plans' =>  $availablePlans

    ];
    
    return view('payments.checkout')->with($data);
}
     

       public function store(Request $request)
    {

  


             $user = auth()->user();

              $paymentMethod = $request->payment_method;
             //  dd($paymentMethod);
    
    
             $planId = $request->plan;

             $user->newSubscription('premium', $planId)->create($paymentMethod);

             return response(['status' => 'success']);
       }

This is the Javascript

 window.addEventListener('load', function (){

// Create a Stripe client. const stripe = Stripe('pk_test_51H2OqqLzAo4pwMcyT4h405wpFRAn3FWhvByfvmVnW6tabrIsDoU1dBXJ0UaWexUJeacCJ9uKpb5OBmmA2KaCg4sd00ZZ5tj2q8');

// Create an instance of Elements.
const elements = stripe.elements();

// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
 
 // const cardElement = elements.create('card', {style: style});

// Create an instance of the card Element.
const cardElement = elements.create('card');

// Add an instance of the card Element into the `card-element` <div>.
cardElement.mount('#card-element');

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

    const plan = document.getElementById('subscription-plan').value;

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

        if (error) {
            // Display "error.message" to the user...
        } else {
            // The card has been verified successfully...
           // console.log('handling success', setupIntent.payment_method);

            axios.post('/subscribe', {
              payment_method: setupIntent.payment_method,
              plan: plan
            })
        }
    });

 });

              

Here is the form

         <form action="{{ route('subscribe')}}" method="POST" id="">
@csrf
  <div class="form-content">

      <div class="field">
        <select class="form-control" name="plan" id="subscription-plan">
          @foreach ($plans as $key=>$plan )
            <option value="{{$key}}">{{$plan}}</option>
          @endforeach
        </select>
      </div>

    <div class="field">
      <input type="text" autocorrect="off" spellcheck="false" id="card-holder-name" maxlength="25" />
      <span class="focus-bar"></span>
      <label for="cardholder">Card holder (Name on card)</label>
    </div>
        
        <div  class="field mb-5" id="card-element">
        <!-- Stripe Elements Placeholder -->
        </div>

    <button id="card-button" data-secret="{{ $intent->client_secret }}"><span>Pay</span></button>
    
  </div>
</form>

The Route

   Route::resource('payments', 'PaymentsController', [
         'names'=> [
              'index'     => 'checkout',
               'store'     => 'subscribe',
   
          ]
      ]);
2

2 Answers

0
votes

Looks like there is something wrong with how you're using axios. Have you tried taking a look at laravel simple axios with argument

0
votes

Adding a hidden input field in the form and setting the value to setupIntent.payment_method passed the payment_method id to the controller which is used to create the subscription so the problem is solved.

A few modifications and adding a hidden input field to the JS

// Handle form submission.
            var form = document.getElementById('payment-form');
            form.addEventListener('submit', async (e) => {
              e.preventDefault();
                //cardButton.addEventListener('click', async (e) => {
                       //e.preventDefault()
                    const { setupIntent, error } = await stripe.handleCardSetup(
                        clientSecret, cardElement, {
                            payment_method_data: { 
                            billing_details: { name: cardHolderName.value }
                            }
                        }
                    );
                   
                    if (error) {
                        // Display "error.message" to the user...
                    } else {
                        // The card has been verified successfully...
                       //console.log('handling success', setupIntent.payment_method);
                         axios.post('/subscribe',{
                         plan : plan
                    })
                       var paymentMethod  = setupIntent.payment_method;

                        var form = document.getElementById('payment-form');
                        var hiddenInput = document.createElement('input');
                        hiddenInput.setAttribute('type', 'hidden');
                        hiddenInput.setAttribute('name', 'payment_method');
                        hiddenInput.setAttribute('value', paymentMethod);
                        form.appendChild(hiddenInput);

                        // Submit the form
                        form.submit();

          
                    }