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

# Hướng Dẫn Tích Hợp Di Động

> Hướng dẫn thống nhất để tích hợp Dodo Payments vào các ứng dụng di động Android, iOS, React Native và Flutter.

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="#integration-workflow">
    Thiết lập tích hợp thanh toán di động của bạn trong 4 bước đơn giản
  </Card>

  <Card title="Platform Examples" icon="code" href="#platform-specific-integration">
    Các ví dụ mã hoàn chỉnh cho Android, iOS, React Native và Flutter
  </Card>
</CardGroup>

## Điều Kiện Tiên Quyết

Trước khi tích hợp Dodo Payments vào ứng dụng di động của bạn, hãy đảm bảo bạn đã có:

* **Tài Khoản Dodo Payments**: Tài khoản thương nhân hoạt động với quyền truy cập API
* **Thông Tin Xác Thực API**: Khóa API và khóa bí mật webhook từ bảng điều khiển của bạn
* **Dự Án Ứng Dụng Di Động**: Ứng dụng Android, iOS, React Native hoặc Flutter
* **Máy Chủ Backend**: Để xử lý an toàn việc tạo phiên thanh toán

## Quy Trình Tích Hợp

Tích hợp di động tuân theo quy trình 4 bước an toàn, trong đó backend của bạn xử lý các cuộc gọi API và ứng dụng di động của bạn quản lý trải nghiệm người dùng.

<Steps>
  <Step title="Backend: Create Checkout Session">
    <Card title="Checkout Session API Docs" icon="book" href="/developer-resources/checkout-session">
      Tìm hiểu cách tạo phiên thanh toán tại backend bằng Node.js, Python và hơn thế. Xem các ví dụ và tham chiếu tham số đầy đủ trong tài liệu <strong>Checkout Sessions API</strong> chuyên dụng.
    </Card>

    <Note>
      **Bảo mật**: Checkout sessions must be created on your backend server, never in the mobile app. This protects your API keys and ensures proper validation.
    </Note>
  </Step>

  <Step title="Mobile: Get Checkout URL">
    Ứng dụng di động của bạn gọi backend của bạn để lấy URL thanh toá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")
            
            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")
                .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',
              },
              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>
    </Tabs>

    <Note>
      **Bảo mật**: Ứng dụng di động chỉ liên lạc với backend của bạn, không bao giờ trực tiếp với Dodo Payments API.
    </Note>
  </Step>

  <Step title="Mobile: Open Checkout in Browser">
    Mở URL thanh toán trong trình duyệt an toàn bên trong ứng dụng để xử lý thanh toán.

    <Card title="See platform-specific integration examples" icon="box" href="#platform-specific-integration">
      Xem đầy đủ mã và hướng dẫn cài đặt cho các thanh toán di động Android, iOS và Flutter.
    </Card>
  </Step>

  <Step title="Backend: Handle Payment Completion">
    Xử lý hoàn tất thanh toán qua webhooks và URL chuyển hướng để xác nhận trạng thái thanh toán.
  </Step>
</Steps>

## Tích Hợp Cụ Thể Nền Tảng

Chọn nền tảng di động của bạn bên dưới để xem các ví dụ triển khai hoàn chỉnh:

<Tabs>
  <Tab title="Android">
    ### Tích Hợp Android

    #### Triển Khai Chrome Custom Tabs

    ```kotlin theme={null}
    // 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)
                }
            }
        }
    }
    ```
  </Tab>

  <Tab title="iOS">
    ### Tích Hợp iOS

    #### Triển Khai SFSafariViewController

    ```swift theme={null}
    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)")
                }
            }
        }
    }
    ```

    #### Xử Lý Liên Kết Sâu

    Cấu hình các sơ đồ URL để xử lý các chuyển hướng hoàn tất thanh toán:

    **1. Thiết lập sơ đồ URL trong Info.plist:**

    ```xml theme={null}
    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleURLName</key>
        <string>myapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>myapp</string>
        </array>
      </dict>
    </array>
    ```

    **2. Xử lý chuyển hướng trong AppDelegate:**

    ```swift theme={null}
    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. Lắng nghe hoàn tất thanh toán:**

    ```swift theme={null}
    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
        }
    }
    ```
  </Tab>

  <Tab title="React Native">
    ### Tích hợp React Native

    #### Sử Dụng Liên Kết Thanh Toán (InAppBrowser)

    Mở trang thanh toán được lưu trữ của Dodo trong trình duyệt trong ứng dụng.

    ##### Triển Khai InAppBrowser

    ```javascript theme={null}
    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;
    ```

    #### Xử Lý Liên Kết Sâu cho Các Callback Thanh Toán

    ```javascript theme={null}
    // 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;
    ```

    #### Phụ Thuộc Gói

    Add this dependency to your `package.json`:

    ```json theme={null}
    {
      "dependencies": {
        "react-native-inappbrowser-reborn": "^6.4.0"
      }
    }
    ```

    #### Lệnh Cài Đặt

    ```bash theme={null}
    # Install InAppBrowser
    npm install react-native-inappbrowser-reborn
    cd ios && pod install
    ```

    #### Cấu Hình Android

    Thêm vào `android/app/src/main/AndroidManifest.xml`:

    ```xml theme={null}
    <activity
      android:name="com.reactnativeinappbrowserreborn.InAppBrowserActivity"
      android:theme="@style/Theme.AppCompat.Dialog"
      android:exported="false" />
    ```

    #### Cấu Hình iOS

    Thêm vào `ios/YourApp/Info.plist`:

    ```xml theme={null}
    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleURLName</key>
        <string>myapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>myapp</string>
        </array>
      </dict>
    </array>
    ```

    <Tip>
      Sử dụng InAppBrowser để có trải nghiệm trình duyệt gốc. Xử lý liên kết sâu cho các phản hồi thanh toán.
    </Tip>

    ***

    #### Lựa Chọn Khác: SDK Thanh Toán Gốc

    Để có trải nghiệm thanh toán tích hợp hơn với giao diện người dùng gốc và tùy chỉnh đầy đủ, Dodo Payments cung cấp một SDK React Native.

    <Warning>
      **Tuân thủ vùng**: SDK thanh toán trong ứng dụng có hạn chế theo vùng. Trên **iOS**, SDK thanh toán gốc chỉ hợp pháp ở **EU** (theo điều khoản DMA). Trên **Android**, chúng hợp pháp ở các thị trường User Choice Billing (UCB). Đối với các khu vực khác, hãy sử dụng cách tiếp cận liên kết thanh toán phía trên để đảm bảo tuân thủ App Store và Play Store.
    </Warning>

    <Card title="React Native SDK Integration Guide" icon="mobile-screen" href="/developer-resources/react-native-integration">
      Hướng dẫn đầy đủ để tích hợp SDK React Native của Dodo với các bảng thanh toán gốc, các tùy chọn tùy chỉnh đầy đủ và xử lý thanh toán trực tiếp. Bao gồm thiết lập, triển khai và hướng dẫn kiểm tra.
    </Card>

    <Info>
      Xem lại [App Store Fee Bypass Guide](/features/bypassing-app-store-fees) để hiểu yêu cầu tuân thủ vùng cho các tích hợp thanh toán trong ứng dụng.
    </Info>
  </Tab>

  <Tab title="Flutter">
    ### Tích Hợp Flutter

    ```dart theme={null}
    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
        }
      }
    }
    ```

    <Tip>
      Sử dụng WebView cho các liên kết thanh toán. Xử lý URL chuyển hướng để phát hiện hoàn tất thanh toán.
    </Tip>
  </Tab>
</Tabs>

## Thực Hành Tốt Nhất

* **Bảo Mật**: Không bao giờ lưu trữ các khóa API trong mã ứng dụng của bạn. Sử dụng lưu trữ an toàn và SSL pinning.
* **Trải Nghiệm Người Dùng**: Hiển thị chỉ báo tải, xử lý lỗi một cách duyên dáng và cung cấp thông điệp rõ ràng.
* **Kiểm Tra**: Sử dụng thẻ thử nghiệm, mô phỏng lỗi mạng và kiểm tra trên nhiều thiết bị.

## Khắc Phục Sự Cố

### Các Vấn Đề Thường Gặp

* **WebView không mở liên kết thanh toán**: Đảm bảo liên kết thanh toán hợp lệ và sử dụng HTTPS.
* **Callback không nhận được**: Kiểm tra URL trả về và cấu hình webhook của bạn.
* **Lỗi khóa API**: Xác minh rằng khóa API của bạn là chính xác và có quyền truy cập cần thiết.

## Tài liệu Tham khảo Bổ sung

* [Hướng dẫn Tích hợp Thanh toán](/developer-resources/integration-guide)
* [Tài liệu Webhook](/developer-resources/webhooks/intents/webhook-events-guide)
* [Quy trình Kiểm tra](/miscellaneous/testing-process)
* [Hỏi đáp Kỹ thuật](/miscellaneous/faq)

<Check>
  Nếu có câu hỏi hoặc cần hỗ trợ, liên hệ <a href="mailto:support@dodopayments.com">[support@dodopayments.com](mailto:support@dodopayments.com)</a>.
</Check>
