Appearance
<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /changelog/Changelog.4.103.md for this page in Markdown format</div>
Version 4
version 4.103
[4.103.1] docs(rest): regenerate OpenAPI specs for the v1.6 REST surface
- Why. The 4.103.0 release added REST endpoints (
GET /rest/checkout/payment-methods,POST /rest/checkout/cancel-payment/{orderId},GET /rest/transporter/{id}/dhl-rates,GET /rest/transporter/{id}/asap-services) and bumped the REST API minor to v1.6, but the tracked OpenAPI artifacts were not regenerated —public/openapi.json/public/openapi-v1.jsonwere missing the new paths andpublic/api-versions.jsondid not list v1.6. Nothing regenerates these at build/deploy time, so the served spec and the api-docs UI were stale. - Fix. Regenerated via
php cli.php job/GenerateOpenApiJsonand committedpublic/openapi.json,public/openapi-v1.json, andpublic/api-versions.json. The four new client endpoints are now documented and v1.6 is listed. (The Piraeus/PayByBank webhook receivers remain intentionally absent — they are provider callbacks on aBase_ccontroller without@OAannotations, consistent with the existing Stripe/Viva/PayPal webhooks.)
- Why. The 4.103.0 release added REST endpoints (
[4.103.0] feat(rest/checkout): REST checkout reaches parity with the legacy storefront flow (epic Advisable-com/ecommercen#253)
- What. Closes the behavioural gap between the modern REST checkout (
POST /rest/checkout/place-order+ gateway flow) and the legacy storefrontAdv_checkoutcontroller. The audit baseline was ~56% coverage; this epic brings it to ~100% — REST orders now fire the same post-success side-effects, support the same payment gateways, persist the same carrier data, and enforce the same order-integrity rules as storefront orders. - Order event bus (#40, #41, #42, #112, #83, #84, #267, #268). New
OrderEventDispatcherwithOrderPaid/OrderCanceledevents and a registered listener chain insrc/Domains/Order/Event/Listeners/: order-confirmation email, ERP webhook, low-stock alert, gift-packaging linkage, loyalty-point restore + coupon-usage decrement on cancel, and analytics dispatch (Matomo ecommerce order, Meta Conversions API purchase, Manago, Project Agora). Slow side-effects (ERP, analytics) defer throughDeferredTaskRunnerso the response is not blocked. - Payment gateway ports (14 gateways). REST adapters for every gateway the storefront supports: generic CardLink (#254) plus the Alpha Bank (#263) / Eurobank (#264) acquirers that reuse it; JCC (#256); IRIS (#255); Klarna Payments (#257); the three Ethniki/NBG variants — iBank Simplify (#258), NBGPay (#259), EE +
NBGEEValidator(#260, closes #142); PayPal Express (#261); Nexi XPay (#262); Piraeus (#271); ApcoPay (#272); PayByBank (#266). Each registers inPaymentInitializerFactory::create()gated on its registry settings; per-gateway webhooks live onsrc/Rest/Webhooks/Controllers/Webhook.php. - Carrier pickup-point & courier data (#113, #126, #127, #273).
/rest/checkout/shippingnow exposes SmartPoint / DHL / ASAP discriminators; newGET /rest/transporter/{id}/dhl-ratesandGET /rest/transporter/{id}/asap-servicesdiscovery endpoints; aCarrierDataPersisterstrategy dispatched fromPlaceOrderServicewrites the selected pickup point / DHL voucher / ASAP data onto the order (newOrder/SmartPointandOrder/DhlVoucherwrite layers plus the fullOrder/AsapDatadomain). - Discovery & cancellation endpoints (#228, #30).
GET /rest/checkout/payment-methodslists the active gateways;POST /rest/checkout/cancel-payment/{id}cancels an awaiting-gateway order through the same pipeline as legacy, emittingOrderCanceled. - Order integrity (#3, #29, #86). REST place-order rejects guest checkout against a registered customer email, rejects the order when
OrderBasketBuildersilently drops items, and persists applied-bundle linkage on basket rows — matching the storefront safeguards. - Status alignment. REST awaiting-gateway status flipped to the legacy
PENDING(waspending_payment);/rest/order/order/{id}/cancelaligned to the legacy status set and cancellation pipeline. - Marketing consent (#269). REST place-order captures the customer marketing-consent flag the storefront records.
- Payment-status polling (#163, partial). Legacy
PayByBankGetStatusported toAdvisable\Domains\Checkout\Jobs\PollPayByBankStatus(registered inapplication/config/jobs.php; schedule entry ships commented as an operator toggle). The bundled 14-carrierGetOrdersTransferStatustransporter-tracking job is out of scope and split to #274. - Tests. Per-adapter unit tests, carrier persister/dispatcher unit + cross-section integration tests, and event-listener unit tests landed alongside each port. Full suite green across Unit / Integration / Database / Legacy.
- What. Closes the behavioural gap between the modern REST checkout (
[4.103.0] fix(rest/checkout): verify PayPal webhook payments server-side before confirming (Advisable-com/ecommercen#4)
- Security. The REST PayPal webhook (
POST /rest/webhooks/paypal) previously trusted the inbound event payload and would mark an order paid from a forgedCHECKOUT.ORDER.APPROVED/PAYMENT.CAPTURE.COMPLETEDPOST (aTODOacknowledged the gap). PayPal'sverifyWebhookSignatureAPI is not usable here — it requires a registered PayPal Webhook ID the project's registry does not carry (only PAYPAL_ADVANCED CLIENT_ID / CLIENT_SECRET / SANDBOX). - Fix. The handler now re-fetches the order server-side via
PayPalRestApi::getOrderDetails()(same PAYPAL_ADVANCED credentials as thepaypaladvancedpayway) and confirms only when PayPal itself reports the capture COMPLETED/APPROVED — mirroring the legacyAdv_checkout::paypalAdvancedSuccess()check. Forged or unverifiable events return202 Acceptedwithout touching the order, logged on thewebhookschannel. 10 unit tests cover the confirm / forged / not-completed / API-error / unconfigured-client paths.
- Security. The REST PayPal webhook (
[4.103.0] fix(checkout): alias
DeferredTaskRunnerFQCN so order event listeners autowire- Bug. The new order-event listeners inject
DeferredTaskRunnerby type, but it was only registered under the named service id'deferred_task'. With autowiring enabled the Symfony container failed to compile (Cannot autowire ... DeferredTaskRunner ... no such service exists) — a rebuiltcache/container.phpwould have taken the whole app down at runtime. The unit suite missed it (listener tests mock the dependency); the integration suite was red branch-wide. - Fix. Added
$services->alias(DeferredTaskRunner::class, 'deferred_task')inapplication/config/container/deferred_task.php. Restores the integration suite to green.
- Bug. The new order-event listeners inject
[4.103.0] fix(rest/transporter): normalize SmartPoint catalog provider shape + complete the provider map (Advisable-com/ecommercen#243, #244)
- #243.
GET /rest/transporter/{id}/smart-pointreturned HTTP 502 for every BoxNow / Skroutz fetch because those providers (shared with the legacy storefront) return a nested array keyed byclass_namerather than a flatSmartPointDTO[]. Rather than change the provider — which the legacyAdv_order::smartPointsInitialize()depends on — the catalogServicenow normalizes the nested shape to flat DTOs; flat lists pass through unchanged and the malformed-shape guard stays as belt-and-suspenders. - #244.
$config['smartPoints']was missing six registered transporter classes. AddedTAXYDEMAV2(the only one with a real provider); documented the five provider-less omissions (CYPRUSPOST, DAILYCOURIER, ASAP, DHL, TAXYDEMA) inline inapplication/config/app.php.
- #243.
[4.103.0] refactor(sitemap): migrate output onto dedicated
storage('sitemap')disk for K8s safety- Bug.
AdvGenerateSitemapswrote sitemap chunks and the index to the cron pod's local filesystem viawrite_file('./sitemap/...'). On Kubernetes multi-pod deployments the web pods serving/sitemap/sitemap_index.xmlhave a different ephemeral filesystem from the cron pod, so crawlers — and the "live preview" link in Settings > XML Feeds (application/views/admin/settings/xml_feeds_settings.php:1110) — hit 404s on any pod that wasn't the one that ran the cron. Same conceptual fix as #235 (private storage migration), this time for public assets. - New storage type. Added
sitemaptoapplication/config/storage.php, parallel tofiles. Local disk roots atFCPATHwithvisibility: public; S3 disk uses dedicated env vars (SITEMAP_S3_BUCKET,SITEMAP_S3_URL, etc., documented in.env.example) so infra can route sitemap traffic to a separate bucket / CDN edge. Driver selection per-tenant viaSITEMAP_STORAGE_DISK. Keeping it separate fromfilesisolates the regen lifecycle and crawler-traffic profile from user-uploaded assets. - Job flow.
executeCommand()now lists everything undersitemap/onstorage('sitemap')and deletes it, writes each chunk tositemap/sitemap-{N}.xml(capturing$disk->url()), then writes the indexsitemap/sitemap_index.xmlreferencing those URLs. Static index, no controller in the request path. Chunking unchanged (in-memory, 500 URLs/chunk) — verified in production at 1M-product scale. - Index URL stable.
/sitemap/sitemap_index.xmlis unchanged — served by nginx directly (local-disk tenants) or by the CDN edge mounted at/sitemap/(K8s tenants). No Google Search Console resubmission, norobots.txtedit. - Static-serving fix (dev nginx + .htaccess). The dev nginx
location /block forwarded every request to PHP-FPM, and FPM'ssecurity.limit_extensions = .php .pharrejected.xmlwith HTTP 403. Addedsitemapto the static-asset location regex in.docker/integration/configs/nginx.default.confand the parallel!^(ui|files|favicon|sitemap)clause inpublic/.htaccess.exampleso/sitemap/*.xmlis served directly. - Directory permissions (Docker image).
.docker/images/app.dockerfilepre-createspublic/sitemapalongside the existingstorage/*dirs, so it ships at uid 1000 / mode 0755 and nginx can serve the files rather than relying on Flysystem's runtime 0700 default. Inert for K8s/S3 tenants whereSITEMAP_STORAGE_DISK=s3. - Flow docs.
docs/flows/system/SY-05-sitemap-generation.mdrewritten for the new design;docs/flows/admin/AD-14-seo-management.mdSection 6 updated to match.
- Bug.