@virex-tech/paywallo-sdk
v2.5.3
Published
SDK React Native para integração com Paywallo - Paywalls, Subscriptions e Analytics
Maintainers
Readme
@virex-tech/paywallo-sdk
Paywalls, subscriptions, analytics and feature flags for React Native.
Install
npm install @virex-tech/paywallo-sdkiOS:
cd ios && pod installThat's it. The SDK includes its own native modules for storage, device info and StoreKit — no extra dependencies needed.
Optional: Ad tracking (IDFA)
If your app uses Facebook Ads, AppsFlyer or any attribution that needs the Apple IDFA, install the tracking transparency module:
npm install expo-tracking-transparencyThis enables the ATT prompt ("Allow this app to track your activity?"). Without it, the SDK skips IDFA collection — everything else works normally.
Quick start
1. Wrap your app
import { PaywalloProvider } from '@virex-tech/paywallo-sdk';
export default function App() {
return (
<PaywalloProvider
config={{
appKey: 'your_app_key',
debug: __DEV__,
}}
>
<YourApp />
</PaywalloProvider>
);
}2. Gate content with a paywall
import { PaywalloClient } from '@virex-tech/paywallo-sdk';
const hasAccess = await PaywalloClient.requireSubscriptionWithCampaign('my-campaign');
if (hasAccess) {
// User has an active subscription or just purchased
}3. Check subscription status
const isActive = await PaywalloClient.hasActiveSubscription();Hooks
usePaywallo()
| Property | Type | Description |
|---|---|---|
| isInitialized | boolean | SDK ready |
| isReady | boolean | SDK ready and products loaded |
| distinctId | string | Current user ID |
| presentCampaign(placement) | Promise<CampaignResult> | Show a campaign paywall |
| hasActiveSubscription() | Promise<boolean> | Check subscription status |
| getSubscription() | Promise<Subscription> | Get subscription data |
| restorePurchases() | Promise<RestoreResult> | Restore previous purchases |
useSubscription()
| Property | Type | Description |
|---|---|---|
| subscription | Subscription \| null | Current subscription |
| hasActiveSubscription | boolean | Whether subscription is active |
| isLoading | boolean | Loading state |
| refresh() | () => void | Refetch subscription data |
usePurchase()
| Property | Type | Description |
|---|---|---|
| purchase(productId) | Promise<PurchaseOutcome> | Purchase a product |
| restore() | Promise<IAPPurchase[]> | Restore purchases |
| isPurchasing | boolean | Purchase in progress |
| isRestoring | boolean | Restore in progress |
| error | PurchaseError \| null | Last error |
Plans & Offerings
An offering is a group of products configured in the dashboard that you display together on a paywall. You can swap the active offering in the dashboard without shipping a new app version.
useOfferings()
import { useOfferings, useOfferingPurchase } from "@virex-tech/paywallo-sdk";
// Fetch specific offerings by identifier
const { offerings } = useOfferings(["mensal", "anual", "lifetime"]);
// Fetch all offerings
const { offerings } = useOfferings();
// Purchase
const { purchase } = useOfferingPurchase();
purchase(offering.product.productId);| Property | Type | Description |
|---|---|---|
| offerings | Offering[] | List of offerings |
| isLoading | boolean | Loading state |
useOfferingPurchase()
| Property | Type | Description |
|---|---|---|
| purchase(productId) | Promise<PurchaseOutcome> | Purchase a product from the offering |
| isPurchasing | boolean | Purchase in progress |
Offering type
interface Offering {
id: string;
name: string;
identifier: string;
isCurrent: boolean;
product: OfferingProduct;
}
interface OfferingProduct {
id: string;
name: string;
productId: string; // App Store / Play Store identifier
priceUsd: number;
billingPeriod: string; // "monthly" | "annual" | "lifetime" | ...
trialDays: number | null;
// Intro offer (synced automatically from Apple/Google)
introOfferMode: "FREE_TRIAL" | "PAY_AS_YOU_GO" | "PAY_UP_FRONT" | null;
introOfferPrice: number | null;
introOfferCurrency: string | null;
introOfferCycles: number | null;
}Example
import { useOfferings, useOfferingPurchase } from "@virex-tech/paywallo-sdk";
function Paywall() {
const { offerings, isLoading } = useOfferings(["mensal", "anual"]);
const { purchase, isPurchasing } = useOfferingPurchase();
if (isLoading) return null;
return offerings.map(offering => (
<View key={offering.id}>
<Text>{offering.product.name} — ${offering.product.priceUsd}</Text>
{offering.product.introOfferMode === "FREE_TRIAL" && (
<Text>{offering.product.trialDays}-day free trial</Text>
)}
{offering.product.introOfferMode === "PAY_AS_YOU_GO" && (
<Text>
{offering.product.introOfferCurrency} {offering.product.introOfferPrice}/period
for {offering.product.introOfferCycles} cycles
</Text>
)}
<Button
onPress={() => purchase(offering.product.productId)}
disabled={isPurchasing}
>
Subscribe
</Button>
</View>
));
}Intro offers are synced automatically from App Store Connect and Google Play — no manual setup needed.
What you integrate (and what you don't)
The SDK is designed so the app wires the minimum. Wrap once with <PaywalloProvider> and the SDK initializes itself (no init() call) and tracks lifecycle automatically.
| Concern | Who handles it |
|---|---|
| App lifecycle (session_start/end, app_opened, cold_start, install) | Automatic — just mount the Provider |
| Paywall (paywall_viewed/closed) | Automatic |
| Screen views | Opt-in helper (one line, see below) |
| Onboarding steps | You declare them (with explicit order) |
| User identity (identify) | Optional — never required for attribution |
| Sales / subscriptions | Server webhooks (Apple/Google/Superwall/RevenueCat) — the SDK does not send purchases |
Onboarding tracking
Declare each step with an explicit order — the funnel respects it instead of guessing by timestamp. Use decimals for A/B variants of the same step (e.g. 2 and 2.1).
import { PaywalloClient } from '@virex-tech/paywallo-sdk';
await PaywalloClient.onboardingStep(1, 'welcome');
await PaywalloClient.onboardingStep(2, 'goals');
await PaywalloClient.onboardingStep(2.1, 'goals_variant_b'); // A/B variant of step 2Screen tracking (opt-in, one line)
Plug the helper into your NavigationContainer once — every screen change emits screen_view automatically, with no per-screen track calls:
import { usePaywalloScreenTracking } from '@virex-tech/paywallo-sdk';
const onStateChange = usePaywalloScreenTracking();
<NavigationContainer onStateChange={onStateChange}>...</NavigationContainer>Works with React Navigation v6/v7 (no extra peer dependency).
Identify users
Call identify after login (and on every logged-in app launch) to link the device to your user:
await PaywalloClient.identify({
email: user.email, // optional — improves ad-platform matching (CAPI)
firstName: user.firstName, // optional
properties: {
userId: user.id, // REQUIRED for per-user features — your backend's user id
},
});Key rules:
properties.userIdis what links the user. It becomesexternal_user_idserver-side and is the value you pass asrecipientsinPOST /v1/notifications/trigger. Callingidentify({ email })alone does not link the user — push tokens stay anonymous and per-user sends silently target zero devices (skippedReason: "no_active_tokens").- Order doesn't matter. The link is retroactive: tokens registered before
identify(e.g. during onboarding, pre-login) are linked whenidentifyruns. - Call it on every logged-in launch, not just once.
identifyis idempotent and queued durably (offline-safe), so repeating it is cheap — and it self-heals devices that missed a previous attempt. reset()unlinks the device. It regenerates the anonymous id (use it on logout/account switch). After the next login, callidentifyagain or new tokens stay anonymous.
Imperative API
For use outside React components:
import { PaywalloClient } from '@virex-tech/paywallo-sdk';
// Identify user (see "Identify users" above — properties.userId is what links the user).
// Model fields: email, firstName, lastName, dateOfBirth, gender, phone, properties.
await PaywalloClient.identify({ email: '[email protected]', properties: { userId: 'user-123' } });
// Custom events
await PaywalloClient.track('my_event', { properties: { plan: 'premium' } });
// Feature flags
const variant = await PaywalloClient.getVariant('new_feature');
// Subscription gate
const hasAccess = await PaywalloClient.requireSubscriptionWithCampaign('premium');Purchases are not sent from the SDK. Validate the receipt to unlock access (
usePurchase/requireSubscriptionWithCampaigndo this), and the sale is recorded from the store webhook on the server.
Config options
interface PaywalloConfig {
appKey: string; // Required — your app key from the dashboard
apiUrl?: string; // API URL (default: https://paywallo.com.br)
debug?: boolean; // Enable debug logs (default: false)
autoStartSession?: boolean; // Auto-start session on init (default: true)
sessionFlags?: string[]; // Flags to resolve during session start
}Types
interface Subscription {
productId: string;
status: 'active' | 'expired' | 'cancelled' | 'in_grace_period';
expiresAt: Date | null;
platform: 'ios' | 'android';
autoRenewEnabled: boolean;
}
interface CampaignResult {
presented: boolean;
purchased: boolean;
restored: boolean;
cancelled: boolean;
error?: Error;
skippedReason?: 'subscriber';
}
interface PurchaseOutcome {
status: 'success' | 'cancelled' | 'failed';
purchase?: IAPPurchase;
error?: PurchaseError;
}License
MIT
