Prerequisites

To integrate the Dodo Payments API into your mobile app, you’ll need:

  • A Dodo Payments merchant account
  • API Credentials (API key and webhook secret key) from dashboard
  • A mobile app project (Android, iOS, Flutter, or React Native)

If you don’t have an account yet, you can get your business approved by contacting the founder or by filling out this form.

Dashboard Setup

  1. Navigate to the Dodo Payments Dashboard

  2. Create a product (one-time payment or subscription)

  3. Test the checkout flow:

    • Click the share button on the product page
    • Open the link in your browser
    • Use test card number: 4242 4242 4242 4242
    • Enter any future expiration date and any 3-digit CVV
  4. Generate your API key:

    • Go to Settings > API
    • Detailed Guide
    • Copy the API key to use in your mobile app
  5. Configure webhooks:

    • Go to Settings > Webhooks
    • Create a webhook URL for payment notifications
    • Detailed Guide
    • Copy the webhook secret key

Mobile Integration

Dodo Payments can be integrated into mobile apps using our payment links. The integration involves:

  1. Creating a payment link using our API
  2. Opening the payment link in a WebView or browser
  3. Handling the payment completion callback

Android Integration

Using WebView

// Add WebView to your layout
<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

// In your Activity
class PaymentActivity : AppCompatActivity() {
    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_payment)

        webView = findViewById(R.id.webview)
        webView.settings.javaScriptEnabled = true
        webView.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
                // Handle payment completion
                if (url.contains("payment_id") && url.contains("status")) {
                    val uri = Uri.parse(url)
                    val paymentId = uri.getQueryParameter("payment_id")
                    val status = uri.getQueryParameter("status")
                    
                    // Handle payment completion
                    handlePaymentCompletion(paymentId, status)
                    return true
                }
                return false
            }
        }

        // Load payment link
        val paymentLink = "https://checkout.dodopayments.com/buy/{productid}"
        webView.loadUrl(paymentLink)
    }

    private fun handlePaymentCompletion(paymentId: String?, status: String?) {
        // Handle payment completion logic
        if (status == "succeeded") {
            // Payment successful
        } else {
            // Payment failed
        }
    }
}

Using Custom Tabs

// 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!!
        )

        // Launch payment link
        val paymentLink = "https://checkout.dodopayments.com/buy/{productid}"
        val customTabsIntent = CustomTabsIntent.Builder(customTabsSession)
            .build()
        customTabsIntent.launchUrl(this, Uri.parse(paymentLink))
    }
}

iOS Integration

Using WKWebView

import WebKit

class PaymentViewController: UIViewController, WKNavigationDelegate {
    private var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: view.bounds, configuration: webConfiguration)
        webView.navigationDelegate = self
        view.addSubview(webView)
        
        // Load payment link
        let paymentLink = "https://checkout.dodopayments.com/buy/{productid}"
        if let url = URL(string: paymentLink) {
            let request = URLRequest(url: url)
            webView.load(request)
        }
    }
    
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let url = navigationAction.request.url {
            if url.absoluteString.contains("payment_id") && url.absoluteString.contains("status") {
                // Handle payment completion
                if 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
                    
                    handlePaymentCompletion(paymentId: paymentId, status: status)
                }
                decisionHandler(.cancel)
                return
            }
        }
        decisionHandler(.allow)
    }
    
    private func handlePaymentCompletion(paymentId: String?, status: String?) {
        // Handle payment completion logic
        if status == "succeeded" {
            // Payment successful
        } else {
            // Payment failed
        }
    }
}
Note: Apple Pay integration only works with SFSafariViewController. If you need to support Apple Pay in your iOS app, you must use SFSafariViewController.

Using SFSafariViewController

import SafariServices

class PaymentViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Launch payment link
        let paymentLink = "https://checkout.dodopayments.com/buy/{productid}"
        if let url = URL(string: paymentLink) {
            let safariVC = SFSafariViewController(url: url)
            present(safariVC, animated: true, completion: nil)
        }
    }
}
Important: SFSafariViewController is required for Apple Pay integration. This is because Apple Pay requires the secure context and authentication capabilities that only SFSafariViewController provides. If your app needs to support Apple Pay payments, you must use SFSafariViewController.

Handling Apple Pay Redirects in SFSafariViewController

When integrating Apple Pay via SFSafariViewController in iOS, you can automatically detect payment completion without requiring users to manually tap the ‘Done’ button. This section explains how to implement this using custom URL schemes or Universal Links.

Objective

Enable seamless detection of Apple Pay completion by intercepting redirect URLs inside your iOS app.

Implementation Steps

  1. Configure the Redirect URL When generating the Dodo Payments payment link, set the redirect_url to a custom scheme or Universal Link:

    // Custom URL Scheme
    myapp://payment-status?payment_id=xyz&status=succeeded
    
    // Universal Link
    https://myapp.com/payment-status?payment_id=xyz&status=succeeded
    
  2. Handle the Redirect in AppDelegate For custom URL schemes, implement this method:

    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. Add Notification Observer in View Controller

    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 and handle the payment status here
        }
    }
    
  4. Present SFSafariViewController

    let safariVC = SFSafariViewController(url: URL(string: paymentLink)!)
    present(safariVC, animated: true)
    

Benefits

  • Apple Pay completes in Safari View
  • Redirect triggers a URL your app can capture
  • App dismisses SFSafariViewController automatically
  • Provides a smooth, professional user experience without manual steps
Note: Make sure to register your custom URL scheme in your app’s Info.plist file under URL Types.

Flutter Integration

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class PaymentScreen extends StatefulWidget {
  
  _PaymentScreenState createState() => _PaymentScreenState();
}

class _PaymentScreenState extends State<PaymentScreen> {
  late WebViewController _controller;
  final String paymentLink = "https://checkout.dodopayments.com/buy/{productid}";

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Payment')),
      body: WebView(
        initialUrl: paymentLink,
        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
    }
  }
}

React Native Integration

import React, { useRef } from 'react';
import { WebView } from 'react-native-webview';
import { Linking } from 'react-native';

const PaymentScreen = () => {
  const webViewRef = useRef(null);

  const handlePaymentCompletion = (paymentId, status) => {
    if (status === 'succeeded') {
      // Payment successful
    } else {
      // Payment failed
    }
  };

  const onNavigationStateChange = (navState) => {
    const { url } = navState;
    if (url.includes('payment_id') && url.includes('status')) {
      const uri = new URL(url);
      const paymentId = uri.searchParams.get('payment_id');
      const status = uri.searchParams.get('status');
      
      handlePaymentCompletion(paymentId, status);
      return false;
    }
    return true;
  };

  return (
    <WebView
      ref={webViewRef}
      source={{ uri: 'https://checkout.dodopayments.com/buy/{productid}' }}
      onNavigationStateChange={onNavigationStateChange}
      javaScriptEnabled={true}
      domStorageEnabled={true}
    />
  );
};

export default PaymentScreen;

Best Practices

  1. Security:

    • Never store API keys in your app’s code
    • Use secure storage solutions for sensitive data
    • Implement proper SSL pinning
    • Validate all payment responses
  2. User Experience:

    • Show loading indicators during payment processing
    • Handle network errors gracefully
    • Provide clear error messages
    • Implement proper back navigation
  3. Testing:

    • Test with test card numbers in development
    • Test different payment scenarios
    • Test network error handling
    • Test on different device sizes and OS versions

Additional Resources

For any questions or support, please contact our support team.