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: 24compileSdkVersion: 35targetSdkVersion: 35
⚙️ Instalação
Atualize as arquiteturas suportadas no build.gradle do app:
build.gradle do app:defaultConfig {
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
}
}Adicione os repositórios Maven no settings.gradle:
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:
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:
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:
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:
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.
Updated 4 months ago