feat(careers): charge 1 credit per job posting - #185
Merged
Merged
Conversation
Charged once, the first time a posting goes open — not per month, not per edit. Closing a role and re-opening it later is free, so a seasonal listing isn't billed twice for the same hire. Drafts cost nothing, and switching the widget on costs nothing. The project owner pays, not whoever clicked Publish: a teammate publishing a role shouldn't have it come out of their personal balance, and the project is already the billing entity for the tracker. Spend first, write second, refund if the write fails. The reverse order publishes roles for free whenever the charge fails, which is the expensive direction to be wrong in. credit_charged_at on the posting is what makes the charge idempotent — a failed charge leaves an obvious null rather than a silently-published posting. Serving costs us almost nothing (the jobs feed is a cached read), so this is priced as a listing fee rather than off cost: 5c a posting at rack, against $200-and-up to list the same role on a real job board. The UI prices the button it is about to charge for, and says "Re-open" instead of "Publish (1 credit)" once a posting has already paid.
ThreatCrush Security Scan52 finding(s) HIGH/CRITICAL: 10 | MEDIUM: 42
…and 2 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #183 (merged). Adds billing to the careers widget.
The rule
1 credit per posting, charged once — the first time it goes open.
JOB_POSTING_CREDITSlives inlib/credits.tsalongside the other prices. Serving costs us almost nothing (the jobs feed is a cached read), so this is priced as a listing fee rather than off cost: 5c a posting at rack, against $200-and-up to list the same role on a real job board.Two decisions worth reviewing
The project owner pays, not whoever clicked Publish. A teammate publishing a role shouldn't have it come out of their personal balance, and the project is already the billing entity for the tracker. Say the word if you'd rather bill the actor.
Spend first, write second, refund if the write fails. The reverse order publishes roles for free whenever the charge fails, which is the expensive direction to be wrong in.
credit_charged_aton the posting is what makes the charge idempotent — a failed charge leaves an obvious null rather than a posting that went live without being billed.Migration
credit_charged_atships as a new migration (20260803150000) rather than an edit to the careers migration from #183. That one is already on master and may have been applied; editing an applied migration in place would leave the column silently missing.UI
The button prices what it's about to charge for —
Publish (1 credit)— and becomesRe-openonce a posting has already paid, so nobody clicks expecting a second charge.Checks
tsc --noEmitcleanvitest run— 1309 passed (14 new), 1 file skippednext buildcompilesThe 14 new tests pin the money rules: charged once, never for drafts, never twice, owner billed not actor, refunded on write failure. I mutation-checked them — removing the double-charge guard fails 3 of them, so they aren't passing vacuously.
Still not done (from the earlier list)
Unchanged by this PR: no spam protection on the public
/api/careers/apply, no email notification on a new application, and/c/pages still aren't insitemap.ts.