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
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,35 @@ final class BillingServiceSubscriptionTests: XCTestCase {
{
"id": "pro",
"name": "Pro",
"price_cents": 2500,
"base_price_cents": 3000,
"base_lookup_key": "pro_base",
"billing_interval": "month",
"machine_tiers": [
{
"tier": "medium",
"label": "medium",
"price_cents": 0,
"lookup_key": "pro_machine_medium",
"cpu_limit": "2.5",
"memory_gib": 5,
"description": "Medium machine (2.5 vCPU, 5 GiB)"
}
],
"storage_tiers": [
{
"tier": "medium",
"label": "256 GiB",
"storage_gib": 256,
"price_cents": 0,
"lookup_key": "pro_storage_medium"
}
],
"included_features": [
"Larger machine size",
"Bundled credits",
"Managed email subdomain",
"Managed Twilio phone numbers",
"90-day grace period on cancellation before managed resources are released"
"Pay-as-you-go credits",
"Custom LLM credentials",
"Configurable machine size",
"Configurable storage",
"Assistant email & subdomain"
]
}
]
Expand All @@ -93,12 +114,17 @@ final class BillingServiceSubscriptionTests: XCTestCase {
XCTAssertFalse(base.included_features.isEmpty)
XCTAssertEqual(base.included_features.first, "Pay-as-you-go credits")

// The Pro entry uses the server's tiered shape: no flat `price_cents`,
// with pricing split across `base_price_cents` + per-tier arrays the
// client doesn't model. Decoding must still succeed (extra keys are
// ignored, `price_cents` decodes to nil) — a regression here is what
// produced "Unable to load plan information." on the Plan card.
let pro = decoded.plans[1]
XCTAssertEqual(pro.id, "pro")
XCTAssertEqual(pro.name, "Pro")
XCTAssertEqual(pro.price_cents, 2500)
XCTAssertNil(pro.price_cents)
XCTAssertEqual(pro.billing_interval, "month")
XCTAssertFalse(pro.included_features.isEmpty)
XCTAssertTrue(pro.included_features.contains("Larger machine size"))
XCTAssertTrue(pro.included_features.contains("Assistant email & subdomain"))
}
}
2 changes: 1 addition & 1 deletion clients/macos/vellum-assistantTests/PlanCardTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class PlanCardTests: XCTestCase {
PlanCatalogEntry(
id: "pro",
name: "Pro",
price_cents: 2500,
price_cents: nil,
billing_interval: "month",
included_features: [
"Larger machine size",
Expand Down
7 changes: 6 additions & 1 deletion clients/shared/App/Auth/AuthModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,12 @@ public struct SubscriptionResponse: Codable, Sendable {
public struct PlanCatalogEntry: Codable, Sendable {
public let id: String // "base" | "pro"
public let name: String // "Base" | "Pro"
public let price_cents: Int
/// Flat monthly price, in cents. Only present on flat-priced plans (Base);
/// tiered plans (Pro) omit it and split pricing across `base_price_cents` +
/// per-tier arrays on the server. MUST stay optional — making it required
/// hard-fails decoding of the whole catalog on the Pro entry, surfacing as
/// "Unable to load plan information." on the Plan card.
public let price_cents: Int?
public let billing_interval: String // "month"
public let included_features: [String]
}
Expand Down