Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement create token method #165

Merged
merged 4 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class _HomePageState extends State<HomePage> {
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Column(children: [
body: ListView(children: [
...ListTile.divideTiles(
context: context,
tiles: [
Expand Down
72 changes: 72 additions & 0 deletions example/lib/screens/legacy_token_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:stripe_example/widgets/loading_button.dart';
import 'package:stripe_platform_interface/stripe_platform_interface.dart';

class LegacyTokenScreen extends StatefulWidget {
@override
_LegacyTokenScreenState createState() => _LegacyTokenScreenState();
}

class _LegacyTokenScreenState extends State<LegacyTokenScreen> {
CardFieldInputDetails? _card;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: CardField(
onCardChanged: (card) {
setState(() {
_card = card;
});
},
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: LoadingButton(
onPressed: _card?.complete == true ? _handleCreateTokenPress : null,
text: 'Create token',
),
),
],
),
);
}

Future<void> _handleCreateTokenPress() async {
if (_card == null) {
return;
}

try {
// 1. Gather customer billing information (ex. email)

final address = Address(
city: 'Houston',
country: 'US',
line1: '1459 Circle Drive',
line2: '',
state: 'Texas',
postalCode: '77063',
); // mocked data for tests

// 2. Create payment method
final tokenData = await Stripe.instance.createToken(
CreateTokenParams(type: TokenType.Card, address: address));

ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Success!: The token was created successfully}!')));
return;
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Error: $e')));
rethrow;
}
}
}
5 changes: 5 additions & 0 deletions example/lib/screens/screens.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../screens/no_webhook_payment_screen.dart';
import '../screens/setup_future_payment_screen.dart';
import '../screens/webhook_payment_screen.dart';
import 'cvc_re_collection_screen.dart';
import 'legacy_token_screen.dart';
import 'payment_sheet_screen.dart';
import 'themes.dart';

Expand Down Expand Up @@ -51,5 +52,9 @@ class Example {
title: 'Payment sheet',
builder: (context) => PaymentSheetScreen(),
),
Example(
title: 'Create token (legacy)',
builder: (context)=> LegacyTokenScreen(),
)
];
}
2 changes: 1 addition & 1 deletion example/lib/screens/setup_future_payment_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class _SetupFuturePaymentScreenState extends State<SetupFuturePaymentScreen> {
await Stripe.instance.confirmPaymentMethod(
_retrievedPaymentIntent!.clientSecret,
PaymentMethodParams.cardFromMethodId(
paymentMethodId: _retrievedPaymentIntent!.paymentMethodId),
paymentMethodId: _retrievedPaymentIntent!.paymentMethodId!),
);
}
}
Expand Down
18 changes: 17 additions & 1 deletion packages/stripe/lib/src/stripe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ class Stripe {
}
}

/// Creates a single-use token that represents a credit card’s details.
///
/// Tokens are considered legacy, use [PaymentMethod] and [PaymentIntent]
/// instead.
/// /// Throws an [StripeError] in case createToken fails.

Future<TokenData> createToken(CreateTokenParams params) async {
await _awaitForSettings();
try {
final tokenData = await _platform.createToken(params);
return tokenData;
} on StripeError catch (error) {
throw StripeError(message: error.message, code: error.message);
}
}

/// Retrieves a [PaymentIntent] using the provided [clientSecret].
///
/// Throws an [StripeError] in case retrieving the intent fails.
Expand Down Expand Up @@ -312,7 +328,7 @@ class Stripe {
ThreeDSecureConfigurationParams? threeDSecureParams,
String? merchantIdentifier,
}) async {
_needsSettings = false;
_needsSettings = false;
await _platform.initialise(
publishableKey: publishableKey,
stripeAccountId: stripeAccountId,
Expand Down
13 changes: 13 additions & 0 deletions packages/stripe_ios/ios/Classes/StripePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public class StripePlugin: StripeSdk, FlutterPlugin {
return retrievePaymentIntent(call, result: result)
case "createPaymentMethod":
return createPaymentMethod(call, result: result)
case "createToken":
return createToken(call, result: result)
default:
result(FlutterMethodNotImplemented)
}
Expand Down Expand Up @@ -284,4 +286,15 @@ extension StripePlugin {
result(nil)
}

public func createToken(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let arguments = call.arguments as? FlutterMap,
let params = arguments["params"] as? NSDictionary else {
result(FlutterError.invalidParams)
return
}
createToken(params: params,
resolver: resolver(for: result),
rejecter: rejecter(for: result))
}

}
2 changes: 1 addition & 1 deletion packages/stripe_ios/ios/Classes/StripeSdk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ public class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSele

STPAPIClient.shared.createToken(withCard: cardSourceParams) { token, error in
if let token = token {
resolve(Mappers.createResult("token", Mappers.mapFromToken(token: token)))
resolve(Mappers.mapFromToken(token: token))
} else {
resolve(Errors.createError(CreateTokenErrorType.Failed.rawValue, error?.localizedDescription))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:stripe_platform_interface/src/models/create_token_data.dart';

import 'models/app_info.dart';
import 'models/apple_pay.dart';
Expand Down Expand Up @@ -56,7 +57,7 @@ class MethodChannelStripe extends StripePlatform {
'options': options,
});

final tmp = result?['paymentMethod'] as Map<String, dynamic>;
final tmp = result?['paymentMethod'] as Map<String, dynamic>;

return PaymentMethod.fromJson(
tmp.unfoldToNonNull(),
Expand Down Expand Up @@ -199,6 +200,21 @@ class MethodChannelStripe extends StripePlatform {
Future<void> confirmPaymentSheetPayment() async {
await _methodChannel.invokeMethod('confirmPaymentSheetPayment');
}

@override
Future<TokenData> createToken(CreateTokenParams params) async {
try {
final result = await _methodChannel.invokeMapMethod<String, dynamic>(
'createToken', {'params': params.toJson()});

return TokenData.fromJson(result.unfoldToNonNull());
} on Exception catch (e) {
throw StripeError<CreateTokenError>(
code: CreateTokenError.unknown,
message: 'Create token failed with exception: $e',
);
}
}
}

class MethodChannelStripeFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,19 @@ class CardFieldFocusName with _$CardFieldFocusName {
/// Enum representing the different fiels on the card field.
enum CardFieldName {
@JsonValue('CardNumber')

/// Card number field.
cardNumber,
@JsonValue('Cvc')

/// Cvc field.
cvc,
@JsonValue('ExpiryDate')

/// Expiry date field.
expiryDate,
@JsonValue('PostalCode')

/// Postal code field.
postalCode,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import 'package:freezed_annotation/freezed_annotation.dart';

import 'address.dart';

part 'create_token_data.freezed.dart';
part 'create_token_data.g.dart';
//ignore_for_file: constant_identifier_names

@freezed

/// parameters that are used to create a token.
///
/// At this moment only card tokens are supported.
class CreateTokenParams with _$CreateTokenParams {
const factory CreateTokenParams({
/// Type of token.
@Default(TokenType.Card) TokenType type,

/// Additional address details
Address? address,
}) = _CreateTokenParams;

factory CreateTokenParams.fromJson(Map<String, dynamic> json) =>
_$CreateTokenParamsFromJson(json);
}

@freezed

/// Data that provides information about the token
class TokenData with _$TokenData {
const factory TokenData({
/// Unique identifier of the token
required String id,

/// Timestamp when token was created
@JsonKey(name: 'created') required String createdDateTime,

/// Type of the token
required TokenType type,

/// Whether or not the object exists in livemode
required bool livemode,

/// Bank account data
BankAccount? bankAccount,

/// Card data
CardData? card,
}) = _TokenData;

factory TokenData.fromJson(Map<String, dynamic> json) =>
_$TokenDataFromJson(json);
}

@freezed

/// Card data associated with the token
class CardData with _$CardData {
@JsonSerializable(explicitToJson: true)
const factory CardData({
/// The brand associated to the card e.g. (visa, amex).
required String brand,

/// Two letter iso code.
String? country,

/// The three letter ISO 4217 code for the currency.
String? currency,

/// four digit number representing the year of expiry of the card.
int? expYear,

/// two digit number representing the month of expire of the card.
int? expMonth,

/// Fullname of the cardholder
String? name,

/// card funding type e.g. (credit, debit).
String? funding,

/// last four digits of the card.
String? last4,

/// Address of the cardholder
Address? address,
}) = _CardData;

factory CardData.fromJson(Map<String, dynamic> json) =>
_$CardDataFromJson(json);
}

@freezed

/// Bank account data related to the token
class BankAccount with _$BankAccount {
const factory BankAccount({
/// Entity that is holder of the account.
required BankAccountHolderType accountHolderType,

/// Status of the bank account.
required BankAccountStatus status,

/// Name of the bank where the account is registered.
String? bankName,

/// Full name of the account holder
String? accountHolderName,

/// 2 letter code of the country where the account is located
String? country,

/// The three letter ISO 4217 code for the currency.
String? currency,

/// The routing number of the bank account (e.g. needer for US accounts).
String? routingNumber,
}) = _BankAccount;

factory BankAccount.fromJson(Map<String, dynamic> json) =>
_$BankAccountFromJson(json);
}

/// Entity that is holder of the account
enum BankAccountHolderType {
/// Company.
Company,

/// Individual.
Individual,

/// Entity cannot be determined.
Unknown,
}

/// Verfication status of the bankaccount.
enum BankAccountStatus {
/// Validation has some errors
Errored,

/// Seen as a new bankaccount
New,

/// Bankaccount is validated
Validated,

/// Failed to verify bankaccount
VerificationFailed,

/// Bankaccount is verified
Verified,

/// Status cannot be determined
Unknown
}

/// Type of token
enum TokenType { Card }
Loading