feat(stripe_api): add ability to fetch billing meters (#3372)

This commit is contained in:
Bryan Oltman
2025-11-07 14:41:49 -05:00
committed by GitHub
parent cdc7a2cdec
commit d74d4e1f89
7 changed files with 156 additions and 0 deletions
@@ -1,3 +1,4 @@
export 'stripe_billing_meter.dart';
export 'stripe_checkout_session.dart';
export 'stripe_customer.dart';
export 'stripe_event.dart';
@@ -0,0 +1,32 @@
import 'package:json_annotation/json_annotation.dart';
part 'stripe_billing_meter.g.dart';
/// {@template stripe_billing_meter}
/// A billing meter in Stripe.
///
/// See https://docs.stripe.com/api/billing/meter/object
/// {@endtemplate}
@JsonSerializable(createToJson: false)
class StripeBillingMeter {
/// {@macro stripe_billing_meter}
StripeBillingMeter({
required this.id,
required this.displayName,
required this.eventName,
});
/// Converts a JSON object to a [StripeBillingMeter].
factory StripeBillingMeter.fromJson(Map<String, dynamic> json) =>
_$StripeBillingMeterFromJson(json);
/// The unique identifier for this object.
final String id;
/// The meter's name.
final String displayName;
/// The name of the meter event to record usage for. Corresponds with the
/// event_name field on meter events.
final String eventName;
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars, unnecessary_lambdas
part of 'stripe_billing_meter.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
StripeBillingMeter _$StripeBillingMeterFromJson(Map<String, dynamic> json) =>
$checkedCreate(
'StripeBillingMeter',
json,
($checkedConvert) {
final val = StripeBillingMeter(
id: $checkedConvert('id', (v) => v as String),
displayName: $checkedConvert('display_name', (v) => v as String),
eventName: $checkedConvert('event_name', (v) => v as String),
);
return val;
},
fieldKeyMap: const {
'displayName': 'display_name',
'eventName': 'event_name',
},
);
@@ -65,6 +65,16 @@ class StripeApi {
);
}
/// Retrieves all [StripeBillingMeter]s associated with the Stripe account.
Future<List<StripeBillingMeter>> fetchActiveBillingMeters() async {
return _fetchAllPages(
path: 'billing/meters',
queryParameters: {'status': 'active'},
fromJson: StripeBillingMeter.fromJson,
getId: (e) => e.id,
);
}
/// Creates a new meter event for the given [customerId].
/// See https://docs.stripe.com/api/billing/meter-event
Future<void> createMeterEvent({
@@ -0,0 +1,31 @@
{
"object": "list",
"data": [
{
"id": "mtr_test_61QvSUDTnLya5cdwG41HSA9cXarIc144",
"object": "billing.meter",
"created": 1723254937,
"customer_mapping": {
"event_payload_key": "stripe_customer_id",
"type": "by_id"
},
"default_aggregation": {
"formula": "sum"
},
"display_name": "Patch Installs",
"event_name": "patch_installs",
"event_time_window": null,
"livemode": false,
"status": "active",
"status_transitions": {
"deactivated_at": null
},
"updated": 1723254937,
"value_settings": {
"event_payload_key": "value"
}
}
],
"has_more": true,
"url": "/v1/billing/meters"
}
@@ -3,6 +3,13 @@ import 'dart:io';
const stripeJsonPath = 'test/fixtures/stripe/json';
String get billingMetersPageJsonString => File(
'$stripeJsonPath/billing_meters_page.json',
).readAsStringSync();
Map<String, dynamic> get billingMetersPageJson =>
jsonDecode(billingMetersPageJsonString) as Map<String, dynamic>;
String get checkoutSessionCompletedEventJsonString => File(
'$stripeJsonPath/checkout_session_completed_event.json',
).readAsStringSync();
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
@@ -215,6 +216,53 @@ void main() {
);
});
group('fetchBillingMeters', () {
const meterId = 'mtr_test_61QvSUDTnLya5cdwG41HSA9cXarIc144';
setUp(() {
when(
() => httpClient.get(
Uri.parse(
'https://api.stripe.com/v1/billing/meters?status=active&limit=100',
),
headers: any(named: 'headers'),
),
).thenAnswer(
(_) async => http.Response(
billingMetersPageJsonString,
HttpStatus.ok,
),
);
when(
() => httpClient.get(
Uri.parse(
'https://api.stripe.com/v1/billing/meters?status=active&limit=100&starting_after=$meterId',
),
headers: any(named: 'headers'),
),
).thenAnswer(
(_) async => http.Response(
jsonEncode({
'object': 'list',
'data': <Map<String, dynamic>>[],
'has_more': false,
}),
HttpStatus.ok,
),
);
});
test('returns a billing meter on successful request', () async {
final billingMeters = await stripeApi.fetchActiveBillingMeters();
expect(billingMeters, hasLength(1));
final billingMeter = billingMeters.first;
expect(billingMeter.id, meterId);
expect(billingMeter.displayName, 'Patch Installs');
expect(billingMeter.eventName, 'patch_installs');
});
});
group('createMeterEvent', () {
final uri = Uri.parse('https://api.stripe.com/v1/billing/meter_events');