final2
This commit is contained in:
140
lib/utils/mini_stripe_api.dart
Normal file
140
lib/utils/mini_stripe_api.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
|
||||
|
||||
import '../vendor/stripe_sdk/lib/stripe_sdk.dart';
|
||||
import '../vendor/stripe_sdk/lib/stripe_sdk_ui.dart';
|
||||
|
||||
import 'mini_stripe_api_handler.dart';
|
||||
|
||||
class MiniStripeApi {
|
||||
static MiniStripeApi? _instance;
|
||||
|
||||
final MiniStripeApiHandler _apiHandler;
|
||||
|
||||
final String publishableKey;
|
||||
|
||||
final String apiVersion;
|
||||
|
||||
/// Create a new instance, which can be used with e.g. dependency injection.
|
||||
/// Throws a [Exception] if an invalid [publishableKey] has been submitted.
|
||||
///
|
||||
/// [publishableKey] is your publishable key, beginning with "sk_".
|
||||
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||
///
|
||||
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||
/// This is a optional parameter.
|
||||
MiniStripeApi(this.publishableKey, {this.apiVersion = defaultApiVersion, String? stripeAccount})
|
||||
: _apiHandler = MiniStripeApiHandler(stripeAccount: stripeAccount) {
|
||||
_validateKey(publishableKey);
|
||||
_apiHandler.apiVersion = apiVersion;
|
||||
}
|
||||
|
||||
/// Initialize the managed singleton instance of [StripeApi].
|
||||
/// Afterwards you can use [StripeApi.instance] to access the created instance.
|
||||
///
|
||||
/// [publishableKey] is your publishable key, beginning with "sk_".
|
||||
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||
///
|
||||
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||
/// This is a optional parameter.
|
||||
static void init(String publishableKey, {String apiVersion = defaultApiVersion, String? stripeAccount}) {
|
||||
// _instance ??= MiniStripeApi(publishableKey, uuidString, apiVersion: apiVersion, stripeAccount: stripeAccount);
|
||||
_instance = MiniStripeApi(publishableKey, apiVersion: apiVersion, stripeAccount: stripeAccount);
|
||||
}
|
||||
|
||||
/// Access the singleton instance of [StripeApi].
|
||||
/// Throws an [Exception] if [StripeApi.init] hasn't been called previously.
|
||||
static MiniStripeApi get instance {
|
||||
if (_instance == null) {
|
||||
throw Exception('Attempted to get singleton instance of StripeApi without initialization');
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
/// Create a stripe Token
|
||||
/// https://stripe.com/docs/api/tokens
|
||||
Future<Map<String, dynamic>> createToken(Map<String, dynamic> data) async {
|
||||
final path = '/tokens';
|
||||
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||
}
|
||||
|
||||
/// Create a PaymentMethod.
|
||||
/// https://stripe.com/docs/api/payment_methods/create
|
||||
Future<Map<String, dynamic>> createPaymentMethod(Map<String, dynamic> data) async {
|
||||
final path = '/payment_methods';
|
||||
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||
}
|
||||
|
||||
/// Create a PaymentMethod from a card.
|
||||
/// This will only create a PaymentMethod with the minimum required properties.
|
||||
/// To include additional properties such as billing details, use [StripeCard.toPaymentMethod], add additional details
|
||||
/// and then use [createPaymentMethod].
|
||||
Future<Map<String, dynamic>> createPaymentMethodFromCard(StripeCard card) async {
|
||||
return createPaymentMethod(card.toPaymentMethod());
|
||||
}
|
||||
|
||||
/// Create a new Source object.
|
||||
/// https://stripe.com/docs/api/sources/create
|
||||
Future<Map<String, dynamic>> createSource(Map<String, dynamic> data) async {
|
||||
final path = '/sources';
|
||||
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||
}
|
||||
|
||||
/// Retrieve a PaymentIntent.
|
||||
/// https://stripe.com/docs/api/payment_intents/retrieve
|
||||
Future<Map<String, dynamic>> retrievePaymentIntent(String clientSecret, {String? apiVersion}) async {
|
||||
final intentId = _parseIdFromClientSecret(clientSecret);
|
||||
final path = '/payment_intents/$intentId';
|
||||
final params = {'client_secret': clientSecret};
|
||||
return _apiHandler.request(RequestMethod.get, path, publishableKey, apiVersion ?? this.apiVersion, params: params);
|
||||
}
|
||||
|
||||
/// Confirm a PaymentIntent
|
||||
/// https://stripe.com/docs/api/payment_intents/confirm
|
||||
Future<Map<String, dynamic>> confirmPaymentIntent(String clientSecret, {Map<String, dynamic>? data, String? idempotencyKey}) async {
|
||||
final params = data ?? {};
|
||||
final intent = _parseIdFromClientSecret(clientSecret);
|
||||
params['client_secret'] = clientSecret;
|
||||
final path = '/payment_intents/$intent/confirm';
|
||||
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: params, idempotencyKey: idempotencyKey);
|
||||
}
|
||||
|
||||
/// Retrieve a SetupIntent.
|
||||
/// https://stripe.com/docs/api/setup_intents/retrieve
|
||||
Future<Map<String, dynamic>> retrieveSetupIntent(String clientSecret, {String? apiVersion}) async {
|
||||
final intentId = _parseIdFromClientSecret(clientSecret);
|
||||
final path = '/setup_intents/$intentId';
|
||||
final params = {'client_secret': clientSecret};
|
||||
return _apiHandler.request(RequestMethod.get, path, publishableKey, apiVersion, params: params);
|
||||
}
|
||||
|
||||
/// Confirm a SetupIntent
|
||||
/// https://stripe.com/docs/api/setup_intents/confirm
|
||||
Future<Map<String, dynamic>> confirmSetupIntent(String clientSecret, {Map<String, dynamic>? data}) async {
|
||||
final params = data ?? {};
|
||||
final intent = _parseIdFromClientSecret(clientSecret);
|
||||
params['client_secret'] = clientSecret;
|
||||
final path = '/setup_intents/$intent/confirm';
|
||||
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: params);
|
||||
}
|
||||
|
||||
/// Validates the received [publishableKey] and throws a [Exception] if an
|
||||
/// invalid key has been submitted.
|
||||
static void _validateKey(String publishableKey) {
|
||||
if (publishableKey == null || publishableKey.isEmpty) {
|
||||
throw Exception('Invalid Publishable Key: '
|
||||
'You must use a valid publishable key to create a token. '
|
||||
'For more info, see https://stripe.com/docs/stripe.js.');
|
||||
}
|
||||
|
||||
if (publishableKey.startsWith('sk_')) {
|
||||
throw Exception('Invalid Publishable Key: '
|
||||
'You are using a secret key to create a token, '
|
||||
'instead of the publishable one. For more info, '
|
||||
'see https://stripe.com/docs/stripe.js');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _parseIdFromClientSecret(String clientSecret) {
|
||||
return clientSecret.split('_secret')[0];
|
||||
}
|
||||
164
lib/utils/mini_stripe_api_handler.dart
Normal file
164
lib/utils/mini_stripe_api_handler.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert' show json;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../vendor/stripe_sdk/lib/stripe_sdk.dart';
|
||||
|
||||
class MiniStripeApiHandler {
|
||||
static const String LIVE_API_BASE = 'https://api.stripe.com';
|
||||
static const String LIVE_LOGGING_BASE = 'https://q.stripe.com';
|
||||
static const String LOGGING_ENDPOINT = 'https://m.stripe.com/4';
|
||||
static const String LIVE_API_PATH = LIVE_API_BASE + '/v1';
|
||||
|
||||
static const String CHARSET = 'UTF-8';
|
||||
static const String CUSTOMERS = 'customers';
|
||||
static const String TOKENS = 'tokens';
|
||||
static const String SOURCES = 'sources';
|
||||
|
||||
static const String HEADER_KEY_REQUEST_ID = 'Request-Id';
|
||||
static const String FIELD_ERROR = 'error';
|
||||
static const String FIELD_SOURCE = 'source';
|
||||
|
||||
String apiVersion = defaultApiVersion;
|
||||
|
||||
static const String MALFORMED_RESPONSE_MESSAGE = 'An improperly formatted error response was found.';
|
||||
|
||||
final http.Client _client = http.Client();
|
||||
|
||||
final String? stripeAccount;
|
||||
|
||||
MiniStripeApiHandler({this.stripeAccount});
|
||||
|
||||
Future<Map<String, dynamic>> request(RequestMethod method, String path, String key, String? apiVersion,
|
||||
{final Map<String, dynamic>? params, String? idempotencyKey}) {
|
||||
final options = RequestOptions(key: key, apiVersion: apiVersion, stripeAccount: stripeAccount, idempotencyKey: idempotencyKey);
|
||||
return _getStripeResponse(method, LIVE_API_PATH + path, options, params: params);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _getStripeResponse(RequestMethod method, final String url, final RequestOptions options,
|
||||
{final Map<String, dynamic>? params}) async {
|
||||
print('url: $url');
|
||||
final headers = _headers(options: options);
|
||||
|
||||
print('headers: $headers');
|
||||
http.Response response;
|
||||
|
||||
switch (method) {
|
||||
case RequestMethod.get:
|
||||
var fUrl = url;
|
||||
if (params != null && params.isNotEmpty) {
|
||||
fUrl = '$url?${_encodeMap(params)}';
|
||||
}
|
||||
response = await _client.get(Uri.parse(fUrl), headers: headers);
|
||||
break;
|
||||
|
||||
case RequestMethod.post:
|
||||
response = await _client.post(
|
||||
Uri.parse(url),
|
||||
headers: headers,
|
||||
body: params != null ? _urlEncodeMap(params) : null,
|
||||
);
|
||||
break;
|
||||
|
||||
case RequestMethod.delete:
|
||||
response = await _client.delete(Uri.parse(url), headers: headers);
|
||||
break;
|
||||
default:
|
||||
throw Exception('Request Method: $method not implemented');
|
||||
}
|
||||
|
||||
final requestId = response.headers[HEADER_KEY_REQUEST_ID];
|
||||
|
||||
final statusCode = response.statusCode;
|
||||
Map<String, dynamic> resp;
|
||||
try {
|
||||
resp = json.decode(response.body);
|
||||
} catch (error) {
|
||||
final stripeError = StripeApiError(requestId, {StripeApiError.fieldMessage: MALFORMED_RESPONSE_MESSAGE});
|
||||
throw StripeApiException(stripeError);
|
||||
}
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
final Map<String, dynamic> errBody = resp[FIELD_ERROR];
|
||||
final stripeError = StripeApiError(requestId, errBody);
|
||||
throw StripeApiException(stripeError);
|
||||
} else {
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
static Map<String, String> _headers({RequestOptions? options}) {
|
||||
final headers = <String, String>{};
|
||||
headers['Accept-Charset'] = CHARSET;
|
||||
headers['Accept'] = 'application/json';
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
headers['User-Agent'] = 'StripeSDK/v2';
|
||||
|
||||
if (options != null) {
|
||||
headers['Authorization'] = 'Bearer ${options.key}';
|
||||
}
|
||||
|
||||
// debug headers
|
||||
final propertyMap = <String, String>{};
|
||||
propertyMap['os.name'] = defaultTargetPlatform.toString();
|
||||
propertyMap['lang'] = 'Dart';
|
||||
propertyMap['publisher'] = 'lars.dahl@gmail.com';
|
||||
|
||||
headers['X-Stripe-Client-User-Agent'] = json.encode(propertyMap);
|
||||
|
||||
if (options != null) {
|
||||
if (options.apiVersion != null) {
|
||||
headers['Stripe-Version'] = options.apiVersion!;
|
||||
}
|
||||
|
||||
if (options.stripeAccount != null) {
|
||||
headers['Stripe-Account'] = options.stripeAccount!;
|
||||
}
|
||||
|
||||
if (options.idempotencyKey != null) {
|
||||
headers['Idempotency-Key'] = options.idempotencyKey!;
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
static String _encodeMap(Map<String, dynamic> params) {
|
||||
return params.keys
|
||||
.map((key) => '${Uri.encodeComponent(key)}=${Uri.encodeComponent(params[key].toString())}')
|
||||
.join('&');
|
||||
}
|
||||
|
||||
static String _urlEncodeMap(dynamic data) {
|
||||
final urlData = StringBuffer('');
|
||||
var first = true;
|
||||
void urlEncode(dynamic sub, String path) {
|
||||
if (sub is List) {
|
||||
for (var i = 0; i < sub.length; i++) {
|
||||
urlEncode(sub[i], '$path%5B%5D');
|
||||
}
|
||||
} else if (sub is Map) {
|
||||
sub.forEach((k, v) {
|
||||
if (path == '') {
|
||||
urlEncode(v, '${Uri.encodeQueryComponent(k)}');
|
||||
} else {
|
||||
urlEncode(v, '$path%5B${Uri.encodeQueryComponent(k)}%5D');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!first) {
|
||||
urlData.write('&');
|
||||
}
|
||||
first = false;
|
||||
urlData.write('$path=${Uri.encodeQueryComponent(sub.toString())}');
|
||||
}
|
||||
}
|
||||
|
||||
urlEncode(data, '');
|
||||
return urlData.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user