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] [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 + }; + }