Skip to content
Cartly Developers

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:

  1. Phase 1 — Self-Serve Returns (RMA): Temporal-powered return workflow with merchant approve/reject signals.
  2. Phase 2 — Passwordless Login: OTP via email/SMS and Magic Links with 15-minute expiry.
  3. Phase 3 — Customer Headless API: SAT/CAT-authenticated profile, address, wishlist, and returns endpoints.
  4. Phase 4 — Portal Extension Points: Six named slots where apps inject Liquid, iframe, or remote UI blocks.
  5. Phase 5 — B2B Company Hierarchy: Companies, locations, memberships, role-based access control, and Temporal budget-reset cron.
  6. 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 SMS
  • POST /storefront/v1/customer/otp/verify — verify code, returns CAT

Magic Link endpoints

  • POST /storefront/v1/customer/magic-link/request — send signed link to email
  • GET /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

typescript
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, phone
  • GET/POST/PUT/DELETE /storefront/v1/customer/addresses — address CRUD
  • GET/POST/DELETE /storefront/v1/customer/wishlist — wishlist
  • GET /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 page
  • footer — bottom of every portal page
  • sidebar — left navigation
  • account_overview — account dashboard
  • order_detail — order detail page
  • return_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/companies
  • GET/POST /admin/companies/:id/locations
  • GET/POST /admin/companies/:id/members

Customer API (CAT required)

  • GET /customer/company
  • GET /customer/company/members
  • GET /customer/company/locations
  • POST /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 flow
  • GET /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: ValidateReturnNotifyMerchantProcessRefund. On rejection, the workflow completes with a notification to the customer. All activities are idempotent.