Configuração do SDK
Guia de configuração e do fluxo de inicialização do SDK.
Ciclo de Uso
O fluxo de preparação do terminal segue estas etapas:
- Criar a instância de
PosPinpadviacreate(). - (Opcional) Configurar os endpoints via
setEndpointProvider(). - Na primeira execução no terminal: buscar a personalização do parceiro via
getTerminalConfig(slug)e registrar o terminal (register(...)). - A cada execução do app: inicializar o terminal via
terminalInitialization(request)(carrega tabelas EMV). - A partir daí, executar pagamentos e demais operações.
Os métodos suspend devem ser chamados dentro de uma coroutine (por exemplo, lifecycleScope.launch). Os callbacks do fluxo de pagamento são entregues na main thread; os de impressão, de tela secundária e de erro HTTP chegam na thread de origem. Veja Callbacks.
Criar instância do SDK
Implemente PosPinpadCallback e crie a instância passando o Context e o callback. Sempre use PosPinpad.create() — ele inicializa o HttpClient com mTLS antes de qualquer request.
import com.tupifintech.apossdk.sdk.api.PosPinpad
import com.tupifintech.apossdk.sdk.api.PosPinpadCallback
private lateinit var posPinpad: PosPinpad
posPinpad = PosPinpad.create(context, callback)
Mantenha uma única instância por processo. Em apps com várias telas, crie-a no Application e repasse os eventos para a tela ativa.
Certificado mTLS
O certificado cliente do gateway vem dentro do SDK, conforme o artefato (apossdk-debug-staging = staging, apossdk-release-prod = produção). Para sobrescrevê-lo, passe um MtlsCertificate no terceiro parâmetro de create:
val posPinpad = PosPinpad.create(
context,
callback,
MtlsCertificate.fromAssets(context, "minha-cadeia.pem", "minha-chave.pem")
)
// ou MtlsCertificate.fromPem(cadeiaEmPem, chaveEmPem)
| Parte | Formato |
|---|---|
| Cadeia | X.509 em PEM, um ou mais blocos BEGIN CERTIFICATE |
| Chave | PKCS#8 em PEM (BEGIN PRIVATE KEY), sem senha. Chave PKCS#1 (BEGIN RSA PRIVATE KEY) precisa ser convertida |
Ordem de resolução: o MtlsCertificate passado em create → o par embutido no SDK → erro (IllegalStateException).
O par embutido é lido dos assets yby-pos-v1-fullchain.pem e yby-pos-v1.key.pem. No merge de assets, o do app vence o da biblioteca: um app que entregue arquivos com esses nomes nos próprios assets substitui o certificado embutido sem passar MtlsCertificate.
Configurar endpoints (EndpointProvider)
Cada artefato já aponta para os endpoints do seu ambiente. O SDK separa os endpoints administrativos (não-PCI) dos financeiros (PCI). Para usar endpoints informados pela Tupi para um ambiente específico, configure-os logo depois de create:
import com.tupifintech.apossdk.sdk.api.EndpointProvider
import com.tupifintech.apossdk.sdk.api.setEndpointProvider
posPinpad.setEndpointProvider(
EndpointProvider(
adminBaseUrl = BuildConfig.ADMIN_BASE_URL, // autenticação, registro, tabelas, config, consultas
financialBaseUrl = BuildConfig.FINANCIAL_BASE_URL // pagamentos, estornos, PIX
)
)
Tempo de Espera do Cartão
Por padrão, o terminal espera o cartão por 30 s em cada tentativa, até 3 tentativas. No estorno e na pré-autorização, a captura tem ainda um teto total de 60 s. Para ajustar:
import com.tupifintech.apossdk.sdk.api.model.CardReadTimeouts
import com.tupifintech.apossdk.sdk.api.setCardReadTimeouts
posPinpad.setCardReadTimeouts(
CardReadTimeouts(perAttemptSeconds = 45, maxAttempts = 2, totalSeconds = 95)
)
| Campo | Padrão | Faixa |
|---|---|---|
perAttemptSeconds | 30 | 1 a 300 |
maxAttempts | 3 | 1 a 10 |
totalSeconds | 60 | de perAttemptSeconds a 300. Vale só no estorno e na pré-autorização |
Valores fora da faixa lançam IllegalArgumentException no construtor. A configuração vale a partir da próxima leitura e pode ser trocada entre transações. Ela não altera os tempos fixos abaixo.
| Etapa | Tempo |
|---|---|
| Entrega das coordenadas do teclado de senha | 5 s |
| Digitação da senha | 60 s |
| CVV, últimos dígitos, seleção de produto e seleção de transação (estorno e pré-autorização) | 30 s |
| Seleção de aplicação EMV | Definido pelo pinpad |
Usuários Locais
O SDK guarda o cadastro de Supervisores e Operadores do terminal, usado para autorizar operações restritas (como o estorno) e identificar quem operou. O cadastro fica no armazenamento privado do seu app, criptografado; não é sincronizado com o backend.
val usuarios = posPinpad.localUsers
when (val r = usuarios.create("Ana", "52998224725", LocalUserProfile.SUPERVISOR, senha)) {
is LocalUserResult.Success -> mostrar(r.value) // LocalUserInfo, sem senha
is LocalUserResult.Failure -> tratar(r.error) // LocalUserError
}
val autorizou = usuarios.verify(senhaDigitada, LocalUserProfile.SUPERVISOR) != null
| Função | O que faz |
|---|---|
list() | Usuários cadastrados, em LocalUserResult<List<LocalUserInfo>> |
hasSupervisor() | Se há ao menos um Supervisor |
create(name, cpf, profile, password) | Cadastra |
update(id, name, profile) | Altera nome e/ou perfil (null mantém) |
changePassword(id, newPassword) | Troca a senha |
delete(id) | Remove |
verify(password, requiredProfile) | Confere uma senha; devolve o usuário ou null |
clearAll() | Apaga todo o cadastro |
Regras garantidas pelo SDK:
| Regra | Erro quando violada |
|---|---|
Senha de exatamente 6 dígitos (CharArray, zerada ao fim da chamada) | INVALID_PASSWORD |
| Nome não vazio e CPF com 11 dígitos | INVALID_DATA |
| CPF único | DUPLICATE_CPF |
| Sem Supervisor, o primeiro usuário tem de ser Supervisor | SUPERVISOR_REQUIRED |
| No máximo 10 Supervisores | SUPERVISOR_LIMIT |
| O último Supervisor só sai se for o último usuário | LAST_SUPERVISOR |
| Id inexistente | NOT_FOUND |
| Cadastro existe mas não pôde ser lido nem gravado | STORAGE_UNAVAILABLE |
As funções fazem I/O de arquivo e derivação de chave: chame fora da main thread (por exemplo, withContext(Dispatchers.IO)). Pedir a senha do Supervisor antes de criar, editar ou remover é responsabilidade da tela do seu app.
Migrando um cadastro anterior
Se o seu app já guardava usuários por conta própria, com a senha em SHA-256 hexadecimal sem salt, importe-os uma vez com importLegacy:
@OptIn(LegacyLocalUserImport::class)
usuarios.importLegacy(antigos.map { LegacyLocalUser(it.id, it.name, it.cpf, it.profile, it.hashHex) })
- O hash antigo é aceito só na verificação e regravado no formato do SDK no primeiro acerto: ninguém precisa cadastrar senha de novo.
- A importação não sobrescreve: se o cadastro do SDK já tem usuários, ela é ignorada. É seguro repetir; apague o cadastro antigo só depois de a importação dar certo.
- A função exige
@OptIn(LegacyLocalUserImport::class)porque é caminho de migração, não API para código novo.
Metadados da Transação
createPayment, createPixPayment, startRefundFlow e startPreAuthFlow aceitam metadata: dados do seu app que seguem com a transação até o gateway (número do pedido, mesa, cliente).
posPinpad.createPayment(
type = TransactionType.AUTHORIZATION,
amount = 1500L,
paymentMethod = PaymentMethod.CREDIT,
installments = 1,
metadata = TransactionMetadata(mapOf("pedido" to "12345", "mesa" to "07"))
)
Limites (o construtor lança IllegalArgumentException fora deles): até 20 chaves; chave com até 40 caracteres em [A-Za-z0-9_.-]; valor com até 256 caracteres; 4 KB no total.
- Os metadados vão em todas as etapas da transação: autorização, PIX, estorno, pré-autorização, captura, confirmação e desfazimento. Também ficam gravados na fila de pendências, para seguirem na confirmação ou no desfazimento enviados mais tarde.
- Sem metadados, o campo não é enviado.
- Hoje o gateway recebe os metadados, mas ainda não os persiste nem os devolve nas consultas de transação.
Não coloque dado de portador em metadata (número do cartão, trilha, CVV, nome impresso). O campo é gravado no terminal enquanto a transação estiver pendente e segue para o backend. O SDK recusa valores com aparência de número de cartão, mas essa checagem é uma proteção adicional, não uma autorização.
Adquirentes e Chaves
O SDK é multi-adquirente: o terminal escolhe o adquirente de cada venda pela bandeira e pelo produto do cartão. O app não escolhe; o nome de quem processou volta em receipt.acquirerName.
val adquirentes = posPinpad.getConfiguredAcquirers() // ex.: ["pagseguro", "adiq"]; o primeiro é o padrão; vazio sem inicialização
val temChave = posPinpad.hasAcquirerKeys("pagseguro") // busca sem diferenciar maiúsculas
hasPagSeguroKeys() está obsoleto: use hasAcquirerKeys("pagseguro"). Falha de chave do adquirente padrão chega como onAppError(SdkErrorCode.PP_ERRKEY, ...); nova tentativa não resolve: acione o suporte.
Transações Pendentes
Confirmações e desfazimentos que não puderam ser entregues ficam numa fila e são reenviados automaticamente.
val pendentes = posPinpad.getPendingTransactionsCount() // suspend; zero é o normal
posPinpad.resolvePendingTransactions() // reenvia já, por exemplo ao recuperar a conexão
A autorização sempre exige rede: a fila guarda apenas confirmações e desfazimentos, não vendas offline. Enquanto houver pendências, os totais vindos do backend podem não refletir tudo o que aconteceu no terminal. Use a contagem para avisar o lojista.
Boas Práticas
1. Instância única
class MyApplication : Application() {
lateinit var posPinpad: PosPinpad
override fun onCreate() {
super.onCreate()
posPinpad = PosPinpad.create(this, globalCallback)
}
}
2. Inicialização a cada execução
lifecycleScope.launch {
val merchantId = armazenamento.merchantId ?: return@launch irParaAtivacao()
val init = posPinpad.terminalInitialization(TerminalInitializationRequest(merchantId))
if (!init.success) mostrarErroComNovaTentativa()
}
3. Logout
lifecycleScope.launch {
posPinpad.logout() // descarta a sessão e os IDs de registro
}