full app
This commit is contained in:
@@ -5,7 +5,7 @@ const GiftModel = require('../../../models/GiftModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const PaymentModel = require('../../../models/PaymentModel')
|
||||
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
|
||||
const { getBucketBalance } = require('../wallet/walletController')
|
||||
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
|
||||
|
||||
const MERCHANT_ID = process.env.ZARINPAL_MERCHANT_ID || process.env.MERCHENT_CODE
|
||||
const GIFT_MIN_AMOUNT = 2000
|
||||
@@ -39,10 +39,22 @@ async function validateGiftTarget (senderId, receiverId, amount) {
|
||||
|
||||
const GIFT_USER_SELECT = 'profile_image user_level first_name last_name user_name is_verified verify_badge'
|
||||
|
||||
async function creditGiftReceiver (receiverId, giftId, giftAmount) {
|
||||
await WalletTransactionModel.create({
|
||||
user: receiverId,
|
||||
bucket: 'gift',
|
||||
type: 'credit',
|
||||
amount: Math.round(giftAmount * (1 - GIFT_COMMISSION_RATE)),
|
||||
referenceType: 'payment',
|
||||
referenceId: giftId,
|
||||
description: 'هدیه دریافتی از کاربر دیگر (پس از کسر ۱۰٪ کارمزد)'
|
||||
})
|
||||
}
|
||||
|
||||
const initiateGiftPayment = async (req, res, next) => {
|
||||
try {
|
||||
const senderId = req.user._id
|
||||
const { receiverId, amount, message } = req.body
|
||||
const { receiverId, amount, message, platform, useWallet } = req.body
|
||||
|
||||
if (!receiverId) {
|
||||
return res.status(400).json({ error: true, message: 'کاربر گیرنده مشخص نشده است' })
|
||||
@@ -64,18 +76,43 @@ const initiateGiftPayment = async (req, res, next) => {
|
||||
return res.status(403).json({ error: true, message: 'این کاربر دریافت هدیه را غیرفعال کرده است' })
|
||||
}
|
||||
|
||||
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(senderId, giftAmount, useWallet)
|
||||
|
||||
if (fullyCovered) {
|
||||
await debitWalletForPurchase(senderId, walletAmount, {
|
||||
referenceType: 'payment',
|
||||
description: 'پرداخت هدیه از کیف پول'
|
||||
})
|
||||
|
||||
const gift = await GiftModel.create({
|
||||
sender: senderId,
|
||||
receiver: receiverId,
|
||||
amount: giftAmount,
|
||||
message: message || null,
|
||||
paymentMethod: 'wallet'
|
||||
})
|
||||
|
||||
await creditGiftReceiver(receiverId, gift._id, giftAmount)
|
||||
|
||||
return res.json({
|
||||
paidFromWallet: true,
|
||||
gift: { _id: gift._id, amount: gift.amount, message: gift.message }
|
||||
})
|
||||
}
|
||||
|
||||
const callbackBase = process.env.APP_SITE_CallBack || process.env.APP_URL
|
||||
if (!callbackBase) {
|
||||
return res.status(500).json({ error: true, message: 'آدرس بازگشت پرداخت تنظیم نشده است' })
|
||||
}
|
||||
|
||||
const callbackUrl = `${callbackBase}/gifts/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&message=${encodeURIComponent(message || '')}`
|
||||
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
|
||||
const callbackUrl = `${callbackBase}/gifts/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&walletAmount=${walletAmount}&message=${encodeURIComponent(message || '')}${platformSuffix}`
|
||||
|
||||
const response = await axios.post(
|
||||
'https://api.zarinpal.com/pg/v4/payment/request.json',
|
||||
{
|
||||
merchant_id: MERCHANT_ID,
|
||||
amount: toRial(giftAmount),
|
||||
amount: toRial(gatewayAmount),
|
||||
description: `هدیه ویژه به ${receiver.first_name || receiver.user_name}`,
|
||||
callback_url: callbackUrl
|
||||
},
|
||||
@@ -98,19 +135,25 @@ const initiateGiftPayment = async (req, res, next) => {
|
||||
}
|
||||
|
||||
const handleGiftPaymentCallback = async (req, res) => {
|
||||
const { Authority, Status, senderId, receiverId, amount, message } = req.query
|
||||
const failRedirect = `${process.env.APP_SITE}/gift/payment/failed${receiverId ? `?receiverId=${receiverId}` : ''}`
|
||||
const { Authority, Status, senderId, receiverId, amount, walletAmount, message, platform } = req.query
|
||||
const isAndroid = platform === 'android'
|
||||
const failRedirect = isAndroid
|
||||
? `modstagram://FailedPay?type=gift${receiverId ? `&receiverId=${receiverId}` : ''}`
|
||||
: `${process.env.APP_SITE}/gift/payment/failed${receiverId ? `?receiverId=${receiverId}` : ''}`
|
||||
|
||||
try {
|
||||
if (Status !== 'OK') return res.redirect(failRedirect)
|
||||
|
||||
const giftAmount = Number(amount)
|
||||
const usedWalletAmount = Number(walletAmount) || 0
|
||||
const gatewayAmount = giftAmount - usedWalletAmount
|
||||
|
||||
const verify = await axios.post(
|
||||
'https://api.zarinpal.com/pg/v4/payment/verify.json',
|
||||
{
|
||||
merchant_id: MERCHANT_ID,
|
||||
authority: Authority,
|
||||
amount: toRial(giftAmount)
|
||||
amount: toRial(gatewayAmount)
|
||||
},
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
@@ -120,8 +163,15 @@ const handleGiftPaymentCallback = async (req, res) => {
|
||||
return res.redirect(failRedirect)
|
||||
}
|
||||
|
||||
// پرداخت درگاه قطعاً موفق شد — همین حالا سهمِ کیف پول هم کسر میشه (نگاه کن به
|
||||
// utils/walletSplit.js)
|
||||
await debitWalletForPurchase(senderId, usedWalletAmount, {
|
||||
referenceType: 'payment',
|
||||
description: 'پرداخت هدیه از کیف پول'
|
||||
})
|
||||
|
||||
const payment = await PaymentModel.create({
|
||||
amount: giftAmount,
|
||||
amount: gatewayAmount,
|
||||
status: 'successful',
|
||||
authority: Authority,
|
||||
user_id: senderId,
|
||||
@@ -133,22 +183,20 @@ const handleGiftPaymentCallback = async (req, res) => {
|
||||
receiver: receiverId,
|
||||
amount: giftAmount,
|
||||
message: message ? decodeURIComponent(message) : null,
|
||||
transaction_id: payment._id
|
||||
transaction_id: payment._id,
|
||||
paymentMethod: usedWalletAmount > 0 ? 'mixed' : 'online'
|
||||
})
|
||||
|
||||
await WalletTransactionModel.create({
|
||||
user: receiverId,
|
||||
bucket: 'gift',
|
||||
type: 'credit',
|
||||
amount: Math.round(giftAmount * (1 - GIFT_COMMISSION_RATE)),
|
||||
referenceType: 'payment',
|
||||
referenceId: gift._id,
|
||||
description: 'هدیه دریافتی از کاربر دیگر (پس از کسر ۱۰٪ کارمزد)'
|
||||
})
|
||||
await creditGiftReceiver(receiverId, gift._id, giftAmount)
|
||||
|
||||
const receiverUser = await UserModel.findById(receiverId).select('user_name').lean()
|
||||
const usernameParam = receiverUser?.user_name ? `&receiverUsername=${encodeURIComponent(receiverUser.user_name)}` : ''
|
||||
|
||||
if (isAndroid) {
|
||||
const usernameParam = receiverUser?.user_name ? `&username=${encodeURIComponent(receiverUser.user_name)}` : ''
|
||||
return res.redirect(`modstagram://SuccessPay?type=gift&giftId=${gift._id}&receiverId=${receiverId}${usernameParam}`)
|
||||
}
|
||||
|
||||
const usernameParam = receiverUser?.user_name ? `&receiverUsername=${encodeURIComponent(receiverUser.user_name)}` : ''
|
||||
return res.redirect(`${process.env.APP_SITE}/gift/payment/success?giftId=${gift._id}&receiverId=${receiverId}${usernameParam}`)
|
||||
} catch (error) {
|
||||
console.error('handleGiftPaymentCallback error:', error?.response?.data || error.message)
|
||||
@@ -217,18 +265,15 @@ const replyToGift = async (req, res, next) => {
|
||||
const initiateChatGift = async (req, res, next) => {
|
||||
try {
|
||||
const senderId = req.user._id
|
||||
const { receiverId, amount, message, return_path: returnPath } = req.body
|
||||
const { receiverId, amount, message, return_path: returnPath, platform, useWallet } = req.body
|
||||
|
||||
const { error, receiver, giftAmount } = await validateGiftTarget(senderId, receiverId, amount)
|
||||
if (error) return res.status(error.status).json({ error: true, message: error.message })
|
||||
|
||||
const chargedBalance = await getBucketBalance(senderId, 'charged')
|
||||
if (chargedBalance >= giftAmount) {
|
||||
await WalletTransactionModel.create({
|
||||
user: senderId,
|
||||
bucket: 'charged',
|
||||
type: 'debit',
|
||||
amount: giftAmount,
|
||||
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(senderId, giftAmount, useWallet)
|
||||
|
||||
if (fullyCovered) {
|
||||
await debitWalletForPurchase(senderId, walletAmount, {
|
||||
referenceType: 'payment',
|
||||
description: 'پرداخت هدیه از کیف پول'
|
||||
})
|
||||
@@ -256,13 +301,14 @@ const initiateChatGift = async (req, res, next) => {
|
||||
}
|
||||
|
||||
const safeReturnPath = typeof returnPath === 'string' && returnPath.startsWith('/') ? returnPath : '/'
|
||||
const callbackUrl = `${callbackBase}/gifts/chat/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&message=${encodeURIComponent(message || '')}&returnPath=${encodeURIComponent(safeReturnPath)}`
|
||||
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
|
||||
const callbackUrl = `${callbackBase}/gifts/chat/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&walletAmount=${walletAmount}&message=${encodeURIComponent(message || '')}&returnPath=${encodeURIComponent(safeReturnPath)}${platformSuffix}`
|
||||
|
||||
const response = await axios.post(
|
||||
'https://api.zarinpal.com/pg/v4/payment/request.json',
|
||||
{
|
||||
merchant_id: MERCHANT_ID,
|
||||
amount: toRial(giftAmount),
|
||||
amount: toRial(gatewayAmount),
|
||||
description: `هدیه در چت به ${receiver.first_name || receiver.user_name}`,
|
||||
callback_url: callbackUrl
|
||||
},
|
||||
@@ -286,20 +332,26 @@ const initiateChatGift = async (req, res, next) => {
|
||||
}
|
||||
|
||||
const handleChatGiftPaymentCallback = async (req, res) => {
|
||||
const { Authority, Status, senderId, receiverId, amount, message, returnPath } = req.query
|
||||
const { Authority, Status, senderId, receiverId, amount, walletAmount, message, returnPath, platform } = req.query
|
||||
const isAndroid = platform === 'android'
|
||||
const safeReturnPath = typeof returnPath === 'string' && returnPath.startsWith('/') ? decodeURIComponent(returnPath) : '/'
|
||||
const failRedirect = `${process.env.APP_SITE}${safeReturnPath}?giftPaymentFailed=1`
|
||||
const failRedirect = isAndroid
|
||||
? `modstagram://FailedPay?type=gift${receiverId ? `&receiverId=${receiverId}` : ''}`
|
||||
: `${process.env.APP_SITE}${safeReturnPath}?giftPaymentFailed=1`
|
||||
|
||||
try {
|
||||
if (Status !== 'OK') return res.redirect(failRedirect)
|
||||
|
||||
const giftAmount = Number(amount)
|
||||
const usedWalletAmount = Number(walletAmount) || 0
|
||||
const gatewayAmount = giftAmount - usedWalletAmount
|
||||
|
||||
const verify = await axios.post(
|
||||
'https://api.zarinpal.com/pg/v4/payment/verify.json',
|
||||
{
|
||||
merchant_id: MERCHANT_ID,
|
||||
authority: Authority,
|
||||
amount: toRial(giftAmount)
|
||||
amount: toRial(gatewayAmount)
|
||||
},
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
@@ -309,8 +361,13 @@ const handleChatGiftPaymentCallback = async (req, res) => {
|
||||
return res.redirect(failRedirect)
|
||||
}
|
||||
|
||||
await debitWalletForPurchase(senderId, usedWalletAmount, {
|
||||
referenceType: 'payment',
|
||||
description: 'پرداخت هدیه از کیف پول'
|
||||
})
|
||||
|
||||
const payment = await PaymentModel.create({
|
||||
amount: giftAmount,
|
||||
amount: gatewayAmount,
|
||||
status: 'successful',
|
||||
authority: Authority,
|
||||
user_id: senderId,
|
||||
@@ -325,12 +382,20 @@ const handleChatGiftPaymentCallback = async (req, res) => {
|
||||
amount: giftAmount,
|
||||
message: decodedMessage,
|
||||
transaction_id: payment._id,
|
||||
paymentMethod: 'online',
|
||||
paymentMethod: usedWalletAmount > 0 ? 'mixed' : 'online',
|
||||
viaChatMessage: true,
|
||||
opened: false,
|
||||
openedAt: null
|
||||
})
|
||||
|
||||
if (isAndroid) {
|
||||
const receiverUser = await UserModel.findById(receiverId).select('user_name').lean()
|
||||
const usernameParam = receiverUser?.user_name ? `&username=${encodeURIComponent(receiverUser.user_name)}` : ''
|
||||
return res.redirect(
|
||||
`modstagram://SuccessPay?type=gift&giftId=${gift._id}&receiverId=${receiverId}${usernameParam}`
|
||||
)
|
||||
}
|
||||
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}${safeReturnPath}?giftId=${gift._id}&giftAmount=${giftAmount}&giftMessage=${encodeURIComponent(decodedMessage || '')}`
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user