Configure RFC destinations via Cloud Connector, call BAPIs from CAP, handle the RETURN table, and commit transactions.
Some critical SAP business logic is only available as BAPIs — not via OData. Payment posting, batch processing, complex material movements — these still route through RFC. Knowing how to call them from CAP keeps you unblocked when the API Hub doesn't have what you need.
| Use OData | Use BAPI/RFC |
|---|---|
| Standard CRUD, reporting | Complex business transactions |
| Available on API Hub | Business logic only in ABAP |
| Performance not critical | Atomic multi-step operations |
| New S/4HANA systems | Legacy ECC/S/4 on-prem |
| S/4HANA Cloud (no RFC) | S/4HANA on-prem |
S/4HANA Cloud Public Edition does not support RFC. RFC is for on-prem or private-cloud systems.
npm install @sap-cloud-sdk/connectivity @sap-cloud-sdk/http-client
# For JCo/RFC: add the JCo client library (requires SAP SDK download)
In SCC admin → Access Control → Cloud to On-Premise → Add:
Back-end Type: SAP ABAP System
Protocol: RFC
Internal Host: sapgw00.corp.internal
Internal Port: 3300
Virtual Host: sapgw00-virtual
Virtual Port: 3300
In BTP Cockpit → Destinations → New:
Name: SAP_ECC_RFC
Type: RFC
jco.client.r3name: ECC
jco.client.client: 100
jco.client.lang: EN
jco.destination.pool_capacity: 5
Authentication: BasicAuthentication
User: BAPI_USER
Password: ****
ProxyType: OnPremise
// srv/legacy-service.js
const { executeHttpRequest } = require('@sap-cloud-sdk/http-client')
const { buildHeadersForDestination } = require('@sap-cloud-sdk/connectivity')
// For BAPIs, use the RFC function module API via SAP Cloud SDK RFC module
// (requires @sap/cloud-sdk-core with JCo support — enterprise license)
this.on('getCustomerCredit', async (req) => {
const { customerId } = req.data
// Direct BAPI call via Cloud SDK
const result = await SoapRfcClient.execute({
destination: 'SAP_ECC_RFC',
functionModule: 'BAPI_CUSTOMER_GET_CREDIT',
importing: {
KUNNR: customerId.padStart(10, '0') // ABAP convention: left-pad with zeros
}
})
return {
creditLimit: result.EXPORTING.CREDIT_LIMIT,
creditUsed: result.EXPORTING.CREDIT_USED,
currency: result.EXPORTING.CURRENCY
}
})
The preferred approach on modern systems is to call BAPI via SAP Gateway's OData wrapper:
ABAP BAPI → SAP Gateway (OData V2) → BTP Destination → CAP
# In ABAP developer tools, create a Gateway project that wraps the BAPI
# Then consume via cds import / odata-v2 connector (same as Lesson 61)
try {
const result = await rfcClient.execute({ functionModule: 'BAPI_ORDER_CREATE1', ... })
// BAPI return messages are in RETURN table (not exceptions)
const errors = result.TABLES.RETURN?.filter(r => r.TYPE === 'E' || r.TYPE === 'A')
if (errors?.length > 0) {
return req.reject(400, errors.map(e => e.MESSAGE).join('; '))
}
// Commit the BAPI work (required for state-changing BAPIs)
await rfcClient.execute({ functionModule: 'BAPI_TRANSACTION_COMMIT' })
return result
} catch (err) {
if (err.rfcErrorCode) {
// RFC-specific errors (connection, auth, etc.)
console.error('RFC error:', err.rfcErrorCode, err.message)
return req.reject(502, 'SAP system temporarily unavailable')
}
throw err
}
Most state-changing BAPIs require an explicit commit:
// Step 1: Call the BAPI
await rfcClient.execute({ functionModule: 'BAPI_SALESORDER_CREATE', ... })
// Step 2: Check RETURN table for errors
// Step 3: Only commit if no errors
await rfcClient.execute({ functionModule: 'BAPI_TRANSACTION_COMMIT', importing: { WAIT: 'X' } })
Never skip the BAPI_TRANSACTION_COMMIT — the order won't be created.
getSystemInfo action that calls RFC_SYSTEM_INFOTYPE = 'E'/'A' rowsBAPI_TRANSACTION_COMMIT after successful execution