Documentation Index Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
Use this file to discover all available pages before exploring further.
Quick Start Get your mobile payment integration running in 4 simple steps
Platform Examples Complete code examples for Android, iOS, React Native, and Flutter
Prerequisites
Before integrating Dodo Payments into your mobile app, ensure you have:
Dodo Payments Account : Active merchant account with API access
API Credentials : API key and webhook secret key from your dashboard
Mobile App Project : Android, iOS, React Native, or Flutter application
Backend Server : To securely handle checkout session creation
Integration Workflow
The mobile integration follows a secure 4-step process where your backend handles API calls and your mobile app manages the user experience.
Backend: Create Checkout Session
Checkout Session API Docs Learn how to create a checkout session in your backend using Node.js, Python, and more. See complete examples and parameter references in the dedicated Checkout Sessions API documentation.
Security : Checkout sessions must be created on your backend server, never in the mobile app. This protects your API keys and ensures proper validation.
Mobile: Get Checkout URL
Your mobile app calls your backend to get the checkout URL: func getCheckoutURL ( productId : String , customerEmail : String , customerName : String ) async throws -> String {
let url = URL ( string : "https://your-backend.com/api/create-checkout-session" ) !
var request = URLRequest ( url : url)
request. httpMethod = "POST"
request. setValue ( "application/json" , forHTTPHeaderField : "Content-Type" )
let requestData: [ String : Any ] = [
"productId" : productId,
"customerEmail" : customerEmail,
"customerName" : customerName
]
request. httpBody = try JSONSerialization. data ( withJSONObject : requestData)
let (data, _ ) = try await URLSession. shared . data ( for : request)
let response = try JSONDecoder (). decode (CheckoutResponse. self , from : data)
return response. checkout_url
}
suspend fun getCheckoutURL (productId: String , customerEmail: String , customerName: String ): String {
val client = OkHttpClient ()
val requestBody = JSONObject (). apply {
put ( "productId" , productId)
put ( "customerEmail" , customerEmail)
put ( "customerName" , customerName)
}. toString (). toRequestBody ( "application/json" . toMediaType ())
val request = Request. Builder ()
. url ( "https://your-backend.com/api/create-checkout-session" )
. post (requestBody)
. build ()
val response = client. newCall (request). execute ()
val responseBody = response.body?. string ()
val jsonResponse = JSONObject (responseBody ?: "" )
return jsonResponse. getString ( "checkout_url" )
}
const getCheckoutURL = async ( productId , customerEmail , customerName ) => {
try {
const response = await fetch ( 'https://your-backend.com/api/create-checkout-session' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
productId ,
customerEmail ,
customerName
})
});
const data = await response . json ();
return data . checkout_url ;
} catch ( error ) {
console . error ( 'Failed to get checkout URL:' , error );
throw error ;
}
};
Security : Mobile apps only communicate with your backend, never directly with Dodo Payments API.
Mobile: Open Checkout in Browser
Open the checkout URL in a secure in-app browser for payment processing.
See platform-specific integration examples View full code and setup instructions for Android, iOS, and Flutter mobile payments.
Backend: Handle Payment Completion
Process payment completion via webhooks and redirect URLs to confirm payment status.
Choose your mobile platform below for complete implementation examples:
Android
iOS
React Native
Flutter
Android Integration Chrome Custom Tabs Implementation // Add Chrome Custom Tabs dependency to build.gradle
implementation 'androidx.browser:browser:1.5.0'
// In your Activity
class PaymentActivity : AppCompatActivity () {
private var customTabsSession: CustomTabsSession ? = null
private var customTabsClient: CustomTabsClient ? = null
private var customTabsServiceConnection: CustomTabsServiceConnection ? = null
override fun onCreate (savedInstanceState: Bundle ?) {
super . onCreate (savedInstanceState)
// Initialize Custom Tabs
customTabsServiceConnection = object : CustomTabsServiceConnection () {
override fun onCustomTabsServiceConnected (name: ComponentName , client: CustomTabsClient ) {
customTabsClient = client
customTabsClient?. warmup ( 0L )
customTabsSession = customTabsClient?. newSession ( object : CustomTabsCallback () {
override fun onNavigationEvent (navigationEvent: Int , extras: Bundle ?) {
// Handle navigation events
}
})
}
override fun onServiceDisconnected (name: ComponentName ) {
customTabsClient = null
}
}
CustomTabsClient. bindCustomTabsService (
this ,
"com.android.chrome" ,
customTabsServiceConnection !!
)
// Get checkout URL from backend and launch
lifecycleScope. launch {
try {
val checkoutURL = getCheckoutURL ( "prod_123" , "customer@example.com" , "Customer Name" )
val customTabsIntent = CustomTabsIntent. Builder (customTabsSession)
. build ()
customTabsIntent. launchUrl ( this@PaymentActivity , Uri. parse (checkoutURL))
} catch (e: Exception ) {
// Handle error
Log. e ( "PaymentActivity" , "Failed to get checkout URL" , e)
}
}
}
}
iOS Integration SFSafariViewController Implementation import SafariServices
class PaymentViewController : UIViewController {
override func viewDidLoad () {
super . viewDidLoad ()
Task {
do {
let checkoutURL = try await getCheckoutURL (
productId : "prod_123" ,
customerEmail : "customer@example.com" ,
customerName : "Customer Name"
)
if let url = URL ( string : checkoutURL) {
let safariVC = SFSafariViewController ( url : url)
present (safariVC, animated : true , completion : nil )
}
} catch {
// Handle error
print ( "Failed to get checkout URL: \( error ) " )
}
}
}
}
Deep Link Handling Configure URL schemes to handle payment completion redirects: 1. Set up URL schemes in Info.plist: < key > CFBundleURLTypes </ key >
< array >
< dict >
< key > CFBundleURLName </ key >
< string > myapp </ string >
< key > CFBundleURLSchemes </ key >
< array >
< string > myapp </ string >
</ array >
</ dict >
</ array >
2. Handle redirects in AppDelegate: func application ( _ app : UIApplication, open url : URL, options : [UIApplication.OpenURLOptionsKey : Any ] = [ : ]) -> Bool {
if url.scheme == "myapp" && url.host == "payment-status" {
NotificationCenter. default . post ( name : . paymentCompleted , object : url)
return true
}
return false
}
3. Listen for payment completion: NotificationCenter. default . addObserver ( self , selector : #selector ( handlePaymentCompletion (_:)), name : . paymentCompleted , object : nil )
@objc func handlePaymentCompletion ( _ notification : Notification) {
if let url = notification.object as? URL {
safariVC ? . dismiss ( animated : true )
// Parse payment status from URL parameters
let components = URLComponents ( url : url, resolvingAgainstBaseURL : false )
let paymentId = components ? . queryItems ? . first ( where : { $0 . name == "payment_id" }) ? . value
let status = components ? . queryItems ? . first ( where : { $0 . name == "status" }) ? . value
// Handle payment completion
}
}
React Native Integration Using Checkout Links (InAppBrowser) Open Dodo’s hosted checkout page in an in-app browser. InAppBrowser Implementation import React from 'react' ;
import { View , TouchableOpacity , Text , Alert } from 'react-native' ;
import { InAppBrowser } from 'react-native-inappbrowser-reborn' ;
const PaymentButton = ({ productId , onPaymentComplete }) => {
const openPaymentLink = async () => {
try {
// Get checkout URL from backend
const checkoutURL = await getCheckoutURL (
productId ,
'customer@example.com' ,
'Customer Name'
);
const result = await InAppBrowser . open ( checkoutURL , {
// iOS options
dismissButtonStyle: 'cancel' ,
preferredBarTintColor: '#453AA4' ,
preferredControlTintColor: 'white' ,
readerMode: false ,
animated: true ,
modalPresentationStyle: 'fullScreen' ,
modalTransitionStyle: 'coverVertical' ,
modalEnabled: true ,
enableBarCollapsing: false ,
// Android options
showTitle: true ,
toolbarColor: '#453AA4' ,
secondaryToolbarColor: 'black' ,
navigationBarColor: 'black' ,
navigationBarDividerColor: 'white' ,
enableUrlBarHiding: true ,
enableDefaultShare: true ,
forceCloseOnRedirection: false ,
animations: {
startEnter: 'slide_in_right' ,
startExit: 'slide_out_left' ,
endEnter: 'slide_in_left' ,
endExit: 'slide_out_right'
},
headers: {
'my-custom-header' : 'my custom header value'
}
});
// Handle payment completion based on result
if ( result . type === 'dismiss' ) {
// User dismissed the browser
console . log ( 'Payment browser dismissed' );
}
} catch ( error ) {
Alert . alert ( 'Error' , 'Failed to open payment page' );
console . error ( 'InAppBrowser error:' , error );
}
};
return (
< View style = { { padding: 20 } } >
< TouchableOpacity
style = { {
backgroundColor: '#007AFF' ,
padding: 15 ,
borderRadius: 8 ,
alignItems: 'center'
} }
onPress = { openPaymentLink }
>
< Text style = { { color: 'white' , fontSize: 16 , fontWeight: 'bold' } } >
Pay Now
</ Text >
</ TouchableOpacity >
</ View >
);
};
export default PaymentButton ;
Deep Link Handling for Payment Callbacks // App.js or your main component
import React , { useEffect } from 'react' ;
import { Linking } from 'react-native' ;
const App = () => {
useEffect (() => {
// Handle deep links when app is already running
const handleDeepLink = ( url ) => {
if ( url . includes ( 'payment-status' )) {
const urlParams = new URLSearchParams ( url . split ( '?' )[ 1 ]);
const paymentId = urlParams . get ( 'payment_id' );
const status = urlParams . get ( 'status' );
// Handle payment completion
handlePaymentCallback ( paymentId , status );
}
};
// Listen for deep links
const subscription = Linking . addEventListener ( 'url' , ({ url }) => {
handleDeepLink ( url );
});
// Handle deep link if app was opened from a link
Linking . getInitialURL (). then (( url ) => {
if ( url ) {
handleDeepLink ( url );
}
});
return () => subscription ?. remove ();
}, []);
const handlePaymentCallback = ( paymentId , status ) => {
if ( status === 'succeeded' ) {
// Navigate to success screen or show success message
console . log ( 'Payment successful:' , paymentId );
} else {
// Handle payment failure
console . log ( 'Payment failed:' , paymentId );
}
};
// Your app components...
};
export default App ;
Package Dependencies Add this dependency to your package.json: {
"dependencies" : {
"react-native-inappbrowser-reborn" : "^6.4.0"
}
}
Installation Commands # Install InAppBrowser
npm install react-native-inappbrowser-reborn
cd ios && pod install
Android Configuration Add to android/app/src/main/AndroidManifest.xml: < activity
android:name = "com.reactnativeinappbrowserreborn.InAppBrowserActivity"
android:theme = "@style/Theme.AppCompat.Dialog"
android:exported = "false" />
iOS Configuration Add to ios/YourApp/Info.plist: < key > CFBundleURLTypes </ key >
< array >
< dict >
< key > CFBundleURLName </ key >
< string > myapp </ string >
< key > CFBundleURLSchemes </ key >
< array >
< string > myapp </ string >
</ array >
</ dict >
</ array >
Use InAppBrowser for native browser experience. Handle deep links for payment callbacks.
Alternative: Native Payment SDK For a more integrated payment experience with native UI and full customization, Dodo Payments offers a React Native SDK. Regional Compliance : In-app payment SDKs have regional restrictions. On iOS , native payment SDKs are only legal in the EU (under DMA terms). On Android , they’re legal in User Choice Billing (UCB) markets. For other regions, use the checkout links approach above to ensure App Store and Play Store compliance.
React Native SDK Integration Guide Complete guide for integrating Dodo’s React Native SDK with native payment sheets, full customization options, and direct payment handling. Includes setup, implementation, and testing instructions.
Flutter Integration import 'package:flutter/material.dart' ;
import 'package:webview_flutter/webview_flutter.dart' ;
import 'package:http/http.dart' as http;
import 'dart:convert' ;
Future < String > getCheckoutURL ( String productId, String customerEmail, String customerName) async {
final response = await http. post (
Uri . parse ( 'https://your-backend.com/api/create-checkout-session' ),
headers : { 'Content-Type' : 'application/json' },
body : json. encode ({
'productId' : productId,
'customerEmail' : customerEmail,
'customerName' : customerName,
}),
);
if (response.statusCode == 200 ) {
final data = json. decode (response.body);
return data[ 'checkout_url' ];
} else {
throw Exception ( 'Failed to get checkout URL: ${ response . statusCode } ' );
}
}
class PaymentScreen extends StatefulWidget {
@override
_PaymentScreenState createState () => _PaymentScreenState ();
}
class _PaymentScreenState extends State < PaymentScreen > {
late WebViewController _controller;
String ? checkoutURL;
@override
void initState () {
super . initState ();
_getCheckoutURL ();
}
Future < void > _getCheckoutURL () async {
try {
final url = await getCheckoutURL ( 'prod_123' , 'customer@example.com' , 'Customer Name' );
setState (() {
checkoutURL = url;
});
} catch (e) {
// Handle error
print ( 'Failed to get checkout URL: $ e ' );
}
}
@override
Widget build ( BuildContext context) {
return Scaffold (
appBar : AppBar (title : Text ( 'Payment' )),
body : checkoutURL == null
? Center (child : CircularProgressIndicator ())
: WebView (
initialUrl : checkoutURL,
javascriptMode : JavascriptMode .unrestricted,
onWebViewCreated : ( WebViewController webViewController) {
_controller = webViewController;
},
navigationDelegate : ( NavigationRequest request) {
if (request.url. contains ( 'payment_id' ) && request.url. contains ( 'status' )) {
// Handle payment completion
final uri = Uri . parse (request.url);
final paymentId = uri.queryParameters[ 'payment_id' ];
final status = uri.queryParameters[ 'status' ];
handlePaymentCompletion (paymentId, status);
return NavigationDecision .prevent;
}
return NavigationDecision .navigate;
},
),
);
}
void handlePaymentCompletion ( String ? paymentId, String ? status) {
if (status == 'succeeded' ) {
// Payment successful
} else {
// Payment failed
}
}
}
Use WebView for payment links. Handle the redirect URL to detect payment completion.
Best Practices
Security : Never store API keys in your app code. Use secure storage and SSL pinning.
User Experience : Show loading indicators, handle errors gracefully, and provide clear messages.
Testing : Use test cards, simulate network errors, and test on various devices.
Troubleshooting
Common Issues
WebView not opening payment link : Ensure the payment link is valid and uses HTTPS.
Callback not received : Check your return URL and webhook configuration.
API key errors : Verify that your API key is correct and has the necessary permissions.
Additional Resources