-
-
Notifications
You must be signed in to change notification settings - Fork 63
Stripe processing squeeze page #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Support of new Lunar install - Update config livewire
- Now the entrypoint script detect if /config/lunar directory exist - Cleaning - Install Redis & Meilisearch
- Partial fix db:seed
…feat/stripe-webhook-squeeze
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alex --- this worked for me, too. Thanks for taking the time to show me exactly what you were thinking.
Did make one edit here due to an error thrown due to a call to a method on null at line 28:
if ($this->cart === null || !$this->cart->completedOrder()->exists()) {
Same issue on line 36 where we need if ($this->cart !== null && $this->cart->completedOrder()->exists()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a processing page between checkout and success to prevent race conditions from creating duplicate orders when both Stripe webhooks and manual processing run simultaneously. The processing page polls the order status and triggers manual processing if the webhook hasn't completed within 5 attempts.
Key changes:
- Adds a new processing page that polls order status before redirecting to success
- Updates checkout flow to route through the processing page for Stripe payments
- Modifies success page to accept cart ID via query parameter instead of relying on session
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| routes/web.php | Adds route for the new checkout processing page |
| resources/views/partials/checkout/payment.blade.php | Updates Stripe payment return URL to processing page |
| resources/views/livewire/checkout-processing.blade.php | Creates UI for processing page with polling |
| config/services.php | Adds Stripe webhook signing secret configuration |
| app/Livewire/CheckoutSuccessPage.php | Changes to use cart ID from request instead of session |
| app/Livewire/CheckoutProcessing.php | Implements processing logic with status polling and manual fallback |
| app/Livewire/CheckoutPage.php | Removes duplicate payment processing logic and adds cart ID to success redirects |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $this->manuallyProcessOrder(); | ||
| } | ||
|
|
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Race condition: Multiple simultaneous requests could increment $this->tries and call manuallyProcessOrder() multiple times before the order completes. Add a check after manuallyProcessOrder() to verify the order was created, or add locking to prevent duplicate manual processing.
| $this->manuallyProcessOrder(); | |
| } | |
| // Double-check to avoid duplicate processing | |
| if (! $this->cart?->completedOrder()->exists()) { | |
| $this->manuallyProcessOrder(); | |
| } | |
| } | |
| // Check again after manual processing |
| } | ||
|
|
||
| if ($this->cart?->completedOrder()->exists()) { | ||
| to_route('checkout-success.view', [ |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing return statement: The to_route() helper should be used with return to actually perform the redirect. Without it, execution continues and the component re-renders.
| to_route('checkout-success.view', [ | |
| return to_route('checkout-success.view', [ |
| public function mount(Request $request): void | ||
| { | ||
| $this->cart = CartSession::current(); | ||
| $this->cart = Cart::find($request->get('cartId')); |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing import: The Cart model is used but not imported. Add use Lunar\Models\Cart; to the imports.
| $this->paymentIntent->update([ | ||
| 'processing_at' => now(), | ||
| ]); | ||
|
|
||
| Payments::driver('stripe')->cart($this->cart)->withData([ | ||
| 'payment_intent_client_secret' => $this->paymentIntentClientSecret, | ||
| 'payment_intent' => $this->paymentIntent->intent_id, | ||
| ])->authorize(); |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The processing_at timestamp is set before authorize() completes. If authorization fails or throws an exception, the timestamp remains set, potentially blocking retries. Consider setting this timestamp after successful authorization or wrapping in a try-catch block.
| $this->paymentIntent->update([ | |
| 'processing_at' => now(), | |
| ]); | |
| Payments::driver('stripe')->cart($this->cart)->withData([ | |
| 'payment_intent_client_secret' => $this->paymentIntentClientSecret, | |
| 'payment_intent' => $this->paymentIntent->intent_id, | |
| ])->authorize(); | |
| try { | |
| Payments::driver('stripe')->cart($this->cart)->withData([ | |
| 'payment_intent_client_secret' => $this->paymentIntentClientSecret, | |
| 'payment_intent' => $this->paymentIntent->intent_id, | |
| ])->authorize(); | |
| $this->paymentIntent->update([ | |
| 'processing_at' => now(), | |
| ]); | |
| } catch (\Exception $e) { | |
| // Optionally log the error or handle it as needed | |
| // logger()->error('Payment authorization failed: ' . $e->getMessage()); | |
| } |
| public function checkStatus(): void | ||
| { | ||
| if (! $this->cart?->completedOrder()->exists()) { | ||
| $this->tries++; | ||
| } | ||
|
|
||
| if ($this->tries >= 5) { | ||
| $this->manuallyProcessOrder(); | ||
| } | ||
|
|
||
| if ($this->cart?->completedOrder()->exists()) { | ||
| to_route('checkout-success.view', [ | ||
| 'cartId' => $this->cart->id, | ||
| ]); | ||
| } | ||
| } |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The $this->cart?->completedOrder()->exists() check is duplicated. Consider storing the result in a variable to avoid redundant database queries and improve readability.
This PR adds a squeeze page to the checkout with the aim of checking the order status due to the webhook and manual processing potentially having a race condition, resulting in 2 orders being created.
A processing page also allows devs to perform more robust checks and call other parts of their app before going to the success page.
This PR is not meant to provide logic for every scenario, just to show what can be done.