Mobile Payment Integration: Apple Pay, Google Pay, and Stripe Mobile SDK in 2026
Integrate Apple Pay, Google Pay, and Stripe Mobile SDK in React Native. In-app purchases with RevenueCat, subscription management, payment sheet implementation, and 3DS handling.
Mobile Payment Integration: Apple Pay, Google Pay, and Stripe Mobile SDK in 2026
Payments on mobile are fundamentally different from web. You can't just render a Stripe Elements form in a WebView and call it done. Native payment sheets (Apple Pay, Google Pay) dramatically increase conversion โ often 30โ50% over card entry forms โ because users don't have to enter 16 digits on a small keyboard. For subscriptions, RevenueCat handles App Store and Play Store billing so you don't have to implement it yourself.
This post covers the full mobile payment stack in React Native: Stripe's @stripe/stripe-react-native for card and wallet payments, and RevenueCat for in-app subscriptions.
Payment Architecture: Native vs. WebView
| Approach | Conversion | Maintenance | Apple Pay / Google Pay | App Store Rules |
|---|---|---|---|---|
| Native Stripe SDK | โ Best | Medium | โ Yes | Compliant |
| WebView โ Stripe.js | โ ๏ธ Lower | Low | โ No | โ ๏ธ Gray area |
| RevenueCat (subscriptions) | โ Best | Low | โ Yes (via StoreKit/Billing) | โ Required for IAP |
| Custom payment sheet | โ ๏ธ Variable | High | โ Possible | Compliant |
Important: For in-app subscriptions and digital goods on iOS, App Store rules require using Apple's StoreKit (in-app purchase) โ you cannot use Stripe. For physical goods, services, and B2B SaaS, Stripe is fine.
Stripe React Native: Setup
npx expo install @stripe/stripe-react-native
# iOS: cd ios && pod install
// App.tsx โ wrap app with StripeProvider
import { StripeProvider } from '@stripe/stripe-react-native';
export default function App() {
return (
<StripeProvider
publishableKey={process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY!}
merchantIdentifier="merchant.com.myapp" // Required for Apple Pay
urlScheme="myapp" // For 3DS redirect back
>
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</StripeProvider>
);
}
๐ Looking for a Dev Team That Actually Delivers?
Most agencies sell you a project manager and assign juniors. Viprasol is different โ senior engineers only, direct Slack access, and a 5.0โ Upwork record across 100+ projects.
- React, Next.js, Node.js, TypeScript โ production-grade stack
- Fixed-price contracts โ no surprise invoices
- Full source code ownership from day one
- 90-day post-launch support included
Payment Sheet: One-Time Payments
The Payment Sheet is Stripe's pre-built UI โ handles card entry, Apple Pay, Google Pay, and 3DS in a single bottom sheet:
// src/api/payments.ts โ create PaymentIntent server-side
export async function createPaymentIntent(
amount: number, // cents
currency: string,
customerId: string,
): Promise<{ clientSecret: string; ephemeralKey: string; customer: string }> {
const response = await fetch(`${API_BASE}/payments/intent`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await getAuthToken()}`,
},
body: JSON.stringify({ amount, currency, customerId }),
});
if (!response.ok) {
throw new Error('Failed to create payment intent');
}
return response.json();
}
// src/screens/CheckoutScreen.tsx
import { useStripe } from '@stripe/stripe-react-native';
import { useCallback, useState } from 'react';
export function CheckoutScreen({ order }: { order: Order }) {
const { initPaymentSheet, presentPaymentSheet } = useStripe();
const [loading, setLoading] = useState(false);
const handleCheckout = useCallback(async () => {
setLoading(true);
try {
// 1. Create PaymentIntent on your server
const { clientSecret, ephemeralKey, customer } = await createPaymentIntent(
order.totalCents,
order.currency,
order.customerId,
);
// 2. Initialize Payment Sheet
const { error: initError } = await initPaymentSheet({
merchantDisplayName: 'MyApp Inc.',
customerId: customer,
customerEphemeralKeySecret: ephemeralKey,
paymentIntentClientSecret: clientSecret,
// Enable Apple Pay / Google Pay
applePay: {
merchantCountryCode: 'US',
},
googlePay: {
merchantCountryCode: 'US',
testEnv: __DEV__,
},
// Show billing address collection
billingDetailsCollectionConfiguration: {
name: 'automatic',
email: 'automatic',
},
// Allow saving payment method for future use
setupFutureUsage: 'off_session',
// Appearance customization
appearance: {
colors: {
primary: '#2563EB',
background: '#FFFFFF',
componentBackground: '#F9FAFB',
primaryText: '#111827',
secondaryText: '#6B7280',
},
shapes: {
borderRadius: 12,
borderWidth: 1,
},
},
});
if (initError) {
Alert.alert('Error', initError.message);
return;
}
// 3. Present Payment Sheet
const { error: presentError } = await presentPaymentSheet();
if (presentError) {
if (presentError.code !== 'Canceled') {
Alert.alert('Payment failed', presentError.message);
}
return;
}
// 4. Payment succeeded โ verify on server and fulfill order
await fulfillOrder(order.id);
navigation.replace('OrderConfirmation', { orderId: order.id });
} catch (err) {
Alert.alert('Error', 'Something went wrong. Please try again.');
} finally {
setLoading(false);
}
}, [order]);
return (
<View style={styles.container}>
<OrderSummary order={order} />
<Pressable
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleCheckout}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>
Pay ${(order.totalCents / 100).toFixed(2)}
</Text>
)}
</Pressable>
</View>
);
}
Apple Pay: Native Integration
For a fully native Apple Pay button (without the Payment Sheet wrapper):
// src/components/ApplePayButton.tsx
import { ApplePayButton, useApplePay } from '@stripe/stripe-react-native';
export function NativeApplePayButton({ order }: { order: Order }) {
const { isApplePaySupported, presentApplePay, confirmApplePayPayment } = useApplePay();
const handleApplePay = async () => {
if (!isApplePaySupported) return;
const { error } = await presentApplePay({
cartItems: [
...order.items.map((item) => ({
label: item.name,
amount: (item.price * item.quantity / 100).toFixed(2),
type: 'final' as const,
})),
{
label: 'Tax',
amount: (order.taxCents / 100).toFixed(2),
type: 'final',
},
{
label: 'MyApp Inc.', // Must match merchant name
amount: (order.totalCents / 100).toFixed(2),
type: 'final',
},
],
country: 'US',
currency: order.currency.toUpperCase(),
shippingMethods: [],
requiredBillingContactFields: ['emailAddress'],
});
if (error) {
Alert.alert('Apple Pay Error', error.message);
return;
}
// Get client secret from server and confirm
const { clientSecret } = await createPaymentIntent(
order.totalCents, order.currency, order.customerId,
);
const { error: confirmError } = await confirmApplePayPayment(clientSecret);
if (!confirmError) {
await fulfillOrder(order.id);
navigation.replace('OrderConfirmation', { orderId: order.id });
}
};
if (!isApplePaySupported) return null;
return (
<ApplePayButton
onPress={handleApplePay}
type="buy"
buttonStyle="black"
borderRadius={8}
style={{ width: '100%', height: 50 }}
/>
);
}
๐ Senior Engineers. No Junior Handoffs. Ever.
You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.
- MVPs in 4โ8 weeks, full platforms in 3โ5 months
- Lighthouse 90+ performance scores standard
- Works across US, UK, AU timezones
- Free 30-min architecture review, no commitment
RevenueCat: In-App Subscriptions
For digital subscriptions sold through the App Store / Play Store, use RevenueCat โ it abstracts StoreKit 2 (iOS) and Google Play Billing Library 5 (Android) behind a unified API:
npx expo install react-native-purchases
// src/lib/revenuecat.ts
import Purchases, { LOG_LEVEL, CustomerInfo } from 'react-native-purchases';
import { Platform } from 'react-native';
export async function initRevenueCat(userId: string): Promise<void> {
const apiKey = Platform.select({
ios: process.env.EXPO_PUBLIC_RC_IOS_KEY!,
android: process.env.EXPO_PUBLIC_RC_ANDROID_KEY!,
})!;
await Purchases.configure({ apiKey, appUserID: userId });
if (__DEV__) {
Purchases.setLogLevel(LOG_LEVEL.DEBUG);
}
}
export interface SubscriptionOffering {
identifier: string;
monthly?: { price: number; priceString: string; productId: string };
annual?: { price: number; priceString: string; productId: string };
}
export async function getOfferings(): Promise<SubscriptionOffering[]> {
const offerings = await Purchases.getOfferings();
return Object.values(offerings.all).map((offering) => ({
identifier: offering.identifier,
monthly: offering.monthly ? {
price: offering.monthly.product.price,
priceString: offering.monthly.product.priceString,
productId: offering.monthly.product.identifier,
} : undefined,
annual: offering.annual ? {
price: offering.annual.product.price,
priceString: offering.annual.product.priceString,
productId: offering.annual.product.identifier,
} : undefined,
}));
}
export async function purchaseSubscription(
packageIdentifier: string,
offeringIdentifier: string,
): Promise<CustomerInfo> {
const offerings = await Purchases.getOfferings();
const offering = offerings.all[offeringIdentifier];
const pkg = offering.availablePackages.find(
(p) => p.identifier === packageIdentifier,
);
if (!pkg) throw new Error('Package not found');
const { customerInfo } = await Purchases.purchasePackage(pkg);
return customerInfo;
}
export async function restorePurchases(): Promise<CustomerInfo> {
return Purchases.restorePurchases();
}
export async function hasActiveSubscription(entitlement: string): Promise<boolean> {
const info = await Purchases.getCustomerInfo();
return info.entitlements.active[entitlement] !== undefined;
}
// src/screens/SubscriptionScreen.tsx
import { useEffect, useState } from 'react';
import { getOfferings, purchaseSubscription, SubscriptionOffering } from '@/lib/revenuecat';
export function SubscriptionScreen() {
const [offerings, setOfferings] = useState<SubscriptionOffering[]>([]);
const [selected, setSelected] = useState<'monthly' | 'annual'>('annual');
const [purchasing, setPurchasing] = useState(false);
useEffect(() => {
getOfferings().then(setOfferings);
}, []);
const handlePurchase = async () => {
const offering = offerings[0];
if (!offering) return;
const pkg = selected === 'annual' ? offering.annual : offering.monthly;
if (!pkg) return;
setPurchasing(true);
try {
await purchaseSubscription(
selected === 'annual' ? '$rc_annual' : '$rc_monthly',
offering.identifier,
);
// Update app state โ user is now subscribed
navigation.replace('Dashboard');
} catch (err: any) {
if (!err.userCancelled) {
Alert.alert('Purchase Failed', err.message);
}
} finally {
setPurchasing(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Choose your plan</Text>
{/* Plan toggle */}
<View style={styles.toggle}>
{(['monthly', 'annual'] as const).map((plan) => (
<Pressable
key={plan}
style={[styles.toggleOption, selected === plan && styles.toggleSelected]}
onPress={() => setSelected(plan)}
>
<Text style={selected === plan ? styles.toggleTextSelected : styles.toggleText}>
{plan === 'monthly' ? 'Monthly' : 'Annual (save 40%)'}
</Text>
</Pressable>
))}
</View>
{/* Price display */}
{offerings[0] && (
<View style={styles.priceBox}>
<Text style={styles.price}>
{selected === 'annual'
? offerings[0].annual?.priceString
: offerings[0].monthly?.priceString}
</Text>
<Text style={styles.priceSubtext}>
{selected === 'annual' ? 'per year' : 'per month'}
</Text>
</View>
)}
<Pressable
style={[styles.ctaButton, purchasing && styles.ctaDisabled]}
onPress={handlePurchase}
disabled={purchasing}
>
{purchasing ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.ctaText}>Start Free Trial</Text>
)}
</Pressable>
<Text style={styles.legal}>
Free 7-day trial. Cancel anytime. Payment charged through{' '}
{Platform.OS === 'ios' ? 'App Store' : 'Google Play'}.
</Text>
</View>
);
}
Server-Side: Handling RevenueCat Webhooks
// src/api/webhooks/revenuecat.ts
export async function revenueCatWebhookHandler(
req: FastifyRequest,
reply: FastifyReply,
): Promise<void> {
const event = req.body as RevenueCatWebhookEvent;
// Verify webhook secret
const secret = req.headers['authorization'];
if (secret !== `Bearer ${process.env.REVENUECAT_WEBHOOK_SECRET}`) {
return reply.status(401).send();
}
reply.status(200).send(); // Respond immediately
const userId = event.app_user_id;
const entitlement = 'premium';
switch (event.type) {
case 'INITIAL_PURCHASE':
case 'RENEWAL':
await db.query(
`UPDATE users SET subscription_status = 'active', subscription_expires_at = $1
WHERE id = $2`,
[new Date(event.expiration_at_ms), userId],
);
break;
case 'CANCELLATION':
case 'EXPIRATION':
await db.query(
`UPDATE users SET subscription_status = 'cancelled' WHERE id = $1`,
[userId],
);
break;
case 'BILLING_ISSUE':
await sendEmail({ to: event.subscriber_attributes?.email, template: 'billing_issue' });
break;
}
}
Payment Security Checklist
## Mobile Payment Security
### Client-side
- [ ] Never log payment tokens or card data
- [ ] Use Stripe SDK โ never handle raw card data
- [ ] Certificate pinning for production API calls
- [ ] Store tokens in Keychain (iOS) / Keystore (Android), not AsyncStorage
### Server-side
- [ ] Verify PaymentIntent on server before fulfilling order (don't trust client)
- [ ] Idempotency keys on all Stripe API calls
- [ ] Webhook signature verification (Stripe-Signature header)
- [ ] RevenueCat webhook secret verification
### App Store compliance
- [ ] Digital goods sold through App Store use StoreKit (IAP)
- [ ] Physical goods can use Stripe
- [ ] Subscription management links to App Store manage subscriptions
Working With Viprasol
Our mobile team integrates Stripe and RevenueCat into React Native apps โ from Apple Pay payment sheets to subscription management and webhook processing.
What we deliver:
- Stripe Payment Sheet integration with Apple Pay and Google Pay
- RevenueCat setup for iOS/Android in-app subscriptions
- Server-side PaymentIntent creation and webhook handlers
- App Store and Play Store subscription compliance review
- 3DS handling and failed payment recovery flows
โ Discuss your mobile payments integration โ Mobile development services
See Also
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need a Modern Web Application?
From landing pages to complex SaaS platforms โ we build it all with Next.js and React.
Free consultation โข No commitment โข Response within 24 hours
Need a custom web application built?
We build React and Next.js web applications with Lighthouse โฅ90 scores, mobile-first design, and full source code ownership. Senior engineers only โ from architecture through deployment.