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
- Camada de dados (direitos de acesso, compras, RGPD): guia Web & React Native.
- Sites independentes de framework (Vue/Svelte/HTML puro): guia de Web Component.
- Novo nos conceitos? Leia Como funciona o SubSovereign.