125 lines
5.1 KiB
Swift
125 lines
5.1 KiB
Swift
import Foundation
|
||
|
||
/// Parsed view of CommandCode `/internal/billing/credits` + `/internal/billing/subscriptions`.
|
||
public struct CommandCodeUsageSnapshot: Sendable {
|
||
/// USD remaining in the current monthly grant (`credits.monthlyCredits`).
|
||
public let monthlyCreditsRemaining: Double
|
||
/// USD top-up balance carried over (`credits.purchasedCredits`).
|
||
public let purchasedCredits: Double
|
||
/// USD remaining in the premium monthly grant (`credits.premiumMonthlyCredits`).
|
||
public let premiumMonthlyCredits: Double
|
||
/// USD remaining in the open-source monthly grant (`credits.opensourceMonthlyCredits`).
|
||
public let opensourceMonthlyCredits: Double
|
||
/// Subscription plan, or nil when the user is on the free tier.
|
||
public let plan: CommandCodePlanCatalog.Plan?
|
||
/// `currentPeriodEnd` from the active subscription.
|
||
public let billingPeriodEnd: Date?
|
||
/// Subscription status (e.g. `active`, `canceled`).
|
||
public let subscriptionStatus: String?
|
||
/// The optional subscription request timed out or failed for this refresh.
|
||
public let subscriptionEnrichmentUnavailable: Bool
|
||
public let updatedAt: Date
|
||
|
||
public init(
|
||
monthlyCreditsRemaining: Double,
|
||
purchasedCredits: Double,
|
||
premiumMonthlyCredits: Double,
|
||
opensourceMonthlyCredits: Double,
|
||
plan: CommandCodePlanCatalog.Plan?,
|
||
billingPeriodEnd: Date?,
|
||
subscriptionStatus: String?,
|
||
subscriptionEnrichmentUnavailable: Bool = false,
|
||
updatedAt: Date = Date())
|
||
{
|
||
self.monthlyCreditsRemaining = monthlyCreditsRemaining
|
||
self.purchasedCredits = purchasedCredits
|
||
self.premiumMonthlyCredits = premiumMonthlyCredits
|
||
self.opensourceMonthlyCredits = opensourceMonthlyCredits
|
||
self.plan = plan
|
||
self.billingPeriodEnd = billingPeriodEnd
|
||
self.subscriptionStatus = subscriptionStatus
|
||
self.subscriptionEnrichmentUnavailable = subscriptionEnrichmentUnavailable
|
||
self.updatedAt = updatedAt
|
||
}
|
||
|
||
/// USD allocation for the active monthly grant (from the catalog).
|
||
public var monthlyCreditsTotal: Double? {
|
||
self.plan?.monthlyCreditsUSD
|
||
}
|
||
|
||
/// USD spent in the current monthly grant (total – remaining), clamped to [0, total].
|
||
public var monthlyCreditsUsed: Double? {
|
||
guard let total = self.monthlyCreditsTotal else { return nil }
|
||
return max(0, min(total, total - self.monthlyCreditsRemaining))
|
||
}
|
||
|
||
public func toUsageSnapshot() -> UsageSnapshot {
|
||
let primary = self.makePrimaryWindow()
|
||
|
||
let identity = ProviderIdentitySnapshot(
|
||
providerID: .commandcode,
|
||
accountEmail: nil,
|
||
accountOrganization: nil,
|
||
loginMethod: self.makeLoginMethod())
|
||
|
||
return UsageSnapshot(
|
||
primary: primary,
|
||
secondary: nil,
|
||
tertiary: nil,
|
||
providerCost: nil,
|
||
commandCodeSubscriptionEnrichmentUnavailable: self.subscriptionEnrichmentUnavailable,
|
||
commandCodeHasSubscriptionPlan: self.plan != nil,
|
||
commandCodeMonthlyGrantDepleted: self.monthlyCreditsRemaining <= 0,
|
||
updatedAt: self.updatedAt,
|
||
identity: identity)
|
||
}
|
||
|
||
private func makePrimaryWindow() -> RateWindow? {
|
||
guard let total = self.monthlyCreditsTotal, total > 0 else {
|
||
// Free / unknown plan with no allowance — surface 100% so the bar renders empty.
|
||
if self.monthlyCreditsRemaining > 0 || self.purchasedCredits > 0 {
|
||
return RateWindow(
|
||
usedPercent: 0,
|
||
windowMinutes: nil,
|
||
resetsAt: self.billingPeriodEnd,
|
||
resetDescription: nil)
|
||
}
|
||
return nil
|
||
}
|
||
let used = self.monthlyCreditsUsed ?? 0
|
||
let percent = min(100, max(0, (used / total) * 100))
|
||
return RateWindow(
|
||
usedPercent: percent,
|
||
windowMinutes: nil,
|
||
resetsAt: self.billingPeriodEnd,
|
||
resetDescription: nil)
|
||
}
|
||
|
||
private func makeLoginMethod() -> String? {
|
||
var parts: [String] = []
|
||
if let name = self.plan?.displayName, !name.isEmpty {
|
||
parts.append(name)
|
||
}
|
||
if let total = self.monthlyCreditsTotal {
|
||
let used = self.monthlyCreditsUsed ?? 0
|
||
parts.append("\(Self.formatUSD(used)) of \(Self.formatUSD(total))")
|
||
} else if self.monthlyCreditsRemaining > 0 {
|
||
parts.append("\(Self.formatUSD(self.monthlyCreditsRemaining)) remaining")
|
||
}
|
||
if self.purchasedCredits > 0 {
|
||
parts.append("+ \(Self.formatUSD(self.purchasedCredits)) credits")
|
||
}
|
||
return parts.isEmpty ? nil : parts.joined(separator: " · ")
|
||
}
|
||
|
||
static func formatUSD(_ value: Double) -> String {
|
||
let formatter = NumberFormatter()
|
||
formatter.numberStyle = .currency
|
||
formatter.currencyCode = "USD"
|
||
formatter.locale = Locale(identifier: "en_US")
|
||
formatter.maximumFractionDigits = value < 100 ? 2 : 0
|
||
formatter.minimumFractionDigits = value < 100 ? 2 : 0
|
||
return formatter.string(from: NSNumber(value: value)) ?? "$\(value)"
|
||
}
|
||
}
|