Skip to main content

💳 Payments with RevenueCat

Shipnative integrates with RevenueCat to simplify in-app purchases and subscriptions across iOS, Android, and Web. RevenueCat handles the complexities of platform-specific billing APIs, allowing you to manage all your subscriptions from a single dashboard.

RevenueCat Project Setup

1. Create Your RevenueCat Account

If you don’t have one, create a free account on RevenueCat:
  1. Go to https://app.revenuecat.com/.
  2. Sign up and create a new project.

2. Configure Your Apps

Connect your mobile app stores and enable web billing:
  • iOS: Link your App Store Connect app.
  • Android: Link your Google Play Console app.
  • Web: Enable Web Billing in your RevenueCat project settings. This typically involves connecting to a payment gateway like Stripe.

3. Obtain API Keys

Retrieve your public API keys from RevenueCat:
  1. In your RevenueCat project dashboard, navigate to Project Settings > API Keys.
  2. Copy the Public SDK keys for each platform: iOS, Android, and Web. The Web Public SDK key is separate from the mobile keys.

4. Configure Environment Variables

During the yarn setup process, you were prompted to enter these keys. If you skipped this or need to update them, you can manually add them to your apps/app/.env file:
EXPO_PUBLIC_REVENUECAT_IOS_KEY=your_ios_key_here
EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=your_android_key_here
EXPO_PUBLIC_REVENUECAT_WEB_KEY=your_web_billing_key_here
Mock Mode: If these environment variables are not set, Shipnative will automatically use a mock RevenueCat service (including on web). You can still exercise the paywall and purchase flows without a live billing configuration.
Important: After adding your API keys, you must create products in RevenueCat. If you see errors about “no products registered” or “offerings”, this is expected and normal - it means your API keys are working but products haven’t been created yet. See step 5 below.

5. Create Products and Entitlements

⚠️ Do this AFTER adding API keys to your .env file. Define your in-app purchase products and entitlements in RevenueCat:
  • Entitlements: Create an entitlement (e.g., “pro”) that represents access to your premium features.
  • Products: Create your subscription products (e.g., “monthly_pro”, “annual_pro”) and link them to the corresponding entitlement.
    • For iOS and Android, these products are configured in App Store Connect and Google Play Console, then linked in RevenueCat.
    • For Web, products are created directly within RevenueCat’s Web Billing dashboard.
  • Offerings: Add your products to an Offering (typically named “default”) so they can be fetched by the app.
Why the order matters: When you add API keys before creating products, RevenueCat will log expected errors about missing products. These errors are normal and will disappear once you create products in the dashboard. The app handles these gracefully and shows a helpful setup message instead of error stack traces.

Architecture Overview

Shipnative’s payment architecture is designed for cross-platform consistency.

Platform Detection

The app automatically detects the platform and uses the appropriate RevenueCat SDK:
  • iOS/Android: Uses the native RevenueCat SDK.
  • Web: Uses the RevenueCat Web SDK (Web Billing). When the web key is missing in development, it falls back to the mock RevenueCat client so you can still test the flow.

Unified Subscription Store

The useSubscriptionStore (found in apps/app/app/stores/subscriptionStore.ts) provides a consistent API for managing subscriptions across all platforms:
import { useSubscriptionStore } from '@/stores/subscriptionStore'
function MyComponent() {
  const {
    isPro,           // Boolean: is user subscribed to the 'pro' entitlement?
    platform,        // Current platform: 'revenuecat' | 'revenuecat-web' | 'mock'
    packages,        // Available subscription packages
    loading,         // True if a purchase or restore operation is in progress
    
    fetchPackages,   // Function to load available packages from RevenueCat
    purchasePackage, // Function to initiate a purchase flow for a given package
    restorePurchases,// Function to restore previous purchases
  } = useSubscriptionStore()
  
  // Use these values in your UI to display paywalls, manage access, etc.
}

Usage Examples

Displaying a Paywall

Shipnative includes a PaywallScreen component that automatically handles platform detection and displays the appropriate UI for available packages.
import { PaywallScreen } from '@/screens/PaywallScreen'
// Render this component to display your subscription options
<PaywallScreen />
Web paywall behavior
  • Provide EXPO_PUBLIC_REVENUECAT_WEB_KEY and a Web Billing offering in RevenueCat to enable real checkout.
  • If the web key is absent in development, the paywall will render using mock offerings so you can validate the UX without payments.
  • If no web offering exists in RevenueCat, the paywall will surface a “No web offering found” message.

Gating Features

Use the isPro status from useSubscriptionStore to gate access to premium features:
import { useSubscriptionStore } from '@/stores/subscriptionStore'
import { PaywallScreen } from '@/screens/PaywallScreen'

function FeatureGate({ children }) {
  const { isPro } = useSubscriptionStore()
  
  if (!isPro) {
    return <PaywallScreen /> // Redirect to paywall if not subscribed
  }
  
  return children // Render premium content
}

Custom Pricing UI

If you need a custom pricing display, you can fetch packages and handle purchases manually:
import { PricingCard } from '@/components/PricingCard' // Example custom component
import { useSubscriptionStore } from '@/stores/subscriptionStore'

function CustomPricing() {
  const { packages, purchasePackage, loading } = useSubscriptionStore()
  
  return (
    <View>
      {packages.map(pkg => (
        <PricingCard
          key={pkg.id}
          title={pkg.title}
          price={pkg.priceString}
          description={pkg.description}
          billingPeriod={pkg.billingPeriod}
          onPress={() => purchasePackage(pkg)}
          loading={loading}
        />
      ))}
    </View>
  )
}

Testing Your Integration

Mock Mode Testing

  1. Ensure RevenueCat API keys are not set in your apps/app/.env file.
  2. Run your app (yarn ios or yarn android).
  3. Navigate to your paywall screen.
  4. Initiate a “purchase” – it will simulate success after a short delay.
  5. Verify that the app recognizes the “pro” status.
  6. Restart the app; the “pro” status should persist (in mock mode).

Testing with Real Services

  • iOS:
    1. Add your EXPO_PUBLIC_REVENUECAT_IOS_KEY to apps/app/.env.
    2. Configure a sandbox tester account in App Store Connect.
    3. Run yarn ios and test purchases using the sandbox account.
  • Android:
    1. Add your EXPO_PUBLIC_REVENUECAT_ANDROID_KEY to apps/app/.env.
    2. Configure a test account in Google Play Console.
    3. Run yarn android and test purchases using the test account.
  • Web:
    1. Add your EXPO_PUBLIC_REVENUECAT_WEB_KEY to apps/app/.env.
    2. Create a Web Billing offering in the RevenueCat dashboard.
    3. Run your Expo web build (yarn app:web) and complete checkout via RevenueCat Web Billing (Stripe test mode works if your connected gateway is in test).

Webhooks (Advanced)

For production environments, setting up webhooks is crucial for real-time synchronization of subscription statuses.

RevenueCat Webhooks

RevenueCat provides robust webhook support:
  1. In your RevenueCat project dashboard, go to Project Settings > Integrations > Webhooks.
  2. Add your webhook URL.
  3. RevenueCat will automatically send events for mobile and web purchases, allowing your backend to update user subscription statuses in real-time.

Troubleshooting

RevenueCat errors about “no products registered” or “offerings”

Problem: You see errors in the console like:
  • "Error fetching offerings - The operation couldn't be completed"
  • "There are no products registered in the RevenueCat dashboard"
  • "RevenueCat SDK Configuration is not valid"
Solution:
  • This is EXPECTED and NORMAL when you’ve added API keys but haven’t created products yet.
  • These errors will automatically disappear once you:
    1. Create products in RevenueCat (see step 5 in setup)
    2. Add products to an Offering
    3. Restart your app
  • The app handles these errors gracefully and shows a helpful setup message instead of error stack traces.
  • You can safely ignore these errors if you’re not using subscriptions yet.

”Mock mode” warning in production

Problem: Your app shows a mock mode warning for RevenueCat even with API keys configured. Solution:
  • Verify that your apps/app/.env file exists and is correctly configured with EXPO_PUBLIC_REVENUECAT_IOS_KEY, EXPO_PUBLIC_REVENUECAT_ANDROID_KEY, and EXPO_PUBLIC_REVENUECAT_WEB_KEY.
  • Ensure your environment variables are prefixed with EXPO_PUBLIC_.
  • Restart your Metro bundler with yarn app:start --clear to ensure environment variables are reloaded.

Packages not loading

Problem: Your paywall shows no available packages. Solution:
  • Double-check that your RevenueCat API keys are correct.
  • Verify that you have created and linked products and entitlements in RevenueCat.
  • For web, ensure Web Billing is enabled and web products are created in RevenueCat.
  • Check your console for any error messages from RevenueCat.
  • Try calling fetchPackages() manually in your code to debug.

Purchase not completing

Problem: A purchase flow starts but doesn’t complete successfully. Solution:
  • Check the browser console (for web) or device logs (for mobile) for errors.
  • Verify that your payment gateway (e.g., Stripe for web) is correctly connected and configured in RevenueCat.
  • Ensure the user is logged in before attempting a purchase.

Subscription status not persisting

Problem: A user’s subscription status resets after app restart or backgrounding. Solution:
  • Verify that the underlying storage mechanism (e.g., MMKV) is working correctly.
  • Check for any errors during the initialization of the useSubscriptionStore.
  • Ensure restorePurchases() is called appropriately (e.g., on app launch).