---
# === IDENTITY ===
id: business/erp-integration/cpq-to-erp/2026
canonical_question: "How do you integrate Salesforce CPQ or Oracle CPQ with ERP for complex order creation?"
aliases:
  - "How do I sync CPQ quotes to ERP sales orders automatically?"
  - "Salesforce CPQ to SAP NetSuite D365 order integration"
  - "Oracle CPQ to ERP Cloud order creation integration patterns"
  - "CPQ-to-ERP quote-to-order data mapping and error handling"
entity_type: erp_integration
domain: business > erp-integration > cpq-to-erp
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Salesforce CPQ / Revenue Cloud"
    vendor: "Salesforce"
    version: "Revenue Cloud (Spring 2026) / CPQ managed package v66.0"
    edition: "Enterprise, Unlimited"
    deployment: cloud
    api_surface: "REST API v66.0, Bulk API 2.0, Platform Events"
  - name: "Oracle CPQ Cloud"
    vendor: "Oracle"
    version: "REST API v19 (24D release)"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST API v19, Commerce API, Configuration API"
  - name: "SAP S/4HANA"
    vendor: "SAP"
    version: "2408"
    edition: "Cloud, On-Premise"
    deployment: hybrid
    api_surface: "OData v4, BAPI, IDoc"
  - name: "Oracle NetSuite"
    vendor: "Oracle"
    version: "2024.2+"
    edition: "SuiteCloud Plus, Premium"
    deployment: cloud
    api_surface: "SuiteTalk REST, SOAP, RESTlet, SuiteQL"
  - name: "Microsoft Dynamics 365 Finance & SCM"
    vendor: "Microsoft"
    version: "10.0.39+"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "OData v4, Dual-write, Business Events"

# === VERIFICATION ===
last_verified: 2026-03-07
confidence: 0.83
version: 1.0
first_published: 2026-03-07

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "Salesforce CPQ sunset for new customers (March 2025) — Revenue Cloud is the successor; no CPQ renewals beyond August 2026"
  next_review: 2026-09-03
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Salesforce CPQ is sunset for new customers (March 2025); no renewals beyond August 2026 — new implementations must use Revenue Cloud"
  - "Salesforce governor limits: 100 SOQL queries per transaction — complex CPQ bundles with cascading triggers can exhaust this during order generation"
  - "Oracle CPQ REST API v19 has no published hard rate limit but throttles above ~50 concurrent requests — contact Oracle for tenant-specific quotas"
  - "Order creation must be idempotent — duplicate order insertion is the #1 CPQ-to-ERP defect in production (30-40% of integration incidents)"
  - "CPQ pricing and ERP pricing must be synchronized — dual-maintenance causes 15-40% of billing errors in multi-system architectures"
  - "NetSuite SuiteTalk REST: default 5 concurrent requests per account (10 for SuiteCloud Plus, 25 with concurrency license)"
  - "SAP S/4HANA OData requires x-csrf-token fetch before every write operation — stateful sessions only for POST/PATCH"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "You need the full Quote-to-Cash flow including billing and revenue recognition"
    use_instead: "business/erp-integration/quote-to-cash-integration/2026"
  - condition: "You need single-system Salesforce CPQ API reference without ERP context"
    use_instead: "business/erp-integration/salesforce-rest-api-capabilities/2026"
  - condition: "You need Oracle CPQ configuration only, no ERP integration"
    use_instead: "business/erp-integration/oracle-erp-cloud-rest-api-capabilities/2026"
  - condition: "You need Order-to-Cash (starts at order, not quote)"
    use_instead: "business/erp-integration/order-to-cash-integration/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: cpq_system
    question: "Which CPQ system are you using?"
    type: choice
    options:
      - "Salesforce CPQ (managed package)"
      - "Salesforce Revenue Cloud (native)"
      - "Oracle CPQ Cloud"
      - "SAP CPQ"
      - "Other (DealHub, Conga, Apttus)"
  - key: erp_system
    question: "Which ERP receives the order?"
    type: choice
    options:
      - "SAP S/4HANA"
      - "Oracle NetSuite"
      - "Microsoft Dynamics 365 F&SCM"
      - "Oracle ERP Cloud"
      - "Other"
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "real-time sync (individual orders, <1s latency)"
      - "batch/bulk (scheduled, high volume)"
      - "event-driven (Platform Events, CDC, webhooks)"
  - key: data_volume
    question: "What's your daily quote/order volume?"
    type: choice
    options:
      - "< 500 orders/day"
      - "500-5,000 orders/day"
      - "> 5,000 orders/day"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "CPQ → ERP (order creation)"
      - "ERP → CPQ (fulfillment/inventory status back)"
      - "bidirectional sync"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/cpq-to-erp/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-07)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "business/erp-integration/salesforce-rest-api-capabilities/2026"
      label: "Salesforce REST API capabilities and rate limits"
    - id: "business/erp-integration/oracle-erp-cloud-rest-api-capabilities/2026"
      label: "Oracle ERP Cloud REST API capabilities"
  related_to:
    - id: "business/erp-integration/quote-to-cash-integration/2026"
      label: "Full Quote-to-Cash integration playbook (CPQ to billing to rev rec)"
    - id: "business/erp-integration/order-to-cash-integration/2026"
      label: "Order-to-Cash integration (starts at order, not quote)"
  solves:
    - id: "business/erp-integration/salesforce-netsuite-integration/2026"
      label: "Salesforce-NetSuite integration playbook"
  alternative_to:
    - id: "business/erp-integration/ecommerce-to-erp/2026"
      label: "eCommerce-to-ERP order integration (B2C, not CPQ)"
  often_confused_with:
    - id: "business/erp-integration/subscription-billing-integration/2026"
      label: "Subscription billing integration — billing-centric, not quote-centric"

# === SOURCES ===
sources:
  - id: src1
    title: "Get Started with Salesforce CPQ API"
    author: Salesforce
    url: https://developer.salesforce.com/docs/atlas.en-us.cpq_dev_api.meta/cpq_dev_api/cpq_api_get_started.htm
    type: official_docs
    published: 2026-01-15
    reliability: authoritative
  - id: src2
    title: "REST API Services for Oracle CPQ — Table of Contents"
    author: Oracle
    url: https://docs.oracle.com/en/cloud/saas/configure-price-quote/cxcpq/toc.htm
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src3
    title: "Oracle CPQ Integration Patterns"
    author: Oracle A-Team
    url: https://www.ateam-oracle.com/post/oracle-cpq-integration-patterns
    type: technical_blog
    published: 2025-04-10
    reliability: high
  - id: src4
    title: "Integrating CPQ with ERP: Creating a Seamless Pricing and Quoting Ecosystem"
    author: Mobileforce
    url: https://mobileforce.ai/blog/integrating-cpq-with-erp-creating-a-seamless-pricing-and-quoting-ecosystem/
    type: technical_blog
    published: 2025-08-15
    reliability: moderate_high
  - id: src5
    title: "Salesforce CPQ: Order Generation"
    author: Milo Massimo
    url: https://milomassimo.com/Salesforce-CPQ-Orders.html
    type: community_resource
    published: 2025-06-20
    reliability: moderate_high
  - id: src6
    title: "Best Practices for Integrating CPQ Software with ERP Systems"
    author: CPQ Integrations
    url: https://cpq-integrations.com/blog/best-practices-for-integrating-cpq-software-with-erp-systems/
    type: technical_blog
    published: 2025-06-26
    reliability: moderate_high
  - id: src7
    title: "CPQ + NetSuite Integration for Efficient Sales Operations"
    author: CPQ Integrations
    url: https://cpq-integrations.com/blog/cpq-netsuite-integration-for-efficient-sales-operations/
    type: technical_blog
    published: 2025-07-10
    reliability: moderate_high
---

# CPQ-to-ERP Integration: Complex Order Creation from Salesforce CPQ and Oracle CPQ

## TL;DR

- **Bottom line**: CPQ-to-ERP integration converts approved quotes into ERP sales orders. Salesforce CPQ uses the `SBQQ__Quote__c.Ordered` checkbox or Revenue Cloud's Order Management API; Oracle CPQ uses Commerce REST API actions (`order_start`, `order_update`) with OIC or direct REST calls to the target ERP. [src1, src2]
- **Key limit**: Salesforce governor limits (100 SOQL queries per transaction) are the binding constraint — complex CPQ bundles with nested products can exhaust the limit during automated order generation. Oracle CPQ throttles above ~50 concurrent REST requests. [src1, src3]
- **Watch out for**: Idempotency — duplicate order creation is the #1 production defect (30-40% of CPQ-to-ERP incidents). Always include an external reference ID (quote number + version) as a unique key on the ERP order. [src4, src6]
- **Best for**: B2B organizations with complex product configurations, tiered pricing, discount approval workflows, and multi-line orders that must flow into SAP, NetSuite, D365, or Oracle ERP Cloud as structured sales orders. [src6]
- **Authentication**: Salesforce uses OAuth 2.0 JWT Bearer for server-to-server; Oracle CPQ uses OAuth 2.0 or basic auth with session tokens; target ERPs use their native auth (OAuth 2.0 for NetSuite/D365, x-csrf-token + OAuth for SAP). [src1, src2]

## System Profile

This integration playbook covers the CPQ-to-ERP order creation segment of the quote-to-cash cycle. It addresses two major CPQ platforms — Salesforce CPQ (including Revenue Cloud) and Oracle CPQ Cloud — and four major target ERPs: SAP S/4HANA, Oracle NetSuite, Microsoft Dynamics 365 Finance & SCM, and Oracle ERP Cloud. The focus is specifically on converting approved, fully-priced quotes into structured sales orders in the ERP, including line items, pricing, discounts, shipping, and tax classification.

This card does NOT cover: downstream billing/invoicing (see subscription-billing-integration), revenue recognition (see revenue-recognition-asc606-ifrs15), or the full quote-to-cash lifecycle (see quote-to-cash-integration). For eCommerce order integration (B2C), see ecommerce-to-erp.

| System | Role | API Surface | Direction |
|---|---|---|---|
| Salesforce CPQ / Revenue Cloud | CPQ — quote authoring, pricing, approvals | REST API v66.0, SBQQ ServiceRouter, Platform Events | Outbound (source) |
| Oracle CPQ Cloud | CPQ — configure, price, quote | REST API v19, Commerce API, Configuration API | Outbound (source) |
| SAP S/4HANA | ERP — sales order management | OData v4 (A_SalesOrder), BAPI (BAPI_SALESORDER_CREATEFROMDAT2), IDoc (ORDERS05) | Inbound (target) |
| Oracle NetSuite | ERP — sales order management | SuiteTalk REST/SOAP, RESTlet | Inbound (target) |
| Microsoft Dynamics 365 F&SCM | ERP — sales order management | OData v4 (SalesOrderHeaders), Dual-write, Data Entities | Inbound (target) |
| iPaaS (MuleSoft / Boomi / Celigo / OIC) | Middleware — orchestration, transformation, error handling | Pre-built connectors | Orchestrator |

## API Surfaces & Capabilities

### Salesforce CPQ / Revenue Cloud APIs

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| SBQQ ServiceRouter (Apex) | Apex REST / Remoting | Quote calculation, save, read model | Per-transaction governor limits | 100 SOQL/txn, 150 DML/txn | Yes | No |
| REST API v66.0 (SBQQ__Quote__c) | HTTPS/JSON | CRUD on quote/quote line objects | 200 composite subrequests | 100K calls/24h (Enterprise) | Yes | No |
| Bulk API 2.0 | HTTPS/CSV | Mass quote line export | 150M per file | 15K batches/24h | No | Yes |
| Platform Events | Bayeux/CometD | Quote status change notifications | N/A | Edition-dependent | Yes | N/A |
| Revenue Cloud Order Management | HTTPS/JSON | Native order creation (Revenue Cloud) | Per-transaction limits | Shared with REST API | Yes | No |

[src1]

### Oracle CPQ Cloud APIs

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| Commerce REST API v19 | HTTPS/JSON | Transaction CRUD, quote → order actions | 50 per collection query | Throttled (~50 concurrent) | Yes | No |
| Configuration REST API | HTTPS/JSON | Product configuration, BOM | Per-model limits | Shared with Commerce | Yes | No |
| Bulk Data Services (24D+) | HTTPS/CSV | Mass transaction export/import | Large batch | Separate quota | No | Yes |
| OIC Integration (pub/sub) | HTTPS/JSON + messaging | Event-driven ERP sync | Queue-based | OIC limits apply | Near-real-time | Yes |

[src2, src3]

## Rate Limits & Quotas

### Per-Request Limits

| Limit Type | Value | System | Notes |
|---|---|---|---|
| Max composite subrequests | 25 | Salesforce Composite API | All-or-nothing by default; use `allOrNone: false` for partial success |
| Max SOQL query results | 2,000 per page | Salesforce REST API | Use `queryMore` for pagination; CPQ quotes with 200+ lines hit this |
| Max request body size | 50 MB | Salesforce REST API | Complex bundle quotes with extended attributes can approach this |
| Max collection query results | 50 per page | Oracle CPQ REST API | Use `offset` and `limit` for pagination |
| Max bulk file size | 150 MB | Salesforce Bulk API 2.0 | Split larger exports |
| Max OData batch subrequests | 1,000 | SAP S/4HANA OData | Per $batch request |
| Max SuiteTalk page size | 1,000 records | NetSuite SuiteTalk REST | Default 100, configurable up to 1,000 |

[src1, src2]

### Rolling / Daily Limits

| Limit Type | Value | Window | System |
|---|---|---|---|
| API calls | 100,000 (Enterprise) / 5M (Unlimited) | 24h rolling | Salesforce |
| Bulk API batches | 15,000 | 24h rolling | Salesforce |
| Concurrent long-running requests | 25 | Per org | Salesforce |
| Concurrent REST requests | 5 (default) / 10 (SuiteCloud Plus) | Per account | NetSuite |
| OData requests | Fair-use / throttled | Per tenant | SAP S/4HANA Cloud |
| Dynamics 365 OData | 6,000 requests/5 min per user | 5-min rolling | Microsoft D365 |
| Oracle CPQ concurrent requests | ~50 (throttled, no published hard limit) | Per tenant | Oracle CPQ |

[src1, src2, src4]

### Transaction / Governor Limits (Salesforce — Critical for CPQ)

| Limit Type | Per-Transaction Value | Notes |
|---|---|---|
| SOQL queries | 100 | Includes queries from triggers — CPQ order generation triggers can cascade |
| DML statements | 150 | Each insert/update/delete counts as 1, regardless of record count |
| Callouts | 100 | HTTP requests to external services within a single Apex transaction |
| CPU time | 10,000 ms (sync), 60,000 ms (async) | Exceeded = transaction abort; complex bundles are the main risk |
| Heap size | 6 MB (sync), 12 MB (async) | Large quote models with 500+ lines can hit this |
| Future calls | 50 per transaction | Limits parallel async processing of order lines |

[src1, src5]

## Authentication

| System | Flow | Use When | Token Lifetime | Notes |
|---|---|---|---|---|
| Salesforce | OAuth 2.0 JWT Bearer | Server-to-server CPQ-to-ERP middleware | Session timeout (default 2h) | Recommended for all integrations; requires connected app + digital certificate |
| Salesforce | OAuth 2.0 Web Server | User-context operations, manual order push | Access: 2h, Refresh: until revoked | Requires callback URL |
| Oracle CPQ | OAuth 2.0 Client Credentials | Server-to-server via OIC or middleware | Configurable (default 1h) | Standard for OIC integrations |
| Oracle CPQ | Basic Auth + session | Legacy integrations, testing | Session-based | NOT recommended for production — no MFA support |
| SAP S/4HANA | OAuth 2.0 + x-csrf-token | All write operations (order creation) | Token: 30 min, csrf: per-session | Must fetch csrf token before every POST/PATCH |
| NetSuite | Token-Based Authentication (TBA) | Server-to-server integration | Does not expire | Recommended; requires integration record + token pair |
| Dynamics 365 | OAuth 2.0 via Azure AD | All API access | Access: 1h, Refresh: 90 days | Register app in Azure AD; use client credentials flow |

[src1, src2, src3]

### Authentication Gotchas

- Salesforce JWT bearer flow requires a connected app with a digital certificate — self-signed works for dev, CA-signed recommended for prod. The integration user must be pre-authorized in the connected app. [src1]
- Oracle CPQ basic auth is being phased out in favor of OAuth 2.0 — plan migration before 2027. [src2]
- SAP x-csrf-token expires within the session — if your middleware pools HTTP connections, each new connection needs a fresh token fetch (GET with `x-csrf-token: fetch` header). [src3]
- NetSuite TBA tokens do not expire but can be revoked by administrators — build token refresh/re-authorization alerting into your middleware. [src7]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- Salesforce CPQ is sunset for new customers as of March 2025; no renewals beyond August 2026 — all new implementations must use Salesforce Revenue Cloud. Existing CPQ customers can continue using the managed package but should plan migration. [src1, src6]
- CPQ pricing rules and ERP pricing must match exactly — discrepancies between CPQ-calculated prices and ERP order prices cause invoice disputes and revenue leakage (15-40% of billing errors trace to this). [src4, src6]
- The `Ordered` checkbox on `SBQQ__Quote__c` can only be set ONCE — subsequent attempts are ignored. The quote must have `Primary = true` and (if configured) `Status = Approved`. [src5]
- Oracle CPQ Commerce actions (`order_start`, `submit_t`) execute server-side BML scripts that can modify transaction data — the order payload reaching the ERP may differ from the quote visible in the UI. [src2, src3]
- NetSuite sales order creation requires matching internal IDs for customer, item, location, and subsidiary — external ID lookups add latency and can fail on first-time customers. [src7]
- SAP sales order creation via `BAPI_SALESORDER_CREATEFROMDAT2` requires COMMIT_WORK call — the BAPI does not auto-commit, and missing this step is a common silent failure. [src4]
- Multi-currency orders require exchange rate synchronization between CPQ and ERP at the time of order creation — stale rates cause margin errors. [src4]

## Integration Pattern Decision Tree

```
START — Need to create ERP orders from CPQ quotes
├── Which CPQ platform?
│   ├── Salesforce CPQ (managed package)
│   │   ├── Order volume < 500/day?
│   │   │   ├── YES → SBQQ Ordered checkbox + trigger-based callout to ERP
│   │   │   └── NO → Platform Events + middleware (MuleSoft/Boomi) → ERP
│   │   └── Need real-time order creation?
│   │       ├── YES → Apex trigger on Order object → callout to ERP REST API
│   │       └── NO → Scheduled batch: query new Orders → bulk push to ERP
│   ├── Salesforce Revenue Cloud
│   │   ├── Native to Salesforce ecosystem?
│   │   │   ├── YES → Revenue Cloud Order Management → Salesforce Order object
│   │   │   └── NO → Order Management API → middleware → ERP
│   │   └── Need to sync with external ERP?
│   │       ├── YES → Platform Events on Order creation → middleware → ERP API
│   │       └── NO → Stay within Salesforce ecosystem
│   └── Oracle CPQ Cloud
│       ├── Target ERP is Oracle ERP Cloud / Fusion?
│       │   ├── YES → Native OIC integration (pub/sub pattern) → Order Management
│       │   └── NO ↓
│       ├── Using Oracle Integration Cloud (OIC)?
│       │   ├── YES → CPQ publishes to OIC queue → OIC transforms → ERP API
│       │   └── NO → CPQ Commerce REST → custom middleware → ERP API
│       └── Need real-time?
│           ├── YES → Commerce action triggers → synchronous integration call
│           └── NO → Batch export via Bulk Data Services → scheduled import
├── Which target ERP?
│   ├── SAP S/4HANA → OData v4 (A_SalesOrder) or BAPI via RFC
│   ├── Oracle NetSuite → SuiteTalk REST (POST /salesOrder) or RESTlet
│   ├── Microsoft D365 → OData v4 (SalesOrderHeaders data entity)
│   └── Oracle ERP Cloud → REST API (fscmRestApi/resources/salesOrders)
└── Error strategy?
    ├── Zero-loss → idempotent key (quote number + version) + dead letter queue
    └── Best-effort → retry 3x with exponential backoff, then alert
```

## Quick Reference

### CPQ-to-ERP Order Creation Flow

| Step | Source System | Action | Target System | Data Objects | Failure Handling |
|---|---|---|---|---|---|
| 1 | Salesforce CPQ | Quote approved → `Ordered` checkbox set → Order + Order Products auto-created | Salesforce (internal) | Order, OrderItem | Governor limit check; if exceeded, order generation fails silently |
| 2 | Salesforce | Platform Event fired on Order creation (or trigger callout) | Middleware (MuleSoft/Boomi/OIC) | Order payload (JSON) | Retry with idempotency key |
| 3 | Middleware | Transform Salesforce Order → ERP sales order format | Target ERP | Mapped sales order + line items | Transformation errors → dead letter queue |
| 4a | Middleware | POST sales order to SAP | SAP S/4HANA | A_SalesOrder + A_SalesOrderItem | 429 → backoff; BAPI_RETURN errors → log + alert |
| 4b | Middleware | POST sales order to NetSuite | Oracle NetSuite | salesOrder + item lines | Concurrency limit (5-10) → queue; validation errors → DLQ |
| 4c | Middleware | POST sales order to D365 | Microsoft D365 | SalesOrderHeaders + SalesOrderLines | OData error → parse response, retry transient |
| 5 | Target ERP | Return order ID + confirmation | Middleware → Salesforce | ERP Order Number | Write-back to Salesforce Order record |
| 6 | Middleware | Update Salesforce Order with ERP order number | Salesforce | Order.ExternalId__c | Retry on lock contention |

[src4, src6]

### Oracle CPQ-to-ERP Order Creation Flow

| Step | Source System | Action | Target System | Data Objects | Failure Handling |
|---|---|---|---|---|---|
| 1 | Oracle CPQ | Quote submitted → approval workflow → `order_start` action | Oracle CPQ (internal) | Transaction (Commerce doc) | BML script validation; errors block submission |
| 2 | Oracle CPQ | Commerce action publishes event to OIC queue | Oracle Integration Cloud | Transaction payload (JSON) | OIC error handling; retry policies on queue |
| 3 | OIC/Middleware | Transform CPQ transaction → ERP order format | Target ERP | Mapped sales order + line items | Transform failures → error hospital |
| 4 | Middleware | POST to ERP order creation API | Target ERP | Sales order | Same as Salesforce flow (steps 4a-4c above) |
| 5 | Target ERP | Return order ID + status | OIC → Oracle CPQ | Order number | Write-back via `order_update` action |

[src2, src3]

## Step-by-Step Integration Guide

### 1. Configure Salesforce CPQ Order Generation

Set up CPQ order generation so approved quotes automatically produce Order and Order Product records. The `Ordered` checkbox on `SBQQ__Quote__c` triggers this process. The quote must be `Primary = true` and (if configured) `Status = Approved`. [src1, src5]

```apex
// Salesforce Apex: Check quote readiness and trigger order generation
// Input:  Quote ID (SBQQ__Quote__c)
// Output: Order record created with Order Products

// Option 1: Declaratively — simply set the Ordered checkbox:
// SBQQ__Quote__c.SBQQ__Ordered__c = true (via UI, Flow, or Process Builder)

// Option 2: Programmatically via Apex trigger or Flow:
SBQQ__Quote__c quote = [
    SELECT Id, SBQQ__Ordered__c, SBQQ__Primary__c, SBQQ__Status__c,
           SBQQ__Account__c, SBQQ__Opportunity2__c
    FROM SBQQ__Quote__c
    WHERE Id = :quoteId
];

// Verify prerequisites
System.assert(quote.SBQQ__Primary__c == true, 'Quote must be Primary');
System.assert(quote.SBQQ__Status__c == 'Approved', 'Quote must be Approved');

// Setting Ordered = true triggers CPQ order generation
quote.SBQQ__Ordered__c = true;
update quote;
// CPQ package trigger creates Order + OrderItem records automatically
```

**Verify**: Query `SELECT Id, OrderNumber, Status FROM Order WHERE SBQQ__Quote__c = :quoteId` -> expect 1 Order record with Status = 'Draft'.

### 2. Capture Order Creation Event and Send to Middleware

Use a Platform Event or Apex trigger to detect new Order records and push them to your integration middleware. [src1]

```apex
// Salesforce Apex Trigger: Fire Platform Event on Order creation
trigger OrderCreatedTrigger on Order (after insert) {
    List<CPQ_Order_Event__e> events = new List<CPQ_Order_Event__e>();

    for (Order ord : Trigger.new) {
        // Only fire for CPQ-generated orders (have a linked quote)
        if (ord.SBQQ__Quote__c != null) {
            events.add(new CPQ_Order_Event__e(
                Order_Id__c = ord.Id,
                Quote_Id__c = ord.SBQQ__Quote__c,
                Account_Id__c = ord.AccountId,
                Total_Amount__c = ord.TotalAmount
            ));
        }
    }

    if (!events.isEmpty()) {
        List<Database.SaveResult> results = EventBus.publish(events);
        for (Database.SaveResult sr : results) {
            if (!sr.isSuccess()) {
                System.debug('Platform Event publish failed: ' + sr.getErrors());
            }
        }
    }
}
```

**Verify**: Check Platform Event in Event Monitoring: Setup -> Platform Events -> CPQ_Order_Event__e -> Subscriptions.

### 3. Transform Salesforce Order to ERP Sales Order Format

In your middleware, query the full Order with line items and map to the target ERP's sales order schema. [src4, src6]

```javascript
// Node.js (MuleSoft/Boomi/custom middleware): Transform SF Order → SAP Sales Order
// Input:  Salesforce Order ID from Platform Event
// Output: SAP S/4HANA A_SalesOrder payload

async function transformSFOrderToSAP(sfOrderId, sfClient) {
  // 1. Query full order with line items from Salesforce
  const order = await sfClient.query(`
    SELECT Id, OrderNumber, Account.Name, Account.SAP_Customer_Number__c,
           SBQQ__Quote__c, ShipToAddress, BillToAddress, CurrencyIsoCode,
           (SELECT Id, Product2.ProductCode, Product2.SAP_Material_Number__c,
                   Quantity, UnitPrice, SBQQ__QuoteLine__r.SBQQ__Discount__c,
                   SBQQ__QuoteLine__r.SBQQ__NetPrice__c,
                   ServiceDate, EndDate
            FROM OrderItems)
    FROM Order WHERE Id = '${sfOrderId}'
  `);

  // 2. Map to SAP S/4HANA A_SalesOrder format
  const sapOrder = {
    SalesOrderType: 'OR',  // Standard order
    SalesOrganization: '1000',
    DistributionChannel: '10',
    OrganizationDivision: '00',
    SoldToParty: order.records[0].Account.SAP_Customer_Number__c,
    PurchaseOrderByCustomer: order.records[0].OrderNumber, // Idempotency key
    TransactionCurrency: order.records[0].CurrencyIsoCode,
    to_Item: {
      results: order.records[0].OrderItems.records.map((item, idx) => ({
        SalesOrderItem: String((idx + 1) * 10).padStart(6, '0'),
        Material: item.Product2.SAP_Material_Number__c,
        RequestedQuantity: String(item.Quantity),
        RequestedQuantityUnit: 'EA',
        NetPriceAmount: String(item.SBQQ__QuoteLine__r.SBQQ__NetPrice__c),
        NetPriceQuantity: '1',
        RequestedDeliveryDate: item.ServiceDate
      }))
    }
  };

  return sapOrder;
}
```

**Verify**: Log the transformed payload and validate all required SAP fields are populated (SoldToParty, Material, RequestedQuantity are mandatory).

### 4. Create Sales Order in Target ERP

Post the transformed order to the ERP's order creation API. This example shows SAP S/4HANA; patterns for NetSuite and D365 follow in Code Examples. [src4]

```bash
# cURL: Create sales order in SAP S/4HANA via OData v4
# Step 1: Fetch CSRF token
curl -s -X GET \
  "https://{sap-host}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder" \
  -H "Authorization: Bearer ${SAP_TOKEN}" \
  -H "x-csrf-token: fetch" \
  -D - -o /dev/null 2>&1 | grep "x-csrf-token"

# Step 2: POST the sales order
curl -X POST \
  "https://{sap-host}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder" \
  -H "Authorization: Bearer ${SAP_TOKEN}" \
  -H "x-csrf-token: ${CSRF_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "SalesOrderType": "OR",
    "SalesOrganization": "1000",
    "DistributionChannel": "10",
    "OrganizationDivision": "00",
    "SoldToParty": "CUST001",
    "PurchaseOrderByCustomer": "SF-Q-00123-v1",
    "to_Item": {
      "results": [{
        "SalesOrderItem": "000010",
        "Material": "MAT-001",
        "RequestedQuantity": "5",
        "RequestedQuantityUnit": "EA"
      }]
    }
  }'
```

**Verify**: Response HTTP 201 with `SalesOrder` number in response body. Query: `GET /A_SalesOrder('{orderNumber}')` returns the created order.

### 5. Write ERP Order Number Back to Salesforce

After successful ERP order creation, update the Salesforce Order record with the ERP order number for cross-reference. [src6]

```javascript
// Node.js: Write-back ERP order number to Salesforce
async function writeBackERPOrderNumber(sfClient, sfOrderId, erpOrderNumber, erpSystem) {
  try {
    await sfClient.sobject('Order').update({
      Id: sfOrderId,
      ERP_Order_Number__c: erpOrderNumber,
      ERP_System__c: erpSystem,
      ERP_Sync_Status__c: 'Synced',
      ERP_Sync_Timestamp__c: new Date().toISOString()
    });
    console.log(`Write-back success: SF ${sfOrderId} → ${erpSystem} ${erpOrderNumber}`);
  } catch (err) {
    // UNABLE_TO_LOCK_ROW is common — retry with jitter
    if (err.errorCode === 'UNABLE_TO_LOCK_ROW') {
      await sleep(Math.random() * 2000 + 500);
      return writeBackERPOrderNumber(sfClient, sfOrderId, erpOrderNumber, erpSystem);
    }
    throw err;
  }
}
```

**Verify**: `SELECT ERP_Order_Number__c, ERP_Sync_Status__c FROM Order WHERE Id = :sfOrderId` returns the ERP order number.

### 6. Handle Oracle CPQ-to-ERP via OIC

For Oracle CPQ, use the Commerce REST API's order actions to trigger order creation, then route through Oracle Integration Cloud. [src2, src3]

```bash
# Oracle CPQ: Create order from approved transaction
# Step 1: Submit transaction for order processing
curl -X POST \
  "https://{cpq-instance}.oracle.com/rest/v19/commerce/{processVarName}/{mainDocVarName}/{transactionId}/actions/order_start" \
  -H "Authorization: Bearer ${ORACLE_CPQ_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"comments": "Order initiated from approved quote"}'

# Response: { "status": "success", "orderNumber": "ORD-2026-00456" }

# Step 2: Retrieve transaction data for ERP mapping
curl -X GET \
  "https://{cpq-instance}.oracle.com/rest/v19/commerce/{processVarName}/{mainDocVarName}/{transactionId}?expand=_lineItems" \
  -H "Authorization: Bearer ${ORACLE_CPQ_TOKEN}" \
  -H "Accept: application/json"
```

**Verify**: Transaction status changes to order-processing state. Check OIC dashboard for corresponding integration flow execution.

## Code Examples

### Python: Salesforce CPQ Quote-to-NetSuite Sales Order

```python
# Input:  Salesforce Order ID (generated from CPQ Ordered checkbox)
# Output: NetSuite Sales Order internal ID

import requests
from simple_salesforce import Salesforce  # v1.12+

def sync_cpq_order_to_netsuite(sf_order_id: str, sf: Salesforce,
                                ns_account_id: str, ns_token: dict) -> str:
    """Push Salesforce CPQ order to NetSuite as a sales order."""

    # 1. Query Salesforce order + line items
    order_data = sf.query(f"""
        SELECT Id, OrderNumber, Account.NetSuite_Internal_ID__c,
               CurrencyIsoCode, ShippingStreet, ShippingCity,
               ShippingState, ShippingPostalCode, ShippingCountry,
               (SELECT Id, Product2.NetSuite_Item_ID__c, Quantity,
                       UnitPrice, SBQQ__QuoteLine__r.SBQQ__NetPrice__c
                FROM OrderItems)
        FROM Order WHERE Id = '{sf_order_id}'
    """)
    order = order_data['records'][0]

    # 2. Build NetSuite sales order payload
    ns_order = {
        'entity': {'id': order['Account']['NetSuite_Internal_ID__c']},
        'tranDate': order.get('EffectiveDate', ''),
        'otherRefNum': order['OrderNumber'],  # Idempotency: CPQ order number
        'currencyRecord': {'refName': order.get('CurrencyIsoCode', 'USD')},
        'item': {
            'items': [{
                'item': {'id': item['Product2']['NetSuite_Item_ID__c']},
                'quantity': item['Quantity'],
                'rate': str(item['SBQQ__QuoteLine__r']['SBQQ__NetPrice__c']),
                'amount': item['Quantity'] * item['SBQQ__QuoteLine__r']['SBQQ__NetPrice__c']
            } for item in order['OrderItems']['records']]
        },
        'shippingAddress': {
            'addr1': order.get('ShippingStreet', ''),
            'city': order.get('ShippingCity', ''),
            'state': order.get('ShippingState', ''),
            'zip': order.get('ShippingPostalCode', ''),
            'country': order.get('ShippingCountry', '')
        }
    }

    # 3. POST to NetSuite SuiteTalk REST
    ns_url = f"https://{ns_account_id}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder"
    resp = requests.post(ns_url, json=ns_order, headers={
        'Authorization': f'Bearer {ns_token["access_token"]}',
        'Content-Type': 'application/json',
        'Prefer': 'respond-async'  # For large orders
    })

    if resp.status_code == 204:
        ns_internal_id = resp.headers.get('Location', '').split('/')[-1]
        return ns_internal_id
    elif resp.status_code == 429:
        raise Exception('NetSuite rate limit hit — retry with backoff')
    else:
        raise Exception(f'NetSuite error {resp.status_code}: {resp.text}')
```

### JavaScript/Node.js: Salesforce CPQ Order to Dynamics 365

```javascript
// Input:  Salesforce Order payload (from Platform Event)
// Output: D365 Sales Order entity ID

const axios = require('axios');  // v1.6+
const qs = require('querystring');

async function createD365SalesOrder(sfOrder, d365Config) {
  // 1. Get D365 OAuth token via Azure AD
  const tokenResp = await axios.post(
    `https://login.microsoftonline.com/${d365Config.tenantId}/oauth2/v2.0/token`,
    qs.stringify({
      grant_type: 'client_credentials',
      client_id: d365Config.clientId,
      client_secret: d365Config.clientSecret,
      scope: `${d365Config.resourceUrl}/.default`
    })
  );
  const token = tokenResp.data.access_token;

  // 2. Map SF order to D365 SalesOrderHeaders data entity
  const d365Order = {
    SalesOrderNumber: sfOrder.OrderNumber,  // Idempotency key
    OrderingCustomerAccountNumber: sfOrder.Account.D365_Customer_Number__c,
    CurrencyCode: sfOrder.CurrencyIsoCode || 'USD',
    RequestedReceiptDate: sfOrder.EffectiveDate,
    SalesOrderLines: sfOrder.OrderItems.records.map((item, idx) => ({
      LineNumber: (idx + 1) * 10,
      ItemNumber: item.Product2.D365_Item_Number__c,
      SalesQuantity: item.Quantity,
      SalesPrice: item.UnitPrice,
      RequestedShipDate: item.ServiceDate
    }))
  };

  // 3. POST to D365 OData
  const resp = await axios.post(
    `${d365Config.resourceUrl}/data/SalesOrderHeadersV2`,
    d365Order,
    {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'OData-MaxVersion': '4.0',
        'OData-Version': '4.0'
      }
    }
  );

  return resp.data.SalesOrderNumber;
}
```

### cURL: Test Oracle CPQ Transaction Retrieval

```bash
# Input:  Oracle CPQ access token, transaction ID
# Output: Full transaction with line items

# Retrieve Oracle CPQ transaction with line items expanded
curl -X GET \
  "https://{instance}.oracle.com/rest/v19/commerce/{processVarName}/{mainDocVarName}/{transactionId}?expand=_lineItems" \
  -H "Authorization: Bearer ${ORACLE_CPQ_TOKEN}" \
  -H "Accept: application/json" | jq '{
    transactionId: ._id,
    quoteNumber: .quoteNumber_t,
    status: ._status,
    totalPrice: .totalPrice_t,
    currency: .currency_t,
    lineItems: [._lineItems[]? | {
      lineId: ._doc_number,
      partNumber: .partNumber_l,
      quantity: .quantity_l,
      netPrice: .netPrice_l,
      listPrice: .listPrice_l
    }]
  }'
```

## Data Mapping

### Field Mapping Reference: Salesforce CPQ → ERP

| CPQ Source Field | SAP S/4HANA Target | NetSuite Target | D365 Target | Type | Transform | Gotcha |
|---|---|---|---|---|---|---|
| Order.OrderNumber | PurchaseOrderByCustomer | salesOrder.otherRefNum | SalesOrderNumber | String | Direct (idempotency key) | Must be unique in ERP — use quote number + version if orders can be re-created |
| Account.ERP_Customer_ID__c | SoldToParty | entity.id (internalId) | OrderingCustomerAccountNumber | String | Lookup | NetSuite requires internal ID, not external — pre-map or use externalId search |
| OrderItem.Product2.ERP_Material_ID__c | Material (A_SalesOrderItem) | item.id (internalId) | ItemNumber | String | Lookup | SAP material number is 18 chars padded; NetSuite uses internal ID; D365 uses ItemNumber |
| OrderItem.Quantity | RequestedQuantity | quantity | SalesQuantity | Decimal | Direct | SAP stores as string; NetSuite/D365 as number |
| OrderItem.UnitPrice | NetPriceAmount | rate | SalesPrice | Currency | Direct, but verify currency match | SAP stores amounts in smallest currency unit for some currencies (JPY = 1, USD = 0.01) |
| SBQQ__QuoteLine__r.SBQQ__Discount__c | ConditionType ZDISC (%) | discountRate (%) | DiscountPercentage | Percentage | Map to ERP discount structure | SAP uses condition types; NetSuite uses rate+amount; D365 uses line/total discount |
| Order.CurrencyIsoCode | TransactionCurrency | currencyRecord.refName | CurrencyCode | String | ISO 4217 code | NetSuite may need currency internal ID, not ISO code — depends on account config |
| OrderItem.ServiceDate | RequestedDeliveryDate | item.expectedShipDate | RequestedShipDate | Date | ISO 8601 | SAP: YYYYMMDD internal; API accepts ISO 8601. NetSuite: date format depends on account preference |
| Order.ShippingStreet | ShipToAddress | shippingAddress.addr1 | DeliveryAddress* | String | Direct | SAP max 35 chars per line; NetSuite max 150; D365 uses address entity |
| SBQQ__Quote__c.SBQQ__PaymentTerms__c | PaymentTerms | terms.refName | PaymentTermsName | String | Lookup value mapping | ERP payment term codes differ from CPQ picklist values — maintain mapping table |

[src1, src4, src6, src7]

### Data Type Gotchas

- Salesforce amounts are always in major currency units (e.g., 100.50 USD), but SAP may store in minor units for zero-decimal currencies (JPY: 100 means 100 yen, not 1 yen). Always check `CurrencyDecimalPlaces` before mapping. [src4]
- NetSuite date format is controlled by account preferences, not the API — always send ISO 8601 and let the API parse it. If using SOAP/SuiteTalk, use `xsd:dateTime` format. [src7]
- Salesforce multi-select picklist values serialize as semicolon-delimited strings via API (`"Val1;Val2;Val3"`) but display as comma-delimited in UI — if mapping product options, split on semicolons. [src1]
- Oracle CPQ custom attributes use variable name suffixes (`_t` for text, `_l` for line-level, `_qt` for quote-level) — the API field names do NOT match UI labels. [src2]
- SAP S/4HANA OData returns amounts as strings (e.g., `"100.50"`) — parse to decimal before comparison or calculation. [src4]

## Error Handling & Failure Points

### Common Error Codes

| Code | System | Meaning | Cause | Resolution |
|---|---|---|---|---|
| 429 | Salesforce, NetSuite, D365 | Rate limit exceeded | Too many API calls in window | Exponential backoff: wait 2^n seconds, max 5 retries; check `Retry-After` header |
| INVALID_FIELD / INVALID_FIELD_FOR_INSERT_UPDATE | Salesforce | Field not writable or doesn't exist | Wrong API version, field-level security, or managed package namespace | Verify field API name includes `SBQQ__` namespace; check FLS for integration user |
| UNABLE_TO_LOCK_ROW | Salesforce | Record locked by another transaction | Concurrent order updates or approval processes | Retry with random jitter (500-2000ms); implement record-level locking strategy |
| INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY | Salesforce | Missing access to related record | Integration user lacks access to Account, Product, or Pricebook | Verify profile/permission set for integration user includes all CPQ objects |
| INVALID_SESSION_ID | Salesforce, Oracle CPQ | Session expired | OAuth token expired or revoked | Refresh token and retry; for JWT, generate new token |
| 403 + x-csrf-token error | SAP S/4HANA | CSRF token missing or expired | Write operation without valid csrf token | Fetch new token: `GET` with `x-csrf-token: fetch` header before retry |
| DUPLICATE_VALUE | NetSuite | Duplicate external ID | Order already exists (idempotency working correctly) | Log as success (not error) — the order was already created |
| SSS_REQUEST_LIMIT_EXCEEDED | NetSuite | Governance limit exceeded | SuiteScript governance units exhausted | Wait for next governance window; optimize script to reduce unit consumption |

[src1, src2, src4, src7]

### Failure Points in Production

- **Silent order generation failure in Salesforce CPQ**: Setting `SBQQ__Ordered__c = true` triggers CPQ managed package code. If governor limits are hit (common with 100+ line quotes), the order generation fails silently — no error is thrown, the checkbox remains true, but no Order record is created. Fix: `Monitor Order count after setting Ordered = true; implement a scheduled job that detects quotes where Ordered = true but no linked Order exists`. [src1, src5]
- **Duplicate orders from retry logic**: Middleware retries after a timeout, but the first request actually succeeded — now two ERP orders exist. Fix: `Always include an idempotency key (quote number + version) as the PO number/external reference on the ERP order; check for existing order before creating`. [src4, src6]
- **Currency mismatch between CPQ and ERP**: CPQ calculated the price in EUR, but the ERP customer master is configured for USD. The order is created with wrong amounts. Fix: `Validate currency match before order creation; if multi-currency, include exchange rate from CPQ in the order payload`. [src4]
- **NetSuite customer/item not found**: Salesforce Account exists but hasn't been synced to NetSuite yet (first-time customer). Order creation fails with "Invalid entity reference." Fix: `Implement customer sync before order sync — if customer lookup fails, create customer first, then create order. Use a saga pattern with compensation`. [src7]
- **SAP BAPI_SALESORDER_CREATEFROMDAT2 silent failure**: BAPI returns RETURN table with type 'E' (error) messages but HTTP 200. Middleware treats 200 as success. Fix: `Always parse the RETURN table; any row with TYPE = 'E' or 'A' means failure. Roll back with BAPI_TRANSACTION_ROLLBACK`. [src4]
- **Oracle CPQ BML script modifies order data**: Commerce actions execute server-side BML scripts that can change pricing, apply additional discounts, or modify line items. The order that reaches the ERP differs from what the user approved. Fix: `Implement post-action validation: compare CPQ transaction total with ERP order total; alert if delta exceeds threshold (e.g., 1%)`. [src2, src3]

## Anti-Patterns

### Wrong: Polling Salesforce for new orders every 60 seconds

```javascript
// BAD — wastes API calls, high latency, misses rapid order creation
setInterval(async () => {
  const newOrders = await sf.query(
    "SELECT Id FROM Order WHERE CreatedDate > LAST_N_MINUTES:1"
  );
  for (const order of newOrders.records) {
    await pushToERP(order.Id);
  }
}, 60000);
```

### Correct: Use Platform Events for real-time, event-driven order sync

```javascript
// GOOD — real-time, no polling, no wasted API calls
const faye = require('faye');  // CometD client
const client = new faye.Client(sf.instanceUrl + '/cometd/66.0');

client.subscribe('/event/CPQ_Order_Event__e', async (message) => {
  const orderId = message.data.payload.Order_Id__c;
  await pushToERP(orderId);
});
```

### Wrong: Creating ERP order without idempotency key

```python
# BAD — if middleware retries, duplicate orders are created in ERP
def create_netsuite_order(order_data):
    resp = requests.post(ns_url + '/salesOrder', json=order_data)
    return resp.json()  # No duplicate detection
```

### Correct: Use CPQ quote/order number as idempotency key

```python
# GOOD — check for existing order before creating
def create_netsuite_order(order_data, sf_order_number):
    # First, check if order already exists
    existing = requests.get(
        ns_url + f'/salesOrder?q=otherRefNum IS "{sf_order_number}"',
        headers=ns_headers
    )
    if existing.json().get('count', 0) > 0:
        return existing.json()['items'][0]  # Already created — return it

    order_data['otherRefNum'] = sf_order_number  # Idempotency key
    resp = requests.post(ns_url + '/salesOrder', json=order_data, headers=ns_headers)
    return resp.json()
```

### Wrong: Synchronous callout to ERP inside Salesforce trigger

```apex
// BAD — blocks trigger execution, governor limits, mixed DML
trigger OrderTrigger on Order (after insert) {
    for (Order ord : Trigger.new) {
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://erp.example.com/api/order');
        req.setMethod('POST');
        req.setBody(JSON.serialize(ord));
        HttpResponse res = h.send(req);  // Synchronous callout in trigger = BAD
    }
}
```

### Correct: Use @future or Queueable for async callouts

```apex
// GOOD — async processing, respects governor limits
trigger OrderTrigger on Order (after insert) {
    Set<Id> orderIds = new Set<Id>();
    for (Order ord : Trigger.new) {
        if (ord.SBQQ__Quote__c != null) {
            orderIds.add(ord.Id);
        }
    }
    if (!orderIds.isEmpty()) {
        // Fire Platform Event or enqueue Queueable — NOT synchronous callout
        System.enqueueJob(new ERPOrderSyncQueueable(orderIds));
    }
}

public class ERPOrderSyncQueueable implements Queueable, Database.AllowsCallouts {
    private Set<Id> orderIds;

    public ERPOrderSyncQueueable(Set<Id> orderIds) {
        this.orderIds = orderIds;
    }

    public void execute(QueueableContext ctx) {
        // Safe to make callouts here — separate transaction
        for (Id ordId : orderIds) {
            ERPIntegrationService.pushOrderToERP(ordId);
        }
    }
}
```

## Common Pitfalls

- **Ordered checkbox is one-shot**: The `SBQQ__Ordered__c` checkbox on `SBQQ__Quote__c` can only be set to true once. If order generation fails (silently) and the checkbox is already true, you cannot re-trigger it by toggling it off and on. Fix: `Build a recovery mechanism: a scheduled Apex job that queries quotes where Ordered = true but no linked Order exists, then uses SBQQ.ServiceRouter to regenerate the order`. [src5]
- **API version mismatch between CPQ and custom code**: Salesforce CPQ managed package runs on its own API version. If your Apex triggers reference different API versions, behavior differences can cause data mismatches. Fix: `Pin all integration code to the same API version as the CPQ managed package — check CPQ release notes for API version changes`. [src1]
- **NetSuite subsidiary mismatch**: Multi-subsidiary NetSuite accounts require the sales order's subsidiary to match the customer's subsidiary. If your Salesforce data doesn't track subsidiary, the order fails with a cryptic validation error. Fix: `Map Salesforce business unit or division to NetSuite subsidiary; add subsidiary to customer sync logic`. [src7]
- **SAP pricing condition type mapping**: SAP uses condition types (PR00, K004, ZDISC) for pricing, not simple unit price + discount. A CPQ net price must be decomposed into the correct condition type structure. Fix: `Map CPQ net price to SAP PR00 (base price) condition; map discounts to appropriate condition types; set pricing procedure determination correctly`. [src4]
- **D365 number sequence exhaustion**: Dynamics 365 uses number sequences for sales order numbers. High-volume CPQ integration can exhaust pre-allocated number ranges, causing order creation failures. Fix: `Configure continuous number sequences (not pre-allocated) for sales orders in D365 number sequence settings`. [src4]
- **Oracle CPQ variable name opacity**: Oracle CPQ REST API uses internal variable names (e.g., `totalPrice_t`, `partNumber_l`) that differ from UI labels. Developers build mappings against UI labels, then the API returns different field names. Fix: `Always use the CPQ Admin > Setup > Process > variable name reference; never assume field names match UI labels`. [src2]

## Diagnostic Commands

```bash
# === SALESFORCE CPQ ===
# Check if CPQ order was generated from quote
curl -s "${SF_INSTANCE}/services/data/v66.0/query?q=SELECT+Id,OrderNumber,Status,SBQQ__Quote__c+FROM+Order+WHERE+SBQQ__Quote__c='${QUOTE_ID}'" \
  -H "Authorization: Bearer ${SF_TOKEN}" | jq '.records'

# Check Salesforce API usage / remaining limits
curl -s "${SF_INSTANCE}/services/data/v66.0/limits" \
  -H "Authorization: Bearer ${SF_TOKEN}" | jq '{DailyApiRequests, ConcurrentAsyncGetReportInstances}'

# Verify CPQ quote readiness for ordering
curl -s "${SF_INSTANCE}/services/data/v66.0/query?q=SELECT+SBQQ__Ordered__c,SBQQ__Primary__c,SBQQ__Status__c+FROM+SBQQ__Quote__c+WHERE+Id='${QUOTE_ID}'" \
  -H "Authorization: Bearer ${SF_TOKEN}" | jq '.records[0]'

# === ORACLE CPQ ===
# Check Oracle CPQ transaction status
curl -s "https://${CPQ_INSTANCE}.oracle.com/rest/v19/commerce/${PROCESS}/${MAIN_DOC}/${TXN_ID}" \
  -H "Authorization: Bearer ${ORACLE_CPQ_TOKEN}" | jq '{status: ._status, total: .totalPrice_t}'

# === SAP S/4HANA ===
# Verify SAP sales order was created (search by PO number = SF order number)
curl -s "https://${SAP_HOST}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?\$filter=PurchaseOrderByCustomer%20eq%20'${SF_ORDER_NUMBER}'" \
  -H "Authorization: Bearer ${SAP_TOKEN}" | jq '.d.results'

# === NETSUITE ===
# Search NetSuite for order by external reference
curl -s "https://${NS_ACCOUNT}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder?q=otherRefNum%20IS%20%22${SF_ORDER_NUMBER}%22" \
  -H "Authorization: Bearer ${NS_TOKEN}" | jq '.items'

# === DYNAMICS 365 ===
# Check D365 sales order by CPQ order number
curl -s "${D365_URL}/data/SalesOrderHeadersV2?\$filter=SalesOrderNumber%20eq%20'${SF_ORDER_NUMBER}'" \
  -H "Authorization: Bearer ${D365_TOKEN}" | jq '.value'
```

## Version History & Compatibility

| Component | Version | Release Date | Status | Key Changes |
|---|---|---|---|---|
| Salesforce CPQ (managed package) | v66.0 (Spring '26) | 2026-02 | Current (sunset announced) | Last major release before sunset; Revenue Cloud migration tools added |
| Salesforce Revenue Cloud | Spring '26 | 2026-02 | Current (GA) | Order Management API v2; enhanced multi-currency support |
| Oracle CPQ REST API | v19 (24D) | 2025-12 | Current | Bulk Data Services for pricing integration; enhanced Commerce actions |
| Oracle CPQ REST API | v17 (23D) | 2023-12 | Supported | Added "Stage" resource parameter for multi-stage commerce flows |
| SAP S/4HANA API | 2408 | 2024-08 | Current | Async OData processing; enhanced A_SalesOrder entity |
| NetSuite SuiteTalk REST | 2024.2 | 2024-09 | Current | Enhanced SuiteQL for complex order queries |
| D365 F&SCM | 10.0.39 | 2025-04 | Current | SalesOrderHeadersV2 data entity improvements |

[src1, src2]

### Deprecation Policy

Salesforce CPQ (managed package) is sunset for new customers as of March 2025, with no renewals beyond August 2026. Existing customers should plan migration to Revenue Cloud. Oracle CPQ maintains backward compatibility across minor versions but deprecates REST API versions with 12-month notice. SAP S/4HANA follows a 2-year API deprecation notice policy. [src1, src2, src6]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| B2B sales with complex product configuration, tiered/negotiated pricing, and approval workflows that must flow to ERP as structured orders | Simple B2C order flow from eCommerce cart | business/erp-integration/ecommerce-to-erp/2026 |
| Quote contains multiple line items with bundles, options, and custom pricing that must map to ERP line items | Single-product, fixed-price orders | Direct ERP order entry or simple webhook |
| CPQ approvals and discount governance must be preserved in the ERP order record | No pricing complexity — ERP prices are authoritative | Skip CPQ; create orders directly in ERP |
| Need to sync order status, fulfillment, and shipment data back from ERP to CPQ/CRM | One-way push without status tracking | Simple REST POST without bidirectional sync |
| Multi-currency, multi-entity, or multi-subsidiary orders across regions | Single-currency, single-entity operations | Simplified order integration without FX handling |

## Cross-System Comparison

| Capability | Salesforce CPQ | Salesforce Revenue Cloud | Oracle CPQ Cloud | Notes |
|---|---|---|---|---|
| Order generation trigger | `Ordered` checkbox on quote | Order Management API | Commerce action (`order_start`) | SF CPQ is declarative; Oracle CPQ is API-driven |
| API style | REST (sObject CRUD on Order/OrderItem) | REST (Order Management API) | Commerce REST API v19 | Revenue Cloud API is newer and more structured |
| Pricing engine location | Salesforce (managed package Apex) | Salesforce (native) | Oracle CPQ (server-side BML) | CPQ pricing must match ERP pricing |
| Native ERP integration | None (requires middleware) | Salesforce-native only | Oracle ERP Cloud via OIC (native) | Oracle CPQ has strongest native Oracle ERP integration |
| Middleware options | MuleSoft, Boomi, Workato, Celigo | Same as CPQ + native connectors | OIC (recommended), MuleSoft, Boomi | OIC is strongly recommended for Oracle CPQ |
| Rate limit (order creation) | 100K API calls/24h (Enterprise) | Shared with REST API | Throttled (~50 concurrent) | Salesforce has most transparent limits |
| Bulk order support | Bulk API 2.0 for mass queries | Bulk API 2.0 | Bulk Data Services (24D+) | Both support high-volume scenarios |
| Sunset risk | Sunset March 2025 (new), Aug 2026 (renewals) | Active — successor platform | Active — continued investment | SF CPQ customers must plan migration |
| Multi-currency | Supported via Salesforce MCE | Native multi-currency | Built-in multi-currency engine | All three support multi-currency |
| Approval workflow | Salesforce Approvals + CPQ Advanced Approvals | Salesforce Flow-based | Oracle CPQ workflow engine | All have approval capabilities |

## Important Caveats

- Salesforce CPQ is being sunset — no new customer sales since March 2025, no renewals beyond August 2026. All new implementations should use Revenue Cloud. Existing CPQ integration patterns documented here remain valid for the managed package's remaining lifecycle. [src1, src6]
- CPQ-to-ERP pricing parity is non-negotiable: if CPQ calculates $10,500 and the ERP order shows $10,450 due to different rounding rules or discount structures, you have a billing dispute waiting to happen. Budget 20-30% of integration effort for pricing reconciliation. [src4, src6]
- Sandbox/test environments have different rate limits than production across all platforms — load test against a full sandbox with production-volume data before go-live. [src1]
- Oracle CPQ REST API field names use variable name suffixes (`_t`, `_l`, `_qt`) that do not match UI labels — always reference the admin setup for correct field names. [src2]
- Governor limits in Salesforce are per-transaction, not per-API-call — a single order generation with 200 line items and triggers on each can cascade to consume 80+ SOQL queries within one transaction. [src1, src5]
- This card covers order creation only, not the full quote-to-cash lifecycle. For billing, invoicing, and revenue recognition, see the quote-to-cash-integration and subscription-billing-integration cards.

## Related Units

- [Quote-to-Cash Integration: Full QTC Lifecycle](/business/erp-integration/quote-to-cash-integration/2026)
- [Order-to-Cash Integration](/business/erp-integration/order-to-cash-integration/2026)
- [Salesforce REST API Capabilities](/business/erp-integration/salesforce-rest-api-capabilities/2026)
- [Oracle ERP Cloud REST API Capabilities](/business/erp-integration/oracle-erp-cloud-rest-api-capabilities/2026)
- [Salesforce-NetSuite Integration Playbook](/business/erp-integration/salesforce-netsuite-integration/2026)
- [eCommerce-to-ERP Order Integration](/business/erp-integration/ecommerce-to-erp/2026)
- [Subscription Billing Integration](/business/erp-integration/subscription-billing-integration/2026)
