> ## 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.

# Mobile Integration Guide

> Unified guide for integrating Dodo Payments into Android, iOS, React Native and Flutter mobile applications.

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="#integration-workflow">
    Get your mobile payment integration running in 4 simple steps
  </Card>

  <Card title="Platform Examples" icon="code" href="#choose-your-sdk">
    Ejemplos de código completos para Android, iOS, React Native y Flutter
  </Card>
</CardGroup>

<Info>
  Dodo Payments ofrece un SDK oficial de checkout para **Android, iOS, React Native,
  y Flutter**. Cada uno encapsula el patrón documentado a continuación (abrir la URL
  de checkout, capturar el retorno, analizar el resultado) detrás de una única llamada
  tipada `start(...)`, con recuperación de sesiones abandonadas integrada. Utiliza un WebView manual solo
  si ninguno se adapta a tu stack.
</Info>

## Requisitos previos

Antes de integrar Dodo Payments en tu aplicación móvil, asegúrate de tener:

* **Cuenta de Dodo Payments**: Cuenta de merchant activa con acceso a la API
* **Credenciales de API**: Clave de API y clave secreta de webhook de tu dashboard
* **Proyecto de aplicación móvil**: Aplicación para Android, iOS, React Native o Flutter
* **Servidor backend**: Para gestionar de forma segura la creación de sesiones de checkout

## Flujo de integración

La integración móvil sigue un proceso seguro de 4 pasos en el que tu backend gestiona las llamadas a la API y tu aplicación móvil gestiona la experiencia del usuario.

<Steps>
  <Step title="Backend: Create Checkout Session">
    <Card title="Checkout Session API Docs" icon="book" href="/developer-resources/checkout-session">
      Aprende a crear una sesión de checkout en tu backend usando Node.js, Python y más. Consulta ejemplos completos y referencias de parámetros en la documentación específica de <strong>Checkout Sessions API</strong>.
    </Card>

    <Note>
      **Seguridad**: Las sesiones de checkout deben crearse en tu servidor backend, nunca en la aplicación móvil. Esto protege tus claves de API y garantiza una validación adecuada.
    </Note>
  </Step>

  <Step title="Mobile: Get Checkout URL">
    Tu aplicación móvil llama a tu backend para obtener la URL de checkout. Autentica
    esta solicitud con el token de sesión propio del usuario que ha iniciado sesión.

    <Tabs>
      <Tab title="iOS (Swift)">
        ```swift theme={null}
        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")
            request.setValue("Bearer \(userSessionToken)", forHTTPHeaderField: "Authorization")
            
            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
        }
        ```
      </Tab>

      <Tab title="Android (Kotlin)">
        ```kotlin theme={null}
        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")
                .header("Authorization", "Bearer $userSessionToken")
                .post(requestBody)
                .build()
            
            val response = client.newCall(request).execute()
            val responseBody = response.body?.string()
            val jsonResponse = JSONObject(responseBody ?: "")
            return jsonResponse.getString("checkout_url")
        }
        ```
      </Tab>

      <Tab title="React Native (JavaScript)">
        ```javascript theme={null}
        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',
                'Authorization': `Bearer ${userSessionToken}`,
              },
              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;
          }
        };
        ```
      </Tab>

      <Tab title="Flutter (Dart)">
        ```dart theme={null}
        import 'dart:convert';
        import 'package:http/http.dart' as http;

        Future<String> getCheckoutUrl({
          required String productId,
          required String customerEmail,
          required String customerName,
        }) async {
          final response = await http.post(
            Uri.parse('https://your-backend.com/api/create-checkout-session'),
            headers: {
              'Content-Type': 'application/json',
              'Authorization': 'Bearer $userSessionToken',
            },
            body: jsonEncode({
              'productId': productId,
              'customerEmail': customerEmail,
              'customerName': customerName,
            }),
          );

          if (response.statusCode != 200) {
            throw Exception('Failed to get checkout URL: ${response.statusCode}');
          }
          return jsonDecode(response.body)['checkout_url'] as String;
        }
        ```
      </Tab>
    </Tabs>

    <Note>
      **Seguridad**: Las aplicaciones móviles solo se comunican con tu backend, nunca directamente con Dodo Payments API.
    </Note>
  </Step>

  <Step title="Mobile: Open Checkout in Browser">
    Abre la URL de checkout en un navegador seguro integrado en la aplicación para procesar el pago.
    También puedes omitir por completo la configuración manual con el SDK oficial de checkout para tu
    plataforma.

    <Card title="Pick your mobile SDK" icon="box" href="#choose-your-sdk">
      Pasos de instalación e instrucciones de configuración para Android, iOS, React Native y Flutter.
    </Card>
  </Step>

  <Step title="Backend: Handle Payment Completion">
    Procesa la finalización del pago mediante webhooks y URL de redirección para confirmar el estado del pago.
  </Step>
</Steps>

## Elige tu SDK

Todos los SDK móviles exponen el mismo contrato: una llamada `start(...)` abre el
checkout alojado de Dodo en la superficie de navegador nativa de la plataforma y devuelve un
`CheckoutResult` tipado cuyo `status` es `succeeded`, `failed`, `cancelled`,
`pending` o `expired`. Ninguno almacena una clave de API ni llama a Dodo
Payments API, y los cuatro admiten la recuperación de sesiones abandonadas.

<CardGroup cols={2}>
  <Card title="Android" icon="android" href="/developer-resources/sdks/android">
    `com.dodopayments.api:checkout-android` abre una pestaña personalizada de Chrome. Requiere `minSdk` 23.
  </Card>

  <Card title="iOS" icon="apple" href="/developer-resources/sdks/ios">
    `dodopayments-mobile-sdk-ios` abre `SFSafariViewController`. Requiere iOS 16 o posterior.
  </Card>

  <Card title="React Native" icon="react" href="/developer-resources/sdks/react-native">
    `@dodopayments/react-native-checkout`, un Turbo Module sobre ambos núcleos nativos. Requiere React Native 0.76 o posterior.
  </Card>

  <Card title="Flutter" icon="layer-group" href="/developer-resources/sdks/flutter">
    `dodopayments_checkout`, un canal Pigeon sobre ambos núcleos nativos. Requiere Flutter 3.44 o posterior.
  </Card>
</CardGroup>

<Warning>
  El `status` que recibes es una indicación de la interfaz, no una prueba de pago. Confirma cada
  pago desde tu backend mediante el webhook `payment.succeeded` / `subscription.active`,
  o recuperando el pago con tu clave secreta.
</Warning>

### Registrar un esquema de URL de callback

Los cuatro SDK devuelven el control a tu aplicación mediante un esquema de URL personalizado que
eliges, por ejemplo `myapp://checkout/return`. Regístralo una vez por
plataforma:

<Tabs>
  <Tab title="Android">
    ```kotlin android/app/build.gradle theme={null}
    android {
        defaultConfig {
            manifestPlaceholders["dodoCallbackScheme"] = "myapp"
        }
    }
    ```

    El propio manifest del SDK ya declara la actividad de redirección, por lo que no hay
    ningún XML de manifest que añadir.
  </Tab>

  <Tab title="iOS">
    ```xml Info.plist theme={null}
    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleURLName</key>
        <string>myapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>myapp</string>
        </array>
      </dict>
    </array>
    ```

    En iOS también debes reenviar las URL entrantes al SDK, porque
    `SFSafariViewController` no puede capturar su propia URL de retorno. Consulta la página de
    [iOS](/developer-resources/sdks/ios) o de
    [React Native](/developer-resources/sdks/react-native) para obtener el
    handler exacto.
  </Tab>

  <Tab title="Expo">
    ```sh theme={null}
    npx expo install expo-build-properties
    ```

    ```json app.json theme={null}
    {
      "expo": {
        "plugins": [
          [
            "expo-build-properties",
            { "android": { "manifestPlaceholders": { "dodoCallbackScheme": "myapp" } } }
          ]
        ],
        "ios": {
          "infoPlist": {
            "CFBundleURLTypes": [{ "CFBundleURLSchemes": ["myapp"] }]
          }
        }
      }
    }
    ```

    <Warning>
      `@dodopayments/react-native-checkout` también incluye un plugin de configuración de Expo, pero
      actualmente no escribe ningún esquema de URL ni ningún marcador de posición del manifest. Añadirlo por sí solo
      **no** registrará tu esquema de callback; utiliza la configuración anterior. Consulta la página del
      [SDK de React Native](/developer-resources/sdks/react-native).
    </Warning>

    Funciona únicamente con builds de desarrollo, no con Expo Go. Ejecuta
    `npx expo prebuild --clean` después de editar `app.json`.
  </Tab>
</Tabs>

<Note>
  ¿Prefieres crearlo tú mismo? Abre `checkout_url` en un WebView e intercepta
  la navegación hacia tu `return_url`; después, lee los parámetros de consulta
  `status` y `payment_id`. Los SDK anteriores hacen esto por ti en la superficie de
  navegador real de la plataforma, por eso Apple Pay y Google Pay siguen funcionando.
</Note>

## Prácticas recomendadas

* **Seguridad**: Nunca incluyas una clave de API en tu aplicación. Crea sesiones de checkout en tu backend y pasa únicamente el `checkout_url` resultante al cliente.
* **Autoridad**: Trata `CheckoutResult.status` como una indicación de la interfaz. Concede acceso solo después de que tu backend confirme el pago.
* **Experiencia del usuario**: Muestra un estado de carga mientras tu backend crea la sesión y gestiona `cancelled` como un resultado normal, no como un error.
* **Pruebas**: Utiliza el modo de prueba y tarjetas de prueba, y verifica el recorrido de ida y vuelta de la URL de retorno en un dispositivo real y también en un simulador.

## Solución de problemas

### Problemas comunes

* **El callback nunca llega**: El esquema de `returnUrl` debe coincidir con el que registraste. En Android es el marcador de posición del manifest `dodoCallbackScheme`; en iOS y React Native es el tipo de URL `Info.plist`.
* **El checkout vuelve al navegador en lugar de a tu aplicación (iOS)**: No has reenviado la URL entrante. Llama a `DodoCheckout.handleOpenURL(url)` desde `.onOpenURL`, `scene(_:openURLContexts:)` o un listener de React Native `Linking`.
* **`PLATFORM_ERROR` en Android**: Lo más habitual es que haya una discrepancia en el esquema. También puede aparecer si tu `MainActivity` establece `android:taskAffinity=""` (el valor predeterminado estándar de `flutter create`), lo que puede hacer que algunos builds de OEM pierdan el checkout en curso.
* **`ALREADY_IN_PROGRESS`**: Hay un checkout todavía abierto. Espera a que finalice o descarta el anterior antes de iniciar otro.
* **El build falla con un marcador de posición sin resolver**: Añadiste el SDK de Android, pero nunca estableciste `manifestPlaceholders["dodoCallbackScheme"]`.
* **El pago se realizó correctamente, pero no se concedió acceso**: Es lo esperado si utilizas el resultado móvil como fuente de verdad. Concede acceso desde el webhook `payment.succeeded` / `subscription.active`.

## Recursos adicionales

* [Guía de integración de pagos](/developer-resources/integration-guide)
* [Documentación de webhooks](/developer-resources/webhooks/intents/webhook-events-guide)
* [Proceso de pruebas](/miscellaneous/testing-process)
* [Preguntas frecuentes técnicas](/miscellaneous/faq)

<Check>
  Para preguntas o asistencia, contacta con <a href="mailto:support@dodopayments.com">[support@dodopayments.com](mailto:support@dodopayments.com)</a>.
</Check>
