zen: support stripe link

This commit is contained in:
Frank
2025-10-16 14:59:44 -04:00
parent 1c1380d3c8
commit 7ec5e49e19
11 changed files with 960 additions and 10 deletions

View File

@@ -112,6 +112,7 @@
"version": "0.15.5", "version": "0.15.5",
"dependencies": { "dependencies": {
"@opencode-ai/console-core": "workspace:*", "@opencode-ai/console-core": "workspace:*",
"@opencode-ai/console-resource": "workspace:*",
}, },
}, },
"packages/css": { "packages/css": {

View File

@@ -100,6 +100,17 @@ export function IconCreditCard(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
) )
} }
export function IconStripe(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24">
<path
fill="currentColor"
d="M15.827 12.506c0 .672-.31 1.175-.771 1.175-.293 0-.468-.106-.589-.237l-.007-1.855c.13-.143.31-.247.596-.247.456-.001.771.51.771 1.164zm3.36-1.253c-.312 0-.659.236-.659.798h1.291c0-.562-.325-.798-.632-.798zm4.813-5.253v12c0 1.104-.896 2-2 2h-20c-1.104 0-2-.896-2-2v-12c0-1.104.896-2 2-2h20c1.104 0 2 .896 2 2zm-17.829 7.372c0-1.489-1.909-1.222-1.909-1.784 0-.195.162-.271.424-.271.38 0 .862.116 1.242.321v-1.176c-.414-.165-.827-.228-1.241-.228-1.012.001-1.687.53-1.687 1.414 0 1.382 1.898 1.158 1.898 1.754 0 .231-.201.305-.479.305-.414 0-.947-.171-1.366-.399v1.192c.464.2.935.283 1.365.283 1.038.001 1.753-.512 1.753-1.411zm2.422-3.054h-.949l-.001-1.084-1.219.259-.005 4.006c0 .739.556 1.285 1.297 1.285.408 0 .71-.074.876-.165v-1.016c-.16.064-.948.293-.948-.443v-1.776h.948v-1.066zm2.596 0c-.166-.06-.75-.169-1.042.369l-.078-.369h-1.079v4.377h1.248v-2.967c.295-.388.793-.313.952-.262v-1.148zm1.554 0h-1.253v4.377h1.253v-4.377zm0-1.664l-1.253.266v1.017l1.253-.266v-1.017zm4.314 3.824c0-1.454-.826-2.244-1.703-2.243-.489 0-.805.23-.978.392l-.065-.309h-1.099v5.828l1.249-.265.003-1.413c.179.131.446.316.883.316.893 0 1.71-.719 1.71-2.306zm3.943.045c0-1.279-.619-2.288-1.805-2.288-1.188 0-1.911 1.01-1.911 2.281 0 1.506.852 2.267 2.068 2.267.597 0 1.045-.136 1.384-.324v-1.006c-.34.172-.731.276-1.227.276-.487 0-.915-.172-.971-.758h2.444l.018-.448z"
/>
</svg>
)
}
export function IconChevron(props: JSX.SvgSVGAttributes<SVGSVGElement>) { export function IconChevron(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return ( return (
<svg {...props} width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg {...props} width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@@ -32,7 +32,8 @@ export async function POST(input: APIEvent) {
.update(BillingTable) .update(BillingTable)
.set({ .set({
paymentMethodID, paymentMethodID,
paymentMethodLast4: paymentMethod.card!.last4, paymentMethodLast4: paymentMethod.card?.last4 ?? null,
paymentMethodType: paymentMethod.type,
}) })
.where(eq(BillingTable.customerID, customerID)) .where(eq(BillingTable.customerID, customerID))
}) })
@@ -77,7 +78,8 @@ export async function POST(input: APIEvent) {
balance: sql`${BillingTable.balance} + ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`, balance: sql`${BillingTable.balance} + ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`,
customerID, customerID,
paymentMethodID: paymentMethod.id, paymentMethodID: paymentMethod.id,
paymentMethodLast4: paymentMethod.card!.last4, paymentMethodLast4: paymentMethod.card?.last4 ?? null,
paymentMethodType: paymentMethod.type,
reload: true, reload: true,
reloadError: null, reloadError: null,
timeReloadError: null, timeReloadError: null,

View File

@@ -70,6 +70,12 @@
font-weight: 500; font-weight: 500;
color: var(--color-text); color: var(--color-text);
} }
[data-slot="type"] {
font-size: var(--font-size-sm);
font-weight: 400;
color: var(--color-text-muted);
}
} }
} }

View File

@@ -1,8 +1,8 @@
import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router" import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
import { createMemo, Show } from "solid-js" import { createMemo, Match, Show, Switch } from "solid-js"
import { Billing } from "@opencode-ai/console-core/billing.js" import { Billing } from "@opencode-ai/console-core/billing.js"
import { withActor } from "~/context/auth.withActor" import { withActor } from "~/context/auth.withActor"
import { IconCreditCard } from "~/component/icon" import { IconCreditCard, IconStripe } from "~/component/icon"
import styles from "./billing-section.module.css" import styles from "./billing-section.module.css"
import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js" import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js" import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
@@ -61,6 +61,7 @@ export function BillingSection() {
// Scenario 1: User has not added billing details and has no balance // Scenario 1: User has not added billing details and has no balance
// const balanceInfo = () => ({ // const balanceInfo = () => ({
// balance: 0, // balance: 0,
// paymentMethodType: null as string | null,
// paymentMethodLast4: null as string | null, // paymentMethodLast4: null as string | null,
// reload: false, // reload: false,
// reloadError: null as string | null, // reloadError: null as string | null,
@@ -70,6 +71,7 @@ export function BillingSection() {
// Scenario 2: User has not added billing details but has a balance // Scenario 2: User has not added billing details but has a balance
// const balanceInfo = () => ({ // const balanceInfo = () => ({
// balance: 1500000000, // $15.00 // balance: 1500000000, // $15.00
// paymentMethodType: null as string | null,
// paymentMethodLast4: null as string | null, // paymentMethodLast4: null as string | null,
// reload: false, // reload: false,
// reloadError: null as string | null, // reloadError: null as string | null,
@@ -79,6 +81,7 @@ export function BillingSection() {
// Scenario 3: User has added billing details (reload enabled) // Scenario 3: User has added billing details (reload enabled)
// const balanceInfo = () => ({ // const balanceInfo = () => ({
// balance: 750000000, // $7.50 // balance: 750000000, // $7.50
// paymentMethodType: "card",
// paymentMethodLast4: "4242", // paymentMethodLast4: "4242",
// reload: true, // reload: true,
// reloadError: null as string | null, // reloadError: null as string | null,
@@ -88,12 +91,23 @@ export function BillingSection() {
// Scenario 4: User has billing details but reload failed // Scenario 4: User has billing details but reload failed
// const balanceInfo = () => ({ // const balanceInfo = () => ({
// balance: 250000000, // $2.50 // balance: 250000000, // $2.50
// paymentMethodType: "card",
// paymentMethodLast4: "4242", // paymentMethodLast4: "4242",
// reload: true, // reload: true,
// reloadError: "Your card was declined." as string, // reloadError: "Your card was declined." as string,
// timeReloadError: new Date(Date.now() - 3600000) as Date // 1 hour ago // timeReloadError: new Date(Date.now() - 3600000) as Date // 1 hour ago
// }) // })
// Scenario 5: User has Link payment method
// const balanceInfo = () => ({
// balance: 500000000, // $5.00
// paymentMethodType: "link",
// paymentMethodLast4: null as string | null,
// reload: true,
// reloadError: null as string | null,
// timeReloadError: null as Date | null
// })
const balanceAmount = createMemo(() => { const balanceAmount = createMemo(() => {
return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2) return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
}) })
@@ -136,13 +150,25 @@ export function BillingSection() {
<div data-slot="payment"> <div data-slot="payment">
<div data-slot="credit-card"> <div data-slot="credit-card">
<div data-slot="card-icon"> <div data-slot="card-icon">
<IconCreditCard style={{ width: "32px", height: "32px" }} /> <Switch fallback={<IconCreditCard style={{ width: "32px", height: "32px" }} />}>
<Match when={balanceInfo()?.paymentMethodType === "link"}>
<IconStripe style={{ width: "32px", height: "32px" }} />
</Match>
</Switch>
</div> </div>
<div data-slot="card-details"> <div data-slot="card-details">
<Switch
fallback={
<Show when={balanceInfo()?.paymentMethodLast4} fallback={<span data-slot="number">----</span>}> <Show when={balanceInfo()?.paymentMethodLast4} fallback={<span data-slot="number">----</span>}>
<span data-slot="secret"></span> <span data-slot="secret"></span>
<span data-slot="number">{balanceInfo()?.paymentMethodLast4}</span> <span data-slot="number">{balanceInfo()?.paymentMethodLast4}</span>
</Show> </Show>
}
>
<Match when={balanceInfo()?.paymentMethodType === "link"}>
<span data-slot="type">Linked to Stripe</span>
</Match>
</Switch>
</div> </div>
</div> </div>
<div data-slot="button-row"> <div data-slot="button-row">

View File

@@ -0,0 +1 @@
ALTER TABLE `billing` ADD `payment_method_type` varchar(32);

View File

@@ -0,0 +1,893 @@
{
"version": "5",
"dialect": "mysql",
"id": "91ef8fda-ca96-4a3f-af29-dd6ae7136398",
"prevId": "b2406421-f22d-4153-a2a4-6deafe70ee54",
"tables": {
"account": {
"name": "account",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
},
"billing": {
"name": "billing",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_type": {
"name": "payment_method_type",
"type": "varchar(32)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"monthly_limit": {
"name": "monthly_limit",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"monthly_usage": {
"name": "monthly_usage",
"type": "bigint",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_monthly_usage_updated": {
"name": "time_monthly_usage_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reload_error": {
"name": "reload_error",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_reload_error": {
"name": "time_reload_error",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_reload_locked_till": {
"name": "time_reload_locked_till",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"payment": {
"name": "payment",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"invoice_id": {
"name": "invoice_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_refunded": {
"name": "time_refunded",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"usage": {
"name": "usage",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"provider": {
"name": "provider",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_write_5m_tokens": {
"name": "cache_write_5m_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_write_1h_tokens": {
"name": "cache_write_1h_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"key_id": {
"name": "key_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"key": {
"name": "key",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"key": {
"name": "key",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_used": {
"name": "time_used",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"model": {
"name": "model",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model": {
"name": "model",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"provider": {
"name": "provider",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"provider": {
"name": "provider",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"credentials": {
"name": "credentials",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"account_id": {
"name": "account_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"color": {
"name": "color",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"role": {
"name": "role",
"type": "enum('admin','member')",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"monthly_limit": {
"name": "monthly_limit",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"monthly_usage": {
"name": "monthly_usage",
"type": "bigint",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_monthly_usage_updated": {
"name": "time_monthly_usage_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"workspace": {
"name": "workspace",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}

View File

@@ -232,6 +232,13 @@
"when": 1759976329502, "when": 1759976329502,
"tag": "0032_white_doctor_doom", "tag": "0032_white_doctor_doom",
"breakpoints": true "breakpoints": true
},
{
"idx": 33,
"version": "5",
"when": 1760637384217,
"tag": "0033_cynical_jack_flag",
"breakpoints": true
} }
] ]
} }

View File

@@ -27,6 +27,7 @@ export namespace Billing {
.select({ .select({
customerID: BillingTable.customerID, customerID: BillingTable.customerID,
paymentMethodID: BillingTable.paymentMethodID, paymentMethodID: BillingTable.paymentMethodID,
paymentMethodType: BillingTable.paymentMethodType,
paymentMethodLast4: BillingTable.paymentMethodLast4, paymentMethodLast4: BillingTable.paymentMethodLast4,
balance: BillingTable.balance, balance: BillingTable.balance,
reload: BillingTable.reload, reload: BillingTable.reload,

View File

@@ -9,6 +9,7 @@ export const BillingTable = mysqlTable(
...timestamps, ...timestamps,
customerID: varchar("customer_id", { length: 255 }), customerID: varchar("customer_id", { length: 255 }),
paymentMethodID: varchar("payment_method_id", { length: 255 }), paymentMethodID: varchar("payment_method_id", { length: 255 }),
paymentMethodType: varchar("payment_method_type", { length: 32 }),
paymentMethodLast4: varchar("payment_method_last4", { length: 4 }), paymentMethodLast4: varchar("payment_method_last4", { length: 4 }),
balance: bigint("balance", { mode: "number" }).notNull(), balance: bigint("balance", { mode: "number" }).notNull(),
monthlyLimit: int("monthly_limit"), monthlyLimit: int("monthly_limit"),

View File

@@ -10,6 +10,7 @@
"shell-prod": "sst shell --stage production -- bun" "shell-prod": "sst shell --stage production -- bun"
}, },
"dependencies": { "dependencies": {
"@opencode-ai/console-core": "workspace:*" "@opencode-ai/console-core": "workspace:*",
"@opencode-ai/console-resource": "workspace:*"
} }
} }