SFCC Subscriptions: How to Sell Subscriptions on Commerce Cloud
Salesforce B2C Commerce (SFCC) has no built-in subscription engine. You cannot switch on "subscribe and save" in Business Manager. Subscriptions are added with a partner cartridge from Salesforce's marketplace, or built on the platform's APIs. Salesforce's own subscription feature, Commerce Subscriptions, belongs to B2B Commerce and D2C Commerce, a different product, and it is limited to digital products.
This guide is for architects choosing how to add them: partner cartridges, renewal orders through SCAPI, payment tokenisation, subscribe-and-save pricing, self-service in SFRA and PWA Kit, and the comparison with Shopify Plus. For the wider platform, see the SFCC guide.
Does SFCC support subscriptions natively?
Not as a feature you can enable. Three things settle it:
- Salesforce's native subscription feature targets a different product. The release note that made Commerce Subscriptions generally available scopes it to B2B Commerce, D2C Commerce and Revenue Cloud where Salesforce Pricing is enabled. It requires Salesforce Payments and is not included in the Commerce licence. The setup article adds: "You can't create subscriptions for physical products."
- B2C Commerce documents building blocks, not a subscription model. The script API and SCAPI cover baskets, orders, customer wallets with tokenised payment instruments, promotions and jobs. None of the B2C Commerce references used for this guide describes a subscription object, billing schedule or renewal process.
- Salesforce's own packaged offer uses a partner for the subscriptions. The B2C Commerce Consumer Goods solution guide lists a subscriptions offering among its use cases and links the Quick Start Commerce for Subscriptions data sheet. That package combines B2C Commerce and Salesforce Order Management with Ordergroove Essentials for the subscription capability, implemented by SI partners. AgentExchange (formerly AppExchange) lists more subscription cartridges, covered below.
The product names cause most of the confusion, so be precise:
| Product | Also known as | How subscriptions work |
|---|---|---|
| B2C Commerce | SFCC, Commerce Cloud, formerly Demandware | Partner cartridge or custom build |
| B2B Commerce and D2C Commerce | Commerce stores in a Salesforce org (Lightning Experience) | Commerce Subscriptions: digital products, Salesforce Pricing and Salesforce Payments, licensed separately |
If you run B2C Commerce, an announcement about "D2C subscriptions" does not apply to your storefront.
What a subscription capability has to do
Every option below is a different split of the same responsibilities:
| Capability | What it involves | Typical owner |
|---|---|---|
| Enrolment | Subscribe option and interval on the PDP or cart | Storefront plus the cartridge |
| Subscription record | Products, interval, next date, address, payment reference, status | Subscription platform, or custom objects |
| Payment token | A reusable, consented reference to the payment method | PSP vault |
| Renewal order | A real B2C Commerce order each cycle, priced and taxed | Shopper APIs, the Orders API or a job |
| Renewal charge | A merchant-initiated authorisation against the token | PSP, triggered by one system |
| Dunning | Retries, card updates, notifications | Subscription platform |
| Self-service | Skip, pause, swap, change date, address or card; cancel | My Account pages or vendor widgets |
| Pricing | Subscribe-and-save discounts, first-order offers | Promotions, custom price adjustment or vendor |
| Fulfilment | Export, OMS, shipping, returns | The existing order pipeline |
One decision shapes the rest: where does the subscription live, and which system charges the renewal? Leave it open and two systems end up believing they own the card.
Option 1: a partner subscription cartridge
These AgentExchange listings have the solution type B2C Commerce Cartridge. They are in alphabetical order, not ranked, and features change, so check the current listing:
- Litmus7 Subscriptions Cartridge for B2C Commerce: skip, pause, cancel, address and payment changes, and a Business Manager extension for customer service with reports. The listing describes it as built entirely with native B2C Commerce functionality.
- Ordergroove Subscriptions: an external subscription platform. Its recurring order placement documentation describes a push model: each due order goes to an HTTPS endpoint the merchant exposes, or into a batch file over SFTP, and a failed placement is retried at the next placement time.
- Smart Order Refill by OSF Digital: intervals configured in Business Manager, repeat-purchase discounts, customer-group access per product, and a My Account dashboard to edit, pause and cancel.
- sticky.io Subscriptions Cartridge for B2C Commerce: subscriptions managed from Business Manager, a customer portal, automatic updates for expired cards and retries on failed renewals.
Two more sources sit outside that list. Subscribe Pro publishes its official SFCC cartridge on GitHub. And some payment cartridges bundle subscriptions: Klarna's SFCC documentation describes a back-end job that processes due subscriptions and a Klarna customer token stored on the customer profile. It documents SFRA only, subscription checkout for logged-in customers only, and restrictions on mixing standard and subscription products in one cart.
Two architectures behind the listings
Read each vendor's documentation for one answer first: where is the subscription's system of record?
| In-platform cartridge | External subscription platform | |
|---|---|---|
| Subscription data | B2C Commerce: custom objects, profile attributes | The vendor's platform |
| Renewal trigger | A scheduled B2C Commerce job | The vendor's scheduler, pushing orders into B2C Commerce |
| Renewal charge | Your PSP cartridge, called from B2C Commerce | The vendor's PSP integration, or a token you share with it |
| Dunning, card updates, analytics | Whatever the cartridge implements | Usually the vendor's core product |
| Main risks | Job runtime and object quotas; you inherit its code | Two data stores to reconcile; another system to monitor |
Neither wins in general: in-platform adds no second system, while an external platform brings dunning and retention tooling but adds an integration to operate.
Questions to ask every vendor
- Which storefronts are supported: SFRA, SiteGenesis, PWA Kit, or a headless client on SCAPI?
- How is the renewal order created: OCAPI, SCAPI, a controller endpoint or an order import? OCAPI is deprecated; see SCAPI vs OCAPI.
- Which PSPs support merchant-initiated renewals, and who stores the token?
- Does the renewal run through
dw.order.calculate, so your promotions, tax and shipping rules apply? - How are multiple sites, currencies and locales handled, and what happens when stock or payment fails?
Option 2: build it on B2C Commerce APIs
Build when the model is unusual, you already run a billing platform, or no cartridge fits your storefront. You then own the subscription record, a scheduler, renewal order creation and the renewal charge. In a headless build the record and scheduler usually live outside B2C Commerce; if you keep them in custom objects, check the current object quotas first.
Renewal orders through the Shopper APIs
For an external scheduler, the cleanest path is to place each renewal the way a shopper would, so B2C Commerce prices, promotes, taxes and validates it. SLAS provides the login: Trusted System on Behalf (TSOB), a login flow for system-to-system communication in back-office workflows, including a system acting on behalf of a shopper. It requires a SLAS private client with the sfcc.ts_ext_on_behalf_of scope. The Build Baskets and Place Orders guide then shows a basket built in one request and submitted to Shopper Orders:
#!/bin/bash
set -euo pipefail
# Server-side only: the SLAS private client secret must never reach a browser
CODE='<short code>'
ORG='<organization id>'
SITE='<site id>'
CLIENT='<SLAS private client id>'
SECRET='<SLAS private client secret>'
LOGIN='subscriber@example.com' # the subscriber's B2C Commerce login
BASE="https://$CODE.api.commercecloud.salesforce.com"
# 1. TSOB token for a registered B2C Commerce shopper
TOKEN=$(
curl "$BASE/shopper/auth/v1/organizations/$ORG/oauth2/trusted-system/token" \
-sS --fail-with-body \
-u "$CLIENT:$SECRET" \
-d 'hint=ts_ext_on_behalf_of' \
-d 'grant_type=client_credentials' \
-d "channel_id=$SITE" \
-d 'idp_origin=ecom' \
--data-urlencode "login_id=$LOGIN" | jq -r '.access_token'
)
# 2. Build the renewal basket in one request. c_subscriptionInterval needs a
# custom attribute on ProductLineItem; the payment instrument must carry the
# stored PSP token in the form your payment cartridge expects.
BASKET=$(
curl "$BASE/checkout/shopper-baskets/v1/organizations/$ORG/baskets?siteId=$SITE" \
-sS --fail-with-body \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"productItems": [
{ "productId": "<sku>", "quantity": 1, "c_subscriptionInterval": "monthly" }
],
"customerInfo": { "email": "subscriber@example.com" },
"shipments": [{
"shipmentId": "me",
"shippingAddress": { "firstName": "Joe", "lastName": "Shopper",
"address1": "415 Mission St.", "city": "San Francisco",
"postalCode": "94105", "stateCode": "CA", "countryCode": "US" },
"shippingMethod": { "id": "<shipping method id>" }
}],
"billingAddress": { "firstName": "Joe", "lastName": "Shopper",
"address1": "415 Mission St.", "city": "San Francisco",
"postalCode": "94105", "stateCode": "CA", "countryCode": "US" },
"paymentInstruments": [{ "paymentMethodId": "<payment method id>" }]
}' | jq -r '.basketId'
)
# 3. Submit the basket as an order
curl "$BASE/checkout/shopper-orders/v1/organizations/$ORG/orders?siteId=$SITE" \
-sS --fail-with-body \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "basketId": "'"$BASKET"'" }'
Constraints that matter for a renewal batch:
- Salesforce notes that TSOB is not the preferred shopper authentication method, because it sits outside the OAuth and OIDC standard. Keep it server-side, for system workflows like this one.
- Repeated logins for the same shopper within the 3-second service window return HTTP 409, so process each subscriber's renewals together.
- Always pass the same
login_id; a different value creates another shopper record. The guide also advises against TSOB when shopper information changes frequently andidp_origin=ecom, because it does not retrieve the latest customer information. - From release 26.6, a TSOB login updates the profile's last-login timestamp, so last login no longer means "the shopper visited".
- With API hook execution enabled (a Business Manager feature switch), basket and order hooks run, including
dw.order.calculate. The SFCC hooks guide covers that switch and the return-value rules that decide whether your extensions and the default logic run. - The checkout guide recommends creating the order before validating payment. Design the path for a renewal order whose charge then fails, and hand that subscription to dunning.
Pre-priced orders through the Orders API
If the subscription platform prices and charges the renewal itself, the Orders API createOrders endpoint creates orders from a third-party system. The precondition is strict: you must already have completed checkout yourself, meaning sanity checks, inventory reservation, payment authorisation, promotions and the full order cost. In return, the documentation says such orders are treated like orders placed directly on B2C Commerce, including status updates and exports. Choose this only when the external platform really owns pricing, inventory and payment; otherwise you are rebuilding checkout elsewhere.
Placing the order and handing it to fulfilment
Renewals should travel the same pipeline as storefront orders. Per the SOM integration guide, once B2C Commerce is connected to Salesforce Order Management (SOM), OrderMgr.placeOrder(order) creates the order in SOM; from PWA Kit 3.19, registered shoppers can also track, cancel and return SOM orders in the storefront. Flag renewal orders with a custom attribute so the OMS, finance and customer service can tell them apart.
Payments: tokens, not cards
Salesforce's SCAPI checkout guide is explicit: "Never pass payment card data to B2C Commerce." The shopper enters card data in the PSP's own script or iframe, and B2C Commerce receives a token.
The script API supports this model. PaymentInstrument.setCreditCardToken() stores the token from a tokenisation provider; once it is set, the account information is masked and the card attributes are frozen. Registered customers keep saved instruments in their wallet (dw.customer.Wallet), and SFRA's registered checkout can use them.
What varies is how each PSP cartridge handles merchant-initiated renewals:
- Adyen separates subscriptions, initiated at regular intervals, from payments on a non-fixed schedule, and ties each token to a shopper reference unique per shopper. Its SFCC plugin feature table lists recurring payments as one-off and non-fixed schedule for SFRA, and one-off for Composable Storefront. Confirm a fixed-schedule flow with Adyen or your vendor rather than assuming it.
- Stripe's SFCC cartridge user guide shows authenticated customers choosing Stripe-saved payment sources at checkout, with a Stripe Customer ID on the profile (
stripeCustomerID). Confirm off-session renewal charging for your version separately. - Klarna stores a customer token for recurring payments on the profile and charges it for each renewal.
Three rules hold whatever the PSP:
- One system charges renewals. Either the subscription platform or B2C Commerce triggers the merchant-initiated charge, never both.
- Consent is captured at sign-up. The shopper agrees to stored credentials and to the schedule; store that consent with the subscription.
- Card updates are a flow, not a field. The SFRA feature list shows My Account viewing, adding and removing payment methods, and notes that editing an existing one is not supported. An "update card" action is therefore "add a new instrument, then repoint the subscription".
Subscribe-and-save pricing
There are three ways to price a subscription line:
- Let the vendor price it, when it owns pricing and creates pre-priced orders.
- Use the promotion engine when the discount fits its qualifiers. The PromotionMgr reference describes active promotions in terms of customer groups, the source code and the basket's coupons, so a renewal process can add a coupon to each renewal basket.
- Add a custom price adjustment when the discount belongs to one line: 10% off the subscribed item, not the one-off item in the same cart.
The custom adjustment uses documented APIs. ProductLineItem.createPriceAdjustment(promotionID, discount) creates an adjustment initialised with the line's tax class and tax rate. The promotion ID must not be the ID of a real promotion, and PercentageDiscount can be created on the fly for exactly this purpose:
'use strict';
// cartridge/scripts/subscription/subscriptionDiscount.js
var PercentageDiscount = require('dw/campaign/PercentageDiscount');
// Must NOT be the ID of a promotion defined in Business Manager
var ADJUSTMENT_ID = 'SUBSCRIBE_AND_SAVE';
/**
* Adds a percentage-off adjustment to every line flagged as a subscription.
* Call it from your dw.order.calculate implementation after promotions are
* applied and before tax and totals are calculated.
*/
function applySubscriptionDiscount(lineItemCtnr, percentage) {
var items = lineItemCtnr.getProductLineItems().iterator();
while (items.hasNext()) {
var pli = items.next();
// Recalculation runs often: creating a second adjustment with the
// same promotion ID throws, so remove the previous one first
var existing = pli.getPriceAdjustmentByPromotionID(ADJUSTMENT_ID);
if (existing) {
pli.removePriceAdjustment(existing);
}
// subscriptionInterval: a custom attribute you define on ProductLineItem
if (pli.custom.subscriptionInterval) {
pli.createPriceAdjustment(ADJUSTMENT_ID, new PercentageDiscount(percentage));
}
}
}
module.exports = { applySubscriptionDiscount: applySubscriptionDiscount };
With API hook execution enabled, renewal baskets built through Shopper Baskets run the same calculation hook, so the sign-up price and every renewal price come from one rule. The line flag can arrive as a c_ property on the basket's product item: SCAPI custom properties can be set in input documents, and ProductItem is a supported type.
Customer self-service in SFRA and PWA Kit
Retention is decided on the account page, and neither storefront ships a subscriptions page:
- SFRA's My Account covers the dashboard, profile, address book, payment methods and order history. Add a subscriptions page with your own controllers and templates, or embed the vendor's widget.
- PWA Kit has account pages with order history, plus SOM order actions from 3.19. A subscriptions page needs a data source: the vendor's API, or a SCAPI custom API over data you keep in B2C Commerce. See SFRA vs PWA Kit for the storefront choice itself.
Details that reduce cancellations and support contacts:
- Show the next renewal total. In storefront code,
BasketMgr.createTemporaryBasket()creates a basket for the current session customer, meant for calculations or order creation that leave the shopper's open storefront basket untouched. Temporary baskets are deleted after 15 minutes, and by default a customer can have 4 open at once. - Offer skip, pause and change date next to cancel, and send a reminder before each renewal.
- Check cancellation and reminder rules for each market with your legal team.
SFCC vs Shopify Plus for subscriptions
On Shopify, including Shopify Plus, subscriptions are a platform model that apps build on, using selling plans and subscription contracts. When a customer buys a product with a selling plan, Shopify generates a subscription contract; at renewal the subscription app initiates a billing attempt, and a billing attempt creates an order. Shopify also publishes its own Shopify Subscriptions app.
| B2C Commerce (SFCC) | Shopify, including Plus | |
|---|---|---|
| Subscription model in the platform | None; cartridge or custom data | Selling plans and subscription contracts |
| Checkout | Standard checkout; the cartridge adds enrolment | Native checkout creates the contract |
| Renewal order | Shopper APIs, the Orders API or a job | An app-initiated billing attempt creates the order |
| Stored payment method | PSP vault, referenced from B2C Commerce or the vendor | Customer payment methods stored by Shopify |
| Where the logic lives | Your cartridge, your services or the vendor | A subscription app on Shopify's APIs |
Shopify defines the contract model and subscription apps work within its APIs; B2C Commerce leaves the model, and therefore the architecture, to you. The wider comparison is in SFCC vs Shopify Plus.
Architecture decision table
| Situation | Recommended approach | Why |
|---|---|---|
| SFRA site replenishing physical goods on standard intervals | Partner cartridge | Enrolment, account pages and renewals come packaged |
| Retention tooling (skip, swap, win-back, dunning) is the priority | External subscription platform with a B2C Commerce cartridge | Retention is the vendor's core product |
| Headless storefront with an existing billing platform | Build: TSOB, Shopper Baskets and Shopper Orders | B2C Commerce still prices, promotes and taxes each renewal |
| Subscription platform already prices and charges | Orders API createOrders | Imports finished orders; checkout logic lives outside |
| Klarna is the main PSP and the site runs SFRA | Evaluate Klarna's subscription feature | Bundled with the payment cartridge; check its documented restrictions |
| Digital-only subscriptions on Salesforce B2B or D2C Commerce | Commerce Subscriptions | Native there, licensed separately |
Implementation checklist
| Check | Why it matters |
|---|---|
| System of record and renewal charger decided in writing | Two owners double-charge or miss renewals |
| PSP supports merchant-initiated charges on your schedule, in your cartridge version | A saved card alone does not guarantee it |
API hook execution is on and renewal baskets run dw.order.calculate | Promotions, tax and shipping match the storefront |
| One subscribe-and-save rule for sign-up and renewal | Prevents drift between the first and later prices |
TSOB logins grouped per subscriber, with a stable login_id | Avoids HTTP 409 and duplicate shopper records |
| Renewal orders flagged with a custom attribute | OMS, finance and customer service can filter them |
| Failure paths designed: payment, stock, offline product, address | They decide churn more than the happy path |
| Account pages cover skip, pause, date, address, card and cancel | Self-service reduces support contacts |
| Renewal runs scheduled away from peak traffic | Renewals compete with shoppers for the same instance |
Key takeaways
- B2C Commerce has no subscription engine; Commerce Subscriptions is a B2B and D2C Commerce feature for digital products.
- Most B2C Commerce sites should start with a partner cartridge, once they know whether its system of record is in-platform or external.
- Custom builds create renewals with SLAS TSOB, Shopper Baskets and Shopper Orders, or import pre-priced orders through the Orders API.
- Tokens stay with the PSP, and exactly one system charges renewals.
- Line-level subscribe-and-save is a custom price adjustment in the calculation hook; subscription account pages are custom work in SFRA and PWA Kit.
Choosing a subscription cartridge, reviewing a renewal-order flow or designing payment tokenisation for recurring orders is part of the SFCC architecture and development services I offer; get in touch if you are planning one.
Further reading
- Sell and Manage Digital Subscriptions in B2B and D2C Stores: scope, prerequisites and licensing of Commerce Subscriptions.
- SLAS Trusted System on Behalf Authorization: the TSOB token flow and its constraints.
- Build Baskets and Place Orders: SCAPI checkout and the payment-token rule.
- Orders API:
createOrdersfor orders created by third-party systems. - Adyen for Salesforce Commerce Cloud: the plugin's feature table, including recurring payments.
- About subscription contracts (Shopify): how Shopify creates contracts and renewal orders.