Feature: Payment resolver, Payment Provider, Completing Stripe Webhooks, Wiring Payments to checkout service

This commit is contained in:
2026-09-03 17:27:34 +03:00
parent 35c3334690
commit 456943dc74
14 changed files with 266 additions and 126 deletions
@@ -2,25 +2,62 @@
namespace Modules\Core\Order\Listeners;
use Illuminate\Support\Facades\Event;
use Lunar\Models\Order;
use Modules\Core\Payment\Events\OrderPaymentStatusResolved;
use Modules\Core\Checkout\Events\OrderPlaced;
use Modules\Core\Payment\Events\PaymentAuthorized;
use Modules\Core\Payment\Events\PaymentCaptured;
/**
* The only place an Order's status column is written in reaction to a
* payment outcome — Payment dispatches OrderPaymentStatusResolved with
* what the status should become, never touching the Order model itself;
* this listener, living in Order's own module, is what applies it.
* payment outcome. Registered against BOTH PaymentCaptured and
* PaymentAuthorized (see OrderServiceProvider) — same handler either way,
* since both carry the same {type, result, context} shape and only differ
* in which config key decides the resulting status.
*
* Reads $event->context['order_id'] to find which Order this outcome
* belongs to — Payment has no concept of an Order, so this is the one
* place that context key gets consumed on the Order side (Payment's own
* StripePaymentDriver reads $context['order_id'] independently, for its
* own unrelated correlation need — see that class's rememberIntent()).
*
* Loads and saves the model (not a bulk ::whereKey()->update()) so
* Order::observe()'s updated() hook fires and OrderStatusUpdated goes out
* the same as any other status write — see that event's own docblock for
* why it's meant to fire "regardless of what wrote it."
*
* Dispatches Checkout\Events\OrderPlaced itself, once placed_at is set —
* see that event's own docblock for why this, not CheckoutService, is now
* the dispatch point.
*/
class ApplyResolvedPaymentStatus
{
public function handle(OrderPaymentStatusResolved $event): void
public function handle(PaymentCaptured|PaymentAuthorized $event): void
{
$order = Order::findOrFail($event->orderId);
$order->update(['status' => $event->status]);
$orderId = $event->context['order_id'] ?? null;
if ($orderId === null) {
return;
}
$order = Order::findOrFail($orderId);
$configKey = $event instanceof PaymentCaptured ? 'captured_status' : 'authorized_status';
$status = config("lunar.payments.types.{$event->type}.{$configKey}");
if ($status === null) {
return;
}
$wasPlaced = ! blank($order->placed_at);
$order->update([
'status' => $status,
'placed_at' => $order->placed_at ?? now(),
]);
if (! $wasPlaced) {
Event::dispatch(new OrderPlaced($order));
}
}
}