From 7ec5e49e19122f53d99836bdda0d7a98eb61c868 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 16 Oct 2025 14:59:44 -0400 Subject: zen: support stripe link --- packages/console/app/src/component/icon.tsx | 11 ++++++ packages/console/app/src/routes/stripe/webhook.ts | 6 ++-- .../[id]/billing/billing-section.module.css | 6 ++++ .../workspace/[id]/billing/billing-section.tsx | 40 ++++++++++++++++++---- 4 files changed, 54 insertions(+), 9 deletions(-) (limited to 'packages/console/app') diff --git a/packages/console/app/src/component/icon.tsx b/packages/console/app/src/component/icon.tsx index fc27ef3b4..0395cad52 100644 --- a/packages/console/app/src/component/icon.tsx +++ b/packages/console/app/src/component/icon.tsx @@ -100,6 +100,17 @@ export function IconCreditCard(props: JSX.SvgSVGAttributes) { ) } +export function IconStripe(props: JSX.SvgSVGAttributes) { + return ( + + + + ) +} + export function IconChevron(props: JSX.SvgSVGAttributes) { return ( diff --git a/packages/console/app/src/routes/stripe/webhook.ts b/packages/console/app/src/routes/stripe/webhook.ts index c12f47afc..f1fa73c91 100644 --- a/packages/console/app/src/routes/stripe/webhook.ts +++ b/packages/console/app/src/routes/stripe/webhook.ts @@ -32,7 +32,8 @@ export async function POST(input: APIEvent) { .update(BillingTable) .set({ paymentMethodID, - paymentMethodLast4: paymentMethod.card!.last4, + paymentMethodLast4: paymentMethod.card?.last4 ?? null, + paymentMethodType: paymentMethod.type, }) .where(eq(BillingTable.customerID, customerID)) }) @@ -77,7 +78,8 @@ export async function POST(input: APIEvent) { balance: sql`${BillingTable.balance} + ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`, customerID, paymentMethodID: paymentMethod.id, - paymentMethodLast4: paymentMethod.card!.last4, + paymentMethodLast4: paymentMethod.card?.last4 ?? null, + paymentMethodType: paymentMethod.type, reload: true, reloadError: null, timeReloadError: null, diff --git a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.module.css index 123bb1c86..b562dcd45 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.module.css @@ -70,6 +70,12 @@ font-weight: 500; color: var(--color-text); } + + [data-slot="type"] { + font-size: var(--font-size-sm); + font-weight: 400; + color: var(--color-text-muted); + } } } diff --git a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx index f9084bbf1..af4a47e48 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx @@ -1,8 +1,8 @@ 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 { withActor } from "~/context/auth.withActor" -import { IconCreditCard } from "~/component/icon" +import { IconCreditCard, IconStripe } from "~/component/icon" import styles from "./billing-section.module.css" import { Database, eq } from "@opencode-ai/console-core/drizzle/index.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 // const balanceInfo = () => ({ // balance: 0, + // paymentMethodType: null as string | null, // paymentMethodLast4: null as string | null, // reload: false, // reloadError: null as string | null, @@ -70,6 +71,7 @@ export function BillingSection() { // Scenario 2: User has not added billing details but has a balance // const balanceInfo = () => ({ // balance: 1500000000, // $15.00 + // paymentMethodType: null as string | null, // paymentMethodLast4: null as string | null, // reload: false, // reloadError: null as string | null, @@ -79,6 +81,7 @@ export function BillingSection() { // Scenario 3: User has added billing details (reload enabled) // const balanceInfo = () => ({ // balance: 750000000, // $7.50 + // paymentMethodType: "card", // paymentMethodLast4: "4242", // reload: true, // reloadError: null as string | null, @@ -88,12 +91,23 @@ export function BillingSection() { // Scenario 4: User has billing details but reload failed // const balanceInfo = () => ({ // balance: 250000000, // $2.50 + // paymentMethodType: "card", // paymentMethodLast4: "4242", // reload: true, // reloadError: "Your card was declined." as string, // 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(() => { return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2) }) @@ -136,13 +150,25 @@ export function BillingSection() {
- + }> + + + +
- ----}> - •••• - {balanceInfo()?.paymentMethodLast4} - + ----}> + •••• + {balanceInfo()?.paymentMethodLast4} + + } + > + + Linked to Stripe + +
-- cgit v1.2.3