From 9c481e5e83aa95f10295de4d4246950faa212f25 Mon Sep 17 00:00:00 2001 From: rafaelsideguide <150964962+rafaelsideguide@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:05:53 -0300 Subject: [PATCH 1/4] [Feat] Coupon system WIP. Idea for solving #57 --- .../src/services/billing/credit_billing.ts | 208 ++++++++++-------- 1 file changed, 113 insertions(+), 95 deletions(-) diff --git a/apps/api/src/services/billing/credit_billing.ts b/apps/api/src/services/billing/credit_billing.ts index bf5be60..7f6f9b8 100644 --- a/apps/api/src/services/billing/credit_billing.ts +++ b/apps/api/src/services/billing/credit_billing.ts @@ -41,14 +41,30 @@ export async function supaBillTeam(team_id: string, credits: number) { return { success: true, credit_usage }; } - // 2. add the credits to the credits_usage + // 2. Check for available coupons + const { data: coupons } = await supabase_service + .from("coupons") + .select("credits") + .eq("team_id", team_id) + .eq("status", "active"); + + let couponValue = 0; + if (coupons && coupons.length > 0) { + couponValue = coupons[0].credits; // Assuming only one active coupon can be used at a time + console.log(`Applying coupon of ${couponValue} credits`); + } + + // Calculate final credits used after applying coupon + const finalCreditsUsed = Math.max(0, credits - couponValue); + + // 3. Log the credit usage const { data: credit_usage } = await supabase_service .from("credit_usage") .insert([ { team_id, - subscription_id: subscription.id, - credits_used: credits, + subscription_id: subscription ? subscription.id : null, + credits_used: finalCreditsUsed, created_at: new Date(), }, ]) @@ -65,61 +81,32 @@ export async function supaCheckTeamCredits(team_id: string, credits: number) { if (team_id === "preview") { return { success: true, message: "Preview team, no credits used" }; } - // 1. Retrieve the team's active subscription based on the team_id. - const { data: subscription, error: subscriptionError } = - await supabase_service - .from("subscriptions") - .select("id, price_id, current_period_start, current_period_end") - .eq("team_id", team_id) - .eq("status", "active") - .single(); - if (subscriptionError || !subscription) { - const { data: creditUsages, error: creditUsageError } = - await supabase_service - .from("credit_usage") - .select("credits_used") - .is("subscription_id", null) - .eq("team_id", team_id); - // .gte("created_at", subscription.current_period_start) - // .lte("created_at", subscription.current_period_end); - - if (creditUsageError) { - throw new Error( - `Failed to retrieve credit usage for subscription_id: ${subscription.id}` - ); - } - - const totalCreditsUsed = creditUsages.reduce( - (acc, usage) => acc + usage.credits_used, - 0 - ); - - console.log("totalCreditsUsed", totalCreditsUsed); - // 5. Compare the total credits used with the credits allowed by the plan. - if (totalCreditsUsed + credits > FREE_CREDITS) { - return { - success: false, - message: "Insufficient credits, please upgrade!", - }; - } - return { success: true, message: "Sufficient credits available" }; - } - - // 2. Get the price_id from the subscription. - const { data: price, error: priceError } = await supabase_service - .from("prices") - .select("credits") - .eq("id", subscription.price_id) + // Retrieve the team's active subscription + const { data: subscription, error: subscriptionError } = await supabase_service + .from("subscriptions") + .select("id, price_id, current_period_start, current_period_end") + .eq("team_id", team_id) + .eq("status", "active") .single(); - if (priceError) { - throw new Error( - `Failed to retrieve price for price_id: ${subscription.price_id}` - ); + if (subscriptionError || !subscription) { + return { success: false, message: "No active subscription found" }; } - // 4. Calculate the total credits used by the team within the current billing period. + // Check for available coupons + const { data: coupons } = await supabase_service + .from("coupons") + .select("credits") + .eq("team_id", team_id) + .eq("status", "active"); + + let couponValue = 0; + if (coupons && coupons.length > 0) { + couponValue = coupons[0].credits; + } + + // Calculate the total credits used by the team within the current billing period const { data: creditUsages, error: creditUsageError } = await supabase_service .from("credit_usage") .select("credits_used") @@ -128,18 +115,27 @@ export async function supaCheckTeamCredits(team_id: string, credits: number) { .lte("created_at", subscription.current_period_end); if (creditUsageError) { - throw new Error( - `Failed to retrieve credit usage for subscription_id: ${subscription.id}` - ); + throw new Error(`Failed to retrieve credit usage for subscription_id: ${subscription.id}`); } - const totalCreditsUsed = creditUsages.reduce( - (acc, usage) => acc + usage.credits_used, - 0 - ); + const totalCreditsUsed = creditUsages.reduce((acc, usage) => acc + usage.credits_used, 0); - // 5. Compare the total credits used with the credits allowed by the plan. - if (totalCreditsUsed + credits > price.credits) { + // Adjust total credits used by subtracting coupon value + const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponValue); + + // Get the price details + const { data: price, error: priceError } = await supabase_service + .from("prices") + .select("credits") + .eq("id", subscription.price_id) + .single(); + + if (priceError) { + throw new Error(`Failed to retrieve price for price_id: ${subscription.price_id}`); + } + + // Compare the adjusted total credits used with the credits allowed by the plan + if (adjustedCreditsUsed + credits > price.credits) { return { success: false, message: "Insufficient credits, please upgrade!" }; } @@ -159,7 +155,17 @@ export async function countCreditsAndRemainingForCurrentBillingPeriod( .single(); if (subscriptionError || !subscription) { - // throw new Error(`Failed to retrieve subscription for team_id: ${team_id}`); + // Check for available coupons even if there's no subscription + const { data: coupons } = await supabase_service + .from("coupons") + .select("value") + .eq("team_id", team_id) + .eq("status", "active"); + + let couponValue = 0; + if (coupons && coupons.length > 0) { + couponValue = coupons[0].value; + } // Free const { data: creditUsages, error: creditUsageError } = @@ -168,13 +174,9 @@ export async function countCreditsAndRemainingForCurrentBillingPeriod( .select("credits_used") .is("subscription_id", null) .eq("team_id", team_id); - // .gte("created_at", subscription.current_period_start) - // .lte("created_at", subscription.current_period_end); if (creditUsageError || !creditUsages) { - throw new Error( - `Failed to retrieve credit usage for subscription_id: ${subscription.id}` - ); + throw new Error(`Failed to retrieve credit usage for team_id: ${team_id}`); } const totalCreditsUsed = creditUsages.reduce( @@ -182,46 +184,62 @@ export async function countCreditsAndRemainingForCurrentBillingPeriod( 0 ); + // Adjust total credits used by subtracting coupon value + const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponValue); + // 4. Calculate remaining credits. - const remainingCredits = FREE_CREDITS - totalCreditsUsed; + const remainingCredits = FREE_CREDITS - adjustedCreditsUsed; - return { totalCreditsUsed, remainingCredits, totalCredits: FREE_CREDITS }; + return { totalCreditsUsed: adjustedCreditsUsed, remainingCredits, totalCredits: FREE_CREDITS }; } - // 2. Get the price_id from the subscription to retrieve the total credits available. - const { data: price, error: priceError } = await supabase_service - .from("prices") - .select("credits") - .eq("id", subscription.price_id) - .single(); + // If there is an active subscription + const { data: coupons } = await supabase_service + .from("coupons") + .select("credits") + .eq("team_id", team_id) + .eq("status", "active"); - if (priceError || !price) { - throw new Error( - `Failed to retrieve price for price_id: ${subscription.price_id}` - ); + let couponValue = 0; + if (coupons && coupons.length > 0) { + couponValue = coupons[0].credits; } - // 3. Calculate the total credits used by the team within the current billing period. const { data: creditUsages, error: creditUsageError } = await supabase_service - .from("credit_usage") - .select("credits_used") - .eq("subscription_id", subscription.id) - .gte("created_at", subscription.current_period_start) - .lte("created_at", subscription.current_period_end); + .from("credit_usage") + .select("credits_used") + .eq("subscription_id", subscription.id) + .gte("created_at", subscription.current_period_start) + .lte("created_at", subscription.current_period_end); if (creditUsageError || !creditUsages) { - throw new Error( - `Failed to retrieve credit usage for subscription_id: ${subscription.id}` - ); + throw new Error(`Failed to retrieve credit usage for subscription_id: ${subscription.id}`); } const totalCreditsUsed = creditUsages.reduce( - (acc, usage) => acc + usage.credits_used, - 0 + (acc, usage) => acc + usage.credits_used, + 0 ); - // 4. Calculate remaining credits. - const remainingCredits = price.credits - totalCreditsUsed; + // Adjust total credits used by subtracting coupon value + const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponValue); - return { totalCreditsUsed, remainingCredits, totalCredits: price.credits }; -} + const { data: price, error: priceError } = await supabase_service + .from("prices") + .select("credits") + .eq("id", subscription.price_id) + .single(); + + if (priceError || !price) { + throw new Error(`Failed to retrieve price for price_id: ${subscription.price_id}`); + } + + // Calculate remaining credits. + const remainingCredits = price.credits - adjustedCreditsUsed; + + return { + totalCreditsUsed: adjustedCreditsUsed, + remainingCredits, + totalCredits: price.credits + }; + } From 06675d1fe329a546ec4d8e395e8f39e60cd60b28 Mon Sep 17 00:00:00 2001 From: rafaelsideguide <150964962+rafaelsideguide@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:42:49 -0300 Subject: [PATCH 2/4] almost finished --- apps/api/src/controllers/scrape.ts | 24 +- apps/api/src/controllers/search.ts | 4 +- apps/api/src/main/runWebScraper.ts | 4 +- .../src/services/billing/credit_billing.ts | 217 ++++++++++-------- 4 files changed, 139 insertions(+), 110 deletions(-) diff --git a/apps/api/src/controllers/scrape.ts b/apps/api/src/controllers/scrape.ts index cfe35b5..eebdcb4 100644 --- a/apps/api/src/controllers/scrape.ts +++ b/apps/api/src/controllers/scrape.ts @@ -46,18 +46,18 @@ export async function scrapeHelper( return { success: true, error: "No page found", returnCode: 200 }; } - const { success, credit_usage } = await billTeam( - team_id, - filteredDocs.length - ); - if (!success) { - return { - success: false, - error: - "Failed to bill team. Insufficient credits or subscription not found.", - returnCode: 402, - }; - } + const billingResult = await billTeam( + team_id, + filteredDocs.length + ); + if (!billingResult.success) { + return { + success: false, + error: + "Failed to bill team. Insufficient credits or subscription not found.", + returnCode: 402, + }; + } return { success: true, diff --git a/apps/api/src/controllers/search.ts b/apps/api/src/controllers/search.ts index bc81f69..5c2cf80 100644 --- a/apps/api/src/controllers/search.ts +++ b/apps/api/src/controllers/search.ts @@ -83,11 +83,11 @@ export async function searchHelper( return { success: true, error: "No page found", returnCode: 200 }; } - const { success, credit_usage } = await billTeam( + const billingResult = await billTeam( team_id, filteredDocs.length ); - if (!success) { + if (!billingResult.success) { return { success: false, error: diff --git a/apps/api/src/main/runWebScraper.ts b/apps/api/src/main/runWebScraper.ts index 0e44310..892a2a3 100644 --- a/apps/api/src/main/runWebScraper.ts +++ b/apps/api/src/main/runWebScraper.ts @@ -89,12 +89,12 @@ export async function runWebScraper({ : docs.filter((doc) => doc.content.trim().length > 0); - const { success, credit_usage } = await billTeam( + const billingResult = await billTeam( team_id, filteredDocs.length ); - if (!success) { + if (!billingResult.success) { // throw new Error("Failed to bill team, no subscription was found"); return { success: false, diff --git a/apps/api/src/services/billing/credit_billing.ts b/apps/api/src/services/billing/credit_billing.ts index 7f6f9b8..e6a05d7 100644 --- a/apps/api/src/services/billing/credit_billing.ts +++ b/apps/api/src/services/billing/credit_billing.ts @@ -18,7 +18,6 @@ export async function supaBillTeam(team_id: string, credits: number) { // created_at: The timestamp of the API usage. // 1. get the subscription - const { data: subscription } = await supabase_service .from("subscriptions") .select("*") @@ -26,51 +25,81 @@ export async function supaBillTeam(team_id: string, credits: number) { .eq("status", "active") .single(); - if (!subscription) { - const { data: credit_usage } = await supabase_service - .from("credit_usage") - .insert([ - { - team_id, - credits_used: credits, - created_at: new Date(), - }, - ]) - .select(); - - return { success: true, credit_usage }; - } - // 2. Check for available coupons const { data: coupons } = await supabase_service .from("coupons") - .select("credits") + .select("id, credits") .eq("team_id", team_id) .eq("status", "active"); - let couponValue = 0; + let couponCredits = 0; if (coupons && coupons.length > 0) { - couponValue = coupons[0].credits; // Assuming only one active coupon can be used at a time - console.log(`Applying coupon of ${couponValue} credits`); + couponCredits = coupons.reduce((total, coupon) => total + coupon.credits, 0); } - // Calculate final credits used after applying coupon - const finalCreditsUsed = Math.max(0, credits - couponValue); + let sortedCoupons = coupons.sort((a, b) => b.credits - a.credits); - // 3. Log the credit usage - const { data: credit_usage } = await supabase_service - .from("credit_usage") - .insert([ - { - team_id, - subscription_id: subscription ? subscription.id : null, - credits_used: finalCreditsUsed, - created_at: new Date(), - }, - ]) - .select(); + // using coupon credits: + if (couponCredits > 0) { + // using only coupon credits: + if (couponCredits > credits && !subscription) { + // remove credits from coupon credits + let usedCredits = credits; + while (usedCredits > 0) { + // update coupons + if (sortedCoupons[0].credits < usedCredits) { + usedCredits = usedCredits - sortedCoupons[0].credits; + // update coupon credits + await supabase_service + .from("coupons") + .update({ + credits: 0 + }) + .eq("id", sortedCoupons[0].id); + sortedCoupons.shift(); - return { success: true, credit_usage }; + } else { + // update coupon credits + await supabase_service + .from("coupons") + .update({ + credits: sortedCoupons[0].credits - usedCredits + }) + .eq("id", sortedCoupons[0].id); + usedCredits = 0; + } + } + + return await createCreditUsage({ team_id, credits: 0 }); + + // @nick ??? HOW TO HANDLE THIS CASE? + // not enough coupon credits but no subscription + } else if (!subscription) { + return await createCreditUsage({ team_id, credits }); + } + + // using coupon + subscription credits: + if (credits > couponCredits) { + // update coupon credits + for (let i = 0; i < sortedCoupons.length; i++) { + await supabase_service + .from("coupons") + .update({ + credits: 0 + }) + .eq("id", sortedCoupons[i].id); + } + const usedCredits = credits - couponCredits; + return await createCreditUsage({ team_id, subscription_id: subscription.id, credits: usedCredits }); + } + } + + // not using coupon credits + if (!subscription) { + return await createCreditUsage({ team_id, credits }); + } + + return await createCreditUsage({ team_id, subscription_id: subscription.id, credits }); } export async function checkTeamCredits(team_id: string, credits: number) { @@ -90,10 +119,6 @@ export async function supaCheckTeamCredits(team_id: string, credits: number) { .eq("status", "active") .single(); - if (subscriptionError || !subscription) { - return { success: false, message: "No active subscription found" }; - } - // Check for available coupons const { data: coupons } = await supabase_service .from("coupons") @@ -101,9 +126,18 @@ export async function supaCheckTeamCredits(team_id: string, credits: number) { .eq("team_id", team_id) .eq("status", "active"); - let couponValue = 0; + let couponCredits = 0; if (coupons && coupons.length > 0) { - couponValue = coupons[0].credits; + couponCredits = coupons.reduce((total, coupon) => total + coupon.credits, 0); + } + + if (subscriptionError || (!subscription && couponCredits <= 0)) { + return { success: false, message: "No active subscription or coupons found" }; + } + + // If there is no active subscription but there are available coupons + if (couponCredits >= credits) { + return { success: true, message: "Sufficient credits available" }; } // Calculate the total credits used by the team within the current billing period @@ -121,7 +155,7 @@ export async function supaCheckTeamCredits(team_id: string, credits: number) { const totalCreditsUsed = creditUsages.reduce((acc, usage) => acc + usage.credits_used, 0); // Adjust total credits used by subtracting coupon value - const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponValue); + const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponCredits); // Get the price details const { data: price, error: priceError } = await supabase_service @@ -154,19 +188,18 @@ export async function countCreditsAndRemainingForCurrentBillingPeriod( .eq("team_id", team_id) .single(); + const { data: coupons } = await supabase_service + .from("coupons") + .select("credits") + .eq("team_id", team_id) + .eq("status", "active"); + + let couponCredits = 0; + if (coupons && coupons.length > 0) { + couponCredits = coupons.reduce((total, coupon) => total + coupon.credits, 0); + } + if (subscriptionError || !subscription) { - // Check for available coupons even if there's no subscription - const { data: coupons } = await supabase_service - .from("coupons") - .select("value") - .eq("team_id", team_id) - .eq("status", "active"); - - let couponValue = 0; - if (coupons && coupons.length > 0) { - couponValue = coupons[0].value; - } - // Free const { data: creditUsages, error: creditUsageError } = await supabase_service @@ -184,62 +217,58 @@ export async function countCreditsAndRemainingForCurrentBillingPeriod( 0 ); - // Adjust total credits used by subtracting coupon value - const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponValue); - - // 4. Calculate remaining credits. - const remainingCredits = FREE_CREDITS - adjustedCreditsUsed; - - return { totalCreditsUsed: adjustedCreditsUsed, remainingCredits, totalCredits: FREE_CREDITS }; - } - - // If there is an active subscription - const { data: coupons } = await supabase_service - .from("coupons") - .select("credits") - .eq("team_id", team_id) - .eq("status", "active"); - - let couponValue = 0; - if (coupons && coupons.length > 0) { - couponValue = coupons[0].credits; + const remainingCredits = FREE_CREDITS + couponCredits - totalCreditsUsed; + return { totalCreditsUsed: totalCreditsUsed, remainingCredits, totalCredits: FREE_CREDITS + couponCredits }; } const { data: creditUsages, error: creditUsageError } = await supabase_service - .from("credit_usage") - .select("credits_used") - .eq("subscription_id", subscription.id) - .gte("created_at", subscription.current_period_start) - .lte("created_at", subscription.current_period_end); + .from("credit_usage") + .select("credits_used") + .eq("subscription_id", subscription.id) + .gte("created_at", subscription.current_period_start) + .lte("created_at", subscription.current_period_end); if (creditUsageError || !creditUsages) { - throw new Error(`Failed to retrieve credit usage for subscription_id: ${subscription.id}`); + throw new Error(`Failed to retrieve credit usage for subscription_id: ${subscription.id}`); } - const totalCreditsUsed = creditUsages.reduce( - (acc, usage) => acc + usage.credits_used, - 0 - ); + const totalCreditsUsed = creditUsages.reduce((acc, usage) => acc + usage.credits_used, 0); // Adjust total credits used by subtracting coupon value - const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponValue); + // const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponCredits); const { data: price, error: priceError } = await supabase_service - .from("prices") - .select("credits") - .eq("id", subscription.price_id) - .single(); + .from("prices") + .select("credits") + .eq("id", subscription.price_id) + .single(); if (priceError || !price) { - throw new Error(`Failed to retrieve price for price_id: ${subscription.price_id}`); + throw new Error(`Failed to retrieve price for price_id: ${subscription.price_id}`); } // Calculate remaining credits. - const remainingCredits = price.credits - adjustedCreditsUsed; + const remainingCredits = price.credits + couponCredits - totalCreditsUsed; return { - totalCreditsUsed: adjustedCreditsUsed, - remainingCredits, - totalCredits: price.credits + totalCreditsUsed, + remainingCredits, + totalCredits: price.credits }; - } +} + +async function createCreditUsage({ team_id, subscription_id, credits }: { team_id: string, subscription_id?: string, credits: number }) { + const { data: credit_usage } = await supabase_service + .from("credit_usage") + .insert([ + { + team_id, + credits_used: credits, + subscription_id: subscription_id || null, + created_at: new Date(), + }, + ]) + .select(); + + return { success: true, credit_usage }; +} \ No newline at end of file From 1f48998970b4817a627a64df6ee8d49928ffdcbf Mon Sep 17 00:00:00 2001 From: rafaelsideguide <150964962+rafaelsideguide@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:27:31 -0300 Subject: [PATCH 3/4] done --- .../src/services/billing/credit_billing.ts | 134 +++++++++++++----- 1 file changed, 100 insertions(+), 34 deletions(-) diff --git a/apps/api/src/services/billing/credit_billing.ts b/apps/api/src/services/billing/credit_billing.ts index e6a05d7..50a77c4 100644 --- a/apps/api/src/services/billing/credit_billing.ts +++ b/apps/api/src/services/billing/credit_billing.ts @@ -38,12 +38,73 @@ export async function supaBillTeam(team_id: string, credits: number) { } let sortedCoupons = coupons.sort((a, b) => b.credits - a.credits); - // using coupon credits: if (couponCredits > 0) { - // using only coupon credits: - if (couponCredits > credits && !subscription) { - // remove credits from coupon credits + if (!subscription) { + // using only coupon credits: + if (couponCredits >= credits) { + // remove credits from coupon credits + let usedCredits = credits; + while (usedCredits > 0) { + // update coupons + if (sortedCoupons[0].credits < usedCredits) { + usedCredits = usedCredits - sortedCoupons[0].credits; + // update coupon credits + await supabase_service + .from("coupons") + .update({ + credits: 0 + }) + .eq("id", sortedCoupons[0].id); + sortedCoupons.shift(); + + } else { + // update coupon credits + await supabase_service + .from("coupons") + .update({ + credits: sortedCoupons[0].credits - usedCredits + }) + .eq("id", sortedCoupons[0].id); + usedCredits = 0; + } + } + + return await createCreditUsage({ team_id, credits: 0 }); + + // not enough coupon credits and no subscription + } else { + // update coupon credits + const usedCredits = credits - couponCredits; + for (let i = 0; i < sortedCoupons.length; i++) { + await supabase_service + .from("coupons") + .update({ + credits: 0 + }) + .eq("id", sortedCoupons[i].id); + } + + return await createCreditUsage({ team_id, credits: usedCredits }); + } + } + + // with subscription + // using coupon + subscription credits: + if (credits > couponCredits) { + // update coupon credits + for (let i = 0; i < sortedCoupons.length; i++) { + await supabase_service + .from("coupons") + .update({ + credits: 0 + }) + .eq("id", sortedCoupons[i].id); + } + const usedCredits = credits - couponCredits; + return await createCreditUsage({ team_id, subscription_id: subscription.id, credits: usedCredits }); + + } else { // using only coupon credits let usedCredits = credits; while (usedCredits > 0) { // update coupons @@ -70,27 +131,7 @@ export async function supaBillTeam(team_id: string, credits: number) { } } - return await createCreditUsage({ team_id, credits: 0 }); - - // @nick ??? HOW TO HANDLE THIS CASE? - // not enough coupon credits but no subscription - } else if (!subscription) { - return await createCreditUsage({ team_id, credits }); - } - - // using coupon + subscription credits: - if (credits > couponCredits) { - // update coupon credits - for (let i = 0; i < sortedCoupons.length; i++) { - await supabase_service - .from("coupons") - .update({ - credits: 0 - }) - .eq("id", sortedCoupons[i].id); - } - const usedCredits = credits - couponCredits; - return await createCreditUsage({ team_id, subscription_id: subscription.id, credits: usedCredits }); + return await createCreditUsage({ team_id, subscription_id: subscription.id, credits: 0 }); } } @@ -131,12 +172,41 @@ export async function supaCheckTeamCredits(team_id: string, credits: number) { couponCredits = coupons.reduce((total, coupon) => total + coupon.credits, 0); } - if (subscriptionError || (!subscription && couponCredits <= 0)) { - return { success: false, message: "No active subscription or coupons found" }; - } + // Free credits, no coupons + if (subscriptionError || !subscription) { + // If there is no active subscription but there are available coupons + if (couponCredits >= credits) { + return { success: true, message: "Sufficient credits available" }; + } + + const { data: creditUsages, error: creditUsageError } = + await supabase_service + .from("credit_usage") + .select("credits_used") + .is("subscription_id", null) + .eq("team_id", team_id); + // .gte("created_at", subscription.current_period_start) + // .lte("created_at", subscription.current_period_end); - // If there is no active subscription but there are available coupons - if (couponCredits >= credits) { + if (creditUsageError) { + throw new Error( + `Failed to retrieve credit usage for subscription_id: ${subscription.id}` + ); + } + + const totalCreditsUsed = creditUsages.reduce( + (acc, usage) => acc + usage.credits_used, + 0 + ); + + console.log("totalCreditsUsed", totalCreditsUsed); + // 5. Compare the total credits used with the credits allowed by the plan. + if (totalCreditsUsed + credits > FREE_CREDITS) { + return { + success: false, + message: "Insufficient credits, please upgrade!", + }; + } return { success: true, message: "Sufficient credits available" }; } @@ -234,9 +304,6 @@ export async function countCreditsAndRemainingForCurrentBillingPeriod( const totalCreditsUsed = creditUsages.reduce((acc, usage) => acc + usage.credits_used, 0); - // Adjust total credits used by subtracting coupon value - // const adjustedCreditsUsed = Math.max(0, totalCreditsUsed - couponCredits); - const { data: price, error: priceError } = await supabase_service .from("prices") .select("credits") @@ -247,7 +314,6 @@ export async function countCreditsAndRemainingForCurrentBillingPeriod( throw new Error(`Failed to retrieve price for price_id: ${subscription.price_id}`); } - // Calculate remaining credits. const remainingCredits = price.credits + couponCredits - totalCreditsUsed; return { From 7689c31d3584b3ca3ce8e73104cb6d6292b2e49b Mon Sep 17 00:00:00 2001 From: Nicolas Date: Fri, 26 Apr 2024 14:36:19 -0700 Subject: [PATCH 4/4] Update credit_billing.ts --- apps/api/src/services/billing/credit_billing.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/api/src/services/billing/credit_billing.ts b/apps/api/src/services/billing/credit_billing.ts index a506026..37db664 100644 --- a/apps/api/src/services/billing/credit_billing.ts +++ b/apps/api/src/services/billing/credit_billing.ts @@ -40,8 +40,10 @@ export async function supaBillTeam(team_id: string, credits: number) { let sortedCoupons = coupons.sort((a, b) => b.credits - a.credits); // using coupon credits: if (couponCredits > 0) { + // if there is no subscription and they have enough coupon credits if (!subscription) { // using only coupon credits: + // if there are enough coupon credits if (couponCredits >= credits) { // remove credits from coupon credits let usedCredits = credits;