This commit is contained in:
payacom
2026-10-01 21:12:05 +03:30
parent 806471241e
commit d4ebc20aa8
41 changed files with 1015 additions and 135 deletions

View File

@@ -17,6 +17,7 @@ const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const { PROJECT_ACCEPT_RATE, PROJECT_COMPLETION_COMMISSION_RATE, creditProjectShare } = require('../../../utils/projectCommission')
const { isVerifiedBadge } = require('../../../utils/verifyBadge')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const { needsUsernameAndPassword } = require('../../../utils/authSteps')
const OFFER_COMMISSION_RATE = 0.5
@@ -108,8 +109,8 @@ const initiatePaymentWeb = async (req, res) => {
message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.'
})
}
user.daily_free_request -= 1
await user.save()
// updateOne + $inc به‌جای user.save() — رجوع کنید به توضیح مشابه در initiateOfferPaymentWeb
await UserModel.updateOne({ _id: userId }, { $inc: { daily_free_request: -1 } })
const project = await ProjectModel.findById(projectId)
if (!project) {
return res.status(404).json({ error: true, message: 'پروژه یافت نشد' })
@@ -573,7 +574,8 @@ const initiateAdvertisingPaymentWeb = async (req, res) => {
if (item.name === 'free' && item.price === 0) {
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
// updateOne + $inc به‌جای user.save() — رجوع کنید به توضیح مشابه در initiateOfferPaymentWeb
await UserModel.updateOne({ _id: userId }, { $inc: { free_billboard_count: 1 } })
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Advertising not found')
@@ -727,7 +729,8 @@ const republishAdvertisingPaymentWeb = async (req, res) => {
if (item.name === 'free' && item.price === 0) {
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
// updateOne + $inc به‌جای user.save() — رجوع کنید به توضیح مشابه در initiateOfferPaymentWeb
await UserModel.updateOne({ _id: userId }, { $inc: { free_billboard_count: 1 } })
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Advertising not found')
@@ -874,7 +877,13 @@ const initiateOfferPaymentWeb = async (req, res) => {
const receiver = await UserModel.findById(receiverId)
if (!user || !receiver) { return res.status(404).json({ error: 'User not found' }) }
const price = await getOfferPriceFromDatabase(offerType) // تومان
if (needsUsernameAndPassword(user)) {
return res.status(403).json({
error: true,
type: 'needs_username_password',
message: 'برای ثبت درخواست همکاری باید نام‌کاربری و رمز عبور خود را تکمیل کنید'
})
}
// 🎁 free offer
if (offerType === 'free' && user.monthly_free_offer > 0) {
@@ -884,8 +893,10 @@ const initiateOfferPaymentWeb = async (req, res) => {
transaction_id: null
})
user.monthly_free_offer -= 1
await user.save()
// updateOne + $inc به‌جای user.save() — save() کل سند رو ری‌ولیدیت می‌کنه و برای
// کاربرهایی که مقدار قدیمی/نامعتبر توی فیلدهای دیگه (مثلاً income_range) دارن
// با ValidationError شکست می‌خوره، حتی وقتی فقط monthly_free_offer تغییر کرده
await UserModel.updateOne({ _id: userId }, { $inc: { monthly_free_offer: -1 } })
return res.json({
type: 'free',
@@ -908,6 +919,11 @@ const initiateOfferPaymentWeb = async (req, res) => {
})
}
// نوعِ «رایگان» قیمتی توی OfferTypeModel نداره (اصلاً پرایس‌دار نیست، سهمیه‌محوره) —
// پس این لوکاپ فقط باید برای نوع‌های واقعاً پولی اجرا بشه، بعد از هر دو حالتِ بالا؛
// قبلاً بی‌قید-و-شرط بالای تابع اجرا می‌شد و برای «رایگان» با خطای «not found» throw می‌کرد
const price = await getOfferPriceFromDatabase(offerType) // تومان
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(userId, price, useWallet)
// کاملاً از کیف پول پوشش داده شد — اصلاً نیازی به درگاه نیست

View File

@@ -14,7 +14,7 @@ function toRial (tomanAmount) {
const initiateOrderPayment = async (req, res) => {
try {
const { orderId, useWallet } = req.body
const { orderId, useWallet, platform } = req.body
if (!orderId) {
return res.status(422).json({ error: true, message: 'سفارش مشخص نشده است' })
}
@@ -71,13 +71,14 @@ const initiateOrderPayment = async (req, res) => {
return res.status(500).json({ error: true, message: 'آدرس بازگشت پرداخت تنظیم نشده است' })
}
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id: MERCHANT_ID,
amount: toRial(gatewayAmount),
description: `پرداخت سفارش شماره ${order._id}`,
callback_url: `${callbackBase}/payment/shop-order/callback?orderId=${order._id}`
callback_url: `${callbackBase}/payment/shop-order/callback?orderId=${order._id}${platformSuffix}`
},
{ headers: { 'Content-Type': 'application/json' } }
)
@@ -101,9 +102,18 @@ const initiateOrderPayment = async (req, res) => {
}
const handleOrderPaymentCallback = async (req, res) => {
const { orderId, Authority, Status } = req.query
const failRedirect = `${process.env.APP_SITE}/shops/payment/failed?orderId=${orderId || ''}`
const successRedirect = (id) => `${process.env.APP_SITE}/shops/payment/success?orderId=${id}`
const { orderId, Authority, Status, platform } = req.query
// اندروید: نتیجه با دیپ‌لینکِ اپ (modstagram://) برگردونده می‌شه، نه یه صفحه‌ی وب —
// عینِ همه‌ی مسیرهای پرداختِ دیگه (آفر/بیلبورد/پروژه). قبلاً این مسیر فقط به وب
// ریدایرکت می‌کرد، یعنی اپ هیچ‌وقت نمی‌فهمید پرداخت واقعاً موفق شده یا نه
const isAndroid = platform === 'android'
const failRedirect = isAndroid
? `modstagram://FailedPay?type=shop&orderId=${orderId || ''}`
: `${process.env.APP_SITE}/shops/payment/failed?orderId=${orderId || ''}`
const successRedirect = (id) =>
isAndroid
? `modstagram://SuccessPay?type=shop&orderId=${id}`
: `${process.env.APP_SITE}/shops/payment/success?orderId=${id}`
const io = req.app.get('io')