Feat: Tying Specific Methods with Carrier Drivers

This commit is contained in:
2026-09-18 01:23:50 +03:00
parent 12aaa43f10
commit 609a63c2f4
4 changed files with 112 additions and 4 deletions
+49 -2
View File
@@ -10,6 +10,7 @@ use Lunar\Base\Addressable;
use Lunar\DataTypes\ShippingOption;
use Lunar\Facades\ShippingManifest;
use Lunar\Models\Cart;
use Lunar\Shipping\Models\ShippingMethod;
use Modules\Core\Cart\Services\CartService;
use Modules\Core\Checkout\Events\BillingAddressSet;
use Modules\Core\Checkout\Events\PaymentMethodSelected;
@@ -20,10 +21,12 @@ use Modules\Core\Checkout\Exceptions\InvalidShippingOptionException;
use Modules\Core\Checkout\Exceptions\NoShippingAddressException;
use Modules\Core\Checkout\Exceptions\TermsNotAcceptedException;
use Modules\Core\Checkout\Exceptions\UnknownPaymentTypeException;
use Modules\Core\Payment\Contracts\RequiresFulfillmentType;
use Modules\Core\Payment\DTOs\PaymentResult;
use Modules\Core\Payment\Models\PaymentMethod;
use Modules\Core\Payment\Services\PaymentDriverRegistry;
use Modules\Core\Payment\Services\PaymentMethodCache;
use Modules\Core\Shipping\Support\FulfillmentType;
/**
* Storefront-facing checkout operations, mirroring
@@ -232,7 +235,7 @@ class CheckoutService
/**
* Every payment method currently offered to the storefront, ordered by
* Modules\Core\Payment\Models\PaymentMethod::position — a row is
* offered only when ALL three checks pass, each meaning something
* offered only when ALL four checks pass, each meaning something
* different to an admin diagnosing why a method isn't showing up (see
* docs/payments.md):
* 1. `enabled` — an admin turned it on.
@@ -243,17 +246,61 @@ class CheckoutService
* vanished driver can never silently look "available").
* 3. the resolved driver reports Configurable::isConfigured() — its
* own runtime requirements (e.g. an API key) are met.
* 4. its driver's RequiresFulfillmentType (if it declares one)
* agrees with the cart's currently selected shipping method's own
* fulfillment type (Modules\Core\Shipping\Support\
* FulfillmentType::resolve()) — "Pay in store" offered alongside
* a courier delivery makes no sense (no staff member present at
* handoff to take cash), and cash-on-delivery alongside store
* pickup is equally meaningless (OfflinePaymentDriver already
* covers that in-person moment). A cart with no shipping option
* selected yet imposes no constraint here — every method is
* offered until a fulfillment type is actually known, the same
* leniency setShippingAddress()'s own docblock describes for
* required-field enforcement happening at the payment gate, not
* mid-checkout.
*
* @return Collection<int, PaymentMethod>
*/
public function getPaymentMethods(): Collection
{
$fulfillmentType = $this->currentFulfillmentType();
return $this->paymentMethods->all()
->filter(fn (PaymentMethod $method) => $method->enabled && $method->driver_missing_at === null)
->filter(fn (PaymentMethod $method) => $this->paymentDrivers->resolve($method->driver)?->isConfigured() ?? false)
->filter(function (PaymentMethod $method) use ($fulfillmentType) {
$driver = $this->paymentDrivers->resolve($method->driver);
if (! $driver?->isConfigured()) {
return false;
}
if ($fulfillmentType === null || ! $driver instanceof RequiresFulfillmentType) {
return true;
}
return $driver->requiredFulfillmentType() === $fulfillmentType;
})
->values();
}
/**
* @return 'carrier'|'store_pickup'|null null when the cart has no
* shipping option selected yet
*/
private function currentFulfillmentType(): ?string
{
$identifier = $this->cart->currentOrCreate()->shippingAddress?->shipping_option;
if ($identifier === null) {
return null;
}
$method = ShippingMethod::where('code', $identifier)->first();
return $method ? FulfillmentType::resolve($method) : null;
}
/**
* Records which payment type the shopper picked (Cart::meta
* ['payment_method']) — read by Modules\Core\Payment\Pipelines\