feat: add StripeClient.create_price for metered billing

This commit is contained in:
Graham McIntire 2026-03-06 13:47:54 -06:00
parent 448ed20401
commit 3e74ac5897
No known key found for this signature in database
3 changed files with 79 additions and 1 deletions

View file

@ -94,4 +94,6 @@ config :towerops,
# Reduce debounce delay in tests for faster test execution (50ms vs 500ms in prod)
agent_channel_debounce_ms: 50,
# Reduce device deletion delay for faster tests (10ms vs 500ms in prod)
device_deletion_delay_ms: 10
device_deletion_delay_ms: 10,
# Stripe test configuration
stripe_product_id: "prod_test"

View file

@ -125,6 +125,33 @@ defmodule Towerops.Billing.StripeClient do
end
end
# Price Management
@doc """
Create a new Price object for metered billing.
Returns {:ok, price_object} or {:error, reason}.
"""
def create_price(unit_amount_decimal) when is_binary(unit_amount_decimal) do
params = %{
product: stripe_product_id(),
currency: "usd",
unit_amount_decimal: unit_amount_decimal,
recurring: %{
interval: "month",
usage_type: "metered",
aggregate_usage: "max"
},
billing_scheme: "per_unit",
metadata: %{
created_by: "towerops_admin_ui",
created_at: DateTime.to_iso8601(DateTime.utc_now())
}
}
post("/v1/prices", params)
end
# HTTP Request Helpers
defp get(path) do
@ -255,4 +282,8 @@ defmodule Towerops.Billing.StripeClient do
defp stripe_price_id do
Application.fetch_env!(:towerops, :stripe_price_id)
end
defp stripe_product_id do
Application.fetch_env!(:towerops, :stripe_product_id)
end
end

View file

@ -315,4 +315,49 @@ defmodule Towerops.Billing.StripeClientTest do
StripeClient.create_customer(organization)
end
end
describe "create_price/1" do
test "creates metered billing price at specified amount" do
Req.Test.stub(StripeClient, fn conn ->
assert conn.method == "POST"
assert conn.request_path == "/v1/prices"
# Verify form params
{:ok, body, conn} = Plug.Conn.read_body(conn)
params = URI.decode_query(body)
assert params["unit_amount_decimal"] == "2.50"
assert params["currency"] == "usd"
assert params["recurring[interval]"] == "month"
assert params["recurring[usage_type]"] == "metered"
assert params["recurring[aggregate_usage]"] == "max"
assert params["billing_scheme"] == "per_unit"
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.send_resp(
200,
Jason.encode!(%{
"id" => "price_test123",
"object" => "price",
"unit_amount_decimal" => "2.50"
})
)
end)
assert {:ok, %{"id" => "price_test123"}} = StripeClient.create_price("2.50")
end
test "returns error on API failure" do
Req.Test.stub(StripeClient, fn conn ->
error = %{"error" => %{"message" => "Invalid amount"}}
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.send_resp(400, Jason.encode!(error))
end)
assert {:error, {:bad_request, "Invalid amount"}} = StripeClient.create_price("invalid")
end
end
end