mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
fixed to use hooks
This commit is contained in:
@@ -33,8 +33,13 @@ export async function updateSeatCount(userId: number) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stripeSubscriptionItemId = subscription.stripeSubscriptionItemId;
|
||||
if (!stripeSubscriptionItemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update stripe
|
||||
await stripe.subscriptionItems.update(subscription.stripeSubscriptionItemId!, {
|
||||
await stripe.subscriptionItems.update(stripeSubscriptionItemId, {
|
||||
quantity: newQuantity,
|
||||
proration_behavior: "always_invoice",
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { BunRequest } from "bun";
|
||||
import { withAuth, withCors, withCSRF } from "../../auth/middleware";
|
||||
import { type AuthedRequest, withAuth, withCors, withCSRF } from "../../auth/middleware";
|
||||
import { getOrganisationMembers, getOrganisationsByUserId } from "../../db/queries/organisations";
|
||||
import { getUserById } from "../../db/queries/users";
|
||||
import { STRIPE_PRICE_ANNUAL, STRIPE_PRICE_MONTHLY, stripe } from "../../stripe/client";
|
||||
@@ -7,7 +6,7 @@ import { errorResponse } from "../../validation";
|
||||
|
||||
const BASE_URL = process.env.FRONTEND_URL || "http://localhost:1420";
|
||||
|
||||
async function handler(req: BunRequest) {
|
||||
async function handler(req: AuthedRequest) {
|
||||
if (req.method !== "POST") {
|
||||
return errorResponse("method not allowed", "METHOD_NOT_ALLOWED", 405);
|
||||
}
|
||||
@@ -20,7 +19,7 @@ async function handler(req: BunRequest) {
|
||||
return errorResponse("missing required fields", "VALIDATION_ERROR", 400);
|
||||
}
|
||||
|
||||
const userId = (req as any).userId;
|
||||
const { userId } = req;
|
||||
const user = await getUserById(userId);
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import type { BunRequest } from "bun";
|
||||
import { withAuth, withCors, withCSRF } from "../../auth/middleware";
|
||||
import { type AuthedRequest, withAuth, withCors, withCSRF } from "../../auth/middleware";
|
||||
import { getSubscriptionByUserId } from "../../db/queries/subscriptions";
|
||||
import { stripe } from "../../stripe/client";
|
||||
import { errorResponse } from "../../validation";
|
||||
|
||||
const BASE_URL = process.env.FRONTEND_URL || "http://localhost:1420";
|
||||
|
||||
async function handler(req: BunRequest) {
|
||||
async function handler(req: AuthedRequest) {
|
||||
if (req.method !== "POST") {
|
||||
return errorResponse("method not allowed", "METHOD_NOT_ALLOWED", 405);
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const { userId } = req;
|
||||
const subscription = await getSubscriptionByUserId(userId);
|
||||
if (!subscription?.stripeCustomerId) {
|
||||
return errorResponse("no active subscription found", "NOT_FOUND", 404);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import type { BunRequest } from "bun";
|
||||
import { withAuth, withCors } from "../../auth/middleware";
|
||||
import { type AuthedRequest, withAuth, withCors } from "../../auth/middleware";
|
||||
import { getSubscriptionByUserId } from "../../db/queries/subscriptions";
|
||||
import { errorResponse } from "../../validation";
|
||||
|
||||
async function handler(req: BunRequest) {
|
||||
async function handler(req: AuthedRequest) {
|
||||
if (req.method !== "GET") {
|
||||
return errorResponse("method not allowed", "METHOD_NOT_ALLOWED", 405);
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = (req as any).userId;
|
||||
const { userId } = req;
|
||||
const subscription = await getSubscriptionByUserId(userId);
|
||||
|
||||
return new Response(JSON.stringify({ subscription }), {
|
||||
|
||||
@@ -9,7 +9,15 @@ import {
|
||||
import { updateUser } from "../../db/queries/users";
|
||||
import { stripe } from "../../stripe/client";
|
||||
|
||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET || "";
|
||||
const webhookSecret = requireEnv("STRIPE_WEBHOOK_SECRET");
|
||||
|
||||
function requireEnv(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`${name} is required`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export default async function webhook(req: BunRequest) {
|
||||
if (req.method !== "POST") {
|
||||
@@ -61,6 +69,13 @@ export default async function webhook(req: BunRequest) {
|
||||
break;
|
||||
}
|
||||
|
||||
// stripe types use snake_case for these fields
|
||||
const sub = stripeSubscription as unknown as {
|
||||
current_period_start: number;
|
||||
current_period_end: number;
|
||||
trial_end: number | null;
|
||||
};
|
||||
|
||||
await createSubscription({
|
||||
userId,
|
||||
stripeCustomerId: session.customer as string,
|
||||
@@ -69,11 +84,9 @@ export default async function webhook(req: BunRequest) {
|
||||
stripePriceId: session.metadata?.priceId || "",
|
||||
status: stripeSubscription.status,
|
||||
quantity: parseInt(session.metadata?.quantity || "1", 10),
|
||||
currentPeriodStart: new Date((stripeSubscription as any).current_period_start * 1000),
|
||||
currentPeriodEnd: new Date((stripeSubscription as any).current_period_end * 1000),
|
||||
trialEnd: stripeSubscription.trial_end
|
||||
? new Date(stripeSubscription.trial_end * 1000)
|
||||
: undefined,
|
||||
currentPeriodStart: new Date(sub.current_period_start * 1000),
|
||||
currentPeriodEnd: new Date(sub.current_period_end * 1000),
|
||||
trialEnd: sub.trial_end ? new Date(sub.trial_end * 1000) : undefined,
|
||||
});
|
||||
|
||||
await updateUser(userId, { plan: "pro" });
|
||||
@@ -99,11 +112,16 @@ export default async function webhook(req: BunRequest) {
|
||||
break;
|
||||
}
|
||||
// safely convert timestamps to dates
|
||||
const currentPeriodStart = (subscription as any).current_period_start
|
||||
? new Date((subscription as any).current_period_start * 1000)
|
||||
// stripe types use snake_case for these fields
|
||||
const sub = subscription as unknown as {
|
||||
current_period_start: number | null;
|
||||
current_period_end: number | null;
|
||||
};
|
||||
const currentPeriodStart = sub.current_period_start
|
||||
? new Date(sub.current_period_start * 1000)
|
||||
: undefined;
|
||||
const currentPeriodEnd = (subscription as any).current_period_end
|
||||
? new Date((subscription as any).current_period_end * 1000)
|
||||
const currentPeriodEnd = sub.current_period_end
|
||||
? new Date(sub.current_period_end * 1000)
|
||||
: undefined;
|
||||
|
||||
await updateSubscription(localSub.id, {
|
||||
@@ -136,34 +154,45 @@ export default async function webhook(req: BunRequest) {
|
||||
case "invoice.payment_succeeded": {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
|
||||
if (!(invoice as any).subscription) break;
|
||||
// stripe types use snake_case for these fields
|
||||
const inv = invoice as unknown as {
|
||||
subscription: string | null;
|
||||
payment_intent: string | null;
|
||||
};
|
||||
|
||||
const localSub = await getSubscriptionByStripeId((invoice as any).subscription as string);
|
||||
if (!inv.subscription) break;
|
||||
|
||||
const localSub = await getSubscriptionByStripeId(inv.subscription);
|
||||
if (!localSub) break;
|
||||
|
||||
await createPayment({
|
||||
subscriptionId: localSub.id,
|
||||
stripePaymentIntentId: (invoice as any).payment_intent as string,
|
||||
stripePaymentIntentId: inv.payment_intent || "",
|
||||
amount: invoice.amount_paid,
|
||||
currency: invoice.currency,
|
||||
status: "succeeded",
|
||||
});
|
||||
|
||||
console.log(`payment recorded for subscription ${(invoice as any).subscription}`);
|
||||
console.log(`payment recorded for subscription ${inv.subscription}`);
|
||||
break;
|
||||
}
|
||||
|
||||
case "invoice.payment_failed": {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
|
||||
if (!(invoice as any).subscription) break;
|
||||
// stripe types use snake_case for these fields
|
||||
const inv = invoice as unknown as {
|
||||
subscription: string | null;
|
||||
};
|
||||
|
||||
const localSub = await getSubscriptionByStripeId((invoice as any).subscription as string);
|
||||
if (!inv.subscription) break;
|
||||
|
||||
const localSub = await getSubscriptionByStripeId(inv.subscription);
|
||||
if (!localSub) break;
|
||||
|
||||
await updateSubscription(localSub.id, { status: "past_due" });
|
||||
|
||||
console.log(`payment failed for subscription ${(invoice as any).subscription}`);
|
||||
console.log(`payment failed for subscription ${inv.subscription}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import Stripe from "stripe";
|
||||
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
|
||||
|
||||
if (!stripeSecretKey) {
|
||||
throw new Error("STRIPE_SECRET_KEY is required");
|
||||
}
|
||||
const stripeSecretKey = requireEnv("STRIPE_SECRET_KEY");
|
||||
|
||||
export const stripe = new Stripe(stripeSecretKey, {
|
||||
apiVersion: "2024-12-18.acacia",
|
||||
apiVersion: "2025-12-15.clover",
|
||||
});
|
||||
|
||||
export const STRIPE_PRICE_MONTHLY = process.env.STRIPE_PRICE_MONTHLY!;
|
||||
export const STRIPE_PRICE_ANNUAL = process.env.STRIPE_PRICE_ANNUAL!;
|
||||
export const STRIPE_PRICE_MONTHLY = requireEnv("STRIPE_PRICE_MONTHLY");
|
||||
export const STRIPE_PRICE_ANNUAL = requireEnv("STRIPE_PRICE_ANNUAL");
|
||||
|
||||
function requireEnv(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`${name} is required`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user