pagseguro_payment_tech 3.3.0
pagseguro_payment_tech: ^3.3.0 copied to clipboard
Integre o Flutter com maquinas smarts da Pagseguro, utilizando o SDK do Pagseguro. Rápido e fácil.
Pagseguro Payment Tech #
Sobre | Tecnologias | Requisitos | Configuração |
⚠️ Plugin não oficial!
🎯 Sobre #
Este projeto visa facilitar a integração com o SDK PagSeguro Smart no Flutter. Compatível apenas com máquinas POS Smart da PagSeguro.
PlugPagServiceWrapper: 1.33.0 #
🔐 Licenciamento #
O plugin exige uma licença válida da JY Labtech, validada no primeiro uso em cada terminal.
Onde adquirir #
- Adquira e gerencie suas licenças no portal portal.jylabtech.com.br (o portal antigo foi substituído; use o mesmo e-mail e senha de antes).
- O produto é único — "Licença plugin de pagamento" — e vale para qualquer plugin de pagamento da JY Labtech.
- Planos:
mensal: cobrado por dispositivo (cada terminal que valida a licença ocupa uma vaga).debug: cortesia concedida automaticamente no cadastro — até 2 aparelhos por 365 dias, para desenvolvimento e testes.
- No portal você pode consultar e desativar aparelhos vinculados à licença (para liberar vagas).
- Chaves de licença já existentes continuam válidas (os mesmos valores). Chaves novas no formato
JYL-...também são aceitas.
Como informar a licença #
A chave é passada em tempo de execução no initPayment (não use mais local.properties):
final payment = PagseguroPaymentTech.I.initPayment(
handler: MyPaymentHandler(),
licenceKey: '<sua_licenca>',
);
// Opcional: aguarde o resultado da validação inicial.
final licencaOk = await payment.licenceReady; // false = veja onError no handler
final nfc = PagseguroPaymentTech.I.initNfc(handler: MyNfcHandler());
licenceKey: a chave da sua licença, obtida no portal. Obrigatória.licenceInternalKey: opcional. O plugin sempre valida no servidor oficial da JY Labtech (https://portal-api.jylabtech.com.br/pos-payments/licence/check), que é o padrão. Só são aceitos valoreshttpsdesse mesmo host com caminho/pos-payments/licence/check...(texto puro ou base64, por exemplo a variante.../check/invoice). Qualquer outro valor é ignorado e o servidor oficial é usado no lugar, inclusive o endereço antigo (aHR0cHM6Ly9wb3MtcGF5bWVudHMtYXBp...) e servidores de terceiros.
Funcionamento #
- O plugin envia ao servidor a chave (
Authorization: Bearer licence:<licenceKey>) e a identificação do aparelho (ANDROID_ID, serial e modelo), com timeout de 15 s. - A licença só é liberada se o servidor responder HTTP 200 com uma licença válida e ativa:
expiredAtno futuro e semisExpired,isInactiveouisCanceled. Qualquer outra resposta (erro HTTP, timeout, sem internet, JSON inválido, licença vencida/suspensa/cancelada) bloqueia o uso. - Se válida, a licença é armazenada localmente de forma criptografada e reutilizada offline até a data de vencimento (
expiredAt), por no máximo 7 dias sem nova consulta ao servidor. Depois disso o terminal precisa de internet para revalidar. Um cache com data de gravação no futuro (relógio adulterado) é descartado. - Trocar a
licenceKey(ou a URL oficial efetiva, por exemplo de.../checkpara.../check/invoice) invalida o cache e força uma nova consulta. - Toda operação (pagamentos, estorno, cancelamento, última transação, ativação/consulta do pinpad, impressão, NFC, dados do usuário, parcelas, beep, reboot, serial e estilo do dialog) confere a licença no nativo antes de chamar o SDK da PagSeguro. A validação roda fora da main thread.
- Sem licença válida a operação não é executada e o Flutter recebe
PlatformException(code: 'LICENCE_ERROR', message: <motivo>). Os métodos que retornambool/valor padrão (activePinpad,printer*,beep,rebootDevice,getSerialNumber,calculateInstallments) devolvemfalse/vazio; os demais lançam a exceção. O motivo também chega emonError/onMessagedoPaymentHandler.
Erros de licença #
| HTTP | Mensagem (exemplo) | Causa / o que fazer |
|---|---|---|
| 404 | Licença não encontrada com a chave fornecida [...] |
Chave inexistente ou digitada errada. Confira a licenceKey no portal. |
| 412 | A quantidade de licenças verificadas excedeu o limite permitido. Quantidade atual: [N] |
Limite de aparelhos atingido (no plano debug, a mensagem cita "modo de depuração"). Desative aparelhos não usados no portal ou contrate mais dispositivos. |
| 412 | Dispositivo desativado para esta licença |
Este aparelho foi desativado no portal. Reative-o no portal. |
| 428 | A data de expiração da licença é inválida. [...] |
Licença vencida, suspensa ou cancelada. Regularize/renove no portal. |
| 403 | Token estático incorreto |
licenceKey vazia ou cabeçalho de autorização ausente. |
| 400 | Schema inválido [...] |
O aparelho não enviou um identificador estável (ANDROID_ID/serial). |
| 429 | — | Muitas consultas em pouco tempo. Aguarde alguns segundos e tente novamente. |
Troubleshooting #
PlatformExceptioncom códigoLICENCE_ERROR: a validação falhou; veja a mensagem da exceção (ou a recebida emonError/onMessagedo seu handler) e consulte a tabela acima.- Sem internet no primeiro uso: a primeira validação (e as revalidações a cada 7 dias) exige conexão.
- Trocou de aparelho: desative o aparelho antigo no portal para liberar a vaga.
- Ainda passa o
licenceInternalKeyantigo: não é mais necessário; o valor é ignorado e o servidor oficial é usado. Pode remover o parâmetro.
⚙️ Requisitos do Projeto #
Para utilizar este plugin, seu projeto Flutter precisa atender aos seguintes requisitos:
Versões Mínimas #
- Flutter SDK: >= 3.0.0
- Dart SDK: >= 3.0.0
- Gradle: >= 8.13
- Android Gradle Plugin (AGP): >= 8.13.1
- Kotlin: >= 2.1.21
- compileSdk: >= 35
- minSdk: >= 23
- Java: >= 17 (para o projeto do exemplo)
Configuração do Gradle #
1. android/gradle/wrapper/gradle-wrapper.properties
Certifique-se de que seu projeto está usando o Gradle 8.13 ou superior:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2. android/gradle.properties
Adicione as seguintes configurações ao seu arquivo gradle.properties:
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options="-Xmx2048M"
3. android/build.gradle (raiz do projeto)
No arquivo build.gradle na raiz do seu projeto Android, configure:
buildscript {
ext {
kotlin_version = '2.1.21'
// ... outras configurações
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
maven {
url 'https://github.com/pagseguro/PlugPagServiceWrapper/raw/master'
}
}
}
4. android/app/build.gradle (módulo app)
Configure o módulo app com as versões corretas:
android {
namespace = "com.seu.pacote.app"
compileSdk = 35
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
defaultConfig {
minSdk = 23
targetSdk = 35
// ... outras configurações
}
}
5. android/settings.gradle (para projetos que usam o novo sistema de plugins)
Se você estiver usando a nova sintaxe de plugins do Flutter, seu settings.gradle deve seguir este padrão:
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.13.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.21" apply false
}
include ":app"
Verificação #
Após configurar seu projeto, execute:
cd android
./gradlew --version
Certifique-se de que a versão do Gradle exibida seja 8.13 ou superior.
🚀 Tecnologias #
- Flutter
- [PlugPagServiceWrapper (Android)]
🏁 Configuração #
pubspec.yaml #
dependencies:
pagseguro_payment_tech: ^3.3.0
Configuração Android #
-
Permissão obrigatória no
AndroidManifest.xmldo app:<uses-permission android:name="br.com.uol.pagseguro.permission.MANAGE_PAYMENTS" /> -
Intent filter na
MainActivitypara receber callbacks:<intent-filter> <action android:name="br.com.uol.pagseguro.PAYMENT" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> -
Configuração mínima do app:
defaultConfig { applicationId "com.exemplo.pagamento" minSdkVersion 23 targetSdkVersion flutter.targetSdkVersion } -
O host precisa do app PlugPagService instalado e atualizado no equipamento.
Inicialização #
As chaves de licença não ficam mais em local.properties. O app deve fornecê-las em tempo de execução (veja Licenciamento).
final payment = PagseguroPaymentTech.I.initPayment(
handler: MyPaymentHandler(),
licenceKey: licenceKeyFromBackend,
maxTimeShowPopUp: 30, // opcional: tempo do dialog de impressão da via do cliente (padrão: 30s)
);
- Busque a
licenceKeycom segurança (ex.: backend autenticado, serviço de segredos). - O módulo nativo valida no servidor oficial, armazena criptografado e reutiliza a licença quando possível (até 7 dias offline). Sem licença válida, toda operação falha com
LICENCE_ERROR. - O parâmetro
maxTimeShowPopUpconfigura o tempo máximo (em segundos) que o dialog de impressão da via do cliente permanece visível no SDK PagSeguro.
Operações de Pagamento #
// Ativar pinpad
await payment.activePinpad('CODIGO_ATIVACAO');
// Pagamentos
await payment.debitPayment(1000); // R$ 10,00
await payment.creditPayment(1000);
await payment.creditPaymentParc(1000, 2); // Parcelado
await payment.voucherPayment(1000);
await payment.pixPayment(1000);
// Fluxo genérico
await payment.startPayment(
PaymentType.TYPE_CREDITO,
2500,
installments: 3,
installmentType: PaymentTypeCredit.CLIENT,
);
// Operações auxiliares
await payment.lastTransaction();
await payment.abortTransaction();
await payment.refund(transactionCode: code, transactionId: id); // ambos obrigatórios
await payment.isAuthenticated();
Retorno dos métodos: com a licença válida, cada
Futurecompleta assim que a operação é disparada no nativo (true;getSerialNumberdevolve o serial ecalculateInstallmentsa lista de parcelas). O resultado da transação, impressão ou leitura NFC continua chegando pelos callbacks doPaymentHandler/NfcHandler(onTransactionSuccess,onError,onFinishedResponse,showSuccess...). Falha ao disparar a operação geraPlatformException(code: 'ERROR')(oufalsenos métodos que retornamboolcom tratamento interno). A partir da 3.3.0debitPaymente os métodos NFC também completam (antes ficavam pendentes para sempre).3.3.0:
getSerialNumber,getUserData,calculateInstallments,beep,rebootDeviceeprinterfromFilerodam numa thread de background no nativo (antes travavam a UI) e oFuturecompleta quando o SDK responde.beep/rebootDevicepassam a devolverfalsese o SDK falhar (antes a falha era engolida e devolviamtrue).
Validação de entrada (3.3.0) #
userReference: o SDK aceita só letras e números, até 10 caracteres. O plugin remove os demais caracteres e corta em 10 ('PED-123/A'vira'PED123A'). Se nada sobrar, usa'SMARTPOS'.refund(emPaymenteNfc):transactionCodeetransactionIdsão obrigatórios. Nulos ou vazios lançamPlatformException(code: 'INVALID_ARGUMENT')(Payment.invalidArgumentCode) sem chamar o SDK. O nativo faz a mesma checagem.
Callbacks (eventos do nativo) #
-
onAbortedSuccessfullypassa a ser enviado quando o SDK confirma oabortTransaction()(antes nunca era chamado). Se não havia nada para abortar, nenhum evento é enviado; falha do SDK chega poronError. -
onTransactionSuccesspassa a ser entregue (o nativo enviavaonTransactionSuccesse o Dart só reconheciaonTransactionSucess, então o callback nunca era chamado). -
PaymentHandlereNfcHandlerpodem ficar ativos ao mesmo tempo:onFinishedResponsevai sempre para oPaymentHandler, os eventosshow*vão sempre para oNfcHandlere os eventos comuns (onError,onMessage,onLoading...) vão para o último handler registrado. Antes, o últimoinitNfc/setHandler"roubava" o canal e os eventos do outro eram perdidos. -
nfc.closeMethodCallHandler()só desliga o NFC; os eventos de pagamento continuam chegando. -
Argumentos nulos vindos do nativo (ex.: erro sem mensagem) chegam como
''em vez de quebrar o callback. -
PagseguroResponseModel.fromJsonagora preenchetransactionID(o nativo enviatransactionId) e aceita números ou textos nos campos. -
Builds release com R8 (padrão do Flutter): o plugin traz regras de keep para o JSON de
onFinishedResponse/writeToFile/onTransactionInfo. Antes o R8 removia os campos e o JSON chegava semhostNsu,nsu,cardBrandetc.
Impressão #
await payment.printerfromFile('/storage/emulated/0/receipt.pdf');
await payment.printerFile('receipt.png'); // nome de arquivo no armazenamento do app (veja abaixo)
await payment.printer('/storage/emulated/0/Download/receipt.png'); // caminho do arquivo
await payment.printerFilePath('/storage/emulated/0/Download/receipt.png');
// printerFile(nome): desde a 3.3.0 o arquivo é procurado no armazenamento do APP, nesta ordem:
// caminho absoluto existente, getExternalFilesDir(DIRECTORY_DOWNLOADS), getExternalFilesDir(null)
// (o mesmo de getExternalStorageDirectory() do path_provider), getFilesDir() e getCacheDir().
// Antes era <armazenamento externo público>/Download (API obsoleta, sem acesso no Android 10+).
// Desde a 3.3.0 os métodos printer* executam a impressão no nativo (passando pela licença)
// e devolvem true quando ela é disparada; antes sempre devolviam false. O resultado da
// impressão chega por onMessage/onAuthProgress/onError do PaymentHandler.
// Renderizar widgets Flutter
PrintRenderWidget.print(
context,
pagseguroPaymentInstance: PagseguroPaymentTech.I,
child: MyCustomReceipt(),
);
Dialog de impressão da via do cliente #
O SDK PagSeguro exibe um dialog para impressão da via do cliente após a transação.
Por padrão, o plugin configura esse tempo em 30 segundos via PlugPagCustomPrinterLayout.setMaxTimeShowPopup().
Definir na inicialização #
PagseguroPaymentTech.I.initPayment(
handler: MyPaymentHandler(),
licenceKey: licenceKeyFromBackend,
maxTimeShowPopUp: 30, // padrão
);
Alterar em runtime #
await PagseguroPaymentTech.I.payment.setPrintDialogMaxTime(maxTimeShowPopUp: 45);
Personalização completa do dialog (cores + tempo) #
Se precisar customizar também título, cores e demais estilos do dialog:
await payment.setDialogstyle(
title: 'Imprimir via do cliente?',
buttonBackgroundColor: '#FFFFFF',
buttonBackgroundColorDisabled: '#CCCCCC',
cancelTextColor: '#37A964',
confirmTextColor: '#37A964',
sendSMSTextColor: '#37A964',
titleColor: '#FFFFFF',
windowBackgroundColor: '#37A964',
maxTimeShowPopUp: 30, // tempo em segundos
);
Após alterações no código nativo em
android/native, recompile os binários commake build-nativeantes de testar no app.
NFC #
final nfc = PagseguroPaymentTech.I.initNfc(handler: MyNfcHandler());
// Todos os argumentos são enviados como String ao nativo (números/bool são convertidos).
// Cada chamada completa com true quando a operação é disparada; o resultado chega pelos
// callbacks do NfcHandler (showSuccess.../showError...). Antes da 3.3.0 o Future nunca completava.
// writeNfc(valor, nome, cpf, numeroTag, celular, aberto, idEvento)
await nfc.writeNfc('500', 'Cliente', '12345678901', 'TAG01', '11999999999', 'true', 'evento');
await nfc.readNfc('evento'); // readNfc(idEvento)
await nfc.debitNfc('evento', '500'); // debitNfc(idEvento, valor)
await nfc.refundNfc('500', 'evento'); // refundNfc(valor, idEvento)
await nfc.reWriteNfc('500', 'evento'); // reWriteNfc(valor, idEvento)
await nfc.formatNfc();
Atenção: qualquer chamada ao plugin (inclusive de pagamento,
beep, impressão etc.) feita enquanto uma operação NFC aguarda o cartão cancela a assinatura NFC pendente: o resultado dessa leitura/gravação deixa de ser entregue aoNfcHandler. É o comportamento das versões anteriores e foi mantido (serve como "cancelar"). Não faça outras chamadas durante uma operação NFC, ou refaça a operação depois.
Página pronta #
O pacote disponibiliza PagseguroPage com fluxo completo de pagamento. Consulte lib/src/app/presentation para personalizar UI e handlers.
final result = await Navigator.of(context).push<PagseguroResponseModel?>(
MaterialPageRoute(
builder: (_) => PagseguroPage(
params: PagseguroPageParams(
amount: 25.90,
amountInCents: 2590, // opcional (3.3.0): evita arredondamento de double
type: PagseguroPaymentType.debit,
options: PagseguroOptions(activationCode: 'CODIGO', isPrinterEstablishment: true),
),
),
),
);
Erros do nativo ao iniciar o pagamento (exceto LICENCE_ERROR, que já chega por onError) agora
fecham a tela com resultCode: 9999999 em vez de deixá-la presa em "Aguarde...".
Executando o exemplo #
O app de exemplo usa String.fromEnvironment para obter as chaves:
flutter run \
--dart-define=PAGSEGURO_LICENCE_KEY=SUA_LICENCE_KEY \
--dart-define=PAGSEGURO_LICENCE_INTERNAL_KEY=aHR0cHM6Ly9wb3J0YWwtYXBpLmp5bGFidGVjaC5jb20uYnIvcG9zLXBheW1lbnRzL2xpY2VuY2UvY2hlY2s=
Certifique-se de que os binários nativos presentes em android/libs/ estejam atualizados (AAR e <plugin>-native.jar).
Testes #
# Testes Dart (MethodChannel mockado): todos os métodos públicos, eventos, modelos,
# PagseguroPage/PagseguroController e PrintRenderWidget (123 testes)
flutter test
# Testes JVM do módulo nativo (JDK 17): licença, gate do canal (ChannelRequestHandler),
# roteamento de todos os métodos (PaymentMethodRouter), casos de uso e presenter de pagamento
# com SDK fake (PaymentGateway), eventos enviados ao Dart, mappers e busca de arquivo de impressão (160 testes)
FLUTTER_SDK=$(dirname $(dirname $(which flutter))) \
example/android/gradlew -p android/native --no-daemon -Dorg.gradle.jvmargs=-Xmx2g \
-Pandroid.jetifier.ignorelist=flutter.jar testDebugUnitTest
A análise técnica completa (tabela por método, bugs corrigidos e otimizações) está em
doc/ANALISE.md (fora do pacote publicado, via .pubignore).
Depois de alterar o código em android/native, regenere os binários com make build-native (ou sh scripts/build_android_aar.sh).
Suporte & Licença #
- Projeto proprietário mantido por JY Labtech.
- Uso restrito conforme os termos internos da empresa.
- Para dúvidas, entre em contato com a equipe responsável ou abra um ticket interno.