stone_deep_tech 1.1.0 copy "stone_deep_tech: ^1.1.0" to clipboard
stone_deep_tech: ^1.1.0 copied to clipboard

PlatformAndroid

Plugin Flutter para integração com Deeplink da Stone para meios de pagamento.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stone_deep_tech/stone_deep_tech.dart';

const String _envLicenceKey = '';

const String _envLicenceInternalKey =
    'aHR0cHM6Ly9wb3MtcGF5bWVudHMtYXBpLTU3NzQ2NDIzNTQwOC5zb3V0aGFtZXJpY2EtZWFzdDEucnVuLmFwcC9wb3MtcGF5bWVudHMvbGljZW5jZS9jaGVjay9pbnZvaWNl';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stone Deeplink Demo',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const DeeplinkPage(),
    );
  }
}

class DeeplinkPage extends StatefulWidget {
  const DeeplinkPage({super.key});

  @override
  State<DeeplinkPage> createState() => _DeeplinkPageState();
}

class _DeeplinkPageState extends State<DeeplinkPage> {
  String _statusMessage = 'Aguardando inicialização...';
  bool _isInitialized = false;
  bool _isLoading = false;

  // Controllers para os campos
  final _amountController = TextEditingController(text: '10000');
  final _orderIdController = TextEditingController();
  final _installmentCountController = TextEditingController();

  // Controllers para cancelamento
  final _cancelAtkController = TextEditingController();
  final _cancelAmountController = TextEditingController();

  // Controllers para reimpressão
  final _reprintAtkController = TextEditingController();

  TransactionType? _selectedTransactionType = TransactionType.debit;
  InstallmentType? _selectedInstallmentType = InstallmentType.none;
  bool _editableAmount = false;
  bool _cancelEditableAmount = false;
  bool _showFeedbackScreen = true;
  bool _reprintShowFeedbackScreen = true;
  TypeCustomer? _selectedTypeCustomer = TypeCustomer.client;

  // Lista de conteúdo para impressão
  final List<PrintContent> _printContents = [];

  String? _lastResponse;
  PrintResult? _lastPrintResult;
  final List<String> _logs = [];

  late final _deepLinkHandler = _MyDeepLinkHandler(_updateStatus, _addLog, _updateResponse);

  void _updateStatus(String message) {
    if (mounted) {
      setState(() {
        _statusMessage = message;
      });
    }
  }

  void _addLog(String log) {
    if (mounted) {
      setState(() {
        _logs.insert(0, '${DateTime.now().toString().substring(11, 19)} - $log');
        if (_logs.length > 20) {
          _logs.removeLast();
        }
      });
    }
  }

  void _updateResponse(String response) {
    if (mounted) {
      setState(() {
        _lastResponse = response;
        // Verificar se é um resultado de impressão
        final printResult = DeeplinkResponseParser.parsePrintResult(response);
        if (printResult != null) {
          _lastPrintResult = printResult;
        } else {
          // Tentar extrair do query parameter
          final resultCode = DeeplinkResponseParser.extractResultCode(response);
          if (resultCode != null) {
            _lastPrintResult = PrintResult.fromString(resultCode);
          } else {
            _lastPrintResult = null;
          }
        }
      });
    }
  }

  @override
  void initState() {
    super.initState();
    _initializeDeeplink();
  }

  @override
  void dispose() {
    _amountController.dispose();
    _orderIdController.dispose();
    _installmentCountController.dispose();
    _cancelAtkController.dispose();
    _cancelAmountController.dispose();
    _reprintAtkController.dispose();
    super.dispose();
  }

  void _initializeDeeplink() {
    try {
      if (_envLicenceKey.isEmpty) {
        throw Exception(
          'Defina as chaves de licença via --dart-define STONE_DEEP_TECH_LICENCE_KEY e STONE_DEEP_TECH_LICENCE_INTERNAL_KEY.',
        );
      }

      StoneDeepTech.I.initDeeplink(
        handler: _deepLinkHandler,
        licenceKey: _envLicenceKey,
        licenceInternalKey: _envLicenceInternalKey,
      );
      setState(() {
        _isInitialized = true;
        _statusMessage = '✓ Plugin inicializado com sucesso!';
      });
      _addLog('Plugin inicializado');
    } catch (e) {
      setState(() {
        _statusMessage = '✗ Erro ao inicializar: $e';
      });
      _addLog('Erro na inicialização: $e');
    }
  }

  Future<void> _sendDeeplink() async {
    if (!_isInitialized) {
      _updateStatus('✗ Plugin não inicializado!');
      return;
    }

    setState(() {
      _isLoading = true;
      _statusMessage = 'Enviando deeplink...';
      _lastResponse = null;
    });

    _addLog('Preparando deeplink...');

    try {
      // Validar e converter valores
      final amountText = _amountController.text.trim();
      if (amountText.isEmpty) {
        throw Exception('Valor não pode estar vazio');
      }

      final amount = int.tryParse(amountText);
      if (amount == null || amount <= 0) {
        throw Exception('Valor inválido. Use um número maior que zero.');
      }

      int? orderId;
      if (_orderIdController.text.trim().isNotEmpty) {
        orderId = int.tryParse(_orderIdController.text.trim());
      }

      int? installmentCount;
      if (_installmentCountController.text.trim().isNotEmpty) {
        installmentCount = int.tryParse(_installmentCountController.text.trim());
        if (installmentCount != null && (installmentCount < 2 || installmentCount > 99)) {
          throw Exception('Número de parcelas deve estar entre 2 e 99');
        }
      }

      final params = DeeplinkParams(
        amount: amount,
        editableAmount: _editableAmount,
        transactionType: _selectedTransactionType,
        installmentCount: installmentCount,
        installmentType: _selectedInstallmentType,
        orderId: orderId,
        returnScheme: 'stone_deep_tech_example',
      );

      _addLog(
        'Enviando deeplink: ${_selectedTransactionType?.value ?? 'N/A'}, R\$ ${(amount / 100).toStringAsFixed(2)}',
      );

      final success = await StoneDeepTech.I.deeplink.sendDeeplink(params);

      if (success) {
        setState(() {
          _statusMessage = '✓ Deeplink enviado com sucesso! Aguardando resposta...';
        });
        _addLog('Deeplink enviado com sucesso');
      } else {
        setState(() {
          _statusMessage = '✗ Erro ao enviar deeplink';
        });
        _addLog('Erro ao enviar deeplink');
      }
    } catch (e) {
      setState(() {
        _statusMessage = '✗ Erro: $e';
      });
      _addLog('Erro: $e');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _sendCancel() async {
    if (!_isInitialized) {
      _updateStatus('✗ Plugin não inicializado!');
      return;
    }

    setState(() {
      _isLoading = true;
      _statusMessage = 'Enviando cancelamento...';
      _lastResponse = null;
    });

    _addLog('Preparando cancelamento...');

    try {
      final atkText = _cancelAtkController.text.trim();
      if (atkText.isEmpty) {
        throw Exception('ATK não pode estar vazio');
      }

      int? amount;
      if (_cancelAmountController.text.trim().isNotEmpty) {
        amount = int.tryParse(_cancelAmountController.text.trim());
        if (amount == null || amount <= 0) {
          throw Exception('Valor inválido. Use um número maior que zero.');
        }
      }

      final params = CancelParams(
        returnScheme: 'stone_deep_tech_example',
        atk: atkText,
        amount: amount,
        editableAmount: _cancelEditableAmount,
      );

      _addLog(
        'Enviando cancelamento: ATK=$atkText${amount != null ? ', Valor=R\$ ${(amount / 100).toStringAsFixed(2)}' : ''}',
      );

      final success = await StoneDeepTech.I.deeplink.sendCancel(params);

      if (success) {
        setState(() {
          _statusMessage = '✓ Cancelamento enviado com sucesso! Aguardando resposta...';
        });
        _addLog('Cancelamento enviado com sucesso');
      } else {
        setState(() {
          _statusMessage = '✗ Erro ao enviar cancelamento';
        });
        _addLog('Erro ao enviar cancelamento');
      }
    } catch (e) {
      setState(() {
        _statusMessage = '✗ Erro: $e';
      });
      _addLog('Erro: $e');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _sendPrint() async {
    if (!_isInitialized) {
      _updateStatus('✗ Plugin não inicializado!');
      return;
    }

    if (_printContents.isEmpty) {
      _updateStatus('✗ Adicione pelo menos um item para impressão!');
      return;
    }

    setState(() {
      _isLoading = true;
      _statusMessage = 'Enviando impressão...';
      _lastResponse = null;
    });

    _addLog('Preparando impressão...');

    try {
      final params = PrintParams(
        schemeReturn: 'stone_deep_tech_example',
        printableContent: _printContents,
        showFeedbackScreen: _showFeedbackScreen,
      );

      _addLog('Enviando impressão com ${_printContents.length} item(ns)');

      final success = await StoneDeepTech.I.deeplink.sendPrint(params);

      if (success) {
        setState(() {
          _statusMessage = '✓ Impressão enviada com sucesso! Aguardando resposta...';
        });
        _addLog('Impressão enviada com sucesso');
      } else {
        setState(() {
          _statusMessage = '✗ Erro ao enviar impressão';
        });
        _addLog('Erro ao enviar impressão');
      }
    } catch (e) {
      setState(() {
        _statusMessage = '✗ Erro: $e';
      });
      _addLog('Erro: $e');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  void _addPrintItem(PrintContentType type) {
    if (type == PrintContentType.text) {
      _printContents.add(
        TextPrintContent(content: 'Texto exemplo', align: PrintTextAlign.center, size: PrintTextSize.medium),
      );
    } else if (type == PrintContentType.line) {
      _printContents.add(LinePrintContent(content: 'Linha exemplo'));
    } else if (type == PrintContentType.image) {
      // Imagem de exemplo em base64 (1x1 pixel transparente)
      _printContents.add(
        ImagePrintContent(
          imagePath: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
        ),
      );
    }
    setState(() {});
    _addLog('Item de impressão adicionado: ${type.value}');
  }

  void _removePrintItem(int index) {
    if (index >= 0 && index < _printContents.length) {
      _printContents.removeAt(index);
      setState(() {});
      _addLog('Item de impressão removido');
    }
  }

  void _clearPrintItems() {
    _printContents.clear();
    setState(() {});
    _addLog('Itens de impressão limpos');
  }

  Future<void> _sendReprint() async {
    if (!_isInitialized) {
      _updateStatus('✗ Plugin não inicializado!');
      return;
    }

    setState(() {
      _isLoading = true;
      _statusMessage = 'Enviando reimpressão...';
      _lastResponse = null;
    });

    _addLog('Preparando reimpressão...');

    try {
      final atkText = _reprintAtkController.text.trim();
      if (atkText.isEmpty) {
        throw Exception('ATK não pode estar vazio');
      }

      if (_selectedTypeCustomer == null) {
        throw Exception('Selecione o tipo de cliente');
      }

      final params = ReprintParams(
        schemeReturn: 'stone_deep_tech_example',
        atk: atkText,
        typeCustomer: _selectedTypeCustomer!,
        showFeedbackScreen: _reprintShowFeedbackScreen,
      );

      _addLog('Enviando reimpressão: ATK=$atkText, Tipo=${_selectedTypeCustomer!.value}');

      final success = await StoneDeepTech.I.deeplink.sendReprint(params);

      if (success) {
        setState(() {
          _statusMessage = '✓ Reimpressão enviada com sucesso! Aguardando resposta...';
        });
        _addLog('Reimpressão enviada com sucesso');
      } else {
        setState(() {
          _statusMessage = '✗ Erro ao enviar reimpressão';
        });
        _addLog('Erro ao enviar reimpressão');
      }
    } catch (e) {
      setState(() {
        _statusMessage = '✗ Erro: $e';
      });
      _addLog('Erro: $e');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Stone Deeplink Demo'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _isLoading ? null : _initializeDeeplink,
            tooltip: 'Reinicializar plugin',
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status Card
            Card(
              color: _isInitialized ? Colors.green.shade50 : Colors.red.shade50,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(
                          _isInitialized ? Icons.check_circle : Icons.error,
                          color: _isInitialized ? Colors.green : Colors.red,
                        ),
                        const SizedBox(width: 8),
                        const Text('Status do Plugin', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      ],
                    ),
                    const SizedBox(height: 8),
                    Text(_statusMessage, style: const TextStyle(fontSize: 14)),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Formulário de Configuração
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Configuração do Deeplink', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 16),

                    // Valor
                    TextField(
                      controller: _amountController,
                      decoration: const InputDecoration(
                        labelText: 'Valor (em centavos)',
                        hintText: 'Ex: 10000 = R\$ 100,00',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.attach_money),
                      ),
                      keyboardType: TextInputType.number,
                      inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                    ),
                    const SizedBox(height: 16),

                    // Tipo de Transação
                    const Text('Tipo de Transação:', style: TextStyle(fontWeight: FontWeight.bold)),
                    const SizedBox(height: 8),
                    SegmentedButton<TransactionType>(
                      segments: const [
                        ButtonSegment(value: TransactionType.debit, label: Text('Débito')),
                        ButtonSegment(value: TransactionType.credit, label: Text('Crédito')),
                        ButtonSegment(value: TransactionType.voucher, label: Text('Voucher')),
                        ButtonSegment(value: TransactionType.instantPayment, label: Text('Instant')),
                        ButtonSegment(value: TransactionType.pix, label: Text('PIX')),
                      ],
                      selected: _selectedTransactionType != null ? {_selectedTransactionType!} : {},
                      onSelectionChanged: (Set<TransactionType> selection) {
                        setState(() {
                          _selectedTransactionType = selection.isNotEmpty ? selection.first : null;
                        });
                      },
                    ),
                    const SizedBox(height: 8),
                    TextButton(
                      onPressed: () {
                        setState(() {
                          _selectedTransactionType = null;
                        });
                      },
                      child: const Text('Limpar seleção (nulo)'),
                    ),
                    const SizedBox(height: 16),

                    // Valor Editável
                    SwitchListTile(
                      title: const Text('Permitir edição do valor'),
                      subtitle: const Text('O usuário pode alterar o valor no app de pagamento'),
                      value: _editableAmount,
                      onChanged: (value) {
                        setState(() {
                          _editableAmount = value;
                        });
                      },
                    ),
                    const SizedBox(height: 16),

                    // Parcelamento
                    TextField(
                      controller: _installmentCountController,
                      decoration: const InputDecoration(
                        labelText: 'Número de Parcelas (opcional)',
                        hintText: 'Entre 2 e 99',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.credit_card),
                      ),
                      keyboardType: TextInputType.number,
                      inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                    ),
                    const SizedBox(height: 8),
                    DropdownButtonFormField<InstallmentType>(
                      decoration: const InputDecoration(
                        labelText: 'Tipo de Parcelamento (default: NONE - à vista)',
                        border: OutlineInputBorder(),
                      ),
                      value: _selectedInstallmentType,
                      items: const [
                        DropdownMenuItem(value: InstallmentType.none, child: Text('NONE (à vista)')),
                        DropdownMenuItem(value: InstallmentType.merchant, child: Text('MERCHANT (sem juros)')),
                        DropdownMenuItem(value: InstallmentType.issuer, child: Text('ISSUER (com juros)')),
                      ],
                      onChanged: (value) {
                        setState(() {
                          _selectedInstallmentType = value ?? InstallmentType.none;
                        });
                      },
                    ),
                    const SizedBox(height: 16),

                    // Order ID
                    TextField(
                      controller: _orderIdController,
                      decoration: const InputDecoration(
                        labelText: 'Order ID (opcional)',
                        hintText: 'ID do pedido',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.tag),
                      ),
                      keyboardType: TextInputType.number,
                      inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                    ),
                    const SizedBox(height: 24),

                    // Botão Enviar
                    SizedBox(
                      width: double.infinity,
                      height: 50,
                      child: ElevatedButton.icon(
                        onPressed: _isLoading || !_isInitialized ? null : _sendDeeplink,
                        icon: _isLoading
                            ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
                            : const Icon(Icons.send),
                        label: Text(_isLoading ? 'Enviando...' : 'Enviar Deeplink'),
                        style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 16)),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Seção de Cancelamento
            Card(
              color: Colors.orange.shade50,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Cancelamento', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 16),

                    // ATK
                    TextField(
                      controller: _cancelAtkController,
                      decoration: const InputDecoration(
                        labelText: 'ATK (Authorization Transaction Key) *',
                        hintText: 'Chave da transação a ser cancelada',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.key),
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Valor do Cancelamento
                    TextField(
                      controller: _cancelAmountController,
                      decoration: const InputDecoration(
                        labelText: 'Valor (em centavos, opcional)',
                        hintText: 'Ex: 10000 = R\$ 100,00',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.attach_money),
                      ),
                      keyboardType: TextInputType.number,
                      inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                    ),
                    const SizedBox(height: 16),

                    // Valor Editável
                    SwitchListTile(
                      title: const Text('Permitir edição do valor'),
                      subtitle: const Text('O usuário pode alterar o valor no app de pagamento'),
                      value: _cancelEditableAmount,
                      onChanged: (value) {
                        setState(() {
                          _cancelEditableAmount = value;
                        });
                      },
                    ),
                    const SizedBox(height: 24),

                    // Botão Cancelar
                    SizedBox(
                      width: double.infinity,
                      height: 50,
                      child: ElevatedButton.icon(
                        onPressed: _isLoading || !_isInitialized ? null : _sendCancel,
                        icon: _isLoading
                            ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
                            : const Icon(Icons.cancel),
                        label: Text(_isLoading ? 'Enviando...' : 'Enviar Cancelamento'),
                        style: ElevatedButton.styleFrom(
                          textStyle: const TextStyle(fontSize: 16),
                          backgroundColor: Colors.orange,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Seção de Impressão
            Card(
              color: Colors.purple.shade50,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Impressão Customizada', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 16),

                    // Itens de impressão
                    if (_printContents.isNotEmpty) ...[
                      const Text('Itens para impressão:', style: TextStyle(fontWeight: FontWeight.bold)),
                      const SizedBox(height: 8),
                      ...List.generate(_printContents.length, (index) {
                        final item = _printContents[index];
                        return Card(
                          margin: const EdgeInsets.only(bottom: 8),
                          child: ListTile(
                            title: Text('${item.type.value.toUpperCase()}'),
                            subtitle: Text(
                              item is TextPrintContent
                                  ? '${item.content} (${item.align.value}, ${item.size.value})'
                                  : item is LinePrintContent
                                  ? item.content
                                  : 'Imagem Base64',
                            ),
                            trailing: IconButton(
                              icon: const Icon(Icons.delete, color: Colors.red),
                              onPressed: () => _removePrintItem(index),
                            ),
                          ),
                        );
                      }),
                      const SizedBox(height: 8),
                    ],

                    // Botões para adicionar itens
                    Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: [
                        ElevatedButton.icon(
                          onPressed: _isLoading ? null : () => _addPrintItem(PrintContentType.text),
                          icon: const Icon(Icons.text_fields),
                          label: const Text('Adicionar Texto'),
                        ),
                        ElevatedButton.icon(
                          onPressed: _isLoading ? null : () => _addPrintItem(PrintContentType.line),
                          icon: const Icon(Icons.remove),
                          label: const Text('Adicionar Linha'),
                        ),
                        ElevatedButton.icon(
                          onPressed: _isLoading ? null : () => _addPrintItem(PrintContentType.image),
                          icon: const Icon(Icons.image),
                          label: const Text('Adicionar Imagem'),
                        ),
                        if (_printContents.isNotEmpty)
                          OutlinedButton.icon(
                            onPressed: _isLoading ? null : _clearPrintItems,
                            icon: const Icon(Icons.clear),
                            label: const Text('Limpar Tudo'),
                          ),
                      ],
                    ),
                    const SizedBox(height: 16),

                    // Show Feedback Screen
                    SwitchListTile(
                      title: const Text('Mostrar tela de feedback'),
                      subtitle: const Text('Mostrar tela de sucesso/erro no final do fluxo'),
                      value: _showFeedbackScreen,
                      onChanged: (value) {
                        setState(() {
                          _showFeedbackScreen = value;
                        });
                      },
                    ),
                    const SizedBox(height: 24),

                    // Botão Imprimir
                    SizedBox(
                      width: double.infinity,
                      height: 50,
                      child: ElevatedButton.icon(
                        onPressed: _isLoading || !_isInitialized || _printContents.isEmpty ? null : _sendPrint,
                        icon: _isLoading
                            ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
                            : const Icon(Icons.print),
                        label: Text(_isLoading ? 'Enviando...' : 'Enviar Impressão'),
                        style: ElevatedButton.styleFrom(
                          textStyle: const TextStyle(fontSize: 16),
                          backgroundColor: Colors.purple,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Seção de Reimpressão
            Card(
              color: Colors.blue.shade50,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Reimpressão', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 16),

                    // ATK
                    TextField(
                      controller: _reprintAtkController,
                      decoration: const InputDecoration(
                        labelText: 'ATK (Authorization Transaction Key) *',
                        hintText: 'Chave da transação a ser reimpressa',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.key),
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Tipo de Cliente
                    DropdownButtonFormField<TypeCustomer>(
                      value: _selectedTypeCustomer,
                      decoration: const InputDecoration(
                        labelText: 'Tipo de Cliente *',
                        border: OutlineInputBorder(),
                        prefixIcon: Icon(Icons.person),
                      ),
                      items: TypeCustomer.values.map((type) {
                        return DropdownMenuItem(
                          value: type,
                          child: Text(type == TypeCustomer.client ? 'Cliente (CLIENT)' : 'Lojista (MERCHANT)'),
                        );
                      }).toList(),
                      onChanged: (value) {
                        setState(() {
                          _selectedTypeCustomer = value;
                        });
                      },
                    ),
                    const SizedBox(height: 16),

                    // Show Feedback Screen
                    SwitchListTile(
                      title: const Text('Mostrar tela de feedback'),
                      subtitle: const Text('Mostrar tela de sucesso/erro no final do fluxo'),
                      value: _reprintShowFeedbackScreen,
                      onChanged: (value) {
                        setState(() {
                          _reprintShowFeedbackScreen = value;
                        });
                      },
                    ),
                    const SizedBox(height: 24),

                    // Botão Reimprimir
                    SizedBox(
                      width: double.infinity,
                      height: 50,
                      child: ElevatedButton.icon(
                        onPressed: _isLoading || !_isInitialized ? null : _sendReprint,
                        icon: _isLoading
                            ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
                            : const Icon(Icons.print),
                        label: Text(_isLoading ? 'Enviando...' : 'Enviar Reimpressão'),
                        style: ElevatedButton.styleFrom(
                          textStyle: const TextStyle(fontSize: 16),
                          backgroundColor: Colors.blue,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Resposta Recebida
            if (_lastResponse != null) ...[
              Card(
                color: _lastPrintResult == PrintResult.success
                    ? Colors.green.shade50
                    : (_lastPrintResult != null ? Colors.red.shade50 : Colors.blue.shade50),
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(
                            _lastPrintResult == PrintResult.success
                                ? Icons.check_circle
                                : (_lastPrintResult != null ? Icons.error : Icons.check_circle),
                            color: _lastPrintResult == PrintResult.success
                                ? Colors.green
                                : (_lastPrintResult != null ? Colors.red : Colors.green),
                          ),
                          const SizedBox(width: 8),
                          Text(
                            _lastPrintResult != null ? 'Resultado da Impressão' : 'Resposta Recebida',
                            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                          ),
                        ],
                      ),
                      if (_lastPrintResult != null) ...[
                        const SizedBox(height: 8),
                        Container(
                          padding: const EdgeInsets.all(12),
                          decoration: BoxDecoration(
                            color: _lastPrintResult == PrintResult.success
                                ? Colors.green.shade100
                                : Colors.red.shade100,
                            borderRadius: BorderRadius.circular(8),
                          ),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                'Código: ${_lastPrintResult!.value}',
                                style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
                              ),
                              const SizedBox(height: 4),
                              Text(_lastPrintResult!.description, style: const TextStyle(fontSize: 13)),
                            ],
                          ),
                        ),
                        const SizedBox(height: 8),
                      ],
                      const SizedBox(height: 8),
                      const Text('URI Completa:', style: TextStyle(fontWeight: FontWeight.bold)),
                      const SizedBox(height: 4),
                      SelectableText(_lastResponse!, style: const TextStyle(fontFamily: 'monospace', fontSize: 12)),
                      const SizedBox(height: 8),
                      SizedBox(
                        width: double.infinity,
                        child: OutlinedButton.icon(
                          onPressed: () {
                            Clipboard.setData(ClipboardData(text: _lastResponse!));
                            ScaffoldMessenger.of(context).showSnackBar(
                              const SnackBar(content: Text('Resposta copiada para a área de transferência')),
                            );
                          },
                          icon: const Icon(Icons.copy),
                          label: const Text('Copiar Resposta'),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
            ],

            // Logs
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text('Logs', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                        TextButton.icon(
                          onPressed: () {
                            setState(() {
                              _logs.clear();
                            });
                          },
                          icon: const Icon(Icons.clear, size: 18),
                          label: const Text('Limpar'),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    Container(
                      height: 200,
                      decoration: BoxDecoration(color: Colors.grey.shade900, borderRadius: BorderRadius.circular(8)),
                      padding: const EdgeInsets.all(12),
                      child: _logs.isEmpty
                          ? const Center(
                              child: Text('Nenhum log ainda', style: TextStyle(color: Colors.grey)),
                            )
                          : ListView.builder(
                              reverse: true,
                              itemCount: _logs.length,
                              itemBuilder: (context, index) {
                                return Padding(
                                  padding: const EdgeInsets.only(bottom: 4),
                                  child: Text(
                                    _logs[index],
                                    style: const TextStyle(
                                      color: Colors.greenAccent,
                                      fontFamily: 'monospace',
                                      fontSize: 12,
                                    ),
                                  ),
                                );
                              },
                            ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _MyDeepLinkHandler implements IDeepLinkHandler {
  final Function(String) onStatusUpdate;
  final Function(String) onLog;
  final Function(String) onResponse;

  _MyDeepLinkHandler(this.onStatusUpdate, this.onLog, this.onResponse);

  @override
  Future<void> onDeeplinkResponse(String responseUri) async {
    debugPrint('Resposta do deeplink recebida: $responseUri');
    onLog('Resposta recebida: $responseUri');

    // Verificar se é resultado de impressão
    final printResult = DeeplinkResponseParser.parsePrintResult(responseUri);
    if (printResult != null) {
      onLog('Resultado da impressão: ${printResult.value} - ${printResult.description}');
      if (printResult == PrintResult.success) {
        onStatusUpdate('✓ Impressão realizada com sucesso!');
      } else {
        onStatusUpdate('✗ Erro na impressão: ${printResult.description}');
      }
    } else {
      // Tentar extrair do query parameter
      final resultCode = DeeplinkResponseParser.extractResultCode(responseUri);
      if (resultCode != null) {
        final parsedResult = PrintResult.fromString(resultCode);
        if (parsedResult != null) {
          onLog('Resultado da impressão: ${parsedResult.value} - ${parsedResult.description}');
          if (parsedResult == PrintResult.success) {
            onStatusUpdate('✓ Impressão realizada com sucesso!');
          } else {
            onStatusUpdate('✗ Erro na impressão: ${parsedResult.description}');
          }
        } else {
          onStatusUpdate('✓ Resposta recebida! Verifique abaixo.');
        }
      } else {
        onStatusUpdate('✓ Resposta recebida! Verifique abaixo.');
      }
    }

    onResponse(responseUri);
  }

  @override
  Future<void> onError(String message) async {
    debugPrint('Erro no deeplink: $message');
    onLog('Erro: $message');
    onStatusUpdate('✗ Erro: $message');
  }

  @override
  Future<void> onDeeplinkSent() async {
    debugPrint('Deeplink enviado com sucesso');
    onLog('Deeplink enviado com sucesso');
  }
}
2
likes
150
points
12
downloads

Documentation

API reference

Publisher

verified publisherjylabtech.com.br

Weekly Downloads

Plugin Flutter para integração com Deeplink da Stone para meios de pagamento.

Homepage

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on stone_deep_tech

Packages that implement stone_deep_tech