Android SDK

O Android SDK do smart checkout permite integrar a experiência de pagamento de forma nativa, oferecendo maior controle sobre a jornada do usuário, melhor performance e suporte a funcionalidades avançadas como carteiras digitais e deep links.


🚀 Principais recursos

  • Callbacks para sucesso, erro e fechamento.
  • Suporte a Google Pay™.
  • Personalização visual do bottom sheet.
  • Suporte a deep link.

📋 Requisitos

  • minSdkVersion: 24
  • compileSdkVersion: 35
  • targetSdkVersion: 35

⚙️ Instalação

Atualize as arquiteturas suportadas no build.gradle do app:

defaultConfig {
    ndk {
        abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
    }
}

Adicione os repositórios Maven no settings.gradle:

dependencyResolutionManagement {
    repositories {
        maven("https://storage.googleapis.com/download.flutter.io")
        maven("https://smartcheckout-sdk-repo.s3.us-east-1.amazonaws.com/android/maven/repository")
    }
}

Adicione a dependência no build.gradle do app:

dependencies {
    implementation("com.bemobi:smart-checkout:0.2.1")
}

💳 Configuração do Google Pay™

Para habilitar o Google Pay™, adicione ao AndroidManifest.xml:

<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true" />

A disponibilidade do Google Pay™ depende de habilitação comercial e configuração adequada do ambiente.


🔗 Integração via deep link

Para receber callbacks do checkout via deep link, adicione o seguinte intent-filter no arquivo AndroidManifest.xml, dentro da sua Activity principal (substitua abc-1234 pelo seu identificador do parceiro):

<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.BROWSABLE" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="http" />
    <data android:scheme="https" />
    <data android:host="smart-checkout.bemobi.com" />
    <data android:path="/callback/abc-1234" />
</intent-filter>

Estrutura do deep link: https://smart-checkout.bemobi.com/callback/{partnerKey}

Importante: para ativar o deep link, informe ao suporte:

  • Application ID
  • SHA-256 Certificate Fingerprint

🧠 Inicialização e uso

Inicializar a engine. Na sua MainActivity:

import android.os.Bundle
import androidx.activity.ComponentActivity
import com.bemobi.SmartCheckout

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        SmartCheckout.initializeEngine(this)
    }
}

Exibir o smart checkout

SmartCheckout.showBottomSheet(
    context = this, // Activity
    config = SmartCheckout.Config(
        environment = SmartCheckout.Environment.PRODUCTION,
        sessionCode = "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
        partnerKey = "abc-1234",
        deepLink = Uri.parse("https://smart-checkout.bemobi.com/callback/abc-1234"),
        initialHeightFraction = 1.0,
        style = SmartCheckout.Style.FLAT
    ),
    callbacks = SmartCheckout.Callbacks(
        onSuccess = { result ->
            println("Checkout completed successfully: $result")
        },
        onError = { error ->
            println("Checkout error: $error")
        },
        onClose = {
            println("Checkout closed")
        }
    )
)

Chamar o checkout via WebView (bridge)

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import org.json.JSONObject
import com.bemobi.SmartCheckout

class WebViewActivity : ComponentActivity() {
    private lateinit var webView: WebView
    private val sessionCode: String by lazy {
        intent.getStringExtra(EXTRA_SESSION_CODE).orEmpty()
    }
    private val environment: String by lazy {
        intent.getStringExtra(EXTRA_ENVIRONMENT).orEmpty()
    }

    private fun postMessage(message: Map<String, Any>) {
        val script = "window.postMessage(${JSONObject(message)}, \"*\");"
        webView.post {
            webView.evaluateJavascript(script, null)
        }
    }

    private fun generateHtml(): String {
        return """
        <!DOCTYPE html>
        <html>

        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
          <style>
            :root {
              --white: #ffffff;
              --blue-start: #2e7cf6;
              --blue-end: #1f63e5;
              --blue-shadow: rgba(31, 99, 229, 0.28);
              --gray-very-light: #f8fafc;
              --gray-light: #e5e7eb;
              --gray-divider: #f1f5f9;
              --gray-very-dark: #111827;
              --slate-deep: #0f172a;
              --slate-muted: #64748b;
              --shadow-soft: rgba(0, 0, 0, .04);
              --green-deep: #166534;
              --green-wash: #ecfdf5;
              --green-border: #bbf7d0;
              --red-deep: #7f1d1d;
              --red-wash: #fef2f2;
              --red-border: #fecaca;
              --indigo-deep: #1e3a8a;
              --indigo-wash: #eff6ff;
              --indigo-border: #bfdbfe;
            }

            body {
              display: flex;
              margin: max(24px, env(safe-area-inset-top, 24px)) 24px max(24px, env(safe-area-inset-bottom, 24px));
              gap: 24px;
              font-family: ui-monospace, monospace;
              font-size: 14px;
              flex-direction: column;
            }

            button {
              appearance: none;
              display: inline-flex;
              align-items: center;
              justify-content: center;
              padding: 14px 20px;
              min-width: 220px;
              font-size: 16px;
              font-weight: 700;
              letter-spacing: .2px;
              line-height: 1;
              color: var(--white);
              background: linear-gradient(180deg, var(--blue-start) 0%, var(--blue-end) 100%);
              border: none;
              border-radius: 12px;
              box-shadow: 0 6px 16px var(--blue-shadow);
              user-select: none;
              transition: transform .04s ease, filter .12s ease;
            }

            button:active {
              transform: translateY(1px) scale(0.99);
              filter: brightness(96%);
            }

            pre {
              margin: 6px 0 0;
              padding: 8px;
              background: var(--gray-very-light);
              border: 1px solid var(--gray-light);
              border-radius: 6px;
              overflow: auto;
              font-size: 12px;
              line-height: 1.5;
            }

            #log {
              padding: 12px;
              border-radius: 6px;
              max-height: 60vh;
              overflow: auto;
              background: var(--white);
              color: var(--gray-very-dark);
              border: 1px solid var(--gray-light);
              box-shadow: 0 2px 10px var(--shadow-soft);
            }

            #log #empty {
              padding: 16px;
              text-align: center;
              color: var(--slate-muted);
              background: var(--gray-very-light);
              border: 1px dashed var(--gray-light);
              border-radius: 8px;
              margin: 8px;
            }

            #log .item {
              border-bottom: 1px solid var(--gray-divider);
              padding: 10px 6px;
            }

            #log .item:last-child {
              border-bottom: 0;
            }

            #log .head {
              display: flex;
              gap: 8px;
              align-items: baseline;
              margin-bottom: 6px;
            }

            #log .time {
              color: var(--slate-muted);
              font-size: 12px;
            }

            #log .message {
              white-space: pre-wrap;
              margin-left: 2px;
            }

            .badge {
              font-size: 11px;
              font-weight: 700;
              padding: 2px 8px;
              border-radius: 999px;
              border: 1px solid var(--gray-light);
              background: var(--gray-very-light);
              color: var(--slate-deep);
            }

            .success .badge {
              color: var(--green-deep);
              background: var(--green-wash);
              border-color: var(--green-border);
            }

            .error .badge {
              color: var(--red-deep);
              background: var(--red-wash);
              border-color: var(--red-border);
            }

            .info .badge {
              color: var(--indigo-deep);
              background: var(--indigo-wash);
              border-color: var(--indigo-border);
            }

            .meta {
              display: flex;
              flex-direction: column;
              gap: 4px;
              color: var(--slate-deep);
            }

            .meta .label {
              font-weight: 700;
              text-transform: uppercase;
            }

            .meta .value {
              word-break: break-all;
            }
          </style>
        </head>

        <body>
          <div class="meta">
            <div class="label">Session Code</div>
            <div class="value">$sessionCode</div>
          </div>

          <div class="meta">
            <div class="label">Environment</div>
            <div class="value">$environment</div>
          </div>

          <button onclick="openSmartCheckout()">Open Smart Checkout SDK</button>

          <div id="log">
            <div id="empty">
              No events yet. Trigger a checkout to see logs here.
            </div>
          </div>

          <script>
            Object.defineProperty(window, 'smartCheckoutLoaded', {
              get() {
                const bridge = window.bridge && window.bridge.startPaymentFlow;
                return !!bridge && typeof bridge === 'function';
              },
            });

            const EVENT_LOG_MAP = {
              'checkout:action': { level: 'info', message: 'Custom action' },
              'checkout:success': { level: 'success', message: 'Payment completed successfully' },
              'checkout:error': { level: 'error', message: 'Payment error' },
              'checkout:close': { level: 'info', message: 'Checkout closed' },
            };

            function appendLog({ level, message, payload }) {
              const empty = document.getElementById('empty');
              if (empty) empty.remove();

              const item = document.createElement('div');
              item.className = ['item', level].join(' ');

              const head = document.createElement('div');
              head.className = 'head';

              const badge = document.createElement('span');
              badge.className = 'badge';
              badge.textContent = level.replace(/^\\w/, char => char.toUpperCase());

              const time = document.createElement('span');
              time.className = 'time';
              time.textContent = new Date().toLocaleTimeString();

              const text = document.createElement('div');
              text.className = 'message';
              text.textContent = message;

              head.appendChild(badge);
              head.appendChild(time);
              item.appendChild(head);
              item.appendChild(text);

              if (payload) {
                const pre = document.createElement('pre');
                pre.textContent = JSON.stringify(payload, null, 2);
                item.appendChild(pre);
              }

              const log = document.getElementById('log');
              log.prepend(item);
              log.scrollTop = 0;
            }

            function openSmartCheckout() {
              if (window.smartCheckoutLoaded) {
                const paymentData = {
                  sessionCode: "$sessionCode",
                  environment: "$environment"
                };

                window.bridge.startPaymentFlow(JSON.stringify(paymentData));
              } else {
                appendLog({ level: 'error', message: 'Smart Checkout bridge is not ready' });
              }
            }

            window.addEventListener('message', (event) => {
              const { type, payload } = event.data;
              const log = EVENT_LOG_MAP[type];
              if (log) appendLog({ ...log, payload });
            });
          </script>
        </body>

        </html>
        """.trimIndent()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Inicializa a engine se ainda não foi feito na MainActivity
        SmartCheckout.initializeEngine(this)

        webView = WebView(this)
        webView.settings.javaScriptEnabled = true
        webView.webViewClient = WebViewClient()
        webView.addJavascriptInterface(object {
            @JavascriptInterface
            fun startPaymentFlow(jsonString: String) {
                runOnUiThread {
                    val jsonObject = JSONObject(jsonString)
                    val sessionCode = jsonObject.getString("sessionCode")
                    val environment = jsonObject.getString("environment")

                    SmartCheckout.setCustomActionHandler { payload ->
                        println("Custom action received: $payload")
                        postMessage(mapOf("type" to "checkout:action", "payload" to payload))
                    }

                    SmartCheckout.showBottomSheet(
                        context = this@WebViewActivity,
                        config = SmartCheckout.Config(
                            sessionCode = sessionCode,
                            environment = SmartCheckout.Environment.from(environment),
                            initialHeightFraction = 0.9
                        ),
                        callbacks = SmartCheckout.Callbacks(
                            onSuccess = { result ->
                                println("Checkout completed successfully: $result")
                                postMessage(mapOf("type" to "checkout:success", "payload" to result))
                            },
                            onError = { error ->
                                println("Checkout error: $error")
                                postMessage(mapOf("type" to "checkout:error", "payload" to error))
                            },
                            onClose = {
                                println("Checkout closed")
                                postMessage(mapOf("type" to "checkout:close"))
                            }
                        )
                    )
                }
            }
        }, "bridge")
        webView.loadDataWithBaseURL(null, generateHtml(), "text/html", "utf-8", null)

        setContentView(webView)
    }

    companion object {
        private const val EXTRA_SESSION_CODE = "extra_session_code"
        private const val EXTRA_ENVIRONMENT = "extra_environment"

        fun intent(context: Context, sessionCode: String, environment: String) =
            Intent(context, WebViewActivity::class.java).apply {
                putExtra(EXTRA_SESSION_CODE, sessionCode)
                putExtra(EXTRA_ENVIRONMENT, environment)
            }
    }
}

Registrar um handler para ações customizadas

SmartCheckout.setCustomActionHandler { payload ->
    println("Custom action received: $payload")
}

Receber o deep link e iniciar o smart checkout. Na sua MainActivity:

import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import com.bemobi.SmartCheckout

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        SmartCheckout.initializeEngine(this)
        intent.data?.let { uri ->
            val config = SmartCheckout.Config(
                environment = SmartCheckout.Environment.PRODUCTION,
                deepLink = uri
            )
            val callbacks = SmartCheckout.Callbacks(
                onSuccess = { result ->
                    println("Checkout completed successfully: $result")
                },
                onError = { error ->
                    println("Checkout error: $error")
                },
                onClose = {
                    println("Checkout closed")
                }
            )
            SmartCheckout.showBottomSheet(
                context = this,
                config = config,
                callbacks = callbacks
            )
        }
    }
}

🧠 Funcionalidades

  • SmartCheckout.initializeEngine(context: Context): inicializa a engine necessária para o fluxo do checkout.
  • SmartCheckout.showBottomSheet(context: Context, config: Config, callbacks: Callbacks): exibe o checkout em um bottom sheet.
  • SmartCheckout.setCustomActionHandler(handler: (Map<String, Any>) -> Unit): registra um handler para ações customizadas disparadas pelo fluxo do checkout.

⚙️ Configuração

A configuração do smart checkout é feita via SmartCheckout.Config:

  • environment: ambiente (PRODUCTION, SANDBOX, DEVELOPMENT).
  • sessionCode: código da sessão (opcional).
  • partnerKey: identificador do parceiro usado no deep link para identificar a origem.
  • deepLink: URL que abre diretamente uma página específica do smart checkout (opcional).
  • initialHeightFraction: altura inicial do bottom sheet (opcional, 0.0 a 1.0).
  • style: estilo visual (ROUNDED, FLAT).

🔔 Callbacks

  • onSuccess(Map<String, Any>): sucesso no pagamento.
  • onError(Map<String, Any>): erro no fluxo.
  • onClose(): fechamento do bottom sheet.

🧠 Boas práticas

  • Inicialize o SDK apenas uma vez por ciclo de vida da aplicação.
  • Utilize deep link para garantir retorno confiável.
  • Sempre trate callbacks de erro e fechamento.
  • Use Webhook como fonte oficial de status da transação.
  • Valide o fluxo em diferentes dispositivos e versões Android.

🎯 Resumo

O Android SDK do smart checkout é a forma mais completa de integração mobile, permitindo uma experiência de pagamento fluida, nativa e altamente personalizável, com suporte a recursos modernos como carteiras digitais e deep linking.


Did this page help you?