Search by

mar-pod-b2b / module-b2b-procurement-integration

Procurement protocol contracts, message processing, and adapter infrastructure for Marpod B2B Advanced.

Maintainers

Package info

gitlab.com/mar-pod-b2b/module-b2b-procurement-integration

Issues

Type:magento2-module

pkg:composer/mar-pod-b2b/module-b2b-procurement-integration

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

1.1.7 2026-08-24 19:33 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 via etc/di.xml's array pool, the same pattern Marpod_B2bAdvancedApproval's ConditionMatcherInterface pool uses;
  • declarative schema: marpod_b2b_procurement_session (company, adapter code, a DB-unique idempotency key, status, signed cart payload) and marpod_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 with ProcurementMessageSignerInterface (HMAC-SHA256; the secret is stored encrypted via backend_model=Encrypted and decrypted with EncryptorInterface before 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 by Cron\ProcessRetryQueue every 5 minutes; exhausting max_attempts moves the item to dead_letter with 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 an anonymous ACL 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 LocalizedException otherwise);
  • 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 null reference 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.