Skip to content

Conversation

@alecritson
Copy link
Contributor

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.

Copy link

@virtruvio virtruvio Nov 8, 2025

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()

@alecritson alecritson marked this pull request as ready for review November 10, 2025 08:10
Copy link

Copilot AI left a 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.

Comment on lines +33 to +35
$this->manuallyProcessOrder();
}

Copy link

Copilot AI Nov 10, 2025

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.

Suggested change
$this->manuallyProcessOrder();
}
// Double-check to avoid duplicate processing
if (! $this->cart?->completedOrder()->exists()) {
$this->manuallyProcessOrder();
}
}
// Check again after manual processing

Copilot uses AI. Check for mistakes.
}

if ($this->cart?->completedOrder()->exists()) {
to_route('checkout-success.view', [
Copy link

Copilot AI Nov 10, 2025

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.

Suggested change
to_route('checkout-success.view', [
return to_route('checkout-success.view', [

Copilot uses AI. Check for mistakes.
public function mount(Request $request): void
{
$this->cart = CartSession::current();
$this->cart = Cart::find($request->get('cartId'));
Copy link

Copilot AI Nov 10, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +52
$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();
Copy link

Copilot AI Nov 10, 2025

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.

Suggested change
$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());
}

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +41
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,
]);
}
}
Copy link

Copilot AI Nov 10, 2025

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants