Skip to content

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.

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 CRMs

It 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).

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 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-integration in backend.
  • Auto-discovery registers the provider and the CrmIntegration Facade.
  • php artisan vendor:publish --tag=crm-integration-config to publish config/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, while crms-middleware uses EGYPT_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 to config('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.md rule 6 says it does not belong in this library, yet routes/api.php still 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.

  • The config/crm-integration.php it depends on is not in the repo. The provider calls mergeConfigFrom(__DIR__.'/../config/crm-integration.php', ...) (src/CrmIntegrationServiceProvider.php:17) and every client reads config('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 to null, and booting the provider would fail on the missing mergeConfigFrom target. 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() calls loadRoutesFrom(__DIR__.'/../routes/api.php') unconditionally (src/CrmIntegrationServiceProvider.php:48), even though CLAUDE.md rule 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:252 declares the crms prefix (BrokerController@register and friends behind a client:crm-webhook guard), i.e. the inbound /api/crms/* receivers — contradicting CLAUDE.md rule 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 CrmIntegrationService yet, so renames look free — but CLAUDE.md warns that once backend composer 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.json sets minimum-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.