@sepetakhq/storefront-kit
v0.1.4
Published
The non-visual half of a Sepetak storefront: API client, config, cart, checkout state, hooks and the helpers that decide what a page may say.
Readme
@sepetakhq/storefront-kit
The half of a Sepetak storefront that is not a design: the API client, the tenant config gate, the cart, checkout state, order tokens, and the helpers that decide what a page may say (sale windows, pre-order, payment kinds, shipping options, vouchers). A template imports these and owns everything visible -- screens, components, CSS, and the sentences.
npm install @sepetakhq/storefront-kit react react-dom @tanstack/react-queryImport paths
One subpath per module, the same tree the monorepo had under src/:
import { apiFetch, ApiError, configureApi } from '@sepetakhq/storefront-kit/api/client';
import { ConfigProvider, useConfig } from '@sepetakhq/storefront-kit/config/ConfigProvider';
import { CartProvider, useCart } from '@sepetakhq/storefront-kit/cart/CartProvider';
import { paymentState, MANUAL_KINDS } from '@sepetakhq/storefront-kit/lib/payment';
import { ROUTE_CONTRACT, routePaths } from '@sepetakhq/storefront-kit/shared/routes';
import { seoDevPlugin } from '@sepetakhq/storefront-kit/vite';Rules a template lives by
Same origin. The client calls
/api/v1on the page's own host; the API resolves the tenant fromHost. Do not callconfigureApiin a storefront served from the template registry.The route contract. Every storefront serves exactly
ROUTE_CONTRACT(/,/katalog,/produk/:slug,/keranjang,/checkout,/pesanan/:token,/track/:token,/lacak,/tentang,*). The API bakes these into emails, the sitemap and the SEO head. Pin your router:import { ROUTE_CONTRACT, routePaths } from '@sepetakhq/storefront-kit/shared/routes'; expect(routePaths(router.routes)).toEqual(ROUTE_CONTRACT);Branch on
payment.kind, neverpayment.method. Renderorder.timelineandorder.status_label, never a local table.unsupported_api_versionmeans this bundle is stale: reload the page.Copy is yours. The helpers default to Indonesian and Lily Alora's colours; pass through the decision (
saleState,leadDays, ...) and write your own note, the waystorefront-balilinen'ssrc/lib/copy.jsdoes.<!--seo:start-->/<!--seo:end-->stay inindex.html. The API fills them per page;seoDevPlugindoes the same undernpm run dev./track/:tokencannot be renamed. The backend composes the emailed tracking link as<origin>/track/<token>with the path hardcoded, so the route has to exist for every confirmation email already sent.
Things that will bite
- Prices are never computed in the browser. Catalogue prices are advisory; the authoritative total appears once, in the draft response, and the server re-prices silently with no "price changed" field. The confirmation step exists so the buyer approves that total before an OTP is spent.
- OTP is scarce: 6 digits, 5-minute TTL, 5 attempts, 5 sends per contact per hour, and a 60s → 5m → 30m resend ladder shared with order lookup. Nothing should ever send a code automatically.
- Wrong code, expired code, too many attempts and a code bound to another checkout are one indistinguishable 400. The UI must not guess a reason.
track_tokenis returned exactly once and stored server-side only as a hash.checkout/writes it tolocalStoragebefore anything else on success, because no endpoint — not even OTP lookup — can give it back.- Payment confirmation is learned only by polling
/orders/track/:token. There is no push and no public payment-status endpoint. - A method can change kind under the storefront when the platform
re-routes it;
kindis derived from which payload the gateway actually returned. Order status beats payment status when they disagree, which they do: an expired order keeps apendingintent. - Images must stay
<img>withsrcset. A CSSbackground-imagecannot usesrcsetat all. - In development
make apiruns the stub gateway unless real credentials are set: QRIS returns an unscannableSTUB-QRIS|…payload and the payment page should show a test-mode panel rather than a QR that fails in every banking app. A real payload flows through unchanged; only theSTUB-guard branches.
Publishing a storefront
sf-publish builds with Vite base set to the CDN prefix for this build and
uploads dist/ to the template registry bucket, pointer last:
SF_TEMPLATE=lilyalora SF_BUCKET=sepetak-storefronts \
SF_ENDPOINT=https://<account>.r2.cloudflarestorage.com \
SF_CDN_BASE=https://cdn.sepetak.com \
AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… npx sf-publishLocally, against the dev MinIO: SF_ENDPOINT=http://localhost:9000
SF_CDN_BASE=http://localhost:9000/sepetak-storefronts with the minioadmin
pair. Rollback is the pointer line alone with an older build id.
Versioning
Semver. The API is versioned by date (X-API-Version, pinned in
api/client.js); a kit release that moves that pin is a minor at least.
