feat(web): GitHub Marketplace plan-change webhook + admin panel - #162
Merged
Conversation
The Marketplace listing can now post marketplace_purchase events to /api/webhooks/github/marketplace, and /admin shows what arrived. Signature: X-Hub-Signature-256 over the RAW body, compared with timingSafeEqual. A missing secret returns 503 rather than 200, so an unconfigured deploy shows red in GitHub's delivery log instead of silently dropping purchases. Two tables. github_marketplace_purchases is the current subscription per GitHub account; github_marketplace_events is every raw delivery. GitHub does not resend failed deliveries, so the raw log is the only way to replay. RLS on with no policies: service role only. Two ordering details worth keeping: - pending_change carries the FUTURE plan at the top level and the current one in previous_marketplace_purchase. Reading the wrong one downgrades a customer the moment they schedule a downgrade instead of at the end of their billing cycle. - an event whose effective_date predates what we already applied is recorded and skipped, not applied. JSON is the configured content type; the urlencoded form is accepted too so a mis-set content type degrades to working rather than to a silent parse failure. 32 tests. The migration still has to be applied by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ThreatCrush Security Scan11 finding(s) HIGH/CRITICAL: 1 | MEDIUM: 5 | LOW: 5
Snippets are redacted; ThreatCrush never prints matched credential material. |
|
|
||
| const action = payload.action; | ||
| if (!isMarketplaceAction(action)) { | ||
| console.warn(`[gh marketplace] unknown action: ${logSafe(action)}`); |
|
|
||
| if (existing && isStaleEvent(existing.effective_date as string | null, row.effective_date)) { | ||
| console.warn( | ||
| `[gh marketplace] ignoring out-of-order ${logSafe(action)} for ${logSafe(row.github_account_login)}`, |
| await finish(true); | ||
|
|
||
| console.log( | ||
| `[gh marketplace] ${logSafe(action)} ${logSafe(row.github_account_login)} -> ${logSafe(row.plan_name)}`, |
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.
The Marketplace listing can now post marketplace_purchase events to /api/webhooks/github/marketplace, and /admin shows what arrived.
Signature: X-Hub-Signature-256 over the RAW body, compared with timingSafeEqual. A missing secret returns 503 rather than 200, so an unconfigured deploy shows red in GitHub's delivery log instead of silently dropping purchases.
Two tables. github_marketplace_purchases is the current subscription per GitHub account; github_marketplace_events is every raw delivery. GitHub does not resend failed deliveries, so the raw log is the only way to replay. RLS on with no policies: service role only.
Two ordering details worth keeping:
JSON is the configured content type; the urlencoded form is accepted too so a mis-set content type degrades to working rather than to a silent parse failure.
32 tests. The migration still has to be applied by hand.