Recently Written · git

subplz-web

git clone https://github.com/equwal/subplz-web

Log | Files | Refs


commit 2545b258f234e3d5806bb25b25fcf72505937ac6
equwal <truex@equwal.com>
2026-09-22 15:55:31 -0700

Sell a 500-book pack

Add pack500: 500 credits for $174.99 ($0.35 a book, 30% below the
10-book pack). The owner chose to sell it against the advice of the
financial advocate: one such sale can hold the one CPU server for about
six weeks. So the pack text says how fast the server works.

The catalogue tests now expect three packs, and the check-out test runs
for pack500 too.

 README.md             |  1 +
 backend/pricing.py    | 18 ++++++++++++++----
 tests/test_billing.py |  6 +++---
 3 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 733404e..f34d931 100644
--- a/README.md
+++ b/README.md
@@ -331,6 +331,7 @@ use of its operator's machines, and nothing else.
 |---|---|---|
 | 10 books | $4.99 | $0.50 |
 | 100 books | $39.99 | $0.40 |
+| 500 books | $174.99 | $0.35 |
 
 A book costs about $0.64 to convert on a rented GPU. Above about $5 a technical
 buyer wraps a raw alignment API (ElevenLabs: $2.20 for a 10-hour book). Below
diff --git a/backend/pricing.py b/backend/pricing.py
index c828f1e..e50fc68 100644
--- a/backend/pricing.py
+++ b/backend/pricing.py
@@ -37,12 +37,13 @@ one heavy user of an unlimited plan costs more than the plan brings in.
   10-book pack      $4.99    ($0.50/book)
   100-book pack     $39.99   ($0.40/book, 20% off: the middle of what credit
                              packs give at 10x volume)
+  500-book pack     $174.99  ($0.35/book, 30% off)
 
 The owner set $4.99 for ten books on 2026-09-22, and asked for bigger packs the
-same day. There is no 500-book pack: one such sale holds this one CPU server
-for about six weeks. A buyer who needs more books buys the 100-book pack again.
-The packs sold before that (one book $4.99, five $16.99, twenty $39) are in
-RETIRED_PLANS.
+same day. The owner chose to sell the 500-book pack against the advice of the
+financial advocate: one such sale can hold this one CPU server for about six
+weeks, so the pack says how fast the server works. The packs sold before that
+(one book $4.99, five $16.99, twenty $39) are in RETIRED_PLANS.
 
 Every number is overridable by env var; these are defaults, not decisions cast
 in code. An operator who wants a recurring plan can add one with
@@ -94,6 +95,15 @@ DEFAULT_PLANS: list[Plan] = [
         blurb="For a whole library. Our server does one book at a time: about "
               "2 hours for a 10-hour book. Credits never expire.",
     ),
+    Plan(
+        id="pack500",
+        name="500 books",
+        credits=500,
+        price_cents=17499,
+        blurb="For a very large library. Our server does one book at a time: "
+              "about 2 hours for a 10-hour book, so 500 long books take weeks. "
+              "Credits never expire.",
+    ),
 ]
 
 # Not for sale. A checkout that started before a plan was retired can still
diff --git a/tests/test_billing.py b/tests/test_billing.py
index 85d9583..051690d 100644
--- a/tests/test_billing.py
+++ b/tests/test_billing.py
@@ -168,7 +168,7 @@ def test_checkout_sends_stripe_the_right_order(client, monkeypatch):
 def test_ten_books_cost_4_99_and_no_unlimited_plan_is_sold(client):
     plans = client.get("/api/pricing").json()["plans"]
     assert [(p["id"], p["credits"], p["price_cents"]) for p in plans] == [
-        ("pack10", 10, 499), ("pack100", 100, 3999)]
+        ("pack10", 10, 499), ("pack100", 100, 3999), ("pack500", 500, 17499)]
     assert not any(p["recurring"] for p in plans)
 
 
@@ -177,14 +177,14 @@ def test_bigger_packs_cost_less_a_book(client):
     # more in all, or the smaller packs are pointless.
     plans = client.get("/api/pricing").json()["plans"]
     packs = sorted((p for p in plans if not p["recurring"]), key=lambda p: p["credits"])
-    assert [p["credits"] for p in packs] == [10, 100]
+    assert [p["credits"] for p in packs] == [10, 100, 500]
     per_book = [p["price_cents"] / p["credits"] for p in packs]
     assert all(small > big for small, big in zip(per_book, per_book[1:]))
     totals = [p["price_cents"] for p in packs]
     assert all(small < big for small, big in zip(totals, totals[1:]))
 
 
-@pytest.mark.parametrize("plan_id", ["pack100"])
+@pytest.mark.parametrize("plan_id", ["pack100", "pack500"])
 def test_a_big_pack_checks_out_at_its_price_and_credits_its_books(client, monkeypatch, plan_id):
     plan = pricing.get(plan_id)
     assert plan is not None