Skip to content

Commit 9d5d64f

Browse files
committed
Split Stripe webhook endpoints
1 parent f10d050 commit 9d5d64f

47 files changed

Lines changed: 6629 additions & 148 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MVP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -799,8 +799,8 @@ X_CALLBACK_URL=https://claws.supply/api/auth/x/callback
799799
# Stripe
800800
STRIPE_SECRET_KEY=sk_...
801801
STRIPE_PUBLISHABLE_KEY=pk_...
802-
STRIPE_WEBHOOK_SECRET=whsec_...
803-
STRIPE_ADS_WEBHOOK_SECRET=whsec_...
802+
STRIPE_PLATFORM_WEBHOOK_SECRET=whsec_...
803+
STRIPE_CONNECT_WEBHOOK_SECRET=whsec_...
804804
STRIPE_AD_PRICE_SIDEBAR_SANDBOX=price_...
805805
STRIPE_AD_PRICE_RESULTS_SANDBOX=price_...
806806
STRIPE_AD_PRICE_BOTH_SANDBOX=price_...

apps/web/app/advertise/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import type { Metadata } from "next";
22
import { OpenClawPageShell } from "@/components/openclaw-page-shell";
33
import { AdvertisePageClient } from "@/components/ads/advertise-page-client";
44
import { getSessionFromNextHeaders } from "@/lib/auth/session";
5+
import { buildSeoMetadata } from "@/lib/seo";
56

6-
export const metadata: Metadata = {
7+
export const metadata: Metadata = buildSeoMetadata({
78
title: "Advertise on Claws.supply",
89
description:
910
"Reach high-intent OpenClaw template buyers with sponsored placements across Claws.supply.",
10-
};
11+
path: "/advertise",
12+
});
1113

1214
export const dynamic = "force-dynamic";
1315

apps/web/app/api/og/route.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { ImageResponse } from "next/og";
2+
3+
export const runtime = "edge";
4+
5+
const OG_WIDTH = 1200;
6+
const OG_HEIGHT = 630;
7+
const DEFAULT_TITLE = "Claws.supply";
8+
const DEFAULT_DESCRIPTION = "OpenClaw AI agent template marketplace.";
9+
10+
function truncateText(value: string, maxLength: number) {
11+
const normalized = value.trim();
12+
13+
if (normalized.length <= maxLength) {
14+
return normalized;
15+
}
16+
17+
return `${normalized.slice(0, Math.max(maxLength - 1, 1)).trimEnd()}…`;
18+
}
19+
20+
export function GET(request: Request) {
21+
const { searchParams } = new URL(request.url);
22+
const title = truncateText(searchParams.get("title") ?? DEFAULT_TITLE, 110);
23+
const description = truncateText(
24+
searchParams.get("description") ?? DEFAULT_DESCRIPTION,
25+
180,
26+
);
27+
28+
return new ImageResponse(
29+
(
30+
<div
31+
style={{
32+
display: "flex",
33+
width: "100%",
34+
height: "100%",
35+
background: "linear-gradient(135deg, #070707 0%, #121212 60%, #1e1e1e 100%)",
36+
color: "#f5f5f5",
37+
position: "relative",
38+
fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif",
39+
}}
40+
>
41+
<div
42+
style={{
43+
position: "absolute",
44+
inset: 0,
45+
backgroundImage:
46+
"linear-gradient(to right, rgba(255,255,255,0.08) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.08) 1px, transparent 1px)",
47+
backgroundSize: "120px 120px",
48+
opacity: 0.25,
49+
}}
50+
/>
51+
<div
52+
style={{
53+
display: "flex",
54+
flexDirection: "column",
55+
justifyContent: "space-between",
56+
width: "100%",
57+
padding: "56px 64px",
58+
position: "relative",
59+
}}
60+
>
61+
<div
62+
style={{
63+
fontSize: 26,
64+
letterSpacing: "0.18em",
65+
textTransform: "uppercase",
66+
color: "#f97316",
67+
fontWeight: 700,
68+
}}
69+
>
70+
claws.supply
71+
</div>
72+
<div style={{ display: "flex", flexDirection: "column", gap: 18, maxWidth: 980 }}>
73+
<div
74+
style={{
75+
fontSize: title.length > 60 ? 58 : 70,
76+
lineHeight: 1.08,
77+
fontWeight: 700,
78+
letterSpacing: "-0.02em",
79+
}}
80+
>
81+
{title}
82+
</div>
83+
<div
84+
style={{
85+
fontSize: 34,
86+
lineHeight: 1.25,
87+
color: "#d4d4d8",
88+
letterSpacing: "-0.01em",
89+
}}
90+
>
91+
{description}
92+
</div>
93+
</div>
94+
</div>
95+
</div>
96+
),
97+
{
98+
width: OG_WIDTH,
99+
height: OG_HEIGHT,
100+
},
101+
);
102+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { jsonSuccess } from "@/lib/api/response";
2+
import { getBaseUrlFromRequest } from "@/lib/api/request";
3+
import {
4+
handleRouteError,
5+
requireSessionOrThrow,
6+
} from "@/lib/api/route-helpers";
7+
import { parseJsonBodyWithSchema } from "@/lib/api/validation";
8+
import { createPurchaseCheckoutSchema } from "@/lib/purchases/schemas";
9+
import { createTemplatePurchaseCheckout } from "@/lib/purchases/service";
10+
11+
export async function POST(request: Request) {
12+
try {
13+
const session = await requireSessionOrThrow(request);
14+
const input = await parseJsonBodyWithSchema(request, createPurchaseCheckoutSchema);
15+
const result = await createTemplatePurchaseCheckout({
16+
buyerId: session.user.id,
17+
input,
18+
baseUrl: getBaseUrlFromRequest(request),
19+
});
20+
21+
return jsonSuccess(result, {
22+
status: result.flow === "paid" ? 201 : 200,
23+
});
24+
} catch (error) {
25+
return handleRouteError(error, {
26+
message: "Unable to start template checkout.",
27+
code: "PURCHASE_CHECKOUT_ERROR",
28+
status: 400,
29+
});
30+
}
31+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { jsonSuccess } from "@/lib/api/response";
2+
import { handleRouteError } from "@/lib/api/route-helpers";
3+
import {
4+
parseConnectStripeWebhookEvent,
5+
processConnectStripeWebhookEvent,
6+
} from "@/lib/stripe-connect-webhooks";
7+
8+
export async function POST(request: Request) {
9+
try {
10+
const signature = request.headers.get("stripe-signature");
11+
const rawBody = await request.text();
12+
const event = parseConnectStripeWebhookEvent(rawBody, signature);
13+
14+
await processConnectStripeWebhookEvent(event);
15+
16+
return jsonSuccess({
17+
received: true,
18+
});
19+
} catch (error) {
20+
return handleRouteError(error, {
21+
message: "Unable to process Stripe connect webhook.",
22+
code: "STRIPE_CONNECT_WEBHOOK_ERROR",
23+
status: 400,
24+
});
25+
}
26+
}

apps/web/app/api/stripe/webhooks/route.ts renamed to apps/web/app/api/stripe/webhooks/platform/route.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import { jsonSuccess } from "@/lib/api/response";
22
import { handleRouteError } from "@/lib/api/route-helpers";
3-
import { parseStripeWebhookEvent, processStripeWebhookEvent } from "@/lib/ads/service";
3+
import {
4+
parsePlatformStripeWebhookEvent,
5+
processPlatformStripeWebhookEvent,
6+
} from "@/lib/ads/service";
47

58
export async function POST(request: Request) {
69
try {
710
const signature = request.headers.get("stripe-signature");
811
const rawBody = await request.text();
9-
const event = parseStripeWebhookEvent(rawBody, signature);
12+
const event = parsePlatformStripeWebhookEvent(rawBody, signature);
1013

11-
await processStripeWebhookEvent(event);
14+
await processPlatformStripeWebhookEvent(event);
1215

1316
return jsonSuccess({
1417
received: true,
1518
});
1619
} catch (error) {
1720
return handleRouteError(error, {
18-
message: "Unable to process Stripe webhook.",
19-
code: "STRIPE_WEBHOOK_ERROR",
21+
message: "Unable to process Stripe platform webhook.",
22+
code: "STRIPE_PLATFORM_WEBHOOK_ERROR",
2023
status: 400,
2124
});
2225
}
2326
}
24-

apps/web/app/auth/sign-in/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import type { Metadata } from "next";
12
import { SignInForm } from "@/components/auth/sign-in-form";
23
import { getSessionFromNextHeaders } from "@/lib/auth/session";
4+
import { buildNoindexMetadata } from "@/lib/seo";
35
import { redirect } from "next/navigation";
46

57
const DEFAULT_REDIRECT_PATH = "/profile";
68

9+
export const metadata: Metadata = buildNoindexMetadata({
10+
title: "Sign In — Claws.supply",
11+
description: "Sign in to access your Claws.supply account and template workspace.",
12+
path: "/auth/sign-in",
13+
});
14+
715
type SignInSearchParams = {
816
next?: string | string[];
917
};

apps/web/app/auth/sign-up/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import type { Metadata } from "next";
12
import { SignUpForm } from "@/components/auth/sign-up-form";
23
import { getSessionFromNextHeaders } from "@/lib/auth/session";
4+
import { buildNoindexMetadata } from "@/lib/seo";
35
import { redirect } from "next/navigation";
46

57
const DEFAULT_REDIRECT_PATH = "/profile";
68

9+
export const metadata: Metadata = buildNoindexMetadata({
10+
title: "Create Account — Claws.supply",
11+
description: "Create a Claws.supply account to buy, publish, and manage OpenClaw templates.",
12+
path: "/auth/sign-up",
13+
});
14+
715
type SignUpSearchParams = {
816
next?: string | string[];
917
};

apps/web/app/cli/auth/device/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import type { Metadata } from "next";
12
import { CliDeviceAuthPage } from "@/components/auth/cli-device-auth-page";
23
import { getSessionFromNextHeaders } from "@/lib/auth/session";
4+
import { buildNoindexMetadata } from "@/lib/seo";
35
import { redirect } from "next/navigation";
46

57
type DeviceAuthPageProps = {
@@ -8,6 +10,13 @@ type DeviceAuthPageProps = {
810
}>;
911
};
1012

13+
export const metadata: Metadata = buildNoindexMetadata({
14+
title: "Authorize CLI Device — Claws.supply",
15+
description:
16+
"Approve or deny Claws.supply CLI device authentication requests from your account.",
17+
path: "/cli/auth/device",
18+
});
19+
1120
export default async function CliDeviceAuthRoute({ searchParams }: DeviceAuthPageProps) {
1221
const params = await searchParams;
1322
const userCode = params.user_code?.trim();

apps/web/app/docs/[[...slug]]/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export async function generateMetadata({
5555
page.data.description ??
5656
"Practical docs for publishing, using, and selling OpenClaw templates.",
5757
path: page.url,
58+
ogType: "article",
5859
});
5960
}
6061

0 commit comments

Comments
 (0)