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

Add Billing Meters and Billing Meter Events #275

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ extension StripeSignatureError: AbortError {
* [x] Subscription Schedule
* [x] Test Clocks
* [x] Usage Records
* [x] Meters
* [x] Meter Events
---
### Connect
* [x] Account
Expand Down
44 changes: 44 additions & 0 deletions Sources/StripeKit/Billing/Meter Events/MeterEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// MeterEvent.swift
// stripe-kit
//
// Created by TelemetryDeck on 08.01.25.
//

import Foundation

/// The [Meter Even Object](https://docs.stripe.com/api/billing/meter-event/object).
public struct MeterEvent: Codable {
/// String representing the object’s type. Objects of the same type share the same value.
public var object: String
/// Time at which the object was created. Measured in seconds since the Unix epoch.
public var created: Date?
/// The name of the meter event. Corresponds with the `event_name` field on a meter.
public var eventName: String
/// A unique identifier for the event.
public var identifier: String
/// Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode.
public var livemode: Bool
/// The payload of the event. This contains the fields corresponding to a meter’s `customer_mapping.event_payload_key` (default is `stripe_customer_id`) and `value_settings.event_payload_key` (default is `value`). Read more about the [payload](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#payload-key-overrides).
public var payload: [String: String]
/// The timestamp passed in when creating the event. Measured in seconds since the Unix epoch.
public var timestamp: Date

public init(
object: String,
created: Date? = nil,
eventName: String,
identifier: String,
livemode: Bool,
payload: [String: String],
timestamp: Date
) {
self.object = object
self.created = created
self.eventName = eventName
self.identifier = identifier
self.livemode = livemode
self.payload = payload
self.timestamp = timestamp
}
}
62 changes: 62 additions & 0 deletions Sources/StripeKit/Billing/Meter Events/MeterEventRoutes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// MeterEventRoutes.swift
// stripe-kit
//
// Created by TelemetryDeck on 08.01.25.
//

import Foundation
import NIO
import NIOHTTP1

public protocol MeterEventRoutes: StripeAPIRoute {
/// Creates a billing meter event.
///
/// - Parameters:
/// - event_name: The name of the meter event. Corresponds with the `event_name` field on a meter.
/// - payload: The payload of the event. This contains the fields corresponding to a meter’s `customer_mapping.event_payload_key` (default is `stripe_customer_id`) and `value_settings.event_payload_key` (default is `value`). Read more about the [payload](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#payload-key-overrides).
/// - identifier: A unique identifier for the event.
/// - timestamp: The timestamp passed in when creating the event. Measured in seconds since the Unix epoch. Must be within the past 35 calendar days or up to 5 minutes in the future. Defaults to current timestamp if not specified.
func create(
event_name: String,
payload: [String: String],
identifier: String?,
timestamp: Date?) async throws -> MeterEvent
}

public struct StripeMeterEventRoutes: MeterEventRoutes {
public var headers: HTTPHeaders = [:]

private let apiHandler: StripeAPIHandler
private let meterevents = APIBase + APIVersion + "billing/meter_events"

init(apiHandler: StripeAPIHandler) {
self.apiHandler = apiHandler
}

public func create(
event_name: String,
payload: [String: String],
identifier: String?,
timestamp: Date?) async throws -> MeterEvent
{
var body: [String: Any] = [
"event_name": event_name,
"payload": payload
]

if let identifier {
body["identifier"] = identifier
}

if let timestamp {
body["timestamp"] = Int(timestamp.timeIntervalSince1970)
}

return try await apiHandler.send(
method: .POST,
path: meterevents,
body: .string(body.queryParameters),
headers: headers)
}
}
83 changes: 83 additions & 0 deletions Sources/StripeKit/Billing/Meters/Meter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// Meter.swift
// stripe-kit
//
// Created by TelemetryDeck on 08.01.25.
//

import Foundation

/// The Meter Object
public struct Meter: Codable {
/// Unique identifier for the object.
public var id: String
/// String representing the object’s type. Objects of the same type share the same value.
public var object: String
/// Time at which the object was created. Measured in seconds since the Unix epoch.
public var created: Date?
/// Fields that specify how to map a meter event to a customer.
public var customerMapping: MeterCustomerMapping
/// The meter’s name.
public var displayName: String
/// The name of the meter event to record usage for. Corresponds with the `event_name` field on meter events.
public var eventName: String
/// The time window to pre-aggregate meter events for, if any.
public var eventTimeWindow: MeterEventTimeWindow?
/// Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode.
public var liveMode: Bool?
/// The meter’s status.
public var status: MeterStatus
/// The timestamps at which the meter status changed.
public var meterStatusTransitions: MeterStatusTransitions?
/// Time at which the object was last updated. Measured in seconds since the Unix epoch.
public var updated: Date
/// Fields that specify how to calculate a meter event’s value.
public var valueSettings: MeterValueSettings
}

public struct MeterCustomerMapping: Codable {
/// The key in the meter event payload to use for mapping the event to a customer.
public var eventPayloadKey: String
/// The method for mapping a meter event to a customer.
public var type: MeterCustomerMappingType
}

public enum MeterCustomerMappingType: String, Codable {
/// Map a meter event to a customer by passing a customer ID in the event’s payload.
case byID = "by_id"
}

public enum MeterEventTimeWindow: String, Codable {
/// Events are pre-aggregated in daily buckets.
case day
/// Events are pre-aggregated in hourly buckets.
case hour
}

public enum MeterStatus: String, Codable {
/// The meter is active.
case active
/// The meter is inactive. No more events for this meter will be accepted. The meter cannot be attached to a price.
case inactive
}

public struct MeterStatusTransitions: Codable {
/// The time the meter was deactivated, if any. Measured in seconds since Unix epoch.
public var deactivatedAt: Date?
}

public struct MeterValueSettings: Codable {
/// The key in the meter event payload to use as the value for this meter.
public var eventPayloadKey: String
}

public struct MeterDefaultAggregation: Codable {
public var formula: MeterDefaultAggregationFormula
}

public enum MeterDefaultAggregationFormula: String, Codable {
/// Count the number of events.
case count
/// Sum each event’s value.
case sum
}
151 changes: 151 additions & 0 deletions Sources/StripeKit/Billing/Meters/MeterRoutes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// MeterRoutes.swift
// stripe-kit
//
// Created by TelemetryDeck on 08.01.25.
//

import Foundation
import NIO
import NIOHTTP1

/// Meters specify how to aggregate meter events over a billing period. Meter events represent the actions that customers take in your system. Meters attach to prices and form the basis of the bill.
///
/// Related guide: [Usage based billing](https://docs.stripe.com/billing/subscriptions/usage-based).
public protocol MeterRoutes: StripeAPIRoute {
/// Creates a billing meter.
///
/// - Parameters:
/// - defaultAggregation: The default settings to aggregate a meter’s events with.
/// - displayName: The meter’s name. Not visible to the customer.
/// - eventName: The name of the meter event to record usage for. Corresponds with the `event_name` field on meter events.
/// - customerMapping: Fields that specify how to map a meter event to a customer.
func create(
defaultAggregation: MeterDefaultAggregation,
displayName: String,
eventName: String,
customerMapping: MeterCustomerMapping?,
eventTimeWindow: MeterEventTimeWindow?,
valueSettings: MeterValueSettings?
) async throws -> Meter

/// Updates a billing meter.
///
/// - Parameters:
/// - id: Unique identifier for the object.
/// - displayName: The meter’s name. Not visible to the customer.
func update(
id: String,
displayName: String?
) async throws -> Meter

/// Retrieves a billing meter.
///
/// - Parameters:
/// - id: Unique identifier for the object.
func retrieve(id: String) async throws -> Meter

/// Returns a list of your billing meters.
func listAll() async throws -> [Meter]

/// Deactivates a billing meter.
///
/// - Parameters:
/// - id: Unique identifier for the object.
func deactivate(id: String) async throws -> Meter

/// Reactivates a billing meter.
///
/// - Parameters:
/// - id: Unique identifier for the object.
func reactivate(id: String) async throws -> Meter
}

public struct StripeMeterRoutes: MeterRoutes {
public var headers: HTTPHeaders = [:]

private let apiHandler: StripeAPIHandler
private let meters = APIBase + APIVersion + "billing/meters"

init(apiHandler: StripeAPIHandler) {
self.apiHandler = apiHandler
}

public func create(
defaultAggregation: MeterDefaultAggregation,
displayName: String,
eventName: String,
customerMapping: MeterCustomerMapping?,
eventTimeWindow: MeterEventTimeWindow?,
valueSettings: MeterValueSettings?
) async throws -> Meter {
var body: [String: Any] = [
"default_aggregation[formula]": defaultAggregation.formula.rawValue,
"display_name": displayName,
"event_name": eventName
]

if let customerMapping = customerMapping {
body["customer_mapping[event_payload_key]"] = customerMapping.eventPayloadKey
body["customer_mapping[type]"] = customerMapping.type.rawValue
}

if let eventTimeWindow = eventTimeWindow {
body["event_time_window"] = eventTimeWindow.rawValue
}

if let valueSettings = valueSettings {
body["value_settings[event_payload_key]"] = valueSettings.eventPayloadKey
}

return try await apiHandler.send(
method: .POST,
path: meters,
body: .string(body.queryParameters),
headers: headers
)
}

public func update(id: String, displayName: String?) async throws -> Meter {
var body: [String: Any] = [:]

if let displayName = displayName {
body["display_name"] = displayName
}

return try await apiHandler.send(
method: .POST,
path: "\(meters)/\(id)",
body: .string(body.queryParameters),
headers: headers
)
}

public func retrieve(id: String) async throws -> Meter {
return try await apiHandler.send(method: .GET, path: "\(meters)/\(id)", headers: headers)
}

public func listAll() async throws -> [Meter] {
return try await apiHandler.send(
method: .GET,
path: meters,
headers: headers
)
}

public func deactivate(id: String) async throws -> Meter {
return try await apiHandler.send(
method: .POST,
path: "\(meters)/\(id)/deactivate",
headers: headers
)
}

public func reactivate(id: String) async throws -> Meter {
return try await apiHandler.send(
method: .POST,
path: "\(meters)/\(id)/reactivate",
headers: headers
)
}
}
Loading
Loading