Skip to content

<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>

Home | Changelog

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.json were missing the new paths and public/api-versions.json did 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/GenerateOpenApiJson and committed public/openapi.json, public/openapi-v1.json, and public/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 a Base_c controller without @OA annotations, consistent with the existing Stripe/Viva/PayPal webhooks.)
  • [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 storefront Adv_checkout controller. 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 OrderEventDispatcher with OrderPaid / OrderCanceled events and a registered listener chain in src/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 through DeferredTaskRunner so 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 in PaymentInitializerFactory::create() gated on its registry settings; per-gateway webhooks live on src/Rest/Webhooks/Controllers/Webhook.php.
    • Carrier pickup-point & courier data (#113, #126, #127, #273). /rest/checkout/shipping now exposes SmartPoint / DHL / ASAP discriminators; new GET /rest/transporter/{id}/dhl-rates and GET /rest/transporter/{id}/asap-services discovery endpoints; a CarrierDataPersister strategy dispatched from PlaceOrderService writes the selected pickup point / DHL voucher / ASAP data onto the order (new Order/SmartPoint and Order/DhlVoucher write layers plus the full Order/AsapData domain).
    • Discovery & cancellation endpoints (#228, #30). GET /rest/checkout/payment-methods lists the active gateways; POST /rest/checkout/cancel-payment/{id} cancels an awaiting-gateway order through the same pipeline as legacy, emitting OrderCanceled.
    • Order integrity (#3, #29, #86). REST place-order rejects guest checkout against a registered customer email, rejects the order when OrderBasketBuilder silently 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 (was pending_payment); /rest/order/order/{id}/cancel aligned 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 PayByBankGetStatus ported to Advisable\Domains\Checkout\Jobs\PollPayByBankStatus (registered in application/config/jobs.php; schedule entry ships commented as an operator toggle). The bundled 14-carrier GetOrdersTransferStatus transporter-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.
  • [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 forged CHECKOUT.ORDER.APPROVED / PAYMENT.CAPTURE.COMPLETED POST (a TODO acknowledged the gap). PayPal's verifyWebhookSignature API 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 the paypaladvanced payway) and confirms only when PayPal itself reports the capture COMPLETED/APPROVED — mirroring the legacy Adv_checkout::paypalAdvancedSuccess() check. Forged or unverifiable events return 202 Accepted without touching the order, logged on the webhooks channel. 10 unit tests cover the confirm / forged / not-completed / API-error / unconfigured-client paths.
  • [4.103.0] fix(checkout): alias DeferredTaskRunner FQCN so order event listeners autowire

    • Bug. The new order-event listeners inject DeferredTaskRunner by 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 rebuilt cache/container.php would 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') in application/config/container/deferred_task.php. Restores the integration suite to green.
  • [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-point returned HTTP 502 for every BoxNow / Skroutz fetch because those providers (shared with the legacy storefront) return a nested array keyed by class_name rather than a flat SmartPointDTO[]. Rather than change the provider — which the legacy Adv_order::smartPointsInitialize() depends on — the catalog Service now 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. Added TAXYDEMAV2 (the only one with a real provider); documented the five provider-less omissions (CYPRUSPOST, DAILYCOURIER, ASAP, DHL, TAXYDEMA) inline in application/config/app.php.
  • [4.103.0] refactor(sitemap): migrate output onto dedicated storage('sitemap') disk for K8s safety

    • Bug. AdvGenerateSitemaps wrote sitemap chunks and the index to the cron pod's local filesystem via write_file('./sitemap/...'). On Kubernetes multi-pod deployments the web pods serving /sitemap/sitemap_index.xml have 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 sitemap to application/config/storage.php, parallel to files. Local disk roots at FCPATH with visibility: 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 via SITEMAP_STORAGE_DISK. Keeping it separate from files isolates the regen lifecycle and crawler-traffic profile from user-uploaded assets.
    • Job flow. executeCommand() now lists everything under sitemap/ on storage('sitemap') and deletes it, writes each chunk to sitemap/sitemap-{N}.xml (capturing $disk->url()), then writes the index sitemap/sitemap_index.xml referencing 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.xml is unchanged — served by nginx directly (local-disk tenants) or by the CDN edge mounted at /sitemap/ (K8s tenants). No Google Search Console resubmission, no robots.txt edit.
    • Static-serving fix (dev nginx + .htaccess). The dev nginx location / block forwarded every request to PHP-FPM, and FPM's security.limit_extensions = .php .phar rejected .xml with HTTP 403. Added sitemap to the static-asset location regex in .docker/integration/configs/nginx.default.conf and the parallel !^(ui|files|favicon|sitemap) clause in public/.htaccess.example so /sitemap/*.xml is served directly.
    • Directory permissions (Docker image). .docker/images/app.dockerfile pre-creates public/sitemap alongside the existing storage/* 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 where SITEMAP_STORAGE_DISK=s3.
    • Flow docs. docs/flows/system/SY-05-sitemap-generation.md rewritten for the new design; docs/flows/admin/AD-14-seo-management.md Section 6 updated to match.