diff --git a/server/package.json b/server/package.json index ac7f97bdc..732d43de5 100644 --- a/server/package.json +++ b/server/package.json @@ -16,7 +16,7 @@ "cron:start": "node --no-deprecation dist/src/cron.js", "clean": "tsx src/clean.ts", "test-all": "mocha 'tests/**/*.ts'", - "test": "mocha 'tests/topup.ts'" + "test": "mocha 'tests/**/*.ts'" }, "mocha": { "node-option": [ @@ -68,7 +68,7 @@ "@types/node": "^22.13.0", "@types/pg": "^8.11.10", "mocha": "^11.1.0", - "puppeteer": "^24.1.1", + "puppeteer": "^24.2.0", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.7.3" diff --git a/server/src/external/stripe/stripeCusUtils.ts b/server/src/external/stripe/stripeCusUtils.ts index 3643750d3..7114c2cab 100644 --- a/server/src/external/stripe/stripeCusUtils.ts +++ b/server/src/external/stripe/stripeCusUtils.ts @@ -4,6 +4,7 @@ import { createStripeCli } from "./utils.js"; import RecaseError from "@/utils/errorUtils.js"; import { ErrCode } from "@/errors/errCodes.js"; import { StatusCodes } from "http-status-codes"; +import { SupabaseClient } from "@supabase/supabase-js"; export const createStripeCustomer = async ({ org, @@ -86,11 +87,42 @@ export const getCusPaymentMethod = async ({ }; // 2. Create a payment method and attach to customer -export const attachPmToCus = async ( - stripeCli: Stripe, - stripeCusId: string, - willFail: boolean = false -) => { +export const attachPmToCus = async ({ + sb, + customer, + org, + env, + willFail = false, +}: { + sb: SupabaseClient; + customer: Customer; + org: Organization; + env: AppEnv; + willFail?: boolean; +}) => { + // 1. Create stripe customer if not exists + + let stripeCusId = customer.processor?.stripe_id; + if (!stripeCusId) { + const stripeCustomer = await createStripeCustomer({ + org, + env, + customer, + }); + + await sb + .from("customers") + .update({ + processor: { + stripe_id: stripeCustomer.id, + }, + }) + .eq("internal_id", customer.internal_id); + stripeCusId = stripeCustomer.id; + } + + const stripeCli = createStripeCli({ org, env }); + try { let token = willFail ? "tok_chargeCustomerFail" : "tok_visa"; const pm = await stripeCli.paymentMethods.create({ diff --git a/server/src/internal/api/customers/products/cusProductRouter.ts b/server/src/internal/api/customers/products/cusProductRouter.ts index 0a72ce869..770b05419 100644 --- a/server/src/internal/api/customers/products/cusProductRouter.ts +++ b/server/src/internal/api/customers/products/cusProductRouter.ts @@ -1,9 +1,11 @@ import { Router } from "express"; import RecaseError, { handleRequestError } from "@/utils/errorUtils.js"; +import { z } from "zod"; import { BillingType, Entitlement, FeatureOptions, + FeatureOptionsSchema, Price, ProcessorType, } from "@autumn/shared"; @@ -289,11 +291,13 @@ attachRouter.post("/attach", async (req: any, res) => { const pricesInput: PricesInput = prices || []; const entsInput: Entitlement[] = entitlements || []; const optionsListInput: FeatureOptions[] = options || []; + const useCheckout = force_checkout || false; console.log("--------------------------------"); console.log("Add product request received"); try { + z.array(FeatureOptionsSchema).parse(optionsListInput); // 1. Get full customer product data const attachParams = await getFullCusProductData({ sb, diff --git a/server/src/internal/api/entitled/entitledRouter.ts b/server/src/internal/api/entitled/entitledRouter.ts index affb975ba..b3d93b619 100644 --- a/server/src/internal/api/entitled/entitledRouter.ts +++ b/server/src/internal/api/entitled/entitledRouter.ts @@ -143,9 +143,16 @@ const checkFeatureAccessAllowed = ({ quantity: number; }) => { if (originalFeature.type === FeatureType.Boolean) { + console.log("Boolean feature", originalFeature.id); + return { allowed: cusEnts.some((ent) => ent.feature_id === originalFeature.id), - balances: [], + balances: [ + { + feature_id: originalFeature.id, + balance: null, + }, + ], }; } diff --git a/server/tests-old/global.ts b/server/tests-old/global.ts new file mode 100644 index 000000000..ff8e05c5d --- /dev/null +++ b/server/tests-old/global.ts @@ -0,0 +1,742 @@ +import dotenv from "dotenv"; +dotenv.config(); + +import * as chai from "chai"; +import { default as chaiHttp, request } from "chai-http"; +import { createClerkCli } from "../src/external/clerkUtils.js"; +import { createSupabaseClient } from "../src/external/supabaseUtils.js"; +import { + AllowanceType, + AppEnv, + BillingInterval, + CusProductStatus, + EntInterval, + Feature, + FeatureType, + Organization, + PriceType, +} from "@autumn/shared"; +import { OrgService } from "@/internal/orgs/OrgService.js"; +import axios from "axios"; +import { expect } from "chai"; + +import { createStripeCli } from "@/external/stripe/utils.js"; +import { attachPmToCus } from "@/external/stripe/stripeCusUtils.js"; + +const ORG_SLUG = "unit-test-org"; +const AUTUMN_API_KEY = "am_test_3ZnkWSjVEpVGjLokP6HnP7t6"; +const clerkCli = createClerkCli(); +const sb = createSupabaseClient(); +const env = AppEnv.Sandbox; + +const axiosInstance = axios.create({ + baseURL: "http://localhost:8080", + headers: { + Authorization: `Bearer ${AUTUMN_API_KEY}`, + }, +}); + +const timeout = async (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +chai.use(chaiHttp); + +const vars = { + customer_id: "test-customer", + metered_1: { + id: "metered-1", + event_name: "metered_1_event", + credit_amount: 1, + }, + metered_2: { + id: "metered-2", + event_name: "metered_2_event", + credit_amount: 5, + }, + credits_1: { + id: "credits-1", + event_name: "credits_1_event", + }, + free: { + id: "free", + metered_1_allowance: 3, + metered_2_allowance: 2, + credits_1_allowance: 11, + }, + pro: { + id: "pro", + metered_1_allowance: "unlimited", + metered_2_allowance: "unlimited", + credits_1_allowance: 100, + }, +}; + +const clearOrg = async ({ orgId, env }: { orgId: string; env: AppEnv }) => { + // 1. Delete all customers + const { error } = await sb + .from("customers") + .delete() + .eq("org_id", orgId) + .eq("env", env); + + // 2. Delete all products + const { error: productError } = await sb + .from("products") + .delete() + .eq("org_id", orgId) + .eq("env", env); + if (productError) { + console.error("Error deleting products:", productError); + } + + // // 3. Delete all features + await sb.from("features").delete().eq("org_id", orgId).eq("env", env); +}; + +const setupOrg = async (org: Organization, env: AppEnv) => { + // 1. Create features + const features: Feature[] = [ + { + id: "boolean-1", + name: "Boolean 1", + type: FeatureType.Boolean, + }, + { + id: "metered-1", + name: "Metered 1", + type: FeatureType.Metered, + config: { + filters: [ + { + value: ["metered_1_event"], + property: "", + operator: "", + }, + ], + aggregate: { + type: "count", + property: null, + }, + }, + }, + { + id: "metered-2", + name: "Metered 2", + type: FeatureType.Metered, + config: { + filters: [ + { + value: ["metered_2_event"], + property: "", + operator: "", + }, + ], + aggregate: { + type: "sum", + property: "value", + }, + }, + }, + { + id: "credits-1", + name: "Credits 1", + type: FeatureType.CreditSystem, + config: { + schema: [ + { + metered_feature_id: "metered-1", + feature_amount: 1, + credit_amount: vars.metered_1.credit_amount, + }, + { + metered_feature_id: "metered-2", + feature_amount: 1, + credit_amount: vars.metered_2.credit_amount, + }, + ], + }, + }, + ]; + + let insertFeatures = []; + for (const feature of features) { + insertFeatures.push(axiosInstance.post("/v1/features", feature)); + } + + await Promise.all(insertFeatures); + + const { data: newFeatures } = await sb + .from("features") + .select("*") + .eq("org_id", org.id) + .eq("env", env); + + console.log( + "✅ Inserted features (boolean-1, metered-1, metered-2, credits-1" + ); + + // 2. Create products + const products: any[] = [ + // A. free product (3 metered-1, 1 metered-2, 5) + { + id: "free", + name: "Free", + group: "g1", + is_add_on: false, + is_default: true, + prices: [], + entitlements: [ + { + feature_id: "metered-1", + allowance_type: AllowanceType.Fixed, + allowance: vars.free.metered_1_allowance, + interval: EntInterval.Month, + internal_feature_id: newFeatures?.find((f) => f.id === "metered-1") + ?.internal_id, + }, + { + feature_id: "metered-2", + allowance_type: AllowanceType.Fixed, + allowance: vars.free.metered_2_allowance, + interval: EntInterval.Month, + internal_feature_id: newFeatures?.find((f) => f.id === "metered-2") + ?.internal_id, + }, + { + feature_id: "credits-1", + allowance_type: AllowanceType.Fixed, + allowance: vars.free.credits_1_allowance, + interval: EntInterval.Month, + internal_feature_id: newFeatures?.find((f) => f.id === "credits-1") + ?.internal_id, + }, + ], + }, + + // B. pro product (20 USD / month) (Unlimited metered-1, Unlimited metered-2, 10 credits-1), 7 day free trial + { + id: "pro", + name: "Pro", + group: "g1", + is_add_on: false, + is_default: false, + prices: [ + { + name: "Monthly", + config: { + type: PriceType.Fixed, + amount: 20, + interval: BillingInterval.Month, + }, + }, + ], + entitlements: [ + { + feature_id: "boolean-1", + internal_feature_id: newFeatures?.find((f) => f.id === "boolean-1") + ?.internal_id, + }, + { + feature_id: "metered-1", + allowance_type: AllowanceType.Unlimited, + allowance: null, + interval: EntInterval.Month, + internal_feature_id: newFeatures?.find((f) => f.id === "metered-1") + ?.internal_id, + }, + { + feature_id: "metered-2", + allowance_type: AllowanceType.Unlimited, + allowance: null, + interval: EntInterval.Month, + internal_feature_id: newFeatures?.find((f) => f.id === "metered-2") + ?.internal_id, + }, + { + feature_id: "credits-1", + allowance_type: AllowanceType.Fixed, + allowance: vars.pro.credits_1_allowance, + interval: EntInterval.Month, + internal_feature_id: newFeatures?.find((f) => f.id === "credits-1") + ?.internal_id, + }, + ], + free_trial: { + length: 7, + unique_fingerprint: true, + }, + }, + ]; + + let insertProducts = []; + for (const product of products) { + const insertProduct = async () => { + await axiosInstance.post("/v1/products", { + product: { + id: product.id, + name: product.name, + group: product.group, + is_add_on: product.is_add_on, + is_default: product.is_default, + }, + }); + + await axiosInstance.post(`/v1/products/${product.id}`, { + prices: product.prices, + entitlements: product.entitlements, + free_trial: product.free_trial, + }); + }; + + insertProducts.push(insertProduct()); + } + + await Promise.all(insertProducts); + + console.log("✅ Inserted products (free, pro)"); + + // Create test customer + const { data } = await axiosInstance.post("/v1/customers", { + id: vars.customer_id, + email: "test@test.com", + name: "Test Customer", + fingerprint: "test-fp", + }); + + console.log("✅ Created test customer"); + return { customer: data.customer }; +}; + +before(async function () { + console.log("Running setup"); + this.timeout(5000); + + const org = await OrgService.getBySlug({ sb, slug: ORG_SLUG }); + this.org = org; + + await clearOrg({ orgId: org.id, env }); + const { customer } = await setupOrg(org, env); + this.customer = customer; + + console.log("--------------------------------"); +}); + +describe("Free tier -- checking balances before any usage", () => { + it("boolean-1", async function () { + const { data: boolean1Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: "boolean-1", + }); + expect(boolean1Data.allowed).to.equal(false); + }); + + it("metered-1 / credits-1", async function () { + // 2. Check metered-1 feature + const { data: metered1Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: vars.metered_1.id, + }); + + // 1. Check that metered-1 balance is 3 + let metered1Balance = metered1Data.balances.find( + (b: any) => b.feature_id === vars.metered_1.id + ); + expect(metered1Balance.balance).to.equal(vars.free.metered_1_allowance); + + // 2. Check that metered-2 balance is 0 + let creditsBalance = metered1Data.balances.find( + (b: any) => b.feature_id === vars.credits_1.id + ); + expect(creditsBalance.balance).to.equal(vars.free.credits_1_allowance); + }); + + it("metered-2 / credits-1", async function () { + const { data: metered2Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: vars.metered_2.id, + }); + + let metered2Balance = metered2Data.balances.find( + (b: any) => b.feature_id === vars.metered_2.id + ); + expect(metered2Balance.balance).to.equal(vars.free.metered_2_allowance); + + let creditsBalance = metered2Data.balances.find( + (b: any) => b.feature_id === vars.credits_1.id + ); + expect(creditsBalance.balance).to.equal(vars.free.credits_1_allowance); + }); +}); + +describe("Free tier -- checking balances after metered-1 / metered-2 events", () => { + before(async function () { + console.log( + ` - Sending ${vars.free.metered_1_allowance} metered-1 events` + ); + console.log(` - Sending 1 metered-2 events`); + + this.timeout(10000); + const sendEvents = []; + for (let i = 0; i < vars.free.metered_1_allowance; i++) { + sendEvents.push( + axiosInstance.post(`/v1/events`, { + customer_id: vars.customer_id, + event_name: vars.metered_1.event_name, + }) + ); + } + + sendEvents.push( + axiosInstance.post(`/v1/events`, { + customer_id: vars.customer_id, + event_name: vars.metered_2.event_name, + properties: { + value: 1, + }, + }) + ); + + await Promise.all(sendEvents); + await timeout(2000); + }); + + it("metered-1 / credits-1", async function () { + const { data: metered1Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: vars.metered_1.id, + }); + + const metered1Balance = metered1Data.balances.find( + (b: any) => b.feature_id === vars.metered_1.id + ); + + const creditsBalance = metered1Data.balances.find( + (b: any) => b.feature_id === vars.credits_1.id + ); + + const expectedCreditsBalance = + vars.free.credits_1_allowance - + vars.free.metered_1_allowance - + vars.metered_2.credit_amount; + + expect(metered1Balance.balance).to.equal(0); + expect(creditsBalance.balance).to.equal(expectedCreditsBalance); + expect(metered1Data.allowed).to.equal(false); + }); + + it("metered-2 / credits-1", async function () { + const { data: metered2Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: vars.metered_2.id, + }); + + const metered2Balance = metered2Data.balances.find( + (b: any) => b.feature_id === vars.metered_2.id + ); + + const expectedMetered2Balance = vars.free.metered_2_allowance - 1; + + expect(metered2Balance.balance).to.equal(expectedMetered2Balance); + expect(metered2Data.allowed).to.equal(false); + }); +}); + +// CASE: Customer upgrades to pro, receives credits +describe("Upgrade to pro -- checking products & balances", () => { + before(async function () { + // 1. Attach payment method to customer + this.timeout(10000); + const org = this.org; + const customer = this.customer; + + const stripeCli = createStripeCli({ org, env }); + + try { + if (!customer.processor || !customer.processor.id) { + const stripeCus = await stripeCli.customers.create({ + email: customer.email, + name: customer.name, + }); + + customer.processor = { + id: stripeCus.id, + type: "stripe", + }; + + const { error } = await sb + .from("customers") + .update({ + processor: { + id: stripeCus.id, + type: "stripe", + }, + }) + .eq("internal_id", customer.internal_id); + + if (error) { + console.log(" - Error updating customer:", error); + } + } + + await attachPmToCus(stripeCli, customer.processor.id); + console.log(" - Attached payment method"); + // 2. Attach product + const { data } = await axiosInstance.post(`/v1/attach`, { + customer_id: customer.id, + product_id: vars.pro.id, + }); + + if (data.checkout_url) { + console.log(" - Checkout URL:", data.checkout_url); + throw new Error("checkout_url received"); + } + + console.log(" - Attached product"); + } catch (error) { + console.log(" - Error attaching payment method", error); + } + }); + + it("on pro product", async function () { + const { data } = await axiosInstance.get( + `/v1/customers/${vars.customer_id}` + ); + const products = data.products; + + expect(products.length).to.equal(1); + expect(products[0].id).to.equal(vars.pro.id); + expect(products[0].status).to.equal(CusProductStatus.Trialing); + }); + + it("boolean-1", async function () { + const { data: boolean1Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: "boolean-1", + }); + expect(boolean1Data.allowed).to.equal(true); + }); + + it("metered-1 / metered-2 / credits-1", async function () { + const { data: metered1Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: vars.metered_1.id, + }); + + const { data: metered2Data } = await axiosInstance.post(`/v1/entitled`, { + customer_id: vars.customer_id, + feature_id: vars.metered_2.id, + }); + + const metered1Balance = metered1Data.balances.find( + (b: any) => b.feature_id === vars.metered_1.id + ); + const metered2Balance = metered2Data.balances.find( + (b: any) => b.feature_id === vars.metered_2.id + ); + + const creditBalance = metered1Data.balances.find( + (b: any) => b.feature_id === vars.credits_1.id + ); + + expect(metered1Balance.unlimited).to.equal(true); + expect(metered2Balance.unlimited).to.equal(true); + expect(creditBalance.balance).to.equal(vars.pro.credits_1_allowance); + expect(metered1Data.allowed).to.equal(true); + expect(metered2Data.allowed).to.equal(true); + }); +}); + +// describe("Downgrade to free", () => { +// before(async function () { +// this.timeout(10000); +// await axiosInstance.post(`/v1/attach`, { +// customer_id: vars.customer_id, +// product_id: vars.free.id, +// }); +// await timeout(3000); +// console.log(" - Attached free product"); +// }); + +// it("Checking free product scheduled", async function () { +// const { data: products } = await axiosInstance.get( +// `/v1/customers/${vars.customer_id}/products` +// ); + +// expect(products.length).to.equal(2); +// const freeProduct = products.find((p: any) => p.id === vars.free.id); +// expect(freeProduct.status).to.equal(CusProductStatus.Scheduled); +// expect(freeProduct.canceled_at).to.equal(null); + +// const proProduct = products.find((p: any) => p.id === vars.pro.id); +// expect(proProduct.status).to.equal(CusProductStatus.Active); +// expect(proProduct.canceled_at).to.not.equal(null); +// }); + +// it("boolean-1", async function () { +// const { data: boolean1Data } = await axiosInstance.post(`/v1/entitled`, { +// customer_id: vars.customer_id, +// feature_id: "boolean-1", +// }); +// expect(boolean1Data.allowed).to.equal(true); +// }); + +// it("metered-1 / metered-2 / credits-1", async function () { +// const { data: metered1Data } = await axiosInstance.post(`/v1/entitled`, { +// customer_id: vars.customer_id, +// feature_id: vars.metered_1.id, +// }); + +// const { data: metered2Data } = await axiosInstance.post(`/v1/entitled`, { +// customer_id: vars.customer_id, +// feature_id: vars.metered_2.id, +// }); + +// const metered1Balance = metered1Data.balances.find( +// (b: any) => b.feature_id === vars.metered_1.id +// ); +// const metered2Balance = metered2Data.balances.find( +// (b: any) => b.feature_id === vars.metered_2.id +// ); + +// const creditBalance = metered1Data.balances.find( +// (b: any) => b.feature_id === vars.credits_1.id +// ); + +// expect(metered1Balance.unlimited).to.equal(true); +// expect(metered1Data.allowed).to.equal(true); + +// expect(metered2Balance.unlimited).to.equal(true); +// expect(metered2Data.allowed).to.equal(true); + +// expect(creditBalance.balance).to.equal(vars.pro.credits_1_allowance); +// }); +// }); + +// describe("Restore to pro", () => { +// before(async function () { +// this.timeout(10000); +// await axiosInstance.post(`/v1/attach`, { +// customer_id: vars.customer_id, +// product_id: vars.pro.id, +// }); +// await timeout(3000); +// console.log(" - Attached pro product"); +// }); + +// it("Pro product active", async function () { +// this.timeout(10000); + +// const { data: products } = await axiosInstance.get( +// `/v1/customers/${vars.customer_id}/products` +// ); + +// expect(products.length).to.equal(1); +// expect(products[0].id).to.equal(vars.pro.id); +// expect(products[0].status).to.equal(CusProductStatus.Active); +// expect(products[0].canceled_at).to.equal(null); +// }); +// }); + +// STRIPE CANCELLATION +describe("Stripe cancellation", () => { + before(async function () { + this.timeout(10000); + const stripeCli = createStripeCli({ org: this.org, env }); + + // 1. Cancel pro product + const { data } = await axiosInstance.get( + `/v1/customers/${vars.customer_id}` + ); + const products = data.products; + const proProduct = products.find((p: any) => p.id === vars.pro.id); + + await stripeCli.subscriptions.update( + proProduct.processor.subscription_id!, + { + cancel_at_period_end: true, + } + ); + + await timeout(3000); + console.log(" - Cancelled pro product"); + }); + + it("Pro product cancelled", async function () { + const { data } = await axiosInstance.get( + `/v1/customers/${vars.customer_id}` + ); + const products = data.products; + + const proProduct = products.find((p: any) => p.id === vars.pro.id); + + expect(proProduct.status).to.equal(CusProductStatus.Trialing); + expect(proProduct.canceled_at).to.not.equal(null); + }); +}); + +describe("Stripe renewal", () => { + before(async function () { + this.timeout(10000); + const stripeCli = createStripeCli({ org: this.org, env }); + + // 1. Enable pro product + const { data } = await axiosInstance.get( + `/v1/customers/${vars.customer_id}` + ); + const products = data.products; + const proProduct = products.find((p: any) => p.id === vars.pro.id); + + await stripeCli.subscriptions.update( + proProduct.processor.subscription_id!, + { + cancel_at_period_end: false, + } + ); + + await timeout(3000); + console.log(" - Enabled pro product"); + }); + + it("Pro product active", async function () { + const { data } = await axiosInstance.get( + `/v1/customers/${vars.customer_id}` + ); + const products = data.products; + const proProduct = products.find((p: any) => p.id === vars.pro.id); + expect(proProduct.status).to.equal(CusProductStatus.Trialing); + expect(proProduct.canceled_at).to.equal(null); + }); +}); + +describe("Stripe cancel now", () => { + before(async function () { + this.timeout(10000); + const stripeCli = createStripeCli({ org: this.org, env }); + + // 1. Cancel pro product + const { data } = await axiosInstance.get( + `/v1/customers/${vars.customer_id}` + ); + const products = data.products; + const proProduct = products.find((p: any) => p.id === vars.pro.id); + + await stripeCli.subscriptions.cancel(proProduct.processor.subscription_id!); + + await timeout(3000); + console.log(" - Cancelled pro product"); + }); + + it("Pro product cancelled", async function () { + const { data } = await axiosInstance.get( + `/v1/customers/${vars.customer_id}` + ); + const products = data.products; + + const freeProduct = products.find((p: any) => p.id === vars.free.id); + expect(products.length).to.equal(1); + expect(freeProduct.id).to.equal(vars.free.id); + expect(freeProduct.status).to.equal(CusProductStatus.Active); + expect(freeProduct.canceled_at).to.equal(null); + }); +}); diff --git a/server/tests-old/metered.ts b/server/tests-old/metered.ts new file mode 100644 index 000000000..ba2cc31e1 --- /dev/null +++ b/server/tests-old/metered.ts @@ -0,0 +1,87 @@ +import dotenv from "dotenv"; +dotenv.config(); +import { AppEnv } from "@autumn/shared"; +import axios from "axios"; +import { initCustomer } from "./utils.js"; + +const apiKey = "am_test_3Zbu1qcRkR6mhq5gtgqScvVN"; +const apiUrl = "http://localhost:8080/v1"; +const orgId = "org_2s4vfEyYVgFZDlOwcMHjsHR0eef"; +const env = AppEnv.Sandbox; + +const timeout = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +const sendEvent = async () => { + try { + const { data } = await axios.post( + `${apiUrl}/events`, + { + customer_id: "123", + event_name: "sonnet_message", + properties: { + value: 1, + }, + }, + { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + } + ); + console.log(data); + } catch (error: any) { + console.error(error.response.data); + } +}; +const main = async () => { + await initCustomer({ + customerName: "Test 11", + customerId: "123", + productId: "starter", + orgId, + env, + apiKey, + apiUrl, + }); + console.log("Customer initialized"); + await timeout(3000); + + const batchEvents = []; + for (let i = 0; i < 50; i++) { + batchEvents.push(sendEvent()); + } + + // await Promise.all(batchEvents); + // console.log("Done sending events"); + + // // View meter usage + // const sb = createSupabaseClient(); + // const customer = await CusService.getById({ + // sb, + // id: "123", + // orgId, + // env, + // }); + + // const org = await OrgService.getFullOrg({ + // sb, + // orgId, + // }); + + // const stripeCli = createStripeCli({ org, env }); + // const meterSummary = await stripeCli.billing.meters.listEventSummaries( + // "mtr_test_61RzIB9NJlpZILzUB41GHoZUApXXdRCK", + // { + // customer: customer.processor.id, + // end_time: Math.floor(Date.now() / 1000), + // start_time: Math.floor(Date.now() / 1000) - 1000 * 60 * 60 * 24, + // } + // ); + // console.log(meterSummary); + // const stripeCus = await stripeCli.customers.retrieve( + // customer.processor.id + // ); +}; + +main(); diff --git a/server/tests-old/utils.ts b/server/tests-old/utils.ts new file mode 100644 index 000000000..fa7042515 --- /dev/null +++ b/server/tests-old/utils.ts @@ -0,0 +1,93 @@ +// Initialize customer +import axios from "axios"; +import { createSupabaseClient } from "@/external/supabaseUtils.js"; +import { OrgService } from "@/internal/orgs/OrgService.js"; +import { AppEnv } from "@autumn/shared"; +import { createStripeCli } from "@/external/stripe/utils.js"; +import { attachPmToCus } from "@/external/stripe/stripeCusUtils.js"; +import { CusService } from "@/internal/customers/CusService.js"; + +export const initCustomer = async ({ + orgId, + env, + apiKey, + apiUrl, + customerId, + customerName, + productId, +}: { + orgId: string; + env: AppEnv; + apiKey: string; + apiUrl: string; + customerId: string; + customerName: string; + productId: string; +}) => { + const sb = createSupabaseClient(); + const org = await OrgService.getFullOrg({ + sb, + orgId, + }); + + // Reinitialize customer + try { + await axios.delete(`${apiUrl}/customers/123`, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + } catch (error) {} + + const { data } = await axios.post( + `${apiUrl}/customers`, + { + id: customerId, + name: customerName, + email: "test@gmail.com", + }, + { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + } + ); + + const customer = data.customer; + + const stripeCli = createStripeCli({ org, env }); + const stripeCus = await stripeCli.customers.create({ + email: data.customer.email, + name: data.customer.name, + }); + + customer.processor = { + id: stripeCus.id, + type: "stripe", + }; + await CusService.update({ + sb, + internalCusId: data.customer.internal_id, + update: { + processor: { id: stripeCus.id, type: "stripe" }, + }, + }); + + await attachPmToCus(stripeCli, data.customer.processor.id); + + if (productId) { + const { data: res } = await axios.post( + `${apiUrl}/attach`, + { + customer_id: data.customer.id, + product_id: productId, + }, + { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + } + ); + console.log("res: ", res); + } +}; diff --git a/server/tsconfig.json b/server/tsconfig.json index 3d4c0827d..326797002 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -25,7 +25,7 @@ "@shared/*": ["../shared/*"] } }, - "include": ["src", "tests"], + "include": ["src", "tests", "tests-old/utils.ts", "tests-old/global.ts"], "references": [ { "path": "../shared"