Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

lat4732's avatar
Level 12

Problem with integrating stripe subscription

Hello everyone! I have the following form:

<form action="{{ route('stripe.pay.create.session') }}" method="POST" data-secret="{{ $intent->client_secret }}">
          @csrf
          <input type="hidden" name="priceId" value="MY PRICE ID" />
         <div id="card-element" class="p-3 mb-3"></div>
         <button type="submit" id="card-button">Checkout</button>
</form>

where the value of the hidden input is my product_id from stripe dashboard and the route "stripe.pay.create.session" have the following code:

        \Stripe\Stripe::setApiKey(env("STRIPE_SECRET"));
        
        $priceId = $request->priceId;
        
        $session = \Stripe\Checkout\Session::create([
            'success_url' => 'https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}',
            'cancel_url' => 'https://example.com/canceled.html',
            'mode' => 'subscription',
            'line_items' => [[
                'price' => $priceId,
                'quantity' => 1
            ]],
            'client_reference_id' => Auth::user()->id,
        ]);
        
        return Redirect($session->url);

the next thing I need to create is the webhook route:

Route::post('/pay/stripe/listener', [StripePayment::class, 'stripePayListener'])->name('stripe.pay.listener');

with the following code:

        // Set your secret key. Remember to switch to your live secret key in production.
        // See your keys here: https://dashboard.stripe.com/apikeys
        \Stripe\Stripe::setApiKey(env("STRIPE_SECRET"));
          

              $logger = $this->get('logger');
              $event = $request->getParsedBody();
              // Parse the message body and check the signature
              $webhookSecret = 'VALUE';
              if ($webhookSecret) {
                try {
                  $event = \Stripe\Webhook::constructEvent(
                    $request->getBody(),
                    $request->getHeaderLine('stripe-signature'),
                    $webhookSecret
                  );
                } catch (\Exception $e) {
                  return response(['error' => $e->getMessage()], 403);
                }
              } else {
                $event = $request->getParsedBody();
              }
              $type = $event['type'];
              $object = $event['data']['object'];
          
              switch ($type) {
                case 'checkout.session.completed':
                  // Payment is successful and the subscription is created.
                  // You should provision the subscription and save the customer ID to your database.
                  break;
                case 'invoice.paid':
                  // Continue to provision the subscription as payments continue to be made.
                  // Store the status in your database and check when a user accesses your service.
                  // This approach helps you avoid hitting rate limits.
                  break;
                case 'invoice.payment_failed':
                  // The payment failed or the customer does not have a valid payment method.
                  // The subscription becomes past_due. Notify your customer and send them to the
                  // customer portal to update their payment information.
                  break;
                // ... handle other event types
                default:
                  // Unhandled event type
              }

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

where the $webhookSecret is the webook secret string from the stripe's dashboard which starts with we_....... And now the problem is when I open a new NGROK tunnel for port 8000 after i did php artisan serve in my project directory and send a test webook I'm getting the following error:

Response
Test webhook error: 419

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Page Expired</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">

        <style>
            /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}code{font-family:monospace,monospace;font-size:1em}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}code{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-gray-400{--border-opacity:1;border-color:#cbd5e0;border-color:rgba(203,213,224,var(--border-opacity))}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-xl{max-width:36rem}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.uppercase{text-transform:uppercase}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tracking-wider{letter-spacing:.05em}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@-webkit-keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes  spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@-webkit-keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@keyframes  ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes  pulse{0%,to{opacity:1}50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes  bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}}
        </style>

        <style>
            body {
                font-family: 'Nunito', sans-serif;
            }
        </style>
    </head>
    <body class="antialiased">
        <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
            <div class="max-w-xl mx-auto sm:px-6 lg:px-8">
                <div class="flex items-center pt-8 sm:justify-start sm:pt-0">
                    <div class="px-4 text-lg text-gray-500 border-r border-gray-400 tracking-wider">
                        419                    </div>

                    <div class="ml-4 text-lg text-gray-500 uppercase tracking-wider">
                        Page Expired                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

Any idea why?

0 likes
3 replies
lat4732's avatar
Level 12

Okay, when I moved my code from web.php to api.php I actually started getting errors that have something understandable inside them. My code now:

Route::post('/pay/stripe/listener', function (Request $request) {

        // Set your secret key. Remember to switch to your live secret key in production.
        // See your keys here: https://dashboard.stripe.com/apikeys
        \Stripe\Stripe::setApiKey(env("STRIPE_SECRET"));
    

        // $logger = $this->get('logger');
        $event = $request->getParsedBody();
        // Parse the message body and check the signature
        $webhookSecret = 'WEBHOOK_SECRET';
        if ($webhookSecret) {
          try {
            $event = \Stripe\Webhook::constructEvent(
              $request->getBody(),
              $request->getHeaderLine('stripe-signature'),
              $webhookSecret
            );
          } catch (\Exception $e) {
            return response(['error' => $e->getMessage()], 403);
          }
        } else {
          $event = $request->getParsedBody();
        }
        $type = $event['type'];
        $object = $event['data']['object'];
    
        switch ($type) {
          case 'checkout.session.completed':
            MY CODE
            break;
          case 'invoice.paid':
              // Continue to provision the subscription as payments continue to be made.
              // Store the status in your database and check when a user accesses your service.
              // This approach helps you avoid hitting rate limits.
            MY CODE
            break;
          case 'invoice.payment_failed':
            // The payment failed or the customer does not have a valid payment method.
            // The subscription becomes past_due. Notify your customer and send them to the
            // customer portal to update their payment information.
            
            MY CODE

            break;
          // ... handle other event types
          default:
            // Unhandled event type
        }

        return response(['status' => 'success'], 200);
})->name('stripe.pay.listener');

The error im getting right now:

BadMethodCallException: Method Illuminate\Http\Request::getParsedBody does not exist.

What should actually mean $event = $request->getParsedBody()? How should I implement this in my code? This code is given by stripe documentation and i no idea what actually should getParsedBody() do.

Tray2's avatar

@crypt.001111101 Don't bump your threads. If you don't get any answers create a new thread and try to explain your problem and question better.

Please or to participate in this conversation.