This commit is contained in:
payacom
2026-10-06 17:08:44 +03:30
parent fade8543e2
commit 367416d419
14 changed files with 6087 additions and 5937 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ const { resolveAdPrice } = require('../../../utils/adPricing')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const { deliverNotificationAd, trackAdEvent, findActiveAd, buildAdLinkedContentItem } = require('../../../utils/adDelivery')
const { resolveLinkedEntity } = require('../../../utils/resolveLinkedEntity')
const { buildPaymentPageUrl } = require('../../../utils/zarinpalPaymentPage')
const SPONSORED_TYPES = ['project', 'product', 'shop', 'billboard']
@@ -220,7 +221,7 @@ const createAd = async (req, res, next) => {
success: true,
adId: ad._id,
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}

View File

@@ -3,6 +3,7 @@ const BookingModel = require('../../../models/BookingModel')
const PaymentModel = require('../../../models/PaymentModel')
const { notifyUser } = require('../../../utils/notifyUser')
const { BOOKING_COMMISSION_RATE, creditBookingShare } = require('../../../utils/bookingCommission')
const { buildPaymentPageUrl } = require('../../../utils/zarinpalPaymentPage')
const apiKey =
process.env.ZARINPAL_MERCHANT_ID ||
@@ -59,7 +60,7 @@ const initiateBookingPayment = async (req, res) => {
await booking.save()
return res.status(200).json({
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}

View File

@@ -6,6 +6,7 @@ const UserModel = require('../../../models/UserModel')
const PaymentModel = require('../../../models/PaymentModel')
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const { buildPaymentPageUrl } = require('../../../utils/zarinpalPaymentPage')
const MERCHANT_ID = process.env.ZARINPAL_MERCHANT_ID || process.env.MERCHENT_CODE
const GIFT_MIN_AMOUNT = 2000
@@ -123,7 +124,7 @@ const initiateGiftPayment = async (req, res, next) => {
if (result?.code === 100 && result?.authority) {
return res.json({
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}
@@ -320,7 +321,7 @@ const initiateChatGift = async (req, res, next) => {
return res.json({
paidFromWallet: false,
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}

View File

@@ -53,8 +53,14 @@ const getOfferTypes = async (req, res, next) => {
]
}
const canFree =
(user.monthly_free_offer || 0) > 0 && canUseFreeCreation(user)
// رایگان بودن درخواست به «گیرنده» بستگی دارد: اگر گیرنده «دریافت هدیه» را غیرفعال
// کرده باشه، فرستنده (با سهمیه‌ی رایگانِ خودش) می‌تونه درخواست رایگان بده
const receiverForFree = req.query.userId
? await UserModel.findById(req.query.userId).select('gift_enabled offer_payment_enabled').lean()
: null
// رایگان وقتی فعاله که گیرنده «دریافت هدیه» یا «دریافت پرداختی از چت» رو غیرفعال کرده باشه
const freeEligible = receiverForFree?.gift_enabled === false || receiverForFree?.offer_payment_enabled === false
const canFree = freeEligible && canUseFreeCreation(user)
// رایگان را حذف نکن — اگر مجاز نیست، با فلگ disabled برگردان تا UI نشان دهد
offerTypes = offerTypes.map((t) => {
@@ -63,8 +69,8 @@ const getOfferTypes = async (req, res, next) => {
plain.disabled = !canFree
plain.unavailableReason = canFree
? null
: (user.monthly_free_offer || 0) <= 0
? 'سهمیه رایگان ماهانه شما تمام شده است'
: !freeEligible
? 'برای درخواست رایگان، دریافت هدیه از چت را در تنظیمات حساب غیرفعال کنید'
: 'سقف ثبت رایگان شما به پایان رسیده است'
}
return plain
@@ -206,19 +212,15 @@ const handleSuccessfulOfferPayment = async (req, res) => {
// اگر گیرنده «دریافت پرداختی از چت» رو غیرفعال کرده باشه، درخواست همکاری براش
// همیشه رایگانه — مستقل از سهمیه‌ی رایگان ماهانه‌ی فرستنده
const receiverOffersFree = reciverUser?.offer_payment_enabled === false
const receiverFreeEligible = reciverUser?.gift_enabled === false || reciverUser?.offer_payment_enabled === false
let price
if (receiverOffersFree) {
price = 0
} else if (user.monthly_free_offer > 0 && offerType === 'free' && canUseFreeCreation(user)) {
if (offerType === 'free' && receiverFreeEligible && canUseFreeCreation(user)) {
const consumed = await consumeFreeCreation(user)
if (!consumed.ok) {
return res.status(403).json(freeCreationLimitResponse())
}
price = 0
user.monthly_free_offer -= 1
await user.save()
} else if (offerType === 'free') {
return res.status(403).json({
error: true,

View File

@@ -21,9 +21,15 @@ const {
creditProjectShare
} = require('../../../utils/projectCommission')
const { isVerifiedBadge } = require('../../../utils/verifyBadge')
const { buildPaymentPageUrl } = require('../../../utils/zarinpalPaymentPage')
const apiKey = process.env.ZARINPAL_MERCHANT_ID || process.env.MERCHENT_CODE // Replace with your ZarinPal API Key
// زرین‌پال callback رو فقط وقتی قبول می‌کنه که دامنه‌اش همون دامنه‌ی ثبت‌شده‌ی مرچنت باشه
// (APP_SITE_CallBack = دامنه‌ی اصلی)؛ APP_URL (زیردامنه‌ی app) باعث خطای «دامنه مجاز نیست» می‌شه
const getCallbackBase = () =>
(process.env.APP_SITE_CallBack || process.env.APP_URL || '').replace(/\/$/, '')
// درگاه قدیمیِ REST زرین‌پال (/pg/rest/WebGate) منسوخ شده — مبلغ به API نسخه‌ی v4
// باید به ریال ارسال بشه، نه تومان (برخلاف API قدیمی)
function toRial (tomanAmount) {
@@ -108,7 +114,7 @@ const initiatePayment = async (req, res) => {
merchant_id: apiKey,
amount: toRial(price),
description: `پرداخت برای سفارش شماره ${projectId}`,
callback_url: `${process.env.APP_URL}/projects/payment?projectId=${projectId}&itemName=${itemName}&userId=${userId}` // Assuming your backend URL for handling payment callback
callback_url: `${getCallbackBase()}/projects/payment?projectId=${projectId}&itemName=${itemName}&userId=${userId}` // Assuming your backend URL for handling payment callback
},
{ headers: { 'Content-Type': 'application/json' } }
)
@@ -116,7 +122,7 @@ const initiatePayment = async (req, res) => {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
res.status(200).json({ authority: result.authority })
res.status(200).json({ authority: result.authority, paymentUrl: buildPaymentPageUrl(result.authority) })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', response.data)
@@ -246,7 +252,7 @@ const paymentRequestStepOne = async (req, res) => {
merchant_id: apiKey,
amount: toRial(acceptAmount),
description: `پرداخت برای سفارش شماره ${projectId}`,
callback_url: `${process.env.APP_URL}/projects/payment-request-step-one-callback?projectId=${projectId}&requestId=${requestId}&userId=${userId}` // Callback URL including requestId
callback_url: `${getCallbackBase()}/projects/payment-request-step-one-callback?projectId=${projectId}&requestId=${requestId}&userId=${userId}` // Callback URL including requestId
},
{ headers: { 'Content-Type': 'application/json' } }
)
@@ -254,7 +260,7 @@ const paymentRequestStepOne = async (req, res) => {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
res.status(200).json({ authority: result.authority })
res.status(200).json({ authority: result.authority, paymentUrl: buildPaymentPageUrl(result.authority) })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', response.data)
@@ -290,7 +296,7 @@ const paymentRequestStepTwo = async (req, res) => {
merchant_id: apiKey,
amount: toRial(secondInstallmentAmount),
description: `پرداخت برای سفارش شماره ${projectId}`,
callback_url: `${process.env.APP_URL}/projects/payment-request-step-two-callback?projectId=${projectId}&userId=${userId}&installmentNumber=${installmentNumber}` // Callback URL
callback_url: `${getCallbackBase()}/projects/payment-request-step-two-callback?projectId=${projectId}&userId=${userId}&installmentNumber=${installmentNumber}` // Callback URL
},
{ headers: { 'Content-Type': 'application/json' } }
)
@@ -298,7 +304,7 @@ const paymentRequestStepTwo = async (req, res) => {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
res.status(200).json({ authority: result.authority })
res.status(200).json({ authority: result.authority, paymentUrl: buildPaymentPageUrl(result.authority) })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', response.data)
@@ -335,7 +341,7 @@ const paymentRequestStepAll = async (req, res) => {
merchant_id: apiKey,
amount: toRial(secondInstallmentAmount),
description: `پرداخت برای سفارش شماره ${projectId}`,
callback_url: `${process.env.APP_URL}/projects/payment-request-step-all-callback?projectId=${projectId}&userId=${userId}` // Callback URL
callback_url: `${getCallbackBase()}/projects/payment-request-step-all-callback?projectId=${projectId}&userId=${userId}` // Callback URL
},
{ headers: { 'Content-Type': 'application/json' } }
)
@@ -343,7 +349,7 @@ const paymentRequestStepAll = async (req, res) => {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
res.status(200).json({ authority: result.authority })
res.status(200).json({ authority: result.authority, paymentUrl: buildPaymentPageUrl(result.authority) })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', response.data)
@@ -690,14 +696,14 @@ const initiateAdvertisingPayment = async (req, res) => {
merchant_id: apiKey,
amount: toRial(showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price)),
description: `پرداخت برای سفارش شماره ${advertisingId}`,
callback_url: `${process.env.APP_URL}/advertising/payment?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
callback_url: `${getCallbackBase()}/advertising/payment?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
},
{ headers: { 'Content-Type': 'application/json' } }
)
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
res.status(200).json({ authority: result.authority })
res.status(200).json({ authority: result.authority, paymentUrl: buildPaymentPageUrl(result.authority) })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', response.data)
@@ -840,14 +846,14 @@ const republishAdvertisingPayment = async (req, res) => {
merchant_id: apiKey,
amount: toRial(showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100)),
description: `پرداخت برای سفارش شماره ${advertisingId}`,
callback_url: `${process.env.APP_URL}/advertising/republish-verify-payment?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
callback_url: `${getCallbackBase()}/advertising/republish-verify-payment?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
},
{ headers: { 'Content-Type': 'application/json' } }
)
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
res.status(200).json({ authority: result.authority })
res.status(200).json({ authority: result.authority, paymentUrl: buildPaymentPageUrl(result.authority) })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', response.data)

View File

@@ -18,6 +18,8 @@ const { PROJECT_ACCEPT_RATE, PROJECT_COMPLETION_COMMISSION_RATE, creditProjectSh
const { isVerifiedBadge } = require('../../../utils/verifyBadge')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const { needsUsernameAndPassword } = require('../../../utils/authSteps')
const { canUseFreeCreation, consumeFreeCreation } = require('../../../utils/freeCreationLimit')
const { buildPaymentPageUrl } = require('../../../utils/zarinpalPaymentPage')
const OFFER_COMMISSION_RATE = 0.5
@@ -331,7 +333,7 @@ const paymentRequestAcceptWeb = async (req, res) => {
if (result?.code === 100 && result?.authority) {
return res.status(200).json({
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}
@@ -487,7 +489,7 @@ const initiateProjectCompletionPaymentWeb = async (req, res) => {
if (result?.code === 100 && result?.authority) {
return res.status(200).json({
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}
@@ -914,38 +916,30 @@ const initiateOfferPaymentWeb = async (req, res) => {
})
}
// 🎁 free offer
if (offerType === 'free' && user.monthly_free_offer > 0) {
// 🎁 رایگان: فقط وقتی مجازه که گیرنده «دریافت هدیه» یا «دریافت پرداختی از چت» رو
// غیرفعال کرده باشه، و سهمیه‌ی رایگان ماهانه‌ی فرستنده هنوز باقی باشه.
// درخواستِ پولی (offerType غیر از free) همیشه با قیمت واقعی ثبت می‌شه
if (offerType === 'free') {
const receiverFreeEligible = receiver.gift_enabled === false || receiver.offer_payment_enabled === false
if (!receiverFreeEligible) {
return res.status(403).json({
error: true,
message: 'برای درخواست رایگان، گیرنده باید دریافت هدیه یا دریافت پرداختی از چت را غیرفعال کرده باشد'
})
}
if (!canUseFreeCreation(user)) {
return res.status(403).json({ error: true, message: 'سهمیه درخواست رایگان ماهانه شما تمام شده است.' })
}
const consumed = await consumeFreeCreation(user)
if (!consumed.ok) {
return res.status(403).json({ error: true, message: 'سهمیه درخواست رایگان ماهانه شما تمام شده است.' })
}
const offer = await OfferModel.create({
sender: userId,
receiver: receiverId,
transaction_id: null
})
// 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',
offerId: offer._id
})
}
// 🎁 گیرنده «دریافت پرداختی از چت» رو غیرفعال کرده — درخواست همکاری براش همیشه
// رایگانه، مستقل از سهمیه‌ی رایگان ماهانه‌ی فرستنده
if (receiver.offer_payment_enabled === false) {
const offer = await OfferModel.create({
sender: userId,
receiver: receiverId,
transaction_id: null
})
return res.json({
type: 'free',
offerId: offer._id
})
return res.json({ type: 'free', offerId: offer._id })
}
// نوعِ «رایگان» قیمتی توی OfferTypeModel نداره (اصلاً پرایس‌دار نیست، سهمیه‌محوره) —
@@ -975,7 +969,7 @@ const initiateOfferPaymentWeb = async (req, res) => {
})
await debitWalletForPurchase(userId, walletAmount, {
referenceType: 'offer',
referenceType: 'payment',
referenceId: offer._id,
description: 'پرداخت آفر از کیف پول'
})
@@ -985,7 +979,7 @@ const initiateOfferPaymentWeb = async (req, res) => {
const amount = toRial(gatewayAmount)
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
const callbackUrl = `https://api.modstagram.com/api/v1/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}&walletAmount=${walletAmount}${platformSuffix}`
const callbackUrl = `${(process.env.APP_SITE_CallBack || process.env.APP_URL || '').replace(/\/$/, '')}/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}&walletAmount=${walletAmount}${platformSuffix}`
const zarinpalRes = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
@@ -1010,7 +1004,7 @@ const initiateOfferPaymentWeb = async (req, res) => {
if (result.code === 100) {
return res.json({
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}
@@ -1086,7 +1080,7 @@ const handleOfferPaymentCallback = async (req, res) => {
// درگاه فقط بخشِ باقی‌مونده رو تایید کرد؛ حالا که پرداخت قطعاً موفق شد، سهمِ کیف
// پول هم واقعاً کسر می‌شه (نگاه کن به utils/walletSplit.js)
await debitWalletForPurchase(userId, usedWalletAmount, {
referenceType: 'offer',
referenceType: 'payment',
referenceId: offer._id,
description: 'پرداخت آفر از کیف پول'
})

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,8 @@ const OrderModel = require('../../../models/OrderModel')
const UserModel = require('../../../models/UserModel')
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const WalletWithdrawalRequestModel = require('../../../models/WalletWithdrawalRequestModel')
const PaymentModel = require('../../../models/PaymentModel')
const { buildPaymentPageUrl } = require('../../../utils/zarinpalPaymentPage')
const MERCHANT_ID = process.env.ZARINPAL_MERCHANT_ID || process.env.MERCHENT_CODE
const GIFT_WITHDRAWAL_MIN = 1000000
@@ -199,49 +201,89 @@ const initiateWalletCharge = async (req, res, next) => {
return walletError(res, 422, 'مبلغ نامعتبر است')
}
const callbackBase = process.env.APP_SITE_CallBack || process.env.APP_URL
const callbackBase = (process.env.APP_SITE_CallBack || process.env.APP_URL || '').replace(/\/$/, '')
if (!callbackBase) {
return walletError(res, 500, 'آدرس بازگشت پرداخت تنظیم نشده است')
}
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
// کال‌بک باید یک آدرس ثابت و بدون query string باشد تا با دامنه/Callback
// ثبت‌شده در زرین‌پال دقیقاً یکی باشد. اطلاعات پرداخت را در دیتابیس نگه می‌داریم.
const callbackUrl = `${callbackBase}/wallet/charge/callback`
const paymentType = platform === 'android' ? 'wallet_charge_android' : 'wallet_charge_web'
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id: MERCHANT_ID,
amount: toRial(chargeAmount),
description: 'شارژ کیف پول مدستاگرام',
callback_url: `${callbackBase}/wallet/charge/callback?userId=${req.user._id}&amount=${chargeAmount}${platformSuffix}`
callback_url: callbackUrl
},
{ headers: { 'Content-Type': 'application/json' } }
{ headers: { 'Content-Type': 'application/json' }, timeout: 20000 }
)
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
return res.json({ authority: result.authority })
await PaymentModel.create({
amount: chargeAmount,
status: 'pending',
authority: result.authority,
user_id: req.user._id,
type: paymentType
})
return res.json({
authority: result.authority,
paymentUrl: `${buildPaymentPageUrl(result.authority)}`
})
}
console.error('ZarinPal wallet charge initiate error:', response.data)
return walletError(res, 502, 'درگاه پرداخت پاسخ نامعتبر داد. لطفاً دوباره تلاش کنید.')
} catch (error) {
console.error('initiateWalletCharge error:', error?.response?.data || error?.message || error)
next(error)
}
}
const handleWalletChargeCallback = async (req, res) => {
const { Authority, Status, userId, amount, platform } = req.query
const isAndroid = platform === 'android'
const failRedirect = isAndroid
? 'modstagram://FailedPay?type=wallet'
: `${process.env.APP_SITE}/settings/wallet?charge=failed`
const successRedirect = isAndroid
? 'modstagram://SuccessPay?type=wallet'
: `${process.env.APP_SITE}/settings/wallet?charge=success`
const { Authority, Status } = req.query
let payment = null
let failRedirect = `${process.env.APP_SITE}/settings/wallet/payment/failed`
let successRedirect = `${process.env.APP_SITE}/settings/wallet/payment/success`
try {
if (Status !== 'OK') return res.redirect(failRedirect)
if (!Authority) return res.redirect(failRedirect)
payment = await PaymentModel.findOne({
authority: Authority,
type: { $in: ['wallet_charge_android', 'wallet_charge_web'] }
})
if (!payment) return res.redirect(failRedirect)
const chargeAmount = Number(payment.amount)
const isAndroid = payment.type === 'wallet_charge_android'
failRedirect = isAndroid
? `modstagram://FailedPay?type=wallet&amount=${encodeURIComponent(chargeAmount)}`
: `${process.env.APP_SITE}/settings/wallet/payment/failed?amount=${encodeURIComponent(chargeAmount)}`
successRedirect = isAndroid
? `modstagram://SuccessPay?type=wallet&amount=${encodeURIComponent(chargeAmount)}`
: `${process.env.APP_SITE}/settings/wallet/payment/success?amount=${encodeURIComponent(chargeAmount)}`
if (Status !== 'OK') {
payment.status = 'failed'
await payment.save()
return res.redirect(failRedirect)
}
// اگر callback دوباره ارسال شد، شارژ دوباره به کیف پول اضافه نشود.
if (payment.status === 'successful') {
return res.redirect(successRedirect)
}
const chargeAmount = Number(amount)
const verify = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
@@ -249,41 +291,60 @@ const handleWalletChargeCallback = async (req, res) => {
authority: Authority,
amount: toRial(chargeAmount)
},
{ headers: { 'Content-Type': 'application/json' } }
{ headers: { 'Content-Type': 'application/json' }, timeout: 20000 }
)
const data = verify.data?.data
if (data?.code === 100 || data?.code === 101) {
const user = await UserModel.findById(userId)
if (!user) return res.redirect(failRedirect)
const user = await UserModel.findById(payment.user_id)
if (!user) {
payment.status = 'failed'
await payment.save()
return res.redirect(failRedirect)
}
await WalletTransactionModel.create({
user: userId,
user: payment.user_id,
bucket: 'charged',
type: 'credit',
amount: chargeAmount,
referenceType: 'payment',
referenceId: payment._id,
description: 'شارژ کیف پول'
})
const bonus = Math.round(chargeAmount * CHARGE_BONUS_RATE)
if (bonus > 0) {
await WalletTransactionModel.create({
user: userId,
user: payment.user_id,
bucket: 'gift',
type: 'credit',
amount: bonus,
referenceType: 'payment',
referenceId: payment._id,
description: 'پاداش شارژ کیف پول (۵٪)'
})
}
payment.status = 'successful'
await payment.save()
return res.redirect(successRedirect)
}
payment.status = 'failed'
await payment.save()
return res.redirect(failRedirect)
} catch (error) {
console.error('handleWalletChargeCallback error:', error?.response?.data || error.message)
console.error('handleWalletChargeCallback error:', error?.response?.data || error?.message || error)
if (payment) {
try {
payment.status = 'failed'
await payment.save()
} catch (saveError) {
console.error('Failed to update wallet payment status:', saveError?.message || saveError)
}
}
return res.redirect(failRedirect)
}
}