Initial commit on Forgejo
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s

Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
Maxfield Luke 2026-07-29 04:43:40 -04:00
commit af6077b9c3
455 changed files with 78366 additions and 0 deletions

View file

@ -0,0 +1,281 @@
defmodule TarakanWeb.Webhooks.StripeControllerTest do
use TarakanWeb.ConnCase, async: true
alias Tarakan.Accounts.Scope
alias Tarakan.Market.Bounty
alias Tarakan.Repo
@secret "whsec_test_secret"
defp fiat_bounty do
repository = listed_github_repository_fixture()
sponsor = active_account_fixture() |> fund_credits(1_000)
{bounty, _checkout_url} = fiat_bounty_fixture(Scope.for_account(sponsor), repository)
bounty
end
defp signed_post(conn, payload, opts \\ []) do
timestamp = Keyword.get(opts, :timestamp, System.system_time(:second))
secret = Keyword.get(opts, :secret, @secret)
signature =
:crypto.mac(:hmac, :sha256, secret, "#{timestamp}.#{payload}")
|> Base.encode16(case: :lower)
conn
|> put_req_header("content-type", "application/json")
|> put_req_header("stripe-signature", "t=#{timestamp},v1=#{signature}")
|> post(~p"/webhooks/stripe", payload)
end
defp checkout_completed_payload(session_id) do
Jason.encode!(%{
"id" => "evt_1",
"type" => "checkout.session.completed",
"data" => %{"object" => %{"id" => session_id, "payment_intent" => "pi_1"}}
})
end
test "a validly signed checkout.session.completed funds the bounty", %{conn: conn} do
bounty = fiat_bounty()
conn = signed_post(conn, checkout_completed_payload(bounty.stripe_checkout_session_id))
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "open"
end
defp checkout_payload(session_id, type, extra) do
Jason.encode!(%{
"id" => "evt_1",
"type" => type,
"data" => %{"object" => Map.merge(%{"id" => session_id}, extra)}
})
end
test "a completed but unpaid session does not fund the bounty", %{conn: conn} do
bounty = fiat_bounty()
# Delayed-notification methods complete the session before the money
# arrives. Funding here would let work be claimed against nothing.
conn =
signed_post(
conn,
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.completed",
%{"payment_status" => "unpaid"}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "the later async payment confirmation funds it", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(
conn,
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.completed",
%{"payment_status" => "unpaid"}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
conn =
conn
|> recycle()
|> signed_post(
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.async_payment_succeeded",
%{}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "open"
end
test "a failed async payment leaves the bounty unfunded", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(
conn,
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.async_payment_failed",
%{}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "an invalid signature is rejected", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(conn, checkout_completed_payload(bounty.stripe_checkout_session_id),
secret: "whsec_wrong"
)
assert json_response(conn, 400) == %{"error" => "invalid_signature"}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "a stale timestamp is rejected", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(conn, checkout_completed_payload(bounty.stripe_checkout_session_id),
timestamp: System.system_time(:second) - 600
)
assert json_response(conn, 400) == %{"error" => "timestamp_outside_tolerance"}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "a missing signature header is rejected", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post(~p"/webhooks/stripe", checkout_completed_payload("cs_test_whatever"))
assert json_response(conn, 400) == %{"error" => "missing_signature"}
end
test "a tampered body is rejected", %{conn: conn} do
timestamp = System.system_time(:second)
signature =
:crypto.mac(
:hmac,
:sha256,
@secret,
"#{timestamp}.{\"type\":\"checkout.session.completed\"}"
)
|> Base.encode16(case: :lower)
conn =
conn
|> put_req_header("content-type", "application/json")
|> put_req_header("stripe-signature", "t=#{timestamp},v1=#{signature}")
|> post(~p"/webhooks/stripe", checkout_completed_payload("cs_test_tampered"))
assert json_response(conn, 400) == %{"error" => "invalid_signature"}
end
test "unknown events are acknowledged and ignored", %{conn: conn} do
payload = Jason.encode!(%{"id" => "evt_2", "type" => "payment_intent.created", "data" => %{}})
conn = signed_post(conn, payload)
assert json_response(conn, 200) == %{"received" => true}
end
test "funding an unknown session still returns 200", %{conn: conn} do
conn = signed_post(conn, checkout_completed_payload("cs_test_unknown"))
assert json_response(conn, 200) == %{"received" => true}
end
describe "subscription events" do
defp subscription_event_payload(type, object) do
Jason.encode!(%{"id" => "evt_sub", "type" => type, "data" => %{"object" => object}})
end
defp checkout_subscription_payload(account) do
subscription_event_payload("checkout.session.completed", %{
"id" => "cs_test_sub",
"mode" => "subscription",
"client_reference_id" => Integer.to_string(account.id),
"customer" => "cus_webhook_1",
"subscription" => "sub_webhook_1",
"metadata" => %{"plan" => "team"}
})
end
test "a signed subscription checkout activates the account's subscription", %{conn: conn} do
account = account_fixture()
conn = signed_post(conn, checkout_subscription_payload(account))
assert json_response(conn, 200) == %{"received" => true}
subscription = Tarakan.Billing.subscription(account)
assert subscription.status == "active"
assert subscription.plan == "team"
assert subscription.stripe_subscription_id == "sub_webhook_1"
end
test "customer.subscription.updated syncs the subscription", %{conn: conn} do
account = account_fixture()
conn = signed_post(conn, checkout_subscription_payload(account))
assert json_response(conn, 200) == %{"received" => true}
payload =
subscription_event_payload("customer.subscription.updated", %{
"id" => "sub_webhook_1",
"status" => "active",
"current_period_end" => 1_900_000_000,
"items" => %{"data" => [%{"price" => %{"id" => "price_enterprise_test"}}]}
})
conn = signed_post(build_conn(), payload)
assert json_response(conn, 200) == %{"received" => true}
subscription = Tarakan.Billing.subscription(account)
assert subscription.plan == "enterprise"
assert DateTime.to_unix(subscription.current_period_end) == 1_900_000_000
end
test "invoice.payment_failed and customer.subscription.deleted move the status", %{
conn: conn
} do
account = account_fixture()
conn = signed_post(conn, checkout_subscription_payload(account))
assert json_response(conn, 200) == %{"received" => true}
failed =
subscription_event_payload("invoice.payment_failed", %{
"subscription" => "sub_webhook_1"
})
conn = signed_post(build_conn(), failed)
assert json_response(conn, 200) == %{"received" => true}
assert Tarakan.Billing.subscription(account).status == "past_due"
deleted =
subscription_event_payload("customer.subscription.deleted", %{
"id" => "sub_webhook_1"
})
conn = signed_post(build_conn(), deleted)
assert json_response(conn, 200) == %{"received" => true}
assert Tarakan.Billing.subscription(account).status == "canceled"
end
test "subscription events require a valid signature", %{conn: conn} do
account = account_fixture()
conn =
signed_post(conn, checkout_subscription_payload(account), secret: "whsec_wrong")
assert json_response(conn, 400) == %{"error" => "invalid_signature"}
assert Tarakan.Billing.subscription(account) == nil
end
end
end