Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cb09c36
feat: add UI customization options for card form
fcarrero Apr 17, 2026
da06e15
feat: update CI workflow to support multiple Flutter versions
fcarrero Apr 17, 2026
249d3c5
feat: add xz-utils installation step to CI workflow
fcarrero Apr 17, 2026
362bbb5
feat: update Dart and Flutter SDK requirements in pubspec and README
fcarrero Apr 17, 2026
eaa8b6c
feat: update Flutter SDK version requirements in pubspec, README, and…
fcarrero Apr 17, 2026
246878f
fix: correct intl package version constraint in pubspec.yaml
fcarrero Apr 17, 2026
4dd2396
test: update CardForm tests to check localized text for SecurePayment…
fcarrero Apr 17, 2026
bb73252
fix: reduce resource allocation for Android emulator in CI workflow
fcarrero Apr 17, 2026
f3e0bd8
fix: ensure output-dir and synthetic-package are correctly defined in…
fcarrero Apr 17, 2026
f1c2762
fix: update google_fonts dependency to version 6.2.1 and add FakeHttp…
fcarrero Apr 17, 2026
9399e9e
fix: update http package dependency to version 1.0.0
fcarrero Apr 17, 2026
59c1371
fix: implement openUrl method in _FakeHttpClient for better HTTP requ…
fcarrero Apr 17, 2026
d15533f
fix: enhance Flutter CI workflow with Mockoon CLI and caching, update…
fcarrero Apr 17, 2026
8d234d1
fix: remove unused FakeHttpOverrides and related tests from card_form…
fcarrero Apr 17, 2026
fd02e83
fix: add hideLogo option to CardFormConfig and update related tests
fcarrero Apr 17, 2026
452fb49
fix: capture lastResult in CardForm test and update expiry date
fcarrero Apr 17, 2026
7392a1a
fix: add redirects and cookies properties to _FakeHttpClientResponse
fcarrero Apr 17, 2026
92a2226
fix: remove synthetic-package property from l10n.yaml
fcarrero Apr 17, 2026
67898bb
fix: update AVD cache key and emulator options in flutter_test.yml
fcarrero Apr 17, 2026
7ee8f03
fix: remove flutter version constraint from pubspec.yaml
fcarrero Apr 20, 2026
c02af98
fix: update SDK version constraint in pubspec.yaml
fcarrero Apr 20, 2026
9d07b14
fix: update Flutter version matrix in flutter_test.yml
fcarrero Apr 20, 2026
7e8b129
fix: update flutter_lints version constraint in pubspec.yaml
fcarrero Apr 20, 2026
9664a8e
fix: revert intl version constraint to maintain compatibility
fcarrero Apr 20, 2026
4963ae0
fix: update google_fonts version constraint in pubspec.yaml
fcarrero Apr 20, 2026
a0c3d12
fix: update README and code for consistency and clarity
fcarrero Apr 20, 2026
75539c8
fix: update Flutter SDK constraint in pubspec.yaml and remove outdate…
fcarrero Apr 20, 2026
8d1a87f
fix: update Dart SDK requirement in README and add logo visibility op…
fcarrero Apr 20, 2026
64d1f15
fix: add disk space cleanup step in Flutter CI workflow
fcarrero Apr 20, 2026
42d6f42
fix: add focus management for card name field in CardForm
fcarrero Apr 20, 2026
b58d116
fix: remove form reset after payment submission in CardForm
fcarrero Apr 20, 2026
0d650f2
fix: update sdkVersion to 1.0.1
fcarrero Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 82 additions & 14 deletions .github/workflows/flutter_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,118 @@ jobs:
integration-test:
name: integration-test
runs-on: ubuntu-latest
env:
API_LEVEL: 30
TARGET: default
ARCH: x86_64
steps:
- name: Free disk space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: false
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true

- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v6

- name: Run Mockoon CLI
uses: mockoon/cli-action@v3
with:
data-file: 'https://raw.githubusercontent.com/conekta/openapi/main/mocks/conekta_api.json'
port: 3000

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
channel: 'stable'
cache: true

- name: Cache Gradle and pub
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.pub-cache
android/.gradle
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'pubspec.lock') }}
restore-keys: |
gradle-${{ runner.os }}-

- name: Get Flutter dependencies
run: flutter pub get

- name: Accept Android licenses
run: yes | flutter doctor --android-licenses

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Set up Android SDK

- name: AVD cache
uses: actions/cache@v5
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-v2-${{ env.API_LEVEL }}-${{ env.TARGET }}-${{ env.ARCH }}

- name: Create AVD snapshot
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.API_LEVEL }}
target: ${{ env.TARGET }}
arch: ${{ env.ARCH }}
cores: 4
ram-size: 4096M
heap-size: 1024M
disable-animations: false
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: echo "Generated AVD snapshot for caching."

- name: Run integration tests on Android emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 31
api-level: ${{ env.API_LEVEL }}
target: ${{ env.TARGET }}
arch: ${{ env.ARCH }}
cores: 4
ram-size: 4096M
heap-size: 1024M
disable-animations: true
cores: 8
ram-size: 12288M
target: google_apis
arch: x86_64
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: flutter test integration_test/

test:
name: Run Flutter Tests
runs-on: ubuntu-latest
name: Run Flutter Tests (Flutter ${{ matrix.flutter-version }})
runs-on: aws-runner-set
strategy:
fail-fast: false
matrix:
flutter-version: ['3.19.x', '3.24.x', '3.29.x', '3.32.x', '3.x']
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v6

- name: Install xz-utils
run: |
if ! command -v xz >/dev/null 2>&1; then
sudo apt-get update && sudo apt-get install -y xz-utils
fi

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
flutter-version: ${{ matrix.flutter-version }}
channel: 'stable'
cache: true

Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# Changelog

## [1.0.1] - 2026-04-17
### Added
- `CardFormConfig` class to group optional UI flags passed to `CardForm` via the `config` parameter.
- `CardFormConfig.hideLogo` (default `false`) to show/hide the "Paga segura con Conekta" section.
- `CardFormConfig.submitButtonText` (default `null`) to override the submit button label; falls back to the localized value when `null`.
- Widget tests for `CardForm` config flags and `CardFormConfig` defaults.
- Example usages in `example/lib/main.dart`.

## [1.0.0] - 2025-09-30
- Final release

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A customizable Flutter widget for securely collecting credit card information. This library provides a user-friendly form with built-in validation and localization support, making it easy to integrate into your Flutter applications for payment processing.

**Current Version:** 1.0.0
**Current Version:** 1.0.1

## Features

Expand All @@ -16,7 +16,7 @@ A customizable Flutter widget for securely collecting credit card information. T

## Minimum Requirements

* **Dart SDK:** Version 2.17.0 or higher
* **Dart SDK:** Version 3.3.0 or higher (Flutter 3.19.0+)

## Installation

Expand Down
1 change: 1 addition & 0 deletions android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<application android:usesCleartextTraffic="true" tools:replace="android:usesCleartextTraffic" xmlns:tools="http://schemas.android.com/tools" />
</manifest>
26 changes: 26 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,34 @@ class CreditCardFormScreen extends StatelessWidget {
onSubmitted: _onSubmitted,
paymentService: paymentService,
locale: const Locale('es'),
config: const CardFormConfig(
hideLogo: false,
submitButtonText: 'Pagar ahora',
),
),
),
);
}
}

/// Example: hide the "Paga segura con Conekta" badge and keep the localized
/// submit label ("Continuar"/"Continue") by leaving [submitButtonText] as null.
///
/// ```dart
/// CardForm(
/// paymentService: paymentService,
/// onSubmitted: _onSubmitted,
/// config: const CardFormConfig(hideLogo: true),
/// )
/// ```
///
/// Example: English locale with a custom submit label.
///
/// ```dart
/// CardForm(
/// paymentService: paymentService,
/// locale: const Locale('en'),
/// onSubmitted: _onSubmitted,
/// config: const CardFormConfig(submitButtonText: 'Pay now'),
/// )
/// ```
20 changes: 13 additions & 7 deletions integration_test/card_form_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:io';

import 'package:conekta_component/card_input_flutter.dart';
import 'package:conekta_component/l10n/app_localizations.dart';
import 'package:conekta_component/src/fields/card_cvv_field.dart';
Expand All @@ -9,25 +11,29 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

import 'mocks/mock_payment_service.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('CardForm submits and returns success', (tester) async {
// Android emulator reaches the host machine via 10.0.2.2.
final mockoonHost =
Platform.isAndroid ? 'http://10.0.2.2:3000' : 'http://localhost:3000';

testWidgets('CardForm submits against Mockoon and returns success',
(tester) async {
bool submitted = false;

await tester.pumpWidget(
MaterialApp(
localizationsDelegates: [
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: Scaffold(
body: CardForm(
paymentService: MockPaymentService(apiKey: "key_xxx"),
paymentService:
PaymentService(apiKey: 'key_xxx', host: mockoonHost),
onSubmitted: (result) {
submitted = result is Success<Map<String, dynamic>>;
},
Expand All @@ -38,11 +44,11 @@ void main() {

await tester.enterText(find.byType(CardNameField), 'Juan Pérez');
await tester.enterText(find.byType(CardNumberField), '4242424242424242');
await tester.enterText(find.byType(CardExpiryFields), '12/25');
await tester.enterText(find.byType(CardExpiryFields), '12/30');
await tester.enterText(find.byType(CardCVVField), '123');

await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
await tester.pumpAndSettle(const Duration(seconds: 30));

expect(submitted, isTrue);
});
Expand Down
15 changes: 0 additions & 15 deletions integration_test/mocks/mock_payment_service.dart

This file was deleted.

1 change: 1 addition & 0 deletions l10n.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-dir: lib/l10n
3 changes: 1 addition & 2 deletions lib/card_input_flutter.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
library credit_card_input_flutter;

export 'src/card_form.dart';
export 'src/services/payment_service.dart';
export 'src/services/result.dart';
export 'src/models/card_model.dart';
export 'src/models/card_form_config.dart';
19 changes: 14 additions & 5 deletions lib/src/card_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'fields/card_cvv_field.dart';
import 'fields/card_expiry_fields.dart';
import 'fields/card_name_field.dart';
import 'fields/card_number_field.dart';
import 'models/card_form_config.dart';
import 'models/card_model.dart';
import 'services/payment_service.dart';
import 'utils/dark_theme.dart';
Expand All @@ -19,12 +20,14 @@ class CardForm extends StatefulWidget {
final PaymentService paymentService;
final Locale locale;
final ThemeData? themeData;
final CardFormConfig config;
const CardForm(
{super.key,
this.onSubmitted,
required this.paymentService,
this.locale = const Locale('es'),
this.themeData});
this.themeData,
this.config = const CardFormConfig()});

@override
State<CardForm> createState() => _CardFormState();
Expand All @@ -37,13 +40,15 @@ class _CardFormState extends State<CardForm> {
final cardNumberController = TextEditingController();
final nameController = TextEditingController();
final cvvController = TextEditingController();
final nameFocusNode = FocusNode();
bool _isLoading = false;
@override
void dispose() {
cardNumberController.dispose();
nameController.dispose();
expiryDateController.dispose();
cvvController.dispose();
nameFocusNode.dispose();
super.dispose();
}

Expand All @@ -68,6 +73,7 @@ class _CardFormState extends State<CardForm> {
final result = await widget.paymentService
.sendPayment(card, widget.locale.languageCode);
cleanFields();
nameFocusNode.requestFocus();
widget.onSubmitted?.call(Success(result));
} on Exception catch (e) {
widget.onSubmitted?.call(Failure(e));
Expand All @@ -79,7 +85,7 @@ class _CardFormState extends State<CardForm> {
}
}

cleanFields() {
void cleanFields() {
cardNumberController.clear();
nameController.clear();
expiryDateController.clear();
Expand Down Expand Up @@ -115,7 +121,8 @@ class _CardFormState extends State<CardForm> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SecurePaymentSection(),
if (!widget.config.hideLogo)
const SecurePaymentSection(),
Text(
AppLocalizations.of(localizedContext)!
.cardNameLabel,
Expand All @@ -124,6 +131,7 @@ class _CardFormState extends State<CardForm> {
const SizedBox(height: 8),
CardNameField(
controller: nameController,
focusNode: nameFocusNode,
decoration: InputDecoration(
hintText: AppLocalizations.of(localizedContext)!
.cardNameHint),
Expand Down Expand Up @@ -258,8 +266,9 @@ class _CardFormState extends State<CardForm> {
.colorScheme
.onPrimary))
: Text(
AppLocalizations.of(localizedContext)!
.submitButton,
widget.config.submitButtonText ??
AppLocalizations.of(localizedContext)!
.submitButton,
style: TextStyle(
fontSize: 16,
color: Theme.of(themedContext)
Expand Down
2 changes: 1 addition & 1 deletion lib/src/fields/card_cvv_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class CardCVVField extends StatelessWidget {
return TextFormField(
controller: controller,
decoration: decoration ??
InputDecoration(
const InputDecoration(
labelText: 'CVV',
isDense: true,
).applyDefaults(Theme.of(context).inputDecorationTheme),
Expand Down
Loading