mar-pod-b2b / module-b2b-procurement-integration
Procurement protocol contracts, message processing, and adapter infrastructure for Marpod B2B Advanced.
Package info
gitlab.com/mar-pod-b2b/module-b2b-procurement-integration
Type:magento2-module
pkg:composer/mar-pod-b2b/module-b2b-procurement-integration
Requires
- php: ^8.5
- magento/framework: ^103.0
- magento/module-config: ^101.2
- magento/module-webapi: ^100.4
- mar-pod-b2b/module-b2b-company: ^1.0
- mar-pod-b2b/module-b2b-core: ^1.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-08-24 17:33:17 UTC
README
Signed, idempotent, replay-safe cart export and inbound order receipt, with a dead-letter retry queue — the generic mechanics of a procurement/punch-out integration, deliberately without any specific protocol.
Status
Full core engine, cron-driven retry queue, a REST webhook endpoint, and an Admin UI (read-only
sessions/messages log plus the retry queue) are shipped. This module ships no real protocol
adapter — only a non-production null reference adapter used to exercise and test the
mechanics. A concrete vendor/protocol adapter (cXML, OCI, PunchOut, ...) is meant to be a
separate, small package (e.g. Marpod_B2bProcurementCxml) that registers into the adapter pool,
exactly as this module's own plan section describes.
Implemented foundation
ProcurementAdapterInterface(getCode(),exportCart(),parseInboundOrder()) — the extension point. Adapters register viaetc/di.xml's array pool, the same patternMarpod_B2bAdvancedApproval'sConditionMatcherInterfacepool uses;- declarative schema:
marpod_b2b_procurement_session(company, adapter code, a DB-unique idempotency key, status, signed cart payload) andmarpod_b2b_procurement_message(direction outbound/inbound, type, a DB-unique idempotency key — a real, enforced guarantee against replay, not just an application-level check — signature, payload, status); ProcurementCartExportServiceInterface::exportCart()— creates a session and message, signs the payload withProcurementMessageSignerInterface(HMAC-SHA256; the secret is stored encrypted viabackend_model=Encryptedand decrypted withEncryptorInterfacebefore use —ScopeConfigInterface::getValue()alone never decrypts it), and attempts immediate delivery. Delivery failure does not throw back to the caller: the session/message are marked failed and the message is enqueued for retry;ProcurementOrderReceiptServiceInterface::receiveInboundOrder()— verifies the signature (rejects and audits on mismatch), then checks the message idempotency key: a key seen before is treated as a safe replay and returns the existing, unprocessed-again message — real idempotency, not just rejection — with a race-condition fallback (DB unique-constraint violation on concurrent inserts is caught and resolved the same way). A new message is parsed through the session's adapter and the session is marked completed;- retry queue (
marpod_b2b_procurement_queue_item: attempts, max_attempts,next_retry_at, status queued/delivered/dead_letter) with exponential backoff (60s × 2^attempts, capped at 3600s), driven byCron\ProcessRetryQueueevery 5 minutes; exhaustingmax_attemptsmoves the item todead_letterwith an audit entry.requeueDeadLetter()lets an admin manually give a dead-lettered message a fresh attempt count; - every event (export, export failure, inbound receipt, signature rejection, replay detected,
dead-letter) is logged through the existing, frozen
Marpod\B2bCore\Api\Audit\AuditLoggerInterface; - the inbound-order webhook is also exposed as REST (
POST /V1/marpod-procurement/inbound-order) with ananonymousACL resource — an external procurement system has no Magento admin or customer token, so authentication is by HMAC signature alone, the same pattern payment-gateway webhooks use.
Admin UI
Marpod_B2bProcurementIntegration::manage provides two read-only screens: Procurement Sessions
(marpod_b2b_procurement_integration/session/index, with a per-session message log) and
Procurement Retry Queue (marpod_b2b_procurement_integration/queue/index, with a "Retry" action
visible only on dead_letter rows, gated by a separate ::retry ACL resource).
Configuration
Stores > Configuration > Marpod B2B > Procurement Integration:
- Enabled (default No) — gates the retry-queue cron job only; cart export and inbound-order receipt work regardless of this setting;
- Message Signing Secret — left empty by default; there is no safe default for a security
secret, so it must be set explicitly before export or receipt will work (both throw a clear
LocalizedExceptionotherwise); - Default Max Retry Attempts (default 5) — used when a failed outbound message is enqueued.
Requirements
- PHP 8.5
- Magento 2.4.9-compatible framework packages
mar-pod-b2b/module-b2b-core,mar-pod-b2b/module-b2b-company
Installation
Run from the Magento project root:
composer require mar-pod-b2b/module-b2b-procurement-integration:^1.1
bin/magento module:enable Marpod_B2bProcurementIntegration
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:clean
Then set the signing secret in configuration before using cart export or inbound receipt.
Development
The planned scope, dependency rules, cache boundaries, security requirements, and test strategy
are documented in packages/B2B_LIGHT_PLAN.md in the development Magento project (section 9.8).
Unit tests:
vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist packages/module-b2b-procurement-integration/Test/Unit
Limitations
- no real protocol adapter ships in this module — only the
nullreference adapter used for testing the generic mechanics; a real cXML/OCI/PunchOut integration is a separate package; - a single, shared signing secret for all sessions/adapters — no per-trading-partner key support.
Uninstallation
Do not remove the package while another installed module requires it. The
marpod_b2b_procurement_* tables contain business/audit data and are retained when the module is
disabled or its package is removed. Disable the cron job (or the module) before removing to stop
new retry attempts against messages that will no longer exist.