feat: add Stripe meter ID configuration

- Add stripe_meter_id to dev, runtime, and test configs
- Add STRIPE_METER_ID to k8s deployment secret references
- Use real Stripe meter and price IDs from Stripe setup
- Meter: mtr_test_61UHPU067veEZ7bhl41S77kvnTfgyODY
- Price: price_1T81XBS77kvnTfgyPlw1jF8N ($1/device/month)
- Include setup script for Stripe billing configuration
This commit is contained in:
Graham McIntire 2026-03-06 10:49:22 -06:00
parent 20f05584dc
commit d0e7eb4308
No known key found for this signature in database
4 changed files with 128 additions and 3 deletions

View file

@ -219,4 +219,5 @@ config :towerops,
stripe_secret_key:
"sk_test_51T7zcQS77kvnTfgyu0DCQU2xVKzeaneVCueHaXV3jsIw6HAwGWWllEL3J8jGZOybHAtPyu6oYIiuvJHhReFtTycE00VrJYJ8Ao",
stripe_webhook_secret: "whsec_dev_fake_secret",
stripe_price_id: "price_dev_fake_id"
stripe_price_id: "price_1T81XBS77kvnTfgyPlw1jF8N",
stripe_meter_id: "mtr_test_61UHPU067veEZ7bhl41S77kvnTfgyODY"

View file

@ -373,10 +373,18 @@ if config_env() == :prod do
This is the Stripe price ID for the metered billing plan.
"""
stripe_meter_id =
System.get_env("STRIPE_METER_ID") ||
raise """
environment variable STRIPE_METER_ID is missing.
This is the Stripe billing meter ID for usage reporting.
"""
config :towerops,
stripe_secret_key: stripe_secret_key,
stripe_webhook_secret: stripe_webhook_secret,
stripe_price_id: stripe_price_id
stripe_price_id: stripe_price_id,
stripe_meter_id: stripe_meter_id
end
# Test environment Stripe configuration
@ -384,5 +392,6 @@ if config_env() == :test do
config :towerops,
stripe_secret_key: "sk_test_fake",
stripe_webhook_secret: "whsec_test_fake",
stripe_price_id: "price_test_fake"
stripe_price_id: "price_test_fake",
stripe_meter_id: "meter_test_fake"
end

View file

@ -84,6 +84,12 @@ spec:
name: towerops-billing
key: STRIPE_PRICE_ID
optional: true
- name: STRIPE_METER_ID
valueFrom:
secretKeyRef:
name: towerops-billing
key: STRIPE_METER_ID
optional: true
envFrom:
- secretRef:
name: towerops-db
@ -189,6 +195,12 @@ spec:
name: towerops-billing
key: STRIPE_PRICE_ID
optional: true
- name: STRIPE_METER_ID
valueFrom:
secretKeyRef:
name: towerops-billing
key: STRIPE_METER_ID
optional: true
envFrom:
# Redis connection configured via towerops-redis secret
- secretRef:

103
setup_stripe_meter.exs Normal file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env elixir
# Setup script for Stripe billing meter
stripe_key = "sk_test_51T7zcQS77kvnTfgyu0DCQU2xVKzeaneVCueHaXV3jsIw6HAwGWWllEL3J8jGZOybHAtPyu6oYIiuvJHhReFtTycE00VrJYJ8Ao"
product_id = "prod_U6DpFdl21ftiDz"
IO.puts("Creating Stripe billing meter...")
# Create billing meter
form_data =
URI.encode_query(%{
"display_name" => "Devices Monitored",
"event_name" => "devices_monitored",
"default_aggregation[formula]" => "sum",
"value_settings[event_payload_key]" => "value"
})
{output, exit_code} =
System.cmd(
"curl",
[
"-X",
"POST",
"https://api.stripe.com/v1/billing/meters",
"-u",
"#{stripe_key}:",
"-H",
"Stripe-Version: 2024-11-20.acacia",
"-H",
"Content-Type: application/x-www-form-urlencoded",
"-d",
form_data
],
stderr_to_stdout: true
)
if exit_code == 0 do
case Jason.decode(output) do
{:ok, meter} ->
IO.puts("\n✓ Billing meter created successfully!")
IO.puts(" Meter ID: #{meter["id"]}")
IO.puts(" Display name: #{meter["display_name"]}")
IO.puts(" Event name: #{meter["event_name"]}")
IO.puts("\nNow creating metered price...")
# Create metered price linked to the meter
price_form =
URI.encode_query(%{
"product" => product_id,
"currency" => "usd",
"recurring[interval]" => "month",
"recurring[usage_type]" => "metered",
"recurring[meter]" => meter["id"],
"billing_scheme" => "per_unit",
# $1.00 in cents
"unit_amount" => "100",
"nickname" => "Per Device Monthly"
})
{price_output, price_exit} =
System.cmd(
"curl",
[
"-X",
"POST",
"https://api.stripe.com/v1/prices",
"-u",
"#{stripe_key}:",
"-H",
"Stripe-Version: 2024-11-20.acacia",
"-H",
"Content-Type: application/x-www-form-urlencoded",
"-d",
price_form
],
stderr_to_stdout: true
)
if price_exit == 0 do
case Jason.decode(price_output) do
{:ok, price} ->
IO.puts("\n✓ Price created successfully!")
IO.puts(" Price ID: #{price["id"]}")
IO.puts(" Amount: $#{price["unit_amount"] / 100}/device/month")
IO.puts("\n" <> String.duplicate("=", 60))
IO.puts("SETUP COMPLETE!")
IO.puts(String.duplicate("=", 60))
IO.puts("\nAdd these to your Kubernetes secret:")
IO.puts(" STRIPE_METER_ID=#{meter["id"]}")
IO.puts(" STRIPE_PRICE_ID=#{price["id"]}")
{:error, _} ->
IO.puts("\nError creating price: #{price_output}")
end
end
{:error, _} ->
IO.puts("\nError: #{output}")
end
else
IO.puts("\nCurl failed: #{output}")
end