@sftech/ng-revenue
v0.2.0
Published
Angular library for RevenueCat subscription and entitlement management. Protects premium routes via a backend-verified guard, exposes subscription status, and wraps the RevenueCat SDK for purchase flows on both web and native (Capacitor) platforms.
Readme
@sftech/ng-revenue
Angular library for RevenueCat subscription and entitlement management. Protects premium routes via a backend-verified guard, exposes subscription status, and wraps the RevenueCat SDK for purchase flows on both web and native (Capacitor) platforms.
Installation
npm install @sftech/ng-revenueOptional: Purchase flows (P2)
Install the RevenueCat SDK that matches your deployment target. Neither package is required for route protection or status checks.
# Web (browser)
npm install @revenuecat/purchases-js
# Native (iOS / Android via Capacitor)
npm install @revenuecat/purchases-capacitor @capacitor/coreConfiguration
1. Add config keys to your environment config
// environment.ts or config loaded via APP_INITIALIZER
export const appConfig = {
apiUrl: 'https://api.example.com/api',
REVENUECAT_PUBLIC_KEY: 'appl_xxxxxxxxxxxxxxxx',
// Optional overrides (defaults shown):
SUBSCRIPTION_API_URL: 'https://api.example.com/api/subscription', // defaults to apiUrl + '/subscription'
REVENUECAT_ENTITLEMENT_ID: 'premium', // defaults to 'premium'
REVENUECAT_UPGRADE_REDIRECT: '/upgrade', // defaults to '/upgrade'
};REVENUECAT_PUBLIC_KEY is the only required key. The mapper throws a descriptive error if it is missing.
2. Register the module in app.config.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { RevenueModule, RevenueConfigMapper } from '@sftech/ng-revenue';
export function appConfig(config: Record<string, unknown>): ApplicationConfig {
return {
providers: [
// ...other providers
importProvidersFrom(
RevenueModule.forRoot(RevenueConfigMapper.map(config)),
),
],
};
}RevenueConfigMapper.map() validates the config and applies defaults. Pass its return value directly to RevenueModule.forRoot().
Features
Route Protection with entitlementGuard
Protects routes from users without an active subscription. The guard queries the backend on every navigation attempt and is fail-closed: any HTTP error redirects to the upgrade path instead of granting access.
import { Routes } from '@angular/router';
import { entitlementGuard } from '@sftech/ng-revenue';
export const routes: Routes = [
{
path: 'premium-feature',
loadComponent: () => import('./premium/premium.component').then((m) => m.PremiumComponent),
canActivate: [entitlementGuard],
},
];The redirect target is IRevenueConfig.upgradeRedirectPath (mapped from REVENUECAT_UPGRADE_REDIRECT, default '/upgrade').
Subscription Status Checking with SubscriptionApiService
Query the backend subscription status directly in any component or service. Useful for conditional UI rendering without gating an entire route.
import { Component, inject } from '@angular/core';
import { SubscriptionApiService, SubscriberStatus } from '@sftech/ng-revenue';
@Component({
selector: 'app-profile',
standalone: true,
template: `<p>Premium: {{ status?.isPremium }}</p>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProfileComponent {
private subscriptionApi = inject(SubscriptionApiService);
status: SubscriberStatus | null = null;
ngOnInit(): void {
this.subscriptionApi.getEntitlementStatus().subscribe({
next: (status) => {
this.status = status;
console.log('Premium:', status.isPremium);
console.log('Expires:', status.expiresAt);
console.log('Active subscriptions:', status.activeSubscriptions);
},
});
}
}getEntitlementStatus() never throws. When the backend is unreachable it returns SubscriberStatus.free() (all fields set to their zero/null values).
Purchase Flows with RevenueService (optional SDK)
RevenueService wraps the RevenueCat SDK with automatic platform detection. It uses the Capacitor plugin on native platforms and the web SDK in a browser. Both paths share the same method signatures.
Requires one of the optional SDK packages listed in the Installation section.
import { Component, inject } from '@angular/core';
import { RevenueService, Offering, Package } from '@sftech/ng-revenue';
@Component({
selector: 'app-upgrade',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UpgradeComponent {
private revenueService = inject(RevenueService);
offerings: Offering[] = [];
async ngOnInit(): Promise<void> {
// Call initialize() once per session, passing the current user's ID
await this.revenueService.initialize('user-123');
this.offerings = await this.revenueService.getOfferings();
}
async purchase(pkg: Package): Promise<void> {
const success = await this.revenueService.purchasePackage(pkg);
if (success) {
// Navigate to premium content
}
}
async restore(): Promise<void> {
await this.revenueService.restorePurchases();
}
async signOut(): Promise<void> {
await this.revenueService.logout();
}
}isInitialized is a signal you can read to disable purchase buttons before initialize() completes:
readonly isInitialized = this.revenueService.isInitialized;purchasePackage() returns false (not throws) when the user cancels the purchase dialog.
API Reference
Services
| Symbol | Description |
|--------|-------------|
| SubscriptionApiService | Calls GET {apiUrl}/status and returns Observable<SubscriberStatus>. Returns SubscriberStatus.free() on error. |
| RevenueService | Wraps the RevenueCat SDK (web + native). Methods: initialize(userId), getOfferings(), purchasePackage(pkg), restorePurchases(), logout(). Signal: isInitialized. |
Guards
| Symbol | Description |
|--------|-------------|
| entitlementGuard | CanActivateFn — allows navigation when isPremium === true, redirects to upgradeRedirectPath otherwise. Fail-closed. |
Models
| Symbol | Description |
|--------|-------------|
| SubscriberStatus | User subscription state. Fields: isPremium, entitlement, expiresAt (Date \| null), activeSubscriptions. Factories: fromDto(), free(). |
| Offering | RevenueCat offering. Fields: identifier, serverDescription, availablePackages. Factory: fromSdkOffering(). |
| Package | RevenueCat package within an offering. Fields: identifier, packageType, product, rcBillingProduct. Factory: fromSdkPackage(). |
| Product | RevenueCat product. Fields: identifier, title, description, priceString, price, currencyCode. Factory: fromSdkProduct(). |
Enums
| Symbol | Values | Description |
|--------|--------|-------------|
| ERevenuePlatform | IOS, ANDROID, STRIPE, AMAZON, MACOS, PLAY_STORE | Platform identifiers used by RevenueCat. |
| ESubscriptionApiEndpoint | STATUS = '/status' | Backend endpoint paths appended to apiUrl. |
Configuration
| Symbol | Description |
|--------|-------------|
| IRevenueConfig | Config interface: publicKey, apiUrl, entitlementId, upgradeRedirectPath. |
| RevenueConfigMapper | Maps flat app-config keys to IRevenueConfig. Required key: REVENUECAT_PUBLIC_KEY. |
| REVENUE_CONFIGURATION | InjectionToken<IRevenueConfig> — inject to access config in custom services. |
DTOs
| Symbol | Description |
|--------|-------------|
| IApiResponse<TDto> | Generic backend response wrapper: status, messages?, data?. |
| ISubscriberStatusResponseDto | Raw backend DTO: isPremium, entitlement, expiresAt, activeSubscriptions. |
Module
| Symbol | Description |
|--------|-------------|
| RevenueModule | NgModule with forRoot(config: IRevenueConfig): ModuleWithProviders<RevenueModule>. Registers all services. |
Peer Dependencies
| Package | Version | Required |
|---------|---------|----------|
| @angular/common | >=20.0.0 | Yes |
| @angular/core | >=20.0.0 | Yes |
| @angular/router | >=20.0.0 | Yes |
| @revenuecat/purchases-js | >=1.0.0 | No (web purchase flows only) |
| @revenuecat/purchases-capacitor | >=12.0.0 | No (native purchase flows only) |
| @capacitor/core | >=6.0.0 | No (required alongside purchases-capacitor) |
The three RevenueCat/Capacitor packages are optional. Route protection and status checks work without them.
Backend Requirements
This library connects to a backend that implements the @sftech/nestjs-revenue API.
Required Endpoint:
GET /subscription/status— Returns the current user's subscription state.
Response Format:
{
status: number;
messages?: string[];
data?: {
isPremium: boolean;
entitlement: string | null;
expiresAt: string | null; // ISO 8601 date string
activeSubscriptions: string[];
};
}The endpoint is expected to be secured (authentication required). Token handling is delegated to @sftech/ng-auth — its authenticationInterceptor attaches the Bearer token to outgoing requests automatically.
Changelog
See CHANGELOG.md for a full history of changes.
