Przeglądaj źródła

core: credit referral invites on first Lite checkout

Applies saved invites during Lite checkout so users who skip the referral page still credit the inviter. Avoids crediting already-active Lite workspaces so rewards stay tied to the first upgrade.
vimtor 3 miesięcy temu
rodzic
commit
6a2cd816b7

+ 2 - 14
packages/console/app/src/component/go-referral.tsx

@@ -2,7 +2,6 @@ import { action, json, query, useAction, useSubmission } from "@solidjs/router"
 import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
 import { getRequestEvent } from "solid-js/web"
 import { Referral } from "@opencode-ai/console-core/referral.js"
-import { Actor } from "@opencode-ai/console-core/actor.js"
 import { withActor } from "~/context/auth.withActor"
 import { Modal } from "~/component/modal"
 import { IconCheck, IconCopy } from "~/component/icon"
@@ -10,7 +9,7 @@ import { useI18n } from "~/context/i18n"
 import { useLanguage } from "~/context/language"
 import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
 import { queryLiteSubscription } from "~/routes/workspace/[id]/go/lite-section"
-import { clearReferralCookie, referralCodeFromCookieHeader } from "~/lib/referral-invite"
+import { createReferralFromCookie } from "~/lib/referral-invite"
 import "./go-referral.css"
 
 type GoReferralSummary = Awaited<ReturnType<typeof Referral.summary>>
@@ -28,18 +27,7 @@ const emptyUsagePreview = {
 export const queryGoReferral = query(async (workspaceID: string) => {
   "use server"
   return withActor(async () => {
-    const event = getRequestEvent()
-    const referralCode = referralCodeFromCookieHeader(event?.request.headers.get("cookie") ?? null)
-    if (referralCode) {
-      await Referral.createFromAccount({
-        accountID: Actor.account(),
-        referralCode,
-      }).catch((error) => {
-        console.error("Referral create failed", error)
-      })
-      event?.response.headers.append("set-cookie", clearReferralCookie())
-    }
-
+    await createReferralFromCookie()
     return Referral.summary()
   }, workspaceID)
 }, "go.referral.get")

+ 16 - 0
packages/console/app/src/lib/referral-invite.ts

@@ -1,4 +1,6 @@
+import { Actor } from "@opencode-ai/console-core/actor.js"
 import { Referral } from "@opencode-ai/console-core/referral.js"
+import { getRequestEvent } from "solid-js/web"
 
 const REFERRAL_COOKIE = "oc_referral"
 const REFERRAL_MAX_AGE = 60 * 60 * 24 * 30
@@ -26,3 +28,17 @@ export function referralCodeFromCookieHeader(header: string | null) {
       ?.slice(`${REFERRAL_COOKIE}=`.length),
   )
 }
+
+export async function createReferralFromCookie() {
+  const event = getRequestEvent()
+  const referralCode = referralCodeFromCookieHeader(event?.request.headers.get("cookie") ?? null)
+  if (!referralCode) return
+
+  await Referral.createFromAccount({
+    accountID: Actor.account(),
+    referralCode,
+  }).catch((error) => {
+    console.error("Referral create failed", error)
+  })
+  event?.response.headers.append("set-cookie", clearReferralCookie())
+}

+ 10 - 8
packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx

@@ -15,6 +15,7 @@ import { useI18n } from "~/context/i18n"
 import { useLanguage } from "~/context/language"
 import { formError } from "~/lib/form-error"
 import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
+import { createReferralFromCookie } from "~/lib/referral-invite"
 
 import { IconAlipay, IconUpi } from "~/component/icon"
 
@@ -75,15 +76,16 @@ const createLiteCheckoutUrl = action(
     "use server"
     return json(
       await withActor(
-        () =>
-          Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl, method })
-            .then((data) => ({ error: undefined, data }))
-            .catch((e) => ({
-              error: e.message as string,
-              data: undefined,
-            })),
+        async () => {
+          const data = await Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl, method })
+          await createReferralFromCookie()
+          return { error: undefined, data }
+        },
         workspaceID,
-      ),
+      ).catch((e) => ({
+        error: e.message as string,
+        data: undefined,
+      })),
       { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
     )
   },

+ 7 - 0
packages/console/core/src/referral.ts

@@ -325,6 +325,13 @@ export namespace Referral {
         .then((rows) => rows.map((row) => row.workspaceID))
       if (workspaceIDs.length === 0) return
 
+      const activeLite = await tx
+        .select({ id: LiteTable.id })
+        .from(LiteTable)
+        .where(and(inArray(LiteTable.workspaceID, workspaceIDs), isNull(LiteTable.timeDeleted)))
+        .then((rows) => rows[0])
+      if (activeLite) return
+
       const litePayment = await tx
         .select({ id: PaymentTable.id })
         .from(PaymentTable)