SubSovereign
All guides

SubSovereign em React (web) — guia de integração

Este guia aborda o @subsovereign/react (sdk-react/) — a camada de IU de paywall para aplicações web em React. Ele renderiza o paywall que você projeta no painel do SubSovereign como componentes React reais, a partir do mesmo renderizador que o painel de pré-visualização usa, para que o que você aprova no painel seja exatamente o que é lançado.

Este SDK desenha o paywall; ele não lida com faturamento ou direitos de acesso (entitlements). Emparelhe-o com o SDK de dados Web/JS (@subsovereign/js-sdk) para checkEntitlements() e validação de compra — os dois foram projetados para serem usados em conjunto.

Antes de começar

Você precisará de um servidor SubSovereign em execução, um aplicativo registrado no painel (um appId e uma chave de API de função SDK), e um paywall publicado (painel → Paywall → Publicar).

Etapa 1 — Instalar

O pacote está em sdk-react/ e ainda não está no npm — adicione-o como uma dependência de caminho ou git (ou copie a pasta para o seu projeto):

npm install ./sdk-react   # path dependency while it's pre-npm
import { Paywall, usePaywallConfig } from '@subsovereign/react';

Etapa 2 — Carregar o paywall publicado

usePaywallConfig busca a configuração para este aplicativo/utilizador. A variante A/B é escolhida no servidor pelo userId, por isso o mesmo utilizador sempre vê o mesmo paywall.

const { config, loading, error } = usePaywallConfig({
  apiUrl: 'https://subs.yourdomain.com/api/v1', // YOUR self-hosted server
  appId:  'your-app-id',
  userId: currentUser.id,                        // your own stable user id
  apiKey: 'YOUR_SDK_KEY',
  locale: 'en',                                  // optional, default 'en'
  platform: 'web',                               // optional, default 'web'
});

Passe null em vez de parâmetros para ignorar a busca (por exemplo, enquanto o utilizador não estiver autenticado).

Etapa 3 — Renderizar

<Paywall
  config={config}
  loading={loading}
  loadingState={<Spinner />}          // optional
  emptyState={<FallbackPaywall />}    // optional — shown when no config is published
  onSelectProduct={(productId) => startCheckout(productId)}
/>

onSelectProduct é acionado quando o utilizador toca num produto — conecte-o ao seu checkout (Stripe na web). Após o sucesso do checkout, valide com o SDK de dados e verifique novamente os direitos de acesso (entitlements):

await SubSovereign.validateStripeSubscription({ subscriptionId, productId, accessLevelId: 'pro' });
const { hasAccess } = await SubSovereign.checkEntitlements();
if (hasAccess) unlockProFeatures();

Se já tiver uma configuração (obtida via SDK de dados), ignore o hook e passe-a diretamente. Para controle total, há o renderizador bruto: <PaywallRenderer config={config} onSelectProduct={…} />.

Botão de desistência (Compliance Passport) — UE

Se vender assinaturas a consumidores da UE online, a Diretiva (UE) 2023/2673 exige uma função de desistência claramente rotulada. O SDK já vem com ela pronta:

import { WithdrawalButton, useWithdrawalConfig } from '@subsovereign/react';

function CancelSubscription({ user, sub }: { user: { id: string; locale: string }; sub: { id: string } }) {
  // Fetches the withdrawal settings AND the notice's wording in the customer's language.
  const { config, strings, error } = useWithdrawalConfig({
    apiUrl: 'https://subs.yourdomain.com/api/v1',
    appId: 'your-app-id',
    apiKey: 'YOUR_SDK_KEY',
    locale: user.locale,          // the CUSTOMER's language, not your console's
  });
  // Log it: a silent failure here is how a tenant ships English to German customers and never finds
  // out. The customer is never shown this — they get the button, in English.
  if (error) console.warn('SubSovereign: withdrawal settings unavailable', error);
  if (config && config.enabled === false) return null;   // the tenant turned it off

  return (
    <WithdrawalButton
      apiUrl="https://subs.yourdomain.com/api/v1"
      appId="your-app-id"
      apiKey="YOUR_SDK_KEY"
      userId={user.id}
      subscriptionId={sub.id}
      strings={strings}           // ← without this the dialog is ENGLISH for every customer
      locale={user.locale}        // ← and without this the DATE is formatted for the device
      labelKey={config?.labelKey} // ← the label the tenant chose in the console
      appearance="auto"
    />
  );
}

Não pode alterar a redação. O painel oferece duas etiquetas para a função de desistência — "Desista do contrato aqui" e "Cancelar aqui" — e labelKey transporta aquela que o locatário escolheu; as próprias palavras vêm do servidor na língua do cliente. Não há como passar o seu próprio texto. Uma prop label costumava aceitar texto livre e agora é ignorada com um aviso no painel: uma notificação estatutária que pode ser reescrita é aquela que pode ser suavizada para marketing, razão pela qual também não pode passar uma cor.

🚩 Passe também locale. Ele formata o carimbo de data/hora no reconhecimento. Sem ele, a data é formatada para o dispositivo, por isso um cliente alemão num navegador americano vê 9/3/2026 — que lerão como 9 de março, não 3 de setembro. Trata-se da data no registo de um ato legal, e a ambiguidade funciona nos dois sentidos.

🚩 Passe strings, ou a notificação estatutária ficará em inglês para todos. As palavras são fornecidas pelo seu servidor SubSovereign no idioma que pedir — são nossas em vez das suas de propósito, para que uma notificação legal não possa ser suavizada para marketing — e o botão renderiza aquilo que recebe. Ele próprio não busca as palavras, porque o mesmo contrato de componente tem de funcionar no Roku, onde um componente de IU não pode fazer rede. useWithdrawalConfig é a única linha que conecta os dois.

Se a busca falhar, o botão ainda aparece, em inglês, em vez de não aparecer. Falhar em colocar uma função de desistência encontrável à frente do consumidor é sua infração; mostrá-la na língua errada, não. O hook retorna error para que possa registar isso; nunca mostre ao cliente.

Aparência — light, dark ou auto (padrão), e nada mais. O diálogo pinta o seu próprio cartão e o seu próprio texto, por isso é legível em qualquer página, clara ou escura; auto segue a definição do navegador do visitante, e onde nenhuma preferência puder ser detetada, resolve para escuro. Não há deliberadamente forma de passar uma cor: esta é uma notificação estatutária, e a sua redação e cores são fixas para que seja lida da mesma forma por todos os clientes de todos os locatários. Se espelhar a regra noutro local na sua IU, resolveAppearance(appearance, scheme) é exportado (scheme é 'light' | 'dark' | null).

Ele mostra o botão rotulado proeminente, confirma uma vez, envia idempotentemente (uma nova tentativa de rede nunca pode criar duas desistências), e mostra o reconhecimento. O próprio botão não faz nenhuma rede para além do envio, por isso cabe a si fazer o controle com config.enabled e config.jurisdictions, como acima. Peças de nível inferior (fetchWithdrawalConfig, useWithdrawal, submitWithdrawal) são exportadas para IUs personalizadas.

Referência rápida

O que deseja… Use
Carregar o paywall publicado usePaywallConfig(params){ config, loading, error }
Desenhá-lo <Paywall config onSelectProduct … />
Controlo total de renderização <PaywallRenderer config onSelectProduct />
Desistência (UE), na língua do cliente useWithdrawalConfig({…, locale}) → passe strings, locale e labelKey para <WithdrawalButton>
Direitos de acesso (entitlements) / validação o guia Web & React Native.

Próximos passos