Bump version to 0.19.0
This commit is contained in:
+103
@@ -4,6 +4,109 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
|
||||||
|
## [0.19.0] - 2026-09-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `Modules\Core\Payment\Contracts\RequiresFulfillmentType` — a payment driver can now declare
|
||||||
|
it only makes sense for one fulfillment type (carrier delivery vs. store pickup), the
|
||||||
|
payment-side mirror of `Shipping\Contracts\DeclaresFulfillmentType`. `CheckoutService::
|
||||||
|
getPaymentMethods()` excludes a method whose driver disagrees with the cart's currently
|
||||||
|
selected shipping method — `OfflinePaymentDriver` ("pay in store") now requires
|
||||||
|
`store_pickup`, `CashOnDeliveryPaymentDriver` requires `carrier`. Previously every enabled,
|
||||||
|
configured payment method was offered regardless of shipping choice, so a shopper picking a
|
||||||
|
courier delivery could still see "Pay in store" (no staff member present to take cash), and a
|
||||||
|
store-pickup shopper could see cash-on-delivery (meaningless — there is no delivery to collect
|
||||||
|
payment on). No constraint is imposed before a shipping option is selected.
|
||||||
|
- `Modules\Core\Payment\Events\PaymentDeferred` — dispatched by any payment driver whose
|
||||||
|
`Pending` result will never resolve via a later gateway callback (currently only
|
||||||
|
`CashOnDeliveryPaymentDriver`), distinct from a Stripe-style `Pending` that a webhook will
|
||||||
|
still resolve. Handled by the new `Modules\Core\Order\Listeners\
|
||||||
|
MarkOrderPlacedOnDeferredPayment`, which sets `Order::placed_at`, dispatches `OrderPlaced`,
|
||||||
|
and advances `status` past `awaiting_payment` — without ever touching `Order::paid`, which
|
||||||
|
still only flips via staff explicitly marking a COD order received.
|
||||||
|
- `Modules\Core\Order\Services\OrderPaymentResolutionService::resolveDeferredPayment()` — the
|
||||||
|
status-advance half of the above, reusing the same "advance past `awaiting_payment`" logic a
|
||||||
|
captured payment already uses.
|
||||||
|
- `Modules\Core\Checkout\Exceptions\NoShippingAddressException`.
|
||||||
|
- `Modules\Core\Shipping\Carriers\BoxNow\BoxNowClient::destinations()` — lists available Box
|
||||||
|
Now lockers (`GET /destinations`), backing a plain, self-hosted locker picker on checkout;
|
||||||
|
Box Now's own Destination Map JS widget only talks to their Production environment, making it
|
||||||
|
unusable while developing against Stage credentials.
|
||||||
|
- `config/shippingCarriers/boxnow.php`: `BOXNOW_PARTNER_ID` — issued alongside Box Now
|
||||||
|
credentials, consumed only by their client-side map widget, never by `BoxNowClient`'s own
|
||||||
|
REST authentication.
|
||||||
|
- A "Tracking history" list under each shipment on the order page (`Shipping\Extensions\
|
||||||
|
OrderShipmentsExtension`) — every recorded carrier checkpoint, oldest first, not just the
|
||||||
|
latest status.
|
||||||
|
- `Modules\Core\Review\Services\ReviewService` and `ReviewEvents\ReviewReplied` — extracted
|
||||||
|
from `ManageProductReviews`'s inline `$record->update()`, following the write-then-dispatch
|
||||||
|
pattern used everywhere else.
|
||||||
|
- `Modules\Core\Catalog\Services\StockService::decrementForOrder()` — extracted from
|
||||||
|
`DecrementStockOnOrderPlaced`, isolating the atomic stock-decrement SQL and Meilisearch
|
||||||
|
reindex from the listener itself.
|
||||||
|
- `Modules\Core\Order\Services\OrderStatusFlow::isValidTransition()` — the single source of
|
||||||
|
truth for "is this a legal next status," replacing several listeners' own hardcoded "only
|
||||||
|
fire from status X" comparisons.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Cash-on-delivery orders were placed but never left `awaiting_payment`, were invisible in
|
||||||
|
customer order history, never decremented stock, and the storefront's own post-checkout
|
||||||
|
confirmation could never find them — `CashOnDeliveryPaymentDriver::pay()` returns `Pending`,
|
||||||
|
which dispatched no event at all, so nothing ever set `Order::placed_at` or advanced
|
||||||
|
`status`. Fixed by `PaymentDeferred`/`MarkOrderPlacedOnDeferredPayment` above.
|
||||||
|
- Staff marking a COD order "paid" (`OrderFulfillmentService::markPaid()`) flipped
|
||||||
|
`Order::paid`/`paid_at` but never recorded a `Transaction` row — no audit trail, and anything
|
||||||
|
reading `$order->transactions` (paid-amount displays included) saw nothing. Now records a
|
||||||
|
`capture` transaction via `TransactionRecorder`, the exact call site its own docblock had
|
||||||
|
already anticipated ("a future admin action ... can write a row the same way").
|
||||||
|
- A confirmed cash-on-delivery shipment dispatched via ACS or Box Now never actually told the
|
||||||
|
carrier to collect payment — `ShipmentRequest::$paymentMode`/`$amountToCollect` were defined
|
||||||
|
on the DTO but no caller ever populated them, permanently dead-coding both carriers' COD
|
||||||
|
branches (`AcsFulfillmentService`'s `Cod_Ammount`/`Cod_Payment_Way`, Box Now's
|
||||||
|
`amountToBeCollected`). `OrderViewExtension`'s "Create Shipment" action now derives both from
|
||||||
|
`OrderStatusFlow::isCod($order)` at dispatch time — never left to staff to remember.
|
||||||
|
- `Modules\Core\Shipping\Jobs\PollShipmentTrackingJob`: one shipment's tracking lookup failing
|
||||||
|
(a carrier 500, a malformed parcel response) aborted the rest of that carrier's shipments in
|
||||||
|
the same batch — now individually caught and reported per shipment.
|
||||||
|
- Every Box Now delivery request 400'd (`P405`, invalid phone number) for any customer whose
|
||||||
|
phone was stored in local Greek format rather than full international — `contactNumber` is
|
||||||
|
now normalized to `+30...` before every request.
|
||||||
|
- Creating a Box Now shipment 400'd (`P401`/`P402`) whenever `BOXNOW_ORIGIN_LOCATION_ID` or the
|
||||||
|
sender contact fields were unset — documented and confirmed against a live sandbox account.
|
||||||
|
- Selecting a Box Now locker at checkout, then making any unrelated address-form edit
|
||||||
|
afterward (even a delivery-instructions keystroke), silently discarded the locker choice —
|
||||||
|
`Lunar\Actions\Carts\AddAddress` deletes and recreates the cart's shipping address row on
|
||||||
|
every save, wiping whatever `meta` a prior save had written onto it.
|
||||||
|
`CheckoutService::setShippingAddress()` now carries the locker forward across that
|
||||||
|
recreation; `selectShippingOption()` clears it when switching away from Box Now, so a stale
|
||||||
|
locker never resurfaces if the shopper switches back later.
|
||||||
|
`Shipping\Extensions\OrderViewExtension`'s "Box Now locker ID" field is no longer locked
|
||||||
|
read-only once a customer choice exists — staff can override it.
|
||||||
|
- Creating a Box Now shipping method 500'd (`Array to string conversion` / invalid JSON insert)
|
||||||
|
— the vendor `ListShippingMethod` page's `CreateAction` builds its form inline, bypassing
|
||||||
|
`ShippingMethodResourceExtension`'s translated-name field entirely; `Filament\Pages\
|
||||||
|
ManageShippingRates`'s method picker and "Shipping Method" table column also queried/sorted
|
||||||
|
the now-JSON `name` column directly in SQL (`could not identify an ordering operator for type
|
||||||
|
json`), both resolved app-side instead.
|
||||||
|
- Creating or editing a Payment Method: `capture_mode` ("Charge immediately" / "Hold now,
|
||||||
|
charge later") was offered even for a driver with no `authorize()` method at all
|
||||||
|
(`CashOnDeliveryPaymentDriver`), which would have fatally errored at checkout had "authorize"
|
||||||
|
ever been selected — now hidden/non-required unless the driver implements
|
||||||
|
`SupportsAuthorization`. A spurious `validation.required` on the translated Name field, and
|
||||||
|
every new Payment Method silently saving at `position` 0 regardless of the intended
|
||||||
|
"last in the list" default — both traced to the same cause: an `Action::schema()` modal only
|
||||||
|
dehydrates fields backed by a real form component, so `fillForm()`'s defaults for `name`/
|
||||||
|
`position` were computed but never actually reached the saved record.
|
||||||
|
- `Modules\Core\Auth\Services\UserOtpService::generateAndSend()` now dispatches `UserCreated`
|
||||||
|
when a new `User` row is created — this event was previously never dispatched anywhere in
|
||||||
|
this package at all, despite listeners existing for it.
|
||||||
|
- Applied a deliberate queueing policy across every Order/Localization/Customer/Payment/
|
||||||
|
Catalog listener, judged case-by-case on "if the queue stalls for minutes/hours, does this
|
||||||
|
cause a real functional break, not just cosmetic staleness" — `RecordPaymentTransaction`,
|
||||||
|
`CompleteOrderOnPickedUp`, `CreateCustomerForUser`, and `DecrementStockOnOrderPlaced` stay
|
||||||
|
synchronous (a stalled queue would mean a real ordering violation or oversell risk); cache
|
||||||
|
flushes, activity logging, and carrier-checkpoint-driven fulfillment listeners are now queued.
|
||||||
|
|
||||||
## [0.18.1] - 2026-09-16
|
## [0.18.1] - 2026-09-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "boboko/core",
|
"name": "boboko/core",
|
||||||
"description": "Core module — authentication and shared panel behaviour",
|
"description": "Core module — authentication and shared panel behaviour",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"version": "0.18.1",
|
"version": "0.19.0",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Modules\\Core\\": "src/"
|
"Modules\\Core\\": "src/"
|
||||||
|
|||||||
Reference in New Issue
Block a user