Developer notes
Technical reference for developers who customize Conrad Marketplace Connector (Mirakl).
Add-on identity
Section titled “Add-on identity”| Item | Value |
|---|---|
| Add-on id | kv_conrad |
| Function prefix | fn_kv_conrad_ |
| Language variables | kv_conrad.* |
| CSS scope | .kv-conrad |
| PHP namespace | Tygh\Addons\KvConrad |
| Preset | marketplace (Mirakl engine copied from the PcComponentes connector; operator file Operators\Conrad) |
Mirakl endpoints used
Section titled “Mirakl endpoints used”Source of every operation: the official Mirakl Marketplace Seller OpenAPI file, https://developer.mirakl.com/_bundle/content/product/mmp/rest/seller/openapi3.json?download (reference pages under https://developer.mirakl.com/content/product/mmp/rest/seller/openapi3). The seller API key goes in the plain Authorization header; the host is the Mirakl address of the seller’s own Conrad instance. Client\Api has one method per operation.
| Id | Method and path | Client\Api method |
Used by |
|---|---|---|---|
| A01 | GET /api/account |
account(), ping() |
Test connection, market currency check |
| H11 | GET /api/hierarchies |
hierarchies() |
Conrad category tree (category mapping) |
| PM11 | GET /api/products/attributes?hierarchy= |
productAttributes() |
Attributes page: the product attributes Conrad defines for a category (read only, shown so you can complete your feature data) |
| AF01 | GET /api/additional_fields?entities=OFFER |
offerFields() |
Offer fields that can be mapped (lead time, shipping class, warehouse …) |
| OF61 | GET /api/offers/states |
offerStates() |
Code of the condition “New” |
| OF01 | POST /api/offers/imports (multipart file, import_mode=NORMAL) |
importOffers() |
Create / update offers; stored before the call |
| OF24 | POST /api/offers |
postOffers() |
JSON alternative, selectable in the operator file (offer_import = json) |
| OF02 | GET /api/offers/imports/{import} |
offerImport() |
State of an offer import |
| OF03 | GET /api/offers/imports/{import}/error_report |
offerImportErrors() |
Reason per rejected line |
| OF04 | GET /api/offers/imports?start_date= |
offerImports() |
Finds an import whose POST answer was lost |
| STO01-STO03 | POST /api/offers/stock/imports, GET .../{id}/status, GET .../{id}/error_report |
importStock(), stockImport(), stockImportErrors() |
Stock |
| PRI01-PRI03 | POST /api/offers/pricing/imports, GET /api/offers/pricing/imports?import_id=, GET .../{id}/error_report |
importPrices(), priceImport(), priceImportErrors() |
Price |
| OR11 | GET /api/orders |
orders() |
New orders (order_state_codes=WAITING_ACCEPTANCE), changed orders, order lines for refunds |
| OR21 | PUT /api/orders/{order_id}/accept |
acceptOrder() |
Accept or refuse each line, decided by stock |
| OR23 | PUT /api/orders/{order_id}/tracking |
setTracking() |
Carrier, tracking number, link |
| OR24 | PUT /api/orders/{order_id}/ship (Content-Length: 0) |
shipOrder() |
Validate the shipment |
| OR28 | PUT /api/orders/refund |
refundLines() |
Credit note for shipped lines |
| OR33 | PUT /api/orders/{order_id}/cancel-order-lines |
cancelLines() |
Cancel lines the seller cannot deliver |
| SH21 | GET /api/shipping/carriers |
carriers() |
Carrier codes |
| RE01 | GET /api/reasons |
reasons() |
Cancellation (CANCELATION) and refund (REFUND) reasons |
| RT11 / RT21 | GET /api/returns, PUT /api/returns/accept |
returns(), acceptReturns() |
Return requests |
tests/openapi/mirakl-subset.json holds the schemas of these operations (built by build-subset.mjs); every request the unit tests and the e2e send is validated against it.
Structure
Section titled “Structure”| Class | Role |
|---|---|
Client\BaseClient |
HTTP (cURL, TLS verification, timeouts), JSON, error mapping, masked logging, scope() (company + mode, used in cache keys) |
Client\Api |
The Mirakl Seller API calls, API key in the Authorization header, multipart upload for the import files, API URL check |
Drivers\Mirakl\Driver |
The Mirakl engine: implements Hub\DriverInterface (offers, stock, price, orders, shipping, refunds, returns) without naming an operator |
Drivers\Mirakl\Mapper |
Pure translation hub arrays <-> Mirakl fields (import files, error reports, orders, refund rows, returns); unit tested with the OpenAPI examples |
Drivers\Mirakl\OperatorConfig |
Reads the operator file; OPERATOR names it |
Drivers\Mirakl\Operators\Conrad |
Everything specific to Conrad: markets and currencies (EUR, CHF), product id type, import style, “new” labels, limits, the sources of the facts |
Hub\Orders |
Order import and acceptLines() (the stock decision of OR21) |
Hub\ProductSync, Hub\ProductMap |
Sync runs; rows are claimed (listed P, batch pending) before a driver call; removed mappings become tombstones (listed D) |
Hub\Shipments |
OR23/OR24 push (only when every unit is shipped), cancel, refund (per-order lock, state written before the call) |
Accounts, Secret |
Credentials (test and live) sealed with AES-256-GCM; one account per store owner or per vendor. For Mirakl the account “id” is the API URL and the “secret” the API key |
Config, Access |
Settings, owner mode, who may see and manage what |
Queue, Jobs, JobException |
Job queue: back-off 5 min / 15 min / 1 h / 3 h / 12 h, per-job isolation, overlap lock |
Log |
Event log with masking of secrets and buyer data, filters, CSV, retention |
Behaviour that matters when you change the code
Section titled “Behaviour that matters when you change the code”- Offer import:
Driver::createListings()writes the batch (empty import id, SKUs, line count, send time) before the POST. A 5xx, a timeout or an answer without an id keeps the batch;batchResult()finds the import with OF04 (oldest import since the send time with the same line count that no other batch owns) and polls OF02; after 30 minutes without a match the lines fail with “did not confirm” and the hub sends them again. A 4xx drops the batch and raises. - Hub claim:
ProductSync::createListings()marks the rowsP/pendingbefore calling the driver; rows that staypendingfor an hour are due again (offer imports are keyed by the offer SKU). - Acceptance:
Driver::acceptNew()asksOrders::acceptLines()per line; it answers every line, thenOrders::reserve()writes the accepted lines as an order map row without order.Orders::reserved()(rows without order of the last 14 days) is subtracted byacceptLines()and byListings::state()before the absolute stock import. - Exactly once:
Orders::handle()claims a new order withplacing_atset (INSERT IGNORE), or an existing row withUPDATE ... WHERE placing_at = <old>; a claim younger than 15 minutes is left alone, an older one or a missing row first looks for the CS-Cart order withOrders::findPlaced()(notes key(package <id>), top orders only) and adopts it.installed_at(options, company 0, written at install) is the floor of the first import. The adoption window starts one day before the CHANNEL order’s creation date (never atinstalled_at, which a reinstall rewrites). - Shipment step:
Driver::pushShipment()reads OR11 first; OR23 + OR24 only in SHIPPING; already shipped states return; CANCELED / REFUSED throw 410 andShipments::push()marks the rowshipment_pushed = 'X'; other states throw 409 and the row waits 30 minutes (retry_at). Rows that cannot go out yet getretry_at = nowand the selection is ordered byretry_at, map_id, so the 50-row window rotates. - Lines:
Mapper::order()drops lines in REFUSED / CANCELED state. - Status follow:
StatusMap::keep()(forward-only rank, final statuses C D F I,cs_status= last status the connector set);Returns::isFullReturn()decides whether an accepted return is a “returned” order. - Multi-Vendor parent orders:
Orders::leafOrders()resolves a parent to its child orders;Shipments::push(),fullyShipped(), the status follow, returns, refund and cancel checks use the children. - Buyer e-mails:
Orders::place()setsOrders::$silentaroundfn_place_order(); theget_notification_ruleshook turns the customer notification off while it is set (optionnotify_buyer). - Tombstones:
ProductMap::delete()andfn_kv_conrad_delete_product_post()never drop a live row;ProductSync::apply()deletes it after the stock import confirmed quantity 0. A row removed while its offer is being created keeps its batch: the withdrawal waits for the create result, a create result never sets it back to listed (AND listed = 'P'), a created offer is withdrawn, an offer that was never created goes at once. - Dates: protocol timestamps use
gmdate(// kv-allow:gmdate); documents and numbers use the shop time zone (date()).
Database tables
Section titled “Database tables”| Table | Purpose |
|---|---|
?:kv_conrad_accounts |
Credentials per company (secrets sealed), last connection check |
?:kv_conrad_log |
Log |
?:kv_conrad_jobs |
Queue |
?:kv_conrad_product_map, ?:kv_conrad_order_map, ?:kv_conrad_category_map, ?:kv_conrad_attr_map |
Product, order, category and offer field mapping (the order map carries refund_state, placing_at, hub_status, cs_status) |
?:kv_conrad_returns, ?:kv_conrad_options, ?:kv_conrad_cache, ?:kv_conrad_batches |
Returns, rules, reference data and batch cache, import batches |
Hooks used
Section titled “Hooks used”See init.php (product save and update_product_amount mark the mapping dirty, product delete withdraws the offer, get_notification_rules keeps the buyer e-mail off during an import) and the template hooks in design/backend/templates/addons/kv_conrad/hooks/ (add-on settings page).
Test without the marketplace
Section titled “Test without the marketplace”No test calls a real Mirakl endpoint. The base URL can be overridden for tests in config.local.php (development only); the e2e scripts refuse to run unless it points at 127.0.0.1:
$config['kv_conrad_base_url'] = 'http://127.0.0.1:11931';node tests/openapi/build-subset.mjs <openapi3.json>buildstests/openapi/mirakl-subset.jsonfrom the official Mirakl Seller OpenAPI file.php tests/unit.php,php tests/unit_mirakl.phpandphp tests/unit_hub.phprun the unit tests; the second records every request andnode tests/openapi/check.mjsvalidates them against the OpenAPI file; the third covers the order, shipment, return and listing rules of the hub with a database fixture.node tests/mock-server/server.js 11931starts a local Mirakl stand-in that validates every request against the same file;node tests/e2e/e2e.mjs,node tests/e2e/e2e-owner.mjs(Multi-Vendor owner mode, an order of two vendors) andnode tests/e2e/e2e-vendor.mjsrun the HTTP end-to-end tests against it.
Template overrides
Section titled “Template overrides”Admin templates live in design/backend/templates/addons/kv_conrad/. Do not edit them in a store; they are replaced on update.