feat: 🎸 watch a vibe code cleanup specialist in action
cleaned up a bunch of crappy logs
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
submitBillingDataToVercel,
|
||||
submitInvoiceToVercel,
|
||||
} from "@/external/vercel/misc/vercelInvoicing.js";
|
||||
import { logVercelWebhook } from "@/external/vercel/misc/vercelMiddleware.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { FeatureService } from "@/internal/features/FeatureService.js";
|
||||
@@ -59,9 +60,6 @@ export const handleInvoiceFinalized = async ({
|
||||
|
||||
const subId = invoiceToSubId({ invoice });
|
||||
|
||||
console.log("subId", subId);
|
||||
console.log("will Invoice Vercel", subId, invoice.amount_due > 0);
|
||||
|
||||
if (subId) {
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
// Handle Vercel custom payment method invoices
|
||||
@@ -72,9 +70,6 @@ export const handleInvoiceFinalized = async ({
|
||||
subscription.metadata?.vercel_installation_id;
|
||||
const vercelBillingPlanId = subscription.metadata?.vercel_billing_plan_id;
|
||||
|
||||
console.log("vercelInstallationId", vercelInstallationId);
|
||||
console.log("vercelBillingPlanId", vercelBillingPlanId);
|
||||
|
||||
if (
|
||||
vercelInstallationId &&
|
||||
vercelBillingPlanId &&
|
||||
@@ -86,15 +81,14 @@ export const handleInvoiceFinalized = async ({
|
||||
|
||||
// Only process if it's a custom payment method (Vercel)
|
||||
if (paymentMethod.type === "custom") {
|
||||
console.info(
|
||||
"🔵 Vercel invoice finalized, submitting to Vercel marketplace",
|
||||
{
|
||||
invoiceId: invoice.id,
|
||||
subscriptionId: subscription.id,
|
||||
vercelInstallationId,
|
||||
amountDue: invoice.amount_due / 100,
|
||||
logVercelWebhook({
|
||||
logger,
|
||||
org,
|
||||
event: {
|
||||
type: "marketplace.invoice.finalized",
|
||||
id: invoice.id,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
// Get customer and product
|
||||
@@ -132,10 +126,8 @@ export const handleInvoiceFinalized = async ({
|
||||
product,
|
||||
});
|
||||
|
||||
console.info("✅ Vercel billing data submitted");
|
||||
|
||||
// Submit invoice to Vercel
|
||||
const result = await submitInvoiceToVercel({
|
||||
await submitInvoiceToVercel({
|
||||
installationId: vercelInstallationId,
|
||||
invoice,
|
||||
customer,
|
||||
@@ -144,14 +136,6 @@ export const handleInvoiceFinalized = async ({
|
||||
features,
|
||||
});
|
||||
|
||||
console.info(
|
||||
"✅ Vercel invoice submitted, waiting for payment confirmation webhook",
|
||||
{
|
||||
vercelInvoiceId: result.invoiceId,
|
||||
stripeInvoiceId: invoice.id,
|
||||
},
|
||||
);
|
||||
|
||||
// Do NOT report payment to Stripe here - we've only submitted the invoice to Vercel
|
||||
// Vercel will process payment asynchronously and send marketplace.invoice.paid webhook
|
||||
// handleMarketplaceInvoicePaid will then:
|
||||
@@ -159,9 +143,11 @@ export const handleInvoiceFinalized = async ({
|
||||
// 2. Report payment as "guaranteed" to Stripe
|
||||
// 3. Attach payment record to invoice (marks it as paid)
|
||||
} catch (error: any) {
|
||||
console.error("❌ Failed to process Vercel invoice", {
|
||||
error: error.message,
|
||||
invoiceId: invoice.id,
|
||||
logger.error("Failed to process Vercel invoice", {
|
||||
data: {
|
||||
error: error.message,
|
||||
invoiceId: invoice.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,11 +233,6 @@ export const handleInvoicePaid = async ({
|
||||
),
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Invoice paid, filtered cus products:",
|
||||
cusProducts.map((cp) => `${cp.product.name} - ${cp.product.id}`),
|
||||
);
|
||||
|
||||
if (cusProducts.length === 0) {
|
||||
cusProducts = activeCusProducts;
|
||||
}
|
||||
|
||||
@@ -61,15 +61,6 @@ function calculatePrepaidCosts({
|
||||
// Calculate total cost for this feature
|
||||
const featureCost = subscriptionItemQuantity * unitAmount;
|
||||
totalPrepaidCost += featureCost;
|
||||
|
||||
console.info("Calculated prepaid feature cost for billing plan", {
|
||||
featureId: options.feature_id,
|
||||
quantity: options.quantity,
|
||||
billingUnits,
|
||||
subscriptionItemQuantity,
|
||||
unitAmount,
|
||||
featureCost,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +130,8 @@ export const handleListBillingPlansPerInstall = createRoute({
|
||||
metadata: z.string().optional(),
|
||||
}),
|
||||
handler: async (c) => {
|
||||
const { orgId, env, integrationConfigurationId } = c.req.param();
|
||||
const { db, org, features, logger } = c.get("ctx");
|
||||
const { orgId, env } = c.req.param();
|
||||
const { db, org, logger } = c.get("ctx");
|
||||
|
||||
// Parse metadata from query params
|
||||
let metadata: Record<string, any> = {};
|
||||
|
||||
@@ -9,11 +9,6 @@ export const handleDeleteInstallation = createRoute({
|
||||
const { db, org, logger } = ctx;
|
||||
|
||||
try {
|
||||
// 1. Delete all resources for this installation in parallel
|
||||
logger.info("Deleting resources for installation", {
|
||||
integrationConfigurationId,
|
||||
});
|
||||
|
||||
const resources = await VercelResourceService.listByInstallation({
|
||||
db,
|
||||
installationId: integrationConfigurationId,
|
||||
@@ -32,19 +27,6 @@ export const handleDeleteInstallation = createRoute({
|
||||
),
|
||||
);
|
||||
|
||||
const successCount = deleteResults.filter(
|
||||
(r) => r.status === "fulfilled",
|
||||
).length;
|
||||
const failCount = deleteResults.filter(
|
||||
(r) => r.status === "rejected",
|
||||
).length;
|
||||
|
||||
logger.info("Resources deletion complete", {
|
||||
total: resources.length,
|
||||
success: successCount,
|
||||
failed: failCount,
|
||||
});
|
||||
|
||||
// 2. Delete the customer/installation
|
||||
await deleteCusById({
|
||||
db: ctx.db,
|
||||
@@ -59,11 +41,6 @@ export const handleDeleteInstallation = createRoute({
|
||||
error,
|
||||
integrationConfigurationId,
|
||||
});
|
||||
console.log(
|
||||
"ERROR: Error deleting installation: --------------------------------",
|
||||
);
|
||||
console.log(error);
|
||||
console.log("--------------------------------");
|
||||
}
|
||||
return c.json(
|
||||
{
|
||||
|
||||
@@ -17,17 +17,6 @@ export const handleGetInstallation = createRoute({
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
// const billingPlan = customer?.customer_products?.find((x) =>
|
||||
// isMainProduct({
|
||||
// product: {
|
||||
// ...x.product,
|
||||
// prices: x.customer_prices.map((y) => y.price) ?? {},
|
||||
// entitlements: x.customer_entitlements.map((y) => y.entitlement) ?? [],
|
||||
// },
|
||||
// prices: x.customer_prices.map((y) => y.price) ?? [],
|
||||
// }),
|
||||
// );
|
||||
|
||||
if (!customer) {
|
||||
return c.json(
|
||||
{
|
||||
@@ -41,6 +30,7 @@ export const handleGetInstallation = createRoute({
|
||||
{
|
||||
notification: null,
|
||||
billingPlan:
|
||||
// edge case: [0] = add-on [1] = main
|
||||
customer.customer_products?.[0] !== undefined
|
||||
? (productToBillingPlan({
|
||||
product: cusProductToProduct({
|
||||
|
||||
@@ -24,17 +24,8 @@ import { productToBillingPlan } from "../handleListBillingPlans.js";
|
||||
|
||||
export const handleUpsertInstallation = createRoute({
|
||||
handler: async (c) => {
|
||||
console.log("Vercel Webhook Router: PUT /installations");
|
||||
console.log("Vercel Webhook Router: req.params", c.req.param());
|
||||
|
||||
const body = await c.req.json<VercelUpsertInstallation>();
|
||||
console.log("Vercel Webhook Router: req.body", body);
|
||||
|
||||
const ctx = c.get("ctx");
|
||||
console.log("Vercel Webhook Router: ctx.org", ctx.org);
|
||||
console.log("Vercel Webhook Router: ctx.env", ctx.env);
|
||||
console.log("Vercel Webhook Router: ctx.features", ctx.features);
|
||||
|
||||
const { integrationConfigurationId } = c.req.param();
|
||||
let createdCustomer: Customer | null = null;
|
||||
|
||||
@@ -50,7 +41,6 @@ export const handleUpsertInstallation = createRoute({
|
||||
c.req.header("Authorization") as string,
|
||||
);
|
||||
const claims = await verifyToken({ token, org: ctx.org, env: ctx.env });
|
||||
console.log("Vercel Webhook Router: claims", claims);
|
||||
|
||||
if (
|
||||
!verifyClaims({
|
||||
@@ -81,13 +71,6 @@ export const handleUpsertInstallation = createRoute({
|
||||
frozen_time: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
testClockId = testClock.id;
|
||||
|
||||
ctx.logger.info(
|
||||
"Created test clock for sandbox Vercel installation",
|
||||
{
|
||||
testClockId: testClock.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const stripeCustomer = await createStripeCustomer({
|
||||
@@ -140,26 +123,15 @@ export const handleUpsertInstallation = createRoute({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (customPaymentMethod) {
|
||||
ctx.logger.info("✅ Created custom payment method for Vercel", {
|
||||
paymentMethodId: customPaymentMethod.id,
|
||||
customerId: stripeCustomer.id,
|
||||
});
|
||||
} else {
|
||||
ctx.logger.warn(
|
||||
"⚠️ No custom payment method created - check org config",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
console.log(
|
||||
ctx.logger.error(
|
||||
"ERROR: Error creating customer: --------------------------------",
|
||||
);
|
||||
console.log(_);
|
||||
console.log(ctx.org);
|
||||
console.log("--------------------------------");
|
||||
ctx.logger.error(_);
|
||||
ctx.logger.error(ctx.org);
|
||||
ctx.logger.error("--------------------------------");
|
||||
}
|
||||
|
||||
if (createdCustomer) {
|
||||
|
||||
@@ -45,13 +45,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
invoiceDate,
|
||||
} = payload;
|
||||
|
||||
logger.info("💰 marketplace.invoice.paid webhook received", {
|
||||
vercelInvoiceId: invoiceId,
|
||||
stripeInvoiceId: externalInvoiceId,
|
||||
amount: invoiceTotal,
|
||||
installationId,
|
||||
});
|
||||
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
|
||||
// 1. Get the invoice
|
||||
@@ -59,23 +52,12 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
expand: ["subscription"],
|
||||
});
|
||||
|
||||
logger.info("Retrieved Stripe invoice", {
|
||||
invoiceId: invoice.id,
|
||||
status: invoice.status,
|
||||
amountDue: invoice.amount_due / 100,
|
||||
});
|
||||
|
||||
// 2. Check if already paid
|
||||
if (invoice.status === "paid") {
|
||||
logger.info("Invoice already marked as paid, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"invoice.lines.data",
|
||||
JSON.stringify(invoice.lines.data, null, 4),
|
||||
);
|
||||
|
||||
// 3. Get subscription and payment method
|
||||
const subscription = await stripeCli.subscriptions.retrieve(
|
||||
invoice.lines.data.find(
|
||||
@@ -85,22 +67,10 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
)?.parent?.subscription_item_details?.subscription as string,
|
||||
);
|
||||
|
||||
console.log("subscription", JSON.stringify(subscription, null, 4));
|
||||
|
||||
const customPaymentMethod = await stripeCli.paymentMethods.retrieve(
|
||||
subscription.default_payment_method as string,
|
||||
);
|
||||
|
||||
logger.info("Found custom payment method", {
|
||||
paymentMethodId: customPaymentMethod.id,
|
||||
type: customPaymentMethod.type,
|
||||
});
|
||||
|
||||
// 4. Create cus_product BEFORE reporting payment
|
||||
// This ensures the user gets access to the product when payment is confirmed
|
||||
// and the invoice.paid webhook can find the cus_product
|
||||
logger.info("Creating customer product before reporting payment");
|
||||
|
||||
try {
|
||||
const partialCustomer = await CusService.getByStripeId({
|
||||
db,
|
||||
@@ -108,9 +78,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
});
|
||||
|
||||
if (!partialCustomer) {
|
||||
logger.error("Customer not found for payment", {
|
||||
stripeCustomerId: invoice.customer,
|
||||
});
|
||||
throw new Error("Customer not found");
|
||||
}
|
||||
|
||||
@@ -122,9 +89,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
logger.error("Customer not found", {
|
||||
internalCustomerId: partialCustomer.internal_id,
|
||||
});
|
||||
throw new Error("Customer not found");
|
||||
}
|
||||
|
||||
@@ -142,9 +106,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
logger.error("Product not found", {
|
||||
billingPlanId: vercelBillingPlanId,
|
||||
});
|
||||
throw new Error("Product not found");
|
||||
}
|
||||
|
||||
@@ -158,12 +119,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
|
||||
const isRenewal = existingCusProducts.length > 0;
|
||||
|
||||
logger.info("Detected subscription type", {
|
||||
isRenewal,
|
||||
subscriptionId: subscription.id,
|
||||
existingCusProductCount: existingCusProducts.length,
|
||||
});
|
||||
|
||||
// Fetch Vercel resource by ID from subscription metadata
|
||||
let optionsList: FeatureOptions[] = [];
|
||||
const vercelResourceId = subscription.metadata?.vercel_resource_id;
|
||||
@@ -178,11 +133,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
});
|
||||
|
||||
if (resource?.metadata && Object.keys(resource.metadata).length > 0) {
|
||||
logger.info("Parsing prepaid quantities from resource metadata", {
|
||||
resourceId: resource.id,
|
||||
metadata: resource.metadata,
|
||||
});
|
||||
|
||||
optionsList = parseVercelPrepaidQuantities({
|
||||
metadata: resource.metadata,
|
||||
product,
|
||||
@@ -196,21 +146,9 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
});
|
||||
// Continue with empty optionsList
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
"No resource ID in subscription metadata (legacy or installation-level billing)",
|
||||
{
|
||||
vercelResourceId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (isRenewal) {
|
||||
// Process renewal - reset balances after payment confirmation
|
||||
logger.info(
|
||||
"Processing renewal - resetting balances after payment confirmation",
|
||||
);
|
||||
|
||||
// Call sendUsageAndReset which handles all balance resets
|
||||
const activeProduct = existingCusProducts[0];
|
||||
|
||||
@@ -223,14 +161,8 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
stripeSubs: [subscription],
|
||||
logger,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
"✅ Renewal balance resets completed after payment confirmation",
|
||||
);
|
||||
} else {
|
||||
// New subscription - create cus_product
|
||||
logger.info("New subscription - creating cus_product");
|
||||
|
||||
await createFullCusProduct({
|
||||
db,
|
||||
attachParams: attachToInsertParams(
|
||||
@@ -260,11 +192,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
scenario: AttachScenario.New,
|
||||
logger,
|
||||
});
|
||||
|
||||
logger.info("✅ Customer product created", {
|
||||
productId: product.id,
|
||||
customerId: customer.id,
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error("❌ Failed to create customer product", {
|
||||
@@ -275,8 +202,6 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
|
||||
// 5. Report successful payment to Stripe via Payment Records API
|
||||
// This marks the payment as "guaranteed" and allows Stripe to mark the invoice as paid
|
||||
logger.info("Reporting guaranteed payment to Stripe");
|
||||
|
||||
const paymentRecord = await stripeCli.paymentRecords.reportPayment({
|
||||
amount_requested: {
|
||||
value: invoice.amount_due,
|
||||
@@ -302,20 +227,11 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
},
|
||||
});
|
||||
|
||||
logger.info("✅ Payment reported to Stripe", {
|
||||
paymentRecordId: paymentRecord.id,
|
||||
});
|
||||
|
||||
// 6. Attach payment record to invoice
|
||||
try {
|
||||
await stripeCli.invoices.attachPayment(externalInvoiceId, {
|
||||
payment_record: paymentRecord.id,
|
||||
});
|
||||
|
||||
logger.info("✅ Payment record attached to invoice", {
|
||||
invoiceId: externalInvoiceId,
|
||||
paymentRecordId: paymentRecord.id,
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Might already be attached from handleInvoicePaymentAttemptRequired
|
||||
if (error.code === "resource_already_exists") {
|
||||
@@ -324,8 +240,4 @@ export const handleMarketplaceInvoicePaid = async ({
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"🎉 Vercel payment confirmed - invoice paid, subscription active",
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,13 +33,6 @@ export const handleMarketplaceInvoiceNotPaid = async ({
|
||||
invoiceDate,
|
||||
} = payload;
|
||||
|
||||
logger.info("❌ marketplace.invoice.notpaid webhook received", {
|
||||
vercelInvoiceId: invoiceId,
|
||||
stripeInvoiceId: externalInvoiceId,
|
||||
amount: invoiceTotal,
|
||||
installationId,
|
||||
});
|
||||
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
|
||||
// 1. Get the invoice
|
||||
@@ -47,23 +40,12 @@ export const handleMarketplaceInvoiceNotPaid = async ({
|
||||
expand: ["subscription"],
|
||||
});
|
||||
|
||||
logger.info("Retrieved Stripe invoice", {
|
||||
invoiceId: invoice.id,
|
||||
status: invoice.status,
|
||||
amountDue: invoice.amount_due / 100,
|
||||
});
|
||||
|
||||
// 2. Check if already paid
|
||||
if (invoice.status === "paid") {
|
||||
logger.info("Invoice already marked as not paid, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"invoice.lines.data",
|
||||
JSON.stringify(invoice.lines.data, null, 4),
|
||||
);
|
||||
|
||||
// 3. Get subscription and payment method
|
||||
const subscription = await stripeCli.subscriptions.retrieve(
|
||||
invoice.lines.data.find(
|
||||
@@ -73,17 +55,10 @@ export const handleMarketplaceInvoiceNotPaid = async ({
|
||||
)?.parent?.subscription_item_details?.subscription as string,
|
||||
);
|
||||
|
||||
console.log("subscription", JSON.stringify(subscription, null, 4));
|
||||
|
||||
const customPaymentMethod = await stripeCli.paymentMethods.retrieve(
|
||||
subscription.default_payment_method as string,
|
||||
);
|
||||
|
||||
logger.info("Found custom payment method", {
|
||||
paymentMethodId: customPaymentMethod.id,
|
||||
type: customPaymentMethod.type,
|
||||
});
|
||||
|
||||
try {
|
||||
const partialCustomer = await CusService.getByStripeId({
|
||||
db,
|
||||
@@ -139,8 +114,6 @@ export const handleMarketplaceInvoiceNotPaid = async ({
|
||||
|
||||
// 5. Report failed payment to Stripe via Payment Records API
|
||||
// This marks the payment as "failed" and allows Stripe to mark the invoice as not paid
|
||||
logger.info("Reporting failed payment to Stripe");
|
||||
|
||||
const paymentRecord = await stripeCli.paymentRecords.reportPayment({
|
||||
amount_requested: {
|
||||
value: invoice.amount_due,
|
||||
@@ -166,36 +139,18 @@ export const handleMarketplaceInvoiceNotPaid = async ({
|
||||
},
|
||||
});
|
||||
|
||||
logger.info("✅ Payment reported to Stripe", {
|
||||
paymentRecordId: paymentRecord.id,
|
||||
});
|
||||
|
||||
// 6. Attach payment record to invoice
|
||||
try {
|
||||
await stripeCli.invoices.attachPayment(externalInvoiceId, {
|
||||
payment_record: paymentRecord.id,
|
||||
});
|
||||
|
||||
logger.info("✅ Payment record attached to invoice", {
|
||||
invoiceId: externalInvoiceId,
|
||||
paymentRecordId: paymentRecord.id,
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Might already be attached from handleMarketplaceInvoicePaid
|
||||
if (error.code === "resource_already_exists") {
|
||||
logger.info("Payment record already attached to invoice");
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await stripeCli.subscriptions.cancel(subscription.id);
|
||||
|
||||
logger.info("Cancelled subscription", {
|
||||
subscriptionId: subscription.id,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
"❌ Vercel payment failed - invoice not paid, subscription cancelled",
|
||||
);
|
||||
};
|
||||
|
||||
@@ -32,13 +32,6 @@ export const handleCreateResource = createRoute({
|
||||
const { db, org, features, logger } = c.get("ctx");
|
||||
const { productId, name, metadata, billingPlanId } = c.req.valid("json");
|
||||
|
||||
logger.info("Creating Vercel resource", {
|
||||
productId,
|
||||
name,
|
||||
billingPlanId,
|
||||
integrationConfigurationId,
|
||||
});
|
||||
|
||||
// 1. Get customer
|
||||
const customer = await CusService.getFull({
|
||||
db,
|
||||
@@ -75,7 +68,7 @@ export const handleCreateResource = createRoute({
|
||||
// 2. Create resource in database (enforces 1-resource limit)
|
||||
const resourceId = generateId("vre");
|
||||
|
||||
const resource = await VercelResourceService.create({
|
||||
await VercelResourceService.create({
|
||||
db,
|
||||
resource: {
|
||||
id: resourceId,
|
||||
@@ -88,10 +81,6 @@ export const handleCreateResource = createRoute({
|
||||
},
|
||||
});
|
||||
|
||||
logger.info("Resource created in database", {
|
||||
resourceId: resource.id,
|
||||
});
|
||||
|
||||
// 3. Create subscription (installation-level billing)
|
||||
const { product } = await createVercelSubscription({
|
||||
db,
|
||||
|
||||
@@ -10,12 +10,7 @@ export const handleDeleteResource = createRoute({
|
||||
handler: async (c) => {
|
||||
const { orgId, env, integrationConfigurationId, resourceId } =
|
||||
c.req.param();
|
||||
const { db, logger } = c.get("ctx");
|
||||
|
||||
logger.info("Deleting Vercel resource", {
|
||||
integrationConfigurationId,
|
||||
resourceId,
|
||||
});
|
||||
const { db } = c.get("ctx");
|
||||
|
||||
await VercelResourceService.delete({
|
||||
db,
|
||||
|
||||
@@ -10,12 +10,7 @@ export const handleGetResource = createRoute({
|
||||
handler: async (c) => {
|
||||
const { orgId, env, integrationConfigurationId, resourceId } =
|
||||
c.req.param();
|
||||
const { db, logger } = c.get("ctx");
|
||||
|
||||
logger.info("Getting Vercel resource", {
|
||||
integrationConfigurationId,
|
||||
resourceId,
|
||||
});
|
||||
const { db } = c.get("ctx");
|
||||
|
||||
const resource = await VercelResourceService.getByIdAndInstallation({
|
||||
db,
|
||||
|
||||
32
server/src/external/vercel/misc/vercelAuth.ts
vendored
32
server/src/external/vercel/misc/vercelAuth.ts
vendored
@@ -46,7 +46,6 @@ export async function verifyToken({
|
||||
throw new AuthError("Invalid issuer");
|
||||
}
|
||||
|
||||
console.log("Vercel auth claims", JSON.stringify(claims, null, 4));
|
||||
return claims;
|
||||
} catch (err) {
|
||||
if (err instanceof JWTExpired) {
|
||||
@@ -117,26 +116,23 @@ export class AuthError extends Error {}
|
||||
* 5. Store validated claims in context
|
||||
*/
|
||||
export const vercelOidcAuthMiddleware = async (c: any, next: any) => {
|
||||
const { org, env, logger } = c.get("ctx");
|
||||
const { org, env } = c.get("ctx");
|
||||
const authHeader = c.req.header("authorization");
|
||||
const authType = c.req.header("x-vercel-auth");
|
||||
|
||||
// Validate required headers
|
||||
if (!authHeader) {
|
||||
logger.warn("Missing Authorization header");
|
||||
return c.json({ error: "Unauthorized", code: "missing_auth_header" }, 401);
|
||||
}
|
||||
|
||||
if (!authType) {
|
||||
logger.warn("Missing X-Vercel-Auth header");
|
||||
return c.json(
|
||||
{ error: "Unauthorized", code: "missing_auth_type_header" },
|
||||
401,
|
||||
);
|
||||
}
|
||||
|
||||
if (authType !== "user" && authType !== "system") {
|
||||
logger.warn("Invalid X-Vercel-Auth value", { authType });
|
||||
if (!["user", "system"].includes(authType)) {
|
||||
return c.json({ error: "Unauthorized", code: "invalid_auth_type" }, 401);
|
||||
}
|
||||
|
||||
@@ -145,7 +141,6 @@ export const vercelOidcAuthMiddleware = async (c: any, next: any) => {
|
||||
try {
|
||||
token = getAuthorizationToken(authHeader);
|
||||
} catch (error) {
|
||||
logger.warn("Invalid Authorization header format");
|
||||
return c.json(
|
||||
{ error: "Unauthorized", code: "invalid_auth_header_format" },
|
||||
401,
|
||||
@@ -157,10 +152,6 @@ export const vercelOidcAuthMiddleware = async (c: any, next: any) => {
|
||||
try {
|
||||
claims = await verifyToken({ token, org, env });
|
||||
} catch (error: any) {
|
||||
logger.warn("JWT verification failed", {
|
||||
error: error.message,
|
||||
authType,
|
||||
});
|
||||
return c.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
@@ -186,12 +177,6 @@ export const vercelOidcAuthMiddleware = async (c: any, next: any) => {
|
||||
if (authType === "user") {
|
||||
// User auth: always validate installation_id matches URL param
|
||||
if (claims.installation_id !== integrationConfigurationId) {
|
||||
logger.warn("Installation ID mismatch for user auth", {
|
||||
claim_installation_id: claims.installation_id,
|
||||
url_installation_id: integrationConfigurationId,
|
||||
path,
|
||||
all_params: c.req.param(),
|
||||
});
|
||||
return c.json(
|
||||
{ error: "Forbidden", code: "installation_id_mismatch" },
|
||||
403,
|
||||
@@ -201,12 +186,6 @@ export const vercelOidcAuthMiddleware = async (c: any, next: any) => {
|
||||
// System auth: validate installation_id only if not null
|
||||
if (claims.installation_id !== null) {
|
||||
if (claims.installation_id !== integrationConfigurationId) {
|
||||
logger.warn("Installation ID mismatch for system auth", {
|
||||
claim_installation_id: claims.installation_id,
|
||||
url_installation_id: integrationConfigurationId,
|
||||
path,
|
||||
all_params: c.req.param(),
|
||||
});
|
||||
return c.json(
|
||||
{ error: "Forbidden", code: "installation_id_mismatch" },
|
||||
403,
|
||||
@@ -216,13 +195,6 @@ export const vercelOidcAuthMiddleware = async (c: any, next: any) => {
|
||||
// If installation_id is null, we only validate JWKS (already done above)
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("OIDC auth successful", {
|
||||
authType,
|
||||
installation_id: claims.installation_id,
|
||||
user_id: claims.user_id,
|
||||
});
|
||||
|
||||
// Store claims in context for downstream handlers
|
||||
c.set("vercelClaims", claims);
|
||||
|
||||
|
||||
@@ -166,8 +166,8 @@ export const submitInvoiceToVercel = async ({
|
||||
? {
|
||||
test: {
|
||||
validate: true,
|
||||
// result: "paid",
|
||||
result: "notpaid",
|
||||
result: "paid",
|
||||
// result: "notpaid",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
@@ -213,11 +213,6 @@ export const parseVercelPrepaidQuantities = ({
|
||||
);
|
||||
|
||||
if (!entitlement) {
|
||||
console.warn("Feature not found in product, skipping prepaid quantity", {
|
||||
featureId,
|
||||
productId: product.id,
|
||||
quantity,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -228,16 +223,6 @@ export const parseVercelPrepaidQuantities = ({
|
||||
});
|
||||
|
||||
if (!prepaidPrice) {
|
||||
console.warn(
|
||||
"Feature is not prepaid or has no price configured, skipping",
|
||||
{
|
||||
featureId,
|
||||
featureName: entitlement.feature.name,
|
||||
internalFeatureId: entitlement.internal_feature_id,
|
||||
productId: product.id,
|
||||
quantity,
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -247,13 +232,6 @@ export const parseVercelPrepaidQuantities = ({
|
||||
internal_feature_id: entitlement.internal_feature_id,
|
||||
quantity,
|
||||
});
|
||||
|
||||
console.info("Parsed prepaid quantity from Vercel metadata", {
|
||||
featureId,
|
||||
featureName: entitlement.feature.name,
|
||||
quantity,
|
||||
priceId: prepaidPrice.id,
|
||||
});
|
||||
}
|
||||
|
||||
return optionsList;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { AppEnv, type Organization } from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import type { Context, Next } from "hono";
|
||||
import type { Logger } from "@/external/logtail/logtailUtils.js";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { FeatureService } from "@/internal/features/FeatureService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
@@ -27,3 +29,26 @@ export const vercelSeederMiddleware = async (
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
export const logVercelWebhook = ({
|
||||
logger,
|
||||
org,
|
||||
event,
|
||||
}: {
|
||||
logger: Logger;
|
||||
org: Organization;
|
||||
event: any;
|
||||
}) => {
|
||||
logger.info(
|
||||
`${chalk.magenta("VERCEL").padEnd(18)} ${event.type.padEnd(30)} ${org.slug} | ${event.id}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const vercelLogMiddleware = async (c: Context<HonoEnv>, next: Next) => {
|
||||
const { db, logger, org } = c.get("ctx");
|
||||
const body = await c.req.json();
|
||||
|
||||
logVercelWebhook({ logger, org, event: body });
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
@@ -72,7 +72,5 @@ export const vercelSignatureMiddleware = async (c: any, next: any) => {
|
||||
return c.json({ error: "Unauthorized", code: "invalid_signature" }, 401);
|
||||
}
|
||||
|
||||
logger.debug("Webhook signature validated", { env });
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
@@ -65,11 +65,6 @@ export const createVercelSubscription = async ({
|
||||
metadata?: Record<string, any>;
|
||||
resourceId?: string;
|
||||
}): Promise<{ subscription: Stripe.Subscription; product: FullProduct }> => {
|
||||
logger.info("Creating Vercel subscription", {
|
||||
billingPlanId,
|
||||
integrationConfigurationId,
|
||||
});
|
||||
|
||||
// 1. Check for existing subscription (only allow one per installation)
|
||||
const existingSubscription = stripeCustomer.subscriptions?.data.find(
|
||||
(s) => s.metadata.vercel_installation_id === integrationConfigurationId,
|
||||
@@ -121,23 +116,13 @@ export const createVercelSubscription = async ({
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("Found custom payment method", {
|
||||
paymentMethodId: customPaymentMethod.id,
|
||||
type: customPaymentMethod.type,
|
||||
});
|
||||
|
||||
// 4. Parse prepaid quantities from metadata if provided
|
||||
let optionsList;
|
||||
if (metadata && Object.keys(metadata).length > 0) {
|
||||
logger.info("Parsing prepaid quantities from metadata", {
|
||||
metadata,
|
||||
productId: product.id,
|
||||
});
|
||||
optionsList = parseVercelPrepaidQuantities({
|
||||
metadata,
|
||||
product,
|
||||
prices: product.prices,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -176,15 +161,6 @@ export const createVercelSubscription = async ({
|
||||
logger,
|
||||
});
|
||||
|
||||
logger.info("Subscription created with custom payment method", {
|
||||
subscriptionId: subscription.id,
|
||||
status: subscription.status,
|
||||
latestInvoiceId:
|
||||
typeof subscription.latest_invoice === "string"
|
||||
? subscription.latest_invoice
|
||||
: subscription.latest_invoice?.id,
|
||||
});
|
||||
|
||||
// Subscription will be 'incomplete' initially with an 'open' invoice
|
||||
// Payment flow:
|
||||
// 1. invoice.finalized webhook → handleInvoiceFinalized submits invoice to Vercel
|
||||
|
||||
@@ -28,7 +28,7 @@ export class VercelResourceService {
|
||||
db,
|
||||
installationId: resource.installation_id,
|
||||
orgId: resource.org_id,
|
||||
env: resource.env,
|
||||
env: resource.env as AppEnv,
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
@@ -136,7 +136,7 @@ export class VercelResourceService {
|
||||
if (!resource) {
|
||||
throw new RecaseError({
|
||||
message: `Resource ${resourceId} not found for installation ${installationId}`,
|
||||
code: ErrCode.NotFound,
|
||||
code: ErrCode.VercelResourceNotFound,
|
||||
statusCode: StatusCodes.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ import { handleGetResource } from "./handlers/resources/handleGetResource.js";
|
||||
import { handleUpdateResource } from "./handlers/resources/handleUpdateResource.js";
|
||||
import { captureRawBody } from "./misc/rawBodyMiddleware.js";
|
||||
import { vercelOidcAuthMiddleware } from "./misc/vercelAuth.js";
|
||||
import { vercelSeederMiddleware } from "./misc/vercelMiddleware.js";
|
||||
import {
|
||||
vercelLogMiddleware,
|
||||
vercelSeederMiddleware,
|
||||
} from "./misc/vercelMiddleware.js";
|
||||
import { vercelSignatureMiddleware } from "./misc/vercelSignatureMiddleware.js";
|
||||
|
||||
export const vercelWebhookRouter = new Hono<HonoEnv>();
|
||||
@@ -95,10 +98,9 @@ vercelWebhookRouter.post(
|
||||
vercelSeederMiddleware,
|
||||
captureRawBody,
|
||||
vercelSignatureMiddleware,
|
||||
vercelLogMiddleware,
|
||||
async (c) => {
|
||||
const { db, org, env, logger } = c.get("ctx");
|
||||
const params = c.req.param();
|
||||
const headers = c.req.header();
|
||||
let body: any;
|
||||
try {
|
||||
body = await c.req.json();
|
||||
@@ -106,16 +108,6 @@ vercelWebhookRouter.post(
|
||||
body = {};
|
||||
}
|
||||
|
||||
logger.info("Vercel webhook received", {
|
||||
method: "POST",
|
||||
eventType: body.type,
|
||||
params,
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
console.log("Vercel webhook headers", JSON.stringify(headers, null, 4));
|
||||
console.log("Vercel webhook received", "POST", params, body);
|
||||
|
||||
const eventType = body.type;
|
||||
|
||||
try {
|
||||
|
||||
@@ -166,4 +166,5 @@ export const ErrCode = {
|
||||
// Vercel
|
||||
VercelSubscriptionAlreadyExists: "vercel_subscription_already_exists",
|
||||
VercelSubscriptionNotFound: "vercel_subscription_not_found",
|
||||
VercelResourceNotFound: "vercel_resource_not_found",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user