bigperson/exchange1c
Laravel package for exchanging data with 1C:Enterprise via CommerceML. Import and export catalogs, products, prices, stock, and orders, with configurable routes, REST endpoints, and straightforward Laravel integration.
Install and wire up
composer require bigperson/exchange1c
php artisan vendor:publish --provider="Bigperson\Exchange1C\ExchangeServiceProvider"
You get config/exchange1c.php and a route the 1C client can hit. Open the config first — it is the whole integration surface:
auth.login / auth.password — credentials 1C sends on mode=checkauthstorage_path / upload_dir — where chunked XML/ZIP uploads landmodels — map of category, product, offer, property, warehouse to your own class namesThe first use case to get working
Get a raw checkauth → init → file → import cycle returning success before worrying about data. Point a 1C node at https://your.site/exchange1c (or your registered route) with the credentials from config, and watch the upload dir. If files appear, transport is done; everything after that is model work.
Then implement models
Your classes must implement the package interfaces (CategoryInterface, ProductInterface, OfferInterface, etc.) — these are the contract the importer calls (getPrimaryKey(), setId(), setName(), setDeleted(), …). Start with Category and Product only; leave offer and property unmapped in config until the catalog imports cleanly.
Thin Eloquent models + a mapping layer. The interfaces are verbose and will fight your schema. Two workable approaches:
import_products, import_categories) that mirror CommerceML 1:1, then a separate job that normalizes into your real products/categories. Cleaner, survives 1C format drift, easy to truncate and re-import.external_id (1C's Ид) to your real tables with a unique index, and implement the interfaces on the real models. Faster to ship, but every interface method is now load-bearing in production code.For a 39-star, low-traffic package, option 1 is usually the safer bet.
Upsert keyed on the 1C GUID, never on name. Every importer entry point should be updateOrCreate(['external_id' => $guid], [...]). Treat the whole import as idempotent — 1C will re-send the full catalog whenever an operator clicks "exchange", and partial failures will replay.
Category tree in one pass. 1C sends <Группа> nested with <Ид> and <РодительИд>. Store the parent GUID as a plain string column, import flat, and resolve parent_id in a second pass after the whole import.xml is read. Don't try to resolve parents inline — ordering is not guaranteed.
Offers are a separate file and a separate concern. offers.xml carries prices, stock, and units per Ид of the offer, which references a product GUID. Sync it as updateOrCreate on external_id, and keep price/quantity as decimal columns — never floats for money.
Register the route yourself if you need middleware. The package ships a route, but you'll want it outside CSRF, on its own rate limit, and typically with IP allow-listing:
Route::post('/exchange1c', Exchange1CController::class)
->withoutMiddleware([VerifyCsrfToken::class]);
If 1C runs behind a dynamic IP, skip IP filtering and rely on checkauth plus a per-request shared secret in the URL.
Images via ZIP, processed after import. mode=file drops a pictures.zip alongside the XML. Unzip into a temp dir, match filenames (1C uses Ид or Ид_1.jpg) to product external_id, then move into your media disk. Do this in a queued job — it's the slowest part and 1C doesn't wait politely.
Extend via the handlers, not by monkey-patching. The import path is a chain of mode handlers (ModeCheckAuth, ModeInit, ModeFile, ModeImport). If you need custom pre/post-import behavior, prefer events or a wrapping service over editing vendor code; bind your own handler implementation in the service provider if you must.
checkauth response format is strict. 1C expects the literal three-line success\n<session>\n<session>\n shape (and failure\n on bad creds), not JSON or an HTTP error code. A 200 with the wrong body looks like an auth failure in 1C and gives you nothing useful in the log.
Sessions are files, and they leak. The package tracks exchange state on disk, not in Laravel sessions. Failed runs leave stale session/limit files behind and the next init can behave oddly. Add a scheduled cleanup for storage_path older than N hours, and always reproduce bugs by clearing that directory first.
Chunked mode=file needs a big enough init limit. The init handler advertises a byte limit per request; 1C honors it. If you have a reverse proxy (nginx client_max_body_size) or PHP upload_max_filesize smaller than the advertised limit, uploads truncate mid-file and the XML parse fails with "unexpected end of file" — which looks like a data bug, not a transport bug. Set the advertised limit below all three real limits.
Encoding. CommerceML from 1C is frequently windows-1251, not UTF-8. If names come out as mojibake, check the XML declaration and run content through iconv/mb_convert_encoding before parsing. Do not "fix" it by setting your DB collation.
Archive and delete flags. 1C sends <Статус>Удален</Статус> (deleted) and <Статус>Не удален</Статус> — the interface's setDeleted/getDeleted is easy to overlook. Soft-delete rather than hard-delete; operators un-delete things and expect the row back with its relations.
Prices carry a unit. <Цена> comes with <Валюта> and <ЕдиницаИзмерения>; don't blindly multiply by a hardcoded currency. If you only sell in one currency, assert on the currency code and log loudly when it changes — a config change on the 1C side once meant a 60x price error at checkout.
Properties are sparse and dynamic. <ЗначенияСвойств> is a key/value bag per product. Store as JSON or a pivot table; don't create a column per property. Also accept that 1C will send properties that only exist on some products.
Debug by logging the raw request. Add a middleware that dumps mode, query params, headers, and the raw body to a rotating log. Given the package's vintage (last release 2022) and small userbase, you will hit edge cases with no documentation — the wire log is your only reliable source of truth. Replay captured files against a local endpoint instead of asking the 1C admin to re-run.
Assume Laravel/PHP version drift. A 2022 release may declare constraints that block current Laravel or PHP 8.2+. Check composer.json before installing; if it's capped, either pin your app or fork and widen the constraints (the package is MIT and small). Test the full four-mode cycle on staging before trusting any patch — this code path is hard to unit test without real 1C payloads.
Concurrency. Operators sometimes trigger exchange from two 1C terminals. Since state is on disk per session, two simultaneous imports can interleave writes to the same files. Serialize imports with a cache/DB lock around the import mode.
How can I help you explore Laravel packages today?