import type { Selectable } from 'kysely'
import type * as Db from '../Db.js'
import type * as db_Schema from '../Schema.js'
/** Columns of the `stripe_customers` table, derived from `Schema.StripeCustomer`. */
export type Table = db_Schema.StripeCustomer
/** A stored Stripe billing-source row. */
export type Record = Selectable
/** A billing status. */
export type Status = Record['status']
/** Billing statuses; `active` alone opens the production sponsorship gate. */
export const statuses = ['active', 'canceled', 'none', 'past_due'] as const satisfies readonly Status[] // prettier-ignore
/**
* Inserts a Stripe billing source (status `none`) and returns the stored record.
* Idempotent per organization: a concurrent first-checkout race resolves to
* the winner's row — the loser's Stripe customer stays orphaned in Stripe,
* which is harmless.
*
* @param db - The database.
* @param input - The Stripe source to insert.
* @returns The stored record.
*/
export async function create(db: Db.Db, input: create.Input): Promise {
const now = new Date().toISOString()
const environment = input.environment ?? 'production'
const inserted = await db.kysely
.insertInto('stripe_customers')
.values({
createdAt: now,
environment,
orgId: input.orgId,
status: 'none',
stripeCustomerId: input.stripeCustomerId,
updatedAt: now,
})
// Untargeted so it holds against either the org pkey (before the pkey drop
// release) or the `(org_id, environment)` unique index (after).
.onConflict((oc) => oc.doNothing())
.returningAll()
.executeTakeFirst()
if (inserted) return inserted
return await db.kysely
.selectFrom('stripe_customers')
.selectAll()
.where('orgId', '=', input.orgId)
.where('environment', '=', environment)
.executeTakeFirstOrThrow()
}
export declare namespace create {
/** Fields accepted when inserting a Stripe billing source. */
type Input = {
/** Environment this source backs; defaults to `production`. */
environment?: Record['environment'] | undefined
/** Organization id (`org_…`) the Stripe source belongs to. */
orgId: string
/** Stripe customer id backing this account. */
stripeCustomerId: string
}
}
/**
* Deletes an organization's Stripe billing source. The Stripe customer is never
* deleted; its invoice history stays in Stripe for audit.
*
* @param db - The database.
* @param orgId - The organization id (`org_…`).
* @returns Whether a Stripe source existed and was deleted.
*/
export async function deleteByOrg(db: Db.Db, orgId: string): Promise {
const result = await db.kysely
.deleteFrom('stripe_customers')
.where('orgId', '=', orgId)
.executeTakeFirst()
return result.numDeletedRows > 0n
}
/**
* Reads an organization's Stripe billing source.
*
* @param db - The database.
* @param orgId - The organization id (`org_…`).
* @param environment - The environment to read; defaults to `production`.
* @returns The stored record, or `undefined` when the org has none.
*/
export function get(
db: Db.Db,
orgId: string,
environment: Record['environment'] = 'production',
): Promise {
return db.kysely
.selectFrom('stripe_customers')
.selectAll()
.where('orgId', '=', orgId)
.where('environment', '=', environment)
.executeTakeFirst()
}
/**
* Lists every billing source in an environment — the driver set for the cron
* job that reconciles cached billing snapshots (e.g. API-key records) against
* this authoritative table.
*
* @param db - The database.
* @param environment - The environment to enumerate.
* @returns The stored records for that environment.
*/
export function listByEnvironment(
db: Db.Db,
environment: Record['environment'],
): Promise {
return db.kysely
.selectFrom('stripe_customers')
.selectAll()
.where('environment', '=', environment)
.execute()
}
/**
* Reads a Stripe billing source by its customer id — the webhook handler's
* lookup for inbound events.
*
* @param db - The database.
* @param stripeCustomerId - The Stripe customer id.
* @returns The stored record, or `undefined` for unknown customers.
*/
export function getByCustomer(db: Db.Db, stripeCustomerId: string): Promise {
return db.kysely
.selectFrom('stripe_customers')
.selectAll()
.where('stripeCustomerId', '=', stripeCustomerId)
.executeTakeFirst()
}
/**
* Sets an organization's billing status.
*
* @param db - The database.
* @param orgId - The organization id (`org_…`).
* @param status - The derived status to persist.
* @param environment - The environment to update; defaults to `production`.
* @returns The updated record, or `undefined` when the org has no Stripe source.
*/
export function setStatus(
db: Db.Db,
orgId: string,
status: Status,
environment: Record['environment'] = 'production',
): Promise {
return db.kysely
.updateTable('stripe_customers')
.set({ status, updatedAt: new Date().toISOString() })
.where('orgId', '=', orgId)
.where('environment', '=', environment)
.returningAll()
.executeTakeFirst()
}