FINTECH & WALLET

Code Scan

A polished payment-code display widget inspired by modern fintech apps. Switches between QR Code and Barcode tabs, renders the code inside a shadowed card with a center brand badge, and includes a Renew code action plus a secure-transactions footer. Wire it to your backend to display dynamic payment codes for cashiers and point-of-sale flows.

paymentqr-codefintech
Preview4 variants

Code Scan

Display one of the codes below to the cashier to complete your payment. Tap to learn how

All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.

QR Code

Square QR code with center diamond badge

Code Scan

Display one of the codes below to the cashier to complete your payment. Tap to learn how

All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.

Barcode

Horizontal barcode lines with center badge

Code Scan

Display one of the codes below to the cashier to complete your payment. Tap to learn how

All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.

With Brand Logo

Solid brand-colored badge overlay

Code Scan

Display one of the codes below to the cashier to complete your payment. Tap to learn how

Compact

Without the security footer

What's Included

  • QR Code variant — square code with center brand badge and Renew code button
  • Barcode variant — horizontal barcode lines with the same controls
  • With Logo variant — solid brand-colored center badge over the QR code
  • Compact variant — just the code body without the security footer
  • Tab switcher between QR Code and Barcode with animated underline
  • Built-in Renew code outline button and Terms & Conditions footer

Use Cases

  • Payment apps — display a dynamic QR code for cashiers at checkout
  • Wallet apps — show a personal barcode for loyalty or membership scans
  • Event check-in — render the attendee QR ticket on the home tab
  • Crypto / banking — display a wallet address as both QR and copyable code

PRO TIP

Regenerate the QR code on a Timer.periodic every 30 seconds and animate the swap with an AnimatedSwitcher — short-lived codes are the standard for payment security. Pair this with the qr_flutter package for production QR rendering and store the active code id in a stream so multiple devices can refresh in lockstep.

Complete Dart Code

Copy this into your Flutter project. No external packages required.

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        backgroundColor: Colors.white,
        body: Center(
          child: CodeScanCard(),
        ),
      ),
    );
  }
}

/// Flutter Code Scan Widget — All Variants
/// Payment QR / Barcode card inspired by modern fintech apps.
/// rohansurve.in/flutterkit/code-scan

enum CodeScanType { qr, barcode }
enum CodeScanStyle { standard, withLogo, compact }

class CodeScanCard extends StatefulWidget {
  final CodeScanType initialType;
  final CodeScanStyle style;
  final String title;
  final String description;
  final String renewLabel;
  final String footerText;
  final VoidCallback? onRenew;
  final VoidCallback? onClose;
  final VoidCallback? onLearnMore;

  const CodeScanCard({
    super.key,
    this.initialType = CodeScanType.qr,
    this.style = CodeScanStyle.standard,
    this.title = 'Code Scan',
    this.description =
        'Display one of the codes below to the cashier to complete your payment.',
    this.renewLabel = 'Renew code',
    this.footerText =
        'All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.',
    this.onRenew,
    this.onClose,
    this.onLearnMore,
  });

  @override
  State<CodeScanCard> createState() => _CodeScanCardState();
}

class _CodeScanCardState extends State<CodeScanCard> {
  late CodeScanType _activeType;

  static const _accent = Color(0xFF2563EB);
  static const _gray950 = Color(0xFF030712);
  static const _gray900 = Color(0xFF111827);
  static const _gray700 = Color(0xFF374151);
  static const _gray500 = Color(0xFF6B7280);
  static const _gray400 = Color(0xFF9CA3AF);
  static const _gray200 = Color(0xFFE5E7EB);
  static const _gray50 = Color(0xFFF9FAFB);

  @override
  void initState() {
    super.initState();
    _activeType = widget.initialType;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 340,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: _gray200),
        boxShadow: const [
          BoxShadow(
            color: Color(0x0A000000),
            blurRadius: 40,
          ),
        ],
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          _buildHeader(),
          _buildTabs(),
          _buildBody(),
        ],
      ),
    );
  }

  Widget _buildHeader() {
    return Padding(
      padding: const EdgeInsets.fromLTRB(20, 20, 20, 8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              const Icon(Icons.qr_code_scanner_rounded,
                  size: 20, color: _gray700),
              const SizedBox(width: 10),
              Expanded(
                child: Text(
                  widget.title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.w600,
                    color: _gray900,
                  ),
                ),
              ),
              InkWell(
                onTap: widget.onClose,
                borderRadius: BorderRadius.circular(80),
                child: Container(
                  width: 20,
                  height: 20,
                  decoration: BoxDecoration(
                    color: _gray200,
                    borderRadius: BorderRadius.circular(80),
                  ),
                  child: const Icon(Icons.close_rounded,
                      size: 14, color: _gray500),
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          RichText(
            text: TextSpan(
              style: const TextStyle(
                fontSize: 14,
                color: _gray500,
                height: 1.6,
              ),
              children: [
                TextSpan(text: widget.description + ' '),
                TextSpan(
                  text: 'Tap to learn how',
                  style: const TextStyle(
                    color: _accent,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTabs() {
    return Container(
      decoration: const BoxDecoration(
        border: Border(bottom: BorderSide(color: _gray200)),
      ),
      padding: const EdgeInsets.symmetric(horizontal: 20),
      child: Row(
        children: [
          _tab('QR Code', CodeScanType.qr),
          _tab('Barcode', CodeScanType.barcode),
        ],
      ),
    );
  }

  Widget _tab(String label, CodeScanType type) {
    final isActive = _activeType == type;
    return Expanded(
      child: InkWell(
        onTap: () => setState(() => _activeType = type),
        child: Container(
          padding: const EdgeInsets.symmetric(vertical: 16),
          decoration: BoxDecoration(
            border: Border(
              bottom: BorderSide(
                color: isActive ? _accent : Colors.transparent,
                width: 2,
              ),
            ),
          ),
          alignment: Alignment.center,
          child: Text(
            label,
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w600,
              color: isActive ? _gray700 : _gray400,
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildBody() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 28),
      child: Column(
        children: [
          _buildCodeCard(),
          if (widget.style != CodeScanStyle.compact) ...[
            const SizedBox(height: 24),
            _buildFooter(),
          ],
        ],
      ),
    );
  }

  Widget _buildCodeCard() {
    return Container(
      padding: const EdgeInsets.fromLTRB(8, 8, 8, 16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: _gray200),
        boxShadow: const [
          BoxShadow(
            color: Color(0x0A000000),
            blurRadius: 12,
          ),
        ],
      ),
      child: Column(
        children: [
          Container(
            width: double.infinity,
            height: 220,
            decoration: BoxDecoration(
              color: _gray50,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Stack(
              alignment: Alignment.center,
              children: [
                _activeType == CodeScanType.qr
                    ? _qrPattern()
                    : _barcodePattern(),
                _centerBadge(),
              ],
            ),
          ),
          const SizedBox(height: 16),
          SizedBox(
            width: double.infinity,
            height: 40,
            child: OutlinedButton(
              onPressed: widget.onRenew,
              style: OutlinedButton.styleFrom(
                foregroundColor: _gray950,
                side: const BorderSide(color: _gray200),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(80),
                ),
              ),
              child: Text(
                widget.renewLabel,
                style: const TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w500,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _qrPattern() {
    return CustomPaint(
      size: const Size(175, 175),
      painter: _QrPainter(),
    );
  }

  Widget _barcodePattern() {
    return CustomPaint(
      size: const Size(240, 90),
      painter: _BarcodePainter(),
    );
  }

  Widget _centerBadge() {
    if (widget.style == CodeScanStyle.withLogo) {
      return Container(
        width: 40,
        height: 40,
        decoration: BoxDecoration(
          color: _accent,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: Colors.white, width: 3),
        ),
        child: const Icon(Icons.bolt_rounded,
            color: Colors.white, size: 22),
      );
    }
    return Container(
      width: 40,
      height: 40,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
      ),
      alignment: Alignment.center,
      child: const Icon(Icons.diamond_outlined,
          color: _accent, size: 22),
    );
  }

  Widget _buildFooter() {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Padding(
          padding: EdgeInsets.only(top: 2),
          child: Icon(Icons.shield_outlined, size: 16, color: _gray400),
        ),
        const SizedBox(width: 8),
        Expanded(
          child: Text(
            widget.footerText,
            style: const TextStyle(
              fontSize: 12,
              color: _gray500,
              height: 1.6,
            ),
          ),
        ),
      ],
    );
  }
}

class _QrPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final cell = size.width / 21;
    final paint = Paint()..color = const Color(0xFF111827);
    final rng = List<int>.generate(
        21 * 21, (i) => (i * 1103515245 + 12345) & 1);
    for (int y = 0; y < 21; y++) {
      for (int x = 0; x < 21; x++) {
        if (rng[y * 21 + x] == 1) {
          canvas.drawRect(
              Rect.fromLTWH(x * cell, y * cell, cell, cell), paint);
        }
      }
    }
    void corner(double cx, double cy) {
      final r = Paint()..color = const Color(0xFF111827);
      canvas.drawRect(Rect.fromLTWH(cx, cy, cell * 7, cell * 7), r);
      canvas.drawRect(
          Rect.fromLTWH(cx + cell, cy + cell, cell * 5, cell * 5),
          Paint()..color = const Color(0xFFF9FAFB));
      canvas.drawRect(
          Rect.fromLTWH(cx + cell * 2, cy + cell * 2, cell * 3, cell * 3),
          r);
    }

    corner(0, 0);
    corner(size.width - cell * 7, 0);
    corner(0, size.height - cell * 7);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

class _BarcodePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final widths = <double>[2, 1, 3, 1, 2, 4, 1, 2, 1, 3, 2, 1, 4, 1, 2, 3, 1,
        2, 1, 3, 2, 4, 1, 2, 1, 3, 2, 1];
    final paint = Paint()..color = const Color(0xFF111827);
    double x = 0;
    final unit = size.width / widths.fold<double>(0, (a, b) => a + b * 1.6);
    for (int i = 0; i < widths.length; i++) {
      final w = widths[i] * unit;
      if (i.isEven) {
        canvas.drawRect(Rect.fromLTWH(x, 0, w, size.height), paint);
      }
      x += w * 1.6;
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

// ─── USAGE ───────────────────────────────────────────────────────

// Standard QR Code
// CodeScanCard(
//   initialType: CodeScanType.qr,
// )

// Barcode tab
// CodeScanCard(
//   initialType: CodeScanType.barcode,
// )

// With brand logo overlay
// CodeScanCard(
//   style: CodeScanStyle.withLogo,
// )

// Compact (no security footer)
// CodeScanCard(
//   style: CodeScanStyle.compact,
// )

How to use this widget

  1. 1Copy the Dart code above
  2. 2Create a new file — code_scan_card.dart in your Flutter project
  3. 3Use CodeScanCard(initialType: CodeScanType.qr) — switch between QR, Barcode, With Logo, and Compact via the type and style parameters

Related components

Data Display

Stat Card

Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress

View component →
Feedback & Alerts

Alert Widget

Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger

View component →
Auth & Onboarding

Login Screen

Clean email + password login with form validation and loading state

View component →