eleven / messenger Public

server: checkout accepts promotion codes (allow_promotion_codes) (#1321)

A dashboard-minted promo code (e.g. an operator comp) can now ride the
normal space-upgrade checkout โ€” a discounted space is entitlement-paid,
so it stops counting against the free-space quota. Includes the session's
SSH ledger why-entry.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 27b7dc0a23ef6c596b53076035bdd2d15d3d8786 parent 2b8811a
4 files changed, +16 −0
  • billing.go +4 −0
  • changelog/2026-07-23-checkout-promo-codes.md +2 −0
  • cloud_billing_test.go +3 −0
  • web/status/ssh/2026-07-23-checked-cloud-provisioner-quota-config-f.json +7 −0
diff --git a/billing.go b/billing.go
index 0e2ed43..49a90d3 100644
--- a/billing.go
+++ b/billing.go
@@ -424,6 +424,10 @@ func (b *stripeBilling) createCheckoutSession(spaceID int64, tier billingTier, b
form.Set("subscription_data[metadata][band]", band)
form.Set("success_url", successURL)
form.Set("cancel_url", cancelURL)
+ // Promotion codes: lets a dashboard-minted code (e.g. an operator comp โ€”
+ // a discounted space is entitlement-paid, so it stops counting against the
+ // free-space quota) ride the normal checkout instead of needing a side door.
+ form.Set("allow_promotion_codes", "true")
// Stripe Tax (docs/pricing-bands.md ยง VAT): consumer Prices are created
// tax-inclusive (the sticker is the total), Business tiers ex-VAT โ€” so a
// Business checkout also collects an optional VAT ID for the EU reverse
diff --git a/changelog/2026-07-23-checkout-promo-codes.md b/changelog/2026-07-23-checkout-promo-codes.md
new file mode 100644
index 0000000..53d6a06
--- /dev/null
+++ b/changelog/2026-07-23-checkout-promo-codes.md
@@ -0,0 +1,2 @@
+๐Ÿค– Upgrading a space now accepts promotion codes โ€” if you've been given a
+discount code, there's an "Add promotion code" field on the checkout page.
diff --git a/cloud_billing_test.go b/cloud_billing_test.go
index 1725245..eb6a59c 100644
--- a/cloud_billing_test.go
+++ b/cloud_billing_test.go
@@ -132,6 +132,9 @@ func TestCheckoutMintsSession(t *testing.T) {
if !strings.Contains(f.Get("success_url"), "upgraded="+host) {
t.Errorf("success_url = %q", f.Get("success_url"))
}
+ if f.Get("allow_promotion_codes") != "true" {
+ t.Errorf("allow_promotion_codes = %q, want true (dashboard promo codes must be redeemable)", f.Get("allow_promotion_codes"))
+ }
for k := range f {
if strings.Contains(k, "customer") || strings.Contains(k, "email") {
t.Errorf("checkout form leaks a customer field: %s", k)
diff --git a/web/status/ssh/2026-07-23-checked-cloud-provisioner-quota-config-f.json b/web/status/ssh/2026-07-23-checked-cloud-provisioner-quota-config-f.json
new file mode 100644
index 0000000..b525d01
--- /dev/null
+++ b/web/status/ssh/2026-07-23-checked-cloud-provisioner-quota-config-f.json
@@ -0,0 +1,7 @@
+{
+ "date": "2026-07-23",
+ "operator": "Paul Campbell",
+ "window": "โ‰ˆ20:05โ€“20:20 UTC (two short sessions)",
+ "why": "Checked cloud provisioner quota config for the space-quota question (read-only; /data/cloud.env turned out operator-unreadable โ€” no values seen, no secrets transited the session). No deploys.",
+ "refs": []
+}