@@ -78,9 +78,6 @@ class Order extends Model
7878
7979 protected $ guarded = [];
8080
81- /** @var bool */
82- protected $ paymentStatusHandled = false ;
83-
8481 /**
8582 * @return int
8683 */
@@ -448,9 +445,10 @@ public function creditApplied()
448445 */
449446 public function handlePaymentFailed (MolliePayment $ molliePayment )
450447 {
451- $ this ->paymentStatusHandled = false ;
452448 $ handled = false ;
453449 $ localPayment = DB ::transaction (function () use ($ molliePayment , &$ handled ) {
450+ // Webhook retries can arrive while callers still hold stale Order instances.
451+ // Reload the current row under lock so credit restoration and item hooks run once.
454452 /** @var static $order */
455453 $ order = static ::whereKey ($ this ->getKey ())->lockForUpdate ()->firstOrFail ();
456454
@@ -468,9 +466,7 @@ public function handlePaymentFailed(MolliePayment $molliePayment)
468466 'credit_used ' => 0 ,
469467 ]);
470468
471- // It's possible a payment from Cashier v1 is not yet tracked in the Cashier database.
472- // In that case we create a record here.
473- $ localPayment = Cashier::$ paymentModel ::findByMolliePaymentOrCreate ($ molliePayment , $ order ->owner );
469+ $ localPayment = Cashier::$ paymentModel ::findByPaymentIdOrFail ($ molliePayment ->id );
474470 $ localPayment ->update ([
475471 'mollie_payment_status ' => PaymentStatus::FAILED ,
476472 'order_id ' => $ order ->id ,
@@ -487,7 +483,6 @@ public function handlePaymentFailed(MolliePayment $molliePayment)
487483 });
488484
489485 $ this ->refresh ();
490- $ this ->paymentStatusHandled = $ handled ;
491486
492487 if ($ handled ) {
493488 Event::dispatch (new OrderPaymentFailed ($ this , $ localPayment ));
@@ -540,9 +535,10 @@ public function handlePaymentFailedDueToInvalidMandate()
540535 */
541536 public function handlePaymentPaid (MolliePayment $ molliePayment )
542537 {
543- $ this ->paymentStatusHandled = false ;
544538 $ handled = false ;
545539 $ localPayment = DB ::transaction (function () use ($ molliePayment , &$ handled ) {
540+ // Paid webhooks have the same stale-instance problem: re-check under lock so the
541+ // order transition, local payment update, and item hooks are idempotent.
546542 /** @var static $order */
547543 $ order = static ::whereKey ($ this ->getKey ())->lockForUpdate ()->firstOrFail ();
548544
@@ -552,9 +548,7 @@ public function handlePaymentPaid(MolliePayment $molliePayment)
552548
553549 $ order ->update (['mollie_payment_status ' => PaymentStatus::PAID ]);
554550
555- // It's possible a payment from Cashier v1 is not yet tracked in the Cashier database.
556- // In that case we create a record here.
557- $ localPayment = Cashier::$ paymentModel ::findByMolliePaymentOrCreate ($ molliePayment , $ order ->owner );
551+ $ localPayment = Cashier::$ paymentModel ::findByPaymentIdOrFail ($ molliePayment ->id );
558552 $ localPayment ->update ([
559553 'mollie_payment_status ' => PaymentStatus::PAID ,
560554 'order_id ' => $ order ->id ,
@@ -569,7 +563,6 @@ public function handlePaymentPaid(MolliePayment $molliePayment)
569563 });
570564
571565 $ this ->refresh ();
572- $ this ->paymentStatusHandled = $ handled ;
573566
574567 if ($ handled ) {
575568 Event::dispatch (new OrderPaymentPaid ($ this , $ localPayment ));
@@ -578,16 +571,6 @@ public function handlePaymentPaid(MolliePayment $molliePayment)
578571 return $ this ;
579572 }
580573
581- /**
582- * Determine if the last payment status handler changed this order.
583- *
584- * @return bool
585- */
586- public function wasPaymentStatusHandled ()
587- {
588- return $ this ->paymentStatusHandled ;
589- }
590-
591574 /**
592575 * @return \Money\Money
593576 */
0 commit comments