Repository: Significant-Gravitas/Auto-GPT
File: autogpt_platform/backend/backend/data/credit.py (Python)

Code before:
                "metadata": metadata,
                "isActive": is_active,
                "createdAt": self.time_now(),
            }
            if transaction_key:
                transaction_data["transactionKey"] = transaction_key
            tx = await CreditTransaction.prisma().create(data=transaction_data)
            return user_balance + amount, tx.transactionKey


class UserCredit(UserCreditBase):

    async def _send_refund_notification(
        self,
        notification_request: RefundRequestData,
        notification_type: NotificationType,
    ):
        await queue_notification_async(
            NotificationEventModel(
                user_id=notification_request.user_id,
                type=notification_type,
                data=notification_request,
            )
        )

    async def _handle_low_balance(
        self,
        user_id: str,
        current_balance: int,
        transaction_cost: int,
    ):
        """Check and handle low balance scenarios after a transaction"""
        LOW_BALANCE_THRESHOLD = settings.config.low_balance_threshold

        # Get balance before the transaction
        balance_before = current_balance + transaction_cost

        # Check if we crossed the threshold
        if (
            current_balance < LOW_BALANCE_THRESHOLD
            and balance_before >= LOW_BALANCE_THRESHOLD
        ):
            # Queue notification for the user
            from backend.data.notifications import (
                LowBalanceData,
                NotificationEventModel,
            )

            await queue_notification_async(
                NotificationEventModel(
                    user_id=user_id,

Review comment (suggestion):
spend_credits already returns the current balance after the transaction. Can we do this entirely outside the credit.py? 

Having a data model calling other services only for notification (plus blocking the spend call) is adding to the tech debt we currently have (we've been calling notification handling here for refund, which should've been moved outside the credits).

Code after:
                "createdAt": self.time_now(),
            }
            if transaction_key:
                transaction_data["transactionKey"] = transaction_key
            tx = await CreditTransaction.prisma().create(data=transaction_data)
            return user_balance + amount, tx.transactionKey


class UserCredit(UserCreditBase):

    async def _send_refund_notification(
        self,
        notification_request: RefundRequestData,
        notification_type: NotificationType,
    ):
        await queue_notification_async(
            NotificationEventModel(
                user_id=notification_request.user_id,
                type=notification_type,
                data=notification_request,
            )
        )

    async def spend_credits(
        self,
        user_id: str,
        cost: int,
        metadata: UsageTransactionMetadata,
    ) -> int:
        if cost == 0:
            return 0

        balance, _ = await self._add_transaction(
            user_id=user_id,
            amount=-cost,
            transaction_type=CreditTransactionType.USAGE,
            metadata=SafeJson(metadata.model_dump()),
        )

        # Auto top-up if balance is below threshold.
        auto_top_up = await get_auto_top_up(user_id)
        if auto_top_up.threshold and balance < auto_top_up.threshold:
            try:
                await self._top_up_credits(
                    user_id=user_id,
                    amount=auto_top_up.amount,
                    # Avoid multiple auto top-ups within the same graph execution.
                    key=f"AUTO-TOP-UP-{user_id}-{metadata.graph_exec_id}",
                    ceiling_balance=auto_top_up.threshold,
                    top_up_type=TopUpType.AUTO,
                )