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/).
|
||||
|
||||
## [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
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "boboko/core",
|
||||
"description": "Core module — authentication and shared panel behaviour",
|
||||
"type": "library",
|
||||
"version": "0.18.1",
|
||||
"version": "0.19.0",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\Core\\": "src/"
|
||||
|
||||
Reference in New Issue
Block a user