feat(desktop) Add Real Name Invitee Rewards (#5366)

This commit is contained in:
limbo
2025-02-11 18:18:44 +08:00
committed by GitHub
parent 3be90e5328
commit ce4a525a51
4 changed files with 404 additions and 121 deletions
@@ -106,7 +106,10 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse, userUid: str
});
if (!info || !info.additionalInfo) {
return jsonRes(res, { code: 200, data: { authTimes: realNameAuthProvider.maxFailedTimes } });
return jsonRes(res, {
code: 200,
data: { remainingAttempts: realNameAuthProvider.maxFailedTimes }
});
}
const additionalInfo = info.additionalInfo as unknown as AdditionalInfo;
@@ -6,6 +6,9 @@ import { globalPrisma } from '@/services/backend/db/init';
import { z } from 'zod';
import { PAYMENTSTATUS } from '@/types/response/enterpriseRealName';
import { AdditionalInfo } from '@/types/response/enterpriseRealName';
import { RealNameAuthProvider } from '@/pages/api/account/faceIdRealNameAuthCallback';
import { TencentCloudFaceAuthConfig } from '@/pages/api/account/faceIdRealNameAuthCallback';
import { getInviterInfo } from '@/utils/getInviteInfo';
const schema = z.object({
transAmt: z.string().min(1)
@@ -25,7 +28,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const payload = await verifyAccessToken(req.headers);
if (!payload) return jsonRes(res, { code: 401, message: 'Token is invalid' });
const userUid = payload.userUid;
const userId = payload.userId;
const inviteInfo = await getInviterInfo(userId);
try {
const realNameAuthProvider: RealNameAuthProvider | null =
await globalPrisma.realNameAuthProvider.findFirst({
where: {
backend: 'TENCENTCLOUD',
authType: 'tcloudFaceAuth'
}
});
if (!realNameAuthProvider) {
throw new Error('faceidRealNameAuth: Real name authentication provider not found');
}
const config: TencentCloudFaceAuthConfig =
realNameAuthProvider.config as TencentCloudFaceAuthConfig;
if (!config) {
throw new Error('faceidRealNameAuth: Real name authentication configuration not found');
}
const realNameAuthReward = config.realNameAuthReward;
const validationResult = schema.safeParse(req.body);
if (!validationResult.success) {
return jsonRes(res, {
@@ -60,17 +89,130 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const additionalInfo = info.additionalInfo as unknown as AdditionalInfo;
if (additionalInfo.transAmt === transAmt) {
await globalPrisma.enterpriseRealNameInfo.update({
where: { userUid: payload.userUid },
if (additionalInfo.transAmt !== transAmt) {
return jsonRes(res, {
code: 200,
message: 'transAmt_not_match',
data: {
isVerified: true,
additionalInfo: {
...additionalInfo,
paymentStatus: PAYMENTSTATUS.SUCCESS
}
authState: 'failed'
}
});
}
if (realNameAuthReward) {
await globalPrisma.$transaction(async (globalPrisma) => {
const currentAccount = await globalPrisma.account.findUniqueOrThrow({
where: { userUid }
});
if (!currentAccount.balance) {
throw new Error('enterpriseRealNameVerify: Account balance not found');
}
const currentActivityBonus = currentAccount.activityBonus || BigInt(0);
const realnameReward = BigInt(realNameAuthReward);
const newActivityBonus = currentActivityBonus + realnameReward;
const newBalance = currentAccount.balance + realnameReward;
const updatedAccount = await globalPrisma.account.update({
where: { userUid },
data: {
activityBonus: newActivityBonus,
balance: newBalance
}
});
const enterpriseRealNameInfo = await globalPrisma.enterpriseRealNameInfo.update({
where: { userUid: payload.userUid },
data: {
isVerified: true,
additionalInfo: {
...additionalInfo,
paymentStatus: PAYMENTSTATUS.SUCCESS
}
}
});
const accountTransaction = await globalPrisma.accountTransaction.create({
data: {
type: 'REALNAME_AUTH_REWARD',
userUid: userUid,
balance: realnameReward,
balance_before: currentAccount.balance,
deduction_balance: 0, // No deduction in this case
deduction_balance_before: currentAccount.deduction_balance,
message: 'Real name authentication reward',
billing_id: enterpriseRealNameInfo.id // You'll need to implement this function
}
});
await globalPrisma.userTask.updateMany({
where: {
userUid,
task: {
taskType: 'REAL_NAME_AUTH'
},
status: 'NOT_COMPLETED'
},
data: {
rewardStatus: 'COMPLETED',
status: 'COMPLETED',
completedAt: new Date()
}
});
if (inviteInfo.inviterId && inviteInfo.amount) {
const inviterUser = await globalPrisma.user.findUniqueOrThrow({
where: { id: inviteInfo.inviterId }
});
if (!inviterUser) {
throw new Error('enterpriseRealNameVerify: Inviter user not found');
}
const inviterAccount = await globalPrisma.account.findUniqueOrThrow({
where: { userUid: inviterUser.uid }
});
if (!inviterAccount.balance) {
throw new Error('enterpriseRealNameVerify: Inviter account balance not found');
}
const currentActivityBonus = inviterAccount.activityBonus || BigInt(0);
const realnameInviteReward = inviteInfo.amount;
const newActivityBonus = currentActivityBonus + realnameInviteReward;
const newBalance = inviterAccount.balance + realnameInviteReward;
await globalPrisma.account.update({
where: { userUid: inviterUser.uid },
data: {
activityBonus: newActivityBonus,
balance: newBalance
}
});
await globalPrisma.accountTransaction.create({
data: {
type: 'REALNAME_AUTH_INVITE_REWARD',
userUid: inviterUser.uid,
balance: realnameInviteReward,
balance_before: inviterAccount.balance,
deduction_balance: 0, // No deduction in this case
deduction_balance_before: inviterAccount.deduction_balance,
message: 'Real name authentication invite reward',
billing_id: enterpriseRealNameInfo.id // You'll need to implement this function
}
});
}
return {
account: updatedAccount,
transaction: accountTransaction,
enterpriseRealNameInfo: enterpriseRealNameInfo
};
});
return jsonRes(res, {
code: 200,
@@ -81,11 +223,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
await globalPrisma.enterpriseRealNameInfo.update({
where: { userUid: payload.userUid },
data: {
isVerified: true,
additionalInfo: {
...additionalInfo,
paymentStatus: PAYMENTSTATUS.SUCCESS
}
}
});
return jsonRes(res, {
code: 200,
message: 'transAmt_not_match',
data: {
authState: 'failed'
authState: 'success',
enterpriseRealName: additionalInfo.keyName
}
});
} catch (error) {
@@ -7,8 +7,9 @@ import { globalPrisma } from '@/services/backend/db/init';
import { GetDetectInfoEnhancedResponse } from 'tencentcloud-sdk-nodejs/tencentcloud/services/faceid/v20180301/faceid_models';
import { RealNameOSSConfigType } from '@/types';
import { Client, ClientOptions } from 'minio';
import { getInviterInfo } from '@/utils/getInviteInfo';
type TencentCloudFaceAuthConfig = {
export type TencentCloudFaceAuthConfig = {
secretId: string;
secretKey: string;
ruleId: string;
@@ -17,7 +18,7 @@ type TencentCloudFaceAuthConfig = {
type JsonValue = string | number | boolean | object | null;
type RealNameAuthProvider = {
export type RealNameAuthProvider = {
id: string;
backend: string;
authType: string;
@@ -95,6 +96,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const isFaceRecognitionSuccess = userRealNameFaceAuthInfo.Text?.ErrCode === 0;
const userUid = payload.userUid;
const userId = payload.userId;
const timestamp = Date.now();
// Fetch existing user real name info
@@ -110,6 +112,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return jsonRes(res, { code: 400, message: 'User real name info already verified' });
}
const inviteInfo = await getInviterInfo(userId);
let additionalInfo: AdditionalInfo = userRealNameInfo.additionalInfo;
additionalInfo.faceRecognition.callback.isUsed = true;
@@ -151,114 +155,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
additionalInfo.userMaterials.push(videoPath);
}
if (isFaceRecognitionSuccess) {
if (realNameAuthReward) {
await globalPrisma.$transaction(async (globalPrisma) => {
const currentAccount = await globalPrisma.account.findUniqueOrThrow({
where: { userUid }
});
if (!currentAccount.balance) {
throw new Error('faceidRealNameAuth: Account balance not found');
}
const currentActivityBonus = currentAccount.activityBonus || BigInt(0);
const rewardBigInt = BigInt(realNameAuthReward);
const newActivityBonus = currentActivityBonus + rewardBigInt;
const newBalance = currentAccount.balance + rewardBigInt;
const updatedAccount = await globalPrisma.account.update({
where: { userUid },
data: {
activityBonus: newActivityBonus,
balance: newBalance
}
});
const userRealNameInfo = await globalPrisma.userRealNameInfo.update({
where: { userUid },
data: {
realName: userRealNameFaceAuthInfo.Text?.Name,
idCard: userRealNameFaceAuthInfo.Text?.IdCard,
isVerified: true,
additionalInfo: additionalInfo
}
});
const accountTransaction = await globalPrisma.accountTransaction.create({
data: {
type: 'REALNAME_AUTH_REWARD',
userUid: userUid,
balance: rewardBigInt,
balance_before: currentAccount.balance,
deduction_balance: 0, // No deduction in this case
deduction_balance_before: currentAccount.deduction_balance,
message: 'Real name authentication reward',
billing_id: userRealNameInfo.id // You'll need to implement this function
}
});
await globalPrisma.userTask.updateMany({
where: {
userUid,
task: {
taskType: 'REAL_NAME_AUTH'
},
status: 'NOT_COMPLETED'
},
data: {
rewardStatus: 'COMPLETED',
status: 'COMPLETED',
completedAt: new Date()
}
});
return {
account: updatedAccount,
transaction: accountTransaction,
userRealNameInfo: userRealNameInfo
};
});
} else {
await globalPrisma.userRealNameInfo.update({
where: { userUid },
data: {
realName: userRealNameFaceAuthInfo.Text?.Name,
idCard: userRealNameFaceAuthInfo.Text?.IdCard,
isVerified: true,
additionalInfo: additionalInfo
}
});
}
res.setHeader('Content-Type', 'text/html');
return res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real Name Authentication</title>
<style>
body, html {
height: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
text-align: center;
}
</style>
</head>
<body>
<h1>Real Name Authentication Successful</h1>
</body>
</html>
`);
} else {
if (!isFaceRecognitionSuccess) {
await globalPrisma.userRealNameInfo.update({
where: { userUid },
data: {
@@ -296,6 +193,185 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
</html>
`);
}
if (realNameAuthReward) {
await globalPrisma.$transaction(async (globalPrisma) => {
const currentAccount = await globalPrisma.account.findUniqueOrThrow({
where: { userUid }
});
if (!currentAccount.balance) {
throw new Error('faceidRealNameAuth: Account balance not found');
}
const currentActivityBonus = currentAccount.activityBonus || BigInt(0);
const realnameReward = BigInt(realNameAuthReward);
const newActivityBonus = currentActivityBonus + realnameReward;
const newBalance = currentAccount.balance + realnameReward;
const updatedAccount = await globalPrisma.account.update({
where: { userUid },
data: {
activityBonus: newActivityBonus,
balance: newBalance
}
});
const userRealNameInfo = await globalPrisma.userRealNameInfo.update({
where: { userUid },
data: {
realName: userRealNameFaceAuthInfo.Text?.Name,
idCard: userRealNameFaceAuthInfo.Text?.IdCard,
isVerified: true,
additionalInfo: additionalInfo
}
});
const accountTransaction = await globalPrisma.accountTransaction.create({
data: {
type: 'REALNAME_AUTH_REWARD',
userUid: userUid,
balance: realnameReward,
balance_before: currentAccount.balance,
deduction_balance: 0, // No deduction in this case
deduction_balance_before: currentAccount.deduction_balance,
message: 'Real name authentication reward',
billing_id: userRealNameInfo.id // You'll need to implement this function
}
});
await globalPrisma.userTask.updateMany({
where: {
userUid,
task: {
taskType: 'REAL_NAME_AUTH'
},
status: 'NOT_COMPLETED'
},
data: {
rewardStatus: 'COMPLETED',
status: 'COMPLETED',
completedAt: new Date()
}
});
if (inviteInfo.inviterId && inviteInfo.amount) {
const inviterUser = await globalPrisma.user.findUniqueOrThrow({
where: { id: inviteInfo.inviterId }
});
if (!inviterUser) {
throw new Error('faceidRealNameAuth: Inviter user not found');
}
const inviterAccount = await globalPrisma.account.findUniqueOrThrow({
where: { userUid: inviterUser.uid }
});
if (!inviterAccount.balance) {
throw new Error('faceidRealNameAuth: Inviter account balance not found');
}
const currentActivityBonus = inviterAccount.activityBonus || BigInt(0);
const realnameInviteReward = inviteInfo.amount;
const newActivityBonus = currentActivityBonus + realnameInviteReward;
const newBalance = inviterAccount.balance + realnameInviteReward;
await globalPrisma.account.update({
where: { userUid: inviterUser.uid },
data: {
activityBonus: newActivityBonus,
balance: newBalance
}
});
await globalPrisma.accountTransaction.create({
data: {
type: 'REALNAME_AUTH_INVITE_REWARD',
userUid: inviterUser.uid,
balance: realnameInviteReward,
balance_before: inviterAccount.balance,
deduction_balance: 0, // No deduction in this case
deduction_balance_before: inviterAccount.deduction_balance,
message: 'Real name authentication invite reward',
billing_id: userRealNameInfo.id // You'll need to implement this function
}
});
}
return {
account: updatedAccount,
transaction: accountTransaction,
userRealNameInfo: userRealNameInfo
};
});
res.setHeader('Content-Type', 'text/html');
return res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real Name Authentication</title>
<style>
body, html {
height: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
text-align: center;
}
</style>
</head>
<body>
<h1>Real Name Authentication Successful</h1>
</body>
</html>
`);
}
await globalPrisma.userRealNameInfo.update({
where: { userUid },
data: {
realName: userRealNameFaceAuthInfo.Text?.Name,
idCard: userRealNameFaceAuthInfo.Text?.IdCard,
isVerified: true,
additionalInfo: additionalInfo
}
});
res.setHeader('Content-Type', 'text/html');
return res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real Name Authentication</title>
<style>
body, html {
height: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
text-align: center;
}
</style>
</head>
<body>
<h1>Real Name Authentication Successful</h1>
</body>
</html>
`);
} catch (error) {
console.error('faceidRealNameAuth: Internal error');
if (error instanceof Error) {
@@ -0,0 +1,51 @@
import { AuthConfigType } from '@/types/system';
interface InviterResponse {
code: number;
message: string;
data?: {
inviterId: string;
reward: {
amount: number;
};
};
}
export async function getInviterInfo(
inviteeId: string
): Promise<{ inviterId?: string; amount?: bigint }> {
const conf = global.AppConfig?.desktop.auth as AuthConfigType;
const inviteEnabled = conf.invite?.enabled || false;
const secretKey = conf.invite?.lafSecretKey || '';
const baseUrl = conf.invite?.lafBaseURL || '';
if (!inviteEnabled || !secretKey || !baseUrl) {
return {};
}
try {
const response = await fetch(`https://${baseUrl}/getInviter`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
inviteeId,
secretKey
})
});
const result: InviterResponse = await response.json();
if (result.code !== 200) {
return {};
}
return {
inviterId: result.data?.inviterId,
amount: result.data?.reward?.amount ? BigInt(result.data.reward.amount * 1000000) : BigInt(0)
};
} catch (error) {
console.error('getInviteInfo error:', error);
return {};
}
}