# How to Add Payments to an Expo / React Native App

> Learn how to add payments to your Expo or React Native app using Dodo Payments and WebBrowser.openBrowserAsync() to avoid the 30% app store fee for SaaS products.
- **Author**: Aarthi Poonia
- **Published**: 2026-03-24
- **Category**: Payments, Mobile, How-To
- **URL**: https://dodopayments.com/blogs/add-payments-react-native-expo

---

Building a mobile app with Expo or React Native is a fantastic way to reach users on both iOS and Android with a single codebase. However, when it comes to monetization, many developers hit a wall. The Apple App Store and Google Play Store both take a 30% cut of all in-app purchases. For a SaaS product with thin margins, this can be a deal-breaker.

In this guide, we will explore how to add payments to your Expo or React Native app using Dodo Payments. We will focus on a strategy that uses external payment links to avoid the 30% platform fee, while remaining compliant with store policies for SaaS and digital services. We will cover everything from setting up your backend to handling deep links for a seamless user experience.

## The 30% Problem and the SaaS Loophole

Apple and Google have strict rules about in-app purchases. Generally, if you are selling digital goods that are consumed within the app, you must use their native payment systems. However, there is a well-known exception for "multi-platform" services. If your app is a companion to a web-based service, you can allow users to pay on your website and then access those features in the app.

> We designed Dodo's API for the solo developer building at midnight. If you need a team of payment engineers to integrate, we have failed at our job.
>
> \- Ayush Agarwal, Co-founder & CPTO at Dodo Payments

By using Dodo Payments to generate checkout links, you can direct users to a secure web-based checkout. This allows you to keep 100% of your revenue (minus Dodo's small transaction fee) while providing a professional payment experience. Dodo Payments also acts as a Merchant of Record, handling all the global tax and compliance issues that come with selling across borders.

## Setting Up Your Expo Environment

To get started, you will need an existing Expo or React Native project. We will use the `expo-web-browser` and `expo-linking` packages to handle the checkout flow and the return trip to your app.

```bash
npx expo install expo-web-browser expo-linking
```

These packages provide a native-feeling browser experience within your app and allow you to handle deep links when the user completes their payment.

## Generating Checkout Links on the Backend

You should never generate payment links directly from your mobile app. This would require exposing your secret API keys, which is a major security risk. Instead, create a simple endpoint on your backend that calls the Dodo Payments API and returns a checkout URL.

```javascript
// Example Node.js/Express backend
const DodoPayments = require("dodopayments");
const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});

app.post("/create-checkout", async (req, res) => {
  const { productId, userEmail } = req.body;

  try {
    const session = await client.checkoutSessions.create({
      product_cart: [{ product_id: productId, quantity: 1 }],
      customer: { email: userEmail },
      return_url: "myapp://payment-success",
    });
    res.json({ url: session.checkout_url });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
```

Notice the `return_url`. This is a custom scheme that will trigger your app to open once the payment is complete. You will need to configure this scheme in your Expo project.

## Configuring Deep Links in Expo

To allow your app to be opened via a custom URL scheme, you need to add the `scheme` property to your `app.json` or `app.config.js` file.

```json
{
  "expo": {
    "name": "My App",
    "slug": "my-app",
    "scheme": "myapp",
    "ios": {
      "bundleIdentifier": "com.yourname.myapp"
    },
    "android": {
      "package": "com.yourname.myapp"
    }
  }
}
```

With this configuration, any link starting with `myapp://` will attempt to open your application. This is the key to creating a seamless transition from the web checkout back to your native UI.

## Implementing the Checkout Flow in React Native

Now, let's build the UI component that triggers the payment process. We will use `WebBrowser.openBrowserAsync` to open the Dodo Payments checkout page in a secure, in-app browser window.

```tsx
import React from "react";
import { Button, Alert } from "react-native";
import * as WebBrowser from "expo-web-browser";
import * as Linking from "expo-linking";

export default function PaymentButton({ productId }) {
  const handlePress = async () => {
    try {
      // 1. Fetch the checkout URL from your backend
      const response = await fetch("https://dodopayments.com/api/create-checkout", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ productId, userEmail: "user@example.com" }),
      });
      const { url } = await response.json();

      // 2. Open the checkout URL in the in-app browser
      const result = await WebBrowser.openBrowserAsync(url);

      // 3. Handle the result (optional, deep links are more reliable)
      if (result.type === "cancel") {
        Alert.alert(
          "Payment Cancelled",
          "You closed the browser before finishing.",
        );
      }
    } catch (error) {
      Alert.alert("Error", "Could not start checkout process.");
    }
  };

  return <Button title="Upgrade to Pro" onPress={handlePress} />;
}
```

Using `WebBrowser` instead of a standard `Linking.openURL` call keeps the user inside your app's context. On iOS, this uses `SFSafariViewController`, and on Android, it uses Chrome Custom Tabs, both of which are highly trusted by users.
## Optimizing the User Experience

A payment flow is only as good as its user experience. In a mobile app, users expect speed and clarity. When they click a "Buy" or "Upgrade" button, they should see immediate feedback.

```tsx
const [loading, setLoading] = useState(false);

const handlePress = async () => {
  setLoading(true);
  try {
    const response = await fetch('https://dodopayments.com/api/create-checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ productId, userEmail: 'user@example.com' }),
    });
    const { url } = await response.json();
    await WebBrowser.openBrowserAsync(url);
  } catch (error) {
    Alert.alert('Error', 'Could not start checkout process.');
  } finally {
    setLoading(false);
  }
};
```

By adding a loading state, you prevent users from clicking the button multiple times and provide a sense of progress. You should also consider adding a "Success" screen that is triggered by the deep link callback, rather than just logging to the console.

## Security Considerations for Mobile Payments

Security is paramount when handling payments. While Dodo Payments handles the sensitive card data, you are responsible for the security of your own API and the integrity of your app.

- **Token-Based Authentication**: Ensure that your `/create-checkout` endpoint is protected by a secure authentication token (like a JWT). Only authenticated users should be able to generate payment links.
- **Input Validation**: Always validate the `productId` and other parameters on your backend. Never trust data coming directly from the mobile app.
- **HTTPS Everywhere**: Ensure all communication between your app and your backend happens over HTTPS. This prevents man-in-the-middle attacks from intercepting sensitive data.
- **Certificate Pinning**: For high-security applications, consider implementing certificate pinning to ensure your app only communicates with your specific backend server.

## Comparing Browser-Based vs. Native SDK Approaches

Choosing between a browser-based checkout and a native SDK depends on your priorities.

| Feature | Browser-Based (WebBrowser) | Native SDK |
| --- | --- | --- |
| **Implementation Speed** | Very Fast | Moderate |
| **User Experience** | Good (Native-feeling) | Excellent (Fully Native) |
| **Platform Fee Avoidance** | High (Easier to justify) | Lower (More scrutiny) |
| **Customization** | Limited to Dodo Dashboard | High (Native UI components) |
| **Maintenance** | Low (Dodo updates the web UI) | Moderate (SDK updates required) |

For most SaaS startups, the browser-based approach is the best starting point. it is easier to implement, requires less maintenance, and provides a clear path to avoiding the 30% platform fee. As you scale and your needs become more complex, you can always transition to the native SDK for a more polished experience.

## Case Studies: SaaS Apps Avoiding the 30% Fee

Many successful SaaS companies use the "reader app" or "multi-platform" model to avoid platform fees.

- **Netflix**: Users cannot sign up or pay for a subscription inside the Netflix app. They must do so on the website, and then they can log in to the app to watch content.
- **Spotify**: Similar to Netflix, Spotify has historically directed users to their website for premium subscriptions to avoid the "Apple Tax."
- **Basecamp**: The team at Basecamp has been vocal about their use of external payment methods for their project management software.

By following these examples, you can build a sustainable business model that doesn't rely on the whims of app store gatekeepers. Dodo Payments provides the infrastructure to make this model work seamlessly for your business.

## Handling the Payment Callback

When the user finishes the payment, Dodo Payments will redirect them to the `return_url` you specified. You need to listen for this event in your app to update the UI and confirm the purchase.

```tsx
import { useEffect } from "react";
import * as Linking from "expo-linking";

export function usePaymentHandler() {
  useEffect(() => {
    const handleUrl = (event) => {
      const { path, queryParams } = Linking.parse(event.url);
      if (path === "payment-success") {
        // Update user state, show success message, etc.
        console.log("Payment successful!", queryParams);
      }
    };

    const subscription = Linking.addEventListener("url", handleUrl);

    // Check if the app was opened via a deep link
    Linking.getInitialURL().then((url) => {
      if (url) handleUrl({ url });
    });

    return () => subscription.remove();
  }, []);
}
```

This hook should be placed high up in your component tree, such as in your `App.js` or a main navigation wrapper. It ensures that your app reacts immediately when the user returns from the checkout.

## Payment Flow Architecture

The following diagram shows how the different pieces of the system interact during a typical payment session.

```mermaid
flowchart LR
    A[Mobile App] -->|"POST /create-checkout"| B[Your Backend]
    B -->|"API Call"| C[Dodo Payments]
    C -->|"Session URL"| B
    B -->|"URL"| A
    A -->|"Open Browser"| D[Dodo Checkout Page]
    D -->|"Payment Success"| E[User Redirect]
    E -->|"myapp://success"| A
    C -->|"Webhook"| B
    B -->|"Update DB"| F[(Database)]
```

This architecture ensures that your backend remains the source of truth. While the deep link provides immediate feedback to the user, the webhook from Dodo Payments is what you should use to actually grant access to features in your database.

## Compliance and Store Policies

It is important to be careful with how you present this payment option. Apple, in particular, is very strict about apps that "direct" users to external payment methods. To stay safe:

- **Don't use "Buy" buttons** that lead directly to a website if you are selling purely digital goods.
- **Use a "Manage Subscription"** or "Account Settings" area to provide the link.
- **Frame it as a multi-platform service**. Mention that users can also manage their account on your website.
- **Avoid mentioning "App Store fees"** or "cheaper prices" in the app UI.

For many SaaS products, this approach is perfectly legal and widely used by companies like Netflix, Spotify, and Basecamp.

## Merchant of Record Benefits for Mobile

Selling globally on mobile adds another layer of complexity. Every country has its own rules for digital taxes (like VAT in the EU or GST in India). If you were to handle this yourself, you would need to register for tax in dozens of jurisdictions.

By using Dodo Payments as your Merchant of Record, you offload all of this. Dodo handles the tax calculation, collection, and remittance. They also handle compliance with local laws and regulations, ensuring that your app is always on the right side of the law. This is especially valuable for solo founders and small teams who don't have a dedicated legal or accounting department.

## Advanced: Using the React Native SDK

While the browser-based approach is great for avoiding fees, Dodo Payments also offers a native React Native SDK for a more integrated experience. This is particularly useful for apps operating in regions where alternative payment methods are legally mandated, such as the EU under the Digital Markets Act (DMA).

The SDK provides a `presentPaymentSheet` function that opens a native UI for collecting card details. This can lead to higher conversion rates as it feels more integrated into the app's design.

```tsx
import { useCheckout } from "dodopayments-react-native-sdk";

const { initPaymentSession, presentPaymentSheet } = useCheckout();

const handleNativePayment = async () => {
  const { clientSecret } = await fetchPaymentParams();
  await initPaymentSession({ clientSecret });
  const result = await presentPaymentSheet();
  // Handle result...
};
```

However, keep in mind that using a native SDK might still trigger platform fee requirements depending on your region and the type of product you are selling. Always review the latest [App Store Fee Bypass Guide](https://dodopayments.com/blogs/sell-software-without-app-store) before deciding on your implementation strategy.

## Testing Your Mobile Integration

Testing mobile payments can be tricky. Expo provides a great environment for this with the Expo Go app. You can test your deep links and browser integration directly on your physical device.

1. **Use Test Mode**: Ensure your Dodo Payments API keys are set to `test_mode`.
2. **Test Deep Links**: Use the `npx uri-scheme open` command to test if your app responds correctly to your custom scheme.
3. **Verify Webhooks**: Use a tool like `ngrok` to pipe Dodo's webhooks to your local development server.

## Conclusion

Adding payments to an Expo or React Native app doesn't have to mean giving up 30% of your hard-earned revenue. By leveraging Dodo Payments and a smart web-based checkout strategy, you can build a profitable SaaS that scales globally.

The combination of `expo-web-browser` for the checkout and `expo-linking` for the return trip provides a smooth user experience that feels native while keeping you in control of your margins. With Dodo Payments handling the tax and compliance, you are free to focus on what matters most: building a great product for your users.

For more insights on mobile monetization, check out our guides on [embedded payments for SaaS](https://dodopayments.com/blogs/embedded-payments-saas) and [subscription pricing models](https://dodopayments.com/blogs/subscription-pricing-models). You can also learn more about the [merchant of record model](https://dodopayments.com/blogs/merchant-of-record-for-saas) and how it simplifies global commerce.

Ready to take your mobile app to the next level? [Sign up for Dodo Payments](https://dodopayments.com) and start accepting payments today.

## FAQ

### Does this approach violate Apple's App Store policies?

For SaaS products and multi-platform services, this approach is generally compliant as long as you don't explicitly direct users to the website to "save money" or bypass fees. Many major apps use this "reader app" or "multi-platform" model successfully.

### Can I use Apple Pay or Google Pay with this method?

Yes! When you open the Dodo Payments checkout in a `WebBrowser` window, the user will have access to all the payment methods enabled in your Dodo dashboard, including Apple Pay and Google Pay, provided they are supported in the browser context.

### How do I handle subscription cancellations?

Subscription management should ideally happen on your website or through a dedicated "Manage Subscription" link in your app that opens the Dodo Payments customer portal. Your backend will receive a `subscription.cancelled` webhook, allowing you to update the user's access in real-time.

### Is the React Native SDK better than the browser approach?

The SDK provides a more "native" feel, which can improve conversion. However, the browser approach is often easier to implement and is more flexible for avoiding platform fees in certain jurisdictions. The best choice depends on your specific product and target market.

### What happens if the user closes the browser before paying?

The `WebBrowser.openBrowserAsync` function returns a result object with a `type: 'cancel'` if the user closes the window manually. You can use this to show a helpful message or offer assistance to the user.

## Final Take

Monetizing a mobile app is a strategic decision. By choosing Dodo Payments, you are not just choosing a payment processor; you are choosing a partner that helps you navigate the complex world of global taxes, platform fees, and compliance. Whether you go with a browser-based flow or a native SDK integration, Dodo has the tools you need to succeed.

Explore our [integration guide](https://docs.dodopayments.com/developer-resources/integration-guide) and [mobile integration docs](https://docs.dodopayments.com/developer-resources/mobile-integration) to get started. Your journey to a more profitable mobile app starts here.
---
- [More Payments articles](https://dodopayments.com/blogs/category/payments)
- [All articles](https://dodopayments.com/blogs)