Customer Portal & B2B
Extend the Cartly customer portal with passwordless login, self-serve returns (RMA), headless customer API, portal extension points, B2B company hierarchy with RBAC, and enterprise OIDC SSO.
Overview
Customer Portal Extensibility (v1.6) ships in six phases:
- Phase 1 — Self-Serve Returns (RMA): Temporal-powered return workflow with merchant approve/reject signals.
- Phase 2 — Passwordless Login: OTP via email/SMS and Magic Links with 15-minute expiry.
- Phase 3 — Customer Headless API: SAT/CAT-authenticated profile, address, wishlist, and returns endpoints.
- Phase 4 — Portal Extension Points: Six named slots where apps inject Liquid, iframe, or remote UI blocks.
- Phase 5 — B2B Company Hierarchy: Companies, locations, memberships, role-based access control, and Temporal budget-reset cron.
- Phase 6 — Enterprise SSO (OIDC): OIDC init/callback flow with auto-membership and AES-GCM encrypted client secrets.
Passwordless Login
Customers log in via a one-time code (OTP) or a signed magic link. Both return a Customer Access Token (CAT) identical to the password-based login token.
OTP endpoints
POST /storefront/v1/customer/otp/request— send OTP to email or SMSPOST /storefront/v1/customer/otp/verify— verify code, returns CAT
Magic Link endpoints
POST /storefront/v1/customer/magic-link/request— send signed link to emailGET /storefront/v1/customer/magic-link/verify?token=...— verify token, returns CAT
SMS delivery uses the configured SMS_PROVIDER (twilio, sns, or mock). Magic links are signed JWTs with a 15-minute expiry.
OTP Login Flow
const SAT = 'sat_your_token';
const BASE = 'https://cartly.pro/storefront/v1';
// Step 1: request OTP
await fetch(`${BASE}/customer/otp/request`, {
method: 'POST',
headers: { 'X-Shopify-Storefront-Access-Token': SAT, 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: 'user@example.com', channel: 'email' }),
});
// Step 2: verify OTP → returns CAT
const { access_token } = await fetch(`${BASE}/customer/otp/verify`, {
method: 'POST',
headers: { 'X-Shopify-Storefront-Access-Token': SAT, 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: 'user@example.com', code: '847291' }),
}).then(r => r.json());Customer Headless API
All customer mutation endpoints under /storefront/v1/customer/ require both a SAT (X-Shopify-Storefront-Access-Token) and a CAT (Authorization: Bearer <cat>).
PUT /storefront/v1/customer/profile— update name, phoneGET/POST/PUT/DELETE /storefront/v1/customer/addresses— address CRUDGET/POST/DELETE /storefront/v1/customer/wishlist— wishlistGET /storefront/v1/customer/returns— list return requests
App Session Token
Exchange a CAT for a short-lived App Session Token (AST) so an embedded app can act on behalf of the logged-in customer without storing credentials.
POST /storefront/v1/customer/app-session— issue AST (requires SAT + CAT)POST /apps/api/customer-session/verify— verify AST server-side (requires app OAuth token)
Portal Extension Points
Apps register Liquid, iframe, or remote UI blocks into one of six named slots in the customer portal:
header— top of every portal pagefooter— bottom of every portal pagesidebar— left navigationaccount_overview— account dashboardorder_detail— order detail pagereturn_detail— return request page
Register: POST /apps/api/blocks/:slug/portal-extensions
Query active extensions: GET /storefront/v1/portal/extensions?point=account_overview
Admin management: GET/PUT/DELETE /admin/customer-portal/extensions
B2B Company Hierarchy
Three new entities support B2B commerce: Company, CompanyLocation, and CompanyMembership. Memberships carry a role: admin, buyer, or viewer. The RequireB2BRole middleware enforces access at the handler level.
Admin API
GET/POST/PUT/DELETE /admin/companiesGET/POST /admin/companies/:id/locationsGET/POST /admin/companies/:id/members
Customer API (CAT required)
GET /customer/companyGET /customer/company/membersGET /customer/company/locationsPOST /customer/orders/:id/reorder
Location budgets reset automatically via a Temporal cron workflow. Set budget_period to monthly, quarterly, or yearly.
Enterprise SSO (OIDC)
Connect a company to an OIDC identity provider so buyers log in with corporate credentials. Supported: Google Workspace, Azure AD, Okta, custom OIDC.
PUT /admin/companies/:id/sso— configure OIDC (client secrets encrypted with AES-GCM)GET /storefront/v1/customer/sso/init?company_id=...— start OIDC flowGET /storefront/v1/customer/sso/callback— handle provider callback, issue CAT, auto-create membership
Members whose email domain matches email_domain in the SSO config are automatically linked to the company on first SSO login.
Self-Serve Returns (RMA)
Customers submit return requests from the order detail page. Each request starts a Temporal RMAWorkflow waiting for a merchant signal (approve/reject).
POST /customer/returns— submit return request (customer)GET /customer/returns— list return requests (customer)GET /storefront/v1/customer/returns— headless variant (SAT + CAT)
On approval, three activities run in sequence: ValidateReturn → NotifyMerchant → ProcessRefund. On rejection, the workflow completes with a notification to the customer. All activities are idempotent.