Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apps/mobile/app/(tabs)/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useSettings } from "@/store/settings.store"
import { useConnections } from "@/store/connection.store"
import { useAuth } from "@/store/auth.store"
import { getAccountNotificationSettings, registerPushDevice, updateAccountNotificationSettings } from "@/lib/account-notifications"
import { createBillingCheckout, createBillingPortal } from "@/lib/billing"
import { requestNotificationsPermission } from "@/lib/notifications"
import { TunnelUsageCard } from "@/components/TunnelUsageCard"
import { OpencodeStatsCard } from "@/components/OpencodeStatsCard"
Expand Down Expand Up @@ -117,6 +118,14 @@ export default function UserPage() {
)
}

const openBilling = async (action: "checkout" | "portal", tier?: "starter" | "builder") => {
if (!serverUrl || !sessionToken) return
const url = action === "portal"
? await createBillingPortal(serverUrl, sessionToken)
: await createBillingCheckout(serverUrl, sessionToken, tier || "starter")
if (url) await Linking.openURL(url)
}

const version = Constants.expoConfig?.version ?? "1.0.0"
const buildNumber = Constants.expoConfig?.ios?.buildNumber ?? Constants.expoConfig?.android?.versionCode ?? undefined
const versionDisplay = buildNumber ? `${version} (${buildNumber})` : version
Expand Down Expand Up @@ -163,6 +172,26 @@ export default function UserPage() {
</CardContent>
</Card>

{isLoggedIn && user && (
<Card>
<CardHeader>
<CardTitle>Subscription</CardTitle>
<CardDescription>Manage your CrossCode plan</CardDescription>
</CardHeader>
<CardContent className="gap-3">
<Text className="text-sm capitalize">Current tier: {user.tier}</Text>
{user.tier === "free" ? (
<View className="gap-2">
<Button onPress={() => openBilling("checkout", "starter")}><Text>Subscribe to Starter</Text></Button>
<Button variant="outline" onPress={() => openBilling("checkout", "builder")}><Text>Subscribe to Builder</Text></Button>
</View>
) : (
<Button variant="outline" onPress={() => openBilling("portal")}><Text>Manage billing</Text></Button>
)}
</CardContent>
</Card>
)}

<Card>
<CardHeader>
<CardTitle>Appearance</CardTitle>
Expand Down
25 changes: 25 additions & 0 deletions apps/mobile/lib/billing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export async function createBillingCheckout(
serverUrl: string,
sessionToken: string,
tier: "starter" | "builder",
cycle: "monthly" | "yearly" = "monthly",
): Promise<string | null> {
const response = await fetch(`${serverUrl}/api/billing/checkout`, {
method: "POST",
headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json" },
body: JSON.stringify({ tier, cycle }),
})
if (!response.ok) return null
const data = await response.json()
return data.checkoutUrl ?? null
}

export async function createBillingPortal(serverUrl: string, sessionToken: string): Promise<string | null> {
const response = await fetch(`${serverUrl}/api/billing/portal`, {
method: "POST",
headers: { Authorization: `Bearer ${sessionToken}` },
})
if (!response.ok) return null
const data = await response.json()
return data.url ?? null
}
9 changes: 9 additions & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ NEXT_PUBLIC_APP_URL=https://crosscode.site
NEXT_PUBLIC_SANITY_PROJECT_ID=
NEXT_PUBLIC_SANITY_DATASET=production
SANITY_API_READ_TOKEN=

# DoDo Payments
DODO_PAYMENTS_API_KEY=
DODO_PAYMENTS_WEBHOOK_KEY=
DODO_PAYMENTS_ENVIRONMENT=live_mode
DODO_PRODUCT_STARTER_MONTHLY=
DODO_PRODUCT_STARTER_YEARLY=
DODO_PRODUCT_BUILDER_MONTHLY=
DODO_PRODUCT_BUILDER_YEARLY=
13 changes: 13 additions & 0 deletions apps/web/drizzle/0000_dodo_billing.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "dodo_customer_id" text;
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "dodo_subscription_id" text;
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "subscription_status" text;
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "subscription_product_id" text;
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "subscription_renews_at" timestamp;
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "subscription_cancel_at_period_end" boolean DEFAULT false NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS "user_dodo_customer_id_unique" ON "user" ("dodo_customer_id");
CREATE UNIQUE INDEX IF NOT EXISTS "user_dodo_subscription_id_unique" ON "user" ("dodo_subscription_id");
CREATE TABLE IF NOT EXISTS "dodo_webhook_event" (
"id" text PRIMARY KEY NOT NULL,
"type" text NOT NULL,
"processed_at" timestamp DEFAULT now() NOT NULL
);
Loading
Loading