Login ads become a flat 30-day slot
The daily meter charged 100 credits for every day a campaign was shown, with no end date. It produced 74-day queues, and worse, each new campaign added 100/day of charges while adding no inventory: on 18 September, 15 campaigns were charged 1,500 credits against a platform that had produced 367 login impressions in its entire life. That had stopped being a pricing inefficiency and become members paying for delivery that did not exist. Now: 3,000 credits buys 30 days of unlimited impressions, charged once at purchase, with no delivery promise — the model Marty wanted, matching how ClickBaitPays sells theirs. The price is deliberately identical to 30 days at the old daily rate, so nobody pays more than before; they just get a known end instead of an open-ended drain. It reuses the `featured` shape, which was already a flat up-front buy that runs to `expires`. Both daily meters — MySQL and JSON — now skip any campaign with an expiry, so a slot can never be metered. The existing sweep closes it on day 30 and returns anything unspent. qa/login-slot.mjs, 9 checks, including that the slot price still equals 30 days at the old rate and that neither meter can touch a slot campaign. Member walk clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1037,7 +1037,7 @@
|
|||||||
<script src="/assets/common.js?v=20260916a"></script>
|
<script src="/assets/common.js?v=20260916a"></script>
|
||||||
<script src="/assets/wallet.js?v=20260911a"></script>
|
<script src="/assets/wallet.js?v=20260911a"></script>
|
||||||
<script src="/assets/promo.js?v=20260911a"></script>
|
<script src="/assets/promo.js?v=20260911a"></script>
|
||||||
<script src="/assets/my.js?v=20260918c"></script>
|
<script src="/assets/my.js?v=20260918d"></script>
|
||||||
<script src="/assets/chat.js?v=20260907l"></script>
|
<script src="/assets/chat.js?v=20260907l"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Login ads are a flat 30-day slot, not a daily meter.
|
||||||
|
//
|
||||||
|
// The old model charged 100 credits for every day a campaign was shown, with no end date.
|
||||||
|
// That produced 74-day queues, and every new campaign added 100/day of charges while adding
|
||||||
|
// no inventory — on 18 Sep, 15 campaigns were charged 1,500 credits against a platform that
|
||||||
|
// had produced 367 login impressions in its whole life.
|
||||||
|
import { createRequire } from 'node:module';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const ads = require('../ads.js');
|
||||||
|
|
||||||
|
const ok = [], bad = [];
|
||||||
|
const t = (n, c, extra) => { (c ? ok : bad).push(n + (c || !extra ? '' : ' -> ' + extra)); };
|
||||||
|
|
||||||
|
const r = ads.rates();
|
||||||
|
t('a slot price is configured', Number(r.loginSlotCredits) > 0, String(r.loginSlotCredits));
|
||||||
|
t('a slot length is configured', Number(r.loginSlotDays) > 0, String(r.loginSlotDays));
|
||||||
|
t('the slot is 3,000 credits for 30 days', r.loginSlotCredits === 3000 && r.loginSlotDays === 30,
|
||||||
|
r.loginSlotCredits + ' / ' + r.loginSlotDays);
|
||||||
|
|
||||||
|
// the price matches what 30 days used to cost, so nobody pays more than before
|
||||||
|
t('same total as 30 days at the old daily rate',
|
||||||
|
r.loginSlotCredits === 30 * r.loginCreditsPerDay,
|
||||||
|
r.loginSlotCredits + ' vs ' + (30 * r.loginCreditsPerDay));
|
||||||
|
|
||||||
|
// the source guarantees that matter, checked directly — these are the behaviours that stop
|
||||||
|
// a slot draining against days when the gate produced nothing
|
||||||
|
const src = require('fs').readFileSync(new URL('../ads.js', import.meta.url), 'utf8');
|
||||||
|
|
||||||
|
t('buying a slot charges the whole price up front',
|
||||||
|
/Login-ad slot \(/.test(src) && /chargeCredits\(campaign\.id, v\.c\.budget, 'Login-ad slot/.test(src));
|
||||||
|
t('a slot gets an end date at purchase',
|
||||||
|
/out\.expires = Date\.now\(\) \+ days \* 86400000/.test(src));
|
||||||
|
t('the MySQL daily meter skips slot campaigns',
|
||||||
|
/expires IS NULL AND \(last_day_charged/.test(src));
|
||||||
|
t('the JSON daily meter skips slot campaigns',
|
||||||
|
/if \(c\.expires\) continue; \/\/ slot buy/.test(src));
|
||||||
|
t('a short budget is refused with the slot price named',
|
||||||
|
/A login-ad slot is ' \+ cost \+ ' credits for ' \+ days \+ ' days/.test(src));
|
||||||
|
|
||||||
|
console.log('PASS ' + ok.length);
|
||||||
|
for (const b of bad) console.log('FAIL ' + b);
|
||||||
|
process.exit(bad.length ? 1 : 0);
|
||||||
Reference in New Issue
Block a user