chore: ai, public course and other ui improvements
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { Polar } from '@polar-sh/sdk';
|
||||
import { TOKEN_PACK } from '@cio/utils/plans';
|
||||
import { dev } from '$app/environment';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
export const GET = async ({ url }: { url: URL }) => {
|
||||
const quantityRaw = Number(url.searchParams.get('quantity') ?? '1');
|
||||
const quantity = Math.min(100, Math.max(1, Number.isFinite(quantityRaw) ? Math.trunc(quantityRaw) : 1));
|
||||
|
||||
let metadata: { orgId?: string; orgSlug?: string; triggeredBy?: string };
|
||||
|
||||
try {
|
||||
metadata = JSON.parse(url.searchParams.get('metadata') ?? '{}');
|
||||
} catch {
|
||||
return new Response('Invalid metadata JSON', { status: 400 });
|
||||
}
|
||||
|
||||
if (!metadata.orgId || !metadata.orgSlug) {
|
||||
return new Response('Missing orgId / orgSlug in metadata', { status: 400 });
|
||||
}
|
||||
|
||||
const productId = env.POLAR_TOKEN_PACK_PRODUCT_ID;
|
||||
|
||||
if (!productId) {
|
||||
return new Response('POLAR_TOKEN_PACK_PRODUCT_ID is not configured', { status: 500 });
|
||||
}
|
||||
|
||||
if (!env.POLAR_ACCESS_TOKEN) {
|
||||
return new Response('POLAR_ACCESS_TOKEN is not configured', { status: 500 });
|
||||
}
|
||||
|
||||
const polar = new Polar({
|
||||
accessToken: env.POLAR_ACCESS_TOKEN,
|
||||
server: dev ? 'sandbox' : 'production'
|
||||
});
|
||||
|
||||
const totalPriceCents = TOKEN_PACK.PRICE_USD_CENTS * quantity;
|
||||
|
||||
const checkout = await polar.checkouts.create({
|
||||
products: [productId],
|
||||
prices: {
|
||||
[productId]: [
|
||||
{
|
||||
amountType: 'fixed',
|
||||
priceAmount: totalPriceCents,
|
||||
priceCurrency: 'usd'
|
||||
}
|
||||
]
|
||||
},
|
||||
customerEmail: url.searchParams.get('customerEmail') ?? undefined,
|
||||
customerName: url.searchParams.get('customerName') ?? undefined,
|
||||
successUrl: `${url.origin}/org/${metadata.orgSlug}/settings/ai-credits?tokens=success`,
|
||||
metadata: {
|
||||
kind: 'token_pack',
|
||||
orgId: metadata.orgId,
|
||||
orgSlug: metadata.orgSlug,
|
||||
triggeredBy: metadata.triggeredBy ?? '',
|
||||
tokensPerUnit: String(TOKEN_PACK.TOKENS_PER_UNIT),
|
||||
quantity: String(quantity)
|
||||
}
|
||||
});
|
||||
|
||||
return Response.redirect(checkout.url, 303);
|
||||
};
|
||||
@@ -1,6 +1,12 @@
|
||||
import { OrgPlanApiServer } from '$features/org/api/org-plan.server';
|
||||
import { PLAN } from '@cio/utils/plans';
|
||||
import type { PolarWebhookPayload } from '$lib/utils/types/polar';
|
||||
import { CreditPurchaseApiServer } from '$features/agent/api/credit-purchase.server';
|
||||
import { PLAN, TOKEN_PACK } from '@cio/utils/plans';
|
||||
import type {
|
||||
PolarOrderWebhookPayload,
|
||||
PolarSubscriptionWebhookPayload,
|
||||
PolarWebhookPayload,
|
||||
SubscriptionData
|
||||
} from '$lib/utils/types/polar';
|
||||
import { Webhooks } from '@polar-sh/sveltekit';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
@@ -9,24 +15,86 @@ export const POST = Webhooks({
|
||||
onPayload
|
||||
});
|
||||
|
||||
function isSubscriptionPayload(payload: PolarWebhookPayload): payload is PolarSubscriptionWebhookPayload {
|
||||
return (
|
||||
payload.type === 'checkout.created' ||
|
||||
payload.type === 'checkout.updated' ||
|
||||
payload.type.startsWith('subscription.')
|
||||
);
|
||||
}
|
||||
|
||||
function isOrderPayload(payload: PolarWebhookPayload): payload is PolarOrderWebhookPayload {
|
||||
return payload.type === 'order.paid' || payload.type === 'order.created' || payload.type === 'order.refunded';
|
||||
}
|
||||
|
||||
async function onPayload(payload: PolarWebhookPayload) {
|
||||
const metadata = payload.data.metadata;
|
||||
console.log('Polar webhook', payload.type);
|
||||
|
||||
if (isOrderPayload(payload)) {
|
||||
if (payload.type !== 'order.paid') {
|
||||
return;
|
||||
}
|
||||
|
||||
const order = payload.data;
|
||||
const md = order.metadata ?? {};
|
||||
|
||||
if (md.kind !== 'token_pack' || !md.orgId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const quantityFromItems = order.items?.[0]?.quantity;
|
||||
const quantityParsed = Number(md.quantity ?? '1');
|
||||
const quantity =
|
||||
quantityFromItems != null && quantityFromItems > 0
|
||||
? quantityFromItems
|
||||
: Number.isFinite(quantityParsed) && quantityParsed > 0
|
||||
? Math.trunc(quantityParsed)
|
||||
: 1;
|
||||
const tokensPerUnit = Number(md.tokensPerUnit ?? '') || TOKEN_PACK.TOKENS_PER_UNIT;
|
||||
const tokens = quantity * tokensPerUnit;
|
||||
const totalCents = order.totalAmount ?? order.amount ?? 0;
|
||||
const unitPriceCents = quantity > 0 ? Math.round(totalCents / quantity) : totalCents;
|
||||
|
||||
const triggeredByRaw = md.triggeredBy?.trim();
|
||||
let triggeredBy: string | undefined;
|
||||
|
||||
if (
|
||||
triggeredByRaw &&
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(triggeredByRaw)
|
||||
) {
|
||||
triggeredBy = triggeredByRaw;
|
||||
}
|
||||
|
||||
try {
|
||||
await CreditPurchaseApiServer.recordPurchase({
|
||||
orgId: md.orgId,
|
||||
triggeredBy,
|
||||
providerOrderId: order.id,
|
||||
tokens,
|
||||
quantity,
|
||||
unitPriceCents,
|
||||
currency: order.currency ?? 'USD',
|
||||
payload: order as unknown as Record<string, unknown>
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error recording credit purchase', error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSubscriptionPayload(payload)) {
|
||||
console.log('Unknown Polar event', payload.type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const data = payload.data as SubscriptionData;
|
||||
const metadata = data.metadata;
|
||||
const orgId = metadata.orgId;
|
||||
const triggeredBy = metadata.triggeredBy;
|
||||
const subscriptionId = payload.data.id;
|
||||
const isSubscriptionActive = payload.data.status === 'active';
|
||||
|
||||
console.log({
|
||||
orgId,
|
||||
triggeredBy,
|
||||
isSubscriptionActive,
|
||||
subscriptionId
|
||||
});
|
||||
|
||||
if (!orgId) {
|
||||
console.error('Org ID is required');
|
||||
throw new Error('Org ID is required');
|
||||
}
|
||||
const subscriptionId = data.id;
|
||||
const isSubscriptionActive = data.status === 'active';
|
||||
|
||||
switch (payload.type) {
|
||||
case 'checkout.created':
|
||||
@@ -34,14 +102,24 @@ async function onPayload(payload: PolarWebhookPayload) {
|
||||
case 'checkout.updated':
|
||||
break;
|
||||
case 'subscription.created':
|
||||
if (metadata.kind === 'token_pack') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!orgId || triggeredBy === undefined || triggeredBy === '') {
|
||||
console.error('subscription.created missing org metadata');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (isSubscriptionActive) {
|
||||
try {
|
||||
const planData = {
|
||||
orgId,
|
||||
triggeredBy: parseInt(triggeredBy),
|
||||
triggeredBy: parseInt(triggeredBy, 10),
|
||||
planName: PLAN.EARLY_ADOPTER as 'EARLY_ADOPTER' | 'ENTERPRISE' | 'BASIC',
|
||||
subscriptionId,
|
||||
payload: payload.data as unknown as Record<string, unknown>
|
||||
payload: data as unknown as Record<string, unknown>
|
||||
};
|
||||
|
||||
const result = await OrgPlanApiServer.createOrgPlan(planData);
|
||||
@@ -50,31 +128,31 @@ async function onPayload(payload: PolarWebhookPayload) {
|
||||
console.error('Error creating org plan', error);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'subscription.updated':
|
||||
if (!isSubscriptionActive) {
|
||||
// Handle Cancel Subscription
|
||||
try {
|
||||
const result = await OrgPlanApiServer.cancelOrgPlan({
|
||||
subscriptionId,
|
||||
payload: payload.data as unknown as Record<string, unknown>
|
||||
payload: data as unknown as Record<string, unknown>
|
||||
});
|
||||
console.log('Subscription canceled', result);
|
||||
} catch (error) {
|
||||
console.error('Error canceling org plan', error);
|
||||
}
|
||||
} else {
|
||||
// Handle Update Subscription
|
||||
try {
|
||||
const result = await OrgPlanApiServer.updateOrgPlan({
|
||||
subscriptionId,
|
||||
payload: payload.data as unknown as Record<string, unknown>
|
||||
payload: data as unknown as Record<string, unknown>
|
||||
});
|
||||
console.log('Subscription updated', result);
|
||||
} catch (error) {
|
||||
console.error('Error updating org plan', error);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'subscription.active':
|
||||
break;
|
||||
@@ -83,7 +161,9 @@ async function onPayload(payload: PolarWebhookPayload) {
|
||||
case 'subscription.canceled':
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown event', payload.type);
|
||||
// Exhaustive over `PolarSubscriptionWebhookPayload`; kept for future Polar event types.
|
||||
console.log('Unknown subscription-related event');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"type": "order.paid",
|
||||
"data": {
|
||||
"id": "ord_fixture_001",
|
||||
"amount": 500,
|
||||
"totalAmount": 1500,
|
||||
"currency": "usd",
|
||||
"metadata": {
|
||||
"kind": "token_pack",
|
||||
"orgId": "00000000-0000-4000-8000-000000000001",
|
||||
"orgSlug": "demo-org",
|
||||
"triggeredBy": "00000000-0000-4000-8000-000000000002",
|
||||
"tokensPerUnit": "2000000",
|
||||
"quantity": "3"
|
||||
},
|
||||
"items": [{ "quantity": 3, "amount": 1500 }]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user