Muhammad Luqman
ย ยทย FlutterFlow Expert | Flutter | OpenAI | Software Engineer

Custom Credit Card Form Widget

// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/actions/actions.dart' as action_blocks;
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

// Import necessary packages
import 'package:flutter/services.dart';

// Begin widget code
class CreditCardForm extends StatefulWidget {
  const CreditCardForm({
    Key? key,
    this.width,
    this.height,
  }) : super(key: key);

  final double? width;
  final double? height;

  @override
  State<CreditCardForm> createState() => _CreditCardFormState();
}

class _CreditCardFormState extends State<CreditCardForm> {
  final _formKey = GlobalKey<FormState>();

  final _cardNumberController = TextEditingController();
  final _expiryDateController = TextEditingController();
  final _securityCodeController = TextEditingController();

  String? _cardNumberError;
  String? _expiryDateError;
  String? _cvvError;

  void _validateField(String fieldName) {
    setState(() {
      switch (fieldName) {
        case 'cardNumber':
          _cardNumberError = _validateCardNumber(_cardNumberController.text);
          break;
        case 'expiryDate':
          _expiryDateError = _validateExpiryDate(_expiryDateController.text);
          break;
        case 'cvv':
          _cvvError = _validateCVV(_securityCodeController.text);
          break;
      }
    });
  }

  String? _validateCardNumber(String? value) {
    if (value == null || value.isEmpty) {
      return 'Please enter a card number';
    }
    if (value.replaceAll(' ', '').length != 16) {
      return 'Please enter a valid 16-digit card number';
    }
    FFAppState().update(() {
      FFAppState().CardNumber = value;
    });
    return null;
  }

  String? _validateExpiryDate(String? value) {
    if (value == null || value.isEmpty) {
      return 'Please enter an expiry date';
    }
    if (!RegExp(r'^(0[1-9]|1[0-2])\/([0-9]{2})$').hasMatch(value)) {
      return 'Please enter a valid expiry date (MM/YY)';
    }

    final now = DateTime.now();
    final int currentYear = int.parse(now.year.toString().substring(2));
    final int currentMonth = now.month;
    final int inputMonth = int.parse(value.substring(0, 2));
    final int inputYear = int.parse(value.substring(3));

    if (inputYear < currentYear ||
        (inputYear == currentYear && inputMonth < currentMonth)) {
      return 'Expiry date cannot be in the past';
    }
    FFAppState().update(() {
      FFAppState().ExpiryDate = value;
    });
    return null;
  }

  String? _validateCVV(String? value) {
    if (value == null || value.isEmpty) {
      return 'Please enter a CVV';
    }
    if (value.length != 3) {
      return 'Please enter a valid 3-digit CVV';
    }
    FFAppState().update(() {
      FFAppState().CVC = value;
    });
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Card Number',
            style: TextStyle(
              color: FlutterFlowTheme.of(context).primaryText,
              fontSize: 14,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(height: 8),
          TextFormField(
            controller: _cardNumberController,
            cursorColor: FlutterFlowTheme.of(context).primaryText,
            decoration: InputDecoration(
              contentPadding:
                  EdgeInsets.symmetric(vertical: 17, horizontal: 17),
              hintText: '0000 0000 0000 0000',
              hintStyle: TextStyle(
                  color: FlutterFlowTheme.of(context).secondaryText,
                  fontSize: 14),
              errorText: _cardNumberError,
              focusedBorder: OutlineInputBorder(
                borderRadius: BorderRadius.circular(8),
                borderSide: BorderSide(
                    color: FlutterFlowTheme.of(context).primary, width: 2.0),
              ),
              enabledBorder: OutlineInputBorder(
                borderRadius: BorderRadius.circular(8),
                borderSide: BorderSide(
                  color: Color(0xFFD3D3D3),
                  width: 1.0,
                ),
              ),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(8),
                borderSide: BorderSide(
                  color: FlutterFlowTheme.of(context)
                      .primary, // Light gray border when typing
                  width: 1.0,
                ),
              ),
            ),
            keyboardType: TextInputType.number,
            inputFormatters: [
              FilteringTextInputFormatter.digitsOnly,
              CardNumberInputFormatter(),
            ],
            onChanged: (value) => _validateField('cardNumber'),
          ),
          const SizedBox(height: 20),
          Row(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Expanded(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Expiry Date',
                      style: TextStyle(
                        color: FlutterFlowTheme.of(context).primaryText,
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    SizedBox(height: 8),
                    TextFormField(
                      controller: _expiryDateController,
                      cursorColor: FlutterFlowTheme.of(context).primaryText,
                      decoration: InputDecoration(
                        contentPadding:
                            EdgeInsets.symmetric(vertical: 17, horizontal: 17),
                        hintText: 'MM/YY',
                        hintStyle: TextStyle(
                            color: FlutterFlowTheme.of(context).secondaryText,
                            fontSize: 14),
                        errorText: _expiryDateError,
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: BorderSide(
                              color: FlutterFlowTheme.of(context).primary,
                              width: 2.0),
                        ),
                        enabledBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: BorderSide(
                            color: Color(0xFFD3D3D3),
                            width: 1.0,
                          ),
                        ),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: BorderSide(
                            color: FlutterFlowTheme.of(context)
                                .primary, // Light gray border when typing
                            width: 1.0,
                          ),
                        ),
                      ),
                      inputFormatters: [
                        FilteringTextInputFormatter.digitsOnly,
                        ExpiryDateInputFormatter(),
                      ],
                      keyboardType: TextInputType.datetime,
                      onChanged: (value) => _validateField('expiryDate'),
                    ),
                  ],
                ),
              ),
              const SizedBox(width: 20),
              Expanded(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'CVC',
                      style: TextStyle(
                        color: FlutterFlowTheme.of(context).primaryText,
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    SizedBox(height: 8),
                    TextFormField(
                      controller: _securityCodeController,
                      cursorColor: const Color(0xFFBCD241),
                      decoration: InputDecoration(
                        contentPadding:
                            EdgeInsets.symmetric(vertical: 17, horizontal: 17),
                        hintText: '123',
                        hintStyle: const TextStyle(
                            color: Color(0xFF7C8BA0), fontSize: 14),
                        errorText: _cvvError,
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: BorderSide(
                              color: FlutterFlowTheme.of(context).primary,
                              width: 2.0),
                        ),
                        enabledBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: BorderSide(
                            color: Color(0xFFD3D3D3),
                            width: 1.0,
                          ),
                        ),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(8),
                          borderSide: BorderSide(
                            color: FlutterFlowTheme.of(context)
                                .primary, // Light gray border when typing
                            width: 1.0,
                          ),
                        ),
                      ),
                      inputFormatters: [
                        FilteringTextInputFormatter.digitsOnly,
                        LengthLimitingTextInputFormatter(3),
                      ],
                      keyboardType: TextInputType.number,
                      onChanged: (value) => _validateField('cvv'),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _cardNumberController.dispose();
    _expiryDateController.dispose();
    _securityCodeController.dispose();
    super.dispose();
  }
}

// Formatter for card numbers
class CardNumberInputFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    final newText = newValue.text.replaceAll(' ', '');
    if (newText.length > 16) return oldValue;

    final buffer = StringBuffer();
    for (int i = 0; i < newText.length; i++) {
      if (i % 4 == 0 && i != 0) buffer.write(' ');
      buffer.write(newText[i]);
    }

    return newValue.copyWith(
      text: buffer.toString(),
      selection: TextSelection.collapsed(offset: buffer.length),
    );
  }
}

// Formatter for expiry dates
class ExpiryDateInputFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    String newText = newValue.text.replaceAll(RegExp(r'[^0-9]'), '');

    if (newText.length > 2) {
      newText = '${newText.substring(0, 2)}/${newText.substring(2)}';
    }

    if (newText.length > 5) {
      newText = newText.substring(0, 5);
    }

    return TextEditingValue(
      text: newText,
      selection: TextSelection.collapsed(offset: newText.length),
    );
  }
}

๐Ÿš€ Custom Credit Card Form for FlutterFlow! ๐Ÿš€

The existing FlutterFlow credit card widget wasnโ€™t working, so I built my own! ๐ŸŽ‰ This custom credit card form is fully functional, user-friendly, and integrates seamlessly with FlutterFlow projects.

โœจ Super easy to use! You just need to create three App State values to store the card details:
๐Ÿ”น CardNumber
๐Ÿ”น CVC
๐Ÿ”น ExpiryDate

๐Ÿ”น When passing data in the API, make sure to:
โœ… Remove spaces: cardNumber.replaceAll(' ', '') (using code expression)
โœ… Extract Expiry Month: int.parse(expiryDate.split('/')[0])
โœ… Extract Expiry Year: int.parse(expiryDate.split('/')[1])

I'm uploading it to the FlutterFlow community to help fellow developers. If you find it useful, like, comment and share! ๐Ÿ’™

Let me know what you think! โœจ #FlutterFlow #CreditCardForm #FlutterflowDevelopment

8
1 reply