This commit is contained in:
payacom
2026-09-24 21:03:27 +03:30
parent 7f0ddfde70
commit 806471241e
43 changed files with 2166 additions and 144 deletions

View File

@@ -18,6 +18,7 @@ const AcademyCategoryModel = require('../../../models/AcademyCategoryModel')
const { buildUnifiedInterestScore, blendFreshIntoRanked: blendInterestIntoRanked } = require('../../../utils/exploreAlgorithm')
const { resolveCommentModerationStatus } = require('../../../utils/contentModeration')
const { saveCommentWithModerationStatus } = require('../../../utils/commentStatusSave')
const { resolveStoryFlags } = require('../../../utils/storyRingFlags')
const { isVerifiedBadge } = require('../../../utils/verifyBadge')
const { maskPhoneNumbers } = require('../../../utils/phoneNumberMasking')
const {
@@ -39,6 +40,7 @@ const {
} = require('../../../utils/academyNotification')
const AdvertisingProfileModel = require('../../../models/AdvertisingProfile')
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const ACADEMY_COMMISSION_RATE = 0.15
@@ -1811,10 +1813,42 @@ const getCourseComments = async (req, res) => {
.sort(sortOptions)
.skip(skip)
.limit(parseInt(limit))
.populate('user_id', 'name username profileImage'),
// فیلدهای قبلی (name/username/profileImage) روی این اسکیما اصلاً وجود نداشتن —
// یعنی اطلاعاتِ نویسنده‌ی کامنت همیشه خالی برمی‌گشت
.populate('user_id', 'user_name first_name last_name profile_image verify_badge is_verified')
.lean(),
CourseComment.countDocuments(query)
])
// بررسی وجود توکن (اختیاری — لیست کامنت‌ها بدون لاگین هم قابل مشاهده‌ست)
let viewerId = null
const authHeader = req.header('Authorization')
if (authHeader) {
const tokenParts = authHeader.split(' ')
if (tokenParts.length === 2) {
try {
viewerId = jwt.verify(tokenParts[1], process.env.APP_SECRET).id
} catch (err) {
console.error('Invalid Token:', err.message)
}
}
}
const commenterIds = comments.map((c) => c.user_id?._id).filter(Boolean)
const storyFlags = await resolveStoryFlags(commenterIds, viewerId)
const commentsWithStoryFlags = comments.map((c) => {
if (!c.user_id) return c
const flags = storyFlags[String(c.user_id._id)] || {}
return {
...c,
user_id: {
...c.user_id,
has_story: flags.has_story || false,
has_unviewed_story: flags.has_unviewed_story || false
}
}
})
// محاسبه آمار کامنت‌ها
const stats = await CourseComment.aggregate([
{ $match: { course_id: courseId, status: 'accepted' } },
@@ -1836,7 +1870,7 @@ const getCourseComments = async (req, res) => {
success: true,
message: 'لیست کامنت‌ها با موفقیت دریافت شد',
data: {
comments,
comments: commentsWithStoryFlags,
pagination: {
currentPage: parseInt(page),
totalPages,
@@ -2058,7 +2092,7 @@ const coursePayment = async (req, res, next) => {
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const { courseId } = req.body
const { courseId, useWallet } = req.body
// ========== 1. بررسی وجود دوره ==========
const course = await CourseModel.findById(courseId)
@@ -2133,8 +2167,68 @@ const coursePayment = async (req, res, next) => {
})
}
// ========== 5.۵ بررسی موجودی کیف پول (عین shopOrderPaymentController.js) ==========
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(
userId,
finalPrice,
useWallet
)
// کاملاً از کیف پول پوشش داده شد — اصلاً نیازی به درگاه نیست
if (fullyCovered) {
await debitWalletForPurchase(userId, walletAmount, {
referenceType: 'payment',
referenceId: course._id,
description: `خرید پکیج آموزشی «${course.cuorse_name}» از کیف پول`
})
const paymentRecord = await AcademyPaymentModel.create({
price: finalPrice,
discountAmount: priceNum - discountedPrice,
taxAmount: finalTaxPrice,
taxRate,
course_name: course.cuorse_name,
course_id: course._id,
academy_id: course.academyId,
user_id: userId,
walletAmountUsed: walletAmount,
status: 'success'
})
await IsPaymentCourse.create({
user_id: userId,
course_id: course._id,
price_paid: finalPrice,
purchased_at: new Date()
})
const courseOwnerId = await resolveCourseOwnerId(course)
await createCoursePurchaseNotification({
ownerId: courseOwnerId,
buyerId: userId,
courseId: course._id
})
if (courseOwnerId) {
const sellerNetAmount = Math.round(finalPrice * (1 - ACADEMY_COMMISSION_RATE))
if (sellerNetAmount > 0) {
await WalletTransactionModel.create({
user: courseOwnerId,
bucket: 'academy',
type: 'credit',
amount: sellerNetAmount,
referenceType: 'payment',
referenceId: paymentRecord._id,
description: `درآمد فروش پکیج آموزشی «${course.cuorse_name}» (پس از کسر ۱۵٪ کارمزد)`
})
}
}
return res.status(200).json({ success: true, paidFromWallet: true })
}
// تبدیل به ریال (ضرب در 10 چون هر تومان = 10 ریال)
const amountInRials = Math.round(finalPrice * 10)
const amountInRials = Math.round(gatewayAmount * 10)
// مبلغ باید حداقل 1000 تومان باشد (10000 ریال)
if (amountInRials < 10000) {
@@ -2172,7 +2266,8 @@ const coursePayment = async (req, res, next) => {
// ========== 8. بررسی پاسخ زرین‌پال ==========
if (result.data && result.data.code === 100) {
// ذخیره اطلاعات پرداخت در حالت pending
// ذخیره اطلاعات پرداخت در حالت pending — walletAmountUsed فقط یادداشت می‌شه،
// کسرِ واقعی توی verify (بعد از تاییدِ قطعیِ پرداخت) انجام می‌شه
await AcademyPaymentModel.create({
price: finalPrice,
discountAmount: priceNum - discountedPrice,
@@ -2183,6 +2278,7 @@ const coursePayment = async (req, res, next) => {
academy_id: course.academyId,
user_id: userId,
payment_authority: result.data.authority,
walletAmountUsed: walletAmount,
status: 'pending'
})
@@ -2245,8 +2341,11 @@ const CoursePaymentCallback = async (req, res) => {
)
}
// ========== 4. محاسبه مبلغ ==========
const amountInRials = Math.round(paymentRecord.price * 10)
// ========== 4. محاسبه مبلغ — فقط سهمِ درگاه (walletAmountUsed تا همینجا فقط
// یادداشت شده، کیف پول دست‌نخورده مونده — عین shopOrderPaymentController.js) ==========
const walletAmount = paymentRecord.walletAmountUsed || 0
const gatewayAmount = paymentRecord.price - walletAmount
const amountInRials = Math.round(gatewayAmount * 10)
// ========== 5. تایید پرداخت با زرین‌پال ==========
const verify = await axios.post(
@@ -2266,6 +2365,14 @@ const CoursePaymentCallback = async (req, res) => {
// ========== 6. بررسی نتیجه تایید ==========
if (data.code === 100) {
// درگاه فقط بخشِ باقی‌مونده رو تایید کرد؛ حالا که پرداخت قطعاً موفق شد، سهمِ
// کیف پول هم واقعاً کسر می‌شه
await debitWalletForPurchase(userId, walletAmount, {
referenceType: 'payment',
referenceId: paymentRecord._id,
description: `خرید پکیج آموزشی «${paymentRecord.course_name}» از کیف پول`
})
// به‌روزرسانی رکورد پرداخت
await AcademyPaymentModel.findByIdAndUpdate(paymentRecord._id, {
status: 'success',