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.
This commit is contained in:
@@ -2,7 +2,6 @@ import { action, json, query, useAction, useSubmission } from "@solidjs/router"
|
|||||||
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
|
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
|
||||||
import { getRequestEvent } from "solid-js/web"
|
import { getRequestEvent } from "solid-js/web"
|
||||||
import { Referral } from "@opencode-ai/console-core/referral.js"
|
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 { withActor } from "~/context/auth.withActor"
|
||||||
import { Modal } from "~/component/modal"
|
import { Modal } from "~/component/modal"
|
||||||
import { IconCheck, IconCopy } from "~/component/icon"
|
import { IconCheck, IconCopy } from "~/component/icon"
|
||||||
@@ -10,7 +9,7 @@ import { useI18n } from "~/context/i18n"
|
|||||||
import { useLanguage } from "~/context/language"
|
import { useLanguage } from "~/context/language"
|
||||||
import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
||||||
import { queryLiteSubscription } from "~/routes/workspace/[id]/go/lite-section"
|
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"
|
import "./go-referral.css"
|
||||||
|
|
||||||
type GoReferralSummary = Awaited<ReturnType<typeof Referral.summary>>
|
type GoReferralSummary = Awaited<ReturnType<typeof Referral.summary>>
|
||||||
@@ -28,18 +27,7 @@ const emptyUsagePreview = {
|
|||||||
export const queryGoReferral = query(async (workspaceID: string) => {
|
export const queryGoReferral = query(async (workspaceID: string) => {
|
||||||
"use server"
|
"use server"
|
||||||
return withActor(async () => {
|
return withActor(async () => {
|
||||||
const event = getRequestEvent()
|
await createReferralFromCookie()
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
return Referral.summary()
|
return Referral.summary()
|
||||||
}, workspaceID)
|
}, workspaceID)
|
||||||
}, "go.referral.get")
|
}, "go.referral.get")
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
import { Referral } from "@opencode-ai/console-core/referral.js"
|
import { Referral } from "@opencode-ai/console-core/referral.js"
|
||||||
|
import { getRequestEvent } from "solid-js/web"
|
||||||
|
|
||||||
const REFERRAL_COOKIE = "oc_referral"
|
const REFERRAL_COOKIE = "oc_referral"
|
||||||
const REFERRAL_MAX_AGE = 60 * 60 * 24 * 30
|
const REFERRAL_MAX_AGE = 60 * 60 * 24 * 30
|
||||||
@@ -26,3 +28,17 @@ export function referralCodeFromCookieHeader(header: string | null) {
|
|||||||
?.slice(`${REFERRAL_COOKIE}=`.length),
|
?.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())
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { useI18n } from "~/context/i18n"
|
|||||||
import { useLanguage } from "~/context/language"
|
import { useLanguage } from "~/context/language"
|
||||||
import { formError } from "~/lib/form-error"
|
import { formError } from "~/lib/form-error"
|
||||||
import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
||||||
|
import { createReferralFromCookie } from "~/lib/referral-invite"
|
||||||
|
|
||||||
import { IconAlipay, IconUpi } from "~/component/icon"
|
import { IconAlipay, IconUpi } from "~/component/icon"
|
||||||
|
|
||||||
@@ -75,15 +76,16 @@ const createLiteCheckoutUrl = action(
|
|||||||
"use server"
|
"use server"
|
||||||
return json(
|
return json(
|
||||||
await withActor(
|
await withActor(
|
||||||
() =>
|
async () => {
|
||||||
Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl, method })
|
const data = await Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl, method })
|
||||||
.then((data) => ({ error: undefined, data }))
|
await createReferralFromCookie()
|
||||||
.catch((e) => ({
|
return { error: undefined, data }
|
||||||
error: e.message as string,
|
},
|
||||||
data: undefined,
|
|
||||||
})),
|
|
||||||
workspaceID,
|
workspaceID,
|
||||||
),
|
).catch((e) => ({
|
||||||
|
error: e.message as string,
|
||||||
|
data: undefined,
|
||||||
|
})),
|
||||||
{ revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
|
{ revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -325,6 +325,13 @@ export namespace Referral {
|
|||||||
.then((rows) => rows.map((row) => row.workspaceID))
|
.then((rows) => rows.map((row) => row.workspaceID))
|
||||||
if (workspaceIDs.length === 0) return
|
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
|
const litePayment = await tx
|
||||||
.select({ id: PaymentTable.id })
|
.select({ id: PaymentTable.id })
|
||||||
.from(PaymentTable)
|
.from(PaymentTable)
|
||||||
|
|||||||
Reference in New Issue
Block a user