added events to frontend + finished invoice below threshold
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { formatUnixToDateTimeString } from "@/utils/formatUtils/formatDateUtils";
|
||||
import { CusProduct } from "@autumn/shared";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
@@ -25,6 +26,10 @@ export const CustomerProductList = ({
|
||||
<TableRow className="">
|
||||
<TableHead className="w-[150px]">Name</TableHead>
|
||||
<TableHead className="">Product ID</TableHead>
|
||||
<TableHead className="">Status</TableHead>
|
||||
|
||||
<TableHead className="">Created At</TableHead>
|
||||
<TableHead className="">Ended At</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -42,7 +47,18 @@ export const CustomerProductList = ({
|
||||
<TableCell>
|
||||
{products.find((p) => p.id === cusProduct.product_id)?.name}
|
||||
</TableCell>
|
||||
<TableCell>{cusProduct.product_id}</TableCell>
|
||||
<TableCell className="max-w-[100px] overflow-hidden text-ellipsis">
|
||||
{cusProduct.product_id}
|
||||
</TableCell>
|
||||
<TableCell>{cusProduct.status}</TableCell>
|
||||
<TableCell>
|
||||
{formatUnixToDateTimeString(cusProduct.created_at)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{cusProduct.ended_at
|
||||
? formatUnixToDateTimeString(cusProduct.ended_at)
|
||||
: ""}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -29,7 +29,10 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSquareUpRight } from "@fortawesome/pro-duotone-svg-icons";
|
||||
import { CustomerProductList } from "./CustomerProductList";
|
||||
import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
|
||||
import {
|
||||
formatUnixToDateTime,
|
||||
formatUnixToDateTimeString,
|
||||
} from "@/utils/formatUtils/formatDateUtils";
|
||||
import { ManageEntitlements } from "./entitlements/ManageEntitlements";
|
||||
import { CustomerEntitlementsList } from "./entitlements/CustomerEntitlementsList";
|
||||
import { navigateTo } from "@/utils/genUtils";
|
||||
@@ -47,12 +50,23 @@ export default function CustomerView({
|
||||
env,
|
||||
});
|
||||
|
||||
const {
|
||||
data: eventsData,
|
||||
isLoading: eventsLoading,
|
||||
error: eventsError,
|
||||
} = useAxiosSWR({
|
||||
url: `/v1/customers/${customer_id}/events`,
|
||||
env,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
router.push("/customers");
|
||||
}
|
||||
|
||||
if (isLoading) return <LoadingScreen />;
|
||||
|
||||
const { customer, products, invoices } = data;
|
||||
const { events } = eventsData;
|
||||
|
||||
return (
|
||||
<CustomerContext.Provider value={{ customer, products, env }}>
|
||||
@@ -139,6 +153,7 @@ export default function CustomerView({
|
||||
<TableHead className="w-[150px]">Products</TableHead>
|
||||
<TableHead className="">URL</TableHead>
|
||||
<TableHead className="">Created At</TableHead>
|
||||
<TableHead className="">Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -154,7 +169,7 @@ export default function CustomerView({
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[400px] truncate">
|
||||
<a
|
||||
href={invoice.processor.hosted_invoice_url}
|
||||
href={invoice.hosted_invoice_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-lime-500"
|
||||
@@ -170,10 +185,21 @@ export default function CustomerView({
|
||||
{formatUnixToDateTime(invoice.created_at).time}{" "}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{invoice.status}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<p className="text-t2 font-medium text-md">Events</p>
|
||||
<div className="flex flex-col gap-1">
|
||||
{events.map((event) => (
|
||||
<div key={event.id} className="flex gap-1">
|
||||
<p>- {formatUnixToDateTimeString(event.timestamp)}</p>
|
||||
<p>- {event.event_name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* customer details */}
|
||||
<div className="flex flex-col gap-4 text-t2 text-sm w-full max-w-[400px] h-fit">
|
||||
|
||||
@@ -24,14 +24,9 @@ export const handleCheckoutSessionCompleted = async ({
|
||||
checkoutSession: Stripe.Checkout.Session;
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
console.log(
|
||||
"Stripe webhook, handlingcheckout.completed, autumn metadata:",
|
||||
checkoutSession.metadata?.autumn_metadata_id
|
||||
);
|
||||
|
||||
const metadata = await getMetadataFromCheckoutSession(checkoutSession, sb);
|
||||
if (!metadata) {
|
||||
console.log("Metadata not found");
|
||||
console.log("checkout.completed: metadata not found, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,15 +41,20 @@ export const handleCheckoutSessionCompleted = async ({
|
||||
} = metadata.data;
|
||||
|
||||
if (metadataOrg.id != org.id) {
|
||||
console.log("Org doesn't match, skipping");
|
||||
console.log("checkout.completed: org doesn't match, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadataEnv != env) {
|
||||
console.log("Environments don't match, skipping");
|
||||
console.log("checkout.completed: environments don't match, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Handling checkout.completed, autumn metadata:",
|
||||
checkoutSession.metadata?.autumn_metadata_id
|
||||
);
|
||||
|
||||
await CusProductService.expireCurrentProduct({
|
||||
sb,
|
||||
internalCustomerId: customer.internal_id,
|
||||
|
||||
@@ -23,6 +23,7 @@ export const handleInvoicePaid = async ({
|
||||
if (!cusProduct) {
|
||||
return;
|
||||
}
|
||||
|
||||
let existingInvoice = await InvoiceService.getInvoiceByStripeId({
|
||||
sb,
|
||||
stripeInvoiceId: invoice.id,
|
||||
|
||||
@@ -105,13 +105,13 @@ export const handleSubscriptionUpdated = async ({
|
||||
org: Organization;
|
||||
subscription: any;
|
||||
}) => {
|
||||
console.log("Subscription updated:", {
|
||||
id: subscription.id,
|
||||
status: subscription.status,
|
||||
customer: subscription.customer,
|
||||
canceled_at: subscription.canceled_at,
|
||||
schedule_id: subscription.subscription_schedule,
|
||||
});
|
||||
// console.log("Subscription updated:", {
|
||||
// id: subscription.id,
|
||||
// status: subscription.status,
|
||||
// customer: subscription.customer,
|
||||
// canceled_at: subscription.canceled_at,
|
||||
// schedule_id: subscription.subscription_schedule,
|
||||
// });
|
||||
|
||||
// 1. Undo stripe sub cancellation if it was cancelled
|
||||
if (subscription.canceled_at !== null) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { ErrCode } from "@/errors/errCodes.js";
|
||||
import { ErrorMessages } from "@/errors/errMessages.js";
|
||||
|
||||
import RecaseError, { formatZodError } from "@/utils/errorUtils.js";
|
||||
import RecaseError, {
|
||||
formatZodError,
|
||||
handleRequestError,
|
||||
} from "@/utils/errorUtils.js";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import { CreateCustomerSchema, Customer, ProcessorType } from "@autumn/shared";
|
||||
import { Router } from "express";
|
||||
@@ -15,6 +18,7 @@ import Stripe from "stripe";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
|
||||
import { EventService } from "../events/EventService.js";
|
||||
|
||||
export const cusRouter = Router();
|
||||
|
||||
@@ -191,4 +195,18 @@ cusRouter.delete("/:customerId", async (req: any, res: any) => {
|
||||
}
|
||||
});
|
||||
|
||||
// cusRouter.use("/:customer_id/products", cusProductApiRouter);
|
||||
cusRouter.get("/:customer_id/events", async (req: any, res: any) => {
|
||||
const customerId = req.params.customer_id;
|
||||
try {
|
||||
const events = await EventService.getByCustomerId({
|
||||
sb: req.sb,
|
||||
customerId,
|
||||
org: req.org,
|
||||
env: req.env,
|
||||
});
|
||||
|
||||
res.status(200).json({ events });
|
||||
} catch (error) {
|
||||
handleRequestError({ error, res, action: "get customer events" });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -107,8 +107,8 @@ const handleExistingProduct = async ({
|
||||
internalCustomerId: customer.internal_id,
|
||||
});
|
||||
|
||||
// 2. Check if customer already has product
|
||||
if (existingCusProduct?.product_id === product.id && !product.is_add_on) {
|
||||
// 2. Don't allow customer to get multiple of the same product
|
||||
if (existingCusProduct?.product_id === product.id) {
|
||||
// If there's a future product, delete, else
|
||||
const deletedCusProduct = await CusProductService.deleteFutureProduct({
|
||||
sb,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { ErrCode, Event } from "@autumn/shared";
|
||||
import { ErrCode, Event, Organization } from "@autumn/shared";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
@@ -28,4 +28,38 @@ export class EventService {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static async getByCustomerId({
|
||||
sb,
|
||||
customerId,
|
||||
org,
|
||||
env,
|
||||
limit = 10,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
customerId: string;
|
||||
org: Organization;
|
||||
env: string;
|
||||
limit?: number;
|
||||
}) {
|
||||
const { data, error } = await sb
|
||||
.from("events")
|
||||
.select("*")
|
||||
.eq("customer_id", customerId)
|
||||
.eq("org_id", org.id)
|
||||
.eq("env", env)
|
||||
.order("timestamp", { ascending: false })
|
||||
.limit(limit);
|
||||
|
||||
if (error) {
|
||||
throw new RecaseError({
|
||||
message: "Failed to get events",
|
||||
code: ErrCode.InternalError,
|
||||
data: error,
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ const getEventAndCustomer = async (req: any) => {
|
||||
org_id: orgId,
|
||||
env: env,
|
||||
timestamp: Date.now(),
|
||||
properties: {},
|
||||
...body,
|
||||
};
|
||||
} catch (error: any) {
|
||||
@@ -216,6 +217,8 @@ eventsRouter.post("", async (req: any, res: any) => {
|
||||
);
|
||||
|
||||
// console.log("Queued update balance task...");
|
||||
} else {
|
||||
console.log("No affected features found");
|
||||
}
|
||||
|
||||
res.status(200).json({ success: true, event_id: event.id });
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ErrCode } from "@/errors/errCodes.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { getEntOptions } from "@/internal/prices/priceUtils.js";
|
||||
import { PriceOptions, CustomerPrice } from "@autumn/shared";
|
||||
import { CustomerPrice } from "@autumn/shared";
|
||||
import { CusProductService } from "../products/CusProductService.js";
|
||||
|
||||
export const initCusEntitlement = ({
|
||||
@@ -26,12 +26,14 @@ export const initCusEntitlement = ({
|
||||
cusProductId,
|
||||
options,
|
||||
nextResetAt,
|
||||
billLaterOnly = false,
|
||||
}: {
|
||||
entitlement: EntitlementWithFeature;
|
||||
customer: Customer;
|
||||
cusProductId: string;
|
||||
options?: FeatureOptions;
|
||||
nextResetAt?: number;
|
||||
billLaterOnly?: boolean;
|
||||
}) => {
|
||||
const feature: Feature = entitlement.feature;
|
||||
|
||||
@@ -39,7 +41,7 @@ export const initCusEntitlement = ({
|
||||
let allowance = entitlement.allowance || 0;
|
||||
let quantity = options?.quantity || 1;
|
||||
|
||||
let balance = allowance * quantity;
|
||||
let balance = billLaterOnly ? 0 : allowance * quantity;
|
||||
|
||||
// 2. Define reset interval (interval at which balance is reset to quantity * allowance)
|
||||
let reset_interval = entitlement.interval as EntInterval;
|
||||
@@ -224,6 +226,7 @@ export const createFullCusProduct = async ({
|
||||
subscriptionId,
|
||||
subscriptionScheduleId,
|
||||
nextResetAt,
|
||||
billLaterOnly = false,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
customer: Customer;
|
||||
@@ -235,6 +238,7 @@ export const createFullCusProduct = async ({
|
||||
subscriptionId?: string;
|
||||
subscriptionScheduleId?: string;
|
||||
nextResetAt?: number;
|
||||
billLaterOnly?: boolean;
|
||||
}) => {
|
||||
if (!product.is_add_on) {
|
||||
await expireOrDeleteCusProduct({
|
||||
@@ -258,6 +262,7 @@ export const createFullCusProduct = async ({
|
||||
cusProductId: cusProdId,
|
||||
options: options || undefined,
|
||||
nextResetAt,
|
||||
billLaterOnly,
|
||||
});
|
||||
|
||||
cusEnts.push(cusEnt);
|
||||
|
||||
@@ -180,6 +180,7 @@ export const handleAddProduct = async ({
|
||||
entitlements,
|
||||
optionsList,
|
||||
subscriptionId: undefined,
|
||||
billLaterOnly: true,
|
||||
});
|
||||
|
||||
console.log("Successfully created full cus product");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { Invoice, ProcessorType } from "@autumn/shared";
|
||||
import { Invoice, InvoiceStatus, ProcessorType } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
@@ -48,7 +48,7 @@ export class InvoiceService {
|
||||
const { data, error } = await sb
|
||||
.from("invoices")
|
||||
.select("*")
|
||||
.eq("processor->>id", stripeInvoiceId)
|
||||
.eq("stripe_id", stripeInvoiceId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
@@ -65,22 +65,22 @@ export class InvoiceService {
|
||||
stripeInvoice,
|
||||
internalCustomerId,
|
||||
productIds,
|
||||
status,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
stripeInvoice: Stripe.Invoice;
|
||||
internalCustomerId: string;
|
||||
productIds: string[];
|
||||
status?: InvoiceStatus | null;
|
||||
}) {
|
||||
const invoice: Invoice = {
|
||||
id: generateId("inv"),
|
||||
internal_customer_id: internalCustomerId,
|
||||
product_ids: productIds,
|
||||
created_at: stripeInvoice.created * 1000,
|
||||
processor: {
|
||||
id: stripeInvoice.id,
|
||||
type: ProcessorType.Stripe,
|
||||
hosted_invoice_url: stripeInvoice.hosted_invoice_url || null,
|
||||
},
|
||||
stripe_id: stripeInvoice.id,
|
||||
hosted_invoice_url: stripeInvoice.hosted_invoice_url || null,
|
||||
status: status || (stripeInvoice.status as InvoiceStatus | null),
|
||||
};
|
||||
|
||||
// Check if invoice already exists
|
||||
@@ -91,10 +91,13 @@ export class InvoiceService {
|
||||
});
|
||||
|
||||
if (existingInvoice) {
|
||||
console.log("Invoice already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await sb.from("invoices").insert(invoice);
|
||||
const { error } = await sb.from("invoices").upsert(invoice, {
|
||||
onConflict: "stripe_id",
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.log("Failed to create invoice from stripe", error);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
FullCusProduct,
|
||||
CusProductStatus,
|
||||
CustomerEntitlement,
|
||||
InvoiceStatus,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import dotenv from "dotenv";
|
||||
@@ -79,6 +80,7 @@ const payForInvoice = async ({
|
||||
});
|
||||
|
||||
if (!paymentMethod) {
|
||||
console.log(" ❌ No payment method found");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -87,7 +89,9 @@ const payForInvoice = async ({
|
||||
payment_method: paymentMethod as string,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log("Failed to pay invoice: " + error?.message || error);
|
||||
console.log(
|
||||
" ❌ Stripe error: Failed to pay invoice: " + error?.message || error
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -96,24 +100,39 @@ const payForInvoice = async ({
|
||||
|
||||
const handleInvoicePaymentFailure = async ({
|
||||
sb,
|
||||
stripeCli,
|
||||
fullCusProduct,
|
||||
fullCusPrice,
|
||||
finalizedInvoice,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
stripeCli: Stripe;
|
||||
fullCusProduct: FullCusProduct;
|
||||
fullCusPrice: FullCustomerPrice;
|
||||
finalizedInvoice: Stripe.Invoice;
|
||||
}) => {
|
||||
// 1. Update customer product
|
||||
console.log(
|
||||
"Payment failed, updating customer product status to past due..."
|
||||
);
|
||||
console.log(" Handling invoice payment failure...");
|
||||
|
||||
// Void invoice
|
||||
await stripeCli.invoices.voidInvoice(finalizedInvoice.id);
|
||||
console.log(" a. Stripe invoice voided");
|
||||
|
||||
await InvoiceService.createInvoiceFromStripe({
|
||||
sb,
|
||||
stripeInvoice: finalizedInvoice,
|
||||
internalCustomerId: fullCusProduct.internal_customer_id,
|
||||
productIds: [fullCusProduct.product.id],
|
||||
status: InvoiceStatus.Void,
|
||||
});
|
||||
console.log(" b. Invoice inserted into db");
|
||||
|
||||
await CusProductService.update({
|
||||
sb,
|
||||
cusProductId: fullCusProduct.id,
|
||||
updates: {
|
||||
status: CusProductStatus.PastDue,
|
||||
status: CusProductStatus.Expired,
|
||||
ended_at: Date.now(),
|
||||
processor: {
|
||||
...fullCusProduct.processor!,
|
||||
last_invoice_id: finalizedInvoice.id,
|
||||
@@ -121,7 +140,7 @@ const handleInvoicePaymentFailure = async ({
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Customer product updated successfully");
|
||||
console.log(" c. Expired customer product");
|
||||
};
|
||||
|
||||
const invoiceCustomer = async ({
|
||||
@@ -159,7 +178,7 @@ const invoiceCustomer = async ({
|
||||
});
|
||||
|
||||
// 1. Create invoice
|
||||
console.log("1. Creating invoice...");
|
||||
console.log(" a. Creating invoice...");
|
||||
const finalizedInvoice = await createBelowThresholdInvoice({
|
||||
stripeCli,
|
||||
customer,
|
||||
@@ -168,7 +187,7 @@ const invoiceCustomer = async ({
|
||||
});
|
||||
|
||||
// 2. Pay for invoice
|
||||
console.log("2. Paying for invoice...");
|
||||
console.log(" b. Paying for invoice...");
|
||||
const paid = await payForInvoice({
|
||||
fullOrg,
|
||||
env: customer.env as AppEnv,
|
||||
@@ -178,9 +197,10 @@ const invoiceCustomer = async ({
|
||||
});
|
||||
|
||||
if (!paid) {
|
||||
console.log("Failed to pay for invoice");
|
||||
console.log(" ❌ Failed to pay for invoice");
|
||||
await handleInvoicePaymentFailure({
|
||||
sb,
|
||||
stripeCli,
|
||||
fullCusProduct,
|
||||
fullCusPrice,
|
||||
finalizedInvoice,
|
||||
@@ -188,16 +208,18 @@ const invoiceCustomer = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("3. Inserting invoice into db...");
|
||||
// 3. Insert invoice into db
|
||||
console.log(" c. Inserting invoice into db...");
|
||||
await InvoiceService.createInvoiceFromStripe({
|
||||
sb,
|
||||
stripeInvoice: finalizedInvoice,
|
||||
internalCustomerId: customer.internal_id,
|
||||
productIds: [fullCusProduct.product.id],
|
||||
status: InvoiceStatus.Paid,
|
||||
});
|
||||
|
||||
// 4. Update customer product
|
||||
console.log("4. Updating customer product...");
|
||||
console.log(" d. Updating customer product...");
|
||||
await CusProductService.update({
|
||||
sb,
|
||||
cusProductId: fullCusProduct.id,
|
||||
@@ -210,11 +232,11 @@ const invoiceCustomer = async ({
|
||||
});
|
||||
|
||||
// 5. Update feature balance
|
||||
console.log("5. Updating feature balance...");
|
||||
console.log(" e. Updating feature balance...");
|
||||
const newBalance = cusEnt.balance! + cusEnt.entitlement.allowance!;
|
||||
|
||||
console.log(
|
||||
"Current balance:",
|
||||
" - Current balance:",
|
||||
cusEnt.balance,
|
||||
"| Update amount:",
|
||||
cusEnt.entitlement.allowance,
|
||||
@@ -314,7 +336,7 @@ export const handleBelowThresholdInvoicing = async ({
|
||||
});
|
||||
|
||||
console.log(
|
||||
` - Feature balance: ${balance}, threshold: ${threshold}, below: ${below}`
|
||||
` - Current balance: ${balance}, threshold: ${threshold}, below: ${below}`
|
||||
);
|
||||
|
||||
if (!below) {
|
||||
|
||||
@@ -54,7 +54,7 @@ export const runUpdateBalanceTask = async (payload: any) => {
|
||||
const { customer, features } = payload;
|
||||
|
||||
console.log("--------------------------------");
|
||||
console.log("Inside runUpdateBalanceTask...");
|
||||
console.log("Inside updateBalanceTask...");
|
||||
|
||||
console.log("1. Updating customer balance...");
|
||||
const cusEnts: any = await updateCustomerBalance({
|
||||
@@ -63,8 +63,13 @@ export const runUpdateBalanceTask = async (payload: any) => {
|
||||
features,
|
||||
});
|
||||
|
||||
// 2. Check if there's below threshold price
|
||||
if (!cusEnts || cusEnts.length === 0) {
|
||||
console.log("✅ No customer entitlements found, skipping");
|
||||
return;
|
||||
}
|
||||
console.log(" ✅ Customer balance updated");
|
||||
|
||||
// 2. Check if there's below threshold price
|
||||
const belowThresholdPrice = await getBelowThresholdPrice({
|
||||
sb,
|
||||
internalCustomerId: customer.internal_id,
|
||||
@@ -81,6 +86,8 @@ export const runUpdateBalanceTask = async (payload: any) => {
|
||||
internalCustomerId: payload.internalCustomerId,
|
||||
belowThresholdPrice,
|
||||
});
|
||||
} else {
|
||||
console.log(" ✅ No below threshold price found");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error updating customer balance: ${error}`);
|
||||
|
||||
@@ -16,6 +16,7 @@ const { data, error } = await sb
|
||||
.eq("id", customerId)
|
||||
.eq("env", "sandbox")
|
||||
.single();
|
||||
|
||||
const stripeCusId = data.processor.id;
|
||||
const stripeCli = new stripe(process.env.STRIPE_TEST_KEY!);
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export enum InvoiceStatus {
|
||||
Draft = "draft",
|
||||
Open = "open",
|
||||
Void = "void",
|
||||
Paid = "paid",
|
||||
Uncollectible = "uncollectible",
|
||||
}
|
||||
|
||||
export const InvoiceSchema = z.object({
|
||||
id: z.string(),
|
||||
created_at: z.number(),
|
||||
internal_customer_id: z.string(),
|
||||
product_ids: z.array(z.string()),
|
||||
|
||||
processor: z.object({
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
hosted_invoice_url: z.string().nullable(),
|
||||
}),
|
||||
// Stripe fields
|
||||
stripe_id: z.string(),
|
||||
status: z.nativeEnum(InvoiceStatus).nullable().optional(),
|
||||
hosted_invoice_url: z.string().nullable(),
|
||||
});
|
||||
|
||||
export type Invoice = z.infer<typeof InvoiceSchema>;
|
||||
|
||||
Reference in New Issue
Block a user