Payment Implementation

Learn how to implement subscription payments in your a0 app using the a0-purchases library.
The a0 payment system uses the a0-purchases library. All apps built with a0 come with the necessary configuration and providers built in.

Prerequisites

Before implementing payments in your code, ensure you have:
1

Completed Payment Setup

Created your features, plans, and offerings in the a0 dashboard. See the Payment Setup guide.
2

Synced to Providers

Synced your plans to Stripe and/or Apple App Store Connect.
3

Created Offerings

Created at least one offering and set it as current - this is required for paywalls to display products.

Building a Paywall

Here’s a simple paywall component using the useA0Purchases hook:
import { useA0Purchases } from 'a0-purchases';

function PaywallScreen() {
  const { 
    isPremium, 
    isLoading,
    offerings,
    purchase,
    restore,
  } = useA0Purchases();

  // Get packages from offerings - REQUIRED for paywall to work
  const packages = offerings?.all?.[0]?.availablePackages || [];

  const handlePurchase = async (packageId) => {
    try {
      await purchase(packageId);
      if (isPremium) {
        // Success! Navigate to premium content
      }
    } catch (error) {
      if (!error.userCancelled) {
        // Show error to user
      }
    }
  };

  if (isLoading) {
    return <ActivityIndicator />;
  }

  if (packages.length === 0) {
    return <Text>No products available</Text>;
  }

  return (
    <View>
      {packages.map((pkg) => (
        <TouchableOpacity
          key={pkg.identifier}
          onPress={() => handlePurchase(pkg.identifier)}
        >
          <Text>{pkg.product.title}</Text>
          <Text>{pkg.product.priceString}</Text>
          <Text>
            {pkg.packageType === 'WEEKLY' ? 'per week' : 
             pkg.packageType === 'MONTHLY' ? 'per month' : 
             pkg.packageType === 'ANNUAL' ? 'per year' : ''}
          </Text>
        </TouchableOpacity>
      ))}
      
      <Button title="Restore Purchases" onPress={restore} />
    </View>
  );
}
Your paywall will show no products if you haven’t created offerings. Offerings are REQUIRED for the paywall to work.

Checking Premium Status

The simplest way to gate content is using the isPremium property:
function PremiumContent() {
  const { isPremium } = useA0Purchases();
  
  if (!isPremium) {
    return <PaywallScreen />;
  }
  
  return <YourPremiumContent />;
}

Checking Specific Entitlements

isPremium returns true if the user has ANY active entitlement. If you have multiple features/entitlements and need to check specific ones, use getCustomerInfo().
function FeatureGatedContent() {
  const { getCustomerInfo } = useA0Purchases();
  const customerInfo = getCustomerInfo();
  
  // Check for specific entitlements
  const hasProFeature = customerInfo?.entitlements.active['PRO'];
  const hasAdFree = customerInfo?.entitlements.active['AD_FREE'];
  const hasUnlimitedStorage = customerInfo?.entitlements.active['UNLIMITED_STORAGE'];
  
  return (
    <View>
      {hasProFeature && <ProContent />}
      {hasAdFree ? <ContentWithoutAds /> : <ContentWithAds />}
      {hasUnlimitedStorage && <UnlimitedUploadButton />}
    </View>
  );
}

Advanced Implementation

Custom Paywall with Package Details

function DetailedPaywallScreen() {
  const { offerings, purchase, isPremium } = useA0Purchases();
  const currentOffering = offerings?.current;
  
  if (!currentOffering) {
    return <Text>Loading products...</Text>;
  }
  
  // Get different package types
  const weeklyPackage = currentOffering.weekly;
  const monthlyPackage = currentOffering.monthly;
  const annualPackage = currentOffering.annual;
  
  return (
    <View>
      <Text>Choose your plan:</Text>
      
      {weeklyPackage && (
        <PlanOption 
          package={weeklyPackage}
          onPress={() => purchase(weeklyPackage.identifier)}
        />
      )}
      
      {monthlyPackage && (
        <PlanOption 
          package={monthlyPackage}
          onPress={() => purchase(monthlyPackage.identifier)}
          highlighted={true} // Most popular
        />
      )}
      
      {annualPackage && (
        <PlanOption 
          package={annualPackage}
          onPress={() => purchase(annualPackage.identifier)}
          savings="Save 25%"
        />
      )}
    </View>
  );
}

Handling Purchase States

function PurchaseButton({ packageId }) {
  const { purchase } = useA0Purchases();
  const [isPurchasing, setIsPurchasing] = useState(false);
  const [error, setError] = useState(null);
  
  const handlePurchase = async () => {
    setIsPurchasing(true);
    setError(null);
    
    try {
      const result = await purchase(packageId);
      // Success - the hook will update isPremium automatically
      navigation.navigate('Success');
    } catch (err) {
      if (err.userCancelled) {
        // User cancelled - no need to show error
      } else if (err.code === 'PRODUCT_ALREADY_PURCHASED') {
        // User already has this product
        setError('You already have an active subscription');
      } else {
        // Other error
        setError('Purchase failed. Please try again.');
      }
    } finally {
      setIsPurchasing(false);
    }
  };
  
  return (
    <>
      <Button 
        title="Subscribe Now"
        onPress={handlePurchase}
        disabled={isPurchasing}
      />
      {error && <Text style={{ color: 'red' }}>{error}</Text>}
    </>
  );
}

Testing Purchases

Test Credentials

Stripe Test Cards:
  • Success: 4242 4242 4242 4242
  • Decline: 4000 0000 0000 0002
  • More test cards in Stripe docs
Apple Sandbox:
  • Use App Store Connect sandbox tester accounts
  • Subscriptions auto-renew every few minutes for testing
  • Clear purchase history in Settings → App Store → Sandbox Account

Testing Flow

1

Enable Test Mode

For Stripe, ensure you synced to the sandbox environment in your payment setup.
2

Test Purchase Flow

Make test purchases using the test credentials above.
3

Verify Entitlements

Check that isPremium updates correctly and specific entitlements are active.
4

Test Restore

Clear app data and test the restore purchases functionality.

Troubleshooting

Next Steps