Merge branch 'useautumn:staging' into staging
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -101,4 +101,5 @@ supabase/
|
||||
|
||||
# To load supabase
|
||||
# 1. Start supabase locally
|
||||
# 2. Load files
|
||||
# 2. Load files
|
||||
migration.sh
|
||||
150
bun.lock
150
bun.lock
@@ -47,7 +47,7 @@
|
||||
"@supabase/supabase-js": "^2.46.2",
|
||||
"@upstash/redis": "^1.35.1",
|
||||
"ai": "^4.3.10",
|
||||
"autumn-js": "^0.0.77",
|
||||
"autumn-js": "^0.1.4",
|
||||
"axios": "^1.8.3",
|
||||
"better-auth": "^1.2.9",
|
||||
"body-parser": "^1.20.3",
|
||||
@@ -147,7 +147,7 @@
|
||||
"@clerk/clerk-react": "^5.24.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@radix-ui/react-accordion": "^1.2.3",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-checkbox": "^1.1.4",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
@@ -167,7 +167,7 @@
|
||||
"ag-charts-community": "^12.0.2",
|
||||
"ag-grid-community": "^34.0.2",
|
||||
"ag-grid-react": "^34.0.2",
|
||||
"autumn-js": "^0.0.93",
|
||||
"autumn-js": "^0.1.8",
|
||||
"axios": "^1.8.3",
|
||||
"better-auth": "^1.2.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -313,15 +313,15 @@
|
||||
|
||||
"@browserbasehq/sdk": ["@browserbasehq/sdk@2.6.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA=="],
|
||||
|
||||
"@clerk/backend": ["@clerk/backend@2.6.2", "", { "dependencies": { "@clerk/shared": "^3.17.0", "@clerk/types": "^4.72.0", "cookie": "1.0.2", "standardwebhooks": "^1.0.0", "tslib": "2.8.1" } }, "sha512-IUTjLmA1QkqoJnB97S8Ay/oeFR1QtBxxzi9V2J8zncGdUUpAHRp9PfbUwe203VEZuoDD8n6PGfK4oiiq5CoKhQ=="],
|
||||
"@clerk/backend": ["@clerk/backend@2.6.3", "", { "dependencies": { "@clerk/shared": "^3.18.0", "@clerk/types": "^4.73.0", "cookie": "1.0.2", "standardwebhooks": "^1.0.0", "tslib": "2.8.1" } }, "sha512-x73eu/zDMc1E2YvvW+KW55B6BOfiUrgFhSJngBsZJ6+W5hWZph+XeCS7oEcZfhvQyUcb9M6tqM2R1w7htNgh1Q=="],
|
||||
|
||||
"@clerk/clerk-react": ["@clerk/clerk-react@5.38.1", "", { "dependencies": { "@clerk/shared": "^3.17.0", "@clerk/types": "^4.72.0", "tslib": "2.8.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" } }, "sha512-IOn/Raet3jwkug8P/gLMe2nsw2wKllWGOGPFOAaaYxbXfIZ8MPngNv2/MMgVRF7cAX1UwrmU1PzrLNtBJ/EHPQ=="],
|
||||
"@clerk/clerk-react": ["@clerk/clerk-react@5.39.0", "", { "dependencies": { "@clerk/shared": "^3.18.0", "@clerk/types": "^4.73.0", "tslib": "2.8.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" } }, "sha512-c1GJMj8wrzFrT3BbvZ4JwYY91BAY1M4yMypaAs3UNvtU0dDze7bmUVs02eW+z0GgaEQbGBVO4f5ru/pBXCGSKA=="],
|
||||
|
||||
"@clerk/express": ["@clerk/express@1.7.15", "", { "dependencies": { "@clerk/backend": "^2.6.2", "@clerk/shared": "^3.17.0", "@clerk/types": "^4.72.0", "tslib": "2.8.1" }, "peerDependencies": { "express": "^4.17.0 || ^5.0.0" } }, "sha512-75yh1fAl8rjPcHZKR0FT84yJLyOaAIOufHLs0SBkGVzYSX+V6pZpriiuh3QtAeXR8gSxQKF5/qMzDKpqUa/nCA=="],
|
||||
"@clerk/express": ["@clerk/express@1.7.16", "", { "dependencies": { "@clerk/backend": "^2.6.3", "@clerk/shared": "^3.18.0", "@clerk/types": "^4.73.0", "tslib": "2.8.1" }, "peerDependencies": { "express": "^4.17.0 || ^5.0.0" } }, "sha512-T+Gw0vjRl9gRG1aEFfJVf3EQ9r6jfHtvOq9NU4q64GrsgvCZmvhjsx514sx3mqKfXM5CzFnT5biUpNwY27aAwQ=="],
|
||||
|
||||
"@clerk/shared": ["@clerk/shared@3.17.0", "", { "dependencies": { "@clerk/types": "^4.72.0", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.9.0", "swr": "2.3.4" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-eYbA0xmKG1DluFmdVykXiElgZGTpCruEyXmIBAwokpxypd5nOpDsS1xvEKwYvZieLTZkFz21Z3Y6HdDI5cPxBQ=="],
|
||||
"@clerk/shared": ["@clerk/shared@3.18.0", "", { "dependencies": { "@clerk/types": "^4.73.0", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.9.0", "swr": "2.3.4" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-QGYbtPfJEp66AgaI0JtiRX04ejoUI8SmqUqqbAm3UXAeUAsKOyXoNbghNal97X7qeq1vPRRiDugK5B9WYN69uA=="],
|
||||
|
||||
"@clerk/types": ["@clerk/types@4.72.0", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-SEkgiQNeTstC0/mQjHCGBEyX0/ALyWAa5QZBBvVOok204r48MLipfIKsXQhyWE2Hk6FIo5WT6YyqD36jaxUEIw=="],
|
||||
"@clerk/types": ["@clerk/types@4.73.0", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-SFM29RFVqs0KyTds3F7s7DBYNoqeBDSZiWuso/WCgfQVI4H3j0gI+0BdBEiqwCILXr4jKBG2srDmzP5FVvx8Hg=="],
|
||||
|
||||
"@clickhouse/client": ["@clickhouse/client@1.12.0", "", { "dependencies": { "@clickhouse/client-common": "1.12.0" } }, "sha512-vJUSX8THhTzlVn0WxPukVjOgNRaSoY02ubQkB0LpqNoHFxXuF5jQZZAYvGZWpBGbYQ/4gfPrqu8g4TX5UKeNxA=="],
|
||||
|
||||
@@ -571,23 +571,23 @@
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
|
||||
|
||||
"@next/env": ["@next/env@15.4.5", "", {}, "sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ=="],
|
||||
"@next/env": ["@next/env@15.4.6", "", {}, "sha512-yHDKVTcHrZy/8TWhj0B23ylKv5ypocuCwey9ZqPyv4rPdUdRzpGCkSi03t04KBPyU96kxVtUqx6O3nE1kpxASQ=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-667R0RTP4DwxzmrqTs4Lr5dcEda9OxuZsVFsjVtxVMVhzSpo6nLclXejJVfQo2/g7/Z9qF3ETDmN3h65mTjpTQ=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-CL6mfGsKuFSyQjx36p2ftwMNSb8PQog8y0HO/ONLdQqDql7x3aJb/wB+LA651r4we2pp/Ck+qoRVUeZZEvSurA=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-KMSFoistFkaiQYVQQnaU9MPWtp/3m0kn2Xed1Ces5ll+ag1+rlac20sxG+MqhH2qYWX1O2GFOATQXEyxKiIscg=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-1hTVd9n6jpM/thnDc5kYHD1OjjWYpUJrJxY4DlEacT7L5SEOXIifIdTye6SQNNn8JDZrcN+n8AWOmeJ8u3KlvQ=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-PnOx1YdO0W7m/HWFeYd2A6JtBO8O8Eb9h6nfJia2Dw1sRHoHpNf6lN1U4GKFRzRDBi9Nq2GrHk9PF3Vmwf7XVw=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-4W+D/nw3RpIwGrqpFi7greZ0hjrCaioGErI7XHgkcTeWdZd146NNu1s4HnaHonLeNTguKnL2Urqvj28UJj6Gqw=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-XBbuQddtY1p5FGPc2naMO0kqs4YYtLYK/8aPausI5lyOjr4J77KTG9mtlU4P3NwkLI1+OjsPzKVvSJdMs3cFaw=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-N6Mgdxe/Cn2K1yMHge6pclffkxzbSGOydXVKYOjYqQXZYjLCfN/CuFkaYDeDHY2VBwSHyM2fUjYBiQCIlxIKDA=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-+WTeK7Qdw82ez3U9JgD+igBAP75gqZ1vbK6R8PlEEuY0OIe5FuYXA4aTjL811kWPf7hNeslD4hHK2WoM9W0IgA=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-YZ3bNDrS8v5KiqgWE0xZQgtXgCTUacgFtnEgI4ccotAASwSvcMPDLua7BWLuTfucoRv6mPidXkITJLd8IdJplQ=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-XP824mCbgQsK20jlXKrUpZoh/iO3vUWhMpxCz8oYeagoiZ4V0TQiKy0ASji1KK6IAe3DYGfj5RfKP6+L2020OQ=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-9Wr4t9GkZmMNcTVvSloFtjzbH4vtT4a8+UHqDoVnxA5QyfWe6c5flTH1BIWPGNWSUlofc8dVJAE7j84FQgskvQ=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-FxrsenhUz0LbgRkNWx6FRRJIPe/MI1JRA4W4EPd5leXO00AZ6YU8v5vfx4MDXTvN77lM/EqsE3+6d2CIeF5NYg=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-voWk7XtGvlsP+w8VBz7lqp8Y+dYw/MTI4KeS0gTVtfdhdJ5QwhXLmNrndFOin/MDoCvUaLWMkYKATaCoUkt2/A=="],
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.6", "", { "os": "win32", "cpu": "x64" }, "sha512-T4ufqnZ4u88ZheczkBTtOF+eKaM14V8kbjud/XrAakoM5DKQWjW09vD6B9fsdsWS2T7D5EY31hRHdta7QKWOng=="],
|
||||
|
||||
"@noble/ciphers": ["@noble/ciphers@0.6.0", "", {}, "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ=="],
|
||||
|
||||
@@ -961,13 +961,13 @@
|
||||
|
||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||
|
||||
"@sentry/core": ["@sentry/core@9.44.0", "", {}, "sha512-U+KBNGgq/eXIj226CPtRk+n5dx0q1xGVvbLbyfAyeek9C/wxQ3f+mvqeVqF9cx8FfrWIOeDM1F8ISH5uRkjjQg=="],
|
||||
"@sentry/core": ["@sentry/core@9.44.2", "", {}, "sha512-4wduCY9vz+VRMZXTT1dzk08L2nReeR+lzpY8hCcc+Wu100BoJR+TNlrSn1rG5iIo98NDW860JsRA7SVDUDOiNQ=="],
|
||||
|
||||
"@sentry/node": ["@sentry/node@9.44.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.2", "@opentelemetry/instrumentation-amqplib": "^0.46.1", "@opentelemetry/instrumentation-connect": "0.43.1", "@opentelemetry/instrumentation-dataloader": "0.16.1", "@opentelemetry/instrumentation-express": "0.47.1", "@opentelemetry/instrumentation-fs": "0.19.1", "@opentelemetry/instrumentation-generic-pool": "0.43.1", "@opentelemetry/instrumentation-graphql": "0.47.1", "@opentelemetry/instrumentation-hapi": "0.45.2", "@opentelemetry/instrumentation-http": "0.57.2", "@opentelemetry/instrumentation-ioredis": "0.47.1", "@opentelemetry/instrumentation-kafkajs": "0.7.1", "@opentelemetry/instrumentation-knex": "0.44.1", "@opentelemetry/instrumentation-koa": "0.47.1", "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", "@opentelemetry/instrumentation-mongodb": "0.52.0", "@opentelemetry/instrumentation-mongoose": "0.46.1", "@opentelemetry/instrumentation-mysql": "0.45.1", "@opentelemetry/instrumentation-mysql2": "0.45.2", "@opentelemetry/instrumentation-pg": "0.51.1", "@opentelemetry/instrumentation-redis-4": "0.46.1", "@opentelemetry/instrumentation-tedious": "0.18.1", "@opentelemetry/instrumentation-undici": "0.10.1", "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.34.0", "@prisma/instrumentation": "6.11.1", "@sentry/core": "9.44.0", "@sentry/node-core": "9.44.0", "@sentry/opentelemetry": "9.44.0", "import-in-the-middle": "^1.14.2", "minimatch": "^9.0.0" } }, "sha512-rU96Q7q7hL4s328z9zFS+ZRK6eHnLFjYbH8XHCxAxGFDLyg9kpkR5to9PjoI+QVPZ/LYAE+Xw0wStoMjWMCFsA=="],
|
||||
"@sentry/node": ["@sentry/node@9.44.2", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.2", "@opentelemetry/instrumentation-amqplib": "^0.46.1", "@opentelemetry/instrumentation-connect": "0.43.1", "@opentelemetry/instrumentation-dataloader": "0.16.1", "@opentelemetry/instrumentation-express": "0.47.1", "@opentelemetry/instrumentation-fs": "0.19.1", "@opentelemetry/instrumentation-generic-pool": "0.43.1", "@opentelemetry/instrumentation-graphql": "0.47.1", "@opentelemetry/instrumentation-hapi": "0.45.2", "@opentelemetry/instrumentation-http": "0.57.2", "@opentelemetry/instrumentation-ioredis": "0.47.1", "@opentelemetry/instrumentation-kafkajs": "0.7.1", "@opentelemetry/instrumentation-knex": "0.44.1", "@opentelemetry/instrumentation-koa": "0.47.1", "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", "@opentelemetry/instrumentation-mongodb": "0.52.0", "@opentelemetry/instrumentation-mongoose": "0.46.1", "@opentelemetry/instrumentation-mysql": "0.45.1", "@opentelemetry/instrumentation-mysql2": "0.45.2", "@opentelemetry/instrumentation-pg": "0.51.1", "@opentelemetry/instrumentation-redis-4": "0.46.1", "@opentelemetry/instrumentation-tedious": "0.18.1", "@opentelemetry/instrumentation-undici": "0.10.1", "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.34.0", "@prisma/instrumentation": "6.11.1", "@sentry/core": "9.44.2", "@sentry/node-core": "9.44.2", "@sentry/opentelemetry": "9.44.2", "import-in-the-middle": "^1.14.2", "minimatch": "^9.0.0" } }, "sha512-HTUDD73Tdr4GvvcNGQunkqEKeijHb4WYq/NX4YZP5VOeOsKsgIUsv55EgWk1BSHAFGTW6bfeMSoqaNVWiRHn0w=="],
|
||||
|
||||
"@sentry/node-core": ["@sentry/node-core@9.44.0", "", { "dependencies": { "@sentry/core": "9.44.0", "@sentry/opentelemetry": "9.44.0", "import-in-the-middle": "^1.14.2" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", "@opentelemetry/core": "^1.30.1 || ^2.0.0", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/resources": "^1.30.1 || ^2.0.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0" } }, "sha512-M6HOcA73WWzRuhqw4Fd2dqv9zEsvMteSNYOguTexIQCT2pzk1srACrt4uFfLY01s9FIKjw+tjrQfTbni2adv7Q=="],
|
||||
"@sentry/node-core": ["@sentry/node-core@9.44.2", "", { "dependencies": { "@sentry/core": "9.44.2", "@sentry/opentelemetry": "9.44.2", "import-in-the-middle": "^1.14.2" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", "@opentelemetry/core": "^1.30.1 || ^2.0.0", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/resources": "^1.30.1 || ^2.0.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0" } }, "sha512-TnyKZQ4FOCA+mkLLaOzFPePUBRBf0FU62hnNMscJviwb0UloOvHXx4Ub1DudfFFdnIeVSSMU96ou8vW1zR/1Uw=="],
|
||||
|
||||
"@sentry/opentelemetry": ["@sentry/opentelemetry@9.44.0", "", { "dependencies": { "@sentry/core": "9.44.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", "@opentelemetry/core": "^1.30.1 || ^2.0.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0" } }, "sha512-OeMiVoLqEXtpYE2VBAGmhK4GfbUa5ivDtL+AF4B+cR+NZkqZFlnA7ItquVfAa2Jd45TIyueEK8yjan5hluQYJQ=="],
|
||||
"@sentry/opentelemetry": ["@sentry/opentelemetry@9.44.2", "", { "dependencies": { "@sentry/core": "9.44.2" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", "@opentelemetry/core": "^1.30.1 || ^2.0.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0" } }, "sha512-KeW5MPXyq9Q8ieYUHO0PuzNNYEYizmTH6x02PG400GwmoeNxnT59Afa4TuPcrXN0QUmK76HJuPfC+7CTuCgoKA=="],
|
||||
|
||||
"@sentry/types": ["@sentry/types@8.55.0", "", { "dependencies": { "@sentry/core": "8.55.0" } }, "sha512-6LRT0+r6NWQ+RtllrUW2yQfodST0cJnkOmdpHA75vONgBUhpKwiJ4H7AmgfoTET8w29pU6AnntaGOe0LJbOmog=="],
|
||||
|
||||
@@ -1129,7 +1129,7 @@
|
||||
|
||||
"@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
||||
"@types/node": ["@types/node@24.2.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw=="],
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||
|
||||
@@ -1181,41 +1181,41 @@
|
||||
|
||||
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.38.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/type-utils": "8.38.0", "@typescript-eslint/utils": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.38.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA=="],
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.39.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.39.0", "@typescript-eslint/type-utils": "8.39.0", "@typescript-eslint/utils": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.39.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.38.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ=="],
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.39.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.39.0", "@typescript-eslint/types": "8.39.0", "@typescript-eslint/typescript-estree": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.38.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.38.0", "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg=="],
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.39.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.39.0", "@typescript-eslint/types": "^8.39.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0" } }, "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ=="],
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.39.0", "", { "dependencies": { "@typescript-eslint/types": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0" } }, "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.38.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ=="],
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.39.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0", "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg=="],
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.39.0", "", { "dependencies": { "@typescript-eslint/types": "8.39.0", "@typescript-eslint/typescript-estree": "8.39.0", "@typescript-eslint/utils": "8.39.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.38.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.38.0", "@typescript-eslint/tsconfig-utils": "8.38.0", "@typescript-eslint/types": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ=="],
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.39.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.39.0", "@typescript-eslint/tsconfig-utils": "8.39.0", "@typescript-eslint/types": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.38.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg=="],
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.39.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.39.0", "@typescript-eslint/types": "8.39.0", "@typescript-eslint/typescript-estree": "8.39.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g=="],
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.39.0", "", { "dependencies": { "@typescript-eslint/types": "8.39.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA=="],
|
||||
|
||||
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20250802.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250802.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250802.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20250802.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250802.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20250802.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250802.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20250802.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-CuxxalPlgXIiAN2b884D/3UX6M1FvAi+MgruRVp4kuKOfpmBjJr6PRhN12iekFQwRG2fPil8svIm4mPnSM71lw=="],
|
||||
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20250806.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250806.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250806.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20250806.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250806.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20250806.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250806.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20250806.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-dQWOle1t2QJHiGK+ILB5SCz1Q6VfGtkrZybT4FPSaddkskYN1djY6voxza8hd8yl1Yw0PmBmbqk3E4vTKyibig=="],
|
||||
|
||||
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20250802.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b8qDaguy/41W2PsA4TmGZ8wlaAg/SzG3jGJTPxv5o3tsyLQ7J/KqHljTox7RPC4tOnTzlsHYJXIuOUM+ZEIGcw=="],
|
||||
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20250806.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rRlWJEkaNCJmd5Ph4NQAJK4NMVwhWuycMKeGxRw8Lj68kNymZTVsNi/TDYN3I1InBj7Fg2kwzygessHHzZTb8A=="],
|
||||
|
||||
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20250802.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-WnHvj1RcW5PeQ7vPcUhGV4F1iyl749EjiWc1FIji1nFK46AJ0NXEO6I+AeqkNTyhpnenDj1YRlqW0JyziiBZSg=="],
|
||||
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20250806.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-n2RRV0+SHR6QnaTlJoTX6Q4M7T/P0GtWfOGiKa7QUzQFRQQNdZpTv6P2KAB38b/cfCfX7PRb6mhCWK9FMHwB3w=="],
|
||||
|
||||
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20250802.1", "", { "os": "linux", "cpu": "arm" }, "sha512-oEtMLt/iqADX7Dy4eVPQXzEA3kGVw47MEosHPg9CTnb9bZrEabwL8yC9knux12pqIH6zhdZAYkQIWxL6jUMOTA=="],
|
||||
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20250806.1", "", { "os": "linux", "cpu": "arm" }, "sha512-uriaW4phv1E6YkGTacGq8YzdZ11F1lgH1kfRjwDpzNB+UDV4ONqihU0mN1y0JRHhlqqhtKhBL6UnSqd4mbu6zA=="],
|
||||
|
||||
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20250802.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpDnhJTsbdOihfBCbnWfF6terDfgNtW/+rwFK1VbLY6JzOQdxWHiTwP+vYA1XBmNmQTidYGNF5ahfCYwx2kftw=="],
|
||||
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20250806.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-fwRhKF3t4OCp4ClfNrXIXwS5G+02AymnmgFGrhvnF0Wg5II8ReVVu9mvazVcfrTN9RtJo4xVofstSF2EbgQ0Sg=="],
|
||||
|
||||
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20250802.1", "", { "os": "linux", "cpu": "x64" }, "sha512-z+QPLdLcjkFPQ+HbBunq6U50Rd6Ywmu5ICAY4NaKgu5l1u/AyyMMhVaSINWjlZacB8CHGRl7ONb1z/vtH/iVyA=="],
|
||||
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20250806.1", "", { "os": "linux", "cpu": "x64" }, "sha512-EJ5/obfzrDse9KYXzZBpwzdpPad7+y/R+atXApAekIdULHLjYZ/5j/tZ9UMEnMWkCmZaFK6JpfHOG1jOGy71nA=="],
|
||||
|
||||
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20250802.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-diHuHgkeqKvQeP9VHMMBv7KJ0RvnPQ+ROenosRiw+sC5wButa5gR+vl1CLOtuhZXawnuRu20cxx9OFOgoODTLA=="],
|
||||
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20250806.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-hHY/4xBsV9SWqb2nAmd9J9FQfsbte5Q4WMB2JVXSbjni0EUdDJmQgdmQ+V7v5teMaaTYhpnRrdsO95jPiR3JAA=="],
|
||||
|
||||
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20250802.1", "", { "os": "win32", "cpu": "x64" }, "sha512-tGTxLuIrEZG8+Pze1QWDoB6AcnoiQ6S1txaeGub0SD8nOCgAiNmqAmaJekeEYlmHm8cj8j0u2G/l+geC9StYDA=="],
|
||||
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20250806.1", "", { "os": "win32", "cpu": "x64" }, "sha512-CFFcMz1CwZPL4TKaR10h/sZuRzGE0kLNfXq1zDDgsL1aO8gcOSDuVKrYDdwYGdedLUCINSort7kyv9nIFUKSqQ=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
@@ -1237,19 +1237,19 @@
|
||||
|
||||
"acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
|
||||
|
||||
"ag-charts-community": ["ag-charts-community@12.1.0", "", { "dependencies": { "ag-charts-core": "12.1.0", "ag-charts-locale": "12.1.0", "ag-charts-types": "12.1.0" } }, "sha512-UEY5SAnW46sE/QNq0hTAz3VlpE2qNScP1DEqAcDzNlxO10y16/Q3E9m0uY0Jprn1jGDrkRTdAFhcSPbkqMZ+Aw=="],
|
||||
"ag-charts-community": ["ag-charts-community@12.1.1", "", { "dependencies": { "ag-charts-core": "12.1.1", "ag-charts-locale": "12.1.1", "ag-charts-types": "12.1.1" } }, "sha512-5RfYXwqUr4yR1KIQAPvCsF9uaNRV6Ghs6Bj9tMn5bHL0xf1EOncEpkzXZjIXpM4yGxYk8UJKRTI3mByPjYRe8A=="],
|
||||
|
||||
"ag-charts-core": ["ag-charts-core@12.1.0", "", { "dependencies": { "ag-charts-types": "12.1.0" } }, "sha512-jfEqb9Ag1Bac7EoYOV8dtlGPL8AXce7+36ggL/2zfrvH/TgSflTYSh1lpGnfDkC0Q4zsl4L4StuBOK25y5/SSQ=="],
|
||||
"ag-charts-core": ["ag-charts-core@12.1.1", "", { "dependencies": { "ag-charts-types": "12.1.1" } }, "sha512-Kp/fXAtNmHWWo4MTHfYN1O4O5F3wWF5ZhLcjbfrdBcx3rBdbQGw46K/FcclK8Wyer3tmITBG9EZQFO/CDeu4vA=="],
|
||||
|
||||
"ag-charts-locale": ["ag-charts-locale@12.1.0", "", {}, "sha512-J7hO9oYAExsN/qv7MZRB6way+q+Z3ePoTXYBvCz+GA7NQ9dgpK/dnyZbWrakF2wlAW0OqasirzhSUM93PVB8kQ=="],
|
||||
"ag-charts-locale": ["ag-charts-locale@12.1.1", "", {}, "sha512-kpmtZvC12idQcNPnM3BIALmFUXvbb2TtCZtTuXqWdmtT7ykgnQCWWFczLOcM4EsgfnaudATI952JLAeQ/C2wwQ=="],
|
||||
|
||||
"ag-charts-react": ["ag-charts-react@12.1.0", "", { "dependencies": { "ag-charts-community": "12.1.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-V4RtA03zhUdqJNVAO/CUfCTpW2Q/Ior/cx5E6lKBtIDQkAfO0AxI/jTnukeOC3jflyiJWHe3kzGWYUhiGokjlg=="],
|
||||
"ag-charts-react": ["ag-charts-react@12.1.1", "", { "dependencies": { "ag-charts-community": "12.1.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-FaFFXdICK1CVfIzCkIZN3L44ME9H6ddDAFWGcHGviA2PtfSz6WiT8fgYLIFvn7l6Qx9IcWMfqFpJ3OTf+DV8tw=="],
|
||||
|
||||
"ag-charts-types": ["ag-charts-types@12.1.0", "", {}, "sha512-qeODwJ1EqKjpwEbp0mQ2wQ0arRNYaZo2BafdAGfcuOwjOBlagSwJvUg5MCvAYZ/W/mg2uEmt7jKMNfDy4ul4+Q=="],
|
||||
"ag-charts-types": ["ag-charts-types@12.1.1", "", {}, "sha512-VbAOfp1E7+Z/TBufJOIjUYdx70kQRDg49WoluiVKVSB0r0el/uvxARp9xjzx4ByBq9+Xq/V23tJVsRj1MS2A/g=="],
|
||||
|
||||
"ag-grid-community": ["ag-grid-community@34.1.0", "", { "dependencies": { "ag-charts-types": "12.1.0" } }, "sha512-3rZiOyyCGqSNqqTsrWafDVj1WfK43jfb53Ka5sqzdOG/yu6ySUFmdc0h/OuGLnkzwW5PC29coQwbS2rkb4c9dA=="],
|
||||
"ag-grid-community": ["ag-grid-community@34.1.1", "", { "dependencies": { "ag-charts-types": "12.1.1" } }, "sha512-ODVvGoMTkyGvMT8b5lzvum5r93bG6CKdJdNrk6u/aYS7oqZ5rUEXJJHC8n8Zq+o76KhFiXMBQrU39xuhz8i+Tg=="],
|
||||
|
||||
"ag-grid-react": ["ag-grid-react@34.1.0", "", { "dependencies": { "ag-grid-community": "34.1.0", "prop-types": "^15.8.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-CY1p4/JnvcwOt2HipmsqME9CWz7M21nb3OB1DhJGOvNUaxo1wF6Hb/pKpa20F3F/E93wQCNmCz+gfrSLPuJrQw=="],
|
||||
"ag-grid-react": ["ag-grid-react@34.1.1", "", { "dependencies": { "ag-grid-community": "34.1.1", "prop-types": "^15.8.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-ZffTltCJe5+yzC57LVTMFpjZiYUzFiX8+G4NNfM1MfiLV6cMl5kCEE6w3Afc0nSt8b1JCAZOiX6K0+VXqjIfYQ=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
@@ -1293,7 +1293,7 @@
|
||||
|
||||
"attr-accept": ["attr-accept@2.2.5", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="],
|
||||
|
||||
"autumn-js": ["autumn-js@0.0.77", "", { "dependencies": { "chalk": "^5.4.1", "rou3": "^0.6.1", "swr": "^2.3.3" } }, "sha512-P5btZQjaq/y4A7lcd4aK3QgZBtLZKwwQypCvJrX8SmLG/Oe+wyuWzIhdNsa/fO5ATExNmAkdP344dSUlfbgQnQ=="],
|
||||
"autumn-js": ["autumn-js@0.1.8", "", { "dependencies": { "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^3.24.1" }, "peerDependencies": { "better-auth": "^1.2.12", "better-call": "^1.0.12" }, "optionalPeers": ["better-auth", "better-call"] }, "sha512-tayOSj4hFYUvfaIkmjwDyMMnKZ4duTMzuLCOXBT1HZSgO/3guAqEmaz3OQupdw/JbhpnsOEVsQfrFV2dFUU2Mw=="],
|
||||
|
||||
"axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="],
|
||||
|
||||
@@ -1321,7 +1321,7 @@
|
||||
|
||||
"better-auth": ["better-auth@1.3.4", "", { "dependencies": { "@better-auth/utils": "0.2.5", "@better-fetch/fetch": "^1.1.18", "@noble/ciphers": "^0.6.0", "@noble/hashes": "^1.8.0", "@simplewebauthn/browser": "^13.0.0", "@simplewebauthn/server": "^13.0.0", "better-call": "^1.0.12", "defu": "^6.1.4", "jose": "^5.9.6", "kysely": "^0.28.1", "nanostores": "^0.11.3", "zod": "^4.0.5" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-JbZYam6Cs3Eu5CSoMK120zSshfaKvrCftSo/+v7524H1RvhryQ7UtMbzagBcXj0Digjj8hZtVkkR4tTZD/wK2g=="],
|
||||
|
||||
"better-call": ["better-call@1.0.12", "", { "dependencies": { "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1", "uncrypto": "^0.1.3" } }, "sha512-ssq5OfB9Ungv2M1WVrRnMBomB0qz1VKuhkY2WxjHaLtlsHoSe9EPolj1xf7xf8LY9o3vfk3Rx6rCWI4oVHeBRg=="],
|
||||
"better-call": ["better-call@1.0.13", "", { "dependencies": { "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1", "uncrypto": "^0.1.3" } }, "sha512-auqdP9lnNOli9tKpZIiv0nEIwmmyaD/RotM3Mucql+Ef88etoZi/t7Ph5LjlmZt/hiSahhNTt6YVnx6++rziXA=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
@@ -1367,7 +1367,7 @@
|
||||
|
||||
"chai-http": ["chai-http@5.1.2", "", { "dependencies": { "@types/superagent": "^8.1.7", "charset": "^1.0.1", "cookiejar": "^2.1.4", "is-ip": "^5.0.1", "methods": "^1.1.2", "qs": "^6.12.1", "superagent": "^10.0.0" } }, "sha512-UFup7mUGkkjmi9bGA7F6vfp3lzGQZjtL//CEd+a4C+vlynSv756XHDUK8PoYk/UpTBBXqSghjQaJOUMUxJXNaA=="],
|
||||
|
||||
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
"chalk": ["chalk@5.5.0", "", {}, "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg=="],
|
||||
|
||||
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
||||
|
||||
@@ -1447,7 +1447,7 @@
|
||||
|
||||
"cookiejar": ["cookiejar@2.1.4", "", {}, "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="],
|
||||
|
||||
"core-js": ["core-js@3.44.0", "", {}, "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw=="],
|
||||
"core-js": ["core-js@3.45.0", "", {}, "sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA=="],
|
||||
|
||||
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
|
||||
|
||||
@@ -1543,7 +1543,7 @@
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"devtools-protocol": ["devtools-protocol@0.0.1464554", "", {}, "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw=="],
|
||||
"devtools-protocol": ["devtools-protocol@0.0.1475386", "", {}, "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA=="],
|
||||
|
||||
"dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
|
||||
|
||||
@@ -1567,7 +1567,7 @@
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
|
||||
|
||||
"drizzle-zod": ["drizzle-zod@0.8.2", "", { "peerDependencies": { "drizzle-orm": ">=0.36.0", "zod": "^3.25.1" } }, "sha512-9Do/16OjFFNrQDZgvMtxtDDwKWbFOxUAIwNPKX98SfxrP8H18vhN1BvNXbhelLcdgCE7GEaXDJqBjMExSkhpkA=="],
|
||||
"drizzle-zod": ["drizzle-zod@0.8.3", "", { "peerDependencies": { "drizzle-orm": ">=0.36.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
@@ -1575,7 +1575,7 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.194", "", {}, "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.197", "", {}, "sha512-m1xWB3g7vJ6asIFz+2pBUbq3uGmfmln1M9SSvBe4QIFWYrRHylP73zL/3nMjDmwz8V+1xAXQDfBd6+HPW0WvDQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
@@ -1587,7 +1587,7 @@
|
||||
|
||||
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="],
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
@@ -2099,7 +2099,7 @@
|
||||
|
||||
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
|
||||
|
||||
"next": ["next@15.4.5", "", { "dependencies": { "@next/env": "15.4.5", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.5", "@next/swc-darwin-x64": "15.4.5", "@next/swc-linux-arm64-gnu": "15.4.5", "@next/swc-linux-arm64-musl": "15.4.5", "@next/swc-linux-x64-gnu": "15.4.5", "@next/swc-linux-x64-musl": "15.4.5", "@next/swc-win32-arm64-msvc": "15.4.5", "@next/swc-win32-x64-msvc": "15.4.5", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g=="],
|
||||
"next": ["next@15.4.6", "", { "dependencies": { "@next/env": "15.4.6", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.6", "@next/swc-darwin-x64": "15.4.6", "@next/swc-linux-arm64-gnu": "15.4.6", "@next/swc-linux-arm64-musl": "15.4.6", "@next/swc-linux-x64-gnu": "15.4.6", "@next/swc-linux-x64-musl": "15.4.6", "@next/swc-win32-arm64-msvc": "15.4.6", "@next/swc-win32-x64-msvc": "15.4.6", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-us++E/Q80/8+UekzB3SAGs71AlLDsadpFMXVNM/uQ0BMwsh9m3mr0UNQIfjKed8vpWXsASe+Qifrnu1oLIcKEQ=="],
|
||||
|
||||
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||
|
||||
@@ -2217,7 +2217,7 @@
|
||||
|
||||
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
||||
|
||||
"posthog-js": ["posthog-js@1.258.5", "", { "dependencies": { "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" }, "peerDependencies": { "@rrweb/types": "2.0.0-alpha.17", "rrweb-snapshot": "2.0.0-alpha.17" }, "optionalPeers": ["@rrweb/types", "rrweb-snapshot"] }, "sha512-Tx6CzS8MsGAQGPrQth5TbkGxGQgAY01SktNW773/KDmVOWiRVZq/WQF/MRJRiuFxJ7qjethZQi3aBWfWKdr1RA=="],
|
||||
"posthog-js": ["posthog-js@1.258.6", "", { "dependencies": { "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" }, "peerDependencies": { "@rrweb/types": "2.0.0-alpha.17", "rrweb-snapshot": "2.0.0-alpha.17" }, "optionalPeers": ["@rrweb/types", "rrweb-snapshot"] }, "sha512-vL5AGG+rOoRg3LGquMfBPO55jD4bGl0CiV44SHdHAoBnOVDDAqxczRGDqMdxor+VLx3/ofTFOJ2FNprfAHp70Q=="],
|
||||
|
||||
"posthog-node": ["posthog-node@4.18.0", "", { "dependencies": { "axios": "^1.8.2" } }, "sha512-XROs1h+DNatgKh/AlIlCtDxWzwrKdYDb2mOs58n4yN8BkGN9ewqeQwG5ApS4/IzwCb7HPttUkOVulkYatd2PIw=="],
|
||||
|
||||
@@ -2255,7 +2255,7 @@
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"puppeteer-core": ["puppeteer-core@24.15.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.6", "chromium-bidi": "7.2.0", "debug": "^4.4.1", "devtools-protocol": "0.0.1464554", "typed-query-selector": "^2.12.0", "ws": "^8.18.3" } }, "sha512-2iy0iBeWbNyhgiCGd/wvGrDSo73emNFjSxYOcyAqYiagkYt5q4cPfVXaVDKBsukgc2fIIfLAalBZlaxldxdDYg=="],
|
||||
"puppeteer-core": ["puppeteer-core@24.16.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.6", "chromium-bidi": "7.2.0", "debug": "^4.4.1", "devtools-protocol": "0.0.1475386", "typed-query-selector": "^2.12.0", "ws": "^8.18.3" } }, "sha512-tZ0tJiOYaDGTRzzr2giDpf8O/55JsoqkrafS1Xu4H6S8oP4eeL6RbZzY9OzjShSf5EQvx/zAc55QKpDqzXos/Q=="],
|
||||
|
||||
"pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="],
|
||||
|
||||
@@ -2323,7 +2323,7 @@
|
||||
|
||||
"recaseai": ["recaseai@0.0.37", "", { "dependencies": { "@anthropic-ai/sdk": "^0.32.1", "@supabase/supabase-js": "^2.47.2", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.10.1", "async-listen": "^3.0.1", "axios": "^1.7.9", "commander": "^12.1.0", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", "figures": "^6.1.0", "inquirer": "^12.1.0", "ksuid": "^3.0.0", "nanoid": "^5.0.9", "openai": "^4.76.0", "ora": "^8.1.1", "picocolors": "^1.1.1", "pino": "^9.5.0", "tsx": "^4.19.2", "typescript": "^5.7.2" }, "bin": { "recase": "dist/cli.js" } }, "sha512-cKVMWTGBnGtm8K+uD2vfMXOzxdHj1U3++vvTePpOoZABI/SY/jjDpW997vM5xeDnzz7Vk/qqVkLQUQ/ZMDXpsQ=="],
|
||||
|
||||
"recharts": ["recharts@3.1.0", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-NqAqQcGBmLrfDs2mHX/bz8jJCQtG2FeXfE0GqpZmIuXIjkpIwj8sd9ad0WyvKiBKPd8ZgNG0hL85c8sFDwascw=="],
|
||||
"recharts": ["recharts@3.1.2", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-vhNbYwaxNbk/IATK0Ki29k3qvTkGqwvCgyQAQ9MavvvBwjvKnMTswdbklJpcOAoMPN/qxF3Lyqob0zO+ZXkZ4g=="],
|
||||
|
||||
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
|
||||
|
||||
@@ -2341,7 +2341,7 @@
|
||||
|
||||
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
|
||||
|
||||
"resend": ["resend@4.7.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-30IbXGBUbmDweQH2IlO53XOXX7ndjYV9xFZ8IEBiWqefqQ/qmTsgrX0Ab6MUnmobJXbpdReVv+iXGRQPubQL5Q=="],
|
||||
"resend": ["resend@4.8.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
|
||||
@@ -2431,7 +2431,7 @@
|
||||
|
||||
"sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
|
||||
|
||||
"sonner": ["sonner@2.0.6", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q=="],
|
||||
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
@@ -2487,7 +2487,7 @@
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"svix": ["svix@1.70.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "@types/node": "^22.7.5", "es6-promise": "^4.2.8", "fast-sha256": "^1.3.0", "svix-fetch": "^3.0.0", "url-parse": "^1.5.10", "uuid": "^10.0.0" } }, "sha512-ZrYvUoHPPtzp+C3t5c2hvMMleeZL7teFMigt6u/gdlpEKyPopeX74eMJuHtuPixB+nB24gfWJ1W1N3B5ZZ+AJw=="],
|
||||
"svix": ["svix@1.70.1", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "@types/node": "^22.7.5", "es6-promise": "^4.2.8", "fast-sha256": "^1.3.0", "svix-fetch": "^3.0.0", "url-parse": "^1.5.10", "uuid": "^10.0.0" } }, "sha512-UDO8VBNyfZcqRjQQLanrOanGK+nCaMhBT7LJyXQBhPLEUcoDzRUhKujFdivl2OrleUVNknmuuz7CnYZ+4Axugg=="],
|
||||
|
||||
"svix-fetch": ["svix-fetch@3.0.0", "", { "dependencies": { "node-fetch": "^2.6.1", "whatwg-fetch": "^3.4.1" } }, "sha512-rcADxEFhSqHbraZIsjyZNh4TF6V+koloX1OzZ+AQuObX9mZ2LIMhm1buZeuc5BIZPftZpJCMBsSiBaeszo9tRw=="],
|
||||
|
||||
@@ -2567,13 +2567,13 @@
|
||||
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.38.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/parser": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0", "@typescript-eslint/utils": "8.38.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg=="],
|
||||
"typescript-eslint": ["typescript-eslint@8.39.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.39.0", "@typescript-eslint/parser": "8.39.0", "@typescript-eslint/typescript-estree": "8.39.0", "@typescript-eslint/utils": "8.39.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q=="],
|
||||
|
||||
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
||||
|
||||
"undefsafe": ["undefsafe@2.0.5", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="],
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
|
||||
|
||||
@@ -2693,8 +2693,6 @@
|
||||
|
||||
"@autumn/vite/@types/node": ["@types/node@22.17.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bbAKTCqX5aNVryi7qXVMi+OkB3w/OyblodicMbvE38blyAz7GxXf6XYhklokijuPwwVg9sDLKRxt0ZHXQwZVfQ=="],
|
||||
|
||||
"@autumn/vite/autumn-js": ["autumn-js@0.0.93", "", { "dependencies": { "chalk": "^5.4.1", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^3.24.1" }, "peerDependencies": { "better-auth": "^1.2.12", "better-call": "^1.0.12" }, "optionalPeers": ["better-auth", "better-call"] }, "sha512-zGq9cfkhHL6xAHlervcZHO2WQ28mHNoxKKI4vnkvKifW7ETDcQk0T7xdJS9g/a+BJh7CLqe697gEdMblMUDfUA=="],
|
||||
|
||||
"@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
|
||||
|
||||
"@autumn/vite/stripe": ["stripe@18.4.0", "", { "dependencies": { "qs": "^6.11.0" }, "peerDependencies": { "@types/node": ">=12.x.x" }, "optionalPeers": ["@types/node"] }, "sha512-LKFeDnDYo4U/YzNgx2Lc9PT9XgKN0JNF1iQwZxgkS4lOw5NunWCnzyH5RhTlD3clIZnf54h7nyMWkS8VXPmtTQ=="],
|
||||
@@ -2713,7 +2711,7 @@
|
||||
|
||||
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@better-auth/stripe/zod": ["zod@4.0.14", "", {}, "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw=="],
|
||||
"@better-auth/stripe/zod": ["zod@4.0.15", "", {}, "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ=="],
|
||||
|
||||
"@browserbasehq/sdk/@types/node": ["@types/node@18.19.121", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ=="],
|
||||
|
||||
@@ -3039,25 +3037,11 @@
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"@typescript-eslint/parser/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"@typescript-eslint/project-service/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"@typescript-eslint/type-utils/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"@typescript-eslint/utils/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"better-auth/zod": ["zod@4.0.14", "", {}, "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw=="],
|
||||
"better-auth/zod": ["zod@4.0.15", "", {}, "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ=="],
|
||||
|
||||
"better-call/rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="],
|
||||
|
||||
@@ -3179,8 +3163,6 @@
|
||||
|
||||
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"typescript-eslint/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"winston-transport/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"@supabase/supabase-js": "^2.46.2",
|
||||
"@upstash/redis": "^1.35.1",
|
||||
"ai": "^4.3.10",
|
||||
"autumn-js": "^0.0.77",
|
||||
"autumn-js": "^0.1.8",
|
||||
"axios": "^1.8.3",
|
||||
"better-auth": "^1.2.9",
|
||||
"body-parser": "^1.20.3",
|
||||
|
||||
@@ -8,7 +8,7 @@ filename=$1
|
||||
|
||||
|
||||
if [[ $filename == *"shell"* ]]; then
|
||||
$filename
|
||||
$filename "${@:2}"
|
||||
elif [[ $filename == *"/tests/"* ]]; then
|
||||
# Extract everything after "/tests/"
|
||||
path_after_tests=$(echo "$filename" | sed 's/.*\/tests\///')
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
# Source shared configuration
|
||||
source "$(dirname "$0")/config.sh"
|
||||
|
||||
# MOCHA_PARALLEL=true $MOCHA_SETUP \
|
||||
# If contains setup then run $MOCHA_SETUP
|
||||
if [[ "$2" == *"setup"* ]]; then
|
||||
MOCHA_PARALLEL=true $MOCHA_SETUP
|
||||
fi
|
||||
|
||||
$MOCHA_CMD \
|
||||
'tests/attach/basic/*.ts' \
|
||||
|
||||
@@ -15,4 +15,17 @@ $MOCHA_CMD 'tests/contUse/update/*.ts'
|
||||
|
||||
$MOCHA_CMD 'tests/contUse/track/*.ts'
|
||||
|
||||
$MOCHA_CMD 'tests/contUse/roles/*.ts'
|
||||
$MOCHA_CMD 'tests/contUse/roles/*.ts'
|
||||
|
||||
# G4
|
||||
$MOCHA_CMD 'tests/advanced/multiFeature/*.ts' \
|
||||
'tests/advanced/coupons/*.ts' \
|
||||
'tests/attach/updateQuantity/*.ts' \
|
||||
'tests/advanced/referrals/*.ts' \
|
||||
'tests/advanced/rollovers/*.ts' \
|
||||
'tests/advanced/customInterval/*.ts'
|
||||
|
||||
$MOCHA_CMD 'tests/attach/multiProduct/*.ts' \
|
||||
'tests/advanced/usageLimit/*.ts'
|
||||
|
||||
$MOCHA_CMD 'tests/advanced/usage/*.ts'
|
||||
@@ -12,10 +12,11 @@ $MOCHA_CMD 'tests/advanced/multiFeature/*.ts' \
|
||||
'tests/advanced/coupons/*.ts' \
|
||||
'tests/attach/updateQuantity/*.ts' \
|
||||
'tests/advanced/referrals/*.ts' \
|
||||
'tests/advanced/rollovers/*.ts'
|
||||
'tests/advanced/rollovers/*.ts' \
|
||||
'tests/advanced/customInterval/*.ts'
|
||||
|
||||
# $MOCHA_CMD 'tests/attach/multiProduct/*.ts' \
|
||||
# 'tests/advanced/usageLimit/*.ts'
|
||||
$MOCHA_CMD 'tests/attach/multiProduct/*.ts' \
|
||||
'tests/advanced/usageLimit/*.ts'
|
||||
|
||||
# $MOCHA_CMD 'tests/advanced/usage/*.ts'
|
||||
$MOCHA_CMD 'tests/advanced/usage/*.ts'
|
||||
|
||||
@@ -33,13 +33,14 @@ import { createSupabaseClient } from "@/external/supabaseUtils.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { getRelatedCusPrice } from "./internal/customers/cusProducts/cusEnts/cusEntUtils.js";
|
||||
import { CusProductService } from "./internal/customers/cusProducts/CusProductService.js";
|
||||
import { logSubItems } from "./utils/scriptUtils/logUtils/logSubItems.js";
|
||||
|
||||
const { db, client } = initDrizzle({ maxConnections: 5 });
|
||||
|
||||
let orgSlugs = process.env.ORG_SLUGS!.split(",");
|
||||
const skipEmails = process.env.SKIP_EMAILS!.split(",");
|
||||
orgSlugs = ["athenahq"];
|
||||
|
||||
orgSlugs = [];
|
||||
const getSingleCustomer = async ({
|
||||
stripeCli,
|
||||
customerId,
|
||||
@@ -136,7 +137,7 @@ const checkCustomerCorrect = async ({
|
||||
"number of stripe subs should be the same as number of subscription ids"
|
||||
);
|
||||
|
||||
const subItems = stripeSubs.flatMap((sub: any) => sub.items.data);
|
||||
let subItems = stripeSubs.flatMap((sub: any) => sub.items.data);
|
||||
|
||||
const prices = cusProductToPrices({ cusProduct });
|
||||
|
||||
@@ -174,7 +175,7 @@ const checkCustomerCorrect = async ({
|
||||
|
||||
let expectedQuantity = options?.upcoming_quantity || options?.quantity;
|
||||
|
||||
console.log("Sub item: ", subItem);
|
||||
// console.log("Sub item: ", subItem);
|
||||
assert(
|
||||
subItem?.quantity == expectedQuantity,
|
||||
`sub item quantity for prepaid price (featureId: ${featureId}) should be ${expectedQuantity}`
|
||||
@@ -282,7 +283,7 @@ export const check = async () => {
|
||||
|
||||
let customerId;
|
||||
|
||||
// customerId = "SwZ6cDgOFAY9S29nHigvPaUk4uq2";
|
||||
// customerId = "94fd7303-c8ae-4873-a6cd-0f5241aef232";
|
||||
|
||||
let customers: FullCustomer[] = [];
|
||||
let stripeSubs: Stripe.Subscription[] = [];
|
||||
|
||||
@@ -104,10 +104,11 @@ const handleShortDurationCusEnt = async ({
|
||||
|
||||
let resetCusEnt = {
|
||||
...cusEnt,
|
||||
next_reset_at: getNextResetAt(
|
||||
new UTCDate(cusEnt.next_reset_at!),
|
||||
ent.interval as EntInterval
|
||||
),
|
||||
next_reset_at: getNextResetAt({
|
||||
curReset: new UTCDate(cusEnt.next_reset_at!),
|
||||
interval: ent.interval as EntInterval,
|
||||
intervalCount: ent.interval_count,
|
||||
}),
|
||||
adjustment: 0,
|
||||
...getResetBalancesUpdate({
|
||||
cusEnt,
|
||||
@@ -116,9 +117,6 @@ const handleShortDurationCusEnt = async ({
|
||||
};
|
||||
let newCusEnt = resetCusEnt;
|
||||
|
||||
// let { customer, customer_product, entitlement, ...rest } = resetCusEnt;
|
||||
// let newCusEnt = rest as CustomerEntitlement;
|
||||
|
||||
let rolloverUpdate = getRolloverUpdates({
|
||||
cusEnt,
|
||||
nextResetAt: cusEnt.next_reset_at! as number,
|
||||
@@ -248,10 +246,11 @@ export const resetCustomerEntitlement = async ({
|
||||
|
||||
// 1. Check if should reset
|
||||
|
||||
let nextResetAt = getNextResetAt(
|
||||
new UTCDate(cusEnt.next_reset_at!),
|
||||
cusEnt.entitlement.interval as EntInterval
|
||||
);
|
||||
let nextResetAt = getNextResetAt({
|
||||
curReset: new UTCDate(cusEnt.next_reset_at!),
|
||||
interval: cusEnt.entitlement.interval as EntInterval,
|
||||
intervalCount: cusEnt.entitlement.interval_count,
|
||||
});
|
||||
|
||||
let rolloverUpdate = getRolloverUpdates({
|
||||
cusEnt,
|
||||
|
||||
@@ -8,9 +8,6 @@ import { schemas as schema } from "@autumn/shared";
|
||||
export let client = postgres(process.env.DATABASE_URL!);
|
||||
export let db = drizzle(client, { schema });
|
||||
|
||||
// export const localClient = postgres("postgresql://postgres:postgres@localhost:54322/postgres")
|
||||
// export const localDb = drizzle(localClient, { schema });
|
||||
|
||||
export const initDrizzle = (params?: { maxConnections?: number }) => {
|
||||
let maxConnections = params?.maxConnections || 10;
|
||||
const client = postgres(process.env.DATABASE_URL!, {
|
||||
@@ -19,7 +16,6 @@ export const initDrizzle = (params?: { maxConnections?: number }) => {
|
||||
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
// logger: process.env.NODE_ENV === "development",
|
||||
});
|
||||
|
||||
return { db, client };
|
||||
|
||||
@@ -1,4 +1,111 @@
|
||||
import pino from "pino";
|
||||
import { Writable } from "stream";
|
||||
|
||||
// Custom log formatter for Bun compatibility
|
||||
const createDevLogStream = () => {
|
||||
const colors = {
|
||||
reset: "\x1b[0m",
|
||||
bright: "\x1b[1m",
|
||||
dim: "\x1b[2m",
|
||||
red: "\x1b[31m",
|
||||
green: "\x1b[32m",
|
||||
yellow: "\x1b[33m",
|
||||
blue: "\x1b[34m",
|
||||
magenta: "\x1b[35m",
|
||||
cyan: "\x1b[36m",
|
||||
white: "\x1b[37m",
|
||||
gray: "\x1b[90m",
|
||||
bgRed: "\x1b[41m",
|
||||
};
|
||||
|
||||
const levelColors: Record<number | string, string> = {
|
||||
// Numeric levels
|
||||
10: colors.gray, // trace
|
||||
20: colors.blue, // debug
|
||||
30: colors.green, // info
|
||||
40: colors.yellow, // warn
|
||||
50: colors.red, // error
|
||||
60: colors.bgRed, // fatal
|
||||
// String levels
|
||||
TRACE: colors.gray,
|
||||
DEBUG: colors.blue,
|
||||
INFO: colors.green,
|
||||
WARN: colors.yellow,
|
||||
ERROR: colors.red,
|
||||
FATAL: colors.bgRed,
|
||||
};
|
||||
|
||||
const levelNames: Record<number | string, string> = {
|
||||
// Numeric levels
|
||||
10: "TRACE",
|
||||
20: "DEBUG",
|
||||
30: "INFO",
|
||||
40: "WARN",
|
||||
50: "ERROR",
|
||||
60: "FATAL",
|
||||
// String levels (pass through)
|
||||
TRACE: "TRACE",
|
||||
DEBUG: "DEBUG",
|
||||
INFO: "INFO",
|
||||
WARN: "WARN",
|
||||
ERROR: "ERROR",
|
||||
FATAL: "FATAL",
|
||||
};
|
||||
|
||||
return new Writable({
|
||||
write(chunk, encoding, callback) {
|
||||
try {
|
||||
const log = JSON.parse(chunk.toString());
|
||||
const timestamp = new Date(log.time)
|
||||
.toISOString()
|
||||
.replace("T", " ")
|
||||
.replace("Z", "");
|
||||
const level = log.level;
|
||||
const levelColor = levelColors[level] || colors.white;
|
||||
const levelName =
|
||||
levelNames[level] || (typeof level === "string" ? level : "UNKNOWN");
|
||||
|
||||
// Format the message
|
||||
let message = log.msg || "";
|
||||
|
||||
// Add any additional fields (excluding standard pino fields)
|
||||
const excludeFields = [
|
||||
"time",
|
||||
"level",
|
||||
"msg",
|
||||
"pid",
|
||||
"hostname",
|
||||
"res",
|
||||
"statusCode",
|
||||
"worker",
|
||||
"context",
|
||||
"req",
|
||||
"data",
|
||||
];
|
||||
const additionalFields = Object.keys(log)
|
||||
.filter((key) => !excludeFields.includes(key))
|
||||
.reduce((acc, key) => {
|
||||
acc[key] = log[key];
|
||||
return acc;
|
||||
}, {} as any);
|
||||
|
||||
if (Object.keys(additionalFields).length > 0) {
|
||||
message += " " + JSON.stringify(additionalFields, null, 2);
|
||||
}
|
||||
|
||||
// Format the final log line
|
||||
const formattedLog = `${colors.gray}${timestamp}${colors.reset} ${levelColor}${colors.bright}${levelName}${colors.reset} ${message}\n`;
|
||||
|
||||
process.stdout.write(formattedLog);
|
||||
callback();
|
||||
} catch (error) {
|
||||
// Fallback for malformed JSON
|
||||
process.stdout.write(chunk);
|
||||
callback();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const initLogger = () => {
|
||||
// Create separate streams for console and HyperDX
|
||||
@@ -7,26 +114,7 @@ export const initLogger = () => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
streams.push({
|
||||
level: process.env.NODE_ENV === "development" ? "debug" : "info",
|
||||
stream: pino.transport({
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: "UTC:yyyy-mm-dd HH:MM:ss",
|
||||
ignore: "pid,hostname,res,statusCode,worker,context,req",
|
||||
customColors: {
|
||||
default: "white",
|
||||
60: "bgRed",
|
||||
50: "red",
|
||||
40: "yellow",
|
||||
30: "green",
|
||||
20: "blue",
|
||||
10: "gray",
|
||||
message: "reset",
|
||||
greyMessage: "gray",
|
||||
time: "darkGray",
|
||||
},
|
||||
},
|
||||
}),
|
||||
stream: createDevLogStream(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,7 +143,7 @@ export const initLogger = () => {
|
||||
},
|
||||
},
|
||||
// Use multistream to send logs to multiple destinations
|
||||
pino.multistream(streams),
|
||||
pino.multistream(streams)
|
||||
);
|
||||
|
||||
return logger;
|
||||
|
||||
12
server/src/external/autumn/autumnCli.ts
vendored
12
server/src/external/autumn/autumnCli.ts
vendored
@@ -12,6 +12,8 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import {
|
||||
CancelParams,
|
||||
CheckoutParams,
|
||||
CheckoutResult,
|
||||
CheckParams,
|
||||
CheckResult,
|
||||
Customer,
|
||||
@@ -168,6 +170,16 @@ export class AutumnInt {
|
||||
|
||||
return data;
|
||||
}
|
||||
async checkout(params: CheckoutParams) {
|
||||
// const data = await this.post(`/attach`, {
|
||||
// customer_id: customerId,
|
||||
// product_id: productId,
|
||||
// options: toSnakeCase(options),
|
||||
// });
|
||||
const data = await this.post(`/checkout`, params);
|
||||
|
||||
return data as CheckoutResult;
|
||||
}
|
||||
|
||||
async sendEvent({
|
||||
customerId,
|
||||
|
||||
3
server/src/external/logtail/logtailUtils.ts
vendored
3
server/src/external/logtail/logtailUtils.ts
vendored
@@ -32,6 +32,7 @@ const createLogMethod = (pinoMethod: any, logtailMethod?: any) => {
|
||||
const strings = args
|
||||
.filter((arg) => typeof arg === "string")
|
||||
.map(rewriteAppPath);
|
||||
|
||||
const objects = args
|
||||
.filter((arg) => typeof arg !== "string" && arg !== null)
|
||||
.map((obj) => (obj instanceof Error ? rewriteErrorStack(obj) : obj));
|
||||
@@ -44,7 +45,7 @@ const createLogMethod = (pinoMethod: any, logtailMethod?: any) => {
|
||||
const errorObject = args.find((arg) => arg instanceof Error);
|
||||
if (errorObject) {
|
||||
message = rewriteAppPath(
|
||||
errorObject.stack || errorObject.message || "Error occurred",
|
||||
errorObject.stack || errorObject.message || "Error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
5
server/src/external/resend/resendUtils.ts
vendored
5
server/src/external/resend/resendUtils.ts
vendored
@@ -11,10 +11,11 @@ export interface ResendEmailProps {
|
||||
body: string;
|
||||
from: string;
|
||||
fromEmail?: string;
|
||||
replyTo?: string;
|
||||
}
|
||||
|
||||
export const nameToEmail = (name: string) => {
|
||||
return `${name.toLowerCase().replace(/\s+/g, ".")}@${process.env.RESEND_DOMAIN}`;
|
||||
return `${name.toLowerCase().replace(/\s+/g, ".")}@hey.${process.env.RESEND_DOMAIN}`;
|
||||
};
|
||||
|
||||
export const sendTextEmail = async ({
|
||||
@@ -71,6 +72,7 @@ export const sendHtmlEmail = async ({
|
||||
subject,
|
||||
body,
|
||||
fromEmail,
|
||||
replyTo,
|
||||
}: ResendEmailProps) => {
|
||||
const resend = createResendCli();
|
||||
|
||||
@@ -83,5 +85,6 @@ export const sendHtmlEmail = async ({
|
||||
to: to,
|
||||
subject: subject,
|
||||
html: body,
|
||||
replyTo,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -94,7 +94,10 @@ export const createStripeMeteredPrice = async ({
|
||||
currency: org.default_currency || "usd",
|
||||
nickname: `Autumn Price (${feature!.name}) [Placeholder]`,
|
||||
recurring: {
|
||||
...(billingIntervalToStripe(price.config!.interval!) as any),
|
||||
...(billingIntervalToStripe({
|
||||
interval: price.config!.interval,
|
||||
intervalCount: price.config!.interval_count,
|
||||
}) as any),
|
||||
meter: meter!.id,
|
||||
usage_type: "metered",
|
||||
},
|
||||
@@ -105,7 +108,7 @@ export const createStripeMeteredPrice = async ({
|
||||
|
||||
export const arrearProratedToStripeTiers = (
|
||||
price: Price,
|
||||
entitlement: EntitlementWithFeature,
|
||||
entitlement: EntitlementWithFeature
|
||||
) => {
|
||||
let usageConfig = structuredClone(price.config) as UsagePriceConfig;
|
||||
|
||||
@@ -161,7 +164,10 @@ export const createStripeArrearProrated = async ({
|
||||
|
||||
let recurringData = undefined;
|
||||
if (price.config!.interval != BillingInterval.OneOff) {
|
||||
recurringData = billingIntervalToStripe(price.config!.interval!);
|
||||
recurringData = billingIntervalToStripe({
|
||||
interval: price.config!.interval,
|
||||
intervalCount: price.config!.interval_count,
|
||||
});
|
||||
}
|
||||
|
||||
const config = price.config as UsagePriceConfig;
|
||||
|
||||
@@ -28,7 +28,10 @@ export const createStripeFixedPrice = async ({
|
||||
unit_amount: amount,
|
||||
currency: org.default_currency!,
|
||||
recurring: {
|
||||
...(billingIntervalToStripe(config.interval!) as any),
|
||||
...(billingIntervalToStripe({
|
||||
interval: config.interval,
|
||||
intervalCount: config.interval_count,
|
||||
}) as any),
|
||||
},
|
||||
|
||||
nickname: `Autumn Price (Fixed)`,
|
||||
|
||||
@@ -53,7 +53,7 @@ export const searchStripeMeter = async ({
|
||||
logger.info(`Stripe meter list took ${end - start}ms`);
|
||||
|
||||
let stripeMeter = allStripeMeters.find(
|
||||
(m) => m.event_name == eventName || m.id == meterId,
|
||||
(m) => m.event_name == eventName || m.id == meterId
|
||||
);
|
||||
|
||||
return stripeMeter;
|
||||
@@ -87,7 +87,7 @@ export const getStripeMeter = async ({
|
||||
createNew = true;
|
||||
} else {
|
||||
logger.info(
|
||||
`✅ Found existing meter for ${product.name} - ${feature!.name}`,
|
||||
`✅ Found existing meter for ${product.name} - ${feature!.name}`
|
||||
);
|
||||
return stripeMeter;
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export const getStripeMeter = async ({
|
||||
// IN ARREAR
|
||||
export const priceToInArrearTiers = (
|
||||
price: Price,
|
||||
entitlement: Entitlement,
|
||||
entitlement: Entitlement
|
||||
) => {
|
||||
let usageConfig = structuredClone(price.config) as UsagePriceConfig;
|
||||
const tiers: any[] = [];
|
||||
@@ -177,7 +177,7 @@ export const createStripeInArrearPrice = async ({
|
||||
if (internalEntityId && !useCheckout) {
|
||||
if (!curStripeProduct) {
|
||||
logger.info(
|
||||
`Creating stripe in arrear product for ${relatedEnt.feature.name} (internal entity ID exists!)`,
|
||||
`Creating stripe in arrear product for ${relatedEnt.feature.name} (internal entity ID exists!)`
|
||||
);
|
||||
let stripeProduct = await stripeCli.products.create({
|
||||
name: `${product.name} - ${feature!.name}`,
|
||||
@@ -199,7 +199,7 @@ export const createStripeInArrearPrice = async ({
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Creating stripe in arrear price for ${relatedEnt.feature.name} (no internal entity ID)`,
|
||||
`Creating stripe in arrear price for ${relatedEnt.feature.name} (no internal entity ID)`
|
||||
);
|
||||
|
||||
if (!feature) {
|
||||
@@ -223,7 +223,7 @@ export const createStripeInArrearPrice = async ({
|
||||
|
||||
const tiers = priceToInArrearTiers(
|
||||
price,
|
||||
getPriceEntitlement(price, entitlements),
|
||||
getPriceEntitlement(price, entitlements)
|
||||
);
|
||||
|
||||
let priceAmountData = {};
|
||||
@@ -257,7 +257,10 @@ export const createStripeInArrearPrice = async ({
|
||||
...priceAmountData,
|
||||
currency: org.default_currency!,
|
||||
recurring: {
|
||||
...(billingIntervalToStripe(price.config!.interval!) as any),
|
||||
...(billingIntervalToStripe({
|
||||
interval: price.config!.interval,
|
||||
intervalCount: price.config!.interval_count,
|
||||
}) as any),
|
||||
meter: meter!.id,
|
||||
usage_type: "metered",
|
||||
},
|
||||
|
||||
@@ -71,7 +71,10 @@ export const createStripePrepaid = async ({
|
||||
|
||||
let recurringData = undefined;
|
||||
if (price.config!.interval != BillingInterval.OneOff) {
|
||||
recurringData = billingIntervalToStripe(price.config!.interval!);
|
||||
recurringData = billingIntervalToStripe({
|
||||
interval: price.config!.interval,
|
||||
intervalCount: price.config!.interval_count,
|
||||
});
|
||||
}
|
||||
|
||||
const config = price.config as UsagePriceConfig;
|
||||
|
||||
@@ -10,9 +10,7 @@ import {
|
||||
Organization,
|
||||
UsagePriceConfig,
|
||||
BillingType,
|
||||
FeatureOptions,
|
||||
} from "@autumn/shared";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { createStripeFixedPrice } from "./createStripeFixedPrice.js";
|
||||
@@ -46,10 +44,7 @@ export const checkCurStripePrice = async ({
|
||||
expand: ["product"],
|
||||
});
|
||||
|
||||
if (
|
||||
!stripePrice.active ||
|
||||
!(stripePrice.product as Stripe.Product).active
|
||||
) {
|
||||
if (!stripePrice.active) {
|
||||
stripePrice = null;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -122,7 +117,12 @@ export const createStripePriceIFNotExist = async ({
|
||||
billingType == BillingType.OneOff
|
||||
) {
|
||||
if (!stripePrice) {
|
||||
logger.info("Creating stripe fixed price");
|
||||
// logger.info("Creating stripe fixed price: ", {
|
||||
// data: {
|
||||
// price,
|
||||
// stripePrice,
|
||||
// },
|
||||
// });
|
||||
await createStripeFixedPrice({
|
||||
db,
|
||||
stripeCli,
|
||||
|
||||
@@ -46,11 +46,11 @@ export const createStripeSubThroughInvoice = async ({
|
||||
|
||||
let subItems = items.filter(
|
||||
(i: any, index: number) =>
|
||||
prices[index].config!.interval !== BillingInterval.OneOff,
|
||||
prices[index].config!.interval !== BillingInterval.OneOff
|
||||
);
|
||||
let invoiceItems = items.filter(
|
||||
(i: any, index: number) =>
|
||||
prices[index].config!.interval === BillingInterval.OneOff,
|
||||
prices[index].config!.interval === BillingInterval.OneOff
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@@ -7,6 +7,7 @@ export const checkKeyValid = async (apiKey: string) => {
|
||||
|
||||
// Call customers.list
|
||||
const customers = await stripe.customers.list();
|
||||
|
||||
// const account = await stripe.accounts.retrieve();
|
||||
// console.log("Account", account);
|
||||
// return account;
|
||||
@@ -15,7 +16,7 @@ export const checkKeyValid = async (apiKey: string) => {
|
||||
export const createWebhookEndpoint = async (
|
||||
apiKey: string,
|
||||
env: AppEnv,
|
||||
orgId: string,
|
||||
orgId: string
|
||||
) => {
|
||||
const stripe = new Stripe(apiKey);
|
||||
|
||||
|
||||
29
server/src/external/stripe/stripePriceUtils.ts
vendored
29
server/src/external/stripe/stripePriceUtils.ts
vendored
@@ -23,27 +23,39 @@ export const createSubMeta = ({ features }: { features: Feature[] }) => {
|
||||
return { usage_features: JSON.stringify(usageFeatures) };
|
||||
};
|
||||
|
||||
export const billingIntervalToStripe = (interval: BillingInterval) => {
|
||||
export const billingIntervalToStripe = ({
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
interval: BillingInterval;
|
||||
intervalCount?: number | null;
|
||||
}) => {
|
||||
const finalCount = intervalCount ?? 1;
|
||||
switch (interval) {
|
||||
case BillingInterval.Week:
|
||||
return {
|
||||
interval: "week",
|
||||
interval_count: finalCount,
|
||||
};
|
||||
case BillingInterval.Month:
|
||||
return {
|
||||
interval: "month",
|
||||
interval_count: 1,
|
||||
interval_count: finalCount,
|
||||
};
|
||||
case BillingInterval.Quarter:
|
||||
return {
|
||||
interval: "month",
|
||||
interval_count: 3,
|
||||
interval_count: finalCount * 3,
|
||||
};
|
||||
case BillingInterval.SemiAnnual:
|
||||
return {
|
||||
interval: "month",
|
||||
interval_count: 6,
|
||||
interval_count: finalCount * 6,
|
||||
};
|
||||
case BillingInterval.Year:
|
||||
return {
|
||||
interval: "year",
|
||||
interval_count: 1,
|
||||
interval_count: finalCount,
|
||||
};
|
||||
default:
|
||||
break;
|
||||
@@ -104,10 +116,12 @@ export const getPlaceholderItem = ({
|
||||
product,
|
||||
org,
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
product: FullProduct;
|
||||
org: Organization;
|
||||
interval: BillingInterval;
|
||||
intervalCount: number;
|
||||
}) => {
|
||||
return {
|
||||
price_data: {
|
||||
@@ -115,7 +129,10 @@ export const getPlaceholderItem = ({
|
||||
unit_amount: 0,
|
||||
currency: org.default_currency || "usd",
|
||||
recurring: {
|
||||
...billingIntervalToStripe(interval as BillingInterval),
|
||||
...billingIntervalToStripe({
|
||||
interval,
|
||||
intervalCount,
|
||||
}),
|
||||
},
|
||||
},
|
||||
quantity: 0,
|
||||
|
||||
27
server/src/external/stripe/stripeSubUtils.ts
vendored
27
server/src/external/stripe/stripeSubUtils.ts
vendored
@@ -45,7 +45,7 @@ export const getStripeSubs = async ({
|
||||
} catch (error: any) {
|
||||
console.log(
|
||||
`(warning) getStripeSubs: Failed to get sub ${subId}`,
|
||||
error.message,
|
||||
error.message
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -133,7 +133,7 @@ export const getUsageBasedSub = async ({
|
||||
let autumnSub = autumnSubs?.find((sub) => sub.stripe_id == stripeSub.id);
|
||||
if (autumnSub) {
|
||||
let containsFeature = autumnSub.usage_features.includes(
|
||||
feature.internal_id!,
|
||||
feature.internal_id!
|
||||
);
|
||||
if (containsFeature) {
|
||||
return stripeSub;
|
||||
@@ -149,7 +149,7 @@ export const getUsageBasedSub = async ({
|
||||
if (
|
||||
!usageFeatures ||
|
||||
usageFeatures.find(
|
||||
(feat: any) => feat.internal_id == feature.internal_id,
|
||||
(feat: any) => feat.internal_id == feature.internal_id
|
||||
) === undefined
|
||||
) {
|
||||
continue;
|
||||
@@ -179,15 +179,14 @@ export const getSubItemsForCusProduct = async ({
|
||||
prices.some(
|
||||
(p) =>
|
||||
p.config?.stripe_price_id == item.price.id ||
|
||||
(p.config as UsagePriceConfig).stripe_product_id ==
|
||||
item.price.product,
|
||||
(p.config as UsagePriceConfig).stripe_product_id == item.price.product
|
||||
)
|
||||
) {
|
||||
subItems.push(item);
|
||||
}
|
||||
}
|
||||
let otherSubItems = stripeSub.items.data.filter(
|
||||
(item) => !subItems.some((i) => i.id == item.id),
|
||||
(item) => !subItems.some((i) => i.id == item.id)
|
||||
);
|
||||
|
||||
return { subItems, otherSubItems };
|
||||
@@ -216,12 +215,17 @@ export const getStripeSchedules = async ({
|
||||
}
|
||||
const prices = await Promise.all(batchPricesGet);
|
||||
const interval = prices[0].recurring?.interval;
|
||||
const billingInterval = stripeToAutumnInterval({
|
||||
interval: prices[0].recurring?.interval as string,
|
||||
intervalCount: prices[0].recurring?.interval_count || 1,
|
||||
});
|
||||
// const billingInterval = stripeToAutumnInterval({
|
||||
// interval: prices[0].recurring?.interval as string,
|
||||
// intervalCount: prices[0].recurring?.interval_count || 1,
|
||||
// });
|
||||
|
||||
return { schedule, interval: billingInterval, prices };
|
||||
return {
|
||||
schedule,
|
||||
interval: interval as BillingInterval,
|
||||
intervalCount: prices[0].recurring?.interval_count || 1,
|
||||
prices,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.log("Error getting stripe schedule.", error.message);
|
||||
return null;
|
||||
@@ -237,6 +241,7 @@ export const getStripeSchedules = async ({
|
||||
return schedulesAndSubs.filter((schedule) => schedule !== null) as {
|
||||
schedule: Stripe.SubscriptionSchedule;
|
||||
interval: BillingInterval;
|
||||
intervalCount: number;
|
||||
prices: Stripe.Price[];
|
||||
}[];
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ export const createStripeSub = async ({
|
||||
org,
|
||||
freeTrial,
|
||||
invoiceOnly = false,
|
||||
finalizeInvoice = false,
|
||||
anchorToUnix,
|
||||
itemSet,
|
||||
now,
|
||||
@@ -36,6 +37,7 @@ export const createStripeSub = async ({
|
||||
freeTrial: FreeTrial | null;
|
||||
org: Organization;
|
||||
invoiceOnly?: boolean;
|
||||
finalizeInvoice?: boolean;
|
||||
anchorToUnix?: number;
|
||||
itemSet: ItemSet;
|
||||
now?: number;
|
||||
@@ -58,6 +60,7 @@ export const createStripeSub = async ({
|
||||
? getAlignedIntervalUnix({
|
||||
alignWithUnix: anchorToUnix,
|
||||
interval: itemSet.interval,
|
||||
intervalCount: itemSet.intervalCount,
|
||||
now,
|
||||
})
|
||||
: undefined;
|
||||
@@ -91,6 +94,12 @@ export const createStripeSub = async ({
|
||||
coupon: reward ? reward.id : undefined,
|
||||
});
|
||||
|
||||
if (invoiceOnly && finalizeInvoice) {
|
||||
await stripeCli.invoices.finalizeInvoice(
|
||||
subscription.latest_invoice as string
|
||||
);
|
||||
}
|
||||
|
||||
// Store
|
||||
await SubService.createSub({
|
||||
db,
|
||||
|
||||
@@ -28,16 +28,21 @@ import {
|
||||
import { isUsagePrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import {
|
||||
intervalKeyToPrice,
|
||||
priceToIntervalKey,
|
||||
} from "@/internal/products/prices/priceUtils/convertPrice.js";
|
||||
|
||||
const getIntervalToPrices = (prices: Price[]) => {
|
||||
const intervalToPrices: Record<string, Price[]> = {};
|
||||
|
||||
for (const price of prices) {
|
||||
const interval = price.config.interval;
|
||||
if (!intervalToPrices[interval]) {
|
||||
intervalToPrices[interval] = [];
|
||||
// const interval = price.config.interval;
|
||||
const key = priceToIntervalKey(price);
|
||||
if (!intervalToPrices[key]) {
|
||||
intervalToPrices[key] = [];
|
||||
}
|
||||
intervalToPrices[interval].push(price);
|
||||
intervalToPrices[key].push(price);
|
||||
}
|
||||
|
||||
let oneOffPrices =
|
||||
@@ -93,8 +98,8 @@ export const getStripeSubItems = async ({
|
||||
|
||||
const intervalToPrices = getIntervalToPrices(prices);
|
||||
|
||||
for (const interval in intervalToPrices) {
|
||||
const prices = intervalToPrices[interval];
|
||||
for (const intervalKey in intervalToPrices) {
|
||||
const prices = intervalToPrices[intervalKey];
|
||||
|
||||
let subItems: any[] = [];
|
||||
|
||||
@@ -165,12 +170,14 @@ export const getStripeSubItems = async ({
|
||||
subItems.push(lineItem);
|
||||
}
|
||||
|
||||
const { interval, intervalCount } = intervalKeyToPrice(intervalKey);
|
||||
if (subItems.length == 0) {
|
||||
subItems.push(
|
||||
...getArrearItems({
|
||||
prices,
|
||||
org,
|
||||
interval: interval as BillingInterval,
|
||||
intervalCount,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -178,6 +185,7 @@ export const getStripeSubItems = async ({
|
||||
itemSets.push({
|
||||
items: subItems,
|
||||
interval,
|
||||
intervalCount,
|
||||
subMeta: {
|
||||
usage_features: JSON.stringify(usage_features),
|
||||
},
|
||||
@@ -186,7 +194,18 @@ export const getStripeSubItems = async ({
|
||||
});
|
||||
}
|
||||
|
||||
itemSets.sort((a, b) => compareBillingIntervals(a.interval, b.interval));
|
||||
itemSets.sort((a, b) =>
|
||||
compareBillingIntervals({
|
||||
configA: {
|
||||
interval: a.interval,
|
||||
intervalCount: a.intervalCount,
|
||||
},
|
||||
configB: {
|
||||
interval: b.interval,
|
||||
intervalCount: b.intervalCount,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return itemSets;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getBillingType } from "@/internal/products/prices/priceUtils.js";
|
||||
import {
|
||||
BillingInterval,
|
||||
BillingType,
|
||||
intervalsDifferent,
|
||||
Organization,
|
||||
UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
@@ -13,15 +14,25 @@ export const getArrearItems = ({
|
||||
prices,
|
||||
org,
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
prices: Price[];
|
||||
interval: BillingInterval;
|
||||
intervalCount: number;
|
||||
org: Organization;
|
||||
}) => {
|
||||
let placeholderItems: any[] = [];
|
||||
for (const price of prices) {
|
||||
let billingType = getBillingType(price.config!);
|
||||
if (price.config!.interval! != interval) {
|
||||
if (
|
||||
intervalsDifferent({
|
||||
intervalA: {
|
||||
interval: price.config!.interval!,
|
||||
intervalCount: price.config!.interval_count!,
|
||||
},
|
||||
intervalB: { interval, intervalCount },
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -33,7 +44,10 @@ export const getArrearItems = ({
|
||||
unit_amount: 1,
|
||||
currency: org.default_currency || "usd",
|
||||
recurring: {
|
||||
...billingIntervalToStripe(interval),
|
||||
...billingIntervalToStripe({
|
||||
interval,
|
||||
intervalCount,
|
||||
}),
|
||||
},
|
||||
},
|
||||
quantity: 0,
|
||||
|
||||
1
server/src/external/stripe/stripeWebhooks.ts
vendored
1
server/src/external/stripe/stripeWebhooks.ts
vendored
@@ -204,6 +204,7 @@ stripeWebhookRouter.post(
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
|
||||
case "subscription_schedule.canceled":
|
||||
const canceledSchedule = event.data.object;
|
||||
await handleSubscriptionScheduleCanceled({
|
||||
|
||||
75
server/src/external/stripe/utils.ts
vendored
75
server/src/external/stripe/utils.ts
vendored
@@ -77,44 +77,55 @@ export const calculateMetered1Price = ({
|
||||
export const subToAutumnInterval = (sub: Stripe.Subscription) => {
|
||||
let recuringItem = sub.items.data.find((i) => i.price.recurring != null);
|
||||
if (!recuringItem) {
|
||||
return BillingInterval.OneOff;
|
||||
return {
|
||||
interval: BillingInterval.OneOff,
|
||||
intervalCount: 1,
|
||||
};
|
||||
}
|
||||
|
||||
return stripeToAutumnInterval({
|
||||
interval: recuringItem.price.recurring!.interval,
|
||||
intervalCount: recuringItem.price.recurring!.interval_count,
|
||||
});
|
||||
return {
|
||||
interval: recuringItem.price.recurring!.interval as BillingInterval,
|
||||
intervalCount: recuringItem.price.recurring!.interval_count || 1,
|
||||
};
|
||||
// return stripeToAutumnInterval({
|
||||
// interval: recuringItem.price.recurring!.interval,
|
||||
// intervalCount: recuringItem.price.recurring!.interval_count,
|
||||
// });
|
||||
};
|
||||
|
||||
export const stripeToAutumnInterval = ({
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
interval: string;
|
||||
intervalCount: number;
|
||||
}) => {
|
||||
if (interval === "month" && intervalCount === 1) {
|
||||
return BillingInterval.Month;
|
||||
}
|
||||
// export const stripeToAutumnInterval = ({
|
||||
// interval,
|
||||
// intervalCount,
|
||||
// }: {
|
||||
// interval: string;
|
||||
// intervalCount: number;
|
||||
// }) => {
|
||||
// if (interval === "month" && intervalCount === 1) {
|
||||
// return BillingInterval.Month;
|
||||
// }
|
||||
|
||||
if (interval === "month" && intervalCount === 3) {
|
||||
return BillingInterval.Quarter;
|
||||
}
|
||||
// // if (interval === "month" && intervalCount === 3) {
|
||||
// // return BillingInterval.Quarter;
|
||||
// // }
|
||||
|
||||
if (interval === "month" && intervalCount === 6) {
|
||||
return BillingInterval.SemiAnnual;
|
||||
}
|
||||
// // if (interval === "month" && intervalCount === 6) {
|
||||
// // return BillingInterval.SemiAnnual;
|
||||
// // }
|
||||
|
||||
if (
|
||||
(interval === "month" && intervalCount === 12) ||
|
||||
(interval === "year" && intervalCount === 1)
|
||||
) {
|
||||
return BillingInterval.Year;
|
||||
}
|
||||
};
|
||||
// if (
|
||||
// (interval === "month" && intervalCount === 12) ||
|
||||
// (interval === "year" && intervalCount === 1)
|
||||
// ) {
|
||||
// return BillingInterval.Year;
|
||||
// }
|
||||
// };
|
||||
export const subItemToAutumnInterval = (item: Stripe.SubscriptionItem) => {
|
||||
return stripeToAutumnInterval({
|
||||
interval: item.price.recurring?.interval!,
|
||||
intervalCount: item.price.recurring?.interval_count!,
|
||||
});
|
||||
return {
|
||||
interval: item.price.recurring?.interval as BillingInterval,
|
||||
intervalCount: item.price.recurring?.interval_count || 1,
|
||||
};
|
||||
// return stripeToAutumnInterval({
|
||||
// interval: item.price.recurring?.interval!,
|
||||
// intervalCount: item.price.recurring?.interval_count!,
|
||||
// });
|
||||
};
|
||||
|
||||
@@ -97,8 +97,9 @@ export const handleCheckoutSub = async ({
|
||||
getArrearItems({
|
||||
prices: attachParams.prices,
|
||||
interval: attachParams.itemSets?.[0]?.interval,
|
||||
intervalCount: attachParams.itemSets?.[0]?.intervalCount,
|
||||
org,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const filterUsagePrices = ({
|
||||
prices: attachParams.prices,
|
||||
interval: itemSet.interval,
|
||||
org: attachParams.org,
|
||||
intervalCount: itemSet.intervalCount,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,17 +90,6 @@ export const handleRemainingSets = async ({
|
||||
|
||||
itemSet.items = filteredItems;
|
||||
|
||||
// itemSet.items =
|
||||
// filteredItems.length == 0
|
||||
// ? attachParams.products.map((p: FullProduct) =>
|
||||
// getPlaceholderItem({
|
||||
// product: p,
|
||||
// org,
|
||||
// interval: itemSet.interval,
|
||||
// }),
|
||||
// )
|
||||
// : filteredItems;
|
||||
|
||||
const subscription = (await createStripeSub({
|
||||
db,
|
||||
stripeCli,
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache.js";
|
||||
import { createSupabaseClient } from "../supabaseUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { safeSb } from "./safeSb.js";
|
||||
import { DrizzleCli, client } from "@/db/initDrizzle.js";
|
||||
|
||||
export const subscribeToOrgUpdates = safeSb({
|
||||
fn: ({ db }: { db: DrizzleCli }) => {
|
||||
try {
|
||||
const sb = createSupabaseClient();
|
||||
sb.channel("table-db-changes")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{
|
||||
event: "UPDATE",
|
||||
schema: "public",
|
||||
table: "organizations",
|
||||
},
|
||||
async (payload) => {
|
||||
try {
|
||||
await clearOrgCache({ db, orgId: payload.new.id });
|
||||
} catch (error) {
|
||||
console.warn("Error clearing org cache:", error);
|
||||
}
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
} catch (error) {
|
||||
console.warn("Error subscribing to org updates:", error);
|
||||
}
|
||||
},
|
||||
action: "subscribe to org updates",
|
||||
});
|
||||
export const subscribeToOrgUpdates = async ({ db }: { db: DrizzleCli }) => {
|
||||
try {
|
||||
await client.listen("org_updates", async (payload) => {
|
||||
try {
|
||||
const data = JSON.parse(payload);
|
||||
if (data.table === "organizations" && data.operation === "UPDATE") {
|
||||
await clearOrgCache({ db, orgId: data.new.id });
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Error processing org update notification:", error);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Successfully subscribed to organization updates via PostgreSQL LISTEN/NOTIFY");
|
||||
} catch (error) {
|
||||
console.warn("Error subscribing to org updates:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ import { ClickHouseManager } from "./external/clickhouse/ClickHouseManager.js";
|
||||
const tracer = trace.getTracer("express");
|
||||
|
||||
checkEnvVars();
|
||||
// subscribeToOrgUpdates({ db });
|
||||
|
||||
const init = async () => {
|
||||
const app = express();
|
||||
@@ -45,8 +46,6 @@ const init = async () => {
|
||||
"https://staging.useautumn.com",
|
||||
"https://*.useautumn.com",
|
||||
"https://localhost:8080",
|
||||
"https://app.aidvize.com",
|
||||
"http://staging.aidvize.com",
|
||||
"https://www.alphalog.ai",
|
||||
"https://*.alphalog.ai",
|
||||
process.env.CLIENT_URL || "",
|
||||
@@ -84,8 +83,6 @@ const init = async () => {
|
||||
await CacheManager.getInstance();
|
||||
await ClickHouseManager.getInstance();
|
||||
|
||||
subscribeToOrgUpdates({ db });
|
||||
|
||||
app.use(async (req: any, res: any, next: any) => {
|
||||
req.env = req.env = req.headers["app_env"] || AppEnv.Sandbox;
|
||||
req.db = db;
|
||||
@@ -180,7 +177,7 @@ if (process.env.NODE_ENV === "development") {
|
||||
console.log(`Master ${process.pid} is running`);
|
||||
console.log("Number of CPUs", numCPUs);
|
||||
|
||||
let numWorkers = 10;
|
||||
let numWorkers = 7;
|
||||
|
||||
for (let i = 0; i < numWorkers; i++) {
|
||||
cluster.fork();
|
||||
|
||||
@@ -72,6 +72,14 @@ export const getEntityResponse = async ({
|
||||
skipCache,
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
throw new RecaseError({
|
||||
message: `Customer ${customerId} not found`,
|
||||
code: ErrCode.CustomerNotFound,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
let entities = customer.entities.filter((e: Entity) =>
|
||||
entityIds.includes(e.id)
|
||||
);
|
||||
|
||||
@@ -118,6 +118,7 @@ export const getOptions = ({
|
||||
anchorToUnix,
|
||||
proration,
|
||||
interval: (i.interval || BillingInterval.OneOff) as BillingInterval,
|
||||
intervalCount: i.interval_count || 1,
|
||||
now,
|
||||
});
|
||||
|
||||
@@ -139,7 +140,7 @@ export const getOptions = ({
|
||||
}
|
||||
|
||||
const currentOptions = cusProduct?.options.find(
|
||||
(o) => o.feature_id == i.feature_id,
|
||||
(o) => o.feature_id == i.feature_id
|
||||
);
|
||||
|
||||
let currentQuantity = currentOptions?.quantity;
|
||||
|
||||
@@ -21,6 +21,7 @@ import { getEventTimestamp } from "./eventUtils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { runUpdateUsageTask } from "@/trigger/updateUsageTask.js";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { isPaidContinuousUse } from "@/internal/features/featureUtils.js";
|
||||
|
||||
export const eventsRouter: Router = Router();
|
||||
export const usageRouter: Router = Router();
|
||||
@@ -201,9 +202,8 @@ export const handleUsageEvent = async ({
|
||||
entityId: entity_id,
|
||||
};
|
||||
|
||||
const featureUsageType = feature.config?.usage_type;
|
||||
// console.log(`Feature Usage Type: ${featureUsageType}`);
|
||||
if (featureUsageType === FeatureUsageType.Continuous) {
|
||||
if (isPaidContinuousUse({ feature, fullCus: customer })) {
|
||||
console.log(`Running update usage task synchronously`);
|
||||
await runUpdateUsageTask({
|
||||
payload,
|
||||
logger: console,
|
||||
|
||||
@@ -61,7 +61,12 @@ export const handleCreateCheckout = async ({
|
||||
});
|
||||
|
||||
let billingCycleAnchorUnixSeconds = org.config.anchor_start_of_month
|
||||
? Math.floor(getNextStartOfMonthUnix(itemSets[0].interval) / 1000)
|
||||
? Math.floor(
|
||||
getNextStartOfMonthUnix({
|
||||
interval: itemSets[0].interval,
|
||||
intervalCount: itemSets[0].intervalCount,
|
||||
}) / 1000
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (attachParams.billingAnchor) {
|
||||
|
||||
@@ -12,7 +12,10 @@ import { priceToProduct } from "@/internal/products/prices/priceUtils/findPriceU
|
||||
import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js";
|
||||
import { AttachConfig } from "@autumn/shared";
|
||||
import { attachToInsertParams } from "@/internal/products/productUtils.js";
|
||||
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
|
||||
import {
|
||||
attachToInvoiceResponse,
|
||||
insertInvoiceFromAttach,
|
||||
} from "@/internal/invoices/invoiceUtils.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { isFixedPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
||||
|
||||
@@ -160,7 +163,7 @@ export const handleOneOffFunction = async ({
|
||||
attachParams: attachToInsertParams(attachParams, product),
|
||||
lastInvoiceId: stripeInvoice.id,
|
||||
logger,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
await Promise.all(batchInsert);
|
||||
@@ -180,12 +183,14 @@ export const handleOneOffFunction = async ({
|
||||
AttachResultSchema.parse({
|
||||
success: true,
|
||||
message: `Successfully purchased ${productNames} and attached to ${customerName}`,
|
||||
invoice: invoiceOnly ? stripeInvoice : undefined,
|
||||
invoice: invoiceOnly
|
||||
? attachToInvoiceResponse({ invoice: stripeInvoice })
|
||||
: undefined,
|
||||
code: SuccessCode.OneOffProductAttached,
|
||||
product_ids: products.map((p) => p.id),
|
||||
customer_id: customer.id || customer.internal_id,
|
||||
scenario: AttachScenario.New,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
AttachParams,
|
||||
AttachResultSchema,
|
||||
} from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
|
||||
import {
|
||||
attachToInvoiceResponse,
|
||||
insertInvoiceFromAttach,
|
||||
} from "@/internal/invoices/invoiceUtils.js";
|
||||
import { getNextStartOfMonthUnix } from "@/internal/products/prices/billingIntervalUtils.js";
|
||||
import { attachToInsertParams } from "@/internal/products/productUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
@@ -20,6 +23,7 @@ import {
|
||||
AttachScenario,
|
||||
BillingInterval,
|
||||
ErrCode,
|
||||
intervalsDifferent,
|
||||
SuccessCode,
|
||||
} from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
@@ -78,15 +82,28 @@ export const handlePaidProduct = async ({
|
||||
continue;
|
||||
}
|
||||
|
||||
let mergeWithSub = mergeSubs.find(
|
||||
(sub) => subToAutumnInterval(sub) == itemSet.interval
|
||||
);
|
||||
let mergeWithSub = mergeSubs.find((sub) => {
|
||||
let subInterval = subToAutumnInterval(sub);
|
||||
return !intervalsDifferent({
|
||||
intervalA: {
|
||||
interval: subInterval.interval,
|
||||
intervalCount: subInterval.intervalCount,
|
||||
},
|
||||
intervalB: {
|
||||
interval: itemSet.interval,
|
||||
intervalCount: itemSet.intervalCount,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
let subscription;
|
||||
try {
|
||||
let billingCycleAnchorUnix;
|
||||
if (org.config.anchor_start_of_month) {
|
||||
billingCycleAnchorUnix = getNextStartOfMonthUnix(itemSet.interval);
|
||||
billingCycleAnchorUnix = getNextStartOfMonthUnix({
|
||||
interval: itemSet.interval,
|
||||
intervalCount: itemSet.intervalCount,
|
||||
});
|
||||
}
|
||||
|
||||
if (attachParams.billingAnchor) {
|
||||
@@ -105,6 +122,7 @@ export const handlePaidProduct = async ({
|
||||
freeTrial,
|
||||
invoiceOnly,
|
||||
itemSet,
|
||||
finalizeInvoice: attachParams.finalizeInvoice,
|
||||
anchorToUnix: billingCycleAnchorUnix,
|
||||
reward: i == 0 ? reward : undefined,
|
||||
now: attachParams.now,
|
||||
@@ -179,7 +197,9 @@ export const handlePaidProduct = async ({
|
||||
code: SuccessCode.NewProductAttached,
|
||||
product_ids: products.map((p) => p.id),
|
||||
customer_id: customer.id || customer.internal_id,
|
||||
invoice: invoiceOnly ? invoices?.[0] : undefined,
|
||||
invoice: invoiceOnly
|
||||
? attachToInvoiceResponse({ invoice: invoices?.[0] })
|
||||
: undefined,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ export const handleScheduleFunction = async ({
|
||||
}) => {
|
||||
const logger = req.logtail;
|
||||
const product = attachParams.products[0];
|
||||
const { stripeCli } = attachParams;
|
||||
const { stripeCli, customer: fullCus } = attachParams;
|
||||
|
||||
const { curMainProduct, curScheduledProduct } = attachParamToCusProducts({
|
||||
attachParams,
|
||||
@@ -55,7 +55,9 @@ export const handleScheduleFunction = async ({
|
||||
// 3. Get schedules for current cus products
|
||||
logger.info(`3. Getting schedules for current cus products`);
|
||||
let schedules = await cusProductsToSchedules({
|
||||
cusProducts: [curMainProduct, curScheduledProduct],
|
||||
// cusProducts: [curMainProduct, curScheduledProduct],
|
||||
// cusProducts: [curMainProduct, curScheduledProduct],
|
||||
cusProducts: fullCus.customer_products,
|
||||
stripeCli,
|
||||
});
|
||||
|
||||
@@ -69,7 +71,9 @@ export const handleScheduleFunction = async ({
|
||||
const stripeSchedules: Stripe.SubscriptionSchedule[] = [];
|
||||
for (const itemSet of itemSets) {
|
||||
let scheduleObj = schedules.find(
|
||||
(schedule) => schedule.interval === itemSet.interval,
|
||||
(schedule) =>
|
||||
schedule.interval === itemSet.interval &&
|
||||
schedule.intervalCount === itemSet.intervalCount
|
||||
);
|
||||
|
||||
// If schedule exists, update it
|
||||
@@ -110,7 +114,7 @@ export const handleScheduleFunction = async ({
|
||||
cusProductId: curCusProduct.id,
|
||||
updates: {
|
||||
scheduled_ids: curCusProduct.scheduled_ids?.filter(
|
||||
(id) => !newScheduledIds.includes(id),
|
||||
(id) => !newScheduledIds.includes(id)
|
||||
),
|
||||
},
|
||||
});
|
||||
@@ -147,7 +151,7 @@ export const handleScheduleFunction = async ({
|
||||
product_ids: [product.id],
|
||||
customer_id:
|
||||
attachParams.customer.id || attachParams.customer.internal_id,
|
||||
}),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
res.status(200).json({
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
UsagePriceConfig,
|
||||
BillingType,
|
||||
BillingInterval,
|
||||
intervalsDifferent,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import Stripe from "stripe";
|
||||
@@ -28,6 +29,7 @@ export const getUsageInvoiceItems = async ({
|
||||
cusProduct,
|
||||
stripeSubs,
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
logger: any;
|
||||
@@ -35,6 +37,7 @@ export const getUsageInvoiceItems = async ({
|
||||
cusProduct: FullCusProduct;
|
||||
stripeSubs: Stripe.Subscription[];
|
||||
interval?: BillingInterval;
|
||||
intervalCount?: number;
|
||||
}) => {
|
||||
const { stripeCli, org } = attachParams;
|
||||
|
||||
@@ -72,7 +75,14 @@ export const getUsageInvoiceItems = async ({
|
||||
});
|
||||
|
||||
if (!sub) continue;
|
||||
if (interval && interval !== subToAutumnInterval(sub)) continue;
|
||||
if (
|
||||
interval &&
|
||||
intervalsDifferent({
|
||||
intervalA: { interval, intervalCount },
|
||||
intervalB: subToAutumnInterval(sub),
|
||||
})
|
||||
)
|
||||
continue;
|
||||
|
||||
cusEntIds.push(cusEnt.id);
|
||||
|
||||
@@ -106,6 +116,7 @@ export const createUsageInvoiceItems = async ({
|
||||
invoiceId,
|
||||
logger,
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
attachParams: AttachParams;
|
||||
@@ -114,6 +125,7 @@ export const createUsageInvoiceItems = async ({
|
||||
invoiceId?: string;
|
||||
logger: any;
|
||||
interval?: BillingInterval;
|
||||
intervalCount?: number;
|
||||
}) => {
|
||||
const { stripeCli } = attachParams;
|
||||
|
||||
@@ -123,6 +135,7 @@ export const createUsageInvoiceItems = async ({
|
||||
cusProduct,
|
||||
stripeSubs,
|
||||
interval,
|
||||
intervalCount,
|
||||
logger,
|
||||
});
|
||||
|
||||
@@ -131,7 +144,7 @@ export const createUsageInvoiceItems = async ({
|
||||
const invoiceItem = invoiceItems[i];
|
||||
const createInvoiceItem = async () => {
|
||||
logger.info(
|
||||
`🌟 Creating usage invoice item: ${invoiceItem.description}, amount: ${invoiceItem.price_data.unit_amount}`,
|
||||
`🌟 Creating usage invoice item: ${invoiceItem.description}, amount: ${invoiceItem.price_data.unit_amount}`
|
||||
);
|
||||
|
||||
await stripeCli.invoiceItems.create({
|
||||
@@ -171,7 +184,7 @@ export const resetUsageBalances = async ({
|
||||
});
|
||||
|
||||
let index = cusProduct.customer_entitlements.findIndex(
|
||||
(ce) => ce.id === cusEntId,
|
||||
(ce) => ce.id === cusEntId
|
||||
);
|
||||
|
||||
cusProduct.customer_entitlements[index] = {
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
|
||||
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
|
||||
import { attachToInsertParams } from "@/internal/products/productUtils.js";
|
||||
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
|
||||
import {
|
||||
attachToInvoiceResponse,
|
||||
insertInvoiceFromAttach,
|
||||
} from "@/internal/invoices/invoiceUtils.js";
|
||||
import { updateSubsDiffInt } from "./updateSubsDiffInt.js";
|
||||
|
||||
export const handleUpgradeDiffInterval = async ({
|
||||
@@ -69,7 +72,7 @@ export const handleUpgradeDiffInterval = async ({
|
||||
cusProductId: curCusProduct.id,
|
||||
updates: {
|
||||
subscription_ids: curCusProduct.subscription_ids!.filter(
|
||||
(subId) => subId !== newSubs[0].id,
|
||||
(subId) => subId !== newSubs[0].id
|
||||
),
|
||||
processor: {
|
||||
type: ProcessorType.Stripe,
|
||||
@@ -106,7 +109,7 @@ export const handleUpgradeDiffInterval = async ({
|
||||
attachParams,
|
||||
invoiceId,
|
||||
logger,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,8 +127,10 @@ export const handleUpgradeDiffInterval = async ({
|
||||
product_ids: products.map((p) => p.id),
|
||||
code: SuccessCode.UpgradedToNewProduct,
|
||||
message: `Successfully upgraded from ${curProductName} to ${product.name}`,
|
||||
invoice: config.invoiceOnly ? invoice : undefined,
|
||||
}),
|
||||
invoice: config.invoiceOnly
|
||||
? attachToInvoiceResponse({ invoice: invoice || undefined })
|
||||
: undefined,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
res.status(200).json({
|
||||
|
||||
@@ -4,7 +4,11 @@ import { updateScheduledSubWithNewItems } from "@/internal/customers/change-prod
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { ItemSet } from "@/utils/models/ItemSet.js";
|
||||
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { FullCusProduct } from "@autumn/shared";
|
||||
import {
|
||||
FullCusProduct,
|
||||
intervalsDifferent,
|
||||
intervalsSame,
|
||||
} from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { attachParamToCusProducts } from "../../attachUtils/convertAttachParams.js";
|
||||
|
||||
@@ -39,7 +43,7 @@ export const updateCurSchedules = async ({
|
||||
});
|
||||
|
||||
for (const scheduleObj of schedules) {
|
||||
const { interval, schedule } = scheduleObj;
|
||||
const { interval, intervalCount, schedule } = scheduleObj;
|
||||
|
||||
// If schedule has passed, skip this step.
|
||||
let phase = schedule.phases.length > 0 ? schedule.phases[0] : null;
|
||||
@@ -51,7 +55,16 @@ export const updateCurSchedules = async ({
|
||||
}
|
||||
|
||||
// Get corresponding item set
|
||||
const itemSet = itemSets.find((itemSet) => itemSet.interval === interval);
|
||||
|
||||
const itemSet = itemSets.find((itemSet) =>
|
||||
intervalsSame({
|
||||
intervalA: { interval, intervalCount },
|
||||
intervalB: {
|
||||
interval: itemSet.interval,
|
||||
intervalCount: itemSet.intervalCount,
|
||||
},
|
||||
})
|
||||
);
|
||||
if (!itemSet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { updateSubsByInt } from "./updateSubsSameInt.js";
|
||||
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { formatUnixToDate } from "@/utils/genUtils.js";
|
||||
import { attachToInvoiceResponse } from "@/internal/invoices/invoiceUtils.js";
|
||||
|
||||
export const handleUpgradeSameInterval = async ({
|
||||
req,
|
||||
@@ -40,7 +41,7 @@ export const handleUpgradeSameInterval = async ({
|
||||
}
|
||||
|
||||
logger.info(`1. Updating subs by interval`);
|
||||
const { replaceables } = await updateSubsByInt({
|
||||
const { replaceables, invoices } = await updateSubsByInt({
|
||||
req,
|
||||
curCusProduct: curCusProduct!,
|
||||
attachParams,
|
||||
@@ -81,9 +82,15 @@ export const handleUpgradeSameInterval = async ({
|
||||
AttachResultSchema.parse({
|
||||
customer_id: attachParams.customer.id,
|
||||
product_ids: attachParams.products.map((p) => p.id),
|
||||
// invoice: attachParams.invoiceOnly
|
||||
// ? attachToInvoiceResponse({ invoice: invoices?.[0] })
|
||||
// : undefined,
|
||||
invoice: attachParams.invoiceOnly
|
||||
? attachToInvoiceResponse({ invoice: invoices?.[0] || undefined })
|
||||
: undefined,
|
||||
code: "updated_product_successfully",
|
||||
message: `Successfully updated product`,
|
||||
}),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
res.status(200).json({
|
||||
|
||||
@@ -3,7 +3,12 @@ import { getStripeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSub
|
||||
import { subToAutumnInterval } from "@/external/stripe/utils.js";
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AttachConfig, FullCusProduct, Replaceable } from "@autumn/shared";
|
||||
import {
|
||||
AttachConfig,
|
||||
FullCusProduct,
|
||||
intervalsSame,
|
||||
Replaceable,
|
||||
} from "@autumn/shared";
|
||||
import { addSubItemsToRemove } from "../attachFuncUtils.js";
|
||||
import { updateStripeSub } from "../../attachUtils/updateStripeSub/updateStripeSub.js";
|
||||
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
|
||||
@@ -51,9 +56,16 @@ export const updateSubsByInt = async ({
|
||||
|
||||
// const replaceables: Replaceable[] = [];
|
||||
for (const sub of stripeSubs) {
|
||||
let interval = subToAutumnInterval(sub);
|
||||
// let interval = subToAutumnInterval(sub);
|
||||
|
||||
let subInterval = subToAutumnInterval(sub);
|
||||
let itemSet = itemSets.find((itemSet) => {
|
||||
return intervalsSame({
|
||||
intervalA: itemSet,
|
||||
intervalB: subInterval,
|
||||
});
|
||||
})!;
|
||||
|
||||
let itemSet = itemSets.find((itemSet) => itemSet.interval === interval)!;
|
||||
await addSubItemsToRemove({
|
||||
sub,
|
||||
cusProduct: curCusProduct,
|
||||
@@ -67,14 +79,17 @@ export const updateSubsByInt = async ({
|
||||
stripeSubs: [sub],
|
||||
itemSet,
|
||||
logger,
|
||||
interval,
|
||||
interval: itemSet.interval,
|
||||
intervalCount: itemSet.intervalCount,
|
||||
});
|
||||
|
||||
if (latestInvoice) {
|
||||
invoices.push(latestInvoice);
|
||||
}
|
||||
|
||||
logger.info(`Updated sub ${sub.id}, interval ${interval}`);
|
||||
logger.info(
|
||||
`Updated sub ${sub.id}, interval ${itemSet.interval}, intervalCount ${itemSet.intervalCount}`
|
||||
);
|
||||
}
|
||||
|
||||
const batchInvUpdate = [];
|
||||
@@ -85,9 +100,9 @@ export const updateSubsByInt = async ({
|
||||
attachParams,
|
||||
stripeInvoice: invoice,
|
||||
logger,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return { replaceables };
|
||||
return { replaceables, invoices };
|
||||
};
|
||||
|
||||
@@ -66,7 +66,8 @@ export const getAttachParams = async ({
|
||||
// Others
|
||||
apiVersion,
|
||||
successUrl: attachBody.success_url,
|
||||
invoiceOnly: attachBody.invoice_only,
|
||||
invoiceOnly: attachBody.invoice_only || attachBody.invoice,
|
||||
finalizeInvoice: attachBody.invoice,
|
||||
billingAnchor: attachBody.billing_cycle_anchor,
|
||||
metadata: attachBody.metadata,
|
||||
disableFreeTrial: attachBody.free_trial === false || false,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { getFirstInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import {
|
||||
getLargestInterval,
|
||||
intervalsDifferent,
|
||||
} from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import { subToAutumnInterval } from "@/external/stripe/utils.js";
|
||||
import Stripe from "stripe";
|
||||
import { attachParamsToProduct } from "./convertAttachParams.js";
|
||||
@@ -12,7 +15,10 @@ export const getCycleWillReset = ({
|
||||
stripeSubs: Stripe.Subscription[];
|
||||
}) => {
|
||||
const product = attachParamsToProduct({ attachParams });
|
||||
const firstInterval = getFirstInterval({ prices: product.prices });
|
||||
const firstInterval = getLargestInterval({ prices: product.prices });
|
||||
const prevInterval = subToAutumnInterval(stripeSubs[0]);
|
||||
return prevInterval !== firstInterval;
|
||||
return intervalsDifferent({
|
||||
intervalA: firstInterval,
|
||||
intervalB: prevInterval,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -273,6 +273,7 @@ const getChangeProductBranch = async ({
|
||||
// }
|
||||
|
||||
let isUpgrade = isProductUpgrade({ prices1: curPrices, prices2: newPrices });
|
||||
|
||||
if (isUpgrade) {
|
||||
if (isTrialing(curMainProduct!)) {
|
||||
return AttachBranch.MainIsTrial;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import { AttachFlags } from "../models/AttachFlags.js";
|
||||
import { AttachConfig, AttachBranch } from "@autumn/shared";
|
||||
import {
|
||||
AttachConfig,
|
||||
AttachBranch,
|
||||
intervalsSame,
|
||||
intervalToValue,
|
||||
} from "@autumn/shared";
|
||||
import { AttachBody } from "@autumn/shared";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
@@ -27,12 +32,66 @@ export const intervalsAreSame = ({
|
||||
let newProduct = attachParamsToProduct({ attachParams });
|
||||
let curPrices = cusProductToPrices({ cusProduct: curCusProduct! });
|
||||
|
||||
let curIntervals = new Set(curPrices.map((p) => p.config.interval));
|
||||
let newIntervals = new Set(newProduct.prices.map((p) => p.config.interval));
|
||||
const curIntervals = new Set(
|
||||
curPrices.map((p) =>
|
||||
intervalToValue(p.config.interval, p.config.interval_count)
|
||||
)
|
||||
);
|
||||
|
||||
const newIntervals = new Set(
|
||||
newProduct.prices.map((p) =>
|
||||
intervalToValue(p.config.interval, p.config.interval_count)
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
curIntervals.size === newIntervals.size &&
|
||||
[...curIntervals].every((interval) => newIntervals.has(interval))
|
||||
);
|
||||
|
||||
for (const price of curPrices) {
|
||||
let hasSimilarInterval = newProduct.prices.some((p) => {
|
||||
return intervalsSame({
|
||||
intervalA: price.config,
|
||||
intervalB: p.config,
|
||||
});
|
||||
});
|
||||
|
||||
if (!hasSimilarInterval) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const price of newProduct.prices) {
|
||||
let hasSimilarInterval = curPrices.some((p) => {
|
||||
return intervalsSame({
|
||||
intervalA: price.config,
|
||||
intervalB: p.config,
|
||||
});
|
||||
});
|
||||
|
||||
if (!hasSimilarInterval) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
// let curIntervals = new Set(
|
||||
// curPrices.map((p) => ({
|
||||
// interval: p.config.interval,
|
||||
// intervalCount: p.config.interval_count,
|
||||
// }))
|
||||
// );
|
||||
// let newIntervals = new Set(
|
||||
// newProduct.prices.map((p) => ({
|
||||
// interval: p.config.interval,
|
||||
// intervalCount: p.config.interval_count,
|
||||
// }))
|
||||
// );
|
||||
// return (
|
||||
// curIntervals.size === newIntervals.size &&
|
||||
// [...curIntervals].every((interval) => newIntervals.has(interval))
|
||||
// );
|
||||
};
|
||||
|
||||
export const getAttachConfig = async ({
|
||||
@@ -51,7 +110,7 @@ export const getAttachConfig = async ({
|
||||
let flags: AttachFlags = {
|
||||
isPublic: req.isPublic,
|
||||
forceCheckout: attachBody.force_checkout || false,
|
||||
invoiceOnly: attachBody.invoice_only || false,
|
||||
invoiceOnly: attachParams.invoiceOnly || false,
|
||||
isFree: isFreeProduct(prices),
|
||||
noPaymentMethod: nullish(paymentMethod) ? true : false,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
BillingType,
|
||||
FullCusProduct,
|
||||
FullProduct,
|
||||
intervalsDifferent,
|
||||
intervalsSame,
|
||||
} from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { getContUseInvoiceItems } from "./getContUseInvoiceItems.js";
|
||||
@@ -35,7 +37,7 @@ export const filterContUsageProrations = async ({
|
||||
subscription: sub.id,
|
||||
});
|
||||
|
||||
const interval = subToAutumnInterval(sub);
|
||||
const intervalSet = subToAutumnInterval(sub);
|
||||
|
||||
for (const item of upcomingLines.data) {
|
||||
if (!item.proration) continue;
|
||||
@@ -48,12 +50,12 @@ export const filterContUsageProrations = async ({
|
||||
|
||||
if (!price) continue;
|
||||
logger.info(
|
||||
`Deleting ii: ${item.description} - ${item.amount / 100} (${interval})`,
|
||||
`Deleting ii: ${item.description} - ${item.amount / 100} (${intervalSet.interval}, ${intervalSet.intervalCount})`
|
||||
);
|
||||
|
||||
await stripeCli.invoiceItems.del(
|
||||
// @ts-ignore -- Stripe types are not correct
|
||||
item.parent.subscription_item_details.invoice_item,
|
||||
item.parent.subscription_item_details.invoice_item
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -64,12 +66,14 @@ export const createAndFilterContUseItems = async ({
|
||||
stripeSubs,
|
||||
logger,
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
attachParams: AttachParams;
|
||||
curMainProduct: FullCusProduct;
|
||||
stripeSubs: Stripe.Subscription[];
|
||||
logger: any;
|
||||
interval?: BillingInterval;
|
||||
intervalCount?: number;
|
||||
}) => {
|
||||
const { stripeCli, customer, org } = attachParams;
|
||||
const product = attachParamsToProduct({ attachParams });
|
||||
@@ -88,8 +92,13 @@ export const createAndFilterContUseItems = async ({
|
||||
});
|
||||
|
||||
let sub =
|
||||
stripeSubs.find((sub) => subToAutumnInterval(sub) == interval) ||
|
||||
stripeSubs[0];
|
||||
stripeSubs.find((sub) => {
|
||||
return intervalsSame({
|
||||
intervalA: { interval: interval!, intervalCount: intervalCount! },
|
||||
intervalB: subToAutumnInterval(sub),
|
||||
});
|
||||
// subToAutumnInterval(sub) == interval
|
||||
}) || stripeSubs[0];
|
||||
|
||||
await filterContUsageProrations({
|
||||
sub,
|
||||
@@ -113,12 +122,20 @@ export const createAndFilterContUseItems = async ({
|
||||
product.prices.find((p) => p.id === item.price_id) ||
|
||||
curPrices.find((p) => p.id === item.price_id);
|
||||
|
||||
if (interval && price?.config.interval !== interval) {
|
||||
if (
|
||||
interval &&
|
||||
price?.config &&
|
||||
intervalsDifferent({
|
||||
// price?.config.interval !== interval
|
||||
intervalA: price?.config,
|
||||
intervalB: { interval, intervalCount },
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Adding invoice item: ${item.description}, ${item.description}, interval: ${interval}`,
|
||||
`Adding invoice item: ${item.description}, ${item.description}, interval: ${interval}`
|
||||
);
|
||||
|
||||
await stripeCli.invoiceItems.create({
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
FullCustomerEntitlement,
|
||||
FullEntitlement,
|
||||
getFeatureInvoiceDescription,
|
||||
intervalsSame,
|
||||
PreviewLineItem,
|
||||
Price,
|
||||
} from "@autumn/shared";
|
||||
@@ -121,7 +122,7 @@ export const getContUseInvoiceItems = async ({
|
||||
const cusEnts = cusProduct ? cusProduct.customer_entitlements : [];
|
||||
|
||||
const product = attachParamsToProduct({ attachParams });
|
||||
const intervalsSame = intervalsAreSame({ attachParams });
|
||||
const allIntervalsSame = intervalsAreSame({ attachParams });
|
||||
const curItems = stripeSubs
|
||||
? await getCurContUseItems({
|
||||
stripeSubs,
|
||||
@@ -150,7 +151,7 @@ export const getContUseInvoiceItems = async ({
|
||||
? getRelatedCusPrice(prevCusEnt, cusPrices)!
|
||||
: undefined;
|
||||
|
||||
if (!intervalsSame || !prevCusEnt || !stripeSubs) {
|
||||
if (!allIntervalsSame || !prevCusEnt || !stripeSubs) {
|
||||
const newItem = await getContUseNewItems({
|
||||
price,
|
||||
ent,
|
||||
@@ -159,7 +160,7 @@ export const getContUseInvoiceItems = async ({
|
||||
});
|
||||
|
||||
const prevItem = curItems.find(
|
||||
(item) => item.price_id === prevCusPrice?.price.id,
|
||||
(item) => item.price_id === prevCusPrice?.price.id
|
||||
);
|
||||
|
||||
newItems.push(newItem);
|
||||
@@ -172,12 +173,16 @@ export const getContUseInvoiceItems = async ({
|
||||
}
|
||||
|
||||
const curItem = curItems.find(
|
||||
(item) => item.price_id === prevCusPrice?.price.id,
|
||||
(item) => item.price_id === prevCusPrice?.price.id
|
||||
);
|
||||
|
||||
let sub = stripeSubs!.find(
|
||||
(sub) => subToAutumnInterval(sub) === price.config.interval,
|
||||
);
|
||||
let sub = stripeSubs!.find((sub) => {
|
||||
let subInterval = subToAutumnInterval(sub);
|
||||
return intervalsSame({
|
||||
intervalA: price.config,
|
||||
intervalB: subInterval,
|
||||
});
|
||||
});
|
||||
|
||||
let {
|
||||
oldItem,
|
||||
|
||||
@@ -213,7 +213,6 @@ export const handleAttachErrors = async ({
|
||||
AttachBranch.NewVersion,
|
||||
AttachBranch.SameCustom,
|
||||
AttachBranch.UpdatePrepaidQuantity,
|
||||
AttachBranch.Renew,
|
||||
];
|
||||
if (updateProductFlows.includes(branch)) {
|
||||
handleNonCheckoutErrors({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Stripe from "stripe";
|
||||
import { BillingInterval, AttachConfig } from "@autumn/shared";
|
||||
import { BillingInterval, AttachConfig, intervalsSame } from "@autumn/shared";
|
||||
import { ProrationBehavior } from "@autumn/shared";
|
||||
import { SubService } from "@/internal/subscriptions/SubService.js";
|
||||
import { ItemSet } from "@/utils/models/ItemSet.js";
|
||||
@@ -23,7 +23,7 @@ export const getSubAndInvoiceItems = async ({
|
||||
|
||||
let subItems = items.filter(
|
||||
(i: any, index: number) =>
|
||||
i.deleted || prices[index].config!.interval !== BillingInterval.OneOff,
|
||||
i.deleted || prices[index].config!.interval !== BillingInterval.OneOff
|
||||
);
|
||||
|
||||
let addInvoiceItems = items.filter((i: any, index: number) => {
|
||||
@@ -48,6 +48,7 @@ export const updateStripeSub = async ({
|
||||
itemSet,
|
||||
logger,
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
attachParams: AttachParams;
|
||||
@@ -58,6 +59,7 @@ export const updateStripeSub = async ({
|
||||
shouldPreview?: boolean;
|
||||
logger: any;
|
||||
interval?: BillingInterval;
|
||||
intervalCount?: number;
|
||||
}) => {
|
||||
const { curMainProduct } = attachParamToCusProducts({ attachParams });
|
||||
const { stripeCli, customer, org, paymentMethod } = attachParams;
|
||||
@@ -69,7 +71,13 @@ export const updateStripeSub = async ({
|
||||
|
||||
const curSub =
|
||||
(interval
|
||||
? stripeSubs.find((s) => subToAutumnInterval(s) === interval)
|
||||
? stripeSubs.find((s) => {
|
||||
let subInterval = subToAutumnInterval(s);
|
||||
return intervalsSame({
|
||||
intervalA: { interval, intervalCount },
|
||||
intervalB: subInterval,
|
||||
});
|
||||
})
|
||||
: stripeSubs[0]) || stripeSubs[0];
|
||||
|
||||
// 1. Update subscription
|
||||
@@ -102,6 +110,16 @@ export const updateStripeSub = async ({
|
||||
};
|
||||
}
|
||||
|
||||
// if (invoiceOnly && attachParams.finalizeInvoice) {
|
||||
// try {
|
||||
// await stripeCli.invoices.finalizeInvoice(latestInvoice?.id as string);
|
||||
// } catch (error) {
|
||||
// logger.error(`Failed to finalize invoice ${latestInvoice?.id}`, {
|
||||
// error,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// 2. Create prorations for single use items
|
||||
let { invoiceItems, cusEntIds } = await createUsageInvoiceItems({
|
||||
db,
|
||||
@@ -110,6 +128,7 @@ export const updateStripeSub = async ({
|
||||
stripeSubs,
|
||||
logger,
|
||||
interval: config.sameIntervals ? interval : undefined,
|
||||
intervalCount: config.sameIntervals ? intervalCount : undefined,
|
||||
});
|
||||
|
||||
// 3. Create prorations for continuous use items
|
||||
@@ -118,6 +137,7 @@ export const updateStripeSub = async ({
|
||||
curMainProduct: curMainProduct!,
|
||||
stripeSubs,
|
||||
interval,
|
||||
intervalCount,
|
||||
logger,
|
||||
});
|
||||
|
||||
@@ -145,6 +165,19 @@ export const updateStripeSub = async ({
|
||||
env: customer.env,
|
||||
});
|
||||
|
||||
if (invoiceOnly && attachParams.finalizeInvoice) {
|
||||
logger.info(`FINALIZING INVOICE ${latestInvoice?.id}`);
|
||||
try {
|
||||
latestInvoice = await stripeCli.invoices.finalizeInvoice(
|
||||
latestInvoice?.id as string
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to finalize invoice ${latestInvoice?.id}`, {
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updatedSub,
|
||||
latestInvoice: latestInvoice,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
AttachBranch,
|
||||
AttachFunction,
|
||||
AttachScenario,
|
||||
CheckoutResponseSchema,
|
||||
@@ -108,17 +107,18 @@ export const handleCheckout = (req: any, res: any) =>
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
const { logger, features } = req;
|
||||
const attachBody = AttachBodySchema.parse(req.body);
|
||||
// Pre-populate options...
|
||||
|
||||
const { attachParams, flags, branch, config, func } = await getAttachVars(
|
||||
{ req, attachBody }
|
||||
);
|
||||
const { attachParams, branch, func } = await getAttachVars({
|
||||
req,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
await getCheckoutOptions({
|
||||
req,
|
||||
attachParams,
|
||||
});
|
||||
|
||||
let checkoutUrl = null;
|
||||
if (func == AttachFunction.CreateCheckout) {
|
||||
await checkStripeConnections({
|
||||
req,
|
||||
@@ -134,22 +134,24 @@ export const handleCheckout = (req: any, res: any) =>
|
||||
returnCheckout: true,
|
||||
});
|
||||
|
||||
const customer = attachParams.customer;
|
||||
res.status(200).json(
|
||||
CheckoutResponseSchema.parse({
|
||||
url: checkout?.url,
|
||||
customer_id: customer.id || customer.internal_id,
|
||||
scenario: AttachScenario.New,
|
||||
lines: [],
|
||||
product: await getProductResponse({
|
||||
product: attachParams.products[0],
|
||||
features: features,
|
||||
withDisplay: false,
|
||||
options: attachParams.optionsList,
|
||||
}),
|
||||
})
|
||||
);
|
||||
return;
|
||||
checkoutUrl = checkout?.url;
|
||||
|
||||
// const customer = attachParams.customer;
|
||||
// res.status(200).json(
|
||||
// CheckoutResponseSchema.parse({
|
||||
// url: checkout?.url,
|
||||
// customer_id: customer.id || customer.internal_id,
|
||||
// scenario: AttachScenario.New,
|
||||
// lines: [],
|
||||
// product: await getProductResponse({
|
||||
// product: attachParams.products[0],
|
||||
// features: features,
|
||||
// withDisplay: false,
|
||||
// options: attachParams.optionsList,
|
||||
// }),
|
||||
// })
|
||||
// );
|
||||
// return;
|
||||
}
|
||||
|
||||
const preview = await attachParamsToPreview({
|
||||
@@ -175,6 +177,7 @@ export const handleCheckout = (req: any, res: any) =>
|
||||
|
||||
res.status(200).json({
|
||||
...checkoutRes,
|
||||
url: checkoutUrl,
|
||||
options: attachParams.optionsList.map((o) => ({
|
||||
quantity: o.quantity,
|
||||
feature_id: o.feature_id,
|
||||
|
||||
@@ -108,10 +108,6 @@ export const previewToCheckoutRes = async ({
|
||||
return acc;
|
||||
}
|
||||
|
||||
// if (item.interval !== newProduct.properties?.interval_group) {
|
||||
// return acc;
|
||||
// }
|
||||
|
||||
if (isPriceItem(item)) {
|
||||
return acc.plus(item.price || 0);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getAttachParams } from "./attachUtils/attachParams/getAttachParams.js";
|
||||
import { getAttachBranch } from "./attachUtils/getAttachBranch.js";
|
||||
import { getAttachConfig } from "./attachUtils/getAttachConfig.js";
|
||||
import { handleAttachErrors } from "./attachUtils/handleAttachErrors.js";
|
||||
import { checkStripeConnections, createStripePrices } from "./attachRouter.js";
|
||||
import { checkStripeConnections } from "./attachRouter.js";
|
||||
import { insertCustomItems } from "./attachUtils/insertCustomItems.js";
|
||||
import { runAttachFunction } from "./attachUtils/getAttachFunction.js";
|
||||
|
||||
@@ -59,6 +59,28 @@ export const handleAttach = async (req: any, res: any) =>
|
||||
customEnts: customEnts || [],
|
||||
});
|
||||
|
||||
try {
|
||||
req.logger.info(`Attach params: `, {
|
||||
data: {
|
||||
products: attachParams.products.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
processor: p.processor,
|
||||
version: p.version,
|
||||
})),
|
||||
prices: attachParams.prices.map((p) => ({
|
||||
id: p.id,
|
||||
config: p.config,
|
||||
})),
|
||||
entitlements: attachParams.entitlements.map((e) => ({
|
||||
internal_feature_id: e.internal_feature_id,
|
||||
feature_id: e.feature_id,
|
||||
})),
|
||||
freeTrial: attachParams.freeTrial,
|
||||
},
|
||||
});
|
||||
} catch (error) {}
|
||||
|
||||
await runAttachFunction({
|
||||
req,
|
||||
res,
|
||||
|
||||
@@ -10,15 +10,10 @@ import {
|
||||
getNextStartOfMonthUnix,
|
||||
} from "@/internal/products/prices/billingIntervalUtils.js";
|
||||
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||
import { getLastInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { getSmallestInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
import { getMergeCusProduct } from "../attachFunctions/addProductFlow/getMergeCusProduct.js";
|
||||
import {
|
||||
formatUnixToDate,
|
||||
formatUnixToDateTime,
|
||||
notNullish,
|
||||
nullish,
|
||||
} from "@/utils/genUtils.js";
|
||||
import { notNullish, nullish } from "@/utils/genUtils.js";
|
||||
|
||||
export const getNewProductPreview = async ({
|
||||
branch,
|
||||
@@ -38,7 +33,10 @@ export const getNewProductPreview = async ({
|
||||
|
||||
let anchorToUnix = undefined;
|
||||
if (org.config.anchor_start_of_month) {
|
||||
anchorToUnix = getNextStartOfMonthUnix(BillingInterval.Month);
|
||||
anchorToUnix = getNextStartOfMonthUnix({
|
||||
interval: BillingInterval.Month,
|
||||
intervalCount: 1,
|
||||
});
|
||||
}
|
||||
|
||||
const { mergeCusProduct, mergeSubs } = await getMergeCusProduct({
|
||||
@@ -77,12 +75,16 @@ export const getNewProductPreview = async ({
|
||||
config,
|
||||
});
|
||||
|
||||
let minInterval = getLastInterval({
|
||||
// let minInterval = getLastInterval({
|
||||
// prices: newProduct.prices,
|
||||
// ents: newProduct.entitlements,
|
||||
// });
|
||||
let min = getSmallestInterval({
|
||||
prices: newProduct.prices,
|
||||
ents: newProduct.entitlements,
|
||||
});
|
||||
|
||||
let getAligned = notNullish(anchorToUnix) && notNullish(minInterval);
|
||||
let getAligned = notNullish(anchorToUnix) && notNullish(min);
|
||||
|
||||
let dueAt = freeTrial
|
||||
? freeTrialToStripeTimestamp({
|
||||
@@ -92,11 +94,16 @@ export const getNewProductPreview = async ({
|
||||
: getAligned
|
||||
? getAlignedIntervalUnix({
|
||||
alignWithUnix: anchorToUnix!,
|
||||
interval: minInterval,
|
||||
interval: min!.interval,
|
||||
intervalCount: min!.intervalCount,
|
||||
now: attachParams.now,
|
||||
})
|
||||
: notNullish(minInterval)
|
||||
? addBillingIntervalUnix(attachParams.now || Date.now(), minInterval)
|
||||
: notNullish(min)
|
||||
? addBillingIntervalUnix({
|
||||
unixTimestamp: attachParams.now || Date.now(),
|
||||
interval: min!.interval,
|
||||
intervalCount: min!.intervalCount,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
dueNextCycle = !nullish(dueAt)
|
||||
@@ -125,18 +132,25 @@ export const getNewProductPreview = async ({
|
||||
// Next cycle at
|
||||
if (!dueNextCycle) {
|
||||
if (!isFreeProduct(newProduct.prices) && branch != AttachBranch.OneOff) {
|
||||
let minInterval = getLastInterval({ prices: newProduct.prices });
|
||||
let min = getSmallestInterval({
|
||||
prices: newProduct.prices,
|
||||
ents: newProduct.entitlements,
|
||||
});
|
||||
dueNextCycle = {
|
||||
line_items: items.filter((item) => {
|
||||
let price = newProduct.prices.find(
|
||||
(price) => price.id == item.price_id
|
||||
);
|
||||
return price?.config.interval == minInterval;
|
||||
return (
|
||||
price?.config.interval == min!.interval &&
|
||||
(price?.config.interval_count || 1) == (min!.intervalCount || 1)
|
||||
);
|
||||
}),
|
||||
due_at: addBillingIntervalUnix({
|
||||
unixTimestamp: attachParams.now || Date.now(),
|
||||
interval: min!.interval,
|
||||
intervalCount: min!.intervalCount,
|
||||
}),
|
||||
due_at: addBillingIntervalUnix(
|
||||
attachParams.now || Date.now(),
|
||||
minInterval
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { getOptions } from "@/internal/api/entitled/checkUtils.js";
|
||||
import { getItemsForCurProduct } from "@/internal/invoices/previewItemUtils/getItemsForCurProduct.js";
|
||||
import { getItemsForNewProduct } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js";
|
||||
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||
import {
|
||||
addBillingIntervalUnix,
|
||||
getAlignedIntervalUnix,
|
||||
} from "@/internal/products/prices/billingIntervalUtils.js";
|
||||
import { getLastInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
import { mapToProductItems } from "@/internal/products/productV2Utils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import {
|
||||
AttachBranch,
|
||||
BillingInterval,
|
||||
FreeTrial,
|
||||
PreviewLineItem,
|
||||
Price,
|
||||
UsageModel,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import Stripe from "stripe";
|
||||
import {
|
||||
attachParamToCusProducts,
|
||||
attachParamsToProduct,
|
||||
} from "../attachUtils/convertAttachParams.js";
|
||||
import { intervalsAreSame } from "../attachUtils/getAttachConfig.js";
|
||||
import { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
|
||||
const getNextCycleAt = ({
|
||||
prices,
|
||||
stripeSubs,
|
||||
willCycleReset,
|
||||
interval,
|
||||
now,
|
||||
freeTrial,
|
||||
}: {
|
||||
prices: Price[];
|
||||
stripeSubs: Stripe.Subscription[];
|
||||
willCycleReset: boolean;
|
||||
interval: BillingInterval;
|
||||
now?: number;
|
||||
freeTrial?: FreeTrial | null;
|
||||
}) => {
|
||||
now = now || Date.now();
|
||||
|
||||
if (freeTrial) {
|
||||
return {
|
||||
next_cycle_at:
|
||||
freeTrialToStripeTimestamp({
|
||||
freeTrial,
|
||||
now,
|
||||
})! * 1000,
|
||||
};
|
||||
}
|
||||
|
||||
if (willCycleReset) {
|
||||
const minInterval = getLastInterval({ prices });
|
||||
return {
|
||||
next_cycle_at: addBillingIntervalUnix(now, minInterval),
|
||||
};
|
||||
}
|
||||
|
||||
const minInterval = getLastInterval({ prices });
|
||||
const nextCycleAt = getAlignedIntervalUnix({
|
||||
alignWithUnix: stripeSubs[0].current_period_end * 1000,
|
||||
interval: minInterval,
|
||||
alwaysReturn: true,
|
||||
});
|
||||
|
||||
return {
|
||||
next_cycle_at: nextCycleAt,
|
||||
};
|
||||
};
|
||||
|
||||
export const getUpdateQuantityPreview = async ({
|
||||
req,
|
||||
attachParams,
|
||||
branch,
|
||||
now,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
attachParams: AttachParams;
|
||||
branch: AttachBranch;
|
||||
now: number;
|
||||
}) => {
|
||||
const { logtail: logger } = req;
|
||||
|
||||
const { stripeCli } = attachParams;
|
||||
|
||||
const { curMainProduct, curSameProduct } = attachParamToCusProducts({
|
||||
attachParams,
|
||||
});
|
||||
const curCusProduct = curMainProduct!;
|
||||
|
||||
const stripeSubs = await getStripeSubs({
|
||||
stripeCli,
|
||||
subIds: curCusProduct?.subscription_ids || [],
|
||||
expand: ["items.data.price.tiers"],
|
||||
});
|
||||
|
||||
const curPreviewItems = await getItemsForCurProduct({
|
||||
stripeSubs,
|
||||
attachParams,
|
||||
now,
|
||||
logger,
|
||||
});
|
||||
|
||||
// Get prorated amounts for new product
|
||||
const newProduct = attachParamsToProduct({ attachParams });
|
||||
const intervalsSame = intervalsAreSame({ attachParams });
|
||||
const anchorToUnix =
|
||||
intervalsSame && stripeSubs.length > 0
|
||||
? stripeSubs[0].current_period_end * 1000
|
||||
: undefined;
|
||||
|
||||
const newPreviewItems = await getItemsForNewProduct({
|
||||
newProduct,
|
||||
attachParams,
|
||||
now,
|
||||
anchorToUnix,
|
||||
freeTrial: attachParams.freeTrial,
|
||||
stripeSubs,
|
||||
logger,
|
||||
});
|
||||
|
||||
const lastInterval = getLastInterval({ prices: newProduct.prices });
|
||||
|
||||
let dueNextCycle = undefined;
|
||||
if (!isFreeProduct(newProduct.prices)) {
|
||||
const nextCycleAt = getNextCycleAt({
|
||||
prices: newProduct.prices,
|
||||
stripeSubs,
|
||||
willCycleReset: !intervalsSame,
|
||||
interval: lastInterval,
|
||||
now,
|
||||
freeTrial: attachParams.freeTrial,
|
||||
});
|
||||
|
||||
let nextCycleItems = await getItemsForNewProduct({
|
||||
newProduct,
|
||||
attachParams,
|
||||
interval: attachParams.freeTrial ? undefined : lastInterval,
|
||||
logger,
|
||||
});
|
||||
|
||||
dueNextCycle = {
|
||||
line_items: nextCycleItems,
|
||||
due_at: nextCycleAt.next_cycle_at,
|
||||
};
|
||||
}
|
||||
|
||||
let items = [...curPreviewItems, ...newPreviewItems];
|
||||
|
||||
for (const item of structuredClone(curPreviewItems)) {
|
||||
let priceId = item.price_id;
|
||||
let newItem = newPreviewItems.find((i) => i.price_id == priceId);
|
||||
|
||||
if (!newItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newItemAmount = new Decimal(newItem?.amount ?? 0).toDecimalPlaces(2);
|
||||
let curItemAmount = new Decimal(item.amount ?? 0).toDecimalPlaces(2);
|
||||
|
||||
if (newItemAmount.add(curItemAmount).eq(0)) {
|
||||
items = items.filter((i) => i.price_id !== priceId);
|
||||
}
|
||||
}
|
||||
|
||||
const dueTodayAmt = items
|
||||
.reduce((acc, item) => acc.plus(item.amount ?? 0), new Decimal(0))
|
||||
.toDecimalPlaces(2)
|
||||
.toNumber();
|
||||
|
||||
let options = getOptions({
|
||||
prodItems: mapToProductItems({
|
||||
prices: newProduct.prices,
|
||||
entitlements: newProduct.entitlements,
|
||||
features: attachParams.features,
|
||||
}),
|
||||
features: attachParams.features,
|
||||
anchorToUnix,
|
||||
now,
|
||||
freeTrial: attachParams.freeTrial,
|
||||
cusProduct: curSameProduct,
|
||||
});
|
||||
|
||||
items = items.filter((item) => item.amount !== 0);
|
||||
|
||||
if (branch == AttachBranch.UpdatePrepaidQuantity) {
|
||||
items = items.filter((item) => item.usage_model == UsageModel.Prepaid);
|
||||
dueNextCycle!.line_items = dueNextCycle!.line_items.filter(
|
||||
(item) => item.usage_model == UsageModel.Prepaid,
|
||||
);
|
||||
}
|
||||
|
||||
let dueToday:
|
||||
| {
|
||||
line_items: PreviewLineItem[];
|
||||
total: number;
|
||||
}
|
||||
| undefined = {
|
||||
line_items: items,
|
||||
total: dueTodayAmt,
|
||||
};
|
||||
|
||||
if (branch == AttachBranch.SameCustomEnts) {
|
||||
dueToday = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
currency: attachParams.org.default_currency,
|
||||
due_today: dueToday,
|
||||
due_next_cycle: dueNextCycle,
|
||||
options,
|
||||
};
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from "../attachUtils/convertAttachParams.js";
|
||||
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getFirstInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import { getLargestInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import { getItemsForNewProduct } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js";
|
||||
import { getItemsForCurProduct } from "@/internal/invoices/previewItemUtils/getItemsForCurProduct.js";
|
||||
import { getOptions } from "@/internal/api/entitled/checkUtils.js";
|
||||
@@ -29,13 +29,12 @@ import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/free
|
||||
import { Decimal } from "decimal.js";
|
||||
import { intervalsAreSame } from "../attachUtils/getAttachConfig.js";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
import { formatUnixToDateTime, notNullish } from "@/utils/genUtils.js";
|
||||
import { formatUnixToDateTime, notNullish, nullish } from "@/utils/genUtils.js";
|
||||
|
||||
const getNextCycleAt = ({
|
||||
prices,
|
||||
stripeSubs,
|
||||
willCycleReset,
|
||||
interval,
|
||||
now,
|
||||
freeTrial,
|
||||
branch,
|
||||
@@ -44,7 +43,6 @@ const getNextCycleAt = ({
|
||||
prices: Price[];
|
||||
stripeSubs: Stripe.Subscription[];
|
||||
willCycleReset: boolean;
|
||||
interval: BillingInterval;
|
||||
now?: number;
|
||||
freeTrial?: FreeTrial | null;
|
||||
branch: AttachBranch;
|
||||
@@ -68,17 +66,28 @@ const getNextCycleAt = ({
|
||||
};
|
||||
}
|
||||
|
||||
if (willCycleReset) {
|
||||
const firstInterval = getFirstInterval({ prices });
|
||||
const firstInterval = getLargestInterval({ prices });
|
||||
|
||||
if (nullish(firstInterval)) {
|
||||
return {
|
||||
next_cycle_at: addBillingIntervalUnix(now, firstInterval),
|
||||
next_cycle_at: now,
|
||||
};
|
||||
}
|
||||
|
||||
if (willCycleReset) {
|
||||
return {
|
||||
next_cycle_at: addBillingIntervalUnix({
|
||||
unixTimestamp: now,
|
||||
interval: firstInterval!.interval,
|
||||
intervalCount: firstInterval!.intervalCount,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const firstInterval = getFirstInterval({ prices });
|
||||
const nextCycleAt = getAlignedIntervalUnix({
|
||||
alignWithUnix: stripeSubs[0].current_period_end * 1000,
|
||||
interval: firstInterval,
|
||||
interval: firstInterval!.interval,
|
||||
intervalCount: firstInterval!.intervalCount,
|
||||
alwaysReturn: true,
|
||||
now,
|
||||
});
|
||||
@@ -154,7 +163,7 @@ export const getUpgradeProductPreview = async ({
|
||||
config,
|
||||
});
|
||||
|
||||
const lastInterval = getFirstInterval({ prices: newProduct.prices });
|
||||
const largestInterval = getLargestInterval({ prices: newProduct.prices });
|
||||
|
||||
let dueNextCycle = undefined;
|
||||
if (!isFreeProduct(newProduct.prices)) {
|
||||
@@ -162,7 +171,6 @@ export const getUpgradeProductPreview = async ({
|
||||
prices: newProduct.prices,
|
||||
stripeSubs,
|
||||
willCycleReset: !intervalsSame,
|
||||
interval: lastInterval,
|
||||
now,
|
||||
freeTrial: attachParams.freeTrial,
|
||||
branch,
|
||||
@@ -172,7 +180,10 @@ export const getUpgradeProductPreview = async ({
|
||||
let nextCycleItems = await getItemsForNewProduct({
|
||||
newProduct,
|
||||
attachParams,
|
||||
interval: attachParams.freeTrial ? undefined : lastInterval,
|
||||
interval: attachParams.freeTrial ? undefined : largestInterval?.interval,
|
||||
intervalCount: attachParams.freeTrial
|
||||
? undefined
|
||||
: largestInterval?.intervalCount,
|
||||
logger,
|
||||
withPrepaid,
|
||||
branch,
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
import { Stripe } from "stripe";
|
||||
import { AttachParams } from "../cusProducts/AttachParams.js";
|
||||
import { FullCusProduct, InvoiceItem } from "@autumn/shared";
|
||||
import { BillingInterval, BillingType, UsagePriceConfig } from "@autumn/shared";
|
||||
|
||||
import {
|
||||
getBillingType,
|
||||
getPriceEntitlement,
|
||||
getPriceForOverage,
|
||||
} from "@/internal/products/prices/priceUtils.js";
|
||||
import {
|
||||
createStripeCli,
|
||||
subToAutumnInterval,
|
||||
} from "@/external/stripe/utils.js";
|
||||
import { CusEntService } from "../cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import { payForInvoice } from "@/external/stripe/stripeInvoiceUtils.js";
|
||||
import { getResetBalancesUpdate } from "../cusProducts/cusEnts/groupByUtils.js";
|
||||
import {
|
||||
getCusPriceUsage,
|
||||
getRelatedCusEnt,
|
||||
} from "../cusProducts/cusPrices/cusPriceUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
|
||||
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import {
|
||||
cusProductsToCusPrices,
|
||||
cusProductToEnts,
|
||||
} from "../cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { getUsageBasedSub } from "@/external/stripe/stripeSubUtils.js";
|
||||
|
||||
// Add usage to end of cycle
|
||||
const addUsageToNextInvoice = async ({
|
||||
db,
|
||||
intervalToInvoiceItems,
|
||||
intervalToSub,
|
||||
customer,
|
||||
org,
|
||||
logger,
|
||||
attachParams,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
intervalToInvoiceItems: any;
|
||||
intervalToSub: any;
|
||||
customer: any;
|
||||
org: any;
|
||||
logger: any;
|
||||
attachParams: AttachParams;
|
||||
}) => {
|
||||
for (const interval in intervalToInvoiceItems) {
|
||||
const itemsToInvoice = intervalToInvoiceItems[interval];
|
||||
|
||||
if (itemsToInvoice.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add items to invoice
|
||||
const stripeCli = createStripeCli({
|
||||
org: org,
|
||||
env: customer.env,
|
||||
});
|
||||
|
||||
for (const item of itemsToInvoice) {
|
||||
const { amount, description } = item;
|
||||
|
||||
logger.info(
|
||||
` feature: ${item.feature.id}, overage: ${item.overage}, amount: ${amount}`,
|
||||
);
|
||||
|
||||
let relatedSub = intervalToSub[interval];
|
||||
if (!relatedSub) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create invoice item
|
||||
let invoiceItem = {
|
||||
customer: customer.processor.id,
|
||||
currency: org.default_currency,
|
||||
description,
|
||||
price_data: {
|
||||
product: (item.price.config! as UsagePriceConfig).stripe_product_id!,
|
||||
unit_amount: Math.round(amount * 100),
|
||||
currency: org.default_currency,
|
||||
},
|
||||
subscription: relatedSub.id,
|
||||
period: {
|
||||
start: item.periodStart,
|
||||
end: item.periodEnd,
|
||||
},
|
||||
};
|
||||
|
||||
await stripeCli.invoiceItems.create(invoiceItem);
|
||||
|
||||
// Update cus ent to 0
|
||||
await CusEntService.update({
|
||||
db,
|
||||
id: item.relatedCusEnt!.id,
|
||||
updates: getResetBalancesUpdate({
|
||||
cusEnt: item.relatedCusEnt!,
|
||||
allowance: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
// Update existing cusEnt in attachParams
|
||||
let cusProducts = attachParams.cusProducts;
|
||||
for (const cusProduct of cusProducts!) {
|
||||
for (let i = 0; i < cusProduct.customer_entitlements.length; i++) {
|
||||
let cusEnt = cusProduct.customer_entitlements[i];
|
||||
if (cusEnt.id === item.relatedCusEnt!.id) {
|
||||
let balancesUpdate = getResetBalancesUpdate({
|
||||
cusEnt,
|
||||
allowance: 0,
|
||||
});
|
||||
cusProduct.customer_entitlements[i] = {
|
||||
...cusEnt,
|
||||
...balancesUpdate,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const invoiceForUsageImmediately = async ({
|
||||
db,
|
||||
intervalToInvoiceItems,
|
||||
customer,
|
||||
org,
|
||||
logger,
|
||||
curCusProduct,
|
||||
attachParams,
|
||||
newSubs,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
intervalToInvoiceItems: any;
|
||||
customer: any;
|
||||
org: any;
|
||||
logger: any;
|
||||
curCusProduct: FullCusProduct;
|
||||
attachParams: AttachParams;
|
||||
newSubs: Stripe.Subscription[];
|
||||
}) => {
|
||||
// 1. Create invoice
|
||||
const stripeCli = createStripeCli({
|
||||
org: org,
|
||||
env: customer.env,
|
||||
});
|
||||
const product = curCusProduct.product;
|
||||
|
||||
let invoiceItems = Object.values(intervalToInvoiceItems).flat() as any[];
|
||||
if (invoiceItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let invoice: Stripe.Invoice;
|
||||
let newInvoice = false;
|
||||
|
||||
if (attachParams.invoiceOnly && newSubs.length > 0) {
|
||||
invoice = await stripeCli.invoices.retrieve(
|
||||
newSubs[0].latest_invoice as string,
|
||||
);
|
||||
|
||||
if (invoice.status !== "draft") {
|
||||
newInvoice = true;
|
||||
invoice = await stripeCli.invoices.create({
|
||||
customer: customer.processor.id,
|
||||
auto_advance: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
newInvoice = true;
|
||||
|
||||
invoice = await stripeCli.invoices.create({
|
||||
customer: customer.processor.id,
|
||||
auto_advance: true,
|
||||
});
|
||||
}
|
||||
|
||||
let autumnInvoiceItems: InvoiceItem[] = [];
|
||||
|
||||
for (const item of invoiceItems) {
|
||||
// const amount = getPriceForOverage(item.price, item.overage);
|
||||
const { amount, description } = item;
|
||||
let config = item.price.config! as UsagePriceConfig;
|
||||
// let stripePrice = await stripeCli.prices.retrieve(config.stripe_price_id!);
|
||||
let stripeProdId = config.stripe_product_id;
|
||||
if (!stripeProdId) {
|
||||
try {
|
||||
let stripePrice = await stripeCli.prices.retrieve(
|
||||
config.stripe_price_id!,
|
||||
);
|
||||
stripeProdId = stripePrice.product as string;
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
if (!stripeProdId) {
|
||||
stripeProdId = product.processor?.id;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`🌟🌟🌟 (Bill remaining) created invoice item: ${description} -- ${amount}`,
|
||||
);
|
||||
|
||||
let invoiceItem = {
|
||||
customer: customer.processor.id,
|
||||
invoice: invoice.id,
|
||||
currency: org.default_currency,
|
||||
description,
|
||||
price_data: {
|
||||
product: stripeProdId!,
|
||||
unit_amount: Math.round(amount * 100),
|
||||
currency: org.default_currency,
|
||||
},
|
||||
period: {
|
||||
start: item.periodStart,
|
||||
end: item.periodEnd,
|
||||
},
|
||||
};
|
||||
|
||||
let stripeInvoiceItem = await stripeCli.invoiceItems.create(invoiceItem);
|
||||
|
||||
autumnInvoiceItems.push({
|
||||
price_id: item.price.id!,
|
||||
internal_feature_id: item.feature.internal_id || null,
|
||||
description: description,
|
||||
period_start: item.periodStart * 1000,
|
||||
period_end: item.periodEnd * 1000,
|
||||
stripe_id: stripeInvoiceItem.id,
|
||||
});
|
||||
|
||||
await CusEntService.update({
|
||||
db,
|
||||
id: item.relatedCusEnt!.id,
|
||||
updates: {
|
||||
balance: 0,
|
||||
},
|
||||
});
|
||||
let index = curCusProduct.customer_entitlements.findIndex(
|
||||
(ce) => ce.id === item.relatedCusEnt!.id,
|
||||
);
|
||||
|
||||
curCusProduct.customer_entitlements[index] = {
|
||||
...curCusProduct.customer_entitlements[index],
|
||||
balance: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (newInvoice) {
|
||||
await stripeCli.invoices.finalizeInvoice(invoice.id);
|
||||
|
||||
const { paid, error } = await payForInvoice({
|
||||
stripeCli,
|
||||
paymentMethod: null,
|
||||
invoiceId: invoice.id,
|
||||
logger,
|
||||
});
|
||||
|
||||
if (!paid) {
|
||||
logger.warn("Failed to pay invoice for remaining usages", {
|
||||
stripeInvoice: newInvoice,
|
||||
paymentError: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await insertInvoiceFromAttach({
|
||||
db,
|
||||
attachParams,
|
||||
invoiceId: invoice.id,
|
||||
logger,
|
||||
});
|
||||
};
|
||||
|
||||
const getRemainingUsagesPreview = async ({
|
||||
intervalToInvoiceItems,
|
||||
curCusProduct,
|
||||
}: {
|
||||
intervalToInvoiceItems: any;
|
||||
curCusProduct: FullCusProduct;
|
||||
}) => {
|
||||
let invoiceItems = Object.values(intervalToInvoiceItems).flat() as any[];
|
||||
if (invoiceItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let items = [];
|
||||
for (const item of invoiceItems) {
|
||||
const amount = getPriceForOverage(item.price, item.overage);
|
||||
const description = `${curCusProduct.product.name} - ${
|
||||
item.feature.name
|
||||
} x ${Math.round(item.usage)}`;
|
||||
|
||||
items.push({
|
||||
amount,
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
// export const billForRemainingUsages = async ({
|
||||
// db,
|
||||
// logger,
|
||||
// attachParams,
|
||||
// curCusProduct,
|
||||
// newSubs,
|
||||
// shouldPreview = false,
|
||||
// billImmediately = false,
|
||||
// }: {
|
||||
// db: DrizzleCli;
|
||||
// logger: any;
|
||||
// attachParams: AttachParams;
|
||||
// curCusProduct: FullCusProduct;
|
||||
// newSubs: Stripe.Subscription[];
|
||||
// shouldPreview?: boolean;
|
||||
// billImmediately?: boolean;
|
||||
// }) => {
|
||||
// const { customer_prices, customer_entitlements } = curCusProduct;
|
||||
// const { customer, org } = attachParams;
|
||||
|
||||
// const intervalToSub: any = {};
|
||||
|
||||
// for (const sub of newSubs) {
|
||||
// const interval = subToAutumnInterval(sub);
|
||||
// if (interval) {
|
||||
// intervalToSub[interval] = sub;
|
||||
// }
|
||||
// }
|
||||
|
||||
// const intervalToInvoiceItems: any = {};
|
||||
// const stripeCli = createStripeCli({
|
||||
// org: org,
|
||||
// env: customer.env,
|
||||
// });
|
||||
|
||||
// for (const cp of customer_prices) {
|
||||
// const config = cp.price.config! as UsagePriceConfig;
|
||||
// const relatedCusEnt = getRelatedCusEnt({
|
||||
// cusPrice: cp,
|
||||
// cusEnts: customer_entitlements,
|
||||
// });
|
||||
// const billingType = getBillingType(config);
|
||||
|
||||
// if (billingType !== BillingType.UsageInArrear) continue;
|
||||
|
||||
// const { usage, overage, description, amount } = getCusPriceUsage({
|
||||
// cusPrice: cp,
|
||||
// cusProduct: curCusProduct,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// if (overage <= 0) continue; // no overage, no need to bill...
|
||||
|
||||
// let interval = config.interval as BillingInterval;
|
||||
// if (!intervalToInvoiceItems[interval]) {
|
||||
// intervalToInvoiceItems[interval] = [];
|
||||
// }
|
||||
|
||||
// let sub = intervalToSub[interval];
|
||||
|
||||
// const stripeNow = await getStripeNow({
|
||||
// stripeCli,
|
||||
// stripeSub: sub,
|
||||
// });
|
||||
|
||||
// intervalToInvoiceItems[interval].push({
|
||||
// overage,
|
||||
// usage,
|
||||
// description,
|
||||
// amount,
|
||||
|
||||
// feature: relatedCusEnt?.entitlement.feature,
|
||||
// price: cp.price,
|
||||
// relatedCusEnt,
|
||||
// periodStart: sub?.current_period_start,
|
||||
// periodEnd: stripeNow,
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (shouldPreview) {
|
||||
// return getRemainingUsagesPreview({
|
||||
// intervalToInvoiceItems,
|
||||
// curCusProduct,
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (billImmediately) {
|
||||
// await invoiceForUsageImmediately({
|
||||
// db,
|
||||
// intervalToInvoiceItems,
|
||||
// customer,
|
||||
// org,
|
||||
// logger,
|
||||
// curCusProduct,
|
||||
// attachParams,
|
||||
// newSubs,
|
||||
// });
|
||||
// } else {
|
||||
// await addUsageToNextInvoice({
|
||||
// db,
|
||||
// intervalToInvoiceItems,
|
||||
// intervalToSub,
|
||||
// customer,
|
||||
// org,
|
||||
// logger,
|
||||
// attachParams,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
@@ -1,4 +1,9 @@
|
||||
import { getSubItemsForCusProduct } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { subToAutumnInterval } from "@/external/stripe/utils.js";
|
||||
import {
|
||||
priceToIntervalKey,
|
||||
toIntervalKey,
|
||||
} from "@/internal/products/prices/priceUtils/convertPrice.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { FullCusProduct } from "@autumn/shared";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
@@ -32,8 +37,10 @@ export const cancelCurSubs = async ({
|
||||
cusProduct: curCusProduct,
|
||||
});
|
||||
|
||||
let interval = sub.items.data[0].price.recurring!.interval;
|
||||
intervalToOtherSubs[interval] = {
|
||||
// let interval = sub.items.data[0].price.recurring!.interval;
|
||||
let subInterval = subToAutumnInterval(sub);
|
||||
let intervalKey = toIntervalKey(subInterval);
|
||||
intervalToOtherSubs[intervalKey] = {
|
||||
otherSubItems,
|
||||
otherSub: sub,
|
||||
};
|
||||
|
||||
@@ -1,569 +0,0 @@
|
||||
// import { getStripeExpandedInvoice } from "@/external/stripe/stripeInvoiceUtils.js";
|
||||
// import {
|
||||
// getStripeSchedules,
|
||||
// getStripeSubs,
|
||||
// } from "@/external/stripe/stripeSubUtils.js";
|
||||
// import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
|
||||
// import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||
// import {
|
||||
// attachToInsertParams,
|
||||
// isFreeProduct,
|
||||
// } from "@/internal/products/productUtils.js";
|
||||
// import RecaseError from "@/utils/errorUtils.js";
|
||||
// import {
|
||||
// FullCusProduct,
|
||||
// ErrCode,
|
||||
// FullProduct,
|
||||
// CusProductStatus,
|
||||
// APIVersion,
|
||||
// UsagePriceConfig,
|
||||
// AttachScenario,
|
||||
// } from "@autumn/shared";
|
||||
|
||||
// import { StatusCodes } from "http-status-codes";
|
||||
// import Stripe from "stripe";
|
||||
// import { createFullCusProduct } from "../add-product/createFullCusProduct.js";
|
||||
// import { handleAddProduct } from "../attach/attachFunctions/addProductFlow/handleAddProduct.js";
|
||||
// import {
|
||||
// AttachParams,
|
||||
// AttachResultSchema,
|
||||
// } from "../cusProducts/AttachParams.js";
|
||||
// import { CusProductService } from "../cusProducts/CusProductService.js";
|
||||
// import {
|
||||
// getInvoiceItems,
|
||||
// insertInvoiceFromAttach,
|
||||
// } from "@/internal/invoices/invoiceUtils.js";
|
||||
// import { InvoiceService } from "@/internal/invoices/InvoiceService.js";
|
||||
// import { updateScheduledSubWithNewItems } from "./scheduleUtils/updateScheduleWithNewItems.js";
|
||||
|
||||
// // import { billForRemainingUsages } from "./billRemainingUsages.js";
|
||||
// import { updateStripeSubscription } from "@/external/stripe/stripeSubUtils/updateStripeSub.js";
|
||||
// import { createStripeSub } from "@/external/stripe/stripeSubUtils/createStripeSub.js";
|
||||
|
||||
// import {
|
||||
// addBillingIntervalUnix,
|
||||
// subtractBillingIntervalUnix,
|
||||
// } from "@/internal/products/prices/billingIntervalUtils.js";
|
||||
|
||||
// import { differenceInSeconds } from "date-fns";
|
||||
// import { SuccessCode } from "@autumn/shared";
|
||||
// import { notNullish } from "@/utils/genUtils.js";
|
||||
// import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
// import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
// import { getStripeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
|
||||
|
||||
// export enum ProrationBehavior {
|
||||
// Immediately = "immediately",
|
||||
// NextBilling = "next_billing",
|
||||
// None = "none",
|
||||
// }
|
||||
|
||||
// // UPGRADE FUNCTIONS
|
||||
// export const handleStripeSubUpdate = async ({
|
||||
// db,
|
||||
// stripeCli,
|
||||
// curCusProduct,
|
||||
// attachParams,
|
||||
// disableFreeTrial,
|
||||
// stripeSubs,
|
||||
// logger,
|
||||
// carryExistingUsages = false,
|
||||
// prorationBehavior = ProrationBehavior.Immediately,
|
||||
// shouldPreview = false,
|
||||
// }: {
|
||||
// db: DrizzleCli;
|
||||
// stripeCli: Stripe;
|
||||
// curCusProduct: FullCusProduct;
|
||||
// attachParams: AttachParams;
|
||||
// disableFreeTrial?: boolean;
|
||||
// stripeSubs: Stripe.Subscription[];
|
||||
// logger: any;
|
||||
// carryExistingUsages?: boolean;
|
||||
// prorationBehavior?: ProrationBehavior;
|
||||
// shouldPreview?: boolean;
|
||||
// }) => {
|
||||
// // HANLDE UPGRADE
|
||||
|
||||
// // 1. Get item sets
|
||||
// const itemSets = await getStripeSubItems({
|
||||
// attachParams,
|
||||
// carryExistingUsages,
|
||||
// });
|
||||
|
||||
// const firstSub = stripeSubs[0];
|
||||
// const firstItemSet = itemSets[0];
|
||||
// let curPrices = curCusProduct.customer_prices.map((cp) => cp.price);
|
||||
|
||||
// // 1. DELETE ITEMS FROM CURRENT SUB THAT CORRESPOND TO OLD PRODUCT
|
||||
// for (const item of firstSub.items.data) {
|
||||
// let stripePriceExists = curPrices.some(
|
||||
// (p) =>
|
||||
// p.config!.stripe_price_id === item.price.id ||
|
||||
// (p.config as UsagePriceConfig).stripe_product_id === item.price.product,
|
||||
// );
|
||||
|
||||
// let stripeProdExists =
|
||||
// item.price.product == curCusProduct.product.processor?.id;
|
||||
|
||||
// if (!stripePriceExists && !stripeProdExists) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// firstItemSet.items.push({
|
||||
// id: item.id,
|
||||
// deleted: true,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 2. Add trial to new subscription?
|
||||
// let trialEnd;
|
||||
// if (!disableFreeTrial) {
|
||||
// trialEnd = freeTrialToStripeTimestamp({
|
||||
// freeTrial: attachParams.freeTrial,
|
||||
// now: attachParams.now,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 3. Update current subscription
|
||||
// let newSubs = [];
|
||||
// const subUpdateRes = await updateStripeSubscription({
|
||||
// db,
|
||||
// stripeCli,
|
||||
// subscriptionId: firstSub.id,
|
||||
// trialEnd,
|
||||
// org: attachParams.org,
|
||||
// customer: attachParams.customer,
|
||||
// invoiceOnly: attachParams.invoiceOnly || false,
|
||||
// prorationBehavior,
|
||||
// logger,
|
||||
// itemSet: firstItemSet,
|
||||
// shouldPreview,
|
||||
// });
|
||||
|
||||
// if (shouldPreview) {
|
||||
// return subUpdateRes;
|
||||
// }
|
||||
|
||||
// let subUpdate = subUpdateRes as Stripe.Subscription;
|
||||
|
||||
// newSubs.push(subUpdate);
|
||||
|
||||
// // 4. If scheduled_ids exist, need to update schedule too (BRUH)!
|
||||
// if (curCusProduct.scheduled_ids && curCusProduct.scheduled_ids.length > 0) {
|
||||
// let schedules = await getStripeSchedules({
|
||||
// stripeCli,
|
||||
// scheduleIds: curCusProduct.scheduled_ids,
|
||||
// });
|
||||
|
||||
// for (const scheduleObj of schedules) {
|
||||
// const { interval, schedule } = scheduleObj;
|
||||
|
||||
// // If schedule has passed, skip this step.
|
||||
// let phase = schedule.phases.length > 0 ? schedule.phases[0] : null;
|
||||
// let now = Date.now();
|
||||
// if (schedule.test_clock) {
|
||||
// let testClock = await stripeCli.testHelpers.testClocks.retrieve(
|
||||
// schedule.test_clock as string,
|
||||
// );
|
||||
// now = testClock.frozen_time * 1000;
|
||||
// }
|
||||
|
||||
// if (phase && phase.start_date * 1000 < now) {
|
||||
// logger.info("Note: Schedule has passed, skipping");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Get corresponding item set
|
||||
// const itemSet = itemSets.find((itemSet) => itemSet.interval === interval);
|
||||
// if (!itemSet) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// await updateScheduledSubWithNewItems({
|
||||
// scheduleObj,
|
||||
// newItems: itemSet.items,
|
||||
// stripeCli,
|
||||
// cusProductsForGroup: [curCusProduct],
|
||||
// itemSet,
|
||||
// db,
|
||||
// org: attachParams.org,
|
||||
// env: attachParams.customer.env,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 5. Insert invoice
|
||||
// await insertInvoiceFromAttach({
|
||||
// db,
|
||||
// attachParams,
|
||||
// invoiceId: subUpdate.latest_invoice as string,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// // 2. Create new subscriptions
|
||||
// let newSubIds = [];
|
||||
// newSubIds.push(firstSub.id);
|
||||
// const newItemSets = itemSets.slice(1);
|
||||
// let invoiceIds = [];
|
||||
|
||||
// // CREATE NEW SUBSCRIPTIONS
|
||||
// for (const itemSet of newItemSets) {
|
||||
// // 1. Next billing date for first sub
|
||||
// // const nextCycleAnchor = firstSub.current_period_end * 1000;
|
||||
// const nextCycleAnchor = subUpdate.current_period_end * 1000;
|
||||
// let nextCycleAnchorUnix = nextCycleAnchor;
|
||||
// const naturalBillingDate = addBillingIntervalUnix(
|
||||
// Date.now(),
|
||||
// itemSet.interval,
|
||||
// );
|
||||
|
||||
// while (true) {
|
||||
// const subtractedUnix = subtractBillingIntervalUnix(
|
||||
// nextCycleAnchorUnix,
|
||||
// itemSet.interval,
|
||||
// );
|
||||
|
||||
// if (subtractedUnix < Date.now()) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// nextCycleAnchorUnix = subtractedUnix;
|
||||
// }
|
||||
|
||||
// let billingCycleAnchorUnix: number | undefined = nextCycleAnchorUnix;
|
||||
// if (
|
||||
// differenceInSeconds(
|
||||
// new Date(naturalBillingDate),
|
||||
// new Date(nextCycleAnchorUnix),
|
||||
// ) < 60
|
||||
// ) {
|
||||
// billingCycleAnchorUnix = undefined;
|
||||
// }
|
||||
|
||||
// const newSub = (await createStripeSub({
|
||||
// db,
|
||||
// stripeCli,
|
||||
// customer: attachParams.customer,
|
||||
// org: attachParams.org,
|
||||
// itemSet,
|
||||
// invoiceOnly: attachParams.invoiceOnly || false,
|
||||
// freeTrial: attachParams.freeTrial,
|
||||
// anchorToUnix: billingCycleAnchorUnix,
|
||||
// })) as Stripe.Subscription;
|
||||
|
||||
// newSubs.push(newSub);
|
||||
// newSubIds.push(newSub.id);
|
||||
// invoiceIds.push(newSub.latest_invoice as string);
|
||||
// }
|
||||
|
||||
// // 3. Cancel old subscriptions
|
||||
// let remainingExistingSubIds = stripeSubs.slice(1).map((sub) => sub.id);
|
||||
|
||||
// return {
|
||||
// subUpdate,
|
||||
// newSubIds,
|
||||
// invoiceIds,
|
||||
// remainingExistingSubIds,
|
||||
// newSubs,
|
||||
// };
|
||||
// };
|
||||
|
||||
// const handleOnlyEntsChanged = async ({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// curCusProduct,
|
||||
// carryExistingUsages = false,
|
||||
// }: {
|
||||
// req: any;
|
||||
// res: any;
|
||||
// attachParams: AttachParams;
|
||||
// curCusProduct: FullCusProduct;
|
||||
// carryExistingUsages?: boolean;
|
||||
// }) => {
|
||||
// const logger = req.logtail;
|
||||
// logger.info("Only entitlements changed, no need to update prices");
|
||||
|
||||
// // Remove subscription from previous cus product
|
||||
// await CusProductService.update({
|
||||
// db: req.db,
|
||||
// cusProductId: curCusProduct.id,
|
||||
// updates: {
|
||||
// subscription_ids: [],
|
||||
// },
|
||||
// });
|
||||
|
||||
// await createFullCusProduct({
|
||||
// db: req.db,
|
||||
// attachParams: attachToInsertParams(attachParams, attachParams.products[0]),
|
||||
// subscriptionIds: curCusProduct.subscription_ids || [],
|
||||
// disableFreeTrial: false,
|
||||
// keepResetIntervals: true,
|
||||
// carryExistingUsages,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// logger.info("✅ Successfully updated entitlements for product");
|
||||
|
||||
// let org = attachParams.org;
|
||||
|
||||
// let apiVersion = org.api_version || APIVersion.v1;
|
||||
// if (apiVersion >= APIVersion.v1_1) {
|
||||
// res.status(200).json(
|
||||
// AttachResultSchema.parse({
|
||||
// customer_id: attachParams.customer.id,
|
||||
// product_ids: attachParams.products.map((p) => p.id),
|
||||
// code: SuccessCode.FeaturesUpdated,
|
||||
// message: `Successfully updated features for customer ${attachParams.customer.id} on product ${attachParams.products[0].name}`,
|
||||
// }),
|
||||
// );
|
||||
// } else {
|
||||
// res.status(200).json({
|
||||
// success: true,
|
||||
// message: `Successfully updated entitlements for ${curCusProduct.product.name}`,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const handleUpgrade = async ({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// curCusProduct,
|
||||
// curFullProduct,
|
||||
// hasPricesChanged = true,
|
||||
// fromReq = true,
|
||||
// carryExistingUsages = false,
|
||||
// prorationBehavior,
|
||||
// newVersion = false,
|
||||
// updateSameProduct = false,
|
||||
// }: {
|
||||
// req: ExtendedRequest;
|
||||
// res: any;
|
||||
// attachParams: AttachParams;
|
||||
// curCusProduct: FullCusProduct;
|
||||
// curFullProduct: FullProduct;
|
||||
// hasPricesChanged?: boolean;
|
||||
// fromReq?: boolean;
|
||||
// carryExistingUsages?: boolean;
|
||||
// prorationBehavior?: ProrationBehavior;
|
||||
// newVersion?: boolean;
|
||||
// updateSameProduct?: boolean;
|
||||
// }) => {
|
||||
// const logger = req.logtail;
|
||||
// const { org, customer, products } = attachParams;
|
||||
// let product = products[0];
|
||||
|
||||
// let disableFreeTrial = false;
|
||||
// if (newVersion) {
|
||||
// disableFreeTrial = true;
|
||||
// }
|
||||
|
||||
// if (!hasPricesChanged) {
|
||||
// await handleOnlyEntsChanged({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// curCusProduct,
|
||||
// carryExistingUsages,
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// logger.info(
|
||||
// `Upgrading ${curFullProduct.name} to ${product.name} for ${customer.id}`,
|
||||
// );
|
||||
|
||||
// const stripeCli = createStripeCli({ org, env: customer.env });
|
||||
// const stripeSubs = await getStripeSubs({
|
||||
// stripeCli,
|
||||
// subIds: curCusProduct.subscription_ids,
|
||||
// });
|
||||
|
||||
// // 1. If current product has trial and new product has trial, cancel and start new subscription
|
||||
// let trialToTrial =
|
||||
// curCusProduct.trial_ends_at &&
|
||||
// curCusProduct.trial_ends_at > Date.now() &&
|
||||
// attachParams.freeTrial &&
|
||||
// !disableFreeTrial;
|
||||
|
||||
// let trialToPaid =
|
||||
// curCusProduct.trial_ends_at &&
|
||||
// curCusProduct.trial_ends_at > Date.now() &&
|
||||
// !attachParams.freeTrial &&
|
||||
// !newVersion; // If trial to paid and not migrating, cancel trial and start new sub immediately.
|
||||
|
||||
// // 2. If upgrade is free to paid, or paid to free (migration / update)
|
||||
// let toFreeProduct = isFreeProduct(attachParams.prices);
|
||||
// let paidToFreeProduct =
|
||||
// isFreeProduct(curCusProduct.customer_prices.map((cp) => cp.price)) &&
|
||||
// !isFreeProduct(attachParams.prices);
|
||||
|
||||
// if (trialToTrial || trialToPaid || toFreeProduct || paidToFreeProduct) {
|
||||
// if (trialToTrial) {
|
||||
// logger.info(
|
||||
// `Upgrading from trial to trial, cancelling and starting new subscription`,
|
||||
// );
|
||||
// } else if (toFreeProduct) {
|
||||
// logger.info(
|
||||
// `switching to free product, cancelling (if needed) and adding free product`,
|
||||
// );
|
||||
// }
|
||||
|
||||
// await handleAddProduct({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// fromRequest: fromReq,
|
||||
// carryExistingUsages,
|
||||
// keepResetIntervals: newVersion, // keep reset intervals if upgrading version (migrations)
|
||||
// disableMerge: true,
|
||||
// });
|
||||
|
||||
// if (notNullish(curCusProduct.subscription_ids)) {
|
||||
// for (const subId of curCusProduct.subscription_ids!) {
|
||||
// try {
|
||||
// await stripeCli.subscriptions.cancel(subId);
|
||||
// } catch (error) {
|
||||
// throw new RecaseError({
|
||||
// message: `Handling upgrade (cur product on trial): failed to cancel subscription ${subId}`,
|
||||
// code: ErrCode.StripeCancelSubscriptionFailed,
|
||||
// statusCode: StatusCodes.BAD_REQUEST,
|
||||
// data: error,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
// logger.info("1. Updating current subscription to new product");
|
||||
// let {
|
||||
// subUpdate,
|
||||
// newSubIds,
|
||||
// invoiceIds,
|
||||
// remainingExistingSubIds,
|
||||
// newSubs,
|
||||
// }: any = await handleStripeSubUpdate({
|
||||
// db: req.db,
|
||||
// curCusProduct,
|
||||
// stripeCli,
|
||||
// attachParams,
|
||||
// disableFreeTrial,
|
||||
// stripeSubs,
|
||||
// logger,
|
||||
// carryExistingUsages,
|
||||
// prorationBehavior,
|
||||
// });
|
||||
|
||||
// // logger.info("2. Bill for remaining usages");
|
||||
// // await billForRemainingUsages({
|
||||
// // db: req.db,
|
||||
// // attachParams,
|
||||
// // curCusProduct,
|
||||
// // newSubs,
|
||||
// // logger,
|
||||
// // });
|
||||
|
||||
// logger.info(
|
||||
// "2.1. Remove old subscription ID from old cus product and expire",
|
||||
// );
|
||||
// await CusProductService.update({
|
||||
// db: req.db,
|
||||
// cusProductId: curCusProduct.id,
|
||||
// updates: {
|
||||
// subscription_ids: curCusProduct.subscription_ids!.filter(
|
||||
// (subId) => subId !== subUpdate.id,
|
||||
// ),
|
||||
// processor: {
|
||||
// ...curCusProduct.processor,
|
||||
// subscription_id: null,
|
||||
// } as any,
|
||||
// status: CusProductStatus.Expired,
|
||||
// },
|
||||
// });
|
||||
|
||||
// if (remainingExistingSubIds && remainingExistingSubIds.length > 0) {
|
||||
// logger.info("2.2. Canceling old subscriptions");
|
||||
// for (const subId of remainingExistingSubIds) {
|
||||
// logger.info(" - Cancelling old subscription", subId);
|
||||
// await stripeCli.subscriptions.cancel(subId);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Handle backend
|
||||
// logger.info("3. Creating new full cus product");
|
||||
|
||||
// await createFullCusProduct({
|
||||
// db: req.db,
|
||||
// attachParams: attachToInsertParams(attachParams, products[0]),
|
||||
// subscriptionIds: newSubIds,
|
||||
|
||||
// anchorToUnix:
|
||||
// newSubs.length > 0 ? newSubs[0].current_period_end * 1000 : undefined,
|
||||
|
||||
// disableFreeTrial,
|
||||
// carryExistingUsages,
|
||||
// carryOverTrial: true,
|
||||
// scenario: AttachScenario.Upgrade,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// // Create invoices
|
||||
// logger.info("4. Creating invoices");
|
||||
// logger.info(`Invoice IDs: ${invoiceIds}`);
|
||||
// const batchInsertInvoice = [];
|
||||
|
||||
// for (const invoiceId of invoiceIds) {
|
||||
// const insertInvoice = async () => {
|
||||
// const stripeInvoice = await getStripeExpandedInvoice({
|
||||
// stripeCli,
|
||||
// stripeInvoiceId: invoiceId,
|
||||
// });
|
||||
|
||||
// let autumnInvoiceItems = await getInvoiceItems({
|
||||
// stripeInvoice,
|
||||
// prices: attachParams.prices,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// await InvoiceService.createInvoiceFromStripe({
|
||||
// db: req.db,
|
||||
// stripeInvoice,
|
||||
// internalCustomerId: customer.internal_id,
|
||||
// internalEntityId: attachParams.internalEntityId,
|
||||
// org,
|
||||
// productIds: products.map((p) => p.id),
|
||||
// internalProductIds: products.map((p) => p.internal_id),
|
||||
// items: autumnInvoiceItems,
|
||||
// });
|
||||
// };
|
||||
// batchInsertInvoice.push(insertInvoice());
|
||||
// }
|
||||
|
||||
// await Promise.all(batchInsertInvoice);
|
||||
// logger.info("✅ Done!");
|
||||
|
||||
// if (fromReq) {
|
||||
// if (org.api_version! >= APIVersion.v1_1) {
|
||||
// res.status(200).json(
|
||||
// AttachResultSchema.parse({
|
||||
// customer_id: customer.id,
|
||||
// product_ids: products.map((p) => p.id),
|
||||
// code: updateSameProduct
|
||||
// ? SuccessCode.UpdatedSameProduct
|
||||
// : newVersion
|
||||
// ? SuccessCode.UpgradedToNewVersion
|
||||
// : SuccessCode.UpgradedToNewProduct,
|
||||
// message: `Successfully attached ${product.name} to ${customer.name} -- upgraded from ${curFullProduct.name}`,
|
||||
// }),
|
||||
// );
|
||||
// } else {
|
||||
// res.status(200).json({
|
||||
// success: true,
|
||||
// message: `Successfully attached ${product.name} to ${customer.name} -- upgraded from ${curFullProduct.name}`,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
AppEnv,
|
||||
AttachScenario,
|
||||
FullCusProduct,
|
||||
intervalsSame,
|
||||
Organization,
|
||||
Product,
|
||||
} from "@autumn/shared";
|
||||
@@ -30,6 +31,7 @@ import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getStripeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { subToAutumnInterval } from "@/external/stripe/utils.js";
|
||||
|
||||
export const getPricesForCusProduct = ({
|
||||
cusProduct,
|
||||
@@ -134,7 +136,7 @@ export const cancelFutureProductSchedule = async ({
|
||||
|
||||
// Case where cur scheduled product is not free
|
||||
for (const scheduleObj of schedules) {
|
||||
const { schedule, interval, prices } = scheduleObj;
|
||||
const { schedule, interval, intervalCount, prices } = scheduleObj;
|
||||
|
||||
if (inIntervals && !inIntervals.includes(interval!)) {
|
||||
continue;
|
||||
@@ -142,7 +144,7 @@ export const cancelFutureProductSchedule = async ({
|
||||
|
||||
// 1. Remove cur scheduled product items from schedule
|
||||
const activeCusProducts = cusProducts.filter((cusProduct) =>
|
||||
isActiveStatus(cusProduct?.status),
|
||||
isActiveStatus(cusProduct?.status)
|
||||
);
|
||||
|
||||
const filteredScheduleItems = getFilteredScheduleItems({
|
||||
@@ -152,8 +154,11 @@ export const cancelFutureProductSchedule = async ({
|
||||
|
||||
// 2. If any items left, update schedule with cur main product!
|
||||
if (filteredScheduleItems.length > 0) {
|
||||
let oldItemSet = oldItemSets.find(
|
||||
(itemSet) => itemSet.interval === interval,
|
||||
let oldItemSet = oldItemSets.find((itemSet) =>
|
||||
intervalsSame({
|
||||
intervalA: { interval, intervalCount },
|
||||
intervalB: itemSet,
|
||||
})
|
||||
);
|
||||
|
||||
await updateScheduledSubWithNewItems({
|
||||
@@ -186,7 +191,7 @@ export const cancelFutureProductSchedule = async ({
|
||||
cusProductId: curMainProduct!.id,
|
||||
updates: {
|
||||
scheduled_ids: curMainProduct!.scheduled_ids?.filter(
|
||||
(id) => id !== schedule.id,
|
||||
(id) => id !== schedule.id
|
||||
),
|
||||
},
|
||||
});
|
||||
@@ -202,15 +207,21 @@ export const cancelFutureProductSchedule = async ({
|
||||
await stripeCli.subscriptionSchedules.cancel(schedule.id);
|
||||
} catch (error: any) {
|
||||
logger.warn(
|
||||
`❌ Error cancelling schedule: ${schedule.id}, ${error.message}`,
|
||||
`❌ Error cancelling schedule: ${schedule.id}, ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
const subWithSameInterval = curSubs.find(
|
||||
(sub) =>
|
||||
const subWithSameInterval = curSubs.find((sub) => {
|
||||
let subInterval = subToAutumnInterval(sub);
|
||||
// sub.items.data[0]?.price?.recurring?.interval === interval
|
||||
return (
|
||||
sub.items.data.length > 0 &&
|
||||
sub.items.data[0]?.price?.recurring?.interval === interval,
|
||||
);
|
||||
intervalsSame({
|
||||
intervalA: { interval, intervalCount },
|
||||
intervalB: subInterval,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
if (subWithSameInterval && renewCurProduct) {
|
||||
await stripeCli.subscriptions.update(subWithSameInterval.id, {
|
||||
@@ -291,7 +302,7 @@ export const cancelFutureProductSchedule = async ({
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`❌ Error sending products updated webhook from cancelFutureProductSchedule: ${error}`,
|
||||
`❌ Error sending products updated webhook from cancelFutureProductSchedule: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -310,7 +321,7 @@ export const cancelFutureProductSchedule = async ({
|
||||
batchRenew.push(
|
||||
stripeCli.subscriptions.update(subId, {
|
||||
cancel_at: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@ import Stripe from "stripe";
|
||||
export interface ScheduleObj {
|
||||
schedule: Stripe.SubscriptionSchedule;
|
||||
interval: BillingInterval;
|
||||
intervalCount: number;
|
||||
prices: Stripe.Price[];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { AppEnv, FullCusProduct, Organization } from "@autumn/shared";
|
||||
import {
|
||||
AppEnv,
|
||||
FullCusProduct,
|
||||
intervalsSame,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
import { getExistingCusProducts } from "../../cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { getStripeSchedules } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { getScheduleIdsFromCusProducts } from "../scheduleUtils.js";
|
||||
@@ -23,7 +28,7 @@ export const getOtherCusProductsOnSub = async ({
|
||||
if (
|
||||
cusProduct.id === curMainProduct.id ||
|
||||
!curMainSubIds?.some((subId) =>
|
||||
cusProduct?.subscription_ids?.includes(subId),
|
||||
cusProduct?.subscription_ids?.includes(subId)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
@@ -72,10 +77,13 @@ export const addCurMainProductToSchedule = async ({
|
||||
});
|
||||
|
||||
for (const scheduleObj of schedules) {
|
||||
const { schedule, interval } = scheduleObj;
|
||||
const { schedule, interval, intervalCount } = scheduleObj;
|
||||
|
||||
let oldItemSet = oldItemSets.find(
|
||||
(itemSet) => itemSet.interval === interval,
|
||||
let oldItemSet = oldItemSets.find((itemSet) =>
|
||||
intervalsSame({
|
||||
intervalA: { interval, intervalCount },
|
||||
intervalB: itemSet,
|
||||
})
|
||||
);
|
||||
|
||||
await updateScheduledSubWithNewItems({
|
||||
@@ -99,7 +107,7 @@ export const addCurMainProductToSchedule = async ({
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`✅ Added old items for product ${curMainProduct.product.name} to schedule: ${schedule.id}`,
|
||||
`✅ Added old items for product ${curMainProduct.product.name} to schedule: ${schedule.id}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export const getFilteredScheduleItems = ({
|
||||
scheduleObj: ScheduleObj;
|
||||
cusProducts: (FullCusProduct | undefined)[];
|
||||
}) => {
|
||||
const { schedule, interval, prices } = scheduleObj;
|
||||
const { schedule, prices } = scheduleObj;
|
||||
let scheduleItems = schedule.phases[0].items;
|
||||
|
||||
let curPrices: any[] = [];
|
||||
@@ -31,10 +31,10 @@ export const getFilteredScheduleItems = ({
|
||||
curPrices.some(
|
||||
(price) =>
|
||||
price.config?.stripe_price_id === scheduleItem.price ||
|
||||
price.config?.stripe_product_id === stripePrice?.product,
|
||||
price.config?.stripe_product_id === stripePrice?.product
|
||||
) ||
|
||||
products.some(
|
||||
(product) => product.processor?.id === stripePrice?.product,
|
||||
(product) => product.processor?.id === stripePrice?.product
|
||||
);
|
||||
|
||||
return !inCurProduct;
|
||||
|
||||
@@ -70,6 +70,7 @@ export type AttachParams = {
|
||||
scenario?: AttachScenario;
|
||||
|
||||
fromMigration?: boolean;
|
||||
finalizeInvoice?: boolean;
|
||||
req?: any;
|
||||
};
|
||||
|
||||
@@ -104,6 +105,7 @@ export type InsertCusProductParams = {
|
||||
internalEntityId?: string;
|
||||
fromMigration?: boolean;
|
||||
apiVersion?: APIVersion;
|
||||
finalizeInvoice?: boolean;
|
||||
};
|
||||
|
||||
export const AttachResultSchema = z.object({
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
CusProductStatus,
|
||||
Customer,
|
||||
EntInterval,
|
||||
entIntervalsDifferent,
|
||||
entIntervalToValue,
|
||||
Entitlement,
|
||||
EntitlementWithFeature,
|
||||
Entity,
|
||||
@@ -46,14 +48,14 @@ export const getCusEntMasterBalance = ({
|
||||
(acc, curr) => {
|
||||
return acc + curr.balance;
|
||||
},
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
let totalAdjustment = Object.values(cusEnt.entities || {}).reduce(
|
||||
(acc, curr) => {
|
||||
return acc + curr.adjustment;
|
||||
},
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -69,7 +71,7 @@ export const getCusEntMasterBalance = ({
|
||||
entities &&
|
||||
entities.filter(
|
||||
(entity) =>
|
||||
entity.internal_feature_id == feature.internal_id && entity.deleted,
|
||||
entity.internal_feature_id == feature.internal_id && entity.deleted
|
||||
).length;
|
||||
|
||||
return {
|
||||
@@ -120,7 +122,7 @@ export const sortCusEntsForDeduction = (
|
||||
cusEnts: (FullCustomerEntitlement & {
|
||||
customer_product?: FullCusProduct;
|
||||
})[],
|
||||
reverseOrder: boolean = false,
|
||||
reverseOrder: boolean = false
|
||||
) => {
|
||||
let intervalOrder: Record<EntInterval, number> = {
|
||||
[EntInterval.Minute]: 0, // 1 minute
|
||||
@@ -206,11 +208,15 @@ export const sortCusEntsForDeduction = (
|
||||
}
|
||||
|
||||
// 3. Sort by interval
|
||||
if (aEnt.interval && bEnt.interval && aEnt.interval != bEnt.interval) {
|
||||
let aVal = entIntervalToValue(aEnt.interval, aEnt.interval_count);
|
||||
let bVal = entIntervalToValue(bEnt.interval, bEnt.interval_count);
|
||||
if (aEnt.interval && bEnt.interval && !aVal.eq(bVal)) {
|
||||
if (reverseOrder) {
|
||||
return intervalOrder[bEnt.interval] - intervalOrder[aEnt.interval];
|
||||
return bVal.sub(aVal).toNumber();
|
||||
// return intervalOrder[bEnt.interval] - intervalOrder[aEnt.interval];
|
||||
} else {
|
||||
return intervalOrder[aEnt.interval] - intervalOrder[bEnt.interval];
|
||||
return aVal.sub(bVal).toNumber();
|
||||
// return intervalOrder[aEnt.interval] - intervalOrder[bEnt.interval];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +247,7 @@ export const sortCusEntsForDeduction = (
|
||||
// Get related cusPrice
|
||||
export const getRelatedCusPrice = (
|
||||
cusEnt: FullCustomerEntitlement,
|
||||
cusPrices: FullCustomerPrice[],
|
||||
cusPrices: FullCustomerPrice[]
|
||||
) => {
|
||||
return cusPrices.find((cusPrice) => {
|
||||
let productMatch =
|
||||
@@ -328,7 +334,7 @@ export const getResetBalance = ({
|
||||
return (entitlement.allowance || 0) + quantity! * billingUnits!;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"WARNING: Failed to return quantity * billing units, returning allowance...",
|
||||
"WARNING: Failed to return quantity * billing units, returning allowance..."
|
||||
);
|
||||
return entitlement.allowance || 0;
|
||||
}
|
||||
@@ -347,14 +353,14 @@ export const getUnlimitedAndUsageAllowed = ({
|
||||
(cusEnt) =>
|
||||
cusEnt.internal_feature_id === internalFeatureId &&
|
||||
(cusEnt.entitlement.allowance_type === AllowanceType.Unlimited ||
|
||||
cusEnt.unlimited),
|
||||
cusEnt.unlimited)
|
||||
);
|
||||
|
||||
const usageAllowed = cusEnts.some(
|
||||
(ent) =>
|
||||
ent.internal_feature_id === internalFeatureId &&
|
||||
ent.usage_allowed &&
|
||||
nullish(ent.entitlement.usage_limit),
|
||||
nullish(ent.entitlement.usage_limit)
|
||||
);
|
||||
|
||||
return { unlimited, usageAllowed };
|
||||
@@ -426,7 +432,7 @@ export const cusEntsContainFeature = ({
|
||||
feature: Feature;
|
||||
}) => {
|
||||
return cusEnts.some(
|
||||
(cusEnt) => cusEnt.internal_feature_id === feature.internal_id!,
|
||||
(cusEnt) => cusEnt.internal_feature_id === feature.internal_id!
|
||||
);
|
||||
};
|
||||
|
||||
@@ -516,7 +522,7 @@ export const getExistingUsageFromCusProducts = ({
|
||||
!cp.product.is_add_on &&
|
||||
(internalEntityId
|
||||
? cp.internal_entity_id === internalEntityId
|
||||
: nullish(cp.internal_entity_id)),
|
||||
: nullish(cp.internal_entity_id))
|
||||
)
|
||||
.flatMap((cp) => cp.customer_entitlements)
|
||||
.find((ce) => ce.internal_feature_id === entitlement.internal_feature_id);
|
||||
@@ -537,15 +543,15 @@ export const getExistingUsageFromCusProducts = ({
|
||||
|
||||
// Get options
|
||||
let cusProduct = cusProducts?.find(
|
||||
(cp) => cp.id === existingCusEnt.customer_product_id,
|
||||
(cp) => cp.id === existingCusEnt.customer_product_id
|
||||
);
|
||||
let options = getEntOptions(
|
||||
cusProduct?.options || [],
|
||||
existingCusEnt.entitlement,
|
||||
existingCusEnt.entitlement
|
||||
);
|
||||
let price = getRelatedCusPrice(
|
||||
existingCusEnt,
|
||||
cusProduct?.customer_prices || [],
|
||||
cusProduct?.customer_prices || []
|
||||
);
|
||||
let existingAllowance = getResetBalance({
|
||||
entitlement: existingCusEnt.entitlement,
|
||||
|
||||
@@ -37,7 +37,7 @@ export const getExistingCusEntAndUsage = async ({
|
||||
|
||||
// 1. If there is only one cus ent, return it and usage
|
||||
let similarCusEnts = curCusProduct.customer_entitlements.filter(
|
||||
(ce) => ce.internal_feature_id === entitlement.internal_feature_id,
|
||||
(ce) => ce.internal_feature_id === entitlement.internal_feature_id
|
||||
// &&
|
||||
// ce.entitlement.interval === entitlement.interval
|
||||
);
|
||||
@@ -47,8 +47,8 @@ export const getExistingCusEntAndUsage = async ({
|
||||
"Similar entitlements:",
|
||||
similarCusEnts.map(
|
||||
(ce) =>
|
||||
`${ce.entitlement.feature_id} (${ce.entitlement.interval}) (${ce.balance})`,
|
||||
),
|
||||
`${ce.entitlement.feature_id} (${ce.entitlement.interval}) (${ce.balance})`
|
||||
)
|
||||
);
|
||||
|
||||
if (similarCusEnts.length === 1) {
|
||||
@@ -79,9 +79,9 @@ export const getExistingUsages = ({
|
||||
// Get entityUsage
|
||||
for (const entity of entities) {
|
||||
let feature = features.find(
|
||||
(f) => f.internal_id === entity.internal_feature_id,
|
||||
(f) => f.internal_id === entity.internal_feature_id
|
||||
);
|
||||
let key = `${feature?.id}-${EntInterval.Lifetime}`;
|
||||
let key = `${feature?.id}-${EntInterval.Lifetime}-1`;
|
||||
|
||||
if (!usages[key]) {
|
||||
usages[key] = {
|
||||
@@ -96,7 +96,7 @@ export const getExistingUsages = ({
|
||||
|
||||
for (const cusEnt of curCusProduct?.customer_entitlements || []) {
|
||||
let ent = cusEnt.entitlement;
|
||||
let key = `${ent.feature_id}-${ent.interval}`;
|
||||
let key = `${ent.feature_id}-${ent.interval}-${ent.interval_count || 1}`;
|
||||
let feature = ent.feature;
|
||||
if (feature.type == FeatureType.Boolean) {
|
||||
continue;
|
||||
@@ -199,8 +199,8 @@ export const addExistingUsagesToCusEnts = ({
|
||||
"Sorted cusEnts:",
|
||||
fullCusEnts.map(
|
||||
(ce) =>
|
||||
`${ce.entitlement.feature_id} (${ce.entitlement.interval}), balance: ${ce.balance}`,
|
||||
),
|
||||
`${ce.entitlement.feature_id} (${ce.entitlement.interval}), balance: ${ce.balance}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ export const addExistingUsagesToCusEnts = ({
|
||||
|
||||
for (const cusEnt of fullCusEnts) {
|
||||
let ent = cusEnt.entitlement;
|
||||
let cusEntKey = `${ent.feature_id}-${ent.interval}`;
|
||||
let cusEntKey = `${ent.feature_id}-${ent.interval}-${ent.interval_count || 1}`;
|
||||
let fromEntities = existingUsages[key].fromEntities;
|
||||
|
||||
if (cusEntKey !== key) {
|
||||
|
||||
@@ -79,7 +79,7 @@ export const cancelCusProductSubscriptions = async ({
|
||||
subIds: cusProduct.subscription_ids,
|
||||
});
|
||||
|
||||
latestSubEnd = stripeSubs[0].current_period_end;
|
||||
latestSubEnd = stripeSubs?.[0]?.current_period_end;
|
||||
}
|
||||
|
||||
const cancelStripeSub = async (subId: string) => {
|
||||
|
||||
@@ -62,6 +62,7 @@ export const initNextResetAt = ({
|
||||
nextResetAtCalculated = getNextEntitlementReset(
|
||||
nextResetAtCalculated || new UTCDate(now),
|
||||
resetInterval,
|
||||
entitlement.interval_count || 1
|
||||
).getTime();
|
||||
|
||||
// If anchorToUnix, align next reset at to anchorToUnix...
|
||||
|
||||
@@ -41,7 +41,7 @@ export const createNewCustomer = async ({
|
||||
const { db, org, env, logger } = req;
|
||||
|
||||
logger.info(
|
||||
`Creating customer: ${customer.email || customer.id}, org: ${org.slug}`,
|
||||
`Creating customer: ${customer.email || customer.id}, org: ${org.slug}`
|
||||
);
|
||||
|
||||
const defaultProds = await ProductService.listDefault({
|
||||
@@ -149,7 +149,10 @@ export const createNewCustomer = async ({
|
||||
}),
|
||||
nextResetAt,
|
||||
anchorToUnix: org.config.anchor_start_of_month
|
||||
? getNextStartOfMonthUnix(BillingInterval.Month)
|
||||
? getNextStartOfMonthUnix({
|
||||
interval: BillingInterval.Month,
|
||||
intervalCount: 1,
|
||||
})
|
||||
: undefined,
|
||||
scenario: AttachScenario.New,
|
||||
logger,
|
||||
|
||||
@@ -87,11 +87,14 @@ export const featuresToObject = ({
|
||||
|
||||
next_reset_at: getEarliestNextResetAt(relatedEnts),
|
||||
interval: relatedEnts.length == 1 ? relatedEnts[0].interval : "multiple",
|
||||
interval_count:
|
||||
relatedEnts.length == 1 ? relatedEnts[0].interval_count : null,
|
||||
overage_allowed: relatedEnts.some((e) => e.overage_allowed),
|
||||
breakdown:
|
||||
!unlimited && relatedEnts.length > 1
|
||||
? relatedEnts.map((e) => ({
|
||||
interval: e.interval!,
|
||||
interval_count: e.interval_count,
|
||||
balance: e.balance,
|
||||
usage: e.usage,
|
||||
included_usage: e.included_usage,
|
||||
|
||||
@@ -141,7 +141,7 @@ export const getRolloverFields = ({
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return cusEnt.rollovers.reduce(
|
||||
return rollovers.reduce(
|
||||
(acc, rollover) => {
|
||||
return {
|
||||
balance: acc.balance + rollover.balance,
|
||||
@@ -204,7 +204,7 @@ export const getCusBalances = async ({
|
||||
const cusProduct = cusEnt.customer_product;
|
||||
const feature = cusEnt.entitlement.feature;
|
||||
const ent: EntitlementWithFeature = cusEnt.entitlement;
|
||||
let key = `${ent.interval || "no-interval"}-${feature.id}`;
|
||||
let key = `${ent.interval || "no-interval"}-${ent.interval_count || 1}-${feature.id}`;
|
||||
|
||||
// 1. Handle boolean
|
||||
let isBoolean = feature.type == FeatureType.Boolean;
|
||||
@@ -239,6 +239,11 @@ export const getCusBalances = async ({
|
||||
unlimited: isBoolean ? undefined : unlimited,
|
||||
interval:
|
||||
isBoolean || unlimited ? undefined : ent.interval || undefined,
|
||||
// interval_count:
|
||||
// isBoolean || unlimited
|
||||
// ? undefined
|
||||
// : ent.interval_count || undefined,
|
||||
|
||||
balance: isBoolean ? undefined : unlimited ? null : 0,
|
||||
total: isBoolean || unlimited ? undefined : 0,
|
||||
adjustment: isBoolean || unlimited ? undefined : 0,
|
||||
@@ -252,6 +257,7 @@ export const getCusBalances = async ({
|
||||
isBoolean || unlimited ? undefined : cusEnt.next_reset_at;
|
||||
data[key].allowance = isBoolean || unlimited ? undefined : 0;
|
||||
data[key].usage_limit = isBoolean || unlimited ? undefined : 0;
|
||||
data[key].interval_count = ent.interval_count || 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,31 +65,6 @@ expireRouter.post("", async (req, res) =>
|
||||
prorate,
|
||||
});
|
||||
|
||||
// console.log(
|
||||
// "CUs products to expire",
|
||||
// cusProductsToExpire.map((c) => c.product.id)
|
||||
// );
|
||||
// throw new Error("test");
|
||||
|
||||
// if (!cusProductsToExpire) {
|
||||
// throw new RecaseError({
|
||||
// code: ErrCode.ProductNotFound,
|
||||
// message: `Product ${product_id} not found for customer ${customer_id}`,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Handle case if there are two products to expire...
|
||||
|
||||
// for (const cusProduct of cusProductsToExpire) {
|
||||
// await expireCusProduct({
|
||||
// req,
|
||||
// cusProduct,
|
||||
// fullCus,
|
||||
// expireImmediately,
|
||||
// prorate,
|
||||
// });
|
||||
// }
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
customer_id: customer_id,
|
||||
|
||||
@@ -2,13 +2,16 @@ import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { cancelFutureProductSchedule } from "@/internal/customers/change-product/scheduleUtils.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import {
|
||||
ACTIVE_STATUSES,
|
||||
CusProductService,
|
||||
} from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import {
|
||||
cancelCusProductSubscriptions,
|
||||
expireAndActivate,
|
||||
fullCusProductToProduct,
|
||||
} from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import { isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import {
|
||||
@@ -21,6 +24,7 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { CusService } from "../CusService.js";
|
||||
import { cusProductToPrices } from "../cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
|
||||
export const removeScheduledProduct = async ({
|
||||
req,
|
||||
@@ -112,14 +116,29 @@ export const expireCusProduct = async ({
|
||||
|
||||
// 1. If main product, can't expire if there's scheduled product
|
||||
let isMain = !cusProduct.product.is_add_on;
|
||||
if (isMain) {
|
||||
let { curScheduledProduct: futureProduct } = getExistingCusProducts({
|
||||
product: cusProduct.product,
|
||||
cusProducts: fullCus.customer_products,
|
||||
internalEntityId: cusProduct.internal_entity_id,
|
||||
});
|
||||
let { curScheduledProduct: futureProduct } = getExistingCusProducts({
|
||||
product: cusProduct.product,
|
||||
cusProducts: fullCus.customer_products,
|
||||
internalEntityId: cusProduct.internal_entity_id,
|
||||
});
|
||||
|
||||
if (futureProduct) {
|
||||
if (isMain) {
|
||||
if (
|
||||
cusProduct.canceled_at &&
|
||||
ACTIVE_STATUSES.includes(cusProduct.status) &&
|
||||
!expireImmediately
|
||||
) {
|
||||
throw new RecaseError({
|
||||
message: `Product ${cusProduct.product.name} is already about to cancel at the end of cycle.`,
|
||||
code: ErrCode.InvalidRequest,
|
||||
statusCode: StatusCodes.BAD_REQUEST,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
futureProduct &&
|
||||
!isFreeProduct(cusProductToPrices({ cusProduct: futureProduct }))
|
||||
) {
|
||||
throw new RecaseError({
|
||||
message: `Please delete scheduled product ${futureProduct.product.name} first`,
|
||||
code: ErrCode.InvalidRequest,
|
||||
@@ -130,6 +149,7 @@ export const expireCusProduct = async ({
|
||||
|
||||
// 2. If expire at cycle end, just cancel subscriptions
|
||||
if (!expireImmediately) {
|
||||
// 1. Check if already canceled
|
||||
await cancelCusProductSubscriptions({
|
||||
cusProduct,
|
||||
org,
|
||||
@@ -175,6 +195,14 @@ export const expireCusProduct = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove scheduled products first...
|
||||
if (futureProduct) {
|
||||
await CusProductService.delete({
|
||||
db,
|
||||
cusProductId: futureProduct.id,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`Expiring current product: ${cusProduct.product.name}`);
|
||||
await expireAndActivate({
|
||||
req,
|
||||
|
||||
@@ -143,13 +143,21 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
delete properties.balance;
|
||||
|
||||
for (const cusEnt of cusEnts) {
|
||||
if (cusEnt.internal_feature_id !== feature!.internal_id!) {
|
||||
continue;
|
||||
}
|
||||
let cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
let deductionIntCount = balance.interval_count || 1;
|
||||
|
||||
let intCountMatch = notNullish(balance.interval_count)
|
||||
? cusEntIntCount === deductionIntCount
|
||||
: true;
|
||||
|
||||
let intMatch = notNullish(balance.interval)
|
||||
? balance.interval === cusEnt.entitlement.interval
|
||||
: true;
|
||||
|
||||
if (
|
||||
notNullish(balance.interval) &&
|
||||
balance.interval !== cusEnt.entitlement.interval
|
||||
cusEnt.internal_feature_id !== feature!.internal_id! ||
|
||||
!intMatch ||
|
||||
!intCountMatch
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -173,6 +181,7 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
toDeduct,
|
||||
properties,
|
||||
interval: balance.interval,
|
||||
intervalCount: balance.interval_count,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -186,12 +195,18 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
// Handle unlimited
|
||||
if (featureDeduction.unlimited) {
|
||||
// Get one active cusEnt and set unlimited to true
|
||||
|
||||
const cusEnt = notNullish(interval)
|
||||
? cusEnts.find(
|
||||
(cusEnt) =>
|
||||
? cusEnts.find((cusEnt) => {
|
||||
let cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
let deductionIntCount = featureDeduction.intervalCount || 1;
|
||||
|
||||
return (
|
||||
cusEnt.internal_feature_id === feature!.internal_id! &&
|
||||
cusEnt.entitlement.interval === interval
|
||||
)
|
||||
cusEnt.entitlement.interval === interval &&
|
||||
cusEntIntCount === deductionIntCount
|
||||
);
|
||||
})
|
||||
: cusEnts.find(
|
||||
(cusEnt) => cusEnt.internal_feature_id === feature!.internal_id!
|
||||
);
|
||||
@@ -218,9 +233,22 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
}
|
||||
|
||||
for (const cusEnt of cusEnts) {
|
||||
let cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
let deductionIntCount = featureDeduction.intervalCount || 1;
|
||||
|
||||
let intCountMatch = notNullish(featureDeduction.intervalCount)
|
||||
? cusEntIntCount === deductionIntCount
|
||||
: true;
|
||||
|
||||
let intMatch = notNullish(featureDeduction.interval)
|
||||
? featureDeduction.interval === cusEnt.entitlement.interval
|
||||
: true;
|
||||
|
||||
if (
|
||||
cusEnt.internal_feature_id !==
|
||||
featureDeduction.feature!.internal_id!
|
||||
featureDeduction.feature!.internal_id! ||
|
||||
!intMatch ||
|
||||
!intCountMatch
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export const handleGetOtp = async (req: any, res: any) =>
|
||||
// Generate API key for the OTP
|
||||
const sandboxKey = await createKey({
|
||||
db,
|
||||
env: env || AppEnv.Sandbox,
|
||||
env: AppEnv.Sandbox,
|
||||
name: `Autumn Key CLI`,
|
||||
orgId: cacheData.orgId,
|
||||
prefix: "am_sk_test",
|
||||
@@ -229,7 +229,7 @@ export const handleGetOtp = async (req: any, res: any) =>
|
||||
|
||||
const prodKey = await createKey({
|
||||
db,
|
||||
env: env || AppEnv.Live,
|
||||
env: AppEnv.Live,
|
||||
name: `Autumn Key CLI`,
|
||||
orgId: cacheData.orgId,
|
||||
prefix: "am_sk_live",
|
||||
|
||||
@@ -27,6 +27,7 @@ export const sendOnboardingEmail = safeResend({
|
||||
to: email,
|
||||
subject: "Anything I can help with?",
|
||||
body: getWelcomeEmailBody(firstName),
|
||||
replyTo: "ayush@useautumn.com",
|
||||
});
|
||||
},
|
||||
action: "send onboarding email",
|
||||
|
||||
@@ -4,25 +4,26 @@ import { ErrCode } from "@/errors/errCodes.js";
|
||||
import { clearOrgCache } from "../orgs/orgUtils/clearOrgCache.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
|
||||
export class FeatureService {
|
||||
static async list({
|
||||
db,
|
||||
orgId,
|
||||
env,
|
||||
showOnlyArchived = false,
|
||||
archived,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
orgId: string;
|
||||
env: AppEnv;
|
||||
showOnlyArchived?: boolean;
|
||||
archived?: boolean;
|
||||
}) {
|
||||
const features = await db.query.features.findMany({
|
||||
where: (features, { eq, and }) =>
|
||||
and(
|
||||
eq(features.org_id, orgId),
|
||||
eq(features.env, env),
|
||||
eq(features.archived, showOnlyArchived)
|
||||
notNullish(archived) ? eq(features.archived, archived!) : undefined
|
||||
),
|
||||
|
||||
orderBy: (features, { desc }) => [desc(features.internal_id)],
|
||||
@@ -208,5 +209,4 @@ export class FeatureService {
|
||||
.delete(features)
|
||||
.where(and(eq(features.org_id, orgId), eq(features.env, env)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,12 +27,15 @@ featureRouter.get("", async (req: any, res: any) =>
|
||||
res,
|
||||
action: "list features",
|
||||
handler: async () => {
|
||||
const includeArchived = req.query.include_archived === "true";
|
||||
let features = await FeatureService.list({
|
||||
db: req.db,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
showOnlyArchived: false,
|
||||
archived: includeArchived ? undefined : false,
|
||||
// showOnlyArchived: includeArchived ? undefined : false,
|
||||
});
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.json({ list: features.map((feature) => toAPIFeature({ feature })) });
|
||||
@@ -119,7 +122,7 @@ featureRouter.post("/:feature_id", async (req: any, res: any) =>
|
||||
}
|
||||
|
||||
if (apiFeature.credit_schema) {
|
||||
newConfig.credit_schema = apiFeature.credit_schema.map((credit) => ({
|
||||
newConfig.schema = apiFeature.credit_schema.map((credit) => ({
|
||||
metered_feature_id: credit.metered_feature_id,
|
||||
credit_amount: credit.credit_cost,
|
||||
}));
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
Organization,
|
||||
ProductItemFeatureType,
|
||||
FeatureType,
|
||||
FullCustomer,
|
||||
UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import { FeatureService } from "./FeatureService.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
@@ -16,6 +18,11 @@ import { generateFeatureDisplay } from "@/external/llm/llmUtils.js";
|
||||
import { ProductService } from "../products/ProductService.js";
|
||||
import { getCreditSystemsFromFeature } from "./creditSystemUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import {
|
||||
cusProductsToCusPrices,
|
||||
cusProductToPrices,
|
||||
} from "../customers/cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { priceToFeature } from "../products/prices/priceUtils/convertPrice.js";
|
||||
|
||||
export const validateFeatureId = (featureId: string) => {
|
||||
if (!featureId.match(/^[a-zA-Z0-9_-]+$/)) {
|
||||
@@ -77,9 +84,9 @@ export const validateCreditSystem = (config: CreditSystemConfig) => {
|
||||
|
||||
// Check if multiple of the same feature
|
||||
const meteredFeatureIds = schema.map(
|
||||
(schemaItem) => schemaItem.metered_feature_id,
|
||||
(schemaItem) => schemaItem.metered_feature_id
|
||||
);
|
||||
console.log("Metered feature ids:", meteredFeatureIds);
|
||||
// console.log("Metered feature ids:", meteredFeatureIds);
|
||||
const uniqueMeteredFeatureIds = Array.from(new Set(meteredFeatureIds));
|
||||
if (meteredFeatureIds.length !== uniqueMeteredFeatureIds.length) {
|
||||
throw new RecaseError({
|
||||
@@ -135,14 +142,14 @@ export const getObjectsUsingFeature = async ({
|
||||
});
|
||||
|
||||
let entitlements = allEnts.filter(
|
||||
(entitlement) => entitlement.internal_feature_id == feature.internal_id,
|
||||
(entitlement) => entitlement.internal_feature_id == feature.internal_id
|
||||
);
|
||||
let linkedEntitlements = allEnts.filter(
|
||||
(entitlement) => entitlement.entity_feature_id == feature.id,
|
||||
(entitlement) => entitlement.entity_feature_id == feature.id
|
||||
);
|
||||
|
||||
let prices = allPrices.filter(
|
||||
(price) => (price.config as any).internal_feature_id == feature.internal_id,
|
||||
(price) => (price.config as any).internal_feature_id == feature.internal_id
|
||||
);
|
||||
|
||||
return { entitlements, prices, creditSystems, linkedEntitlements };
|
||||
@@ -163,7 +170,7 @@ export const runSaveFeatureDisplayTask = async ({
|
||||
try {
|
||||
if (!process.env.ANTHROPIC_API_KEY) {
|
||||
logger.warn(
|
||||
"ANTHROPIC_API_KEY is not set, skipping feature display generation",
|
||||
"ANTHROPIC_API_KEY is not set, skipping feature display generation"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -204,3 +211,29 @@ export const getCusFeatureType = ({ feature }: { feature: Feature }) => {
|
||||
export const isCreditSystem = ({ feature }: { feature: Feature }) => {
|
||||
return feature.type == FeatureType.CreditSystem;
|
||||
};
|
||||
|
||||
export const isPaidContinuousUse = ({
|
||||
feature,
|
||||
fullCus,
|
||||
}: {
|
||||
feature: Feature;
|
||||
fullCus: FullCustomer;
|
||||
}) => {
|
||||
let isContinuous = feature.config?.usage_type == FeatureUsageType.Continuous;
|
||||
|
||||
if (!isContinuous) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let cusPrices = cusProductsToCusPrices({
|
||||
cusProducts: fullCus.customer_products,
|
||||
});
|
||||
let hasPaid = cusPrices.some((cp) => {
|
||||
let config = cp.price.config as UsagePriceConfig;
|
||||
if (config.internal_feature_id == feature.internal_id) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return hasPaid;
|
||||
};
|
||||
|
||||
@@ -9,11 +9,13 @@ import { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
|
||||
export const handleCreateFeature = async (req: any, res: any) => {
|
||||
try {
|
||||
console.log("Trying to create feature");
|
||||
const data = req.body;
|
||||
let { db, orgId, env, logtail: logger } = req;
|
||||
let parsedFeature = validateFeature(data);
|
||||
|
||||
let feature: Feature = {
|
||||
const feature: Feature = {
|
||||
archived: false,
|
||||
internal_id: generateId("fe"),
|
||||
org_id: orgId,
|
||||
created_at: Date.now(),
|
||||
|
||||
@@ -350,6 +350,17 @@ export const handleUpdateFeature = async (req: any, res: any) =>
|
||||
}
|
||||
}
|
||||
|
||||
const newConfig =
|
||||
data.config !== undefined
|
||||
? feature.type == FeatureType.CreditSystem
|
||||
? validateCreditSystem(data.config)
|
||||
: feature.type == FeatureType.Metered
|
||||
? validateMeteredConfig(data.config)
|
||||
: data.config
|
||||
: feature.config;
|
||||
|
||||
console.log(`feature: ${feature.id}, new config:`, newConfig);
|
||||
|
||||
let updatedFeature = await FeatureService.update({
|
||||
db: req.db,
|
||||
id: featureId,
|
||||
@@ -359,15 +370,10 @@ export const handleUpdateFeature = async (req: any, res: any) =>
|
||||
id: data.id !== undefined ? data.id : feature.id,
|
||||
name: data.name !== undefined ? data.name : feature.name,
|
||||
type: data.type !== undefined ? data.type : feature.type,
|
||||
archived: data.archived !== undefined ? data.archived : feature.archived,
|
||||
archived:
|
||||
data.archived !== undefined ? data.archived : feature.archived,
|
||||
|
||||
config: data.config !== undefined
|
||||
? (feature.type == FeatureType.CreditSystem
|
||||
? validateCreditSystem(data.config)
|
||||
: feature.type == FeatureType.Metered
|
||||
? validateMeteredConfig(data.config)
|
||||
: data.config)
|
||||
: feature.config,
|
||||
config: newConfig,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -29,14 +29,14 @@ export const internalFeatureRouter: Router = express.Router();
|
||||
internalFeatureRouter.get("", async (req: any, res: any) => {
|
||||
try {
|
||||
let { showArchived } = req.query;
|
||||
|
||||
|
||||
if (showArchived !== undefined) {
|
||||
// If showArchived is specified, use FeatureService.list with the parameter
|
||||
let features = await FeatureService.list({
|
||||
db: req.db,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
showOnlyArchived: showArchived === 'true',
|
||||
archived: showArchived === "true" ? true : false,
|
||||
});
|
||||
res.status(200).json({ features });
|
||||
} else {
|
||||
@@ -50,8 +50,6 @@ internalFeatureRouter.get("", async (req: any, res: any) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
export const validateFeature = (data: any) => {
|
||||
let featureType = data.type;
|
||||
|
||||
@@ -96,26 +94,23 @@ export const initNewFeature = ({
|
||||
|
||||
internalFeatureRouter.post("", async (req: any, res) => {
|
||||
let data = req.body;
|
||||
|
||||
try {
|
||||
let { db, orgId, env, logtail: logger } = req;
|
||||
let parsedFeature = validateFeature(data);
|
||||
|
||||
let feature: Feature = {
|
||||
const feature: Feature = {
|
||||
archived: false,
|
||||
internal_id: generateId("fe"),
|
||||
org_id: orgId,
|
||||
created_at: Date.now(),
|
||||
env: env,
|
||||
...parsedFeature,
|
||||
};
|
||||
|
||||
let org = await OrgService.getFromReq(req);
|
||||
let insertedData = await FeatureService.insert({
|
||||
db,
|
||||
data: feature,
|
||||
logger,
|
||||
});
|
||||
|
||||
await addTaskToQueue({
|
||||
jobName: JobName.GenerateFeatureDisplay,
|
||||
payload: {
|
||||
@@ -123,7 +118,6 @@ internalFeatureRouter.post("", async (req: any, res) => {
|
||||
org: org,
|
||||
},
|
||||
});
|
||||
|
||||
let insertedFeature =
|
||||
insertedData && insertedData.length > 0 ? insertedData[0] : null;
|
||||
res.status(200).json(insertedFeature);
|
||||
@@ -132,60 +126,63 @@ internalFeatureRouter.post("", async (req: any, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
internalFeatureRouter.get("/data/deletion_text/:feature_id", async (req: any, res) => {
|
||||
try {
|
||||
let { db } = req;
|
||||
let { feature_id } = req.params;
|
||||
|
||||
// Get the feature first
|
||||
let feature = await FeatureService.get({
|
||||
db,
|
||||
id: feature_id,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
});
|
||||
internalFeatureRouter.get(
|
||||
"/data/deletion_text/:feature_id",
|
||||
async (req: any, res) => {
|
||||
try {
|
||||
let { db } = req;
|
||||
let { feature_id } = req.params;
|
||||
|
||||
if (!feature) {
|
||||
return res.status(404).json({ error: "Feature not found" });
|
||||
}
|
||||
// Get the feature first
|
||||
let feature = await FeatureService.get({
|
||||
db,
|
||||
id: feature_id,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
});
|
||||
|
||||
// Use Drizzle query similar to ProductService.getDeletionText
|
||||
let res_data = await db
|
||||
.select({
|
||||
productName: sql<string>`CASE WHEN ROW_NUMBER() OVER (ORDER BY ${products.created_at}) = 1 THEN ${products.name ?? "Product name not found"} ELSE NULL END`,
|
||||
totalCount: sql<number>`COUNT(*) OVER ()`,
|
||||
})
|
||||
.from(products)
|
||||
.innerJoin(
|
||||
entitlements,
|
||||
eq(products.internal_id, entitlements.internal_product_id)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(entitlements.internal_feature_id, feature.internal_id!),
|
||||
eq(products.env, req.env),
|
||||
eq(products.org_id, req.orgId)
|
||||
if (!feature) {
|
||||
return res.status(404).json({ error: "Feature not found" });
|
||||
}
|
||||
|
||||
// Use Drizzle query similar to ProductService.getDeletionText
|
||||
let res_data = await db
|
||||
.select({
|
||||
productName: sql<string>`CASE WHEN ROW_NUMBER() OVER (ORDER BY ${products.created_at}) = 1 THEN ${products.name ?? "Product name not found"} ELSE NULL END`,
|
||||
totalCount: sql<number>`COUNT(*) OVER ()`,
|
||||
})
|
||||
.from(products)
|
||||
.innerJoin(
|
||||
entitlements,
|
||||
eq(products.internal_id, entitlements.internal_product_id)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
.where(
|
||||
and(
|
||||
eq(entitlements.internal_feature_id, feature.internal_id!),
|
||||
eq(products.env, req.env),
|
||||
eq(products.org_id, req.orgId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
// If no products found, return explicit zero count
|
||||
if (!res_data || res_data.length === 0) {
|
||||
res.status(200).json({
|
||||
productName: null,
|
||||
totalCount: 0,
|
||||
});
|
||||
} else {
|
||||
res.status(200).json({
|
||||
productName: res_data[0]?.productName || null,
|
||||
totalCount: Number(res_data[0]?.totalCount) || 0,
|
||||
});
|
||||
// If no products found, return explicit zero count
|
||||
if (!res_data || res_data.length === 0) {
|
||||
res.status(200).json({
|
||||
productName: null,
|
||||
totalCount: 0,
|
||||
});
|
||||
} else {
|
||||
res.status(200).json({
|
||||
productName: res_data[0]?.productName || null,
|
||||
totalCount: Number(res_data[0]?.totalCount) || 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to get feature deletion text", error);
|
||||
res.status(500).send(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to get feature deletion text", error);
|
||||
res.status(500).send(error);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
internalFeatureRouter.post("/:feature_id", handleUpdateFeature);
|
||||
internalFeatureRouter.delete("/:featureId", handleDeleteFeature);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getFeatureNameWithCapital,
|
||||
Organization,
|
||||
Price,
|
||||
ProductItemInterval,
|
||||
UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import {
|
||||
@@ -24,6 +25,8 @@ import {
|
||||
} from "../customers/cusProducts/cusPrices/cusPriceUtils.js";
|
||||
import { getFeatureQuantity } from "../customers/cusProducts/cusProductUtils.js";
|
||||
import { formatAmount } from "@/utils/formatUtils.js";
|
||||
import { getIntervalString } from "../products/productUtils/productResponseUtils/getProductItemDisplay.js";
|
||||
import { billingToItemInterval } from "../products/product-items/itemIntervalUtils.js";
|
||||
|
||||
const getSingularAndPlural = (feature: Feature) => {
|
||||
const singular = getFeatureName({
|
||||
@@ -72,10 +75,16 @@ export const formatFixedPrice = ({
|
||||
const config = price.config as FixedPriceConfig;
|
||||
const amount = formatAmount({ org, amount: config.amount });
|
||||
|
||||
const intervalStr = getIntervalString({
|
||||
interval: billingToItemInterval(config.interval) as ProductItemInterval,
|
||||
intervalCount: config.interval_count || 1,
|
||||
prefix: "",
|
||||
});
|
||||
|
||||
if (config.interval == BillingInterval.OneOff) {
|
||||
return `${amount}`;
|
||||
} else {
|
||||
return `${amount} / ${config.interval}`;
|
||||
return `${amount} / ${intervalStr}`;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ export const invoicesToResponse = ({
|
||||
invoice: i,
|
||||
withItems: false,
|
||||
features: [],
|
||||
}),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -119,10 +119,33 @@ export const getInvoiceItems = async ({
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to get invoice items for invoice ${stripeInvoice.id}`,
|
||||
error,
|
||||
error
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return invoiceItems;
|
||||
};
|
||||
|
||||
export const attachToInvoiceResponse = ({
|
||||
invoice,
|
||||
}: {
|
||||
invoice?: Stripe.Invoice;
|
||||
}) => {
|
||||
if (!invoice) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
status: invoice.status,
|
||||
stripe_id: invoice.id,
|
||||
hosted_invoice_url: invoice.hosted_invoice_url,
|
||||
total: invoice.total,
|
||||
currency: invoice.currency,
|
||||
// id: invoice.id,
|
||||
// stripe_id: invoice.stripe_id,
|
||||
// status: invoice.status,
|
||||
// created_at: invoice.created_at,
|
||||
// updated_at: invoice.updated_at,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -63,6 +63,7 @@ export const getCurContUseItems = async ({
|
||||
const finalProration = getProration({
|
||||
now,
|
||||
interval: price.config.interval!,
|
||||
intervalCount: price.config.interval_count || 1,
|
||||
anchorToUnix: sub.current_period_end * 1000,
|
||||
})!;
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ export const getItemsForCurProduct = async ({
|
||||
const finalProration = getProration({
|
||||
now,
|
||||
interval: price.config.interval!,
|
||||
intervalCount: price.config.interval_count || 1,
|
||||
anchorToUnix: sub.current_period_end * 1000,
|
||||
})!;
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
import { sortPricesByType } from "@/internal/products/prices/priceUtils/sortPriceUtils.js";
|
||||
import { getMergeCusProduct } from "@/internal/customers/attach/attachFunctions/addProductFlow/getMergeCusProduct.js";
|
||||
import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js";
|
||||
import { intervalsDifferent } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
|
||||
export const getDefaultPriceStr = ({
|
||||
org,
|
||||
@@ -83,6 +84,7 @@ export const getProration = ({
|
||||
anchorToUnix,
|
||||
now,
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
proration?: {
|
||||
start: number;
|
||||
@@ -90,6 +92,7 @@ export const getProration = ({
|
||||
};
|
||||
anchorToUnix?: number;
|
||||
interval: BillingInterval;
|
||||
intervalCount: number;
|
||||
now: number;
|
||||
}) => {
|
||||
if (!proration && !anchorToUnix) return undefined;
|
||||
@@ -103,11 +106,16 @@ export const getProration = ({
|
||||
let end = getAlignedIntervalUnix({
|
||||
alignWithUnix: anchorToUnix!,
|
||||
interval,
|
||||
intervalCount,
|
||||
now,
|
||||
alwaysReturn: true,
|
||||
});
|
||||
|
||||
let start = subtractBillingIntervalUnix(end!, interval);
|
||||
let start = subtractBillingIntervalUnix({
|
||||
unixTimestamp: end!,
|
||||
interval,
|
||||
intervalCount,
|
||||
});
|
||||
|
||||
return {
|
||||
start,
|
||||
@@ -121,6 +129,7 @@ export const getItemsForNewProduct = async ({
|
||||
now,
|
||||
proration,
|
||||
interval,
|
||||
intervalCount,
|
||||
anchorToUnix,
|
||||
freeTrial,
|
||||
stripeSubs,
|
||||
@@ -137,6 +146,7 @@ export const getItemsForNewProduct = async ({
|
||||
end: number;
|
||||
};
|
||||
interval?: BillingInterval;
|
||||
intervalCount?: number;
|
||||
anchorToUnix?: number;
|
||||
freeTrial?: FreeTrial | null;
|
||||
stripeSubs?: Stripe.Subscription[];
|
||||
@@ -156,13 +166,27 @@ export const getItemsForNewProduct = async ({
|
||||
const ent = getPriceEntitlement(price, newProduct.entitlements);
|
||||
const billingType = getBillingType(price.config);
|
||||
|
||||
if (interval && price.config.interval !== interval) continue;
|
||||
if (
|
||||
interval &&
|
||||
intervalsDifferent({
|
||||
intervalA: {
|
||||
interval: interval,
|
||||
intervalCount: intervalCount,
|
||||
},
|
||||
intervalB: {
|
||||
interval: price.config.interval,
|
||||
intervalCount: price.config.interval_count,
|
||||
},
|
||||
})
|
||||
)
|
||||
continue;
|
||||
|
||||
const finalProration = getProration({
|
||||
proration,
|
||||
anchorToUnix,
|
||||
now,
|
||||
interval: price.config.interval!,
|
||||
intervalCount: price.config.interval_count!,
|
||||
});
|
||||
|
||||
if (isFixedPrice({ price })) {
|
||||
|
||||
@@ -116,7 +116,7 @@ mainRouter.use(
|
||||
},
|
||||
identify: async (req: any) => {
|
||||
return {
|
||||
customerId: "user_123",
|
||||
customerId: "onboarding_demo_user",
|
||||
customerData: {
|
||||
name: "Demo User",
|
||||
email: "demo@useautumn.com",
|
||||
|
||||
@@ -32,9 +32,11 @@ export const connectStripe = async ({
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
// 1. Check if key is valid
|
||||
|
||||
await checkKeyValid(apiKey);
|
||||
|
||||
let stripe = new Stripe(apiKey);
|
||||
|
||||
let account = await stripe.accounts.retrieve();
|
||||
|
||||
// 2. Disconnect existing webhook endpoints
|
||||
@@ -47,6 +49,7 @@ export const connectStripe = async ({
|
||||
|
||||
// 3. Create webhook endpoint
|
||||
let webhook = await createWebhookEndpoint(apiKey, env, orgId);
|
||||
console.log("MADE IT HERE");
|
||||
|
||||
// 3. Return encrypted
|
||||
if (env === AppEnv.Sandbox) {
|
||||
@@ -96,7 +99,9 @@ export const connectAllStripe = async ({
|
||||
|
||||
// Get default currency from Stripe
|
||||
let stripe = new Stripe(testApiKey);
|
||||
|
||||
let account = await stripe.accounts.retrieve();
|
||||
|
||||
if (nullish(defaultCurrency) && nullish(account.default_currency)) {
|
||||
throw new RecaseError({
|
||||
message: "Default currency not set",
|
||||
@@ -107,7 +112,7 @@ export const connectAllStripe = async ({
|
||||
defaultCurrency = account.default_currency;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error checking stripe keys", error);
|
||||
// console.error("Error checking stripe keys", error);
|
||||
throw new RecaseError({
|
||||
message: error.message || "Invalid Stripe API keys",
|
||||
code: ErrCode.StripeKeyInvalid,
|
||||
|
||||
@@ -77,12 +77,14 @@ orgRouter.post("/stripe", async (req: any, res) => {
|
||||
logger,
|
||||
});
|
||||
|
||||
console.log("Connecting Stripe");
|
||||
await checkKeyValid(testApiKey);
|
||||
await checkKeyValid(liveApiKey);
|
||||
|
||||
// Get default currency from Stripe
|
||||
let stripe = new Stripe(testApiKey);
|
||||
let account = await stripe.accounts.retrieve();
|
||||
|
||||
if (nullish(defaultCurrency) && nullish(account.default_currency)) {
|
||||
throw new RecaseError({
|
||||
message: "Default currency not set",
|
||||
@@ -93,6 +95,14 @@ orgRouter.post("/stripe", async (req: any, res) => {
|
||||
defaultCurrency = account.default_currency;
|
||||
}
|
||||
} catch (error: any) {
|
||||
// if (error.message.includes("rk_***")) {
|
||||
// throw new RecaseError({
|
||||
// message: "Invalid Stripe restricted key. Please add the ",
|
||||
// code: ErrCode.StripeKeyInvalid,
|
||||
// statusCode: 500,
|
||||
// });
|
||||
// }
|
||||
|
||||
throw new RecaseError({
|
||||
message: error.message || "Invalid Stripe API keys",
|
||||
code: ErrCode.StripeKeyInvalid,
|
||||
|
||||
@@ -265,12 +265,14 @@ export class ProductService {
|
||||
orgId,
|
||||
env,
|
||||
version,
|
||||
allowNotFound = false,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
idOrInternalId: string;
|
||||
orgId: string;
|
||||
env: AppEnv;
|
||||
version?: number;
|
||||
allowNotFound?: boolean;
|
||||
}) {
|
||||
let data = (await db.query.products.findFirst({
|
||||
where: and(
|
||||
@@ -298,7 +300,7 @@ export class ProductService {
|
||||
parseFreeTrials({ product: data });
|
||||
|
||||
if (!data) {
|
||||
// return null;
|
||||
if (allowNotFound) return null;
|
||||
throw new RecaseError({
|
||||
message: `Product ${idOrInternalId} not found`,
|
||||
code: ErrCode.ProductNotFound,
|
||||
|
||||
@@ -23,28 +23,34 @@ import { addDays } from "date-fns";
|
||||
import { getBillingType } from "@/internal/products/prices/priceUtils.js";
|
||||
import { features } from "process";
|
||||
|
||||
export const entIntervalToTrialDuration = (interval: EntInterval) => {
|
||||
export const entIntervalToTrialDuration = ({
|
||||
interval,
|
||||
intervalCount,
|
||||
}: {
|
||||
interval: EntInterval;
|
||||
intervalCount: number;
|
||||
}) => {
|
||||
switch (interval) {
|
||||
case EntInterval.Day:
|
||||
return 1;
|
||||
return intervalCount;
|
||||
case EntInterval.Week:
|
||||
return 7;
|
||||
return intervalCount * 7;
|
||||
case EntInterval.Month:
|
||||
return 30;
|
||||
return intervalCount * 30;
|
||||
case EntInterval.Quarter:
|
||||
return 90;
|
||||
return intervalCount * 90;
|
||||
case EntInterval.SemiAnnual:
|
||||
return 180;
|
||||
return intervalCount * 180;
|
||||
case EntInterval.Year:
|
||||
return 365;
|
||||
return intervalCount * 365;
|
||||
case EntInterval.Lifetime:
|
||||
return 1000;
|
||||
return intervalCount * 1000;
|
||||
}
|
||||
};
|
||||
|
||||
export const applyTrialToEntitlement = (
|
||||
entitlement: EntitlementWithFeature,
|
||||
freeTrial: FreeTrial | null,
|
||||
freeTrial: FreeTrial | null
|
||||
) => {
|
||||
if (!freeTrial) return false;
|
||||
|
||||
@@ -54,7 +60,10 @@ export const applyTrialToEntitlement = (
|
||||
if (entitlement.allowance_type === AllowanceType.Unlimited) return false;
|
||||
|
||||
const trialDays = freeTrial.length;
|
||||
const entDays = entIntervalToTrialDuration(entitlement.interval!);
|
||||
const entDays = entIntervalToTrialDuration({
|
||||
interval: entitlement.interval!,
|
||||
intervalCount: entitlement.interval_count || 1,
|
||||
});
|
||||
|
||||
if (entDays && entDays > trialDays) {
|
||||
return true;
|
||||
@@ -65,7 +74,7 @@ export const applyTrialToEntitlement = (
|
||||
|
||||
export const addTrialToNextResetAt = (
|
||||
nextResetAt: number,
|
||||
freeTrial: FreeTrial | null,
|
||||
freeTrial: FreeTrial | null
|
||||
) => {
|
||||
if (!freeTrial) return nextResetAt;
|
||||
|
||||
@@ -76,7 +85,7 @@ export const entsAreSame = (ent1: Entitlement, ent2: Entitlement) => {
|
||||
// 1. Check if they have same internal_feature_id
|
||||
if (ent1.internal_feature_id !== ent2.internal_feature_id) {
|
||||
console.log(
|
||||
`Internal feature ID different: ${ent1.internal_feature_id} !== ${ent2.internal_feature_id}`,
|
||||
`Internal feature ID different: ${ent1.internal_feature_id} !== ${ent2.internal_feature_id}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -84,7 +93,7 @@ export const entsAreSame = (ent1: Entitlement, ent2: Entitlement) => {
|
||||
// 2. Check if they have same allowance type
|
||||
if (ent1.allowance_type !== ent2.allowance_type) {
|
||||
console.log(
|
||||
`Allowance type different: ${ent1.allowance_type} !== ${ent2.allowance_type}`,
|
||||
`Allowance type different: ${ent1.allowance_type} !== ${ent2.allowance_type}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -94,6 +103,10 @@ export const entsAreSame = (ent1: Entitlement, ent2: Entitlement) => {
|
||||
condition: ent1.interval !== ent2.interval,
|
||||
message: `Interval different: ${ent1.interval} !== ${ent2.interval}`,
|
||||
},
|
||||
intervalCount: {
|
||||
condition: ent1.interval_count !== ent2.interval_count,
|
||||
message: `Interval count different: ${ent1.interval_count} !== ${ent2.interval_count}`,
|
||||
},
|
||||
allowance: {
|
||||
condition:
|
||||
ent1.allowance_type !== AllowanceType.Unlimited &&
|
||||
@@ -113,7 +126,8 @@ export const entsAreSame = (ent1: Entitlement, ent2: Entitlement) => {
|
||||
message: `Usage limit different: ${ent1.usage_limit} !== ${ent2.usage_limit}`,
|
||||
},
|
||||
rollover: {
|
||||
condition: JSON.stringify(ent1.rollover) !== JSON.stringify(ent2.rollover),
|
||||
condition:
|
||||
JSON.stringify(ent1.rollover) !== JSON.stringify(ent2.rollover),
|
||||
message: `Rollover different: ${ent1.rollover} !== ${ent2.rollover}`,
|
||||
},
|
||||
};
|
||||
@@ -136,7 +150,7 @@ export const entsAreSame = (ent1: Entitlement, ent2: Entitlement) => {
|
||||
export const getEntRelatedPrice = (
|
||||
entitlement: Entitlement,
|
||||
prices: Price[],
|
||||
allowFeatureMatch = false,
|
||||
allowFeatureMatch = false
|
||||
) => {
|
||||
return prices.find((price) => {
|
||||
if (price.config?.type === PriceType.Fixed) {
|
||||
@@ -158,10 +172,10 @@ export const getEntRelatedPrice = (
|
||||
|
||||
export const getEntitlementsForProduct = (
|
||||
product: FullProduct,
|
||||
entitlements: EntitlementWithFeature[],
|
||||
entitlements: EntitlementWithFeature[]
|
||||
) => {
|
||||
return entitlements.filter(
|
||||
(ent) => ent.internal_product_id === product.internal_id,
|
||||
(ent) => ent.internal_product_id === product.internal_id
|
||||
);
|
||||
};
|
||||
|
||||
@@ -174,7 +188,7 @@ export const getEntsWithFeature = ({
|
||||
}) => {
|
||||
return ents.map((ent) => {
|
||||
let feature = features.find(
|
||||
(f) => f.internal_id === ent.internal_feature_id,
|
||||
(f) => f.internal_id === ent.internal_feature_id
|
||||
);
|
||||
if (!feature) {
|
||||
throw new RecaseError({
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { sortFullProducts } from "../productUtils/sortProductUtils.js";
|
||||
import { toPricecnProduct } from "../pricecn/pricecnUtils.js";
|
||||
import { getProductResponse } from "../productUtils/productResponseUtils/getProductResponse.js";
|
||||
import { getCusWithCache } from "@/internal/customers/cusCache/getCusWithCache.js";
|
||||
|
||||
@@ -38,14 +36,6 @@ export const handleListProductsBeta = async (req: any, res: any) =>
|
||||
env,
|
||||
logger: req.logger,
|
||||
});
|
||||
// return await CusService.getFull({
|
||||
// db,
|
||||
// orgId: org.id,
|
||||
// env,
|
||||
// idOrInternalId: customerId as string,
|
||||
// allowNotFound: true,
|
||||
// entityId,
|
||||
// });
|
||||
})(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { addTaskToQueue } from "@/queue/queueUtils.js";
|
||||
import { JobName } from "@/queue/JobName.js";
|
||||
import { productsAreSame } from "../../productUtils/compareProductUtils.js";
|
||||
import { initProductInStripe } from "../../productUtils.js";
|
||||
import { handleCreateProduct } from "../handleCreateProduct.js";
|
||||
|
||||
export const handleUpdateProductV2 = async (req: any, res: any) =>
|
||||
routeHandler({
|
||||
@@ -25,7 +26,7 @@ export const handleUpdateProductV2 = async (req: any, res: any) =>
|
||||
action: "Update product",
|
||||
handler: async () => {
|
||||
const { productId } = req.params;
|
||||
const { version } = req.query;
|
||||
const { version, upsert, disable_version } = req.query;
|
||||
const { orgId, env, logger, db } = req;
|
||||
|
||||
const [features, org, fullProduct, rewardPrograms] = await Promise.all([
|
||||
@@ -37,6 +38,7 @@ export const handleUpdateProductV2 = async (req: any, res: any) =>
|
||||
orgId,
|
||||
env,
|
||||
version: version ? parseInt(version) : undefined,
|
||||
allowNotFound: upsert == "true",
|
||||
}),
|
||||
RewardProgramService.getByProductId({
|
||||
db,
|
||||
@@ -47,6 +49,12 @@ export const handleUpdateProductV2 = async (req: any, res: any) =>
|
||||
]);
|
||||
|
||||
if (!fullProduct) {
|
||||
console.log("Upserting:", upsert);
|
||||
if (upsert == "true") {
|
||||
await handleCreateProduct(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new RecaseError({
|
||||
message: "Product not found",
|
||||
code: ErrCode.ProductNotFound,
|
||||
@@ -75,6 +83,15 @@ export const handleUpdateProductV2 = async (req: any, res: any) =>
|
||||
let itemsExist = notNullish(req.body.items);
|
||||
|
||||
if (cusProductExists && itemsExist) {
|
||||
if (disable_version == "true") {
|
||||
throw new RecaseError({
|
||||
message: "Cannot auto save product as there are existing customers",
|
||||
code: ErrCode.InvalidRequest,
|
||||
statusCode: 400,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { itemsSame, freeTrialsSame } = productsAreSame({
|
||||
newProductV2: req.body,
|
||||
curProductV1: fullProduct,
|
||||
|
||||
@@ -42,7 +42,13 @@ const productDetailsSame = (prod1: Product, prod2: UpdateProduct) => {
|
||||
if (notNullish(prod2.is_default) && prod1.is_default != prod2.is_default) {
|
||||
return false;
|
||||
}
|
||||
console.log("prod1.archived", prod1.archived, prod2.archived, notNullish(prod2.archived), prod1.archived !== prod2.archived);
|
||||
// console.log(
|
||||
// "prod1.archived",
|
||||
// prod1.archived,
|
||||
// prod2.archived,
|
||||
// notNullish(prod2.archived),
|
||||
// prod1.archived !== prod2.archived
|
||||
// );
|
||||
if (notNullish(prod2.archived) && prod1.archived !== prod2.archived) {
|
||||
return false;
|
||||
}
|
||||
@@ -71,7 +77,7 @@ const updateStripeProductNames = async ({
|
||||
});
|
||||
let stripeProdId = curProduct.processor?.id;
|
||||
|
||||
if (!stripeProdId) {
|
||||
if (!stripeProdId || !newName) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ export const handleVersionProductV2 = async ({
|
||||
orgId: org.id,
|
||||
env: latestProduct.env as AppEnv,
|
||||
processor: latestProduct.processor,
|
||||
baseVariantId: latestProduct.base_variant_id,
|
||||
});
|
||||
|
||||
// Validate product items...
|
||||
@@ -114,12 +115,16 @@ export const handleVersionProductV2 = async ({
|
||||
});
|
||||
}
|
||||
|
||||
await addTaskToQueue({
|
||||
jobName: JobName.DetectBaseVariant,
|
||||
payload: {
|
||||
curProduct: newProduct,
|
||||
},
|
||||
});
|
||||
// await addTaskToQueue({
|
||||
// jobName: JobName.DetectBaseVariant,
|
||||
// payload: {
|
||||
// curProduct: {
|
||||
// ...newProduct,
|
||||
// // prices: customPrices,
|
||||
// // entitlements: getEntsWithFeature({ ents: customEnts, features }),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
await initProductInStripe({
|
||||
db,
|
||||
|
||||
@@ -27,14 +27,16 @@ productRouter.get("/data", async (req: any, res) => {
|
||||
try {
|
||||
let { db } = req;
|
||||
|
||||
const allVersions = req.query.all_versions === "true";
|
||||
|
||||
const [products, features, org, coupons, rewardPrograms] =
|
||||
await Promise.all([
|
||||
ProductService.listFull({
|
||||
db,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
returnAll: true,
|
||||
archived: false,
|
||||
returnAll: allVersions,
|
||||
}),
|
||||
FeatureService.getFromReq(req),
|
||||
OrgService.getFromReq(req),
|
||||
@@ -77,7 +79,7 @@ productRouter.post("/data", async (req: any, res) => {
|
||||
db,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
returnAll: true,
|
||||
// returnAll: true,
|
||||
archived: showArchived,
|
||||
}),
|
||||
FeatureService.getFromReq(req),
|
||||
@@ -116,8 +118,17 @@ productRouter.get("/counts", async (req: any, res) => {
|
||||
// returnAll: true,
|
||||
});
|
||||
|
||||
const latestVersion = req.query.latest_version === "true";
|
||||
|
||||
let counts = await Promise.all(
|
||||
products.map(async (product) => {
|
||||
if (latestVersion) {
|
||||
return CusProdReadService.getCounts({
|
||||
db,
|
||||
internalProductId: product.internal_id,
|
||||
});
|
||||
}
|
||||
|
||||
return CusProdReadService.getCountsForAllVersions({
|
||||
db,
|
||||
productId: product.id,
|
||||
|
||||
@@ -22,7 +22,7 @@ import { isPriceItem } from "../product-items/productItemUtils/getItemType.js";
|
||||
import { isFeaturePriceItem } from "../product-items/productItemUtils/getItemType.js";
|
||||
import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { isProductUpgrade } from "../productUtils.js";
|
||||
import { getFirstInterval } from "../prices/priceUtils/priceIntervalUtils.js";
|
||||
import { getLargestInterval } from "../prices/priceUtils/priceIntervalUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { getFreeTrialAfterFingerprint } from "../free-trials/freeTrialUtils.js";
|
||||
|
||||
@@ -388,7 +388,7 @@ export const toPricecnProduct = async ({
|
||||
let baseVariant = null;
|
||||
if (fullProduct.base_variant_id) {
|
||||
baseVariant = otherProducts.find(
|
||||
(p) => p.id == fullProduct.base_variant_id,
|
||||
(p) => p.id == fullProduct.base_variant_id
|
||||
);
|
||||
}
|
||||
|
||||
@@ -402,7 +402,8 @@ export const toPricecnProduct = async ({
|
||||
baseVariant ||
|
||||
otherProducts.some((p) => p.base_variant_id == product.id)
|
||||
) {
|
||||
intervalGroup = getFirstInterval({ prices: fullProduct.prices });
|
||||
let intervalSet = getLargestInterval({ prices: fullProduct.prices });
|
||||
intervalGroup = intervalSet?.interval;
|
||||
}
|
||||
|
||||
let trialAvailable = false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user