Fix top-up credit amounts

This commit is contained in:
Krzysztof Czerwinski 2024-12-31 18:51:38 +01:00
parent b3d7804ba1
commit 7063751bb0
5 changed files with 38 additions and 26 deletions

View File

@ -72,7 +72,7 @@ class UserCreditBase(ABC):
Args:
user_id (str): The user ID.
amount (int): The amount to top up.
amount (int): The amount of credits to top up.
Returns:
str: The redirect url to the payment page.
@ -231,7 +231,7 @@ class UserCredit(UserCreditBase):
async def top_up_credits(self, user_id: str, amount: int):
if amount < 0:
raise ValueError(f"Top up amount must not be negative: {amount}")
await CreditTransaction.prisma().create(
data={
"userId": user_id,
@ -258,7 +258,8 @@ class UserCredit(UserCreditBase):
# Create checkout session
# https://docs.stripe.com/checkout/quickstart?client=react
# amount param is always in the smallest currency unit (so cents for usd)
# unit_amount param is always in the smallest currency unit (so cents for usd)
# which equals to amount of credits
checkout_session = stripe.checkout.Session.create(
customer=user.stripeCustomerId,
line_items=[
@ -268,7 +269,7 @@ class UserCredit(UserCreditBase):
"product_data": {
"name": "AutoGPT Platform Credits",
},
"unit_amount": amount * 100,
"unit_amount": amount,
},
"quantity": 1,
}

View File

@ -60,3 +60,4 @@ class UpdatePermissionsRequest(pydantic.BaseModel):
class RequestTopUp(pydantic.BaseModel):
amount: int
"""Amount of credits to top up."""

View File

@ -3,7 +3,6 @@ import logging
from collections import defaultdict
from typing import TYPE_CHECKING, Annotated, Any, Sequence
from fastapi.responses import RedirectResponse
import pydantic
import stripe
from autogpt_libs.auth.middleware import auth_middleware
@ -42,9 +41,9 @@ from backend.server.model import (
CreateAPIKeyRequest,
CreateAPIKeyResponse,
CreateGraph,
RequestTopUp,
SetGraphActiveVersion,
UpdatePermissionsRequest,
RequestTopUp,
)
from backend.server.utils import get_user_id
from backend.util.service import get_service_client
@ -137,10 +136,12 @@ async def get_user_credits(
user_id: Annotated[str, Depends(get_user_id)],
) -> dict[str, int]:
# Credits can go negative, so ensure it's at least 0 for user to see.
return {"credits": max(await _user_credit_model.get_or_refill_credit(user_id), 0)}
return {"credits": max(await _user_credit_model.get_credits(user_id), 0)}
@v1_router.post(path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)])
@v1_router.post(
path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)]
)
async def request_top_up(
request: RequestTopUp, user_id: Annotated[str, Depends(get_user_id)]
):
@ -153,7 +154,7 @@ async def stripe_webhook(request: Request):
# Get the raw request body
payload = await request.body()
# Get the signature header
sig_header = request.headers.get('stripe-signature')
sig_header = request.headers.get("stripe-signature")
try:
event = stripe.Webhook.construct_event(
@ -165,14 +166,12 @@ async def stripe_webhook(request: Request):
except stripe.SignatureVerificationError:
# Invalid signature
raise HTTPException(status_code=400)
print(event)
if (
event['type'] == 'checkout.session.completed'
or event['type'] == 'checkout.session.async_payment_succeeded'
event["type"] == "checkout.session.completed"
or event["type"] == "checkout.session.async_payment_succeeded"
):
await _user_credit_model.fulfill_checkout(event['data']['object']['id'])
await _user_credit_model.fulfill_checkout(event["data"]["object"]["id"])
return Response(status_code=200)

View File

@ -167,7 +167,10 @@ export default function PrivatePage() {
min="5"
step="1"
/>
<Button onClick={() => requestTopUp(amount)} className="whitespace-nowrap">
<Button
onClick={() => requestTopUp(amount)}
className="whitespace-nowrap"
>
Top-Up
</Button>
</div>

View File

@ -3,12 +3,14 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { loadStripe } from "@stripe/stripe-js";
import { useRouter } from "next/navigation";
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
);
export default function useCredits(): {
credits: number | null;
fetchCredits: () => void;
requestTopUp: (amount: number) => Promise<void>;
requestTopUp: (usd_amount: number) => Promise<void>;
} {
const [credits, setCredits] = useState<number | null>(null);
const api = useMemo(() => new AutoGPTServerAPI(), []);
@ -23,18 +25,24 @@ export default function useCredits(): {
fetchCredits();
}, [fetchCredits]);
const requestTopUp = useCallback(async (amount: number) => {
const stripe = await stripePromise;
const requestTopUp = useCallback(
async (usd_amount: number) => {
const stripe = await stripePromise;
if (!stripe) {
console.error("Stripe failed to load");
return;
}
if (!stripe) {
console.error("Stripe failed to load");
return;
}
const response = await api.requestTopUp(amount);
// Convert dollar amount to credit count
const response = await api.requestTopUp(usd_amount * 100);
router.push(response.checkout_url);
}, [api]);
console.log(response);
router.push(response.checkout_url);
},
[api],
);
return {
credits,