Enterprise Service

enterprise-service is a Quarkus service that owns cXML PunchOut/PurchaseOrder connectors and ERP integration adapters. Multi-store organizational hierarchy is owned by store-service. PunchOut cart sessions are held in cart-service under a dedicated session type. cXML buyer credentials and ERP API keys are stored in Vault.

cXML PunchOut#

PunchOut enables business buyers to browse the SS3 catalog from within their eProcurement system (SAP Ariba, Coupa, Oracle, etc.) and return a populated cart for procurement approval.

Flow#

  1. The buyer’s procurement system sends a cXML PunchOut Setup Request to POST /cxml/punchout/setup.
  2. enterprise-service validates the request using the buyer’s shared secret (HMAC verification against cxml_buyer_configs).
  3. A PUNCHOUT cart session is created in cart-service. A session token is stored in Redis with a 4-hour TTL.
  4. A cXML PunchOut Setup Response is returned containing a tokenized storefront URL.
  5. The buyer browses the storefront. The PUNCHOUT session type signals the storefront to render in PunchOut mode.
  6. When the buyer clicks “Return Cart”, the storefront calls CompletePunchOut. enterprise-service builds a cXML OrderMessage from the cart contents and returns it to the storefront, which POSTs it back to the buyer’s BrowserFormPost URL.
  7. The PunchOut session is expired. No SS3 order is created at this stage.

PunchOut sessions are held entirely in Redis (key: punchout_session:{token}, TTL = 4 hours). There is no database table for in-flight sessions.

cXML Purchase Orders#

After procurement approval, the buyer’s system sends a cXML PurchaseOrder to POST /cxml/order.

enterprise-service:

  1. Parses and authenticates the cXML document (shared secret)
  2. Inserts a purchase_orders record with status = RECEIVED
  3. Maps buyer SupplierPartID values to SS3 catalog variant IDs using part_id_mappings
  4. Calls order-service CreateOrder via gRPC with the translated line items
  5. Updates the PO record with status = ACCEPTED and the resulting ss3_order_id

Unmapped part IDs or order creation failures set status = REJECTED and return a cXML error response to the buyer.

ERP Integration#

ERP Adapter Interface#

interface ErpConnector {
    void pushOrder(Order order);
    void pushInvoice(Invoice invoice);
    void pushCustomer(Customer customer);

    List<InventoryUpdate> pullInventory(String cursor);
    List<ProductUpdate>   pullProducts(String cursor);
    List<PurchaseOrder>   pullPurchaseOrders(String cursor);

    String connectorType();
}

Built-in Adapters#

AdapterNotes
SapConnectorSAP S/4HANA OData APIs
NetSuiteConnectorNetSuite REST Record API
DynamicsConnectorMicrosoft Dynamics 365 Finance & Operations API
OdooConnectorOdoo XML-RPC / JSON-RPC API

Outbound Push#

Triggered by Kafka events. When order.placed, order.invoiced, or customer.created is consumed, the active ERP connector for the store is resolved and the relevant push method is called. Failures are retried with exponential backoff (3 attempts) before being logged as errors.

Inbound Pull (Scheduled)#

A scheduled job runs at a configurable interval per ERP config. It calls the pull method for each configured entity type (INVENTORY, PRODUCT, PURCHASE_ORDER), passes the stored delta cursor, and fans the results out to the appropriate downstream service:

Pull entityTarget service
INVENTORYinventory-service BulkUpdateStock (gRPC)
PRODUCTcatalog-service BulkUpdateProducts (gRPC)
PURCHASE_ORDERInserted as enterprise purchase orders

The cursor is updated atomically after each successful pull batch.

Data Model#

cxml_buyer_configsconfig_id UUID PK, store_id, name, network_id, domain, is_active, settings JSONB (allowed IPs, timeout overrides). Shared secret in Vault.

purchase_orderspo_id UUID PK, store_id, buyer_po_number, buyer_network_id, ss3_order_id (after successful translation), cxml_payload TEXT (archived), status ENUM (RECEIVED / PROCESSING / ACCEPTED / REJECTED), received_at, processed_at, error_message.

part_id_mappingsmapping_id UUID PK, store_id, buyer_part_id, ss3_variant_id, is_active. UNIQUE on (store_id, buyer_part_id).

erp_configsconfig_id UUID PK, store_id, erp_type, display_name, is_active, sync_interval_minutes, settings JSONB (non-sensitive: company ID, API endpoint, environment).

erp_sync_cursorscursor_id UUID PK, config_id FK, entity_type, last_synced_at, last_cursor, error_count, last_error.

erp_sync_log — per-run record of direction, entity type, record count, status, and any error summary.

Interfaces#

HTTP (cXML)#

POST /cxml/punchout/setup    — cXML PunchOut Setup Request/Response
POST /cxml/order             — cXML PurchaseOrder receipt

The gateway routes /cxml/* without JWT validation. Authentication is handled by enterprise-service via cXML HMAC shared secrets.

gRPC#

MethodCallerPurpose
CompletePunchOut(cartId, sessionToken)storefrontBuild and return cXML OrderMessage
GetPurchaseOrder(poId)admin toolingRead a received PO record

Kafka Topics#

Consumed#

TopicAction
order.placedPush order to ERP via ErpConnector
order.invoicedPush invoice to ERP
customer.createdPush customer record to ERP if sync enabled

Published#

TopicPayload
enterprise.po.receivedpoId, storeId, buyerPoNumber
enterprise.po.acceptedpoId, storeId, ss3OrderId
enterprise.po.rejectedpoId, storeId, reason
erp.sync.completedconfigId, storeId, entityType, recordCount, status

Vault Configuration#

Key pathPurpose
ss3/kv/enterprise-service/db.urlPostgreSQL JDBC URL
ss3/kv/enterprise-service/db.usernameDatabase username
ss3/kv/enterprise-service/db.passwordDatabase password
ss3/kv/enterprise-service/cxml/{config_id}/shared-secretPer-buyer cXML HMAC secret
ss3/kv/enterprise-service/erp/{config_id}/api-keyPer-ERP API key
ss3/kv/enterprise-service/erp/{config_id}/client-secretPer-ERP OAuth client secret
ss3/kv/shared/Shared Kafka bootstrap, OTel config, Redis URL