Compare commits
13 Commits
a8e571ca7b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ffeb2d199 | |||
| 3e140d6310 | |||
| dcc5cf010f | |||
| 713f35c692 | |||
| abcbf96d08 | |||
| f4b05dbccf | |||
| ef189517c1 | |||
| 462e40308f | |||
| d7d27a1cf5 | |||
| 1c918f72f9 | |||
| 4b0e4a98de | |||
| 645d0749da | |||
| bd91c1f814 |
@@ -1,3 +1,45 @@
|
|||||||
|
## 0.0.10
|
||||||
|
|
||||||
|
- One little 'Y' I missed
|
||||||
|
- Fixed out of range error with `_adjustSelection`
|
||||||
|
|
||||||
|
## 0.0.9
|
||||||
|
|
||||||
|
- Drastically improved usability and performance with flutter web and canvaskit renderer, especially on mobile
|
||||||
|
- Using streams to more accurately call `widget.onValidCardDetails` when the card details are valid and completed
|
||||||
|
- Added `cursorColor` customization
|
||||||
|
- Reworked widget life cycle so that hot reloads work as expected (resizing, focus, etc.).
|
||||||
|
|
||||||
|
## 0.0.8
|
||||||
|
|
||||||
|
- Updated dart sdk constraints again... oops (>=3.0.0)
|
||||||
|
|
||||||
|
## 0.0.7
|
||||||
|
|
||||||
|
- Changed pubspec versioning to allow lower SDK constraints (>=2.12.0)
|
||||||
|
|
||||||
|
## 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`
|
||||||
|
- Analysis issues fixed for pub points
|
||||||
|
|
||||||
|
## 0.0.4
|
||||||
|
|
||||||
|
- Fix for focus and soft keyboard on mobile devices
|
||||||
|
- Added README gif to show `CardTextField` in action
|
||||||
|
- Added Icon color customization to `CardProviderIcon` widget
|
||||||
|
- Fleshed out a dark mode styling example
|
||||||
|
|
||||||
## 0.0.3
|
## 0.0.3
|
||||||
|
|
||||||
Lots of improvements!
|
Lots of improvements!
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ elements they provide in flutter was inconvenient for me, so I made this package
|
|||||||
|
|
||||||
Got to use emojis and taglines for attention grabbing and algorithm hacking:
|
Got to use emojis and taglines for attention grabbing and algorithm hacking:
|
||||||
|
|
||||||
- Blazingly fast ( its as fast as the rest of flutter )
|
- ⚡ Blazingly fast ( its as fast as the rest of flutter )
|
||||||
- Cleaner ( fewer dependencies than the official stripe elements )
|
- 🧹 Cleaner & Easier to Use ( fewer dependencies than the official stripe elements )
|
||||||
- 🛡 Safe and Supports all Flutter Targets ( its native flutter with minimal dependencies )
|
- 🛡 Safe and Supports all Flutter Targets ( its native flutter with minimal dependencies )
|
||||||
- ☑ Seemless UI/UX ( hard to match stripe quality, but I think I got close )
|
- ☑ Seemless UI/UX ( hard to match stripe quality, but I think I got pretty close )
|
||||||
- Built-in Stripe Integration ( guess that one is obvious )
|
- 💳 Built-in Stripe Integration ( guess that one is obvious )
|
||||||
- ☯ Chi Energy Boost ( alright I'm fishing... )
|
- ☯ Chi Energy Boost ( alright I'm fishing... )
|
||||||
|
|
||||||
## Why StripeNativeCardField?
|
## Why StripeNativeCardField?
|
||||||
@@ -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
|
- Native Implementation: compiles and loads like the rest of your app, unlike embeded html
|
||||||
- Automatic validation: no `inputFormatters` or `RegExp` needed on your side
|
- 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
|
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.
|
are filled out, and return the token with the `onTokenReceived` callback.
|
||||||
|
|
||||||
@@ -31,19 +31,23 @@ are filled out, and return the token with the `onTokenReceived` callback.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
[Documentation to supported card providers](https://pub.dev/documentation/stripe_native_card_field/latest/card_details/CardProviderID.html)
|
[Supported Card Providers in Docs](https://pub.dev/documentation/stripe_native_card_field/latest/card_details/CardProviderID.html)
|
||||||
|
|
||||||
Currently support American Express, Diners Club, Discover Card, Mastercard, Jcb, Visa
|
|
||||||
|
|
||||||
### Customizable Styles
|
### Customizable Styles
|
||||||
|
|
||||||
![Customizable Style 1]()
|

|
||||||
|
|
||||||
![Customizable Style 2]()
|
This dark mode style example provided [here](https://git.fosscat.com/n8r/stripe_native_card_field/raw/branch/main/example/lib/dark_customization.dart)
|
||||||
|
|
||||||
### Cross Platform
|
For documentation on all of the available customizable aspects of the `CardTextField`, go
|
||||||
|
to the [API docs here](https://pub.dev/documentation/stripe_native_card_field/latest/stripe_native_card_field/CardTextField-class.html).
|
||||||
|
|
||||||

|
### Smooth UX
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Mimics the Stripe html elements behavior wherever possible. Auto focusing / transitioning text fields, backspacing focuses to last field,
|
||||||
|
automatically validating user input, etc.
|
||||||
|
|
||||||
# Getting started
|
# Getting started
|
||||||
|
|
||||||
@@ -58,12 +62,13 @@ Include the package in a file:
|
|||||||
import 'package:stripe_native_card_field/stripe_native_card_field.dart';
|
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
|
```dart
|
||||||
CardTextField(
|
CardTextField(
|
||||||
width: 500,
|
width: 500,
|
||||||
onCardDetailsComplete: (details) {
|
onValidCardDetails: (details) {
|
||||||
// Save the card details to use with your call to Stripe, or whoever
|
// Save the card details to use with your call to Stripe, or whoever
|
||||||
setState(() => _cardDetails = details);
|
setState(() => _cardDetails = details);
|
||||||
},
|
},
|
||||||
@@ -72,22 +77,27 @@ CardTextField(
|
|||||||
|
|
||||||
### For Stripe Token
|
### For Stripe Token
|
||||||
|
|
||||||
|
Simply provide a function for the `onStripeResponse` callback!
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
CardTextField(
|
CardTextField(
|
||||||
width: 500,
|
width: 500,
|
||||||
stripePublishableKey: 'pk_test_abc123', // Your stripe key here
|
stripePublishableKey: 'pk_test_abc123', // Your stripe key here
|
||||||
onTokenReceived: (token) {
|
onStripeResponse: (Map<String, dynamic> data) {
|
||||||
// Save the stripe token to send to your backend
|
// Save the stripe token to send to your backend
|
||||||
setState(() => _token = token);
|
setState(() => _tokenData = data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cumstomization
|
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
|
||||||
For documentation on all of the available customizable aspects of the `CardTextField`, go
|
`getStripeResponse()` function yourself. See the provided [example](https://pub.dev/packages/stripe_native_card_field/example)
|
||||||
to the [API docs here](https://pub.dev/documentation/stripe_native_card_field/latest/stripe_native_card_field/CardTextField-class.html).
|
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
|
# Additional information
|
||||||
|
|
||||||
Repository located [here](https://git.fosscat.com/n8r/stripe_native_card_field)
|
Repository located [here](https://git.fosscat.com/n8r/stripe_native_card_field)
|
||||||
|
|
||||||
|
Please email me at n8r@fosscat.com for any issues or PRs.
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,101 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stripe_native_card_field/card_details.dart';
|
||||||
|
import 'package:stripe_native_card_field/stripe_native_card_field.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
// This widget is the root of your application.
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
title: 'Native Stripe Field Demo',
|
||||||
|
theme: ThemeData(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: Colors.deepPurple, brightness: Brightness.dark),
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
|
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyHomePage extends StatefulWidget {
|
||||||
|
const MyHomePage({super.key, required this.title});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyHomePage> createState() => _MyHomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
|
CardDetailsValidState? state;
|
||||||
|
String? errorText;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black45,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
|
title: Text(widget.title),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'Enter your card details below:',
|
||||||
|
style: TextStyle(color: Colors.white70),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CardTextField(
|
||||||
|
width: 300,
|
||||||
|
onValidCardDetails: (details) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(details);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textStyle:
|
||||||
|
const TextStyle(fontFamily: 'Lato', color: Colors.tealAccent),
|
||||||
|
hintTextStyle:
|
||||||
|
const TextStyle(fontFamily: 'Lato', color: Colors.teal),
|
||||||
|
errorTextStyle: const TextStyle(color: Colors.purpleAccent),
|
||||||
|
boxDecoration: BoxDecoration(
|
||||||
|
color: Colors.black54,
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.teal.withAlpha(255),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errorBoxDecoration: BoxDecoration(
|
||||||
|
color: Colors.black54,
|
||||||
|
border: Border.all(width: 3.0, color: Colors.purple),
|
||||||
|
),
|
||||||
|
cardIconColor: 'teal',
|
||||||
|
cardIconErrorColor: '#b65cc2',
|
||||||
|
overrideValidState: state,
|
||||||
|
errorText: errorText,
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text('Set manual error'),
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
errorText = 'There is a problem';
|
||||||
|
state = CardDetailsValidState.invalidCard;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-20
@@ -38,13 +38,27 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
CardDetailsValidState? state;
|
CardDetailsValidState? state;
|
||||||
String? errorText;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
||||||
title: Text(widget.title),
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -55,28 +69,23 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
'Enter your card details below:',
|
'Enter your card details below:',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CardTextField(
|
cardField,
|
||||||
width: 300,
|
|
||||||
onCardDetailsComplete: (details) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(details);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// textStyle: TextStyle(fontSize: 24.0),
|
|
||||||
// cardFieldWidth: 260,
|
|
||||||
// expFieldWidth: 100.0,
|
|
||||||
// securityFieldWidth: 60.0,
|
|
||||||
// postalFieldWidth: 130.0,
|
|
||||||
// iconSize: Size(50.0, 35.0),
|
|
||||||
overrideValidState: state,
|
|
||||||
errorText: errorText,
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: const Text('Set manual error'),
|
child: const Text('Set manual error'),
|
||||||
onPressed: () => setState(() {
|
onPressed: () => setState(() {
|
||||||
errorText = 'There is a problem';
|
errorText = 'There is a problem';
|
||||||
state = CardDetailsValidState.invalidCard;
|
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);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
+14
-14
@@ -45,10 +45,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.2"
|
version: "1.18.0"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -135,10 +135,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -180,18 +180,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -206,7 +206,7 @@ packages:
|
|||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.2"
|
version: "0.0.9"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -219,10 +219,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -267,10 +267,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4-beta"
|
version: "0.3.0"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -280,5 +280,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.3 <4.0.0"
|
dart: ">=3.2.0-194.0.dev <4.0.0"
|
||||||
flutter: ">=3.7.0-0"
|
flutter: ">=3.7.0-0"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.3 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -25,3 +25,10 @@ flutter:
|
|||||||
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fonts:
|
||||||
|
- family: Lato
|
||||||
|
fonts:
|
||||||
|
- asset: assets/Lato-Medium.ttf
|
||||||
|
weight: 400
|
||||||
|
|||||||
+33
-20
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
/// Class encapsulating the card's data
|
/// Class encapsulating the card's data
|
||||||
@@ -18,7 +20,11 @@ class CardDetails {
|
|||||||
/// Sets every field to null, a default
|
/// Sets every field to null, a default
|
||||||
/// `CardDetails` when nothing has been entered.
|
/// `CardDetails` when nothing has been entered.
|
||||||
factory CardDetails.blank() {
|
factory CardDetails.blank() {
|
||||||
return CardDetails(cardNumber: null, securityCode: null, expirationString: null, postalCode: null);
|
return CardDetails(
|
||||||
|
cardNumber: null,
|
||||||
|
securityCode: null,
|
||||||
|
expirationString: null,
|
||||||
|
postalCode: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the CardNumber as a `String` with the spaces removed.
|
/// Returns the CardNumber as a `String` with the spaces removed.
|
||||||
@@ -35,6 +41,7 @@ class CardDetails {
|
|||||||
CardDetailsValidState _validState = CardDetailsValidState.blank;
|
CardDetailsValidState _validState = CardDetailsValidState.blank;
|
||||||
int _lastCheckHash = 0;
|
int _lastCheckHash = 0;
|
||||||
CardProvider? provider;
|
CardProvider? provider;
|
||||||
|
StreamController<CardDetails> onCompleteController = StreamController();
|
||||||
|
|
||||||
set overrideValidState(CardDetailsValidState state) => _validState = state;
|
set overrideValidState(CardDetailsValidState state) => _validState = state;
|
||||||
|
|
||||||
@@ -47,12 +54,12 @@ class CardDetails {
|
|||||||
String get expMonth => isComplete ? expirationString!.split('/').first : '';
|
String get expMonth => isComplete ? expirationString!.split('/').first : '';
|
||||||
String get expYear => isComplete ? expirationString!.split('/').last : '';
|
String get expYear => isComplete ? expirationString!.split('/').last : '';
|
||||||
|
|
||||||
// TODO rename to be more clear
|
|
||||||
/// Returns true if `_cardNumber` is null, or
|
/// Returns true if `_cardNumber` is null, or
|
||||||
/// if the _cardNumber matches the detected `provider`'s
|
/// if the _cardNumber matches the detected `provider`'s
|
||||||
/// card lenght, defaulting to 16.
|
/// card lenght, defaulting to 16.
|
||||||
bool get cardNumberFilled =>
|
bool get cardNumberFilled => _cardNumber == null
|
||||||
_cardNumber == null ? false : (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
|
? false
|
||||||
|
: (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
|
||||||
|
|
||||||
/// Returns true if all details are complete and valid
|
/// Returns true if all details are complete and valid
|
||||||
/// otherwise, return false.
|
/// otherwise, return false.
|
||||||
@@ -61,6 +68,14 @@ class CardDetails {
|
|||||||
return _complete;
|
return _complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detects if the card is complete, then broadcasts
|
||||||
|
/// card details to `onCompleteController`
|
||||||
|
void broadcastStatus() {
|
||||||
|
if (isComplete) {
|
||||||
|
onCompleteController.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The maximum length of the INN (identifier)
|
/// The maximum length of the INN (identifier)
|
||||||
/// of a card provider.
|
/// of a card provider.
|
||||||
int get maxINNLength => 4;
|
int get maxINNLength => 4;
|
||||||
@@ -77,9 +92,12 @@ class CardDetails {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastCheckHash = currentHash;
|
|
||||||
if (_cardNumber == null && expirationString == null && securityCode == null && postalCode == null) {
|
|
||||||
_complete = false;
|
_complete = false;
|
||||||
|
_lastCheckHash = currentHash;
|
||||||
|
if (_cardNumber == null &&
|
||||||
|
expirationString == null &&
|
||||||
|
securityCode == null &&
|
||||||
|
postalCode == null) {
|
||||||
_validState = CardDetailsValidState.blank;
|
_validState = CardDetailsValidState.blank;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -91,61 +109,52 @@ class CardDetails {
|
|||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
if (!_luhnAlgorithmCheck(nums)) {
|
if (!_luhnAlgorithmCheck(nums)) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.invalidCard;
|
_validState = CardDetailsValidState.invalidCard;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_cardNumber == null || !cardNumberFilled) {
|
if (_cardNumber == null || !cardNumberFilled) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.missingCard;
|
_validState = CardDetailsValidState.missingCard;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (expirationString == null) {
|
if (expirationString == null) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.missingDate;
|
_validState = CardDetailsValidState.missingDate;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final expSplits = expirationString!.split('/');
|
final expSplits = expirationString!.split('/');
|
||||||
if (expSplits.length != 2 || expSplits.last == '') {
|
if (expSplits.length != 2 || expSplits.last == '') {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.missingDate;
|
_validState = CardDetailsValidState.missingDate;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final month = int.parse(expSplits.first[0] == '0' ? expSplits.first[1] : expSplits.first);
|
final month = int.parse(
|
||||||
|
expSplits.first[0] == '0' ? expSplits.first[1] : expSplits.first);
|
||||||
if (month < 1 || month > 12) {
|
if (month < 1 || month > 12) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.invalidMonth;
|
_validState = CardDetailsValidState.invalidMonth;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final year = 2000 + int.parse(expSplits.last);
|
final year = 2000 + int.parse(expSplits.last);
|
||||||
final date = DateTime(year, month);
|
final date = DateTime(year, month);
|
||||||
if (date.isBefore(DateTime.now())) {
|
if (date.isBefore(DateTime.now())) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.dateTooEarly;
|
_validState = CardDetailsValidState.dateTooEarly;
|
||||||
return;
|
return;
|
||||||
} else if (date.isAfter(DateTime.now().add(const Duration(days: 365 * 50)))) {
|
} else if (date
|
||||||
_complete = false;
|
.isAfter(DateTime.now().add(const Duration(days: 365 * 50)))) {
|
||||||
_validState = CardDetailsValidState.dateTooLate;
|
_validState = CardDetailsValidState.dateTooLate;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
expirationDate = date;
|
expirationDate = date;
|
||||||
if (securityCode == null) {
|
if (securityCode == null) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.missingCVC;
|
_validState = CardDetailsValidState.missingCVC;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (provider != null && securityCode!.length != provider!.cvcLength) {
|
if (provider != null && securityCode!.length != provider!.cvcLength) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.invalidCVC;
|
_validState = CardDetailsValidState.invalidCVC;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (postalCode == null) {
|
if (postalCode == null) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.missingZip;
|
_validState = CardDetailsValidState.missingZip;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!RegExp(r'^\d{5}(-\d{4})?$').hasMatch(postalCode!)) {
|
if (!RegExp(r'^\d{5}(-\d{4})?$').hasMatch(postalCode!)) {
|
||||||
_complete = false;
|
|
||||||
_validState = CardDetailsValidState.invalidZip;
|
_validState = CardDetailsValidState.invalidZip;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -282,7 +291,11 @@ class CardProvider {
|
|||||||
int cvcLength;
|
int cvcLength;
|
||||||
|
|
||||||
CardProvider(
|
CardProvider(
|
||||||
{required this.id, required this.cardLength, required this.cvcLength, this.innValidNums, this.innValidRanges}) {
|
{required this.id,
|
||||||
|
required this.cardLength,
|
||||||
|
required this.cvcLength,
|
||||||
|
this.innValidNums,
|
||||||
|
this.innValidRanges}) {
|
||||||
// Must provide one or the other
|
// Must provide one or the other
|
||||||
assert(innValidNums != null || innValidRanges != null);
|
assert(innValidNums != null || innValidRanges != null);
|
||||||
// Do not provide empty list of valid nums
|
// Do not provide empty list of valid nums
|
||||||
|
|||||||
+20
-10
@@ -7,21 +7,36 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
///
|
///
|
||||||
/// To see a list of supported card providers, see `CardDetails.provider`.
|
/// To see a list of supported card providers, see `CardDetails.provider`.
|
||||||
class CardProviderIcon extends StatefulWidget {
|
class CardProviderIcon extends StatefulWidget {
|
||||||
const CardProviderIcon({required this.cardDetails, this.size, super.key});
|
const CardProviderIcon(
|
||||||
|
{required this.cardDetails,
|
||||||
|
this.size,
|
||||||
|
this.defaultCardColor,
|
||||||
|
this.errorCardColor,
|
||||||
|
super.key});
|
||||||
|
|
||||||
final CardDetails? cardDetails;
|
final CardDetails? cardDetails;
|
||||||
final Size? size;
|
final Size? size;
|
||||||
|
final String? defaultCardColor;
|
||||||
|
final String? errorCardColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CardProviderIcon> createState() => _CardProviderIconState();
|
State<CardProviderIcon> createState() => _CardProviderIconState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CardProviderIconState extends State<CardProviderIcon> {
|
class _CardProviderIconState extends State<CardProviderIcon> {
|
||||||
final Map<String, String> cardProviderSvg = {
|
late final Map<String, String> cardProviderSvg;
|
||||||
|
late final Size _size;
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
final errorCardColor = widget.errorCardColor ?? 'red';
|
||||||
|
final defaultCardColor = widget.defaultCardColor ?? 'black';
|
||||||
|
|
||||||
|
cardProviderSvg = {
|
||||||
'credit-card':
|
'credit-card':
|
||||||
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H21.4C21.7314 5 22 5.26863 22 5.6V9M2 9V18.4C2 18.7314 2.26863 19 2.6 19H21.4C21.7314 19 22 18.7314 22 18.4V9M2 9H22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="$defaultCardColor" stroke="$defaultCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H21.4C21.7314 5 22 5.26863 22 5.6V9M2 9V18.4C2 18.7314 2.26863 19 2.6 19H21.4C21.7314 19 22 18.7314 22 18.4V9M2 9H22" stroke="$defaultCardColor" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||||
'error':
|
'error':
|
||||||
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 3L21 21" stroke="red" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="red" stroke="red" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.5 19H2.6C2.26863 19 2 18.7314 2 18.4V9H8.5" stroke="red" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H4.5" stroke="red" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 9H22V17" stroke="red" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 9V5.6C22 5.26863 21.7314 5 21.4 5H10" stroke="red" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 3L21 21" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="$errorCardColor" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.5 19H2.6C2.26863 19 2 18.7314 2 18.4V9H8.5" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H4.5" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 9H22V17" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 9V5.6C22 5.26863 21.7314 5 21.4 5H10" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||||
CardProviderID.discoverCard.name:
|
CardProviderID.discoverCard.name:
|
||||||
'<svg height="500" viewBox="0 0 780 500" width="780" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m54.992 0c-30.365 0-54.992 24.63-54.992 55.004v390.992c0 30.38 24.619 55.004 54.992 55.004h670.016c30.365 0 54.992-24.63 54.992-55.004v-390.992c0-30.38-24.619-55.004-54.992-55.004z" fill="#4d4d4d"/><path d="m327.152 161.893c8.837 0 16.248 1.784 25.268 6.09v22.751c-8.544-7.863-15.955-11.154-25.756-11.154-19.264 0-34.414 15.015-34.414 34.05 0 20.075 14.681 34.196 35.37 34.196 9.312 0 16.586-3.12 24.8-10.857v22.763c-9.341 4.14-16.911 5.776-25.756 5.776-31.278 0-55.582-22.596-55.582-51.737 0-28.826 24.951-51.878 56.07-51.878zm-97.113.627c11.546 0 22.11 3.72 30.943 10.994l-10.748 13.248c-5.35-5.646-10.41-8.028-16.564-8.028-8.853 0-15.3 4.745-15.3 10.989 0 5.354 3.619 8.188 15.944 12.482 23.365 8.044 30.29 15.176 30.29 30.926 0 19.193-14.976 32.553-36.32 32.553-15.63 0-26.994-5.795-36.458-18.872l13.268-12.03c4.73 8.61 12.622 13.222 22.42 13.222 9.163 0 15.947-5.952 15.947-13.984 0-4.164-2.055-7.734-6.158-10.258-2.066-1.195-6.158-2.977-14.2-5.647-19.291-6.538-25.91-13.527-25.91-27.185 0-16.225 14.214-28.41 32.846-28.41zm234.723 1.728h22.437l28.084 66.592 28.446-66.592h22.267l-45.494 101.686h-11.053zm-397.348.152h30.15c33.312 0 56.534 20.382 56.534 49.641 0 14.59-7.104 28.696-19.118 38.057-10.108 7.901-21.626 11.445-37.574 11.445h-29.992zm96.135 0h20.54v99.143h-20.54zm411.734 0h58.252v16.8h-37.725v22.005h36.336v16.791h-36.336v26.762h37.726v16.785h-58.252v-99.143zm71.858 0h30.455c23.69 0 37.265 10.71 37.265 29.272 0 15.18-8.514 25.14-23.986 28.105l33.148 41.766h-25.26l-28.429-39.828h-2.678v39.828h-20.515zm20.515 15.616v30.025h6.002c13.117 0 20.069-5.362 20.069-15.328 0-9.648-6.954-14.697-19.745-14.697zm-579.716 1.183v65.559h5.512c13.273 0 21.656-2.394 28.11-7.88 7.103-5.955 11.376-15.465 11.376-24.98 0-9.499-4.273-18.725-11.376-24.681-6.785-5.78-14.837-8.018-28.11-8.018z" fill="#fff"/><path d="m415.13 161.21c30.941 0 56.022 23.58 56.022 52.709v.033c0 29.13-25.081 52.742-56.021 52.742s-56.022-23.613-56.022-52.742v-.033c0-29.13 25.082-52.71 56.022-52.71zm364.85 127.15c-26.05 18.33-221.08 149.34-558.75 212.62h503.76c30.365 0 54.992-24.63 54.992-55.004v-157.62z" fill="#f47216"/></g></svg>',
|
'<svg height="500" viewBox="0 0 780 500" width="780" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m54.992 0c-30.365 0-54.992 24.63-54.992 55.004v390.992c0 30.38 24.619 55.004 54.992 55.004h670.016c30.365 0 54.992-24.63 54.992-55.004v-390.992c0-30.38-24.619-55.004-54.992-55.004z" fill="#4d4d4d"/><path d="m327.152 161.893c8.837 0 16.248 1.784 25.268 6.09v22.751c-8.544-7.863-15.955-11.154-25.756-11.154-19.264 0-34.414 15.015-34.414 34.05 0 20.075 14.681 34.196 35.37 34.196 9.312 0 16.586-3.12 24.8-10.857v22.763c-9.341 4.14-16.911 5.776-25.756 5.776-31.278 0-55.582-22.596-55.582-51.737 0-28.826 24.951-51.878 56.07-51.878zm-97.113.627c11.546 0 22.11 3.72 30.943 10.994l-10.748 13.248c-5.35-5.646-10.41-8.028-16.564-8.028-8.853 0-15.3 4.745-15.3 10.989 0 5.354 3.619 8.188 15.944 12.482 23.365 8.044 30.29 15.176 30.29 30.926 0 19.193-14.976 32.553-36.32 32.553-15.63 0-26.994-5.795-36.458-18.872l13.268-12.03c4.73 8.61 12.622 13.222 22.42 13.222 9.163 0 15.947-5.952 15.947-13.984 0-4.164-2.055-7.734-6.158-10.258-2.066-1.195-6.158-2.977-14.2-5.647-19.291-6.538-25.91-13.527-25.91-27.185 0-16.225 14.214-28.41 32.846-28.41zm234.723 1.728h22.437l28.084 66.592 28.446-66.592h22.267l-45.494 101.686h-11.053zm-397.348.152h30.15c33.312 0 56.534 20.382 56.534 49.641 0 14.59-7.104 28.696-19.118 38.057-10.108 7.901-21.626 11.445-37.574 11.445h-29.992zm96.135 0h20.54v99.143h-20.54zm411.734 0h58.252v16.8h-37.725v22.005h36.336v16.791h-36.336v26.762h37.726v16.785h-58.252v-99.143zm71.858 0h30.455c23.69 0 37.265 10.71 37.265 29.272 0 15.18-8.514 25.14-23.986 28.105l33.148 41.766h-25.26l-28.429-39.828h-2.678v39.828h-20.515zm20.515 15.616v30.025h6.002c13.117 0 20.069-5.362 20.069-15.328 0-9.648-6.954-14.697-19.745-14.697zm-579.716 1.183v65.559h5.512c13.273 0 21.656-2.394 28.11-7.88 7.103-5.955 11.376-15.465 11.376-24.98 0-9.499-4.273-18.725-11.376-24.681-6.785-5.78-14.837-8.018-28.11-8.018z" fill="#fff"/><path d="m415.13 161.21c30.941 0 56.022 23.58 56.022 52.709v.033c0 29.13-25.081 52.742-56.021 52.742s-56.022-23.613-56.022-52.742v-.033c0-29.13 25.082-52.71 56.022-52.71zm364.85 127.15c-26.05 18.33-221.08 149.34-558.75 212.62h503.76c30.365 0 54.992-24.63 54.992-55.004v-157.62z" fill="#f47216"/></g></svg>',
|
||||||
CardProviderID.americanExpress.name:
|
CardProviderID.americanExpress.name:
|
||||||
@@ -36,13 +51,8 @@ class _CardProviderIconState extends State<CardProviderIcon> {
|
|||||||
'<svg enable-background="new 0 0 780 500" height="500" viewBox="0 0 780 500" width="780" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(132.87 0 0 323.02 -120270 -100930)" gradientUnits="userSpaceOnUse" x1="908.72" x2="909.72" y1="313.21" y2="313.21"><stop offset="0" stop-color="#007b40"/><stop offset="1" stop-color="#55b330"/></linearGradient><linearGradient id="b" gradientTransform="matrix(133.43 0 0 323.02 -121080 -100920)" gradientUnits="userSpaceOnUse" x1="908.73" x2="909.73" y1="313.21" y2="313.21"><stop offset="0" stop-color="#1d2970"/><stop offset="1" stop-color="#006dba"/></linearGradient><linearGradient id="c" gradientTransform="matrix(132.96 0 0 323.03 -120500 -100930)" gradientUnits="userSpaceOnUse" x1="908.72" x2="909.72" y1="313.21" y2="313.21"><stop offset="0" stop-color="#6e2b2f"/><stop offset="1" stop-color="#e30138"/></linearGradient><path d="m632.24 361.27c0 41.615-33.729 75.36-75.357 75.36h-409.13v-297.88c0-41.626 33.73-75.371 75.364-75.371h409.12l-.001 297.89z" fill="#fff"/><path d="m498.86 256.54c11.686.254 23.438-.516 35.077.4 11.787 2.199 14.628 20.043 4.156 25.887-7.145 3.85-15.633 1.434-23.379 2.113h-15.854zm41.834-32.145c2.596 9.164-6.238 17.392-15.064 16.13h-26.77c.188-8.642-.367-18.022.272-26.209 10.724.302 21.547-.616 32.209.48 4.581 1.151 8.415 4.917 9.353 9.599zm64.425-135.9c.498 17.501.072 35.927.215 53.783-.033 72.596.07 145.19-.057 217.79-.47 27.207-24.582 50.848-51.601 51.391-27.045.11-54.094.017-81.143.047v-109.75c29.471-.152 58.957.309 88.416-.23 13.666-.858 28.635-9.875 29.271-24.914 1.609-15.104-12.631-25.551-26.151-27.201-5.197-.135-5.045-1.515 0-2.117 12.895-2.787 23.021-16.133 19.227-29.499-3.233-14.058-18.771-19.499-31.695-19.472-26.352-.179-52.709-.025-79.062-.077.17-20.489-.355-41 .283-61.474 2.088-26.716 26.807-48.748 53.446-48.27 26.287-.004 52.57-.004 78.851-.005z" fill="url(#a)"/><path d="m174.74 139.54c.673-27.164 24.888-50.611 51.872-51.008 26.945-.083 53.894-.012 80.839-.036-.074 90.885.146 181.78-.111 272.66-1.038 26.834-24.989 49.834-51.679 50.309-26.996.098-53.995.014-80.992.041v-113.45c26.223 6.195 53.722 8.832 80.474 4.723 15.991-2.573 33.487-10.426 38.901-27.016 3.984-14.191 1.741-29.126 2.334-43.691v-33.825h-46.297c-.208 22.371.426 44.781-.335 67.125-1.248 13.734-14.849 22.46-27.802 21.994-16.064.17-47.897-11.642-47.897-11.642-.08-41.914.466-94.405.693-136.18z" fill="url(#b)"/><path d="m324.72 211.89c-2.437.517-.49-8.301-1.113-11.646.166-21.15-.347-42.323.283-63.458 2.082-26.829 26.991-48.916 53.738-48.288h78.768c-.074 90.885.145 181.78-.111 272.66-1.039 26.834-24.992 49.833-51.683 50.309-26.997.102-53.997.016-80.996.042v-124.3c18.439 15.129 43.5 17.484 66.472 17.525 17.318-.006 34.535-2.676 51.353-6.67v-22.772c-18.953 9.446-41.233 15.446-62.243 10.019-14.656-3.648-25.295-17.812-25.058-32.937-1.698-15.729 7.522-32.335 22.979-37.011 19.191-6.008 40.107-1.413 58.096 6.398 3.854 2.018 7.766 4.521 6.225-1.921v-17.899c-30.086-7.158-62.104-9.792-92.33-2.005-8.749 2.468-17.273 6.211-24.38 11.956z" fill="url(#c)"/></svg>',
|
'<svg enable-background="new 0 0 780 500" height="500" viewBox="0 0 780 500" width="780" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(132.87 0 0 323.02 -120270 -100930)" gradientUnits="userSpaceOnUse" x1="908.72" x2="909.72" y1="313.21" y2="313.21"><stop offset="0" stop-color="#007b40"/><stop offset="1" stop-color="#55b330"/></linearGradient><linearGradient id="b" gradientTransform="matrix(133.43 0 0 323.02 -121080 -100920)" gradientUnits="userSpaceOnUse" x1="908.73" x2="909.73" y1="313.21" y2="313.21"><stop offset="0" stop-color="#1d2970"/><stop offset="1" stop-color="#006dba"/></linearGradient><linearGradient id="c" gradientTransform="matrix(132.96 0 0 323.03 -120500 -100930)" gradientUnits="userSpaceOnUse" x1="908.72" x2="909.72" y1="313.21" y2="313.21"><stop offset="0" stop-color="#6e2b2f"/><stop offset="1" stop-color="#e30138"/></linearGradient><path d="m632.24 361.27c0 41.615-33.729 75.36-75.357 75.36h-409.13v-297.88c0-41.626 33.73-75.371 75.364-75.371h409.12l-.001 297.89z" fill="#fff"/><path d="m498.86 256.54c11.686.254 23.438-.516 35.077.4 11.787 2.199 14.628 20.043 4.156 25.887-7.145 3.85-15.633 1.434-23.379 2.113h-15.854zm41.834-32.145c2.596 9.164-6.238 17.392-15.064 16.13h-26.77c.188-8.642-.367-18.022.272-26.209 10.724.302 21.547-.616 32.209.48 4.581 1.151 8.415 4.917 9.353 9.599zm64.425-135.9c.498 17.501.072 35.927.215 53.783-.033 72.596.07 145.19-.057 217.79-.47 27.207-24.582 50.848-51.601 51.391-27.045.11-54.094.017-81.143.047v-109.75c29.471-.152 58.957.309 88.416-.23 13.666-.858 28.635-9.875 29.271-24.914 1.609-15.104-12.631-25.551-26.151-27.201-5.197-.135-5.045-1.515 0-2.117 12.895-2.787 23.021-16.133 19.227-29.499-3.233-14.058-18.771-19.499-31.695-19.472-26.352-.179-52.709-.025-79.062-.077.17-20.489-.355-41 .283-61.474 2.088-26.716 26.807-48.748 53.446-48.27 26.287-.004 52.57-.004 78.851-.005z" fill="url(#a)"/><path d="m174.74 139.54c.673-27.164 24.888-50.611 51.872-51.008 26.945-.083 53.894-.012 80.839-.036-.074 90.885.146 181.78-.111 272.66-1.038 26.834-24.989 49.834-51.679 50.309-26.996.098-53.995.014-80.992.041v-113.45c26.223 6.195 53.722 8.832 80.474 4.723 15.991-2.573 33.487-10.426 38.901-27.016 3.984-14.191 1.741-29.126 2.334-43.691v-33.825h-46.297c-.208 22.371.426 44.781-.335 67.125-1.248 13.734-14.849 22.46-27.802 21.994-16.064.17-47.897-11.642-47.897-11.642-.08-41.914.466-94.405.693-136.18z" fill="url(#b)"/><path d="m324.72 211.89c-2.437.517-.49-8.301-1.113-11.646.166-21.15-.347-42.323.283-63.458 2.082-26.829 26.991-48.916 53.738-48.288h78.768c-.074 90.885.145 181.78-.111 272.66-1.039 26.834-24.992 49.833-51.683 50.309-26.997.102-53.997.016-80.996.042v-124.3c18.439 15.129 43.5 17.484 66.472 17.525 17.318-.006 34.535-2.676 51.353-6.67v-22.772c-18.953 9.446-41.233 15.446-62.243 10.019-14.656-3.648-25.295-17.812-25.058-32.937-1.698-15.729 7.522-32.335 22.979-37.011 19.191-6.008 40.107-1.413 58.096 6.398 3.854 2.018 7.766 4.521 6.225-1.921v-17.899c-30.086-7.158-62.104-9.792-92.33-2.005-8.749 2.468-17.273 6.211-24.38 11.956z" fill="url(#c)"/></svg>',
|
||||||
};
|
};
|
||||||
|
|
||||||
late final Size _size;
|
|
||||||
|
|
||||||
@override
|
|
||||||
initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_size = widget.size ?? const Size(30.0, 20.0);
|
_size = widget.size ?? const Size(30.0, 20.0);
|
||||||
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
+809
-499
File diff suppressed because it is too large
Load Diff
+2
-3
@@ -1,11 +1,10 @@
|
|||||||
name: stripe_native_card_field
|
name: stripe_native_card_field
|
||||||
description: A native flutter implementation of the elegant Stripe Card Field.
|
description: A native flutter implementation of the elegant Stripe Card Field.
|
||||||
version: 0.0.3
|
version: 0.0.10
|
||||||
repository: https://git.fosscat.com/n8r/stripe_native_card_field
|
repository: https://git.fosscat.com/n8r/stripe_native_card_field
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.3 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
flutter: ">=1.17.0"
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 924 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 56 MiB |
@@ -1,111 +0,0 @@
|
|||||||
---
|
|
||||||
author: Nathan Anderson
|
|
||||||
date: MMMM dd, YYYY
|
|
||||||
paging: Slide %d / %d
|
|
||||||
---
|
|
||||||
|
|
||||||
# Steps to Publish
|
|
||||||
|
|
||||||
1. Initialize Library
|
|
||||||
2. Write Library
|
|
||||||
4. Write some tests
|
|
||||||
5. Publish!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 1. Initialize Your Library!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Publishing Requirements
|
|
||||||
|
|
||||||
- You must include a LICENSE file.
|
|
||||||
- Your package must be smaller than 100 MB after gzip compression.
|
|
||||||
- Your package should depend only on hosted dependencies (from the default pub
|
|
||||||
package server) and SDK dependencies (sdk: flutter).
|
|
||||||
- You must have a Google Account,
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Publishing is Forever
|
|
||||||
|
|
||||||
To make sure projects don't break after using a dependency, you are unable to
|
|
||||||
take down a published project.
|
|
||||||
|
|
||||||
If you will have regrets publishing, turn back now.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Initialize Project
|
|
||||||
|
|
||||||
Creating a flutter library is straightforward, simply run the command
|
|
||||||
|
|
||||||
```sh
|
|
||||||
flutter create -template=package <package_name>
|
|
||||||
```
|
|
||||||
|
|
||||||
Creates a new flutter project with a simple example library inside.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Select License
|
|
||||||
|
|
||||||
This is important. The dart team recommends the BSD-3 clause license.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## pubspec.yaml Considerations
|
|
||||||
|
|
||||||
In the `pubspec.yaml` file, it is recommended to include a "homepage" or "repository"
|
|
||||||
field. This gets popultated into the package page on [pub.dev](https://pub.dev).
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Top of pubspec.yaml file
|
|
||||||
name: stripe_native_card_field
|
|
||||||
description: A native flutter implementation of Stripes Card Field.
|
|
||||||
version: 0.0.1
|
|
||||||
repository: https://git.fosscat.com/nathananderson98/stripe_native_card_field
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 2. Write Your Library
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Important Bits
|
|
||||||
|
|
||||||
Be sure to include a `README.md` file at the root of your library as this is
|
|
||||||
what determines the content of your packages page on [pub.dev](https://pub.dev)
|
|
||||||
|
|
||||||
A `CHANGELOG.md` file can also be included and will fill out a tab on the
|
|
||||||
package's page
|
|
||||||
|
|
||||||
### Verified Publisher
|
|
||||||
|
|
||||||
You can publish under a verified domain as a "Verified Publisher". Its a bit of
|
|
||||||
a process. But it adds a cool checkmark on your package and you can hide your email.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
Its a good idea to include some Unit Tests and Widget Tests for your library.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 3. Publishing!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dry Run
|
|
||||||
|
|
||||||
To see the results of what publishing will look like without going through with it run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
dart pub publish --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helpful Links
|
|
||||||
|
|
||||||
[Dart Publishing Guide](https://dart.dev/tools/pub/publishing)
|
|
||||||
@@ -18,13 +18,14 @@ void main() {
|
|||||||
CardDetails? details;
|
CardDetails? details;
|
||||||
final cardField = CardTextField(
|
final cardField = CardTextField(
|
||||||
width: width,
|
width: width,
|
||||||
onCardDetailsComplete: (cd) => details = cd,
|
onValidCardDetails: (cd) => details = cd,
|
||||||
);
|
);
|
||||||
await tester.pumpWidget(baseCardFieldWidget(cardField));
|
await tester.pumpWidget(baseCardFieldWidget(cardField));
|
||||||
|
|
||||||
final input = TestTextInput();
|
final input = TestTextInput();
|
||||||
|
|
||||||
final cardState = tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
final cardState =
|
||||||
|
tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
||||||
|
|
||||||
assertEmptyTextFields(tester, cardState.isWideFormat);
|
assertEmptyTextFields(tester, cardState.isWideFormat);
|
||||||
|
|
||||||
@@ -47,7 +48,8 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(getTextFormField(expirationFieldKey).controller?.text, '');
|
expect(getTextFormField(expirationFieldKey).controller?.text, '');
|
||||||
expect(getTextFormField(cardFieldKey).controller?.text, '4242 4242 4242 424');
|
expect(getTextFormField(cardFieldKey).controller?.text,
|
||||||
|
'4242 4242 4242 424');
|
||||||
expect(cardState.cardNumberFocusNode.hasFocus, true);
|
expect(cardState.cardNumberFocusNode.hasFocus, true);
|
||||||
expect(cardState.expirationFocusNode.hasFocus, false);
|
expect(cardState.expirationFocusNode.hasFocus, false);
|
||||||
// Postal code should now be gone
|
// Postal code should now be gone
|
||||||
@@ -58,7 +60,8 @@ void main() {
|
|||||||
input.enterText("4242424242424242");
|
input.enterText("4242424242424242");
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(getTextFormField(cardFieldKey).controller?.text, '4242 4242 4242 4242');
|
expect(getTextFormField(cardFieldKey).controller?.text,
|
||||||
|
'4242 4242 4242 4242');
|
||||||
expect(cardState.cardNumberFocusNode.hasFocus, false);
|
expect(cardState.cardNumberFocusNode.hasFocus, false);
|
||||||
expect(cardState.expirationFocusNode.hasFocus, true);
|
expect(cardState.expirationFocusNode.hasFocus, true);
|
||||||
// Postal code should move back into view
|
// Postal code should move back into view
|
||||||
@@ -90,7 +93,10 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
final expectedCardDetails = CardDetails(
|
final expectedCardDetails = CardDetails(
|
||||||
cardNumber: '4242 4242 4242 4242', securityCode: '333', expirationString: '10/28', postalCode: '91555');
|
cardNumber: '4242 4242 4242 4242',
|
||||||
|
securityCode: '333',
|
||||||
|
expirationString: '10/28',
|
||||||
|
postalCode: '91555');
|
||||||
// print('${expectedCardDetails.toString()}\n${details?.toString()}');
|
// print('${expectedCardDetails.toString()}\n${details?.toString()}');
|
||||||
expect(details?.hash, expectedCardDetails.hash);
|
expect(details?.hash, expectedCardDetails.hash);
|
||||||
},
|
},
|
||||||
@@ -104,13 +110,14 @@ void main() {
|
|||||||
|
|
||||||
final cardField = CardTextField(
|
final cardField = CardTextField(
|
||||||
width: width,
|
width: width,
|
||||||
onCardDetailsComplete: (cd) => details = cd,
|
onValidCardDetails: (cd) => details = cd,
|
||||||
);
|
);
|
||||||
await tester.pumpWidget(baseCardFieldWidget(cardField));
|
await tester.pumpWidget(baseCardFieldWidget(cardField));
|
||||||
|
|
||||||
final input = TestTextInput();
|
final input = TestTextInput();
|
||||||
|
|
||||||
final cardState = tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
final cardState =
|
||||||
|
tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
||||||
|
|
||||||
assertEmptyTextFields(tester, cardState.isWideFormat);
|
assertEmptyTextFields(tester, cardState.isWideFormat);
|
||||||
|
|
||||||
@@ -135,7 +142,8 @@ void main() {
|
|||||||
input.enterText('0055');
|
input.enterText('0055');
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text("Your card's expiration month is invalid."), findsOneWidget);
|
expect(
|
||||||
|
find.text("Your card's expiration month is invalid."), findsOneWidget);
|
||||||
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@@ -145,7 +153,8 @@ void main() {
|
|||||||
input.enterText('1099');
|
input.enterText('1099');
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text("Your card's expiration year is invalid."), findsOneWidget);
|
expect(
|
||||||
|
find.text("Your card's expiration year is invalid."), findsOneWidget);
|
||||||
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@@ -169,7 +178,8 @@ void main() {
|
|||||||
await input.receiveAction(TextInputAction.done);
|
await input.receiveAction(TextInputAction.done);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text("The postal code you entered is not correct."), findsOneWidget);
|
expect(find.text("The postal code you entered is not correct."),
|
||||||
|
findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.byType(CardTextField));
|
await tester.tap(find.byType(CardTextField));
|
||||||
|
|
||||||
@@ -182,7 +192,10 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
final expectedCardDetails = CardDetails(
|
final expectedCardDetails = CardDetails(
|
||||||
cardNumber: '4242 4242 4242 4242', expirationString: '02/28', securityCode: '123', postalCode: '12345');
|
cardNumber: '4242 4242 4242 4242',
|
||||||
|
expirationString: '02/28',
|
||||||
|
securityCode: '123',
|
||||||
|
postalCode: '12345');
|
||||||
|
|
||||||
expect(details?.hash, expectedCardDetails.hash);
|
expect(details?.hash, expectedCardDetails.hash);
|
||||||
});
|
});
|
||||||
@@ -209,7 +222,8 @@ void assertEmptyTextFields(WidgetTester tester, bool isWideFormat) {
|
|||||||
// expect(find.text("Postal Code"), findsNothing);
|
// expect(find.text("Postal Code"), findsNothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> enterTextByKey(WidgetTester tester, {required String key, required String text}) async {
|
Future<void> enterTextByKey(WidgetTester tester,
|
||||||
|
{required String key, required String text}) async {
|
||||||
await tester.enterText(find.byKey(ValueKey(key)), text);
|
await tester.enterText(find.byKey(ValueKey(key)), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user