mirror of
https://github.com/labring/sealos.git
synced 2026-09-24 15:46:19 +08:00
feat(desktop) Add Real Name Invitee Rewards (#5366)
This commit is contained in:
@@ -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 {};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user