SubSovereign
All guides

SubSovereign op React (web) — integratiegids

Deze gids behandelt @subsovereign/react (sdk-react/) — de paywall-UI-laag voor React-webapps. Het rendert de paywall die je ontwerpt in het SubSovereign-dashboard als echte React-componenten, met dezelfde renderer die het dashboardpreview gebruikt. Wat je daar goedkeurt, is precies wat naar de klant gaat.

Deze SDK tekent de paywall; hij regelt niet facturering of toegangsrechten. Combineer hem met de Web/JS-data-SDK (@subsovereign/js-sdk) voor checkEntitlements() en aankoopvalidatie — de twee zijn ontworpen om samen te werken.

Voordat je begint

Je hebt een draaiende SubSovereign-server nodig, een app geregistreerd in het dashboard (een appId en een SDK-rol API-sleutel), en een gepubliceerde paywall (dashboard → Paywall → Publiceren).

Stap 1 — Installeren

Het pakket staat in sdk-react/ en is nog niet op npm beschikbaar — voeg het toe als pad- of git-afhankelijkheid (of kopieer de map in je project):

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

Stap 2 — De gepubliceerde paywall laden

usePaywallConfig haalt de configuratie voor deze app/gebruiker op. De A/B-variant wordt server-side gekozen op basis van userId, zodat dezelfde gebruiker altijd dezelfde paywall ziet.

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'
});

Geef null door in plaats van parameters om het ophalen over te slaan (bijv. terwijl de gebruiker is uitgelogd).

Stap 3 — Rendert het

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

onSelectProduct wordt geactiveerd wanneer de gebruiker op een product tikt — koppel het aan je checkout (Stripe op web). Na een geslaagde checkout valideer je met de data-SDK en controleer je de toegangsrechten opnieuw:

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

Als je al een configuratie hebt (opgehaald via de data-SDK), sla dan de hook over en geef de configuratie direct door. Voor volledige controle is er de ruwe renderer: <PaywallRenderer config={config} onSelectProduct={…} />.

EU-herroepingsknop (Compliance Passport)

Als je abonnementen verkoopt aan EU-consumenten online, vereist Richtlijn (EU) 2023/2673 een duidelijk gelabelde herroepingsfunctie (fail-closed). De SDK levert deze kant-en-klaar:

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"
    />
  );
}

Je kunt de tekst niet wijzigen. De console biedt twee labels voor de herroepingsfunctie — "Withdraw from contract here" en "Cancel here" — en labelKey bevat degene die de tenant heeft gekozen; de woorden zelf komen van de server in de taal van de klant. Er is geen manier om je eigen tekst door te geven. Een label-prop accepteerde eerder vrije tekst en wordt nu genegeerd met een consolewaarschuwing: een wettelijke mededeling die kan worden herformuleerd, is er een die kan worden verzacht tot marketing — dezelfde reden waarom je geen kleur kunt doorgeven.

🚩 Geef ook locale door. Dit formatteert de tijdstempel op de bevestiging. Zonder deze wordt de datum geformatteerd voor het apparaat, dus een Duitse klant op een Amerikaans browserscherm ziet 9/3/2026 — wat zij lezen als 9 maart, niet 3 september. Het is de datum op het verslag van een juridische handeling, en de ambiguïteit werkt beide kanten op.

🚩 Geef strings door, anders staat de wettelijke mededeling voor iedereen in het Engels. De woorden worden geleverd door jouw SubSovereign-server in de gevraagde taal — ze zijn van ons in plaats van van jou, zodat een wettelijke mededeling niet kan worden verzacht tot marketing — en de knop rendert wat hij krijgt. Hij haalt ze niet zelf op, omdat hetzelfde componentcontract ook moet werken op Roku, waar een UI-component geen netwerkverkeer kan maken. useWithdrawalConfig is de regel die de twee verbindt.

Mislukt het ophalen, dan verschijnt de knop nog steeds, in het Engels, in plaats van niet te verschijnen. Het niet tonen van een vindbare herroepingsfunctie voor de consument is jouw overtreding; het tonen in de verkeerde taal niet. De hook retourneert error zodat je dat kunt loggen; toon het nooit aan de klant.

Uiterlijk — light, dark of auto (de standaard), en niets anders. De dialoog tekent zijn eigen kaart en eigen tekst, zodat hij leesbaar is op elke pagina, licht of donker; auto volgt de voorkeur van de bezoeker, en waar geen voorkeur kan worden gedetecteerd, kiest het voor donker. Er is bewust geen manier om een kleur door te geven: dit is een wettelijke mededeling, en zowel de tekst als de kleuren zijn vastgelegd, zodat het voor elke klant van elke tenant hetzelfde leest. Als je de regel elders in je UI toepast, is resolveAppearance(appearance, scheme) geëxporteerd (scheme is 'light' | 'dark' | null).

Het toont de opvallende gelabelde knop, bevestigt eenmaal, dient idempotent in (een netwerkpoging kan nooit twee herroepingen maken) en toont de bevestiging. De knop zelf doet geen netwerkwerkzaamheden buiten de submit, dus het afschermen op config.enabled en config.jurisdictions is aan jou. Lagere-niveau onderdelen (fetchWithdrawalConfig, useWithdrawal, submitWithdrawal) zijn geëxporteerd voor aangepaste UIs.

Snelle referentie

Je wilt… Gebruik
De gepubliceerde paywall laden usePaywallConfig(params){ config, loading, error }
Het rendert <Paywall config onSelectProduct … />
Volledige controle over rendering <PaywallRenderer config onSelectProduct />
EU-herroeping in de taal van de klant useWithdrawalConfig({…, locale}) → geef strings, locale en labelKey door aan <WithdrawalButton>
Toegangsrechten / validatie de Web & React Native-gids

Volgende stappen