npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

a0-purchases

v0.4.1

Published

Lightweight subscription management for AI apps with auto-detecting providers

Downloads

63

Readme

a0-purchases

A unified cross-platform in-app purchase library for React Native and Web with RevenueCat-level features.

Installation

npm install a0-purchases
# or
yarn add a0-purchases
# or
pnpm add a0-purchases

Features

  • 🌐 Cross-platform: Works on iOS, Android, and Web
  • 🔄 Automatic user management: Anonymous users with seamless aliasing
  • 🎯 Simple API: One unified interface across all platforms
  • 🔌 Platform adapters: Native IAP for mobile, Stripe for web
  • 🏃 Zero configuration: Works out of the box
  • 🐛 Debug mode: Comprehensive logging for development

Quick Start

Basic Setup (Direct API)

import { Purchases } from 'a0-purchases';

// Initialize the SDK
await Purchases.initialize({
  debug: true // Enable debug logs in development
});

// Check if user has premium access
if (Purchases.isPremium()) {
  // Unlock premium features
}

// Get available packages
const offerings = Purchases.getOfferings();
const package = offerings.current?.availablePackages[0];

// Make a purchase
try {
  const result = await Purchases.purchase(package.identifier);
  console.log('Purchase successful!', result.customerInfo);
} catch (error) {
  console.error('Purchase failed', error);
}

React Integration

import { A0PurchaseProvider, useA0Purchases } from 'a0-purchases';

// Wrap your app with the provider
function App() {
  return (
    <A0PurchaseProvider config={{ debug: true }}>
      <YourApp />
    </A0PurchaseProvider>
  );
}

// Use the hook in your components
function PremiumButton() {
  const { 
    isPremium, 
    isAnonymous,
    purchase, 
    logIn,
    isLoading 
  } = useA0Purchases();
  
  if (isPremium) {
    return <Text>You have premium!</Text>;
  }
  
  return (
    <View>
      <Button 
        onPress={() => purchase('premium_monthly')}
        disabled={isLoading}
      >
        Upgrade to Premium
      </Button>
      
      {isAnonymous && (
        <Text onPress={() => logIn('user_123')}>
          Sign in to save your purchase
        </Text>
      )}
    </View>
  );
}

User ID Management

The SDK provides flexible user ID management with automatic aliasing support:

Anonymous Users (Default)

// Initialize without a user ID - creates anonymous user
await Purchases.initialize();

console.log(Purchases.getUserId()); // "$AnonymousUser:abc123..."
console.log(Purchases.isAnonymous()); // true

Custom User IDs

// Initialize with your own user ID
await Purchases.initialize({
  appUserId: 'user_12345'
});

console.log(Purchases.getUserId()); // "user_12345"
console.log(Purchases.isAnonymous()); // false

Anonymous to Identified (Aliasing)

// Start with anonymous user
await Purchases.initialize();

// User makes purchases while anonymous
await Purchases.purchase('premium_monthly');

// Later, after user signs in
await Purchases.logIn('user_12345');

// The anonymous user is now aliased with the identified user
// All purchases are transferred automatically

Note: Aliasing only works from anonymous → identified users. You cannot alias two identified users together for security reasons.

Auth State Synchronization

The SDK automatically keeps purchase user state in sync with your app's auth state:

// User logs in to your app
await Purchases.initialize({ appUserId: 'user_123' });

// Later, user logs out of your app
// Initialize without appUserId to reset to anonymous
await Purchases.initialize();
// Creates new anonymous user - old custom ID is cleared

// This prevents users from staying logged in to purchases
// after logging out of your app

API Reference

Core Methods

  • Purchases.initialize(config?) - Initialize the SDK
  • Purchases.getCustomerInfo() - Get current user's purchase info
  • Purchases.getOfferings() - Get available packages
  • Purchases.isPremium() - Quick check for premium access
  • Purchases.isAnonymous() - Check if current user is anonymous
  • Purchases.getUserId() - Get current user ID
  • Purchases.purchase(packageId) - Make a purchase
  • Purchases.restore() - Restore purchases (mobile only)
  • Purchases.refreshCustomerInfo() - Sync with backend
  • Purchases.logIn(userId) - Switch to identified user
  • Purchases.logOut() - Sign out current user
  • Purchases.getManageSubscriptionUrl() - Get subscription management URL
  • Purchases.subscribe(listener) - Listen to state changes
  • Purchases.destroy() - Clean up resources

Configuration Options

interface PurchasesConfig {
  // Enable debug logging (default: false)
  debug?: boolean;
  
  // Optional custom user ID (default: anonymous)
  appUserId?: string;
}

React Hook API

const {
  isPremium,        // boolean - has active premium
  isLoading,        // boolean - operation in progress
  isAnonymous,      // boolean - is current user anonymous
  userId,           // string | null - current user ID
  purchase,         // (packageId: string) => Promise<void>
  restore,          // () => Promise<void>
  logIn,            // (userId: string) => Promise<void>
  logOut,           // () => Promise<void>
  refreshCustomerInfo, // () => Promise<void>
  getCustomerInfo,  // () => CustomerInfo | null
  offerings,        // PurchasesOfferings
} = useA0Purchases();

React Hook Examples

function UserProfile() {
  const { userId, isAnonymous, logIn, logOut } = useA0Purchases();
  
  if (isAnonymous) {
    return (
      <Button onPress={() => logIn('user_123')}>
        Sign In to Save Purchases
      </Button>
    );
  }
  
  return (
    <View>
      <Text>Logged in as: {userId}</Text>
      <Button onPress={logOut}>Sign Out</Button>
    </View>
  );
}

function SubscriptionManager() {
  const { isPremium, purchase, restore, refreshCustomerInfo } = useA0Purchases();
  
  return (
    <View>
      {isPremium ? (
        <Text>Premium Active ✓</Text>
      ) : (
        <Button onPress={() => purchase('premium_monthly')}>
          Upgrade to Premium
        </Button>
      )}
      <Button onPress={restore}>Restore Purchases</Button>
      <Button onPress={refreshCustomerInfo}>Refresh Status</Button>
    </View>
  );
}

Platform Support

| Platform | Purchase Method | Restore | Notes | |----------|----------------|---------|-------| | iOS | StoreKit (via expo-iap) | ✅ | Native Apple payments | | Android | Google Play Billing | ✅ | Native Google payments | | Web | Stripe Checkout | N/A | Redirects to hosted checkout |

Best Practices

✅ DO: Let the Library Handle User IDs

// GOOD: Let the library manage user state
function App() {
  const userIdFromAuth = getCurrentUserId(); // undefined if logged out
  
  return (
    <A0PurchaseProvider config={{ 
      appUserId: userIdFromAuth, // Pass through your auth state
      debug: __DEV__ 
    }}>
      <YourApp />
    </A0PurchaseProvider>
  );
}

// BAD: Hardcoding user IDs
<A0PurchaseProvider config={{ appUserId: "123" }}>

✅ DO: Handle Missing Offerings

// GOOD: Check for offerings before using
const { offerings } = useA0Purchases();
const packages = offerings?.current?.availablePackages || [];

// BAD: Assuming offerings exist
Object.values(offerings.all)[0].availablePackages // Can crash!

✅ DO: Use the Provider Directly

// GOOD: Use A0PurchaseProvider directly
export function App() {
  return (
    <A0PurchaseProvider>
      <MainScreen />
    </A0PurchaseProvider>
  );
}

// UNNECESSARY: Double-wrapping contexts
function SubscriptionProvider({ children }) {
  return (
    <A0PurchaseProvider>
      <AnotherProvider>
        {children}
      </AnotherProvider>
    </A0PurchaseProvider>
  );
}

✅ DO: Sync with Your Auth System

// GOOD: Keep purchase user in sync with app auth
function App() {
  const { user } = useAuth(); // Your auth system
  
  return (
    <A0PurchaseProvider config={{ 
      appUserId: user?.id // undefined when logged out = anonymous
    }}>
      <YourApp />
    </A0PurchaseProvider>
  );
}

✅ DO: Handle All Purchase States

function PurchaseButton() {
  const { purchase, isLoading, isPremium } = useA0Purchases();
  
  if (isPremium) return <Text>Already Premium!</Text>;
  if (isLoading) return <ActivityIndicator />;
  
  return (
    <Button 
      onPress={async () => {
        try {
          await purchase('premium_monthly');
        } catch (error) {
          if (error.userCancelled) {
            // User cancelled - no action needed
          } else {
            Alert.alert('Purchase failed', error.message);
          }
        }
      }}
      title="Upgrade"
    />
  );
}

Debugging

Enable debug mode to see detailed logs:

await Purchases.initialize({ debug: true });

This will log:

  • Network requests to the backend
  • User ID changes and aliasing
  • Purchase flow steps
  • Platform adapter operations

Troubleshooting

ChunkLoadError in Web Environments

If you encounter ChunkLoadError: Loading chunk # failed in production, this is typically caused by:

  1. Deployment timing: Users have the old version loaded while you deploy a new version
  2. CDN caching: Old chunk files are cached but no longer exist
  3. Bundler issues: Dynamic imports creating chunks that aren't properly deployed

Solution in v0.2.5+: The library now uses static imports instead of dynamic imports to prevent chunk splitting issues.

If you still encounter this error:

  • Clear browser cache
  • Ensure all build artifacts are deployed together
  • Consider using service workers to handle version mismatches

Migration from RevenueCat

// RevenueCat
Purchases.configure({ apiKey: "..." });
const offerings = await Purchases.getOfferings();
await Purchases.purchasePackage(package);

// a0-purchases
await Purchases.initialize();
const offerings = Purchases.getOfferings();
await Purchases.purchase(package.identifier);

Error Handling

The library uses typed errors with specific error codes:

try {
  await Purchases.purchase('premium_monthly');
} catch (error) {
  if (error.userCancelled) {
    // User cancelled the purchase
  } else if (error.code === PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_ERROR) {
    // Product not available
  } else {
    // Other error
    console.error(error.message);
  }
}

License

MIT