summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/payment-section.module.css26
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/payment-section.tsx28
-rw-r--r--packages/console/core/script/credit-workspace.ts20
-rw-r--r--packages/console/core/script/lookup-user.ts28
-rw-r--r--packages/console/core/src/billing.ts18
5 files changed, 105 insertions, 15 deletions
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/payment-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/payment-section.module.css
index 2e1afe78b..3a3b2f7a8 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/payment-section.module.css
+++ b/packages/console/app/src/routes/workspace/[id]/billing/payment-section.module.css
@@ -45,6 +45,19 @@
text-decoration: line-through;
}
}
+
+ &[data-slot="payment-receipt"] {
+ span {
+ display: inline-block;
+ padding: var(--space-3) var(--space-4);
+ font-size: var(--font-size-sm);
+ line-height: 1.5;
+ }
+
+ button {
+ font-size: var(--font-size-sm);
+ }
+ }
}
tbody tr {
@@ -54,6 +67,7 @@
}
@media (max-width: 40rem) {
+
th,
td {
padding: var(--space-2) var(--space-3);
@@ -61,16 +75,22 @@
}
th {
- &:nth-child(2) /* Payment ID */ {
+ &:nth-child(2)
+
+ /* Payment ID */
+ {
display: none;
}
}
td {
- &:nth-child(2) /* Payment ID */ {
+ &:nth-child(2)
+
+ /* Payment ID */
+ {
display: none;
}
}
}
}
-}
+} \ No newline at end of file
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/payment-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/payment-section.tsx
index 3712513bf..e51ccc9f5 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/payment-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/payment-section.tsx
@@ -77,6 +77,7 @@ export function PaymentSection() {
<For each={payments()!}>
{(payment) => {
const date = new Date(payment.timeCreated)
+ const isCredit = !payment.paymentID
return (
<tr>
<td data-slot="payment-date" title={formatDateUTC(date)}>
@@ -85,19 +86,24 @@ export function PaymentSection() {
<td data-slot="payment-id">{payment.id}</td>
<td data-slot="payment-amount" data-refunded={!!payment.timeRefunded}>
${((payment.amount ?? 0) / 100000000).toFixed(2)}
+ {isCredit ? " (credit)" : ""}
</td>
<td data-slot="payment-receipt">
- <button
- onClick={async () => {
- const receiptUrl = await downloadReceiptAction(params.id!, payment.paymentID!)
- if (receiptUrl) {
- window.open(receiptUrl, "_blank")
- }
- }}
- data-slot="receipt-button"
- >
- View
- </button>
+ {isCredit ? (
+ <span>-</span>
+ ) : (
+ <button
+ onClick={async () => {
+ const receiptUrl = await downloadReceiptAction(params.id!, payment.paymentID!)
+ if (receiptUrl) {
+ window.open(receiptUrl, "_blank")
+ }
+ }}
+ data-slot="receipt-button"
+ >
+ View
+ </button>
+ )}
</td>
</tr>
)
diff --git a/packages/console/core/script/credit-workspace.ts b/packages/console/core/script/credit-workspace.ts
new file mode 100644
index 000000000..29fb1fa64
--- /dev/null
+++ b/packages/console/core/script/credit-workspace.ts
@@ -0,0 +1,20 @@
+import { Billing } from "../src/billing.js"
+
+// get input from command line
+const workspaceID = process.argv[2]
+const dollarAmount = process.argv[3]
+
+if (!workspaceID || !dollarAmount) {
+ console.error("Usage: bun credit-workspace.ts <workspaceID> <dollarAmount>")
+ process.exit(1)
+}
+
+const amountInDollars = parseFloat(dollarAmount)
+if (isNaN(amountInDollars) || amountInDollars <= 0) {
+ console.error("Error: dollarAmount must be a positive number")
+ process.exit(1)
+}
+
+await Billing.grantCredit(workspaceID, amountInDollars)
+
+console.log(`Added payment of $${amountInDollars.toFixed(2)} to workspace ${workspaceID}`)
diff --git a/packages/console/core/script/lookup-user.ts b/packages/console/core/script/lookup-user.ts
index bb919e34d..d0b583a18 100644
--- a/packages/console/core/script/lookup-user.ts
+++ b/packages/console/core/script/lookup-user.ts
@@ -1,7 +1,7 @@
import { Database, eq, sql, inArray } from "../src/drizzle/index.js"
import { AuthTable } from "../src/schema/auth.sql.js"
import { UserTable } from "../src/schema/user.sql.js"
-import { BillingTable, PaymentTable } from "../src/schema/billing.sql.js"
+import { BillingTable, PaymentTable, UsageTable } from "../src/schema/billing.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
// get input from command line
@@ -95,6 +95,32 @@ async function printWorkspace(workspaceID: string) {
})),
),
)
+
+ await printTable("Usage", (tx) =>
+ tx
+ .select({
+ model: UsageTable.model,
+ provider: UsageTable.provider,
+ inputTokens: UsageTable.inputTokens,
+ outputTokens: UsageTable.outputTokens,
+ reasoningTokens: UsageTable.reasoningTokens,
+ cacheReadTokens: UsageTable.cacheReadTokens,
+ cacheWrite5mTokens: UsageTable.cacheWrite5mTokens,
+ cacheWrite1hTokens: UsageTable.cacheWrite1hTokens,
+ cost: UsageTable.cost,
+ timeCreated: UsageTable.timeCreated,
+ })
+ .from(UsageTable)
+ .where(eq(UsageTable.workspaceID, workspace.id))
+ .orderBy(sql`${UsageTable.timeCreated} DESC`)
+ .limit(1000)
+ .then((rows) =>
+ rows.map((row) => ({
+ ...row,
+ cost: `$${(row.cost / 100000000).toFixed(2)}`,
+ })),
+ ),
+ )
}
function printHeader(title: string) {
diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts
index 049ee29bb..c14df11ae 100644
--- a/packages/console/core/src/billing.ts
+++ b/packages/console/core/src/billing.ts
@@ -157,6 +157,24 @@ export namespace Billing {
})
}
+ export const grantCredit = async (workspaceID: string, dollarAmount: number) => {
+ const amountInMicroCents = centsToMicroCents(dollarAmount * 100)
+ await Database.transaction(async (tx) => {
+ await tx
+ .update(BillingTable)
+ .set({
+ balance: sql`${BillingTable.balance} + ${amountInMicroCents}`,
+ })
+ .where(eq(BillingTable.workspaceID, workspaceID))
+ await tx.insert(PaymentTable).values({
+ workspaceID,
+ id: Identifier.create("payment"),
+ amount: amountInMicroCents,
+ })
+ })
+ return amountInMicroCents
+ }
+
export const setMonthlyLimit = fn(z.number(), async (input) => {
return await Database.use((tx) =>
tx