Visão geral da integração
O SDK roda dentro do seu aplicativo Android instalado na maquininha. Ele conversa com o app de pagamentos do terminal (PayStore), que cuida da leitura do cartão, da comunicação com a adquirente e das telas de senha. Seu app só pede a cobrança e recebe o resultado.
Instalar as dependências
Bibliotecas obrigatórias e o .aar fornecido pela VenturePay.
Inicializar em sandbox
Credenciais de teste com applicationId = "0".
Implementar o fluxo
Pagar, confirmar, estornar, consultar e imprimir.
Enviar para a VenturePay
Gere o build do app e envie ao suporte para as próximas etapas.
Dependências do projeto
Primeiro baixe as dependências de que o SDK precisa para funcionar. Depois instale o pacote .aar da VenturePay, que você recebe do nosso time.
-
1
Adicione as dependências obrigatórias no
app/build.gradle.ktse sincronize o projeto (Sync Now) para o Gradle baixá-las. -
2
Copie o arquivo
venturepay_pos-1.0.0.0.aarpara a pastaapp/libs/(crie a pasta se ela não existir). -
3
Adicione a linha do
.aare sincronize de novo.
dependencies {
// 1. Dependências obrigatórias do SDK
implementation("com.jakewharton.threetenabp:threetenabp:1.0.5")
// 2. SDK da maquininha fornecido pela VenturePay (arquivo em app/libs/)
implementation(files("libs/venturepay_pos-1.0.0.0.aar"))
}
AndroidManifest.xml
Declare a consulta ao serviço de pagamentos dentro de <manifest> e fora de <application>. Sem isso, no Android 11 ou mais novo, o app não consegue se conectar ao serviço da maquininha.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<intent>
<action android:name="phoebus.payments.api.controller" />
</intent>
</queries>
<application ...>
<!-- suas activities -->
</application>
</manifest>
Inicialização do SDK
Antes de usar qualquer funcionalidade, inicialize o SDK com os dados abaixo. Eles acompanham todas as chamadas.
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
context |
Context | Sim | Use applicationContext. |
applicationId |
String | Sim | ID da aplicação. Em sandbox é 0. |
secretToken |
String | Sim | Token secreto associado ao applicationId. Em sandbox são 24 zeros. |
softwareVersion |
String | Sim | Versão do seu app, para identificação e auditoria. |
Credenciais
Durante o desenvolvimento, o app chama o SDK sempre com as credenciais de sandbox. As credenciais de produção, fornecidas pela VenturePay, ficam no código sempre comentadas.
0
000000000000000000000000
O APPLICATION_ID e o SECRET_TOKEN de produção são fornecidos pela VenturePay. Deixe as duas linhas comentadas no código: a ativação acontece nas próximas etapas, junto com o nosso time.
public final class VenturePayConfig {
// Produção: credenciais fornecidas pela VenturePay. Mantenha sempre comentado.
// public static final String APPLICATION_ID = "";
// public static final String SECRET_TOKEN = "";
// Sandbox: credenciais usadas para chamar o SDK durante o desenvolvimento.
public static final String APPLICATION_ID = "0";
public static final String SECRET_TOKEN = "000000000000000000000000";
private VenturePayConfig() {}
}
// Classes do pacote br.com.phoebus.android.payments.api
val credentials = Credentials().apply {
applicationId = VenturePayConfig.APPLICATION_ID
secretToken = VenturePayConfig.SECRET_TOKEN
}
val appInfo = ApplicationInfo().apply {
this.credentials = credentials
softwareVersion = BuildConfig.VERSION_NAME
}
Com credenciais inválidas para o app ou para o ambiente, o SDK responde SDK - 0008: Credenciais inválidas.
Conexão com o serviço (PaymentClient)
Todas as operações passam por um PaymentClient conectado (bind) ao serviço de pagamentos da maquininha.
val client = PaymentClient()
client.bind(applicationContext, object : Client.OnConnectionCallback {
override fun onConnected() {
// pronto para chamar startPaymentV2, reversePaymentV2 etc.
}
override fun onDisconnected(forced: Boolean) {
// o serviço caiu
}
})
// ao sair da tela
if (client.isBound) client.unbind(applicationContext)
Regras da conexão
-
Chamar uma operação sem estar conectado lança
ClientException. -
Os callbacks podem chegar fora da main thread. Use
runOnUiThreadou umHandlerantes de mexer na tela. - O SDK aceita uma operação por vez. Espere o callback antes de chamar a próxima.
Todas as respostas usam o mesmo callback:
object : PaymentClient.PaymentCallback<T> {
override fun onSuccess(result: T) { }
override fun onError(error: ErrorData) { }
}
ErrorData
| Campo | Descrição |
|---|---|
paymentsResponseCode |
Código do SDK, por exemplo PC-0005 (cancelado) ou PC-0069 (parcelas fora da faixa) |
responseMessage |
Mensagem legível |
acquirerResponseCode |
Código da adquirente, por exemplo PS-2009 |
acquirerAdditionalMessage |
Mensagem extra da adquirente |
Fazer um pagamento
Monte um PaymentRequestV2 e chame startPaymentV2. A maquininha assume a tela, lê o cartão e devolve o resultado no callback.
val request = PaymentRequestV2().apply {
value = BigDecimal("10.90") // em reais, 2 casas
installments = 1 // 1 = à vista
paymentTypes = listOf(PaymentType.CREDIT) // um tipo só: pula a tela "crédito ou débito"
appTransactionId = "VP-${System.currentTimeMillis()}" // ID único seu
appInfo = appInfo
isShowReceiptView = false // não mostrar o comprovante padrão
printCustomerReceipt = false // não imprimir automaticamente
printMerchantReceipt = false
previewCustomerReceipt = false
previewMerchantReceipt = false
}
client.startPaymentV2(request, object : PaymentClient.PaymentCallback<PaymentV2> {
override fun onSuccess(p: PaymentV2) {
// aprovado, mas ainda PENDENTE: precisa confirmar
val id = p.paymentId
}
override fun onError(e: ErrorData) { }
})
Campos de PaymentRequestV2
| Campo | Tipo | Descrição |
|---|---|---|
value | BigDecimal | Valor em reais (ex.: 10.90) |
installments | Integer | Número de parcelas. Acima de 1, use CREDIT ou PARCELED_CREDIT |
paymentTypes | List<PaymentType> | Tipos aceitos. Com mais de um, a maquininha pergunta ao cliente |
appTransactionId | String | Seu identificador da transação |
appInfo | ApplicationInfo | Credenciais e versão |
isShowReceiptView | Boolean | Mostrar ou não a tela de comprovante padrão |
printCustomerReceipt / printMerchantReceipt | Boolean | Imprimir as vias automaticamente |
previewCustomerReceipt / previewMerchantReceipt | Boolean | Pré-visualizar as vias |
note, additionalData, dni | String | Opcionais |
before…/afterPrintAdditionalInfoCustomer/Merchant | String | Texto extra no comprovante padrão |
tokenizeCard, tokenizeEmail, tokenizeNationalDocument | — | Tokenização de cartão (opcional) |
PaymentType
CREDITDEBITPARCELED_CREDITCREDIT_STORECREDIT_ADMINPIXVOUCHERVOUCHER_MEALVOUCHER_FEEDVOUCHER_FLEETPRE_AUTHORIZATIONPRE_AUTHORIZATION_CONFIRMATIONPRE_AUTHORIZATION_CREDIT_STOREPRE_AUTHORIZATION_CREDIT_ADMINPRE_AUTHORIZATION_SUBSTITUTIVEDEBIT_POSTDATEDPRIVATE_LABELPRIVATE_LABEL_QUERY
Retorno PaymentV2
paymentIdvalueoriginalValueinstallmentspaymentTypepaymentStatusacquirerNameacquirerIdacquirerAuthorizationNumberacquirerNsunsuTerminalacquirerResponseCodeacquirerAdditionalMessagecardHolderNamecardcaptureTypeterminalIdbatchNumberticketNumberappTransactionIdpaymentDateUTCacquirerResponseDateUTCreceipt.clientViareceipt.merchantViacardTokencorrelationId
receipt.clientVia e receipt.merchantVia trazem o texto das vias do cliente e do estabelecimento, para você imprimir do seu jeito.
Confirmar ou desfazer uma transação
Toda transação aprovada fica pendente até ser confirmada. Se não for confirmada, ela é desfeita automaticamente.
// confirma (liquida)
client.confirmPayment(paymentId, object : PaymentClient.PaymentCallback<Any?> {
override fun onSuccess(r: Any?) { /* CONFIRMED */ }
override fun onError(e: ErrorData) { }
})
// desiste da transação pendente (desfazimento)
client.cancelPayment(paymentId, callback)
Transação ponta a ponta
O fluxo completo é pagar, confirmar e, se quiser, imprimir.
fun pagarPontaAPonta(req: PaymentRequestV2, done: (PaymentV2?, ErrorData?) -> Unit) {
client.startPaymentV2(req, object : PaymentClient.PaymentCallback<PaymentV2> {
override fun onSuccess(p: PaymentV2) {
client.confirmPayment(p.paymentId, object : PaymentClient.PaymentCallback<Any?> {
override fun onSuccess(r: Any?) = done(p, null) // aprovado e confirmado
override fun onError(e: ErrorData) = done(null, e) // falhou ao confirmar
})
}
override fun onError(e: ErrorData) = done(null, e) // negado ou cancelado
})
}
Se o seu sistema precisa registrar a venda antes de liquidar, confirme só depois de registrar. Se o registro falhar, chame cancelPayment.
Listar as últimas cobranças
As transações da maquininha ficam disponíveis por um content provider exportado, que não exige permissão.
Parâmetros da URI
| Parâmetro | Obrigatório | Descrição |
|---|---|---|
applicationId | Sim | Mesmo das credenciais |
secretToken | Sim | Mesmo das credenciais |
softwareVersion | Sim | Versão do seu app |
startDate | Não | Início, ISO-8601 UTC (2026-10-01T00:00:00.000Z) |
finishDate | Não | Fim, ISO-8601 UTC |
status | Não | Status separados por vírgula (ex.: 0,1,2,3) |
allResults | Não | true para trazer todas, sem paginação |
val iso = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
val uri = Uri.parse("content://br.com.phoebus.android.payments.provider/payments").buildUpon()
.appendQueryParameter("softwareVersion", BuildConfig.VERSION_NAME)
.appendQueryParameter("applicationId", VenturePayConfig.APPLICATION_ID)
.appendQueryParameter("secretToken", VenturePayConfig.SECRET_TOKEN)
.appendQueryParameter("startDate", iso.format(inicio))
.appendQueryParameter("finishDate", iso.format(fim))
.appendQueryParameter("status", PaymentStatus.entries.joinToString(",") { it.ordinal.toString() })
.appendQueryParameter("allResults", "true")
.build()
// rode fora da main thread
applicationContext.contentResolver.query(uri, null, null, null, null)?.use { c ->
while (c.moveToNext()) {
val id = c.getString(c.getColumnIndexOrThrow("id"))
val valor = c.getString(c.getColumnIndexOrThrow("value"))
val status = PaymentStatus.entries[c.getInt(c.getColumnIndexOrThrow("status"))]
val tipo = PaymentType.entries[c.getInt(c.getColumnIndexOrThrow("paymentType"))]
val data = c.getLong(c.getColumnIndexOrThrow("dateUTC")) // epoch em ms
}
}
Colunas retornadas
idvaluepaymentTypeinstallmentsstatusdatedateUTCacquirerNameacquirerAuthorizationNumberacquirerNsunsuTerminalcardBrandcardPanLast4DigitscardHolderNamecaptureTypeappTrnsIdreceiptClientreceiptMerchant
As colunas status e paymentType vêm como a posição no enum (ordinal), não como o id. Converta com PaymentStatus.entries[n] e PaymentType.entries[n]. Com fromId(n), uma venda confirmada aparece como "Pendente".
PaymentStatus
O número é a posição no enum, que é o valor usado no filtro status e retornado na coluna.
| Posição | Status | Posição | Status |
|---|---|---|---|
| 0 | PENDING | 6 | UNREACHABLE |
| 1 | CONFIRMED | 7 | WAITING_VALIDATION |
| 2 | CANCELLED | 8 | WAITING_CAPTURE |
| 3 | REVERSED | 9 | REFUNDED_DEVOLUTION |
| 4 | PROCESSING | 10 | REFUNDED |
| 5 | DENIED | 11 | APPROVED |
Para ordenar da mais recente para a mais antiga, use dateUTC em ordem decrescente. Para trazer só as aprovadas, filtre com status=1 (CONFIRMED).
Dados do terminal
Também é um content provider exportado, sem parâmetros.
applicationContext.contentResolver.query(
Uri.parse("content://br.com.phoebus.android.terminalinfo.provider/terminalInfo"),
null, null, null, null
)?.use { c ->
if (c.moveToFirst()) {
val nome = c.getString(c.getColumnIndexOrThrow("merchantName"))
val tid = c.getString(c.getColumnIndexOrThrow("terminalId"))
val cnpj = c.getString(c.getColumnIndexOrThrow("nationalId"))
}
}
| Coluna | Descrição |
|---|---|
merchantId | Código do estabelecimento |
merchantName | Razão social |
merchantCommercialName | Nome fantasia |
terminalId | TID |
nationalId | CNPJ ou CPF |
merchantNationalType | Tipo do documento |
merchantCategoryCode | MCC |
subAcquirerId | Subadquirente |
street, addressNumber, complement, neighbourhood, city, state, stateAbbreviation, country, postalCode | Endereço |
merchantWebsite, merchantEmail, merchantPhone | Contato |
terminalCurrencyCode, terminalCurrencyIso4217 | Moeda (ex.: 986 = BRL) |
O número de série da maquininha não vem nesse provider. Leia da propriedade ro.serialno, ro.boot.serialno ou ro.sunmi.serial.
Estornar uma cobrança
Use o paymentId da cobrança (a coluna id do provider) e o mesmo appTransactionId usado no pagamento.
val request = ReversePaymentRequestV2().apply {
paymentId = "7" // id da cobrança (coluna id do provider)
appTransactionId = "VP-1729..." // o mesmo usado no pagamento
value = BigDecimal("10.90") // valor total da cobrança
credentials = credentials
isShowReceiptView = false
printCustomerReceipt = false
printMerchantReceipt = false
previewCustomerReceipt = false
previewMerchantReceipt = false
autoConfirm = true // confirma o estorno automaticamente
}
client.reversePaymentV2(request, object : PaymentClient.PaymentCallback<ReversePayment> {
override fun onSuccess(r: ReversePayment) {
val viaCliente = r.receipt?.clientVia // comprovante do estorno
}
override fun onError(e: ErrorData) { }
})
Retorno ReversePayment
paymentIdstatuscancelableacquirerIdacquirerAuthorizationNumberacquirerResponseCodeacquirerResponseDateUTCacquirerAdditionalMessagensuTerminalbatchNumberticketNumbercardHolderNamecardBinpanLast4DigitsterminalIdreceipt.clientViareceipt.merchantViacorrelationId
Com autoConfirm = false, o estorno também precisa ser confirmado ou desfeito depois:
client.confirmReversePayment(paymentId, callback)
client.cancelReversePayment(paymentId, callback)
Também existe o reversePaymentWithFilter(ReversePaymentFilterRequest, callback), que estorna buscando a transação por filtro em vez do paymentId.
Imprimir ou reimprimir uma via
Imprima uma imagem própria (bitmap de 384 px de largura, em base64 PNG ou WEBP), um texto (por exemplo, o receiptClient vindo do provider) ou reimprima o comprovante padrão.
// imagem própria (bitmap de 384 px, base64 PNG ou WEBP)
client.printFromBase64(applicationContext, base64, /* feed */ true, callback)
// texto (ex.: receiptClient do provider)
client.printFromString(applicationContext, texto, true, callback)
// reimprimir o comprovante padrão
client.reprintV2(ReprintReceiptRequestV2(), callback)
Outras funções do PaymentClient
| Função | Uso |
|---|---|
startInitialization(InitializationRequest(), cb) | Reinicializa o terminal (parâmetros da adquirente) |
startEchoTest(cb) | Testa a comunicação com a adquirente |
setMainApp(pacote, cb) / getPackageName(cb) | Define qual app o botão início abre / obtém o pacote do app de pagamentos. Não use setMainApp com o seu pacote: trava o terminal. |
setTheme(nome, cb) / getTheme(cb) | Tema de cores das telas de pagamento |
getLogo(cb) / getReceiptLogo(cb) | Lê os logos configurados (não permite trocar) |
closeBatchV2 / openBatch | Fechamento e abertura de lote |
refundPaymentV2(RefundRequestV2, cb) | Devolução |
startContactlessPix / abortContactlessPix | PIX por aproximação |
Erros comuns
| Código | Causa |
|---|---|
SDK - 0008 | Credenciais inválidas para este app ou ambiente |
PC-0005 | Operação cancelada pelo usuário |
PC-0069 "parcelas fora da faixa 0X - 0X" | Terminal não habilitado para parcelamento (configuração na adquirente) |
PS-2009 "terminal não inicializado com adquirente" | O terminal precisa ser inicializado ou configurado na adquirente |
ClientException "not binding" | Chamada feita antes do onConnected |
Enviar o aplicativo para a VenturePay
Quando o aplicativo estiver pronto, gere o build e envie para o suporte VenturePay. Nosso time valida a integração e conduz as próximas etapas até a produção.
Antes de enviar, confira
-
As dependências e o
venturepay_pos-1.0.0.0.aarestão instalados e o projeto sincroniza sem erros. -
O app chama o SDK com as credenciais de sandbox (
applicationId = "0"e 24 zeros nosecretToken). -
As linhas
APPLICATION_IDeSECRET_TOKENde produção continuam comentadas. - O fluxo de pagamento confirma toda venda aprovada e o estorno foi testado.
Gere o build
Gere o pacote final do aplicativo com a integração concluída.
Envie ao suporte
Mande o arquivo para o suporte VenturePay informando a versão do app.
Aguarde as próximas etapas
Nosso time valida o app e retorna com as orientações para produção.