npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rock-encantech/vtex-faststore-sdk

v1.0.1

Published

SDK for Propz FastStore integration — offers context, price display, add-to-cart with manual price

Readme

@rockencantech/vtex-faststore-sdk

SDK para integração de ofertas personalizadas Rock Encantech com lojas VTEX FastStore.


Sumário


Instalação

npm install @rockencantech/vtex-faststore-sdk

Configuração

Variáveis de ambiente

NEXT_PUBLIC_ROCK_API_URL=https://sua-rock-api.com
NEXT_PUBLIC_ROCK_CLIENT_ID=seu-client-id
NEXT_PUBLIC_ROCK_CLIENT_SECRET=seu-client-secret

Adicione no painel de secrets do FastStore (WebOps Settings).


Duas abordagens de uso

Abordagem 1: Plug-and-play (zero código)

O SDK faz tudo automaticamente. O dev só monta os componentes:

// src/components/index.tsx
import { RockProductDetails, RockShelf, RockProvider } from '../sdk/propz-sdk'
import { OverriddenDefaultProductDetails as ProductDetails } from 'src/components/sections/ProductDetails/OverriddenDefaultProductDetails'
import { OverriddenDefaultHero as Hero } from 'src/components/sections/Hero/OverriddenDefaultHero'
import React from 'react'

function ProductDetailsWithRock(props: any) {
  return (
    <>
      <RockProductDetails />
      <ProductDetails {...props} />
    </>
  )
}
ProductDetailsWithRock.$componentKey = 'ProductDetails'

function HeroWithRock(props: any) {
  return (
    <>
      <RockProvider />
      <Hero {...props} />
      <RockShelf />
    </>
  )
}
HeroWithRock.$componentKey = 'Hero'

export default { ProductDetails: ProductDetailsWithRock, Hero: HeroWithRock }

O que o plug-and-play entrega:

  • PDP: preço riscado + preço promo + badge da mecânica (tudo via DOM)
  • Carrinho: manual price aplicado automaticamente
  • Checkout: interceptor garante preço correto
  • Shelf: vitrine com cards, badges e slider
  • Funciona com o botão padrão do FastStore (não precisa trocar)

Abordagem 2: Dados + layout próprio (controle total)

O dev usa hooks e funções para montar seu próprio layout:

import {
  useOffers,
  useProductOffer,
  getOfferBadge,
  calculateOfferPrice,
  isOfferApplicable,
  RockProvider
} from '@rockencantech/vtex-faststore-sdk'

// Monte o RockProvider em algum lugar global (ex: Hero, Navbar)
// para inicializar a busca de ofertas:
<RockProvider />

// Depois, em qualquer componente:
function MinhaPDP({ product }) {
  const offer = useProductOffer(product.sku)
  const badge = offer ? getOfferBadge(offer) : null

  return (
    <div>
      {badge && <span className="minha-badge">{badge}</span>}
      <h1>{product.name}</h1>
      {offer ? (
        <div>
          <s>R${offer.listPrice}</s>
          <strong>R${offer.sellingPrice}</strong>
        </div>
      ) : (
        <span>R${product.price}</span>
      )}
    </div>
  )
}

// Shelf customizada com cards da loja:
function MinhaShelf() {
  const { offers, isAuthenticated } = useOffers()
  if (!isAuthenticated || !offers.length) return null

  return (
    <div className="minha-shelf">
      {offers.map(offer => (
        <div key={offer.skuId} className="meu-card">
          <img src={offer.imageUrl} alt={offer.productName} />
          <h3>{offer.itemName}</h3>
          <span>{getOfferBadge(offer)}</span>
          <p>R${offer.sellingPrice}</p>
        </div>
      ))}
    </div>
  )
}

Nota sobre o carrinho/checkout: Mesmo usando layout próprio, se quiser que o desconto seja aplicado no orderForm (carrinho + checkout), use <RockProductDetails /> junto. Ele é invisível e cuida da lógica de apply/restore em background sem afetar seu layout.


Componentes

RockProductDetails

Componente invisível que gerencia toda a lógica de ofertas na PDP e carrinho.

O que faz automaticamente:

  • Busca ofertas se ainda não carregadas (self-contained)
  • Aplica preço riscado + preço promo no DOM da PDP
  • Injeta badge da mecânica acima do título do produto
  • Escuta click no [data-fs-buy-button] → aplica manual price no orderForm
  • Polling (5s) para virtual_pack/get_and_pay → recalcula quando qty muda
  • Restaura preço original quando qty cai abaixo do mínimo
  • Intercepta checkout → aplica/recalcula todos items antes de redirecionar
  • Detecta troca de variação (poll no data-sku do DOM)
  • CPF cacheado (não re-busca a cada click)

Uso:

<RockProductDetails />

Sem props. Sem configuração. Monta e esquece.


RockProvider

Componente invisível que inicializa a busca de ofertas no offersStore.

Quando usar: Em pages que não têm RockProductDetails (ex: Home). O RockProductDetails já faz isso internamente, então na PDP não precisa.

Uso:

<RockProvider />

RockShelf

Vitrine de ofertas personalizadas. 5 cards por linha, slider com setas quando > 5.

Uso básico:

<RockShelf />

Customizado:

<RockShelf
  title="Suas ofertas exclusivas"
  loadingText="Buscando promoções..."
  loginImage="https://cdn.com/banner-login.png"
  renderCard={(offer) => <MeuCard product={offer} />}
/>

Props:

| Prop | Tipo | Padrão | Descrição | |------|------|--------|-----------| | title | string | "Ofertas para você" | Título (só aparece quando há ofertas) | | loadingText | string | "Carregando ofertas..." | Texto durante loading | | loginImage | string | "" | URL da imagem quando deslogado (vazio = não renderiza) | | renderCard | (offer: RockOffer) => ReactNode | undefined | Card customizado da loja |

Comportamento:

  • Deslogado + sem loginImage → não renderiza nada
  • Deslogado + com loginImage → exibe imagem
  • Logado sem ofertas → não renderiza nada
  • Logado com ofertas → exibe vitrine
  • Mais de 5 ofertas → slider horizontal com setas

RockBadge

Badge da mecânica como componente React. Para uso fora do plug-and-play (o RockProductDetails já injeta badge automaticamente).

Uso:

import { RockBadge } from '@rockencantech/vtex-faststore-sdk'

// Detecta SKU do DOM automaticamente:
<RockBadge />

// Com SKU específico:
<RockBadge skuId="8" />

// Estilizado:
<RockBadge style={{ background: '#ff6600', fontSize: '1rem' }} className="my-badge" />

Props:

| Prop | Tipo | Descrição | |------|------|-----------| | skuId | string (opcional) | SKU da oferta. Se omitido, lê do DOM ([data-fs-buy-button][data-sku]) | | style | CSSProperties | Override de estilo inline | | className | string | Classe CSS adicional |

Comportamento:

  • SKU sem oferta → não renderiza nada
  • SKU com oferta → renderiza badge colorida com texto da mecânica
  • Atualiza ao trocar variação (poll 500ms)

RockPrice

Componente React de preço. Self-contained (busca ofertas se necessário).

Uso:

import { RockPrice } from '@rockencantech/vtex-faststore-sdk'

<RockPrice skuId="3" basePrice={16.90} />

Props:

| Prop | Tipo | Descrição | |------|------|-----------| | skuId | string | SKU do produto | | basePrice | number | Preço original (sem desconto) | | listPrice | number (opcional) | Preço "de" (se diferente de basePrice) | | formatter | (price: number) => string | Formatador customizado |

Comportamento:

  • Sem oferta → renderiza preço normal
  • Com oferta → renderiza preço riscado + preço promo

RockAddToCart

Botão de adicionar ao carrinho com lógica de desconto embutida. Opcional — o botão padrão do FastStore funciona com RockProductDetails.

Uso:

import { RockAddToCart } from '@rockencantech/vtex-faststore-sdk'

<RockAddToCart skuId="3" quantity={1} onAddToCart={() => cartStore.addItem(item)}>
  Comprar com desconto
</RockAddToCart>

Hooks

useOffers()

Retorna todas as ofertas da sessão do usuário logado.

import { useOffers } from '@rockencantech/vtex-faststore-sdk'

function MeuComponente() {
  const { offers, isLoading, isAuthenticated } = useOffers()

  // offers: RockOffer[]
  // isLoading: boolean
  // isAuthenticated: boolean
}

Exemplo completo:

function OfertasCustomizadas() {
  const { offers, isLoading, isAuthenticated } = useOffers()

  if (isLoading) return <p>Carregando...</p>
  if (!isAuthenticated) return <p>Faça login para ver ofertas</p>
  if (offers.length === 0) return null

  return (
    <ul>
      {offers.map(offer => (
        <li key={offer.skuId}>
          <img src={offer.imageUrl} alt={offer.productName} />
          <h3>{offer.itemName}</h3>
          <p>De: R${offer.listPrice} | Por: R${offer.sellingPrice}</p>
          <span>{getOfferBadge(offer)}</span>
        </li>
      ))}
    </ul>
  )
}

useProductOffer(skuId)

Retorna a oferta de um SKU específico ou null.

import { useProductOffer } from '@rockencantech/vtex-faststore-sdk'

function MeuPreco({ skuId }: { skuId: string }) {
  const offer = useProductOffer(skuId)

  if (!offer) return <span>Sem oferta</span>

  return (
    <div>
      <s>R${offer.listPrice}</s>
      <strong>R${offer.sellingPrice}</strong>
      <small>{offer.mechanic}</small>
    </div>
  )
}

Funções utilitárias

getOfferBadge(offer)

Retorna o texto da badge para a mecânica da oferta.

import { getOfferBadge } from '@rockencantech/vtex-faststore-sdk'

const badge = getOfferBadge(offer)

Retornos:

| Mecânica | Badge gerada | |----------|-------------| | percent_off | "Desconto exclusivo" | | money_off | "Desconto exclusivo" | | reduced_price | "Preço exclusivo" | | virtual_pack | "A cada 3 un. pague R$ 2,00/un." | | get_and_pay | "A partir de 6 un. por R$ 5,00/un." |

Exemplo de uso customizado:

function MinhaBadge({ skuId }: { skuId: string }) {
  const offer = useProductOffer(skuId)
  if (!offer) return null
  const text = getOfferBadge(offer)
  if (!text) return null

  return <div className="badge-customizada">{text}</div>
}

calculateOfferPrice(basePrice, offer, quantity?)

Calcula o preço final por unidade considerando mecânica e quantidade.

import { calculateOfferPrice } from '@rockencantech/vtex-faststore-sdk'

// Mecânicas simples (qty não importa):
calculateOfferPrice(16.90, offer)       // percent_off: 14.90
calculateOfferPrice(16.90, offer)       // money_off: 14.90
calculateOfferPrice(16.90, offer)       // reduced_price: 10.00

// virtual_pack (preço médio muda com qty):
calculateOfferPrice(5.00, offer, 3)     // 2.00 (3 no pack)
calculateOfferPrice(5.00, offer, 5)     // 3.20 (média: 3×2 + 2×5 / 5)
calculateOfferPrice(5.00, offer, 6)     // 2.00 (2 packs completos)

// get_and_pay (todas com desconto quando qty >= min):
calculateOfferPrice(5.00, offer, 3)     // 2.00
calculateOfferPrice(5.00, offer, 5)     // 2.00

isOfferApplicable(offer, quantity)

Verifica se a oferta pode ser aplicada para uma dada quantidade.

import { isOfferApplicable } from '@rockencantech/vtex-faststore-sdk'

isOfferApplicable(offer, 1)  // false (virtual_pack com minQty=3)
isOfferApplicable(offer, 3)  // true
isOfferApplicable(offer, 5)  // true

Tipos

RockOffer

interface RockOffer {
  skuId: string                              // ID do SKU (itemId VTEX)
  productId: string                          // ID do produto
  productName: string                        // Nome do produto
  linkText: string                           // Slug do produto
  itemName: string                           // Nome completo da variação
  itemDetailUrl: string                      // URL da PDP da variação
  imageUrl: string                           // URL da imagem do produto
  mechanic: OfferMechanic                    // Tipo de mecânica
  discountPercent: number | null             // % desconto (percent_off)
  discountAmount: number | null              // R$ desconto (money_off)
  finalPrice: number | null                  // Preço fixo (reduced_price/virtual_pack/get_and_pay)
  minQuantity: number                        // Qty mínima para ativar
  remainingMaxPerCustomer: number | 'full-promotion'  // Limite por cliente
  sellingPrice: number                       // Preço com desconto
  listPrice: number                          // Preço original
}

type OfferMechanic = 'percent_off' | 'money_off' | 'reduced_price' | 'virtual_pack' | 'get_and_pay'

Mecânicas

percent_off / money_off / reduced_price

Desconto aplicado imediatamente para qualquer quantidade.

  • PDP: preço riscado + preço promo + badge "Desconto exclusivo" ou "Preço exclusivo"
  • Carrinho: manual price aplicado ao clicar "Comprar"
  • Checkout: garantido pelo interceptor

virtual_pack — "A cada X pague Y por unidade"

Desconto aplicado em packs completos. Unidades restantes pagam preço cheio.

  • PDP: não mostra desconto (exige qty mínima no carrinho)
  • Shelf: badge "A cada 3 un. pague R$ 2,00/un."
  • Carrinho: aplica preço médio por unidade
  • Qty < min: restaura preço original automaticamente

| Qty | Cálculo (minQty=3, promo=R$2, base=R$5) | Total | Preço/un | |-----|------------------------------------------|-------|----------| | 2 | Sem desconto | R$10,00 | R$5,00 | | 3 | 1 pack (3×R$2) | R$6,00 | R$2,00 | | 5 | 1 pack (3×R$2) + 2 restantes (2×R$5) | R$16,00 | R$3,20 | | 6 | 2 packs (6×R$2) | R$12,00 | R$2,00 | | 7 | 2 packs (6×R$2) + 1 restante (1×R$5) | R$17,00 | R$2,43 |


get_and_pay — "A partir de X pague Y por unidade"

Desconto aplicado para todas as unidades quando qty >= min.

  • PDP: não mostra desconto (exige qty mínima no carrinho)
  • Shelf: badge "A partir de 6 un. por R$ 5,00/un."
  • Carrinho: aplica finalPrice para todas as unidades
  • Qty < min: restaura preço original automaticamente

| Qty | Cálculo (minQty=3, promo=R$2, base=R$5) | Total | Preço/un | |-----|------------------------------------------|-------|----------| | 2 | Sem desconto | R$10,00 | R$5,00 | | 3 | Todas a R$2 | R$6,00 | R$2,00 | | 5 | Todas a R$2 | R$10,00 | R$2,00 | | 6 | Todas a R$2 | R$12,00 | R$2,00 |


Regras de negócio

  • Menor preço prevalece: preço Propz > preço VTEX → mantém VTEX, não mostra na shelf
  • Restauração automática: qty < min + manual price existente → restaura preço base
  • Interceptor de checkout: garante preço correto antes do redirect (anti-exploit)
  • Limite por cliente: respeita remainingMaxPerCustomer da Propz
  • Variações: cada SKU tem preço próprio (match por refId)
  • Botão padrão: funciona com [data-fs-buy-button] do FastStore (não precisa trocar)

Arquitetura interna

┌───────────────────────────────────────────────────────────────┐
│                    Browser (FastStore)                          │
├───────────────────────────────────────────────────────────────┤
│                                                                │
│  offersStore (singleton global)                                │
│  └── { offers: RockOffer[], isLoading, isAuthenticated }       │
│                                                                │
│  RockProductDetails (invisível, tudo automático)               │
│  ├── ensureOffersLoaded → /api/vtexid → getPromotions          │
│  ├── ensureCpfLoaded (cacheado)                                │
│  ├── applyPdpPrice → DOM [data-fs-price-variant]               │
│  ├── injectBadge → DOM [data-fs-product-details-title]         │
│  ├── skuPoll (500ms) → detecta troca variação                  │
│  ├── handleBuyClick → polling 300ms → /api/offers/apply        │
│  │   └── fsCart.set → força re-validate                        │
│  ├── periodicCheck (5s) → virtual_pack/get_and_pay             │
│  │   └── aplica ou restaura conforme qty vs minQty             │
│  └── handleCheckoutClick → apply all → redirect                │
│                                                                │
│  RockShelf / RockBadge / RockPrice                             │
│  └── Lêem do offersStore via hooks                             │
│                                                                │
└───────────────────────────────────────────────────────────────┘
         │
         │ HTTPS (X-Client-Id + X-Client-Secret)
         ▼
┌───────────────────────────────────────────────────────────────┐
│                   Rock Encantech API (BFF)                      │
│  /api/promotion    → ofertas Propz + catálogo VTEX             │
│  /api/offers/apply → manual price + restore                    │
│  /api/user-document → email → CPF                              │
└───────────────────────────────────────────────────────────────┘

Troubleshooting

| Problema | Verificar | |----------|-----------| | Preço não aparece na PDP | Env vars NEXT_PUBLIC_ROCK_* configuradas? Usuário logado? | | Badge não aparece | O produto tem oferta na Propz? RockProductDetails montado? | | Desconto não aplica no carrinho | Logs da API: /api/offers/apply chamado? appKey com permissão? | | virtual_pack não recalcula | periodicCheck roda a cada 5s. Para garantia: vá ao checkout (interceptor) | | Preço errado no checkout | Impossível — interceptor aplica/recalcula antes de redirecionar | | Shelf não aparece | RockProvider montado? Usuário autenticado? loginImage definido? | | Variações com preço errado | Cada variação precisa de promoção separada na Propz (refId individual) | | Preço Propz > VTEX | Correto — produto não aparece na shelf (regra de menor preço) | | Console: "Missing environment variables" | Env vars no build. Warning pode ser ignorado se funciona no browser |