File size: 6,176 Bytes
6778ee0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | defmodule PlausibleWeb.Api.PaddleControllerTest do
use PlausibleWeb.ConnCase, async: true
use Plausible.Repo
import Mox
setup :verify_on_exit!
@webhook_body %{
"alert_id" => "16173800",
"alert_name" => "subscription_created",
"cancel_url" =>
"https://checkout.paddle.com/subscription/cancel?user=1032746&subscription=1869424&hash=eyJpdiI6Ik5XSnhkT1k2RSticStZcEVYZ2FEeTVZTHVSaTNpcFM4XC9aMFJNSjh6TFNrPSIsInZhbHVlIjoiWTMzd3dENGQySTc0UjVPM0U3dzVDUWlBMEQ3dGVzM2lyODRPczBVcjdBRXlncmQzRUVmQjhnZVhpTzJIRjFtNW41MmZOVmJNVUJ4empmeXgyUno1Z3c9PSIsIm1hYyI6ImFjNDMyNDIxNmNmMWNiNmViMmFlNGVkMzQ3ZjYyYTQ5ZWI2YTEyMDQ4YjFhNTEyMjAxNzNlNjEwOWU4OTVhOWMifQ%3D%3D",
"checkout_id" => "38111668-chre6449b9c3cc8-f473e48a86",
"currency" => "USD",
"email" => "josh@joshuae.com",
"event_time" => "2019-08-20 21:44:54",
"marketing_consent" => 0,
"next_bill_date" => "2019-09-20",
"passthrough" => "235",
"quantity" => "1",
"status" => "active",
"subscription_id" => "1869424",
"subscription_plan_id" => "558018",
"unit_price" => "6.00",
"update_url" =>
"https://checkout.paddle.com/subscription/update?user=1032746&subscription=1869424&hash=eyJpdiI6IkNUS2VZQlRxcFA5MVlEXC9Oa1ZwRDBNaGN4VVwvaU1RU2srWXc0bU1tQndyTT0iLCJ2YWx1ZSI6ImtTeU1ESkxWcEVrTDFmKzkyZ0FaSFo2Q0VNK3A2XC9NdEU4S2tGVFE2blJicGxBQzZ1XC9mMG1PcUo3MWV2OGY4YURjT1UxY2hpeHh5SFhlMmhXaFpoalE9PSIsIm1hYyI6ImZkNGU5MTg3YzQxZWYxNDJjNDkyMWFkYmZhZjIyMGQ3ZGI2YTVmMTcxZGViY2VkNzI0ZjNlMDRkZTgwNTEwMzUifQ%3D%3D",
"user_id" => "1032746",
"p_signature" =>
"qfqKA3dI9d60uie9IORcvkHYV+rd1UaCu/f5kh4miTkeIQNimgusQG8pS1OHobCvN/OktwKCjFcbIwoa4nakOOWGroHJ8FjLJHBK4g1uI37Bp6l73dNl8mB4dNGW1M+atkz7ag6pETRIdEKCmC5tV9afN5CvbcqRV1lsj/x2fAsjAe/sQkmAP1jbDXOMEuHqkWssSB7Q+NGHHLHuNQ67m7YFBnZSgYzLeLMEApkZClJn0j6MokUVjW37ISn5eA5FlUbT7s6Kph54roRzLIpYvC+ff/n6ae2Iu1OsORxRBg4Uv8dqqjqBKXlv84/OB80U89yMIbRw/pbHD6+zF4FxgNV7nk2bjgK2V6h55AOuhJHHUMb4XX9R8i8iG1FOlNJaTwbhkIkvQF3q7nEItKCqizn+l4tFQ9MUcrjw8jytDznbOnSlmNhtcDVlnvXNDaSPkEA7AyR6c+BiZV/Y6I3y8sr8h/F/cBM3OPTwfdKK34jyWW4LRn15nSxq2kjH3SyLPEpTJUMdcRGAgBZc06E4lENU2x22E/JKG5BRi1aDs5OFQtrjYi2hOTI0dyPF3OLNeZcCgBCKBmKq5XIf1T0RPFWAWtKkzXhl/QH+4feNATb9/i6k5xKeUJf0ltWzsI5x84kvsC/m05hn/AuBDmcZGkVnDLXrqttR+zDXY6P1euE="
}
describe "webhook verification" do
test "is verified when signature is correct", %{conn: conn} do
insert(:user, id: 235)
# NOTE: signature check happens sooner
assert_raise RuntimeError, ~r/Invalid passthrough sent via Paddle/, fn ->
post(conn, Routes.paddle_path(conn, :webhook), @webhook_body)
end
end
test "not verified when signature is corrupted", %{conn: conn} do
corrupted = Map.put(@webhook_body, "p_signature", Base.encode64("123 fake signature"))
conn = post(conn, Routes.paddle_path(conn, :webhook), corrupted)
assert conn.status == 400
end
end
describe "fetching currency" do
test "retrieves successfully", %{conn: conn} do
expect_get_prices_response(get_prices_body("USD"))
conn = get(conn, Routes.paddle_path(conn, :currency))
assert_receive :paddle_queried
assert json_response(conn, 200) == %{"currency" => "$"}
end
test "caches per ip", %{conn: initial_conn} do
expect_get_prices_response(get_prices_body("USD"))
conn = get(initial_conn, Routes.paddle_path(initial_conn, :currency))
assert json_response(conn, 200) == %{"currency" => "$"}
assert_receive :paddle_queried
expect_get_prices_response(get_prices_body("GBP"))
conn = get(initial_conn, Routes.paddle_path(initial_conn, :currency))
assert json_response(conn, 200) == %{"currency" => "$"}
refute_receive :paddle_queried
new_ip =
Plug.Conn.put_req_header(initial_conn, "x-forwarded-for", random_ip())
conn = get(new_ip, Routes.paddle_path(initial_conn, :currency))
assert json_response(conn, 200) == %{"currency" => "£"}
assert_receive :paddle_queried
end
test "falls back to EUR when paddle fails to respond", %{conn: conn} do
expect_get_prices_response(%{"response" => %{}})
conn = get(conn, Routes.paddle_path(conn, :currency))
assert_receive :paddle_queried
assert json_response(conn, 200) == %{"currency" => "€"}
end
test "does not cache failed fetches", %{conn: initial_conn} do
expect_get_prices_response(%{"response" => %{}})
conn = get(initial_conn, Routes.paddle_path(initial_conn, :currency))
assert json_response(conn, 200) == %{"currency" => "€"}
expect_get_prices_response(get_prices_body("USD"))
conn = get(initial_conn, Routes.paddle_path(initial_conn, :currency))
assert json_response(conn, 200) == %{"currency" => "$"}
end
defp expect_get_prices_response(body) do
test = self()
expect(
Plausible.HTTPClient.Mock,
:get,
fn "https://checkout.paddle.com/api/2.0/prices" <> query, _, _ ->
assert query =~ "product_ids=857097"
send(test, :paddle_queried)
{:ok,
%Finch.Response{
status: 200,
headers: [{"content-type", "application/json"}],
body: body
}}
end
)
end
defp get_prices_body(currency) do
%{
"response" => %{
"customer_country" => "PL",
"products" => [
%{
"applied_coupon" => [],
"currency" => currency,
"list_price" => %{"gross" => 49.0, "net" => 49.0, "tax" => 0.0},
"price" => %{"gross" => 49.0, "net" => 49.0, "tax" => 0.0},
"product_id" => 857_097,
"product_title" => "random",
"subscription" => %{
"frequency" => 1,
"interval" => "month",
"list_price" => %{"gross" => 49.0, "net" => 49.0, "tax" => 0.0},
"price" => %{"gross" => 49.0, "net" => 49.0, "tax" => 0.0},
"trial_days" => 0
},
"vendor_set_prices_included_tax" => false
}
]
},
"success" => true
}
end
end
end
|