@bc-subscriptions/storefront-catalyst
v0.1.0
Published
Catalyst-baseline subscription components per ADR-0013. Headless React components consumed by Catalyst-based BigCommerce storefronts: <SubscriptionWidget> on PDPs, <SubscriptionLineItem> in cart, <SubscriberPortalApp> on /account/subscriptions.
Readme
@bc-subscriptions/storefront-catalyst
Catalyst-baseline subscription components per ADR-0013. Headless React 19 components consumed by Catalyst-based BigCommerce storefronts.
Pre-1.0 — API is not yet stable. Minor versions may introduce breaking changes until
1.0.0.
Install
npm install @bc-subscriptions/storefront-catalystPeer dependencies (must already be in your Catalyst app):
npm install react@^19 react-dom@^19Three components
| Component | Where | Ports |
|---|---|---|
| <SubscriptionWidget> | Product detail page | prototype/prototypes/storefront-widget/ |
| <SubscriptionLineItem> | Cart line item | (cart treatment of the same slice) |
| <SubscriberPortalApp> | /account/subscriptions route | prototype/prototypes/subscriber-portal/ + portal-auth/ |
Plus createApiClient({ baseUrl }) — minimal fetch wrapper for the apps/api endpoints (/api/plans, /api/portal/auth/*, /api/subscriptions/*). Note: /api/checkout/intent was retired in ADR-0026; subscription intent capture now rides the BC cart-flow path.
Quick start (Next.js App Router consumer)
// app/(default)/products/[slug]/page.tsx
import { SubscriptionWidget, createApiClient } from '@bc-subscriptions/storefront-catalyst';
const api = createApiClient({ baseUrl: process.env.NEXT_PUBLIC_SUBS_API_URL! });
export default function ProductPage({ params }) {
// Server fetches BC product (existing Catalyst code path)
const product = await getProduct(params.slug);
return (
<>
<ExistingProductDetails product={product} />
<SubscriptionWidget
bcProductId={product.entityId}
bcCustomerId={getCurrentCustomerId()}
apiClient={api}
onSubscribed={(r) => router.push(r.magic_link_url)}
/>
</>
);
}SubscriptionWidget is a Client Component (uses useState + useEffect). In a strict RSC consumer, wrap it in a thin 'use client' boundary or import via a client-only module.
Subscription detection
The widget calls apiClient.getPlans({ bcProductId }) on mount. If the call returns zero plans, the widget renders null — products without our subscription metafield don't show the widget. This means:
- Existing Catalyst PDPs render unchanged for non-subscribable products
- Subscription-enabled products show the widget alongside the existing add-to-cart flow
The metafield (subscription.enabled, subscription.plan_id) is the discovery pointer; the actual plan data comes from the API. Storefronts that prefer to read metafields directly (server-side) can skip the widget entirely and render their own UI.
Headless styling
Components use minimal inline styles for the demo. Real Catalyst storefronts will override via the className props (Tailwind / CSS Modules / styled-components — your call). Replace inline styles wholesale once branded.
API client
const api = createApiClient({
baseUrl: 'https://subs-api.bigcommerce-testing-7727.workers.dev',
authToken: null, // populated after magic-link verify
});
await api.getPlans({ bcProductId: 12345 });
await api.postCheckoutIntent({ bc_customer_id: 1, bc_product_id: 12345, plan_id: 'plan_abc' });
await api.requestMagicLink({ email: '[email protected]' });
const session = await api.verifyMagicLink('xyz...');
api.authToken = session.token; // mutates client state
const sub = await api.getSubscription('sub_abc123');The client is stateless except for authToken. Persist the token to sessionStorage in your host app; don't rely on the client to do it.
Out of scope (deferred)
- Stencil adapter — ADR-0013 §3 commits to a separate
@bc-subscriptions/storefront-stencilpackage - Storybook / visual tests — Phase 2
- Storefront SDK (US-8.3) — wraps the API client in a higher-level "subscribe to this product" call; Phase 2
- i18n — uses native
Intl.NumberFormat/Intl.DateTimeFormatfor currency/date; translatable copy will route through@bc-subscriptions/i18n(ADR-0006) once a vendor pipeline lands
Releases
This package publishes to npm via the publish-storefront-catalyst.yml workflow. Trigger is a tag push matching storefront-catalyst-v*.
One-time operator setup (required before first publish):
- Mint a Granular npm Access Token scoped to
@bc-subscriptions/*(Read+Write) - Add it as
NPM_TOKENto the repo's GitHub Actions secrets (Settings → Secrets and variables → Actions)
To publish a new version:
# 1. Bump version in this package.json
# 2. Commit + push (via PR to main)
# 3. Tag the merge commit
git tag storefront-catalyst-v<new-version>
git push origin storefront-catalyst-v<new-version>The workflow handles the rest (npm ci → npm run build → npm publish --access public with provenance attestation).
Full runbook (all @bc-subscriptions/* packages, rollback, verification): docs/release-management.md.
Related
- ADR-0005 — three-surface UI architecture
- ADR-0013 — Catalyst-baseline + Stencil first-class
- Storefront consumer:
apps/storefront-catalyst/(Catalyst chassis); alsoapps/storefront-svelte/(north-star SvelteKit host) - Backend API:
apps/api/—/api/plans,/api/portal/auth/*(/api/checkout/intentretired per ADR-0026) - Hive #1069 — headless activation spec: unprivate + publish workflow (AC-1)
