dist/codex/salesforce-commerce/skills/sf-b2b-apex/SKILL.md
Build B2B Commerce Apex extensions using the CartExtension namespace — PricingCartCalculator, ShippingCartCalculator, TaxCartCalculator, InventoryCartCalculator implementations, governor limits, bulkification, test coverage (75% minimum), and checkout flow customization. Use when implementing server-side B2B commerce logic.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins sf-b2b-apexInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
ALWAYS fetch live documentation BEFORE writing any Apex code:
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/https://developer.salesforce.com/docs/commerce/salesforce-commerce/guide/b2b-cart-calculate.htmlWhy this matters: Salesforce releases three times per year (Spring, Summer, Winter). Commerce APIs, CartExtension interfaces, and governor limits change. Live docs prevent using deprecated patterns.
Salesforce B2B Commerce uses the CartExtension namespace to provide extension points for customizing checkout logic. Each calculator extends CartExtension.AbstractCartCalculator and overrides the calculate() method.
| Extension Point | Purpose | Example Use Cases |
|---|---|---|
| PricingCartCalculator | Custom pricing calculations | Volume discounts, contract pricing, tiered pricing |
| ShippingCartCalculator | Shipping rate determination | Carrier integration, zone-based rates |
| TaxCartCalculator | Tax calculations | Avalara, Vertex, custom tax engines |
| InventoryCartCalculator | Real-time inventory validation | Warehouse checks, ATP calculations |
calculate() methodCartCalculateCalculatorRequest with access to the full cartrequest.getCart(), which returns a CartExtension.Cart| Object | Access Method | Purpose |
|---|---|---|
| Cart | request.getCart() | Root cart object with all items and delivery groups |
| CartItemList | cart.getCartItems() | Iterable list of all line items |
| CartItem | cartItems.get(index) | Individual line item with product, quantity, pricing |
| CartDeliveryGroupList | cart.getCartDeliveryGroups() | Shipping delivery groups |
| CartDeliveryGroup | deliveryGroups.get(index) | Address, shipping method, delivery charges |
| CartDeliveryGroupMethod | Constructor-based | Shipping option with name, cost, carrier |
Note: CartItemList uses index-based access (get(i)) and size(), not enhanced for-loops.
Register calculators via the Salesforce UI. There is NO custom metadata type (B2B_Commerce_Hook__mdt or similar) for registration.
| Limit | Synchronous | Asynchronous | |---|---|---| | SOQL queries | 100 | 200 | | DML statements | 150 | 150 | | CPU time | 10,000 ms | 60,000 ms | | Heap size | 6 MB | 12 MB | | Callouts per transaction | 100 | 100 | | Callout timeout (per call) | 10 s (configurable up to 120 s) | 10 s | | Callout total response size | 12 MB | 12 MB | | Future calls per transaction | 50 | 0 (not allowed from async) | | Queueable jobs per transaction | 50 | 1 |
Custom Metadata queries do NOT count against SOQL limits. Always verify these limits against the current release notes -- values change across Salesforce releases.
B2B carts can have hundreds of line items. All calculator code must be bulkified:
WHERE Id IN :idSetMap<Id, SObject> for O(1) lookups when applying resultsList<SObject> for batch DML operations| Pattern | Returns Value? | Accepts Complex Params? | Use in calculate()? |
|---|---|---|---|
| Synchronous HTTP | Yes | Yes | Yes -- preferred for real-time data |
| @future(callout=true) | No (void only) | No (primitives only) | Not for return values |
| Queueable | No (async) | Yes | For post-processing follow-up |
callout:MyCredential/path) -- never hardcode auth headersreq.setTimeout(10000))The ConnectApi namespace provides programmatic cart and checkout operations:
| Class | Purpose |
|---|---|
| ConnectApi.CommerceCart | Create, retrieve, delete carts; add items |
| ConnectApi.CommerceCatalog | Product search, category browse |
| ConnectApi.CheckoutInput / CartCheckoutOutput | Start and manage checkout |
ConnectApi classes are not constructable in test context by default. Use wrapper/mock patterns or Test.isRunningTest() guards for testability.
@TestSetup to create shared test data (Account, Product2, WebStore, WebCart, CartDeliveryGroup, CartItem)CartCalculateCalculatorRequest is not directly constructable in tests -- test business logic methods separatelyTest.setMock(HttpCalloutMock.class, ...) for callout testsLimits.getQueries(), Limits.getDmlStatements(), Limits.getCpuTime()| Artifact | Convention | Example |
|---|---|---|
| Calculator class | [Purpose]Calculator | VolumePricingCalculator |
| Test class | [ClassName]Test | VolumePricingCalculatorTest |
| Test data factory | B2BCommerceTestDataFactory | Shared across test classes |
| Custom Metadata | Commerce_Config__mdt | Configuration without SOQL cost |
| Platform Cache | local.CommerceCache | Frequently accessed pricing data |
| Queueable job | [Purpose]Job | ShippingRateLoggerJob |
Pattern: CartExtension calculator skeleton
public class MyCalculator extends CartExtension.PricingCartCalculator {
public override void calculate(
CartExtension.CartCalculateCalculatorRequest request
) {
CartExtension.Cart cart = request.getCart();
// Fetch live docs for CartCalculateCalculatorRequest API
}
}
Pattern: Bulkified query + map lookup
Set<Id> ids = new Set<Id>();
// ... collect ids from cart items ...
Map<Id, Product2> products = new Map<Id, Product2>(
[SELECT Id, Field__c FROM Product2 WHERE Id IN :ids]
);
// ... apply via products.get(itemProductId) ...
Pattern: HttpCalloutMock for tests
private class MockResponse implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
// Fetch live docs for HttpCalloutMock interface
}
}
Pattern: Graceful error handling in calculate()
try {
applyPricing(cartItems);
} catch (CalloutException e) {
CommerceLogger.logError('Calculator', 'calculate', e);
// Allow checkout to continue with existing prices
}
Cache.Org) for frequently accessed configuration dataitem.setMessage() for validation errors visible to the buyerQueryException, CalloutException, and generic ExceptionB2BCommerceTestDataFactory class for reusable test dataLimits class methods@TestVisible annotation to expose private helper methods for unit testingQueueable (not @future) for complex async post-processing after calculate()Database.AllowsCallouts on Queueable classes that make HTTP callscalculate() with System.enqueueJob()Fetch the latest Salesforce B2B Commerce Apex developer guide and CartExtension namespace reference for exact method signatures and current governor limits before implementing.
development
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.
tools
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.