Skip to content
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
1 change: 1 addition & 0 deletions lib/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ part 'src/messages/requests/create_refund.dart';
part 'src/messages/requests/create_subscription_schedule.dart';
part 'src/messages/requests/list_prices.dart';
part 'src/messages/requests/list_products.dart';
part 'src/messages/requests/list_promotion_codes.dart';
part 'src/messages/requests/list_subscription_items.dart';
part 'src/messages/requests/list_subscription_schedules.dart';
part 'src/messages/requests/list_subscriptions.dart';
Expand Down
85 changes: 85 additions & 0 deletions lib/messages.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 62 additions & 30 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:stripe/messages.dart';
import 'package:stripe/src/exceptions.dart';

const _defaultUrl = 'https://api.stripe.com/v1/';
Expand Down Expand Up @@ -30,6 +31,49 @@ abstract class Client {
String? idempotencyKey,
Map<String, dynamic>? queryParameters,
});

@protected
Map<String, dynamic> processResponse({
required int? statusCode,
required Object? data,
}) {
if (statusCode != 200) {
if (data == null ||
data is! Map<String, dynamic> ||
data['error'] == null) {
throw InvalidRequestException(
'The status code returned was $statusCode but no error was provided.',
statusCode: statusCode,
);
}
final errorJson = data['error'] as Map<String, dynamic>;
final error = StripeApiError.fromJson(errorJson);

switch (error.type) {
case StripeApiErrorType.invalidRequestError:
throw InvalidRequestException(
error.message.toString(),
statusCode: statusCode,
error: error,
);
default:
throw UnknownTypeException(
'The status code returned was $statusCode but the error '
'type is unknown.',
statusCode: statusCode,
error: error,
);
}
}
if (data == null || data is! Map<String, dynamic>) {
throw InvalidRequestException(
'The JSON returned was unparsable ($data).',
statusCode: statusCode,
);
}

return data;
}
}

/// The http client implementation that will make requests to the stripe API.
Expand Down Expand Up @@ -87,13 +131,16 @@ class DioClient extends Client {
final response = await dio.post<Map<String, dynamic>>(path,
data: data,
options: _createRequestOptions(idempotencyKey: idempotencyKey));
return processResponse(response);
return _processDioResponse(response);
} on DioException catch (e) {
var message = e.message ?? '';
if (e.response?.data != null) {
message += '${e.response!.data}';
}
throw InvalidRequestException(message);
throw InvalidRequestException(
message,
statusCode: e.response?.statusCode,
);
}
}

Expand All @@ -108,13 +155,16 @@ class DioClient extends Client {
final response = await dio.delete<Map<String, dynamic>>(path,
data: data,
options: _createRequestOptions(idempotencyKey: idempotencyKey));
return processResponse(response);
return _processDioResponse(response);
} on DioException catch (e) {
var message = e.message ?? '';
if (e.response?.data != null) {
message += '${e.response!.data}';
}
throw InvalidRequestException(message);
throw InvalidRequestException(
message,
statusCode: e.response?.statusCode,
);
}
}

Expand All @@ -130,39 +180,21 @@ class DioClient extends Client {
queryParameters: queryParameters,
options: _createRequestOptions(idempotencyKey: idempotencyKey),
);
return processResponse(response);
return _processDioResponse(response);
}

Options? _createRequestOptions({String? idempotencyKey}) =>
idempotencyKey == null
? null
: Options(headers: {'Idempotency-Key': idempotencyKey});

Map<String, dynamic> processResponse(
Response<Map<String, dynamic>> response) {
final responseStatusCode = response.statusCode;

final data = response.data;

if (responseStatusCode != 200) {
if (data == null || data['error'] == null) {
throw InvalidRequestException(
'The status code returned was $responseStatusCode but no error was provided.');
}
final error = data['error'] as Map;
switch (error['type'].toString()) {
case 'invalid_request_error':
throw InvalidRequestException(error['message'].toString());
default:
throw UnknownTypeException(
'The status code returned was $responseStatusCode but the error type is unknown.');
}
}
if (data == null) {
throw InvalidRequestException(
'The JSON returned was unparsable (${response.data}).');
}
return data;
Map<String, dynamic> _processDioResponse(
Response<Map<String, dynamic>> response,
) {
return processResponse(
statusCode: response.statusCode,
data: response.data,
);
}
}

Expand Down
30 changes: 25 additions & 5 deletions lib/src/exceptions.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
import 'package:stripe/messages.dart';

/// Exceptions thrown by Stripe
abstract class StripeException implements Exception {
final String message;

StripeException(this.message);
final int? statusCode;
final StripeApiError? error;

StripeException(
this.message, {
this.statusCode,
this.error,
});
}

/// Invalid request errors arise when your request has invalid parameters.
class InvalidRequestException extends StripeException {
InvalidRequestException(String message) : super(message);
InvalidRequestException(
String message, {
int? statusCode,
StripeApiError? error,
}) : super(message, statusCode: statusCode, error: error);

@override
String toString() => 'Invalid request: $message.';
}

/// For all API error responses where the type is unknown or not provided.
class UnknownTypeException extends StripeException {
UnknownTypeException(String message) : super(message);
UnknownTypeException(
String message, {
int? statusCode,
StripeApiError? error,
}) : super(message, statusCode: statusCode, error: error);

@override
String toString() => 'Invalid type: $message.';
}

/// Invalid resource.
class InvalidResourceException extends StripeException {
InvalidResourceException(String message) : super(message);
InvalidResourceException(
String message, {
int? statusCode,
StripeApiError? error,
}) : super(message, statusCode: statusCode, error: error);

@override
String toString() => 'Invalid resource: $message.';
Expand Down
1 change: 1 addition & 0 deletions lib/src/expanded.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'expanded/subscription_expanded.dart';
11 changes: 11 additions & 0 deletions lib/src/expanded/subscription_expanded.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:stripe/messages.dart';

class SubscriptionExpanded {
final Subscription subscription;
final List<Discount>? discounts;

SubscriptionExpanded({
required this.subscription,
this.discounts,
});
}
1 change: 1 addition & 0 deletions lib/src/messages/enums.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'enums/expandable_fields/subscription_expandable_field.dart';
export 'enums/payment_behavior.dart';
export 'enums/proration_behavior.dart';
export 'enums/stripe_api_error_type.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
enum SubscriptionExpandableField {
discounts,
}
27 changes: 27 additions & 0 deletions lib/src/messages/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ abstract class Event<T extends Message> extends Message {
return PaymentMethodEvent.fromJson(json) as T;
case 'invoice':
return InvoiceEvent.fromJson(json) as T;
case 'coupon':
return CouponEvent.fromJson(json) as T;
default:
throw FormatException(
'Unrecognized/unsupported Stripe object `${json['object']}` in event webhook');
Expand Down Expand Up @@ -290,3 +292,28 @@ class InvoiceEvent extends Event<Invoice> {
@override
Map<String, dynamic> toJson() => _$InvoiceEventToJson(this);
}

@JsonSerializable()
class CouponEvent extends Event<Coupon> {
CouponEvent({
required _EventObject object,
required String id,
required int created,
required String type,
required EventData<Coupon> data,
required bool livemode,
}) : super(
object: object,
id: id,
created: created,
data: data,
type: type,
livemode: livemode,
);

factory CouponEvent.fromJson(Map<String, dynamic> json) =>
_$CouponEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$CouponEventToJson(this);
}
Loading