# Salesforce D2c Setup

> Use when setting up, configuring, or troubleshooting a Salesforce D2C (or B2B) Commerce store — WebStore/catalog/categories/pricebooks, product images (the CMS multipart upload), the two cart-calculation frameworks (legacy sfdc_checkout vs CartExtension/Cart Calculate), payment + shipping + tax integrations (mock or real), promotions + coupons, inventory, buyer groups + entitlement (incl. Commerc…

- **Type:** Skill
- **Install:** `agentstack add skill-alexkwitko-salesforce-agentforce-salesforce-d2c-setup`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [alexkwitko](https://agentstack.voostack.com/s/alexkwitko)
- **Installs:** 0
- **Category:** [Finance & Payments](https://agentstack.voostack.com/c/finance-and-payments)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [alexkwitko](https://github.com/alexkwitko)
- **Source:** https://github.com/alexkwitko/Salesforce-Agentforce/tree/main/skills/salesforce-d2c-setup

## Install

```sh
agentstack add skill-alexkwitko-salesforce-agentforce-salesforce-d2c-setup
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Salesforce D2C Commerce Setup Playbook

Practical, **DX-first** playbook for standing up a Salesforce **D2C (or B2B) Commerce** store end to end. Drawn from a real build (a coffee-equipment D2C store, "Bean & Brew"); the patterns apply to any catalog. Substitute your own products/brand.

> **Companion reference:** [`reference/commerce-api-and-data-model.md`](reference/commerce-api-and-data-model.md) has the exhaustive catalog — every Connect REST endpoint, the full cart/order/promotions object model, both calc-framework interface tables, the `commerce/*` LWC adapter list, the B2B-vs-D2C matrix, and the payment-framework objects. Load it when you need exact paths/signatures. This file is the *workflow*; that file is the *catalog*.
>
> **Scope note:** this is **Commerce on the core Salesforce platform** (Experience Cloud LWR + the Commerce data model), NOT "B2C Commerce Cloud" (SFRA / PWA Kit / Demandware) — a separate product. The official guide is the *B2B & D2C Commerce Developer Guide*.

## #0 rule: Official-first for COMMERCE PLUMBING; custom LWC for DISTINCTIVE DESIGN
Keep the **standard commerce components for the transactional path** (PDP, cart, **checkout**, search) — never re-implement cart/checkout/pricing/tax; use the official mechanisms (Commerce store, OrderDeliveryMethod, CommercePayments, entitlement policies, Guest Buyer Profile, CartExtension calculators). But the OOTB Commerce LWR **template's marketing pages (home/landing) are a generic placeholder** ("Alpine Energy" banners, footer links all to `/`) — for a professional, on-brand 2026 storefront you SHOULD build custom LWR design components for the homepage/editorial sections. That's not "Frankenstein," it's the supported path (reference §17). Rule of thumb: **custom design components for storytelling/merchandising pages; standard components for the buy-flow.**

## #1 rule: DX/CLI/API first, UI last
Order for every task:
1. **`sf` CLI** — `sf project deploy/retrieve`, `sf data`, `sf apex run`.
2. **Connect/REST API via CLI** — `sf api request rest "/services/data/v62.0/..."` (uses the stored session even though the raw token is hidden).
3. **Metadata XML deploy** (mdapi when source-format type inference fails).
4. **Apex self-callout** (for APIs with no `sf api` path, e.g. multipart uploads).
5. **Browser/Setup UI** — last resort. To drive the org UI headlessly **without a password**, use `sf org open --path "" --url-only` → navigate the browser to that frontdoor URL (auto-authenticates with the CLI token). `sf org display` **redacts** the access token (returns a 54-char placeholder), so raw `curl -H "Authorization: Bearer ..."` 401s — use `sf api request rest` or the Apex session instead.

## #2 rule: pick the calc framework up front (one or the other, never both)
Cart/checkout calculations run on **one** of two mutually-exclusive frameworks — they **cannot be mixed**:
- **Legacy "Checkout Integrations"** — Apex implements `sfdc_checkout.*` interfaces, registered via `RegisteredExternalService` + `StoreIntegratedService`. Fast to mock; what older stores use.
- **Cart Extensions / Cart Calculate** (current **GA**, Salesforce-recommended) — Apex extends `CartExtension.*` classes registered against extension points (e.g. `Commerce_Domain_Tax_CartCalculator`). Required for the Cart Calculate API and finer control.

Default to **legacy** only for a quick mock/demo or a store already on it. Choose **CartExtension** for new builds. Migrating between them is a rebuild of every calculator, not a flag — decide early. (Full interface/extension-point tables + signatures in the reference file §1.) Payment is outside both — always `commercepayments` (§4).

---

## 1. Store + data model (the spine)
Core objects: `WebStore`, `ProductCatalog`, `ProductCategory`, `ProductCategoryProduct` (product→category junction), `Product2`, `PricebookEntry` (standard + the store's pricebook → `WebStorePricebook`), `BuyerGroup`, `CommerceEntitlementPolicy`, `CommerceEntitlementBuyerGroup` (policy↔group), `WebStoreBuyerGroup` (store↔group), `WebStoreCatalog` (store↔catalog).

- The standard **"Commerce Store (LWR)"** template in some orgs is **B2B-only** (no separate D2C template); the D2C distinction is buyer access + guest, not a different template. Create the store from the standard template either way.
- Products need a `PricebookEntry` in **both** the standard pricebook and the **store pricebook** to be sellable. (8 products × 2 = 16 entries in the reference build.)
- Add products to a category via `ProductCategoryProduct(ProductCategoryId, ProductId)`; rebuild the search index afterward (see §8).
- `Product2` images are NOT a URL field — see §3.

## 2. Catalog distinct from other stores
If one org hosts multiple brands/stores, keep catalogs distinct: separate `Product2` SKUs per brand, add only the brand's products to that store's category, and (if a category was shared) remove the other brand's `ProductCategoryProduct` rows. Use a `Brand__c` picklist on `Order`/`Web_Event__c` as a *dimension* (not a separate identity) when you want one unified customer across brands.

## 3. Product images — the hard part (CMS multipart upload)
`ProductMedia` (the image link) requires `ProductId` + `ElectronicMediaId` (→ **`ManagedContent`**, required) + `ElectronicMediaGroupId`. Gotchas that cost the most time:
- `ManagedContent` is **NOT DML-createable**; `ManagedContentVersion` is **not an SObject**. Images must be created through the **Connect CMS API**.
- The CMS `source.ref` will **NOT** accept a `ContentDocument`/`ContentVersion` Id ("This image reference isn't available").
- The standard media groups already exist per store: query `ElectronicMediaGroup` — you'll find **Tile Image, Product List Image, Product Detail Images** (+ Banner, Attachments). Reuse them; `ElectronicMediaGroup` is not createable.
- CMS workspace = `ManagedContentSpace` (query `SELECT Id, Name FROM ManagedContentSpace`).

**Winning create recipe** (`POST /connect/cms/contents`, multipart):
- JSON part named `content`: `{"contentSpaceOrFolderId":"","title":"...","urlName":"img-...","contentType":"sfdc_cms__image","contentBody":{"sfdc_cms:media":{"source":{"type":"file"}}}}`
  - `source.type` **must be the constant `"file"`**; do **NOT** include `ref` and do **NOT** include `sfdc_cms:altText` (both rejected with `additionalProperties` errors).
- Binary part named **exactly `contentData`** (the API demands this name regardless of `ref`), with `filename` + `Content-Type: image/png`.
- **Order matters**: content part FIRST, then the `contentData` binary part.

**How to send multipart headlessly** (`sf api request rest` can't do multipart; the token is redacted): build the whole multipart body in **Python**, base64 it, and POST from **Apex self-callout**:
- Add a `RemoteSiteSetting` for the org My-Domain (deploy as **mdapi** — source-format `.remoteSiteSetting-meta.xml` throws `TypeInferenceError`; use a `package.xml` + mdapi dir).
- Apex: `req.setEndpoint('https://.my.salesforce.com/services/data/v62.0/connect/cms/contents'); req.setHeader('Authorization','Bearer '+UserInfo.getSessionId()); req.setHeader('Content-Type','multipart/form-data; boundary=...'); req.setBodyAsBlob(EncodingUtil.base64Decode(b64));`
- **Anonymous-apex script cap ≈ 1MB** → upload **one image per run** (a batch of 8 base64 bodies = 207KB and still failed "Script too large" when combined with maps; loop one at a time, ~25KB each).
- Generate images with **Pillow** (available locally) — clean branded PNGs render fine.

**Then link + publish:**
- `ProductMedia` IS DML-createable: `insert new ProductMedia(ProductId=p, ElectronicMediaId=managedContentId, ElectronicMediaGroupId=, SortOrder=n)` — one per group (List/Detail/Tile = 3 per product).
- Publish the content: `POST /connect/cms/contents/publish` body `{"contentIds":[...]}` (field is **`contentIds`**, not `managedContentIds`/`ids`). Returns a `deploymentId` / `#DEPLOYED`.
- Verify per product via `GET /services/data/v62.0/commerce/webstores/{webStoreId}/products/{productId}` → `defaultImage.url` populated + `mediaGroups`. The product **search** API (`POST .../search/product-search`) is what the LIST page reads — confirm it returns `defaultImage` too.
- Direct media URLs require the storefront session; a bare-domain `curl` 301→404s to the wrong site — that's expected, not a defect.

## 4. Payment (mock for demo, real otherwise)
*(Payment uses the `commercepayments` framework regardless of calc framework — request types, objects, client vs server flow in reference §8.)*
- Mock: an Apex `CommercePayments.PaymentGatewayAdapter` that always approves (real adapter branches on `ctx.getPaymentRequestType()`: Tokenize/Authorize/Capture/Refund). `PaymentGatewayProvider` **can't be inserted via Apex DML** — create with `sf data create record`. Then `PaymentGateway` + a `NamedCredential`.
- Wire to the store: `StoreIntegratedService(StoreId, ServiceProviderType='Payment', Integration=)`. Verify: `SELECT ServiceProviderType, Integration FROM StoreIntegratedService WHERE StoreId='...'`.

## 5. Shipping
- `OrderDeliveryMethod` (e.g. "Standard Shipping", `IsActive=true`). The standard checkout can use a flat delivery method without a custom Apex shipping service. For dynamic rates, register a shipping integration like tax (§6).

## 6. Tax (mock = a real store-completeness win)
*(This is the **legacy** `sfdc_checkout` path — see rule #2. On a CartExtension store, extend `CartExtension.TaxCartCalculator` against `Commerce_Domain_Tax_CartCalculator` instead; reference §1.)*
- Apex `class BeanBrewMockTax implements sfdc_checkout.CartTaxCalculations { IntegrationStatus startCartProcessAsync(IntegrationInfo, Id cartId) {...} }` — compute e.g. 8% per `CartItem`, write `CartTax` rows.
- **Gotcha**: `sfdc_checkout.IntegrationStatus` has **no `message` field** — set only `.status` (`SUCCESS`/`FAILED`); log errors via `System.debug`.
- Register: `RegisteredExternalService(DeveloperName, MasterLabel, ExternalServiceProviderType='Tax', ExternalServiceProviderId=)` then `StoreIntegratedService(StoreId, ServiceProviderType='Tax', Integration=)`. Both ARE Apex-DML-creatable. `StoreIntegratedService.ServiceProviderType` enum: Flow, Price, Promotions, Inventory, Shipment, Tax, Payment, Extension.

## 7. Inventory / supply chain
- `ProductInventory`/OCI is a separate (heavy, often UI-gated) enablement. **Without it, products sell as available by default** — fine for demos.
- For Woo-style stock parity, seed `Product2.Stock_On_Hand__c` (this field often already exists from a Woo sync). It's a data layer, not a hard checkout block unless OCI is on.

## 8. Search index
- Rebuild after catalog/category/image/entitlement changes: `POST /services/data/v62.0/commerce/management/webstores/{id}/search/indexes` body `{}`. Poll `GET` the same endpoint until `indexStatus != "InProgress"` (≈2–5 min). The LIST page reads the index; PDP reads live.

## 9. Buyers + entitlement (authenticated)
- A buyer = `Account` → `BuyerAccount(BuyerId, BuyerStatus='Active', IsActive=true)` → `BuyerGroupMember(BuyerGroupId, BuyerId)`. **`BuyerStatus` defaults to `Pending` — must be `Active`** or carts fail "Invalid effective accountId."
- D2C buyers are **Person Accounts**.
- The store's buyer group must have an **active `CommerceEntitlementPolicy`** (`CanViewProduct`/`CanViewPrice`=true) linked via `CommerceEntitlementBuyerGroup`, and the group linked to the store via `WebStoreBuyerGroup`. The note in Setup: "each assigned buyer group must have at least one active entitlement."
- ⚠️ **An entitlement policy with `CanViewProduct=true` but ZERO `CommerceEntitlementProduct` rows entitles NOTHING** — there is no "grant all products" boolean field; the storefront returns **404 on every product** (guest AND buyer) until you insert `CommerceEntitlementProduct(PolicyId, ProductId)` rows for the products (or category rows). This is the #1 "catalog is empty for shoppers but admin API sees products" cause.
- **REST cart as internal admin doesn't work**: `POST /commerce/webstores/{id}/carts?effectiveAccountId=X` returns "Invalid effective accountId" even for a valid Active buyer — D2C checkout runs in the **shopper's storefront session** (guest or community user), not an admin REST context. To prove checkout, log in as a real buyer (recipe below).

### Provisioning a test BUYER LOGIN (to prove checkout headlessly-ish via the live storefront)
A buyer storefront login needs ALL of these — missing any one yields 403/404 on the commerce APIs:
1. **Portal User** on the Person Account (`ContactId = PersonContactId`, profile = the store's CspLitePortal "Shopper Profile"). ⚠️ **The portal account's OWNER must have a `UserRole`** ("portal account owner must have a role") — assign one first. Set the password **in a SEPARATE transaction** from the insert (`System.setPassword` right after insert silently doesn't take for login).
2. **D2C Commerce Shopper permission-set LICENSE** (`PermissionSetLicense` DeveloperName `CommerceShopperPsg`) assigned via `PermissionSetLicenseAssign` — without a commerce PSL the APIs 403.
3. The standard **`CommerceUser`** ("Shopper") permission set (grants the commerce system perm) + a custom permset with **Read** on `ProductCatalog`/`ProductCategory`/`ProductCategoryProduct`/`Product2`/`ProductMedia` (the Customer Community license **rejects `Create WebCart`** — cart objects are handled by the commerce runtime, so don't add cart CRUD).
4. The Account = Active `BuyerAccount` + `BuyerGroupMember`, and the policy has `CommerceEntitlementProduct` rows (above).
5. **Re-login after any permset/PSL change** — community sessions cache permissions; the storefront keeps 403ing on the old session until you log out/in.

## 10. Guest browsing + guest checkout (the saga)
1. **Site public access**: in the site's `DigitalExperienceBundle`, `sfdc_cms__site//content.json` → `contentBody.authenticationType` must be **`AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED`** (valid enum; NOT `AUTHENTICATED_AND_ANONYMOUS`). Deploy (mdapi) + `sf community publish --name ""`.
2. **WebStore flags** (Apex updateable): `OptionsGuestBrowsingEnabled`, `OptionsGuestCartEnabled`, `OptionsGuestCheckoutEnabled`, `OptionsPreserveGuestCartEnabled`; `GuestBuyerProfileId` (object prefix `3K0`, only Id/Name queryable).
3. **CRITICAL — run the official Guest Access automation in Setup UI**: Commerce app → Store → **Settings → Store → Buyer Access tab → Guest Access → Disable → Enable → Continue**. This is the step that **assigns object permissions to the guest profile**, creates the Guest Buyer Profile, sets person-account default, and sets Experience-Builder public access. **Setting the WebStore flags via Apex does NOT run this automation** — the UI banner literally says "Click Disable and then Enable to run the full automation." There is **no headless API** for the guest entitlement/permission assignment.
4. Confirm Store Access → Buyer Groups = your buyer group (re-assigning errors as a duplicate = it's wired).
5. Rebuild the search index; **guest catalog visibility can lag** (entitlement + index recalc; minutes to hours in a Dev Edition org). Even with a complete, correct setup the guest grid may stay empty for a while — verify in an authenticated session meanwhile.
- Drive all the Setup-UI steps headlessly via the `sf org open --url-only` frontdoor (no password).

### Guest visibility = THREE independent gates (all must pass) — the authoritative checklist
A guest 404 on the product API and 403 on pricing almost always means one of these is missing:
1. **Commerce entitlement** — the policy on the store's buyer group needs **`CommerceEntitlementProduct` rows** (no "all products" flag; 0 rows ⇒ 404 for everyone). Guests inherit the **store** buyer group via `WebStoreBuyerGroup`; the **Manage Guest Access automation associates the Guest Buyer Profile with the buyer group** (the guest buyer profile is NOT a `BuyerGroupMember` row — that association is internal, which is why

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [alexkwitko](https://github.com/alexkwitko)
- **Source:** [alexkwitko/Salesforce-Agentforce](https://github.com/alexkwitko/Salesforce-Agentforce)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-alexkwitko-salesforce-agentforce-salesforce-d2c-setup
- Seller: https://agentstack.voostack.com/s/alexkwitko
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
