feat: implement consume credits action and get credit balance action, update credits balance references

This commit is contained in:
javayhu 2025-07-10 22:00:33 +08:00
parent 263440742a
commit de1ccca27b
9 changed files with 76 additions and 71 deletions

View File

@ -624,7 +624,7 @@
"remainingAmount": "剩余金额",
"paymentId": "支付编号",
"expirationDate": "过期日期",
"expirationDateProcessedAt": "过期日期处理时间",
"expirationDateProcessedAt": "过期处理时间",
"createdAt": "创建时间",
"updatedAt": "更新时间"
},

View File

@ -0,0 +1,43 @@
'use server';
import { consumeCredits } from '@/credits/credits';
import { getSession } from '@/lib/server';
import { createSafeActionClient } from 'next-safe-action';
import { z } from 'zod';
const actionClient = createSafeActionClient();
// consume credits schema
const consumeSchema = z.object({
amount: z.number().min(1),
description: z.string().optional(),
});
/**
* Consume credits
*/
export const consumeCreditsAction = actionClient
.schema(consumeSchema)
.action(async ({ parsedInput }) => {
const session = await getSession();
if (!session) {
console.warn('unauthorized request to consume credits');
return { success: false, error: 'Unauthorized' };
}
try {
await consumeCredits({
userId: session.user.id,
amount: parsedInput.amount,
description:
parsedInput.description || `Consume credits: ${parsedInput.amount}`,
});
return { success: true };
} catch (error) {
console.error('consume credits error:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Something went wrong',
};
}
});

View File

@ -1,61 +0,0 @@
'use server';
import {
addMonthlyFreeCredits,
addRegisterGiftCredits,
consumeCredits,
getUserCredits,
} from '@/credits/credits';
import { getSession } from '@/lib/server';
import { createSafeActionClient } from 'next-safe-action';
import { z } from 'zod';
const actionClient = createSafeActionClient();
// get current user's credits
export const getCreditsAction = actionClient.action(async () => {
const session = await getSession();
if (!session) return { success: false, error: 'Unauthorized' };
const credits = await getUserCredits(session.user.id);
return { success: true, credits };
});
// add register gift credits (for testing)
export const addRegisterCreditsAction = actionClient.action(async () => {
const session = await getSession();
if (!session) return { success: false, error: 'Unauthorized' };
await addRegisterGiftCredits(session.user.id);
return { success: true };
});
// add monthly free credits (for testing)
export const addMonthlyCreditsAction = actionClient.action(async () => {
const session = await getSession();
if (!session) return { success: false, error: 'Unauthorized' };
await addMonthlyFreeCredits(session.user.id);
return { success: true };
});
// consume credits (simulate button)
const consumeSchema = z.object({
amount: z.number().min(1),
description: z.string().optional(),
});
export const consumeCreditsAction = actionClient
.schema(consumeSchema)
.action(async ({ parsedInput }) => {
const session = await getSession();
if (!session) return { success: false, error: 'Unauthorized' };
try {
await consumeCredits({
userId: session.user.id,
amount: parsedInput.amount,
description:
parsedInput.description || `Consume credits: ${parsedInput.amount}`,
});
return { success: true };
} catch (e) {
return { success: false, error: (e as Error).message };
}
});

View File

@ -0,0 +1,21 @@
'use server';
import { getUserCredits } from '@/credits/credits';
import { getSession } from '@/lib/server';
import { createSafeActionClient } from 'next-safe-action';
const actionClient = createSafeActionClient();
/**
* Get current user's credits
*/
export const getCreditBalanceAction = actionClient.action(async () => {
const session = await getSession();
if (!session) {
console.warn('unauthorized request to get credit balance');
return { success: false, error: 'Unauthorized' };
}
const credits = await getUserCredits(session.user.id);
return { success: true, credits };
});

View File

@ -45,11 +45,13 @@ export const getCreditTransactionsAction = actionClient
try {
const { pageIndex, pageSize, search, sorting } = parsedInput;
// search by type, amount, paymentId, description
const where = search
? or(
ilike(creditTransaction.description, `%${search}%`),
ilike(creditTransaction.type, `%${search}%`),
ilike(creditTransaction.paymentId, `%${search}%`)
ilike(creditTransaction.amount, `%${search}%`),
ilike(creditTransaction.paymentId, `%${search}%`),
ilike(creditTransaction.description, `%${search}%`)
)
: undefined;

View File

@ -1,6 +1,6 @@
'use client';
import { getCreditsAction } from '@/actions/credits.action';
import { getCreditBalanceAction } from '@/actions/get-credit-balance';
import { Button } from '@/components/ui/button';
import { useLocaleRouter } from '@/i18n/navigation';
import { Routes } from '@/routes';
@ -17,7 +17,7 @@ export function CreditsBalanceButton() {
useEffect(() => {
const fetchCredits = async () => {
try {
const result = await getCreditsAction();
const result = await getCreditBalanceAction();
if (result?.data?.success && result.data.credits !== undefined) {
setCredits(result.data.credits);
}

View File

@ -1,6 +1,6 @@
'use client';
import { getCreditsAction } from '@/actions/credits.action';
import { getCreditBalanceAction } from '@/actions/get-credit-balance';
import { useLocaleRouter } from '@/i18n/navigation';
import { Routes } from '@/routes';
import { useTransactionStore } from '@/stores/transaction-store';
@ -18,7 +18,7 @@ export function CreditsBalanceMenu() {
useEffect(() => {
const fetchCredits = async () => {
try {
const result = await getCreditsAction();
const result = await getCreditBalanceAction();
if (result?.data?.success && result.data.credits !== undefined) {
setCredits(result.data.credits);
}

View File

@ -1,6 +1,6 @@
'use client';
import { getCreditsAction } from '@/actions/credits.action';
import { getCreditBalanceAction } from '@/actions/get-credit-balance';
import { Badge } from '@/components/ui/badge';
import {
Card,
@ -44,7 +44,7 @@ export function CreditPackages() {
const fetchCredits = async () => {
try {
setLoadingCredits(true);
const result = await getCreditsAction();
const result = await getCreditBalanceAction();
if (result?.data?.success) {
console.log('CreditPackages, fetched credits:', result.data.credits);
setCredits(result.data.credits || 0);

View File

@ -828,7 +828,7 @@ export class StripeProvider implements PaymentProvider {
userId,
amount: Number.parseInt(credits),
type: CREDIT_TRANSACTION_TYPE.PURCHASE,
description: `+${credits} credits for package ${packageId} (${amount})`,
description: `+${credits} credits for package ${packageId} ($${amount.toLocaleString()})`,
paymentId: session.id,
expireDays: creditPackage.expireDays,
});