The middleware-package (unused library)
middleware-package is a candidate replacement, not a production component. It is a
standalone PHP/Laravel Composer library — package name robusta/laravel-crm-integration,
PSR-4 namespace Robusta\CrmIntegration\ mapped to src/ (middleware-package/composer.json:2,
middleware-package/composer.json:24) — that mirrors the logic of the live
crms-middleware HTTP service so the backend could one day talk
to the Dynamics 365 CRMs in-process instead of over an HTTP hop. That cutover has not
happened. Nothing consumes this package yet.
What it is
Section titled “What it is”The plan on record (“Phase 1 — Eliminating Middleware Application”) is to lift the middleware app’s translation logic into a library and retire the network hop. The direction of the data flow stays identical — backend to CRM — but an in-process PHP method call replaces the HTTP boundary:
Today (production) Future (post-cutover, NOT built) ┌──────────┐ ┌──────────┐ │ backend │ ── HTTP ──▶ crms-middleware │ backend │ ── PHP method call ──▶ middleware-package │ (Laravel)│ (Laravel app) │ (Laravel)│ Robusta\CrmIntegration (Composer lib) └──────────┘ │ └──────────┘ │ ▼ ▼ Dynamics 365 CRMs Dynamics 365 CRMsIt is a library only — no artisan, no database, no models (middleware-package/CLAUDE.md:1).
It ships an auto-discovered service provider and a Facade, and its whole job is to be
composer require-d by a consumer (intended: backend) that then calls its methods.
The README claims 1:1 parity with the original middleware: 112 methods, one per route in the
old service. That claim is structurally consistent with the code — CrmIntegrationService exposes
112 public methods plus a constructor (middleware-package/src/Services/CrmIntegrationService.php),
and routes/api.php defines exactly 112 routes — but the parity has never been verified against
a real CRM (see Gotchas).
Status: NOT wired in
Section titled “Status: NOT wired in”What it provides
Section titled “What it provides”Everything lives under middleware-package/src/ behind the Robusta\CrmIntegration\ namespace.
The public surface is small; the bulk of the code is the per-CRM plumbing behind it.
| Component | Location | What it is |
|---|---|---|
CrmIntegrationServiceProvider |
src/CrmIntegrationServiceProvider.php |
Auto-discovered Laravel provider (registered via composer.json extra.laravel.providers). Binds the singletons, merges config, and loads routes. |
CrmIntegration Facade |
src/Facades/CrmIntegration.php |
The public entry point, aliased as CrmIntegration. Its docblock lists all 112 static methods. |
CrmIntegrationService |
src/Services/CrmIntegrationService.php |
The Facade target — 112 public methods, one per legacy route (portalsLogin, portalsUnitsSync, portalsLeadsSync, portalsBrokersLeadsSubmit, salesmanUserInfo, and so on). This is the method-stable contract. |
CrmClientFactory |
src/Services/CrmClientFactory.php |
Resolves the right per-CRM client for a given country. |
| Per-CRM clients | src/Clients/Crms/ |
Isolated HTTP wrappers per CRM — EgyptCrmClient/, OmanCrmClient/, MontenegroCrmClient/, SalesManCrmClient/, plus a LaunchesClient/ and shared BrokerClient, LeadClient, UnitClient, ShopperClient, CustomerRequestClient. Contracts live in CrmResourcesContracts/. |
| Transport base | src/Clients/ |
AbstractClient, LinkDevAuthenticatedClient (OAuth against the Dynamics / LinkDev endpoint), PortalClient, AuthenticationResponse. |
Actions/Crms/ |
src/Actions/Crms/ |
Per-domain business logic — Sale/, Unit/, Leads/, EOIs/, Meetings/, Broker/, SalesMan/, Launches/, CustomerRequests/, Taskeen/, UnitAddon/, Currencies/. |
Mapper/ |
src/Mapper/ |
Request-payload shapers per CRM and domain (the mirror of the middleware’s request mapping). |
ResponseMapper/ |
src/ResponseMapper/ |
Per-country response normalisers (Egypt/, Oman/, Montenegro/, SalesMan/) behind Country/ interfaces, producing the unified envelope. |
Enums/ |
src/Enums/ |
CountryName, CrmChangeTypes, SourceOfSale, EOIPaymentMethodEnums, EOIPaymentStatusEnums, and the *ViewNumbersEnums. |
Dto/, Exceptions/, Traits/, Http/ |
src/... |
CrmCredentials DTO; a custom exception set; the EnumToArray trait; and a Laravel Http/Middleware + Http/Controllers/Api stack used by the optional routes. |
routes/api.php |
middleware-package/routes/api.php |
112 route definitions under the portals and crms prefixes, for consumers that want HTTP parity instead of direct method calls. |
Every method returns the same standardised envelope — ['message' => ..., 'data' => ...] — and that
shape is treated as part of the contract (middleware-package/CLAUDE.md).
The migration plan
Section titled “The migration plan”The goal is to collapse the two-Laravel-app topology (backend calling crms-middleware over HTTP)
into one, with the CRM translation logic running as an in-process library inside the backend. The
intended consumer flow is a normal Composer install:
composer require robusta/laravel-crm-integrationinbackend.- Auto-discovery registers the provider and the
CrmIntegrationFacade. php artisan vendor:publish --tag=crm-integration-configto publishconfig/crm-integration.php.- Configure the per-CRM URLs and credentials, then call
CrmIntegration::portalsLeadsSubmit(...)instead of making an HTTP request.
Why it isn’t done. This library is a parallel re-implementation with several open blockers
before it could safely replace the HTTP service (middleware-package/CLAUDE.md, section 6;
middleware-package/PROJECT-OVERVIEW.md):
- No verified parity. The 112-method claim has never been run end-to-end against a real staging CRM. The test suite uses Orchestra Testbench with mocked clients — no real Dynamics call is exercised, so there is no regression record proving the responses match the middleware’s.
- Config drift on credentials. The README documents
EGYPT_CRM_USERNAME/EGYPT_CRM_PASSWORD, whilecrms-middlewareusesEGYPT_CRM_CLIENT_ID/EGYPT_CRM_CLIENT_SECRET. One is wrong, and it can’t be resolved from the repo because the config file that maps env toconfig('crm-integration.*')is not committed (see Gotchas). - Webhook ingress is unhomed. The inbound
/api/crms/*webhook surface that the middleware serves has no agreed destination post-cutover;CLAUDE.mdrule 6 says it does not belong in this library, yetroutes/api.phpstill declares those routes. - OAuth ownership undecided. Client registration currently lives in the middleware’s Laravel Passport tables. Who owns it after retirement is an open question.
Until those are closed and the cutover is deliberately scheduled, the backend stays on the HTTP
service. For the live path this library would replace, see
the crms-middleware service reference and
the Dynamics 365 integration reference.
Gotchas
Section titled “Gotchas”-
The
config/crm-integration.phpit depends on is not in the repo. The provider callsmergeConfigFrom(__DIR__.'/../config/crm-integration.php', ...)(src/CrmIntegrationServiceProvider.php:17) and every client readsconfig('crm-integration.*')(e.g.src/Clients/PortalClient.php:11,src/Clients/LinkDevAuthenticatedClient.php:11) — but that file is git-ignored (middleware-package/.gitignore, last line/config/crm-integration.php). As checked out, the package has no config: config reads resolve tonull, and booting the provider would fail on the missingmergeConfigFromtarget. Whoever cuts over must supply this file, and it is the missing piece that would settle the credential-key drift above. -
The provider auto-registers routes, against its own stated rule.
boot()callsloadRoutesFrom(__DIR__.'/../routes/api.php')unconditionally (src/CrmIntegrationServiceProvider.php:48), even thoughCLAUDE.mdrule 5 says “Don’t add a service provider that auto-registers HTTP routes by default.” The moment a consumer installs the package it would mount all 112 routes — an unexpected HTTP surface on top of the method API. -
It carries a webhook ingress surface it isn’t supposed to.
routes/api.php:252declares thecrmsprefix (BrokerController@registerand friends behind aclient:crm-webhookguard), i.e. the inbound/api/crms/*receivers — contradictingCLAUDE.mdrule 6 (“No webhook ingress in this library”). Combined with the auto-route-loading above, installing the package today would expose those receivers. -
Public methods are a hard contract the instant it is consumed. Nothing imports
CrmIntegrationServiceyet, so renames look free — butCLAUDE.mdwarns that oncebackendcomposer requires this, every one of the 112 method signatures and the['message', 'data']envelope shape become a contract that cannot change without a major version bump. -
Distribution channel is unconfirmed. The package is not on Packagist;
composer.jsonsetsminimum-stability: dev. A real install would need a private VCS or path repository configured in the consumer, which is not set up anywhere in the platform today. -
Do not treat this as a source of truth for CRM behaviour. Because it is unverified against a real CRM, the authoritative implementation remains
crms-middleware. If you need to know what the platform actually does with a CRM call, read the middleware, not this mirror.
