Android ou iOS Nativo
Guia prático para integrar o checkout Pagaleve BNPL em apps nativos Android (Kotlin) e iOS (Swift) usando WebView. Focado na implementação do WebView e deep links.
Pré-requisitos
Android
- Android Studio 4.0+
 - minSdkVersion 21+
 - Kotlin 1.8+ ou Java 8+
 - Conta merchant Pagaleve com credenciais de API
 
iOS
- Xcode 14.0+
 - iOS 13.0+
 - Swift 5.0+
 - Conta merchant Pagaleve com credenciais de API
 
Implementação Android
Configuração do Projeto Android
Dependências (build.gradle - Module)
dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
    implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
    // Networking
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
    // JSON parsing
    implementation 'com.google.code.gson:gson:2.10.1'
    // Material Design
    implementation 'com.google.android.material:material:1.11.0'
    // Testing
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}Configuração de Deep Links (AndroidManifest.xml)
<!-- app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.PagaleveApp">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".CheckoutActivity"
            android:exported="false" />
        <activity
            android:name=".SuccessActivity"
            android:exported="true"
            android:launchMode="singleTop">
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="your-android-app"
                      android:host="success" />
            </intent-filter>
        </activity>
        <activity
            android:name=".CancelActivity"
            android:exported="true"
            android:launchMode="singleTop">
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="your-android-app"
                      android:host="cancel" />
            </intent-filter>
        </activity>
    </application>
</manifest>Recebendo a URL do Checkout
O checkout da Pagaleve deve ser criado no seu backend através da API. Uma vez criado, você receberá uma checkout_url que deve ser passada para o WebView do seu app Android ou iOS.
Exemplo de URL de checkout:
https://checkout.pagaleve.com.br/checkout/abc123...
Activities Android
MainActivity
// MainActivity.kt
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
class MainActivity : AppCompatActivity() {
    private lateinit var urlEditText: TextInputEditText
    private lateinit var openCheckoutButton: MaterialButton
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initViews()
        setupClickListeners()
    }
    private fun initViews() {
        urlEditText = findViewById(R.id.urlEditText)
        openCheckoutButton = findViewById(R.id.openCheckoutButton)
    }
    private fun setupClickListeners() {
        openCheckoutButton.setOnClickListener {
            val checkoutUrl = urlEditText.text.toString().trim()
            if (checkoutUrl.isEmpty()) {
                Toast.makeText(this, "Por favor, insira a URL do checkout", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            if (!isValidUrl(checkoutUrl)) {
                Toast.makeText(this, "URL inválida", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            if (!checkoutUrl.contains("checkout.pagaleve.com.br")) {
                Toast.makeText(this, "Esta não parece ser uma URL da Pagaleve", Toast.LENGTH_LONG).show()
                return@setOnClickListener
            }
            val intent = Intent(this, CheckoutActivity::class.java)
            intent.putExtra("checkout_url", checkoutUrl)
            startActivity(intent)
        }
    }
    private fun isValidUrl(url: String): Boolean {
        return try {
            val uri = android.net.Uri.parse(url)
            uri.scheme != null && uri.host != null
        } catch (e: Exception) {
            false
        }
    }
}CheckoutActivity (WebView)
// CheckoutActivity.kt
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.webkit.*
import androidx.appcompat.app.AppCompatActivity
class CheckoutActivity : AppCompatActivity() {
    private lateinit var webView: WebView
    @SuppressLint("SetJavaScriptEnabled")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_checkout)
        val checkoutUrl = intent.getStringExtra("checkout_url") ?: run {
            finish()
            return
        }
        webView = findViewById(R.id.webView)
        // Configure WebView settings
        val settings: WebSettings = webView.settings
        settings.javaScriptEnabled = true
        settings.domStorageEnabled = true
        settings.cacheMode = WebSettings.LOAD_NO_CACHE
        settings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
        webView.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
                val url = request?.url.toString()
                return when {
                    url.startsWith("your-android-app://success") -> {
                        val intent = Intent(this@CheckoutActivity, SuccessActivity::class.java)
                        startActivity(intent)
                        finish()
                        true
                    }
                    url.startsWith("your-android-app://cancel") -> {
                        val intent = Intent(this@CheckoutActivity, CancelActivity::class.java)
                        startActivity(intent)
                        finish()
                        true
                    }
                    else -> false
                }
            }
            override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
                super.onPageStarted(view, url, favicon)
                // Show loading indicator if needed
            }
            override fun onPageFinished(view: WebView?, url: String?) {
                super.onPageFinished(view, url)
                // Hide loading indicator if needed
            }
            override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
                super.onReceivedError(view, request, error)
                // Handle error
            }
        }
        webView.loadUrl(checkoutUrl)
    }
    override fun onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack()
        } else {
            super.onBackPressed()
        }
    }
}SuccessActivity
// SuccessActivity.kt
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton
class SuccessActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_success)
        val backButton = findViewById<MaterialButton>(R.id.backButton)
        backButton.setOnClickListener {
            val intent = Intent(this, MainActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
            startActivity(intent)
            finish()
        }
    }
}CancelActivity
// CancelActivity.kt
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton
class CancelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cancel)
        val tryAgainButton = findViewById<MaterialButton>(R.id.tryAgainButton)
        val backButton = findViewById<MaterialButton>(R.id.backButton)
        tryAgainButton.setOnClickListener {
            val intent = Intent(this, MainActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
            startActivity(intent)
            finish()
        }
        backButton.setOnClickListener {
            val intent = Intent(this, MainActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
            startActivity(intent)
            finish()
        }
    }
}Layouts Android
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:gravity="center">
    <ImageView
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/ic_payment"
        android:layout_marginBottom="24dp"
        android:tint="@color/blue" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pagaleve Checkout"
        android:textAlignment="center"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginBottom="16dp" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Insira a URL do checkout recebida do seu backend"
        android:textAlignment="center"
        android:textSize="16sp"
        android:textColor="@color/gray"
        android:layout_marginBottom="32dp" />
    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/urlEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="https://checkout.pagaleve.com.br/checkout/..."
            android:inputType="textUri"
            android:minLines="3"
            android:gravity="top" />
    </com.google.android.material.textfield.TextInputLayout>
    <com.google.android.material.button.MaterialButton
        android:id="@+id/openCheckoutButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Abrir Checkout" />
</LinearLayout>activity_checkout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>Implementação iOS
Configuração do Projeto iOS
Info.plist Configuration
<!-- ios/Runner/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>com.yourcompany.yourapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>your-ios-app</string>
        </array>
    </dict>
</array>
<!-- Allow arbitrary loads for development -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>Modelo Simples para URL iOS
// Models/CheckoutURL.swift
import Foundation
struct CheckoutURL {
    let url: String
    var isValid: Bool {
        guard let url = URL(string: url) else { return false }
        return url.scheme != nil && url.host != nil
    }
    var isPagaleveURL: Bool {
        return url.contains("checkout.pagaleve.com.br")
    }
}View Controllers iOS
ViewController Principal
// ViewControllers/ViewController.swift
import UIKit
class ViewController: UIViewController {
    @IBOutlet weak var urlTextView: UITextView!
    @IBOutlet weak var openCheckoutButton: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    private func setupUI() {
        title = "Pagaleve Checkout"
        // Configure text view
        urlTextView.layer.borderColor = UIColor.systemGray4.cgColor
        urlTextView.layer.borderWidth = 1
        urlTextView.layer.cornerRadius = 8
        urlTextView.font = UIFont.systemFont(ofSize: 16)
        urlTextView.text = "https://checkout.pagaleve.com.br/checkout/..."
        urlTextView.textColor = UIColor.placeholderText
        urlTextView.delegate = self
    }
    @IBAction func openCheckoutButtonTapped(_ sender: UIButton) {
        guard let urlText = urlTextView.text?.trimmingCharacters(in: .whitespacesAndNewlines),
              !urlText.isEmpty,
              urlText != "https://checkout.pagaleve.com.br/checkout/..." else {
            showAlert(message: "Por favor, insira a URL do checkout")
            return
        }
        guard isValidURL(urlText) else {
            showAlert(message: "URL inválida")
            return
        }
        guard urlText.contains("checkout.pagaleve.com.br") else {
            showAlert(message: "Esta não parece ser uma URL da Pagaleve")
            return
        }
        presentCheckoutWebView(url: urlText)
    }
    private func isValidURL(_ string: String) -> Bool {
        guard let url = URL(string: string) else { return false }
        return url.scheme != nil && url.host != nil
    }
    private func presentCheckoutWebView(url: String) {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        if let checkoutVC = storyboard.instantiateViewController(withIdentifier: "CheckoutViewController") as? CheckoutViewController {
            checkoutVC.checkoutUrl = url
            present(checkoutVC, animated: true)
        }
    }
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Aviso", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}
extension ViewController: UITextViewDelegate {
    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.textColor == UIColor.placeholderText {
            textView.text = ""
            textView.textColor = UIColor.label
        }
    }
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.text.isEmpty {
            textView.text = "https://checkout.pagaleve.com.br/checkout/..."
            textView.textColor = UIColor.placeholderText
        }
    }
}CheckoutViewController (WebView)
// ViewControllers/CheckoutViewController.swift
import UIKit
import WebKit
class CheckoutViewController: UIViewController {
    @IBOutlet weak var webView: WKWebView!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    var checkoutUrl: String?
    override func viewDidLoad() {
        super.viewDidLoad()
        setupWebView()
        loadCheckoutUrl()
    }
    private func setupWebView() {
        // Configure WebView for security
        let configuration = WKWebViewConfiguration()
        configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
        webView = WKWebView(frame: view.bounds, configuration: configuration)
        webView.navigationDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(webView)
        // Setup constraints
        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        // Add close button
        navigationItem.leftBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .close,
            target: self,
            action: #selector(closeButtonTapped)
        )
    }
    @objc private func closeButtonTapped() {
        dismiss(animated: true)
    }
    private func loadCheckoutUrl() {
        guard let urlString = checkoutUrl,
              let url = URL(string: urlString) else {
            showError(message: "URL inválida")
            return
        }
        let request = URLRequest(url: url)
        webView.load(request)
    }
    private func showError(message: String) {
        let alert = UIAlertController(title: "Erro", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
            self.dismiss(animated: true)
        })
        present(alert, animated: true)
    }
}
extension CheckoutViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        activityIndicator.startAnimating()
        activityIndicator.isHidden = false
    }
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        activityIndicator.stopAnimating()
        activityIndicator.isHidden = true
    }
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        guard let url = navigationAction.request.url else {
            decisionHandler(.allow)
            return
        }
        let urlString = url.absoluteString
        if urlString.hasPrefix("your-ios-app://success") {
            dismiss(animated: true) {
                self.presentSuccessScreen()
            }
            decisionHandler(.cancel)
        } else if urlString.hasPrefix("your-ios-app://cancel") {
            dismiss(animated: true) {
                self.presentCancelScreen()
            }
            decisionHandler(.cancel)
        } else {
            decisionHandler(.allow)
        }
    }
    private func presentSuccessScreen() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        if let successVC = storyboard.instantiateViewController(withIdentifier: "SuccessViewController") as? SuccessViewController {
            if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
               let window = windowScene.windows.first {
                window.rootViewController?.present(successVC, animated: true)
            }
        }
    }
    private func presentCancelScreen() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        if let cancelVC = storyboard.instantiateViewController(withIdentifier: "CancelViewController") as? CancelViewController {
            if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
               let window = windowScene.windows.first {
                window.rootViewController?.present(cancelVC, animated: true)
            }
        }
    }
}SuccessViewController
// ViewControllers/SuccessViewController.swift
import UIKit
class SuccessViewController: UIViewController {
    @IBOutlet weak var successImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var backButton: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    private func setupUI() {
        successImageView.image = UIImage(systemName: "checkmark.circle.fill")
        successImageView.tintColor = .systemGreen
        titleLabel.text = "Pagamento Concluído!"
        titleLabel.textColor = .systemGreen
        messageLabel.text = "Seu pagamento foi processado com sucesso.\nVocê receberá uma confirmação por email."
        backButton.setTitle("Voltar ao Início", for: .normal)
    }
    @IBAction func backButtonTapped(_ sender: UIButton) {
        dismiss(animated: true)
    }
}CancelViewController
// ViewControllers/CancelViewController.swift
import UIKit
class CancelViewController: UIViewController {
    @IBOutlet weak var cancelImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var tryAgainButton: UIButton!
    @IBOutlet weak var backButton: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    private func setupUI() {
        cancelImageView.image = UIImage(systemName: "xmark.circle.fill")
        cancelImageView.tintColor = .systemRed
        titleLabel.text = "Pagamento Cancelado"
        titleLabel.textColor = .systemRed
        messageLabel.text = "O pagamento foi cancelado ou não pôde ser processado.\nTente novamente ou escolha outro método de pagamento."
        tryAgainButton.setTitle("Tentar Novamente", for: .normal)
        backButton.setTitle("Voltar ao Início", for: .normal)
    }
    @IBAction func tryAgainButtonTapped(_ sender: UIButton) {
        dismiss(animated: true)
    }
    @IBAction func backButtonTapped(_ sender: UIButton) {
        dismiss(animated: true)
    }
}Updated about 1 month ago
