fix: cancelled tier, remove key logging, add billing rate limits
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 10m13s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 10m13s
- Add 'cancelled' tier (0 req/month) for downgraded subscriptions - Remove full API key from recovery endpoint logs (security) - Add IP-based rate limiting (10/15min) to billing endpoints - Bump version to 0.7.0 - 4 new tests (338 total)
This commit is contained in:
parent
f3a363fb17
commit
9575d312fe
5 changed files with 68 additions and 7 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { getTierLimit, getKeyByEmail, getCustomerIdByEmail } from '../keys.js'
|
||||
import { getTierLimit, getKeyByEmail, getCustomerIdByEmail, downgradeByCustomer } from '../keys.js'
|
||||
|
||||
// Mock the db module
|
||||
vi.mock('../db.js', () => ({
|
||||
|
|
@ -25,6 +25,10 @@ describe('getTierLimit', () => {
|
|||
expect(getTierLimit('business')).toBe(25000)
|
||||
})
|
||||
|
||||
it('should return 0 for cancelled tier', () => {
|
||||
expect(getTierLimit('cancelled')).toBe(0)
|
||||
})
|
||||
|
||||
it('should return 100 for unknown tier', () => {
|
||||
expect(getTierLimit('enterprise')).toBe(100)
|
||||
})
|
||||
|
|
@ -124,3 +128,20 @@ describe('getCustomerIdByEmail', () => {
|
|||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('downgradeByCustomer', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should set tier to cancelled instead of free', async () => {
|
||||
vi.mocked(queryWithRetry).mockResolvedValue({ rows: [] })
|
||||
|
||||
await downgradeByCustomer('cus_test_123')
|
||||
|
||||
expect(queryWithRetry).toHaveBeenCalledWith(
|
||||
expect.stringContaining("'cancelled'"),
|
||||
['cus_test_123']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { queryWithRetry } from "./db.js";
|
|||
|
||||
export interface ApiKey {
|
||||
key: string;
|
||||
tier: "free" | "starter" | "pro" | "business";
|
||||
tier: "free" | "cancelled" | "starter" | "pro" | "business";
|
||||
email: string;
|
||||
createdAt: string;
|
||||
stripeCustomerId?: string;
|
||||
|
|
@ -130,6 +130,7 @@ export function getAllKeys(): ApiKey[] {
|
|||
export function getTierLimit(tier: string): number {
|
||||
switch (tier) {
|
||||
case "free": return 100;
|
||||
case "cancelled": return 0;
|
||||
case "starter": return 1000;
|
||||
case "pro": return 5000;
|
||||
case "business": return 25000;
|
||||
|
|
@ -184,16 +185,16 @@ export async function createPaidKey(email: string, tier: "starter" | "pro" | "bu
|
|||
|
||||
export async function downgradeByCustomer(customerId: string): Promise<void> {
|
||||
await queryWithRetry(
|
||||
"UPDATE api_keys SET tier = 'free', stripe_customer_id = NULL WHERE stripe_customer_id = $1",
|
||||
"UPDATE api_keys SET tier = 'cancelled', stripe_customer_id = NULL WHERE stripe_customer_id = $1",
|
||||
[customerId]
|
||||
);
|
||||
for (const k of keysCache) {
|
||||
if (k.stripeCustomerId === customerId) {
|
||||
k.tier = "free";
|
||||
k.tier = "cancelled";
|
||||
k.stripeCustomerId = undefined;
|
||||
}
|
||||
}
|
||||
logger.info({ customerId }, "Downgraded customer to free");
|
||||
logger.info({ customerId }, "Downgraded customer to cancelled");
|
||||
}
|
||||
|
||||
export async function updateEmailByCustomer(customerId: string, newEmail: string): Promise<void> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue