Files
modstagram-back/utils/offerReplyGate.js
2026-09-24 21:00:07 +03:30

120 lines
4.0 KiB
JavaScript

const OfferModel = require('../models/OfferModel')
const WalletTransactionModel = require('../models/WalletTransactionModel')
const { notifyUser } = require('./notifyUser')
const OFFER_REPLY_WINDOW_MS = 12 * 60 * 60 * 1000
/**
* Called whenever a chat message is sent. If there's a pending paid Offer
* between these two users, tracks the requester's first direct message and
* the receiver's reply — the receiver's 50% commission is only credited if
* they reply within 12 hours of that first message.
*/
async function trackOfferMessageForCommission (io, senderId, receiverId) {
try {
const offer = await OfferModel.findOne({
$or: [
{ sender: senderId, receiver: receiverId },
{ sender: receiverId, receiver: senderId }
],
commissionCredited: false,
pendingCommissionAmount: { $gt: 0 }
}).sort({ createdAt: -1 })
if (!offer) return
const isFromRequester = String(offer.sender) === String(senderId)
if (isFromRequester) {
if (!offer.firstMessageAt) {
offer.firstMessageAt = new Date()
offer.replyDeadline = new Date(Date.now() + OFFER_REPLY_WINDOW_MS)
await offer.save()
}
return
}
// پیام از طرف گیرنده — کسی که باید ظرف ۱۲ ساعت پاسخ بدهد
if (!offer.firstMessageAt || offer.repliedAt) return
if (new Date() > offer.replyDeadline) return
offer.repliedAt = new Date()
offer.commissionCredited = true
await offer.save()
await WalletTransactionModel.create({
user: offer.receiver,
bucket: 'gift',
type: 'credit',
amount: offer.pendingCommissionAmount,
referenceType: 'payment',
referenceId: offer._id,
description: 'سهم دریافتی از درخواست همکاری (۵۰٪، پس از پاسخ به‌موقع)'
})
if (io) {
try {
io.to(`user:${String(offer.receiver)}`).emit('notification', {
type: 'offer_commission',
title: 'واریز سهم درخواست همکاری',
description: 'با پاسخ به‌موقع، ۵۰٪ مبلغ درخواست همکاری به کیف پول شما واریز شد.'
})
} catch (_) {}
}
} catch (error) {
console.error('trackOfferMessageForCommission error:', error)
}
}
/**
* Sweep for paid Offers whose 12-hour reply deadline has passed without the
* receiver replying — refunds the pending 30% commission to the sender's
* wallet instead (both web and Android go through the same payment callback,
* so a single server-side sweep covers both). Meant to be called
* periodically by a cron job (see services/OfferReplyCron.js).
*/
async function refundExpiredOfferCommissions (io) {
const now = new Date()
const expiredOffers = await OfferModel.find({
commissionCredited: false,
commissionRefunded: false,
pendingCommissionAmount: { $gt: 0 },
firstMessageAt: { $ne: null },
repliedAt: null,
replyDeadline: { $lte: now }
})
for (const offer of expiredOffers) {
try {
offer.commissionRefunded = true
await offer.save()
await WalletTransactionModel.create({
user: offer.sender,
bucket: 'gift',
type: 'credit',
amount: offer.pendingCommissionAmount,
referenceType: 'payment',
referenceId: offer._id,
description: 'بازگشت وجه درخواست همکاری (طرف مقابل ظرف ۱۲ ساعت مقرر پاسخ نداد)'
})
await notifyUser(io, offer.sender, {
type: 'offer_commission_refunded',
title: 'بازگشت وجه به کیف پول',
description: 'طرف مقابل ظرف مهلت ۱۲ ساعته به درخواست همکاری شما پاسخ نداد؛ مبلغ به کیف پول شما بازگشت.'
})
} catch (error) {
console.error('refundExpiredOfferCommissions error for offer', offer._id, error)
}
}
return expiredOffers.length
}
module.exports = {
trackOfferMessageForCommission,
refundExpiredOfferCommissions,
OFFER_REPLY_WINDOW_MS
}