0.0.6 release
This commit is contained in:
		
							parent
							
								
									d7d27a1cf5
								
							
						
					
					
						commit
						462e40308f
					
				
							
								
								
									
										10
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@ -1,3 +1,13 @@
 | 
			
		||||
## 0.0.6
 | 
			
		||||
 | 
			
		||||
- Improved assertion and error messaging when missing stripe implements
 | 
			
		||||
- Added better doc comments
 | 
			
		||||
- Fixed `CardTextField.delayToShowLoading`, now it uses it
 | 
			
		||||
- Fixed bad assertion logic when providing stripe keys
 | 
			
		||||
- Added ability to make Stripe call with `GlobalKey`
 | 
			
		||||
- Refactored method `onTokenReceived` to `onStripeResponse` to be clearer
 | 
			
		||||
- Refactored method `onCardDetailsComplete` to `onValidCardDetails` to be clearer
 | 
			
		||||
 | 
			
		||||
## 0.0.5
 | 
			
		||||
 | 
			
		||||
- Fix Web, invalid call to `Platform.isAndroid`
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							@ -23,7 +23,7 @@ Got to use emojis and taglines for attention grabbing and algorithm hacking:
 | 
			
		||||
- Native Implementation: compiles and loads like the rest of your app, unlike embeded html
 | 
			
		||||
- Automatic validation: no `inputFormatters` or `RegExp` needed on your side
 | 
			
		||||
 | 
			
		||||
The card data can either be retrieved with the `onCardDetailsComplete` callback, or
 | 
			
		||||
The card data can either be retrieved with the `onValidCardDetails` callback, or
 | 
			
		||||
you can have the element automatically create a Stripe card token when the fields
 | 
			
		||||
are filled out, and return the token with the `onTokenReceived` callback.
 | 
			
		||||
 | 
			
		||||
@ -62,12 +62,13 @@ Include the package in a file:
 | 
			
		||||
import 'package:stripe_native_card_field/stripe_native_card_field.dart';
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### For just Card Data
 | 
			
		||||
### For Raw Card Data
 | 
			
		||||
 | 
			
		||||
Provide a callback for the `CardTextField` to return you the data when its complete.
 | 
			
		||||
```dart
 | 
			
		||||
CardTextField(
 | 
			
		||||
  width: 500,
 | 
			
		||||
  onCardDetailsComplete: (details) {
 | 
			
		||||
  onValidCardDetails: (details) {
 | 
			
		||||
    // Save the card details to use with your call to Stripe, or whoever
 | 
			
		||||
    setState(() => _cardDetails = details);
 | 
			
		||||
  },
 | 
			
		||||
@ -76,17 +77,27 @@ CardTextField(
 | 
			
		||||
 | 
			
		||||
### For Stripe Token
 | 
			
		||||
 | 
			
		||||
Simply provide a function for the `onStripeResponse` callback!
 | 
			
		||||
 | 
			
		||||
```dart
 | 
			
		||||
CardTextField(
 | 
			
		||||
  width: 500,
 | 
			
		||||
  stripePublishableKey: 'pk_test_abc123', // Your stripe key here
 | 
			
		||||
  onTokenReceived: (token) {
 | 
			
		||||
  onStripeResponse: (Map<String, dynamic> data) {
 | 
			
		||||
    // Save the stripe token to send to your backend
 | 
			
		||||
    setState(() => _token = token);
 | 
			
		||||
    setState(() => _tokenData = data);
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If you want more fine-grained control of when the stripe call is made, you
 | 
			
		||||
can create a `GlobalKey` and access the `CardTextFieldState`, calling the
 | 
			
		||||
`getStripeResponse()` function yourself. See the provided [example](https://pub.dev/packages/stripe_native_card_field/example)
 | 
			
		||||
for details. If you choose this route, do not provide an `onStripeResponse` callback, or you will end up
 | 
			
		||||
making two calls to stripe!
 | 
			
		||||
 | 
			
		||||
# Additional information
 | 
			
		||||
 | 
			
		||||
Repository located [here](https://git.fosscat.com/n8r/stripe_native_card_field)
 | 
			
		||||
 | 
			
		||||
Please email me at n8r@fosscat.com for any issues or PRs.
 | 
			
		||||
 | 
			
		||||
@ -61,14 +61,15 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
            ),
 | 
			
		||||
            CardTextField(
 | 
			
		||||
              width: 300,
 | 
			
		||||
              onCardDetailsComplete: (details) {
 | 
			
		||||
              onValidCardDetails: (details) {
 | 
			
		||||
                if (kDebugMode) {
 | 
			
		||||
                  print(details);
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              textStyle:
 | 
			
		||||
                  const TextStyle(fontFamily: 'Lato', color: Colors.tealAccent),
 | 
			
		||||
              hintTextStyle: const TextStyle(fontFamily: 'Lato', color: Colors.teal),
 | 
			
		||||
              hintTextStyle:
 | 
			
		||||
                  const TextStyle(fontFamily: 'Lato', color: Colors.teal),
 | 
			
		||||
              errorTextStyle: const TextStyle(color: Colors.purpleAccent),
 | 
			
		||||
              boxDecoration: BoxDecoration(
 | 
			
		||||
                color: Colors.black54,
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,26 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
  CardDetailsValidState? state;
 | 
			
		||||
  String? errorText;
 | 
			
		||||
 | 
			
		||||
  // Creating a global key here allows us to call the `getStripeResponse()`
 | 
			
		||||
  // inside the CardTextFieldState widget in our build method. See below
 | 
			
		||||
  final _key = GlobalKey<CardTextFieldState>();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final cardField = CardTextField(
 | 
			
		||||
      key: _key,
 | 
			
		||||
      loadingWidgetLocation: LoadingLocation.above,
 | 
			
		||||
      stripePublishableKey: 'pk_test_abc123testmykey',
 | 
			
		||||
      width: 600,
 | 
			
		||||
      onValidCardDetails: (details) {
 | 
			
		||||
        if (kDebugMode) {
 | 
			
		||||
          print(details);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      overrideValidState: state,
 | 
			
		||||
      errorText: errorText,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: Center(
 | 
			
		||||
        child: Column(
 | 
			
		||||
@ -51,22 +69,23 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
                'Enter your card details below:',
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            CardTextField(
 | 
			
		||||
              width: 300,
 | 
			
		||||
              onCardDetailsComplete: (details) {
 | 
			
		||||
                if (kDebugMode) {
 | 
			
		||||
                  print(details);
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              overrideValidState: state,
 | 
			
		||||
              errorText: errorText,
 | 
			
		||||
            ),
 | 
			
		||||
            cardField,
 | 
			
		||||
            ElevatedButton(
 | 
			
		||||
              child: const Text('Set manual error'),
 | 
			
		||||
              onPressed: () => setState(() {
 | 
			
		||||
                errorText = 'There is a problem';
 | 
			
		||||
                state = CardDetailsValidState.invalidCard;
 | 
			
		||||
              }),
 | 
			
		||||
            ),
 | 
			
		||||
            const SizedBox(height: 12),
 | 
			
		||||
            ElevatedButton(
 | 
			
		||||
              child: const Text('Get Stripe token'),
 | 
			
		||||
              onPressed: () async {
 | 
			
		||||
                // Here we use the global key to get the stripe data, rather than
 | 
			
		||||
                // using the `onStripeResponse` callback in the widget
 | 
			
		||||
                final tok = await _key.currentState?.getStripeResponse();
 | 
			
		||||
                if (kDebugMode) print(tok);
 | 
			
		||||
              },
 | 
			
		||||
            )
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
@ -206,7 +206,7 @@ packages:
 | 
			
		||||
      path: ".."
 | 
			
		||||
      relative: true
 | 
			
		||||
    source: path
 | 
			
		||||
    version: "0.0.3"
 | 
			
		||||
    version: "0.0.5"
 | 
			
		||||
  term_glyph:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								lib/card_text_field_error.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/card_text_field_error.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
/// Error class that `CardTextField` throws if any errors are encountered
 | 
			
		||||
class CardTextFieldError extends Error {
 | 
			
		||||
  /// Details provided for the error
 | 
			
		||||
  String? details;
 | 
			
		||||
  CardTextFieldErrorType type;
 | 
			
		||||
 | 
			
		||||
  CardTextFieldError(this.type, {this.details});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() {
 | 
			
		||||
    return 'CardTextFieldError-${type.name}: $details';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Enum to add typing to the `CardTextFieldErrorType`
 | 
			
		||||
enum CardTextFieldErrorType {
 | 
			
		||||
  stripeImplementation,
 | 
			
		||||
  unknown,
 | 
			
		||||
}
 | 
			
		||||
@ -2,13 +2,13 @@ library stripe_native_card_field;
 | 
			
		||||
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:developer';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
import 'package:stripe_native_card_field/card_text_field_error.dart';
 | 
			
		||||
 | 
			
		||||
import 'card_details.dart';
 | 
			
		||||
import 'card_provider_icon.dart';
 | 
			
		||||
@ -17,13 +17,23 @@ import 'card_provider_icon.dart';
 | 
			
		||||
/// entry process.
 | 
			
		||||
enum CardEntryStep { number, exp, cvc, postal }
 | 
			
		||||
 | 
			
		||||
// enum LoadingLocation { ontop, rightInside }
 | 
			
		||||
enum LoadingLocation { above, below }
 | 
			
		||||
 | 
			
		||||
/// A uniform text field for entering card details, based
 | 
			
		||||
/// on the behavior of Stripe's various html elements.
 | 
			
		||||
///
 | 
			
		||||
/// Required `width`.
 | 
			
		||||
///
 | 
			
		||||
/// To get the card data or stripe token, provide callbacks
 | 
			
		||||
/// for either `onValidCardDetails`, which will return a
 | 
			
		||||
/// `CardDetails` object, or `onStripeResponse`, which will
 | 
			
		||||
/// return a Map<String, dynamic> response from the Stripe Api.
 | 
			
		||||
///
 | 
			
		||||
/// If stripe integration is desired, you must provide both
 | 
			
		||||
/// `stripePublishableKey` and `onStripeResponse`, otherwise
 | 
			
		||||
/// `CardTextField` will `assert(false)` in debug mode or
 | 
			
		||||
/// throw a `CardTextFieldError` in profile or release mode
 | 
			
		||||
///
 | 
			
		||||
/// If the provided `width < 450.0`, the `CardTextField`
 | 
			
		||||
/// will scroll its content horizontally with the cursor
 | 
			
		||||
/// to compensate.
 | 
			
		||||
@ -32,7 +42,7 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.width,
 | 
			
		||||
    this.onStripeResponse,
 | 
			
		||||
    this.onCardDetailsComplete,
 | 
			
		||||
    this.onValidCardDetails,
 | 
			
		||||
    this.stripePublishableKey,
 | 
			
		||||
    this.height,
 | 
			
		||||
    this.textStyle,
 | 
			
		||||
@ -41,8 +51,9 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
    this.boxDecoration,
 | 
			
		||||
    this.errorBoxDecoration,
 | 
			
		||||
    this.loadingWidget,
 | 
			
		||||
    this.loadingWidgetLocation = LoadingLocation.below,
 | 
			
		||||
    this.showInternalLoadingWidget = true,
 | 
			
		||||
    this.delayToShowLoading = const Duration(milliseconds: 750),
 | 
			
		||||
    this.delayToShowLoading = const Duration(milliseconds: 0),
 | 
			
		||||
    this.onCallToStripe,
 | 
			
		||||
    this.overrideValidState,
 | 
			
		||||
    this.errorText,
 | 
			
		||||
@ -53,21 +64,17 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
    this.iconSize,
 | 
			
		||||
    this.cardIconColor,
 | 
			
		||||
    this.cardIconErrorColor,
 | 
			
		||||
    // this.loadingWidgetLocation = LoadingLocation.rightInside,
 | 
			
		||||
  }) : super(key: key) {
 | 
			
		||||
    // Setup logic for the CardTextField
 | 
			
		||||
    // Will assert in debug mode, otherwise will throw `CardTextFieldError` in profile or release
 | 
			
		||||
    if (stripePublishableKey != null) {
 | 
			
		||||
      assert(stripePublishableKey!.startsWith('pk_'));
 | 
			
		||||
      if (kReleaseMode && !stripePublishableKey!.startsWith('pk_live_')) {
 | 
			
		||||
        log('StripeNativeCardField: *WARN* You are not using a live publishableKey in production.');
 | 
			
		||||
      } else if ((kDebugMode || kProfileMode) &&
 | 
			
		||||
          stripePublishableKey!.startsWith('pk_live_')) {
 | 
			
		||||
        log('StripeNativeCardField: *WARN* You are using a live stripe key in a debug environment, proceed with caution!');
 | 
			
		||||
        log('StripeNativeCardField: *WARN* Ideally you should be using your test keys whenever not in production.');
 | 
			
		||||
      if (!stripePublishableKey!.startsWith('pk_')) {
 | 
			
		||||
        const msg = 'Invalid stripe key, doesn\'t start with "pk_"';
 | 
			
		||||
        if (kDebugMode) assert(false, msg);
 | 
			
		||||
        if (kReleaseMode || kProfileMode) {
 | 
			
		||||
          throw CardTextFieldError(CardTextFieldErrorType.stripeImplementation,
 | 
			
		||||
              details: msg);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
      if (onStripeResponse != null) {
 | 
			
		||||
        log('StripeNativeCardField: *ERROR* You provided the onTokenReceived callback, but did not provide a stripePublishableKey.');
 | 
			
		||||
        assert(false);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -96,7 +103,8 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
  /// Overrides the default box decoration of the text field when there is a validation error
 | 
			
		||||
  final BoxDecoration? errorBoxDecoration;
 | 
			
		||||
 | 
			
		||||
  /// Shown and overrides CircularProgressIndicator() if the request to stripe takes longer than `delayToShowLoading`
 | 
			
		||||
  /// Shown and overrides `LinearProgressIndicator` if the request to stripe takes longer than `delayToShowLoading`
 | 
			
		||||
  /// Recommended to only override with a `LinearProgressIndicator` or similar widget, or spacing will be messed up
 | 
			
		||||
  final Widget? loadingWidget;
 | 
			
		||||
 | 
			
		||||
  /// Overrides default icon size of the card provider, defaults to `Size(30.0, 20.0)`
 | 
			
		||||
@ -109,7 +117,7 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
  final String? cardIconErrorColor;
 | 
			
		||||
 | 
			
		||||
  /// Determines where the loading indicator appears when contacting stripe
 | 
			
		||||
  // final LoadingLocation loadingWidgetLocation;
 | 
			
		||||
  final LoadingLocation loadingWidgetLocation;
 | 
			
		||||
 | 
			
		||||
  /// Default TextStyle
 | 
			
		||||
  final TextStyle? textStyle;
 | 
			
		||||
@ -122,7 +130,7 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
  /// If null, inherits from the `textStyle`.
 | 
			
		||||
  final TextStyle? errorTextStyle;
 | 
			
		||||
 | 
			
		||||
  /// Time to wait until showing the loading indicator when retrieving Stripe token
 | 
			
		||||
  /// Time to wait until showing the loading indicator when retrieving Stripe token, defaults to 0 milliseconds.
 | 
			
		||||
  final Duration delayToShowLoading;
 | 
			
		||||
 | 
			
		||||
  /// Whether to show the internal loading widget on calls to Stripe
 | 
			
		||||
@ -138,7 +146,7 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
  final void Function(Map<String, dynamic>)? onStripeResponse;
 | 
			
		||||
 | 
			
		||||
  /// Callback that returns the completed CardDetails object
 | 
			
		||||
  final void Function(CardDetails)? onCardDetailsComplete;
 | 
			
		||||
  final void Function(CardDetails)? onValidCardDetails;
 | 
			
		||||
 | 
			
		||||
  /// Can manually override the ValidState to surface errors returned from Stripe
 | 
			
		||||
  final CardDetailsValidState? overrideValidState;
 | 
			
		||||
@ -146,14 +154,31 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
  /// Can manually override the errorText displayed to surface errors returned from Stripe
 | 
			
		||||
  final String? errorText;
 | 
			
		||||
 | 
			
		||||
  /// GlobalKey used for calling `getStripeToken` in the `CardTextFieldState`
 | 
			
		||||
  // final GlobalKey<CardTextFieldState> _key = GlobalKey<CardTextFieldState>();
 | 
			
		||||
 | 
			
		||||
  // CardTextFieldState? get state => _key.currentState;
 | 
			
		||||
 | 
			
		||||
  /// Validates the current fields and makes an http request to get the stripe
 | 
			
		||||
  /// token for the `CardDetails` provided. Will return null if the data is not
 | 
			
		||||
  /// complete or does not validate properly.
 | 
			
		||||
  // Future<Map<String, dynamic>?> fetchStripeResponse() async {
 | 
			
		||||
  //   if (kDebugMode && _key.currentState == null) print('Could not fetch Stripe Response, currentState == null');
 | 
			
		||||
  //   return _key.currentState?.getStripeResponse();
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<CardTextField> createState() => CardTextFieldState();
 | 
			
		||||
  // {
 | 
			
		||||
  //   _state = CardTextFieldState();
 | 
			
		||||
  //   return _state;
 | 
			
		||||
  // }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// State Widget for CardTextField
 | 
			
		||||
/// Should not be used directly, create a
 | 
			
		||||
/// `CardTextField()` instead.
 | 
			
		||||
@visibleForTesting
 | 
			
		||||
/// Should not be used directly, except to
 | 
			
		||||
/// create a GlobalKey for directly accessing
 | 
			
		||||
/// the `getStripeResponse` function
 | 
			
		||||
class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
  late TextEditingController _cardNumberController;
 | 
			
		||||
  late TextEditingController _expirationController;
 | 
			
		||||
@ -373,7 +398,9 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            onHorizontalDragEnd: (details) {
 | 
			
		||||
              if (!_isMobile || isWideFormat || details.primaryVelocity == null) {
 | 
			
		||||
              if (!_isMobile ||
 | 
			
		||||
                  isWideFormat ||
 | 
			
		||||
                  details.primaryVelocity == null) {
 | 
			
		||||
                return;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
@ -399,13 +426,33 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                    child: SizedBox(
 | 
			
		||||
                      width: _internalFieldWidth,
 | 
			
		||||
                      height: widget.height ?? 60.0,
 | 
			
		||||
                      child: Column(
 | 
			
		||||
                        children: [
 | 
			
		||||
                          if (widget.loadingWidgetLocation ==
 | 
			
		||||
                              LoadingLocation.above)
 | 
			
		||||
                            AnimatedOpacity(
 | 
			
		||||
                              duration: const Duration(milliseconds: 300),
 | 
			
		||||
                              opacity:
 | 
			
		||||
                                  _loading && widget.showInternalLoadingWidget
 | 
			
		||||
                                      ? 1.0
 | 
			
		||||
                                      : 0.0,
 | 
			
		||||
                              child: widget.loadingWidget ??
 | 
			
		||||
                                  const LinearProgressIndicator(),
 | 
			
		||||
                            ),
 | 
			
		||||
                          Padding(
 | 
			
		||||
                            padding: switch (widget.loadingWidgetLocation) {
 | 
			
		||||
                              LoadingLocation.above =>
 | 
			
		||||
                                const EdgeInsets.only(top: 0, bottom: 4.0),
 | 
			
		||||
                              LoadingLocation.below =>
 | 
			
		||||
                                const EdgeInsets.only(top: 4.0, bottom: 0),
 | 
			
		||||
                            },
 | 
			
		||||
                            child: Row(
 | 
			
		||||
                              crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
                              mainAxisAlignment: MainAxisAlignment.start,
 | 
			
		||||
                              children: [
 | 
			
		||||
                                Padding(
 | 
			
		||||
                            padding:
 | 
			
		||||
                                const EdgeInsets.symmetric(horizontal: 6.0),
 | 
			
		||||
                                  padding: const EdgeInsets.symmetric(
 | 
			
		||||
                                      horizontal: 6.0),
 | 
			
		||||
                                  child: CardProviderIcon(
 | 
			
		||||
                                    cardDetails: _cardDetails,
 | 
			
		||||
                                    size: widget.iconSize,
 | 
			
		||||
@ -445,9 +492,10 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                    },
 | 
			
		||||
                                    onChanged: (str) {
 | 
			
		||||
                                      final numbers = str.replaceAll(' ', '');
 | 
			
		||||
                                setState(
 | 
			
		||||
                                    () => _cardDetails.cardNumber = numbers);
 | 
			
		||||
                                if (str.length <= _cardDetails.maxINNLength) {
 | 
			
		||||
                                      setState(() =>
 | 
			
		||||
                                          _cardDetails.cardNumber = numbers);
 | 
			
		||||
                                      if (str.length <=
 | 
			
		||||
                                          _cardDetails.maxINNLength) {
 | 
			
		||||
                                        _cardDetails.detectCardProvider();
 | 
			
		||||
                                      }
 | 
			
		||||
                                      if (numbers.length == 16) {
 | 
			
		||||
@ -479,14 +527,16 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                    // fit: _currentStep == CardEntryStep.number ? FlexFit.loose : FlexFit.tight,
 | 
			
		||||
                                    child: AnimatedContainer(
 | 
			
		||||
                                      curve: Curves.easeInOut,
 | 
			
		||||
                                duration: const Duration(milliseconds: 400),
 | 
			
		||||
                                constraints:
 | 
			
		||||
                                    _currentStep == CardEntryStep.number
 | 
			
		||||
                                      duration:
 | 
			
		||||
                                          const Duration(milliseconds: 400),
 | 
			
		||||
                                      constraints: _currentStep ==
 | 
			
		||||
                                              CardEntryStep.number
 | 
			
		||||
                                          ? BoxConstraints.loose(
 | 
			
		||||
                                              Size(_expanderWidthExpanded, 0.0),
 | 
			
		||||
                                            )
 | 
			
		||||
                                          : BoxConstraints.tight(
 | 
			
		||||
                                            Size(_expanderWidthCollapsed, 0.0),
 | 
			
		||||
                                              Size(
 | 
			
		||||
                                                  _expanderWidthCollapsed, 0.0),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
@ -499,7 +549,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      // Must manually add hint label because they wont show on mobile with backspace hack
 | 
			
		||||
                                      if (_isMobile &&
 | 
			
		||||
                                    _expirationController.text == '\u200b')
 | 
			
		||||
                                          _expirationController.text ==
 | 
			
		||||
                                              '\u200b')
 | 
			
		||||
                                        Text('MM/YY', style: _hintTextSyle),
 | 
			
		||||
                                      TextFormField(
 | 
			
		||||
                                        key: const Key('expiration_field'),
 | 
			
		||||
@ -517,33 +568,39 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                        validator: (content) {
 | 
			
		||||
                                          if (content == null ||
 | 
			
		||||
                                              content.isEmpty ||
 | 
			
		||||
                                        _isMobile && content == '\u200b') {
 | 
			
		||||
                                              _isMobile &&
 | 
			
		||||
                                                  content == '\u200b') {
 | 
			
		||||
                                            return null;
 | 
			
		||||
                                          }
 | 
			
		||||
 | 
			
		||||
                                          if (_isMobile) {
 | 
			
		||||
                                            setState(() =>
 | 
			
		||||
                                                _cardDetails.expirationString =
 | 
			
		||||
                                              content.replaceAll('\u200b', ''));
 | 
			
		||||
                                                    content.replaceAll(
 | 
			
		||||
                                                        '\u200b', ''));
 | 
			
		||||
                                          } else {
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                .expirationString = content);
 | 
			
		||||
                                          }
 | 
			
		||||
 | 
			
		||||
                                          if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.dateTooEarly) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .dateTooEarly) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'Your card\'s expiration date is in the past.');
 | 
			
		||||
                                          } else if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.dateTooLate) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .dateTooLate) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'Your card\'s expiration year is invalid.');
 | 
			
		||||
                                          } else if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.missingDate) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .missingDate) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'You must include your card\'s expiration date.');
 | 
			
		||||
                                          } else if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.invalidMonth) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .invalidMonth) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'Your card\'s expiration month is invalid.');
 | 
			
		||||
                                          }
 | 
			
		||||
@ -554,12 +611,12 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                            if (str.isEmpty) {
 | 
			
		||||
                                              _backspacePressed();
 | 
			
		||||
                                            }
 | 
			
		||||
                                      setState(() =>
 | 
			
		||||
                                          _cardDetails.expirationString =
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                    .expirationString =
 | 
			
		||||
                                                str.replaceAll('\u200b', ''));
 | 
			
		||||
                                          } else {
 | 
			
		||||
                                      setState(() =>
 | 
			
		||||
                                          _cardDetails.expirationString = str);
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                .expirationString = str);
 | 
			
		||||
                                          }
 | 
			
		||||
                                          if (str.length == 5) {
 | 
			
		||||
                                            _currentCardEntryStepController
 | 
			
		||||
@ -592,7 +649,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                    alignment: Alignment.centerLeft,
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      if (_isMobile &&
 | 
			
		||||
                                    _securityCodeController.text == '\u200b')
 | 
			
		||||
                                          _securityCodeController.text ==
 | 
			
		||||
                                              '\u200b')
 | 
			
		||||
                                        Text(
 | 
			
		||||
                                          'CVC',
 | 
			
		||||
                                          style: _hintTextSyle,
 | 
			
		||||
@ -611,24 +669,29 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                        validator: (content) {
 | 
			
		||||
                                          if (content == null ||
 | 
			
		||||
                                              content.isEmpty ||
 | 
			
		||||
                                        _isMobile && content == '\u200b') {
 | 
			
		||||
                                              _isMobile &&
 | 
			
		||||
                                                  content == '\u200b') {
 | 
			
		||||
                                            return null;
 | 
			
		||||
                                          }
 | 
			
		||||
 | 
			
		||||
                                          if (_isMobile) {
 | 
			
		||||
                                      setState(() => _cardDetails.securityCode =
 | 
			
		||||
                                          content.replaceAll('\u200b', ''));
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                            setState(() =>
 | 
			
		||||
                                          _cardDetails.securityCode = content);
 | 
			
		||||
                                                _cardDetails.securityCode =
 | 
			
		||||
                                                    content.replaceAll(
 | 
			
		||||
                                                        '\u200b', ''));
 | 
			
		||||
                                          } else {
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                .securityCode = content);
 | 
			
		||||
                                          }
 | 
			
		||||
 | 
			
		||||
                                          if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.invalidCVC) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .invalidCVC) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'Your card\'s security code is invalid.');
 | 
			
		||||
                                          } else if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.missingCVC) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .missingCVC) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'Your card\'s security code is incomplete.');
 | 
			
		||||
                                          }
 | 
			
		||||
@ -642,16 +705,17 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                            if (str.isEmpty) {
 | 
			
		||||
                                              _backspacePressed();
 | 
			
		||||
                                            }
 | 
			
		||||
                                      setState(() =>
 | 
			
		||||
                                          _cardDetails.expirationString =
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                    .expirationString =
 | 
			
		||||
                                                str.replaceAll('\u200b', ''));
 | 
			
		||||
                                          } else {
 | 
			
		||||
                                      setState(() =>
 | 
			
		||||
                                          _cardDetails.expirationString = str);
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                .expirationString = str);
 | 
			
		||||
                                          }
 | 
			
		||||
 | 
			
		||||
                                          if (str.length ==
 | 
			
		||||
                                        _cardDetails.provider?.cvcLength) {
 | 
			
		||||
                                              _cardDetails
 | 
			
		||||
                                                  .provider?.cvcLength) {
 | 
			
		||||
                                            _currentCardEntryStepController
 | 
			
		||||
                                                .add(CardEntryStep.postal);
 | 
			
		||||
                                          }
 | 
			
		||||
@ -660,7 +724,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                          LengthLimitingTextInputFormatter(
 | 
			
		||||
                                              _cardDetails.provider == null
 | 
			
		||||
                                                  ? 4
 | 
			
		||||
                                            : _cardDetails.provider!.cvcLength),
 | 
			
		||||
                                                  : _cardDetails
 | 
			
		||||
                                                      .provider!.cvcLength),
 | 
			
		||||
                                          FilteringTextInputFormatter.allow(
 | 
			
		||||
                                              RegExp('[0-9]')),
 | 
			
		||||
                                        ],
 | 
			
		||||
@ -681,7 +746,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                    alignment: Alignment.centerLeft,
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      if (_isMobile &&
 | 
			
		||||
                                    _postalCodeController.text == '\u200b')
 | 
			
		||||
                                          _postalCodeController.text ==
 | 
			
		||||
                                              '\u200b')
 | 
			
		||||
                                        Text(
 | 
			
		||||
                                          'Postal Code',
 | 
			
		||||
                                          style: _hintTextSyle,
 | 
			
		||||
@ -700,24 +766,29 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                        validator: (content) {
 | 
			
		||||
                                          if (content == null ||
 | 
			
		||||
                                              content.isEmpty ||
 | 
			
		||||
                                        _isMobile && content == '\u200b') {
 | 
			
		||||
                                              _isMobile &&
 | 
			
		||||
                                                  content == '\u200b') {
 | 
			
		||||
                                            return null;
 | 
			
		||||
                                          }
 | 
			
		||||
 | 
			
		||||
                                          if (_isMobile) {
 | 
			
		||||
                                      setState(() => _cardDetails.postalCode =
 | 
			
		||||
                                          content.replaceAll('\u200b', ''));
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                            setState(() =>
 | 
			
		||||
                                          _cardDetails.postalCode = content);
 | 
			
		||||
                                                _cardDetails.postalCode =
 | 
			
		||||
                                                    content.replaceAll(
 | 
			
		||||
                                                        '\u200b', ''));
 | 
			
		||||
                                          } else {
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                .postalCode = content);
 | 
			
		||||
                                          }
 | 
			
		||||
 | 
			
		||||
                                          if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.invalidZip) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .invalidZip) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'The postal code you entered is not correct.');
 | 
			
		||||
                                          } else if (_cardDetails.validState ==
 | 
			
		||||
                                        CardDetailsValidState.missingZip) {
 | 
			
		||||
                                              CardDetailsValidState
 | 
			
		||||
                                                  .missingZip) {
 | 
			
		||||
                                            _setValidationState(
 | 
			
		||||
                                                'You must enter your card\'s postal code.');
 | 
			
		||||
                                          }
 | 
			
		||||
@ -728,11 +799,12 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                            if (str.isEmpty) {
 | 
			
		||||
                                              _backspacePressed();
 | 
			
		||||
                                            }
 | 
			
		||||
                                      setState(() => _cardDetails.postalCode =
 | 
			
		||||
                                            setState(() => _cardDetails
 | 
			
		||||
                                                    .postalCode =
 | 
			
		||||
                                                str.replaceAll('\u200b', ''));
 | 
			
		||||
                                          } else {
 | 
			
		||||
                                      setState(
 | 
			
		||||
                                          () => _cardDetails.postalCode = str);
 | 
			
		||||
                                            setState(() =>
 | 
			
		||||
                                                _cardDetails.postalCode = str);
 | 
			
		||||
                                          }
 | 
			
		||||
                                        },
 | 
			
		||||
                                        textInputAction: TextInputAction.done,
 | 
			
		||||
@ -741,7 +813,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                        },
 | 
			
		||||
                                        decoration: InputDecoration(
 | 
			
		||||
                                          contentPadding: EdgeInsets.zero,
 | 
			
		||||
                                    hintText: _isMobile ? '' : 'Postal Code',
 | 
			
		||||
                                          hintText:
 | 
			
		||||
                                              _isMobile ? '' : 'Postal Code',
 | 
			
		||||
                                          hintStyle: _hintTextSyle,
 | 
			
		||||
                                          fillColor: Colors.transparent,
 | 
			
		||||
                                          border: InputBorder.none,
 | 
			
		||||
@ -750,6 +823,11 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ],
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                          if (widget.loadingWidgetLocation ==
 | 
			
		||||
                              LoadingLocation.below)
 | 
			
		||||
                            AnimatedOpacity(
 | 
			
		||||
                              duration: const Duration(milliseconds: 300),
 | 
			
		||||
                              opacity:
 | 
			
		||||
@ -757,7 +835,7 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
                                      ? 1.0
 | 
			
		||||
                                      : 0.0,
 | 
			
		||||
                              child: widget.loadingWidget ??
 | 
			
		||||
                                const CircularProgressIndicator(),
 | 
			
		||||
                                  const LinearProgressIndicator(),
 | 
			
		||||
                            ),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
@ -784,22 +862,31 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _postalFieldSubmitted() async {
 | 
			
		||||
  // Makes an http call to stripe API with provided card credentials and returns the result
 | 
			
		||||
  Future<Map<String, dynamic>?> getStripeResponse() async {
 | 
			
		||||
    _validateFields();
 | 
			
		||||
    if (_cardDetails.isComplete) {
 | 
			
		||||
      if (widget.onCardDetailsComplete != null) {
 | 
			
		||||
        widget.onCardDetailsComplete!(_cardDetails);
 | 
			
		||||
      } else if (widget.onStripeResponse != null) {
 | 
			
		||||
        bool returned = false;
 | 
			
		||||
 | 
			
		||||
    if (!_cardDetails.isComplete) {
 | 
			
		||||
      if (kDebugMode)
 | 
			
		||||
        print(
 | 
			
		||||
            'Could not get stripe response, card details not complete: $_cardDetails');
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    if (widget.onCallToStripe != null) widget.onCallToStripe!();
 | 
			
		||||
    if (widget.stripePublishableKey == null) {
 | 
			
		||||
      if (kDebugMode)
 | 
			
		||||
        print(
 | 
			
		||||
            '***ERROR tried calling `getStripeToken()` but no stripe key provided');
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool returned = false;
 | 
			
		||||
    Future.delayed(
 | 
			
		||||
          const Duration(milliseconds: 750),
 | 
			
		||||
      widget.delayToShowLoading,
 | 
			
		||||
      () => returned ? null : setState(() => _loading = true),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const stripeCardUrl = 'https://api.stripe.com/v1/tokens';
 | 
			
		||||
        // Callback that stripe call is being made
 | 
			
		||||
        if (widget.onCallToStripe != null) widget.onCallToStripe!();
 | 
			
		||||
    final response = await http.post(
 | 
			
		||||
      Uri.parse(stripeCardUrl),
 | 
			
		||||
      body: {
 | 
			
		||||
@ -814,9 +901,22 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    returned = true;
 | 
			
		||||
        final jsonBody = jsonDecode(response.body);
 | 
			
		||||
    final Map<String, dynamic> jsonBody = jsonDecode(response.body);
 | 
			
		||||
    if (_loading) setState(() => _loading = false);
 | 
			
		||||
    return jsonBody;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
        widget.onStripeResponse!(jsonBody);
 | 
			
		||||
  Future<void> _postalFieldSubmitted() async {
 | 
			
		||||
    _validateFields();
 | 
			
		||||
    if (_cardDetails.isComplete) {
 | 
			
		||||
      if (widget.onValidCardDetails != null) {
 | 
			
		||||
        widget.onValidCardDetails!(_cardDetails);
 | 
			
		||||
      } else if (widget.onStripeResponse != null) {
 | 
			
		||||
        // Callback that stripe call is being made
 | 
			
		||||
        if (widget.onCallToStripe != null) widget.onCallToStripe!();
 | 
			
		||||
        final jsonBody = await getStripeResponse();
 | 
			
		||||
 | 
			
		||||
        if (jsonBody != null) widget.onStripeResponse!(jsonBody);
 | 
			
		||||
        if (_loading) setState(() => _loading = false);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
name: stripe_native_card_field
 | 
			
		||||
description: A native flutter implementation of the elegant Stripe Card Field.
 | 
			
		||||
version: 0.0.5
 | 
			
		||||
version: 0.0.6
 | 
			
		||||
repository: https://git.fosscat.com/n8r/stripe_native_card_field
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ void main() {
 | 
			
		||||
      CardDetails? details;
 | 
			
		||||
      final cardField = CardTextField(
 | 
			
		||||
        width: width,
 | 
			
		||||
        onCardDetailsComplete: (cd) => details = cd,
 | 
			
		||||
        onValidCardDetails: (cd) => details = cd,
 | 
			
		||||
      );
 | 
			
		||||
      await tester.pumpWidget(baseCardFieldWidget(cardField));
 | 
			
		||||
 | 
			
		||||
@ -110,7 +110,7 @@ void main() {
 | 
			
		||||
 | 
			
		||||
    final cardField = CardTextField(
 | 
			
		||||
      width: width,
 | 
			
		||||
      onCardDetailsComplete: (cd) => details = cd,
 | 
			
		||||
      onValidCardDetails: (cd) => details = cd,
 | 
			
		||||
    );
 | 
			
		||||
    await tester.pumpWidget(baseCardFieldWidget(cardField));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user