SDK-PUNTO - Manual de Integração

INTRODUÇÃO

Conheça o SDK-PUNTO

O SDK-PUNTO é uma solução completa de pagamentos para terminais POS Android, permitindo que desenvolvedores integrem funcionalidades de pagamento de forma simples e segura em suas aplicações de automação comercial.

Com o SDK-PUNTO, você tem acesso a múltiplas formas de pagamento em um único dispositivo, incluindo débito, crédito, Pix, Ticket Log, Good Card e muito mais. O SDK é fornecido como arquivo AAR (Android Archive) e oferece uma API moderna baseada em Builder Pattern para facilitar a integração.

Principais Benefícios

Modelos de Integração

O SDK-PUNTO pode ser integrado em diferentes cenários de negócio:


PRIMEIROS PASSOS

Pré-requisitos

Antes de iniciar a integração com o SDK-PUNTO, certifique-se de atender aos seguintes requisitos técnicos:

Linguagem de Programação

O SDK-PUNTO é compatível com aplicativos desenvolvidos em Java ou Kotlin. Recomenda-se o uso dessas linguagens para garantir melhor desempenho, estabilidade e integração com o sistema.

Ambiente de Desenvolvimento

Recomenda-se o uso da IDE Android Studio versão 2022.1 ou superior para garantir a correta execução dos aplicativos.

Links úteis:
- Guia de instalação e configuração do Android Studio
- Criando um novo projeto no Android Studio

Versão Target da Aplicação

Para garantir compatibilidade com os terminais POS, configure:

Dependências Necessárias

O SDK requer as seguintes dependências:

implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("com.google.code.gson:gson:2.10.1")

Permissões Necessárias

No AndroidManifest.xml do seu aplicativo, adicione as seguintes permissões:

<!-- Permissões customizadas do SDK -->
<permission 
    android:name="com.sysdata.pos.permission.READ_SHARED_PREFS" 
    android:protectionLevel="normal" />
<permission 
    android:name="com.sysdata.pos.permission.WRITE_SHARED_PREFS" 
    android:protectionLevel="normal" />

Glossário


VISÃO GERAL TÉCNICA

Funcionalidades

O SDK-PUNTO oferece as seguintes funcionalidades principais:

Arquitetura

O SDK-PUNTO é baseado em uma arquitetura moderna Android:

Fluxo de Integração

[Aplicativo Parceiro]
       ↓
   SetupSdk.startSdk(context)
       ↓
   PayOrder.Builder()
       ↓
   (Processamento no Terminal)
       ↓
   ListenerPay.onSuccess() ou onError()
       ↓
[Aplicativo recebe SuccessData ou erro]

Terminais Compatíveis

O SDK-PUNTO é compatível com os seguintes terminais:


CONFIGURAÇÃO DO PROJETO

1. Fazer o download do sdk

Passo 1: Faça o download do sdk_punto.aar

1. Adicionar o SDK ao Projeto

Passo 1: Copie o arquivo sdk_punto.aar para a pasta app/libs/ do seu projeto Android.

Passo 2: Configure o arquivo app/build.gradle.kts:

android {
    namespace = "com.seuapp.package"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.seuapp.package"
        minSdk = 22
        targetSdk = 34
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {
    // Importa todos os arquivos .aar e .jar da pasta libs
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))

    // Dependências necessárias
    implementation("androidx.core:core-ktx:1.13.1")
    implementation("androidx.appcompat:appcompat:1.7.0")
    implementation("com.google.code.gson:gson:2.10.1")
}

2. Inicializar o SDK

No método onCreate() da sua MainActivity, inicialize o SDK:

import com.sysdata.sdk.device.SetupSdk

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // IMPORTANTE: Inicializar o SDK antes de qualquer operação
        SetupSdk.startSdk(this)
    }
}

IMPORTANTE: A inicialização deve ser feita apenas uma vez, preferencialmente na Activity principal do aplicativo.

3. Tipos de Operação

As operações são controladas pelo enum OperationType:

Operação Enum Descrição
Débito OperationType.DEBIT Pagamento com cartão de débito
Crédito OperationType.CREDIT Pagamento com cartão de crédito (à vista ou parcelado)
Pix OperationType.PIX Pagamento via Pix
Ticket Log OperationType.TICKET_LOG Pagamento com cartão de frota/benefícios
Estorno OperationType.REVERSAL Cancelamento de transações
TAG OperationType.TAG Operações com tags

4. Tipos de Crédito

Quando usar OperationType.CREDIT, configure o tipo através do enum CreditType:

| Tipo | Enum | Descrição |
|------|------|-----------||
| À Vista | CreditType.IN_CASH | Crédito à vista |
| Parcelado Sem Juros | CreditType.INSTALLMENTS_WITHOUT_INTEREST | Parcelado sem juros |
| Parcelado Com Juros | CreditType.INSTALLMENTS_WITH_INTEREST | Parcelado com juros |
| Sem Parcelamento | CreditType.NO_INSTALLMENTS | Sem parcelamento (usado para Ticket Log) |

5. Implementar Callbacks

Todas as transações retornam resultado através da interface ListenerPay:

import android.os.Bundle
import com.sysdata.sdk.device.init.ListenerPay
import com.sysdata.sdk.device.init.ConstStatus
import com.sysdata.sdk.device.SuccessData

private val listenerOrder = object : ListenerPay {

    override fun onError(bundle: Bundle?) {
        bundle?.let {
            val errorMessage = it.getString(ConstStatus.ERROR_DATA.name)
            // Tratar erro
            Toast.makeText(this@YourActivity, errorMessage, Toast.LENGTH_SHORT).show()
        }
    }

    override fun onSuccess(bundle: Bundle?) {
        bundle?.let {
            val successData = it.getParcelable(ConstStatus.SUCCESS_DATA.name) as? SuccessData

            successData?.let { data ->
                // Dados da transação aprovada
                val nsu = data.nsuAuthorization          // NSU da transação
                val transactionId = data.transactionId   // ID/DOC da transação
                val value = data.value                   // Valor
                val cardFlag = data.cardFlag             // Bandeira (Visa, Master, etc)
                val bin = data.bin                       // BIN do cartão
                val authCode = data.codAuth              // Código de autorização
                val date = data.date                     // Data (dd/MM/yyyy)
                val hour = data.hour                     // Hora (HH:mm:ss)
                val ticketClient = data.ticketClient     // Comprovante cliente
                val ticketCompany = data.ticketCompany   // Comprovante estabelecimento

                // Processar dados conforme necessário
            }
        }
    }
}

6. Conversão de Valores

O SDK espera valores em BigDecimal. Use esta função para converter:

import java.math.BigDecimal

fun getInputtedAmount(imputedAmountText: String): BigDecimal {
    // Remove todos os caracteres não numéricos
    val imputedCentsAmount = imputedAmountText.replace("\\D".toRegex(), "")
    val decimalPlaces = 2
    // Move o ponto decimal 2 casas para a esquerda
    return BigDecimal(imputedCentsAmount).movePointLeft(decimalPlaces)
}

// Exemplos de uso:
// "R$ 10,50" -> BigDecimal("10.50")
// "1050"     -> BigDecimal("10.50")
// "100"      -> BigDecimal("1.00")

EXEMPLOS DE CÓDIGO

Estrutura Básica de Transação

Todas as transações seguem o padrão Builder:

import com.sysdata.sdk.device.init.PayOrder
import com.sysdata.sdk.device.init.OperationType
import com.sysdata.sdk.device.init.ListenerPay
import java.math.BigDecimal

PayOrder.Builder()
    .context(this)                          // Context da Activity
    .amount(BigDecimal("10.50"))            // Valor da transação
    .listener(listenerOrder)                // Callback de resposta
    .operationType(OperationType.CREDIT)    // Tipo de operação
    .build()                                // Executa a transação

Pagamento com Débito

import com.sysdata.sdk.device.init.PayOrder
import com.sysdata.sdk.device.init.OperationType
import java.math.BigDecimal

PayOrder.Builder()
    .amount(BigDecimal("50.00"))
    .context(this)
    .listener(listenerOrder)
    .operationType(OperationType.DEBIT)
    .build()

Pagamento com Crédito

Crédito à Vista

import com.sysdata.sdk.device.init.CreditType

PayOrder.Builder()
    .amount(BigDecimal("100.00"))
    .creditType(CreditType.IN_CASH)
    .listener(listenerOrder)
    .operationType(OperationType.CREDIT)
    .build()

Crédito Parcelado Sem Juros

PayOrder.Builder()
    .amount(BigDecimal("300.00"))
    .installments("3")                                      // Número de parcelas
    .creditType(CreditType.INSTALLMENTS_WITHOUT_INTEREST)
    .listener(listenerOrder)
    .operationType(OperationType.CREDIT)
    .build()

Crédito Parcelado Com Juros

PayOrder.Builder()
    .amount(BigDecimal("600.00"))
    .installments("6")
    .creditType(CreditType.INSTALLMENTS_WITH_INTEREST)
    .listener(listenerOrder)
    .operationType(OperationType.CREDIT)
    .build()

Pagamento com Pix

PayOrder.Builder()
    .amount(BigDecimal("75.50"))
    .context(this)
    .listener(listenerOrder)
    .operationType(OperationType.PIX)
    .build()

Pagamento com Ticket Log/Ecofrota

Modo Normal (SDK solicita dados)

PayOrder.Builder()
    .context(this)
    .amount(BigDecimal("200.00"))
    .listener(listenerOrder)
    .creditType(CreditType.NO_INSTALLMENTS)
    .operationType(OperationType.TICKET_LOG)
    .build()

Modo Bypass (Dados pré-informados)

PayOrder.Builder()
    .context(this)
    .amount(BigDecimal("200.00"))
    .ecofrotaOdometroKm("12345")              // Odômetro em KM
    .ecofrotaLitrosCombustivel("50.5")        // Litros de combustível
    .ecofrotaLitrosOleo("2.0")                // Litros de óleo
    .ecofrotaCodigoManutencao("MNT001")       // Código de manutenção
    .ecofrotaTipoCombustive("1")              // Tipo: 1=Gasolina, 2=Diesel, etc
    .ecofrotaMatricula("ABC1234")             // Matrícula do veículo
    .listener(listenerOrder)
    .creditType(CreditType.NO_INSTALLMENTS)
    .operationType(OperationType.TICKET_LOG)
    .build()

IMPORTANTE: Quando os dados Ecofrota são informados via Builder (bypass), o SDK não solicitará novamente durante a transação.

### Good Card (Pagamento por Litros)

```kotlin
PayOrder.Builder()
    .context(this)
    .qtdLiters(45.5)                          // Quantidade de litros
    .fuelType(1)                              // Tipo de combustível
    .listener(listenerOrder)
    .creditType(CreditType.NO_INSTALLMENTS)
    .operationType(OperationType.TICKET_LOG)
    .build()

Estorno/Cancelamento

PayOrder.Builder()
    .context(this)
    .listener(listenerOrder)
    .operationType(OperationType.REVERSAL)
    .build()

TAG

PayOrder.Builder()
    .listener(listenerOrder)
    .operationType(OperationType.TAG)
    .build()

Impressão de Comprovantes

Estrutura Básica

import com.sysdata.sdk.device.init.PrintVoucher
import com.sysdata.sdk.device.print.PrintService

PrintVoucher.Builder()
    .context(this)
    .listener(listenerPrint)
    // Escolher um dos métodos abaixo
    .build()

Listener de Impressão

private val listenerPrint = object : PrintService.ListenerPrint {
    override fun onPrintFailed() {
        Toast.makeText(this@YourActivity, "Falha na impressão", Toast.LENGTH_SHORT).show()
    }

    override fun onPrintSuccess() {
        Toast.makeText(this@YourActivity, "Impressão realizada", Toast.LENGTH_SHORT).show()
    }
}

Impressão via Bitmap

import android.graphics.Bitmap

val bitmap: Bitmap = // seu bitmap

PrintVoucher.Builder()
    .bitmap(bitmap)
    .context(this)
    .listener(listenerPrint)
    .build()

Impressão via Texto

val textoComprovante = """
    COMPROVANTE DE VENDA

    Data: 10/03/2026
    Valor: R$ 10,50

    Obrigado pela preferência!
""".trimIndent()

PrintVoucher.Builder()
    .textForPrint(textoComprovante)
    .context(this)
    .listener(listenerPrint)
    .build()

Impressão via Base64

import java.io.BufferedReader

val base64Reader: BufferedReader = // seu BufferedReader com dados Base64

PrintVoucher.Builder()
    .base64(base64Reader)
    .context(this)
    .listener(listenerPrint)
    .build()

Reimpressão de Comprovantes

// Obter o ticket salvo do SuccessData
val ticket = successData.ticketClient  // ou ticketCompany

// Normalizar o formato do ticket
val normalizedTicket = ticket
    .trim()
    .trimStart('[')
    .trimEnd(']')
    .replace("@[", "\n")

// Imprimir usando printTicket
PrintVoucher.Builder()
    .context(this)
    .printTicket(normalizedTicket)
    .listenerPay(object : ListenerPay {
        override fun onError(bundle: Bundle?) {
            val error = bundle?.getString(ConstStatus.ERROR_DATA.name)
            Toast.makeText(this@YourActivity, error ?: "Erro na reimpressão", Toast.LENGTH_SHORT).show()
        }

        override fun onSuccess(bundle: Bundle?) {
            Toast.makeText(this@YourActivity, "Reimpressão realizada", Toast.LENGTH_SHORT).show()
        }
    })
    .build()

CONTRATO DE RETORNO

Estrutura do SuccessData

Quando uma transação é aprovada, o callback onSuccess() recebe um objeto SuccessData com os seguintes campos:

Campo Tipo Descrição
nsuAuthorization Int NSU da transação
transactionId String ID/DOC da transação
value String Valor da transação
cardFlag String Bandeira do cartão (Visa, Master, Elo, etc)
bin String BIN do cartão (primeiros 6 dígitos)
codAuth String Código de autorização
date String Data da transação (dd/MM/yyyy)
hour String Hora da transação (HH:mm:ss)
ticketClient String Comprovante do cliente (formato texto)
ticketCompany String Comprovante do estabelecimento (formato texto)
discount String Desconto aplicado
valueAuthorized String Valor cobrado

Processando Sucesso

override fun onSuccess(bundle: Bundle?) {
    bundle?.let {
        val successData = it.getParcelable(ConstStatus.SUCCESS_DATA.name) as? SuccessData

        successData?.let { data ->
            // Exibir dados da transação
            val message = """
                Transação Aprovada!
                NSU: ${data.nsuAuthorization}
                DOC: ${data.transactionId}
                Valor: ${data.value}
                Autorização: ${data.codAuth}
                Bandeira: ${data.cardFlag}
            """.trimIndent()

            Toast.makeText(this@YourActivity, message, Toast.LENGTH_LONG).show()

            // Salvar transação no banco de dados
            saveTransaction(data)

            // Imprimir comprovante (opcional)
            printReceipt(data.ticketClient)
        }
    }
}

Processando Erro

override fun onError(bundle: Bundle?) {
    bundle?.let {
        val errorMessage = it.getString(ConstStatus.ERROR_DATA.name)

        when {
            errorMessage?.contains("cancelada", ignoreCase = true) == true -> {
                Toast.makeText(this@YourActivity, "Operação cancelada", Toast.LENGTH_SHORT).show()
            }
            errorMessage?.contains("não inicializado", ignoreCase = true) == true -> {
                showDialog("Terminal não configurado", "Configure o terminal antes de continuar")
            }
            else -> {
                Toast.makeText(this@YourActivity, errorMessage ?: "Erro desconhecido", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

Exemplo Completo de Implementação

import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.sysdata.sdk.device.SetupSdk
import com.sysdata.sdk.device.SuccessData
import com.sysdata.sdk.device.init.ConstStatus
import com.sysdata.sdk.device.init.CreditType
import com.sysdata.sdk.device.init.ListenerPay
import com.sysdata.sdk.device.init.OperationType
import com.sysdata.sdk.device.init.PayOrder
import java.math.BigDecimal

class PaymentActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_payment)

        // Inicializar SDK
        SetupSdk.startSdk(this)

        setupPaymentButtons()
    }

    private fun setupPaymentButtons() {
        val edtValue = findViewById<EditText>(R.id.ed_value)

        // Botão de pagamento em crédito
        findViewById<Button>(R.id.btnPayCredit).setOnClickListener {
            val amount = getInputtedAmount(edtValue.text.toString())

            PayOrder.Builder()
                .amount(amount)
                .creditType(CreditType.IN_CASH)
                .listener(listenerOrder)
                .operationType(OperationType.CREDIT)
                .build()
        }

        // Botão de pagamento em débito
        findViewById<Button>(R.id.btnPayDebit).setOnClickListener {
            val amount = getInputtedAmount(edtValue.text.toString())

            PayOrder.Builder()
                .amount(amount)
                .context(this)
                .listener(listenerOrder)
                .operationType(OperationType.DEBIT)
                .build()
        }

        // Botão PIX
        findViewById<Button>(R.id.btnPayPix).setOnClickListener {
            val amount = getInputtedAmount(edtValue.text.toString())

            PayOrder.Builder()
                .amount(amount)
                .context(this)
                .listener(listenerOrder)
                .operationType(OperationType.PIX)
                .build()
        }
    }

    private val listenerOrder = object : ListenerPay {
        override fun onError(bundle: Bundle?) {
            bundle?.let {
                val error = it.getString(ConstStatus.ERROR_DATA.name)
                Toast.makeText(this@PaymentActivity, error, Toast.LENGTH_SHORT).show()
            }
        }

        override fun onSuccess(bundle: Bundle?) {
            bundle?.let {
                val successData = it.getParcelable(ConstStatus.SUCCESS_DATA.name) as? SuccessData

                successData?.let { data ->
                    val message = """
                        Transação Aprovada!
                        NSU: ${data.nsuAuthorization}
                        DOC: ${data.transactionId}
                        Valor: ${data.value}
                        Autorização: ${data.codAuth}
                    """.trimIndent()

                    Toast.makeText(this@PaymentActivity, message, Toast.LENGTH_LONG).show()
                }
            }
        }
    }

    private fun getInputtedAmount(imputedAmountText: String): BigDecimal {
        val imputedCentsAmount = imputedAmountText.replace("\\D".toRegex(), "")
        val decimalPlaces = 2
        return BigDecimal(imputedCentsAmount).movePointLeft(decimalPlaces)
    }
}

BOAS PRÁTICAS PARA DESENVOLVEDORES

Segurança e Privacidade

Performance e Otimização

Qualidade de Código

Usabilidade e Experiência do Usuário

Monitoramento e Logs

Exemplo de Implementação com Boas Práticas

class PaymentManager(
    private val context: Context,
    private val crashlytics: FirebaseCrashlytics
) {

    companion object {
        private const val TAG = "PaymentManager"
        private const val TIMEOUT_SECONDS = 60L
    }

    fun processPayment(
        amount: String,
        paymentType: PaymentType,
        onSuccess: (String) -> Unit,
        onError: (String) -> Unit
    ) {
        // Validação de entrada
        if (!isValidAmount(amount)) {
            onError("Valor inválido")
            return
        }

        // Log de evento (sem dados sensíveis)
        crashlytics.log("Payment initiated: type=$paymentType")

        try {
            val intent = createPaymentIntent(amount, paymentType)
            // Lançar intent...
        } catch (e: Exception) {
            crashlytics.recordException(e)
            onError("Erro ao processar pagamento: ${e.message}")
        }
    }

    private fun isValidAmount(amount: String): Boolean {
        return try {
            val value = amount.toDoubleOrNull()
            value != null && value > 0
        } catch (e: Exception) {
            false
        }
    }

    private fun createPaymentIntent(amount: String, type: PaymentType): Intent {
        return Intent().apply {
            setClassName(
                "com.sysdata.pos",
                "com.sysdata.pos.activities.sdk.SalesActivity"
            )
            putExtra("action", type.action)
            putExtra("amount", amount)
        }
    }
}

enum class PaymentType(val action: String) {
    DEBIT("DEBIT"),
    CREDIT("CREDIT"),
    PIX("PIX"),
    TICKET_LOG("TICKET_LOG")
}

CÓDIGOS DE ERRO

Erros Comuns

Código Mensagem Causa Solução
- "Terminal não inicializado" Terminal POS não está configurado Inicialize o terminal antes de usar
- "Operação cancelada" Usuário cancelou a operação Informar ao usuário e permitir nova tentativa
- "Valor inválido" Formato do valor está incorreto Validar formato antes de enviar
- "Erro de comunicação" Falha na comunicação com o terminal Verificar conexão e tentar novamente
- "Cartão não inserido" Cartão não foi inserido/aproximado Solicitar inserção/aproximação do cartão

Tratamento de Erros

private fun handleError(errorMessage: String) {
    when {
        errorMessage.contains("não inicializado", ignoreCase = true) -> {
            showDialog(
                title = "Terminal não configurado",
                message = "Por favor, configure o terminal antes de continuar.",
                action = "Configurar"
            )
        }

        errorMessage.contains("cancelada", ignoreCase = true) -> {
            showToast("Operação cancelada")
        }

        errorMessage.contains("valor", ignoreCase = true) -> {
            showDialog(
                title = "Valor inválido",
                message = "Por favor, verifique o valor e tente novamente.",
                action = "OK"
            )
        }

        else -> {
            showDialog(
                title = "Erro",
                message = errorMessage,
                action = "OK"
            )
        }
    }
}

PERGUNTAS FREQUENTES (FAQ)

Integração

Q: Como sei se a operação foi aprovada?

A: Verifique se operation_status >= 0 e se ERROR_DATA está vazio ou nulo.

val isApproved = operationStatus != null && 
                 operationStatus >= 0 && 
                 errorMessage.isNullOrBlank()

Q: Como tratar cancelamento pelo usuário?

A: Verifique se a mensagem de erro contém "cancelada":

val isCancelled = errorMessage?.contains("cancelada", ignoreCase = true) == true

Q: Posso usar o SDK em aplicativos React Native ou Flutter?

A: Sim, você pode criar módulos nativos (bridges) para chamar o SDK a partir dessas plataformas.

Operações

Q: Como faço para parcelar um pagamento?

A: Use action = CREDIT com os parâmetros installments e creditType:

putExtra("action", "CREDIT")
putExtra("amount", "300.00")
putExtra("installments", "3")
putExtra("creditType", "INSTALLMENTS_WITHOUT_INTEREST")

Q: Como reimprimir um comprovante?

A: Use action = REPRINT com o conteúdo do comprovante no parâmetro ticketClient.

Configuração

Q: Quais permissões são necessárias?

A: No mínimo INTERNET e ACCESS_NETWORK_STATE. Veja a seção Permissões Necessárias.

Q: O SDK funciona em modo debug?

A: Sim, o SDK funciona normalmente em modo debug.

Q: Preciso de credenciais especiais?

A: Não, a integração via Intent não requer credenciais. O terminal já deve estar configurado.

Troubleshooting

Q: O Intent não está sendo reconhecido

A: Verifique se você declarou a visibilidade do pacote no AndroidManifest.xml (Android 11+):

<queries>
    <package android:name="com.sysdata.pos" />
</queries>

Q: A Activity não retorna resultado

A: Certifique-se de usar ActivityResultContracts.StartActivityForResult() e não startActivityForResult() (deprecated).

Q: Como debugar problemas?

A: Adicione logs para verificar os parâmetros enviados e recebidos:

Log.d("SDK-PUNTO", "Sending: action=$action, amount=$amount")
Log.d("SDK-PUNTO", "Received: status=$operationStatus, error=$errorMessage")

Desenvolvido por Sysdata | Última atualização: Agosto 2026