- C1: Replace innerHTML template literals with DOM textContent in sites_map.ts - C2: Derive user_id from current_scope instead of client params in policy consent - C3: Fix broken halt() in GDPR data export (orphaned _ = ... halt()) - C4: Add owner/admin authorization check to MembersController update/delete - C5: Require HMAC signature on agent release webhook (was optional) - C6: Fix Repo.all_by/2 (not a real Ecto function) → Repo.all with query - C7: Move auth exemption to before_send callback in BruteForceProtection - C8: Block </style>/<script injection in status page custom_css validation - C9: Create secrets.example.yaml with placeholders; secrets.yaml already gitignored - C10: Load Stripe key from STRIPE_SECRET_KEY env var instead of hardcoded value - C13: Remove credentials from PubSub backup broadcast; channel resolves them C11 (no force_ssl): by design — Cloudflared terminates TLS C12 (device quota race): false positive — FOR UPDATE serializes correctly
103 lines
2.8 KiB
Elixir
103 lines
2.8 KiB
Elixir
#!/usr/bin/env elixir
|
|
|
|
# Setup script for Stripe billing meter
|
|
|
|
stripe_key = System.get_env("STRIPE_SECRET_KEY", "sk_test_use_your_own_key")
|
|
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
|