full app
This commit is contained in:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user