Add self-serve billing: tiers, credits, BTCPay and Zaprite
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { sdk } from '../sdk'
|
||||
import { configFile } from '../file-models/config.json'
|
||||
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
// Operator-set per-tier monthly subscription prices, in USD. Used by
|
||||
// the dashboard to compute revenue and operating margin (Gemini cost
|
||||
// already comes out of the audit log). Pure accounting — the relay
|
||||
// itself does no billing.
|
||||
const inputSpec = InputSpec.of({
|
||||
core_price: Value.number({
|
||||
name: 'Core (Free) — Monthly Price',
|
||||
description:
|
||||
'Monthly subscription price for the Core tier in USD. Typically $0 since Core is the free entry tier. Used by the dashboard to compute total revenue; leave at 0 unless you actually charge for Core.',
|
||||
required: true,
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 10_000,
|
||||
integer: false,
|
||||
step: 0.01,
|
||||
units: 'USD',
|
||||
placeholder: null,
|
||||
}),
|
||||
pro_price: Value.number({
|
||||
name: 'Pro — Monthly Price',
|
||||
description:
|
||||
'Monthly subscription price for the Pro tier in USD. Should match what you actually charge Pro customers on the licensing side.',
|
||||
required: true,
|
||||
default: 5,
|
||||
min: 0,
|
||||
max: 10_000,
|
||||
integer: false,
|
||||
step: 0.01,
|
||||
units: 'USD',
|
||||
placeholder: null,
|
||||
}),
|
||||
max_price: Value.number({
|
||||
name: 'Max — Monthly Price',
|
||||
description:
|
||||
'Monthly subscription price for the Max tier in USD. Should match what you actually charge Max customers on the licensing side.',
|
||||
required: true,
|
||||
default: 15,
|
||||
min: 0,
|
||||
max: 10_000,
|
||||
integer: false,
|
||||
step: 0.01,
|
||||
units: 'USD',
|
||||
placeholder: null,
|
||||
}),
|
||||
})
|
||||
|
||||
export const setTierPrices = sdk.Action.withInput(
|
||||
'set-tier-prices',
|
||||
|
||||
async ({ effects }) => ({
|
||||
name: 'Set Tier Prices (USD)',
|
||||
description:
|
||||
'Configure the monthly USD price you charge per tier. The dashboard uses these numbers to compute revenue and operating margin against Gemini API cost. Has no effect on actual billing — it is for the operator’s accounting view only.',
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: 'Tiers',
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
|
||||
inputSpec,
|
||||
|
||||
async ({ effects }) => {
|
||||
const config = await configFile.read().once()
|
||||
let parsed: any = {}
|
||||
try {
|
||||
parsed = JSON.parse(config?.relay_tier_prices_usd_json || '{}')
|
||||
} catch {
|
||||
parsed = {}
|
||||
}
|
||||
return {
|
||||
core_price: typeof parsed?.core === 'number' ? parsed.core : 0,
|
||||
pro_price: typeof parsed?.pro === 'number' ? parsed.pro : 5,
|
||||
max_price: typeof parsed?.max === 'number' ? parsed.max : 15,
|
||||
}
|
||||
},
|
||||
|
||||
async ({ effects, input }) => {
|
||||
const prices = {
|
||||
core: Number(input.core_price ?? 0),
|
||||
pro: Number(input.pro_price ?? 5),
|
||||
max: Number(input.max_price ?? 15),
|
||||
}
|
||||
await configFile.merge(effects, {
|
||||
relay_tier_prices_usd_json: JSON.stringify(prices),
|
||||
})
|
||||
return null
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user