This commit is contained in:
payacom
2026-09-03 16:06:02 +03:30
parent 3f416a7a60
commit 90c5f94b57
31 changed files with 3968 additions and 4283 deletions

View File

@@ -12,15 +12,29 @@ const AdvertisingModel = require('../../../models/AdvertisingModel')
const {
canUseFreeCreation,
consumeFreeCreation,
freeCreationLimitResponse,
freeCreationLimitResponse
} = require('../../../utils/freeCreationLimit')
const {
PROJECT_ACCEPT_RATE,
PROJECT_COMPLETION_COMMISSION_RATE,
creditProjectShare
} = require('../../../utils/projectCommission')
const { isVerifiedBadge } = require('../../../utils/verifyBadge')
const apiKey = process.env.MERCHENT_CODE // Replace with your ZarinPal API Key
const apiKey = process.env.ZARINPAL_MERCHANT_ID || process.env.MERCHENT_CODE // Replace with your ZarinPal API Key
// درگاه قدیمیِ REST زرین‌پال (/pg/rest/WebGate) منسوخ شده — مبلغ به API نسخه‌ی v4
// باید به ریال ارسال بشه، نه تومان (برخلاف API قدیمی)
function toRial (tomanAmount) {
return Number(tomanAmount) * 10
}
// کاربرِ بدون تیک وریفای طلایی/سبز/آبی باید از پنل ادمین تایید بشه تا پروژه/ویترینِ
// پرداخت‌شده‌ش مستقیم توی اپ/وب نمایش داده نشه
async function resolveApprovalStatus (userId, acceptedStatus, pendingStatus = 'pending') {
const user = await UserModel.findById(userId).select('verify_badge').lean()
return isVerifiedBadge(user?.verify_badge) ? acceptedStatus : pendingStatus
}
// Function to get price based on item name from database
const getPriceFromDatabase = async (itemName) => {
@@ -68,28 +82,31 @@ const initiatePayment = async (req, res) => {
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = 'temp_accept' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', projectId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.' })
}
} else {
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: apiKey,
Amount: Number(price),
Description: `پرداخت برای سفارش شماره ${projectId}`,
CallbackURL: `${process.env.APP_URL}/projects/payment?projectId=${projectId}&itemName=${itemName}&userId=${userId}` // Assuming your backend URL for handling payment callback
})
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
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
},
{ headers: { 'Content-Type': 'application/json' } }
)
const data = response.data
if (data.Status === 100) {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
const authority = data.Authority // The payment authority (شناسه پرداخت)
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
}
@@ -112,16 +129,15 @@ const handlePaymentCallback = async (req, res) => {
const userId = req.query.userId // Get project ID from session
// Clear project ID from session
// Query ZarinPal API to verify payment status
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const price = await getPriceFromDatabase(itemName) // Get price from database
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: Number(price)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{ merchant_id: apiKey, authority, amount: toRial(price) },
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
const payment = new PaymentModel({
amount: Number(price),
status: 'successful',
@@ -142,7 +158,7 @@ const handlePaymentCallback = async (req, res) => {
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = 'temp_accept' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
// Send event based on payment status
@@ -189,95 +205,6 @@ const handlePaymentCallback = async (req, res) => {
}
}
// Bazaar
const handleSuccessfulPayment = async (req, res) => {
try {
const { projectId } = req.body
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const project = await ProjectModel.findById(projectId)
if (!project) {
res.status(404).send('Project not found')
return
}
const price = await getPriceFromDatabase(project.project_type)
// Create a new payment entry
const payment = new PaymentModel({
amount: Number(price),
status: 'successful',
authority: '', // Add authority if available
user_id: userId,
project_id: projectId,
type: 'create',
installment_step: null
})
await payment.save()
// Update the project status to 'done' or 'paid'
project.payment_status = 'done'
project.status = 'temp_accept'
await project.save()
// Emit a success event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Payment successful' })
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({ message: 'Payment recorded successfully', projectId, price })
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
// Controller function for handling failed payment
const handleFailedPayment = async (req, res) => {
try {
const { projectId } = req.body
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const project = await ProjectModel.findById(projectId)
if (!project) {
res.status(404).send('Project not found')
return
}
const price = await getPriceFromDatabase(project.project_type)
// Create a new payment entry
const payment = new PaymentModel({
amount: Number(price),
status: 'failed',
authority: '', // Add authority if available
user_id: userId,
project_id: projectId,
type: 'create'
})
await payment.save()
// Emit a failure event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Payment failed' })
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({ message: 'Payment failed', projectId, price })
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
const paymentRequestStepOne = async (req, res) => {
try {
const token = req.header('Authorization').split(' ')[1]
@@ -300,21 +227,24 @@ const paymentRequestStepOne = async (req, res) => {
const acceptAmount = Math.round(Number(request.price) * PROJECT_ACCEPT_RATE)
// Proceed with ZarinPal payment request
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: apiKey,
Amount: parseInt(acceptAmount + acceptAmount / 100 * 9),
Description: `پرداخت برای سفارش شماره ${projectId}`,
CallbackURL: `${process.env.APP_URL}/projects/payment-request-step-one-callback?projectId=${projectId}&requestId=${requestId}&userId=${userId}` // Callback URL including requestId
})
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
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
},
{ headers: { 'Content-Type': 'application/json' } }
)
const data = response.data
if (data.Status === 100) {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
const authority = data.Authority // The payment authority
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
} catch (error) {
@@ -341,21 +271,24 @@ const paymentRequestStepTwo = async (req, res) => {
const totalAmount = project.final_price
const secondInstallmentAmount = totalAmount / 4
// Proceed with ZarinPal payment request for the second installment
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: apiKey,
Amount: parseInt(secondInstallmentAmount + secondInstallmentAmount / 100 * 9),
Description: `پرداخت برای سفارش شماره ${projectId}`,
CallbackURL: `${process.env.APP_URL}/projects/payment-request-step-two-callback?projectId=${projectId}&userId=${userId}&installmentNumber=${installmentNumber}` // Callback URL
})
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
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
},
{ headers: { 'Content-Type': 'application/json' } }
)
const data = response.data
if (data.Status === 100) {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
const authority = data.Authority // The payment authority
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
} catch (error) {
@@ -383,21 +316,24 @@ const paymentRequestStepAll = async (req, res) => {
const secondInstallmentAmount = totalAmount / 2
// Proceed with ZarinPal payment request for the second installment
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: apiKey,
Amount: parseInt(secondInstallmentAmount + secondInstallmentAmount / 100 * 9),
Description: `پرداخت برای سفارش شماره ${projectId}`,
CallbackURL: `${process.env.APP_URL}/projects/payment-request-step-all-callback?projectId=${projectId}&userId=${userId}` // Callback URL
})
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
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
},
{ headers: { 'Content-Type': 'application/json' } }
)
const data = response.data
if (data.Status === 100) {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
const authority = data.Authority // The payment authority
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
} catch (error) {
@@ -415,20 +351,19 @@ const paymentRequestStepOneCallback = async (req, res) => {
const userId = req.query.userId
// Verify payment status with ZarinPal
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const request = await RequestModel.findById(requestId)
if (!request) {
return res.status(404).json({ error: 'Request not found' })
}
const price = Math.round(Number(request.price) * PROJECT_ACCEPT_RATE)
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: parseInt(price + price / 100 * 9)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{ merchant_id: apiKey, authority, amount: toRial(price) },
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
// Payment verification is successful
const payment = new PaymentModel({
amount: price,
@@ -517,21 +452,20 @@ const paymentRequestStepTwoCallback = async (req, res) => {
const installmentNumber = req.query.installmentNumber
// Verify payment status with ZarinPal
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const project = await ProjectModel.findById(projectId)
if (!project) {
return res.status(404).json({ error: 'Project not found' })
}
const totalAmount = project.final_price
const secondInstallmentAmount = totalAmount / 4
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: parseInt(secondInstallmentAmount + secondInstallmentAmount / 100 * 9)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{ merchant_id: apiKey, authority, amount: toRial(secondInstallmentAmount) },
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
// Payment verification is successful
const payment = new PaymentModel({
amount: secondInstallmentAmount,
@@ -612,21 +546,20 @@ const paymentRequestStepAllCallback = async (req, res) => {
const userId = req.query.userId
// Verify payment status with ZarinPal
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const project = await ProjectModel.findById(projectId)
if (!project) {
return res.status(404).json({ error: 'Project not found' })
}
const totalAmount = project.final_price
const secondInstallmentAmount = totalAmount / 2
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: parseInt(secondInstallmentAmount + secondInstallmentAmount / 100 * 9)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{ merchant_id: apiKey, authority, amount: toRial(secondInstallmentAmount) },
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
// Payment verification is successful
const payment = new PaymentModel({
amount: secondInstallmentAmount,
@@ -726,27 +659,30 @@ const initiateAdvertisingPayment = async (req, res) => {
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = 'temp_accept' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.' })
}
} else {
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: apiKey,
Amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
Description: `پرداخت برای سفارش شماره ${advertisingId}`,
CallbackURL: `${process.env.APP_URL}/advertising/payment?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
})
const data = response.data
if (data.Status === 100) {
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
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
},
{ headers: { 'Content-Type': 'application/json' } }
)
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
const authority = data.Authority // The payment authority (شناسه پرداخت)
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
}
@@ -769,16 +705,19 @@ const handleAdvertisingPaymentCallback = async (req, res) => {
// Clear project ID from session
// Query ZarinPal API to verify payment status
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
merchant_id: apiKey,
authority,
amount: toRial(showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price))
},
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
status: 'successful',
@@ -799,7 +738,7 @@ const handleAdvertisingPaymentCallback = async (req, res) => {
return
}
advertising.payment_status = 'done' // Set advertising status to 'done'
advertising.status = 'temp_accept' // Set advertising status to 'done'
advertising.status = await resolveApprovalStatus(userId, 'temp_accept') // Set advertising status to 'done'
await advertising.save()
// Send event based on payment status
@@ -846,96 +785,6 @@ const handleAdvertisingPaymentCallback = async (req, res) => {
}
}
// Controller function for handling successful advertising payment
const handleSuccessfulAdvertisingPayment = async (req, res) => {
try {
const { advertisingId } = req.body
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const showDiscount = req.query.showDiscount // Get project ID from session
const advertising = await AdvertisingModel.findById(advertisingId)
if (!advertising) {
res.status(404).send('Advertising not found')
return
}
const price = await getAdvertisingPriceFromDatabase(advertising.type)
// Create a new payment entry
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
status: 'successful',
authority: '', // Add authority if available
user_id: userId,
advertising_id: advertisingId,
type: 'advertising',
installment_step: null
})
await payment.save()
// Update the advertising status to 'done' or 'paid'
advertising.payment_status = 'done'
advertising.status = 'temp_accept'
await advertising.save()
// Emit a success event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Payment successful' })
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({ message: 'Payment recorded successfully', advertisingId, price })
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
// Controller function for handling failed advertising payment
const handleFailedAdvertisingPayment = async (req, res) => {
try {
const { advertisingId } = req.body
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const showDiscount = req.query.showDiscount // Get project ID from session
const advertising = await AdvertisingModel.findById(advertisingId)
if (!advertising) {
res.status(404).send('Advertising not found')
return
}
const price = await getAdvertisingPriceFromDatabase(advertising.type)
// Create a new payment entry
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
status: 'failed',
authority: '', // Add authority if available
user_id: userId,
advertising_id: advertisingId,
type: 'advertising'
})
await payment.save()
// Emit a failure event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Payment failed' })
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({ message: 'Payment failed', advertisingId, price })
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
const republishAdvertisingPayment = async (req, res) => {
try {
const token = req.header('Authorization').split(' ')[1]
@@ -966,27 +815,30 @@ const republishAdvertisingPayment = async (req, res) => {
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = 'temp_accept' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.' })
}
} else {
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: apiKey,
Amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100),
Description: `پرداخت برای سفارش شماره ${advertisingId}`,
CallbackURL: `${process.env.APP_URL}/advertising/republish-verify-payment?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
})
const data = response.data
if (data.Status === 100) {
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
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
},
{ headers: { 'Content-Type': 'application/json' } }
)
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
// Payment request was successful
const authority = data.Authority // The payment authority (شناسه پرداخت)
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
}
@@ -1008,16 +860,19 @@ const handleAdvertisingRepublishPaymentCallback = async (req, res) => {
const showDiscount = req.query.showDiscount // Get project ID from session
// Clear project ID from session
// Query ZarinPal API to verify payment status
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
merchant_id: apiKey,
authority,
amount: toRial(showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100))
},
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100),
status: 'successful',
@@ -1038,7 +893,7 @@ const handleAdvertisingRepublishPaymentCallback = async (req, res) => {
return
}
advertising.payment_status = 'done' // Set advertising status to 'done'
advertising.status = 'accepted' // Set advertising status to 'done'
advertising.status = await resolveApprovalStatus(userId, 'accepted') // Set advertising status to 'done'
advertising.acceptedAt = new Date()
await advertising.save()
@@ -1085,100 +940,6 @@ const handleAdvertisingRepublishPaymentCallback = async (req, res) => {
res.status(500).send('Internal server error')
}
}
// Controller function for handling successful republish advertising payment
const handleSuccessfulAdvertisingRepublishPayment = async (req, res) => {
try {
const { advertisingId } = req.body
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const showDiscount = req.query.showDiscount
const advertising = await AdvertisingModel.findById(advertisingId)
if (!advertising) {
res.status(404).send('Advertising not found')
return
}
const price = await getAdvertisingPriceFromDatabase(advertising.type)
// Create a new payment entry
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number((price + 20000) * 0.7) : Number(price * 0.7),
status: 'successful',
authority: '', // Add authority if available
user_id: userId,
advertising_id: advertisingId,
type: 'advertising-republish',
installment_step: null
})
await payment.save()
// Update the advertising status to 'done' or 'accepted'
advertising.payment_status = 'done'
advertising.status = 'accepted'
advertising.acceptedAt = new Date()
await advertising.save()
// Emit a success event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Republish payment successful' })
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({ message: 'Republish payment recorded successfully', advertisingId, price })
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
// Controller function for handling failed republish advertising payment
const handleFailedAdvertisingRepublishPayment = async (req, res) => {
try {
const { advertisingId } = req.body
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const showDiscount = req.query.showDiscount
const advertising = await AdvertisingModel.findById(advertisingId)
if (!advertising) {
res.status(404).send('Advertising not found')
return
}
const price = await getAdvertisingPriceFromDatabase(advertising.type)
// Create a new payment entry
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number((price + 20000) * 0.7) : Number(price * 0.7),
status: 'failed',
authority: '', // Add authority if available
user_id: userId,
advertising_id: advertisingId,
type: 'advertising-republish'
})
await payment.save()
// Emit a failure event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Republish payment failed' })
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({ message: 'Republish payment failed', advertisingId, price })
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
module.exports = {
initiatePayment,
handlePaymentCallback,
@@ -1191,11 +952,5 @@ module.exports = {
initiateAdvertisingPayment,
handleAdvertisingPaymentCallback,
republishAdvertisingPayment,
handleAdvertisingRepublishPaymentCallback,
handleSuccessfulPayment,
handleFailedPayment,
handleSuccessfulAdvertisingPayment,
handleFailedAdvertisingPayment,
handleSuccessfulAdvertisingRepublishPayment,
handleFailedAdvertisingRepublishPayment
handleAdvertisingRepublishPaymentCallback
}

View File

@@ -1,19 +1,20 @@
/* eslint-disable eqeqeq */
const TypeAndPriceModel = require('../../../models/TypeAndPriceModel');
const ProjectModel = require('../../../models/ProjectModel');
const PaymentModel = require('../../../models/PaymentModel');
const jwt = require('jsonwebtoken');
const RequestModel = require('../../../models/RequestModel');
const NotificationModel = require('../../../models/NotificationModel');
const UserModel = require('../../../models/UserModel');
const AdvertisingTypeModel = require('../../../models/AdvertisingTypeModel');
const AdvertisingModel = require('../../../models/AdvertisingModel');
const OfferTypeModel = require('../../../models/OfferTypeModel');
const OfferModel = require('../../../models/OfferModel');
const { default: axios } = require('axios');
const WalletTransactionModel = require('../../../models/WalletTransactionModel');
const { PROJECT_ACCEPT_RATE, PROJECT_COMPLETION_COMMISSION_RATE, creditProjectShare } = require('../../../utils/projectCommission');
const TypeAndPriceModel = require('../../../models/TypeAndPriceModel')
const ProjectModel = require('../../../models/ProjectModel')
const PaymentModel = require('../../../models/PaymentModel')
const jwt = require('jsonwebtoken')
const RequestModel = require('../../../models/RequestModel')
const NotificationModel = require('../../../models/NotificationModel')
const UserModel = require('../../../models/UserModel')
const AdvertisingTypeModel = require('../../../models/AdvertisingTypeModel')
const AdvertisingModel = require('../../../models/AdvertisingModel')
const OfferTypeModel = require('../../../models/OfferTypeModel')
const OfferModel = require('../../../models/OfferModel')
const { default: axios } = require('axios')
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const { PROJECT_ACCEPT_RATE, PROJECT_COMPLETION_COMMISSION_RATE, creditProjectShare } = require('../../../utils/projectCommission')
const { isVerifiedBadge } = require('../../../utils/verifyBadge')
const OFFER_COMMISSION_RATE = 0.3
@@ -22,30 +23,43 @@ const apiKey =
process.env.MERCHENT_CODE ||
'c7c41e8a-918f-4741-bcd5-58f3bc51db73'
// درگاه قدیمیِ REST زرین‌پال (/pg/rest/WebGate) منسوخ شده — مبلغ به API نسخه‌ی v4
// باید به ریال ارسال بشه، نه تومان (برخلاف API قدیمی)
function toRial (tomanAmount) {
return Number(tomanAmount) * 10
}
// کاربرِ بدون تیک وریفای طلایی/سبز/آبی باید از پنل ادمین تایید بشه تا پروژه/ویترینِ
// پرداخت‌شده‌ش مستقیم توی اپ/وب نمایش داده نشه
async function resolveApprovalStatus (userId, acceptedStatus, pendingStatus = 'pending') {
const user = await UserModel.findById(userId).select('verify_badge').lean()
return isVerifiedBadge(user?.verify_badge) ? acceptedStatus : pendingStatus
}
// Function to get price based on item name from database
const getPriceFromDatabase = async (itemName) => {
const item = await TypeAndPriceModel.findOne({ name: itemName });
const item = await TypeAndPriceModel.findOne({ name: itemName })
if (!item) {
throw new Error(`Item "${itemName}" not found in TypeAndPriceModel`);
throw new Error(`Item "${itemName}" not found in TypeAndPriceModel`)
}
return item.price;
};
return item.price
}
const getAdvertisingPriceFromDatabase = async (itemName) => {
const item = await AdvertisingTypeModel.findOne({ name: itemName });
const item = await AdvertisingTypeModel.findOne({ name: itemName })
if (!item) {
throw new Error(`Item "${itemName}" not found in AdvertisingTypeModel`);
throw new Error(`Item "${itemName}" not found in AdvertisingTypeModel`)
}
return item.price;
};
return item.price
}
const getOfferPriceFromDatabase = async (offerType) => {
const item = await OfferTypeModel.findOne({ name: offerType });
const item = await OfferTypeModel.findOne({ name: offerType })
if (!item) {
throw new Error(`Offer type "${offerType}" not found in OfferTypeModel`);
throw new Error(`Offer type "${offerType}" not found in OfferTypeModel`)
}
return item.price;
};
return item.price
}
// Controller function for initiating payment
const initiatePaymentWeb = async (req, res) => {
try {
@@ -99,7 +113,7 @@ const initiatePaymentWeb = async (req, res) => {
return res.status(404).json({ error: true, message: 'پروژه یافت نشد' })
}
project.payment_status = 'done'
project.status = 'temp_accept'
project.status = await resolveApprovalStatus(userId, 'temp_accept')
await project.save()
return res.status(200).json({
message: 'پروژه رایگان با موفقیت ایجاد شد',
@@ -122,26 +136,26 @@ const initiatePaymentWeb = async (req, res) => {
}
const response = await axios.post(
'https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json',
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
MerchantID: apiKey,
Amount: Number(price),
Description: `پرداخت برای سفارش شماره ${projectId}`,
CallbackURL: `${callbackBase}/projects/payment-web?projectId=${projectId}&itemName=${encodeURIComponent(itemName)}&userId=${userId}`
merchant_id: apiKey,
amount: toRial(price),
description: `پرداخت برای سفارش شماره ${projectId}`,
callback_url: `${callbackBase}/projects/payment-web?projectId=${projectId}&itemName=${encodeURIComponent(itemName)}&userId=${userId}`
},
{ timeout: 20000 }
{ headers: { 'Content-Type': 'application/json' }, timeout: 20000 }
)
const data = response.data
if (data.Status === 100 && data.Authority) {
return res.status(200).json({ authority: data.Authority })
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
return res.status(200).json({ authority: result.authority })
}
console.error('ZarinPal initiate error:', data)
console.error('ZarinPal initiate error:', response.data)
return res.status(502).json({
error: true,
message: 'درگاه پرداخت پاسخ نامعتبر داد. لطفاً دوباره تلاش کنید.',
zarinpalStatus: data?.Status
zarinpalStatus: result?.code
})
} catch (error) {
console.error('initiatePaymentWeb error:', error?.response?.data || error?.message || error)
@@ -166,16 +180,15 @@ const handlePaymentCallbackWeb = async (req, res) => {
const userId = req.query.userId // Get project ID from session
// Clear project ID from session
// Query ZarinPal API to verify payment status
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const price = await getPriceFromDatabase(itemName) // Get price from database
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: Number(price)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{ merchant_id: apiKey, authority, amount: toRial(price) },
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
const payment = new PaymentModel({
amount: Number(price),
status: 'successful',
@@ -196,7 +209,7 @@ const handlePaymentCallbackWeb = async (req, res) => {
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = 'temp_accept' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
// Send event based on payment status
@@ -288,25 +301,25 @@ const paymentRequestAcceptWeb = async (req, res) => {
}
const response = await axios.post(
'https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json',
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
MerchantID: apiKey,
Amount: acceptAmount,
Description: `پرداخت پذیرش همکاری برای پروژه ${project.title || projectId}`,
CallbackURL: `${callbackBase}/projects/payment-request-step-one-callback/web?projectId=${projectId}&requestId=${requestId}&userId=${userId}`
merchant_id: apiKey,
amount: toRial(acceptAmount),
description: `پرداخت پذیرش همکاری برای پروژه ${project.title || projectId}`,
callback_url: `${callbackBase}/projects/payment-request-step-one-callback/web?projectId=${projectId}&requestId=${requestId}&userId=${userId}`
},
{ timeout: 20000 }
{ headers: { 'Content-Type': 'application/json' }, timeout: 20000 }
)
const data = response.data
if (data.Status === 100 && data.Authority) {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
return res.status(200).json({
authority: data.Authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${data.Authority}`
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
})
}
console.error('paymentRequestAcceptWeb ZarinPal error:', data)
console.error('paymentRequestAcceptWeb ZarinPal error:', response.data)
return res.status(502).json({ error: true, message: 'درگاه پرداخت پاسخ نامعتبر داد. لطفاً دوباره تلاش کنید.' })
} catch (error) {
console.error('paymentRequestAcceptWeb error:', error?.response?.data || error?.message || error)
@@ -332,12 +345,13 @@ const paymentRequestAcceptWebCallback = async (req, res) => {
const acceptAmount = Math.round(Number(request.price) * PROJECT_ACCEPT_RATE)
const verificationResponse = await axios.post(
'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json',
{ MerchantID: apiKey, Authority, Amount: acceptAmount }
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{ merchant_id: apiKey, authority: Authority, amount: toRial(acceptAmount) },
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status !== 100 && verificationData.Status !== 101) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code !== 100 && verificationData?.code !== 101) {
const payment = new PaymentModel({
amount: acceptAmount,
status: 'failed',
@@ -433,25 +447,25 @@ const initiateProjectCompletionPaymentWeb = async (req, res) => {
}
const response = await axios.post(
'https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json',
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
MerchantID: apiKey,
Amount: remainingAmount,
Description: `پرداخت باقی‌مانده پروژه ${project.title || projectId}`,
CallbackURL: `${callbackBase}/projects/payment-completion-callback/web?projectId=${projectId}&userId=${userId}`
merchant_id: apiKey,
amount: toRial(remainingAmount),
description: `پرداخت باقی‌مانده پروژه ${project.title || projectId}`,
callback_url: `${callbackBase}/projects/payment-completion-callback/web?projectId=${projectId}&userId=${userId}`
},
{ timeout: 20000 }
{ headers: { 'Content-Type': 'application/json' }, timeout: 20000 }
)
const data = response.data
if (data.Status === 100 && data.Authority) {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
return res.status(200).json({
authority: data.Authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${data.Authority}`
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
})
}
console.error('initiateProjectCompletionPaymentWeb ZarinPal error:', data)
console.error('initiateProjectCompletionPaymentWeb ZarinPal error:', response.data)
return res.status(502).json({ error: true, message: 'درگاه پرداخت پاسخ نامعتبر داد. لطفاً دوباره تلاش کنید.' })
} catch (error) {
console.error('initiateProjectCompletionPaymentWeb error:', error?.response?.data || error?.message || error)
@@ -476,12 +490,13 @@ const handleProjectCompletionPaymentCallbackWeb = async (req, res) => {
const remainingAmount = Math.round(Number(project.final_price)) - acceptAmount
const verificationResponse = await axios.post(
'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json',
{ MerchantID: apiKey, Authority, Amount: remainingAmount }
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{ merchant_id: apiKey, authority: Authority, amount: toRial(remainingAmount) },
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status !== 100 && verificationData.Status !== 101) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code !== 100 && verificationData?.code !== 101) {
const payment = new PaymentModel({
amount: remainingAmount,
status: 'failed',
@@ -551,27 +566,30 @@ const initiateAdvertisingPaymentWeb = async (req, res) => {
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = 'temp_accept' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از بیلبورد رایگان این ماه استفاده کرده‌اید.' })
}
} else {
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: process.env.ZARINPAL_MERCHANT_ID,
Amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
Description: `پرداخت برای سفارش شماره ${advertisingId}`,
CallbackURL: `${process.env.APP_SITE_CallBack}/advertising/payment-web?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
})
const data = response.data
if (data.Status === 100) {
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id: process.env.ZARINPAL_MERCHANT_ID,
amount: toRial(showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price)),
description: `پرداخت برای سفارش شماره ${advertisingId}`,
callback_url: `${process.env.APP_SITE_CallBack}/advertising/payment-web?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
const authority = data.Authority // The payment authority (شناسه پرداخت)
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
}
@@ -594,16 +612,19 @@ const handleAdvertisingPaymentCallbackWeb = async (req, res) => {
// Clear project ID from session
// Query ZarinPal API to verify payment status
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
merchant_id: apiKey,
authority,
amount: toRial(showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price))
},
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
status: 'successful',
@@ -624,7 +645,7 @@ const handleAdvertisingPaymentCallbackWeb = async (req, res) => {
return
}
advertising.payment_status = 'done' // Set advertising status to 'done'
advertising.status = 'temp_accept' // Set advertising status to 'done'
advertising.status = await resolveApprovalStatus(userId, 'temp_accept') // Set advertising status to 'done'
await advertising.save()
// Send event based on payment status
@@ -699,27 +720,30 @@ const republishAdvertisingPaymentWeb = async (req, res) => {
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = 'temp_accept' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.' })
}
} else {
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
MerchantID: apiKey,
Amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100),
Description: `پرداخت برای سفارش شماره ${advertisingId}`,
CallbackURL: `${process.env.APP_URL}/advertising/republish-verify-payment-web?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
})
const data = response.data
if (data.Status === 100) {
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
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-web?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
const authority = data.Authority // The payment authority (شناسه پرداخت)
res.status(200).json({ authority })
res.status(200).json({ authority: result.authority })
} else {
// Payment request failed
console.error('Error occurred while initiating payment:', data)
console.error('Error occurred while initiating payment:', response.data)
res.status(500).json({ error: 'Error occurred while initiating payment' })
}
}
@@ -741,16 +765,19 @@ const handleAdvertisingRepublishPaymentCallbackWeb = async (req, res) => {
const showDiscount = req.query.showDiscount // Get project ID from session
// Clear project ID from session
// Query ZarinPal API to verify payment status
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
const verificationResponse = await axios.post(verificationUrl, {
MerchantID: apiKey,
Authority: authority,
Amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100)
})
const verificationResponse = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
merchant_id: apiKey,
authority,
amount: toRial(showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100))
},
{ headers: { 'Content-Type': 'application/json' } }
)
const verificationData = verificationResponse.data
if (verificationData.Status === 100) {
const verificationData = verificationResponse.data?.data
if (verificationData?.code === 100 || verificationData?.code === 101) {
const payment = new PaymentModel({
amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100),
status: 'successful',
@@ -771,7 +798,7 @@ const handleAdvertisingRepublishPaymentCallbackWeb = async (req, res) => {
return
}
advertising.payment_status = 'done' // Set advertising status to 'done'
advertising.status = 'accepted' // Set advertising status to 'done'
advertising.status = await resolveApprovalStatus(userId, 'accepted') // Set advertising status to 'done'
advertising.acceptedAt = new Date()
await advertising.save()
@@ -818,121 +845,38 @@ const handleAdvertisingRepublishPaymentCallbackWeb = async (req, res) => {
res.status(500).send('Internal server error')
}
}
// const initiateOfferPaymentWeb = async (req, res) => {
// try {
// const token = req.header('Authorization')?.split(' ')[1];
// if (!token) {
// return res.status(401).json({ error: 'Access Denied: No token provided' });
// }
// let decodedToken;
// try {
// decodedToken = jwt.verify(token, process.env.APP_SECRET);
// } catch (err) {
// return res.status(401).json({ error: 'Invalid token' });
// }
// const userId = decodedToken.id;
// const { receiverId, offerType } = req.body;
// // اعتبارسنجی ورودی‌ها
// if (!receiverId || !offerType) {
// return res.status(400).json({ error: 'receiverId and offerType are required' });
// }
// const user = await UserModel.findById(userId);
// if (!user) {
// return res.status(404).json({ error: 'User not found' });
// }
// const receiverUser = await UserModel.findById(receiverId);
// if (!receiverUser) {
// return res.status(404).json({ error: 'Receiver not found' });
// }
// let price;
// if (user.monthly_free_offer > 0 && offerType === 'free') {
// price = 0;
// user.monthly_free_offer -= 1;
// await user.save();
// } else {
// price = await getOfferPriceFromDatabase(offerType);
// }
// if (price === 0) {
// const offer = new OfferModel({
// sender: userId,
// receiver: receiverId,
// transaction_id: null,
// });
// await offer.save();
// return res.status(200).json({ message: 'آفر رایگان با موفقیت ثبت شد', offerId: offer._id, type: 'free' });
// }
// console.log('🔹 Initiating ZarinPal Payment');
// console.log('MerchantID:', apiKey);
// console.log('Amount:', Number(price * 10));
// console.log('Description:', `پرداخت برای ارسال آفر به ${receiverUser.first_name}`);
// console.log('CallbackURL:', `${process.env.APP_URL}/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`);
// const ZARINPAL_URL = 'https://api.zarinpal.com/pg/v4/payment/request.json';
// //
// const response = await axios.post(ZARINPAL_URL, {
// merchant_id: process.env.ZARINPAL_MERCHANT_ID, // ❌ MerchantID نیست!
// amount: Number(price), // ✅ به ریال
// description: `پرداخت برای ارسال آفر به ${receiverUser.first_name}`,
// callback_url: `${process.env.APP_SITE_CallBack}/offer/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`,
// }, { headers: { 'Content-Type': 'application/json' } });
// const result = response.data.data; // ✅ data.data
// if (result.code === 100) { // ✅ code === 100
// return res.status(200).json({
// authority: result.authority,
// paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
// });
// } else {
// console.error('ZarinPal error:', data);
// return res.status(500).json({ error: `Failed to initiate payment: ZarinPal Status ${data.Status}` });
// }
// } catch (error) {
// console.error('❌ Error in initiateOfferPaymentWeb:', error.message, error.stack);
// return res.status(500).json({ error: error.message || 'Internal server error' });
// }
// }
const initiateOfferPaymentWeb = async (req, res) => {
try {
const token = req.header('Authorization')?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'No token' });
const token = req.header('Authorization')?.split(' ')[1]
if (!token) return res.status(401).json({ error: 'No token' })
const decoded = jwt.verify(token, process.env.APP_SECRET);
const userId = decoded.id;
const decoded = jwt.verify(token, process.env.APP_SECRET)
const userId = decoded.id
const { receiverId, offerType, platform } = req.body;
if (!receiverId || !offerType)
return res.status(400).json({ error: 'Invalid data' });
const { receiverId, offerType, platform } = req.body
if (!receiverId || !offerType) { return res.status(400).json({ error: 'Invalid data' }) }
const user = await UserModel.findById(userId);
const receiver = await UserModel.findById(receiverId);
if (!user || !receiver)
return res.status(404).json({ error: 'User not found' });
const user = await UserModel.findById(userId)
const receiver = await UserModel.findById(receiverId)
if (!user || !receiver) { return res.status(404).json({ error: 'User not found' }) }
let price = await getOfferPriceFromDatabase(offerType); // ✅ ریال
const price = await getOfferPriceFromDatabase(offerType) // ✅ ریال
// 🎁 free offer
if (offerType === 'free' && user.monthly_free_offer > 0) {
const offer = await OfferModel.create({
sender: userId,
receiver: receiverId,
transaction_id: null,
});
transaction_id: null
})
user.monthly_free_offer -= 1;
await user.save();
user.monthly_free_offer -= 1
await user.save()
return res.json({
type: 'free',
offerId: offer._id,
});
offerId: offer._id
})
}
// 🎁 گیرنده «دریافت پرداختی از چت» رو غیرفعال کرده — درخواست همکاری براش همیشه
@@ -941,23 +885,23 @@ const initiateOfferPaymentWeb = async (req, res) => {
const offer = await OfferModel.create({
sender: userId,
receiver: receiverId,
transaction_id: null,
});
transaction_id: null
})
return res.json({
type: 'free',
offerId: offer._id,
});
offerId: offer._id
})
}
const amount = Number(price) * 10; // ✅ ریال – بدون ×10
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : '';
const callbackUrl = `https://api.modstagram.com/api/v1/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}${platformSuffix}`;
const amount = Number(price) * 10 // ✅ ریال – بدون ×10
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
const callbackUrl = `https://api.modstagram.com/api/v1/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}${platformSuffix}`
const zarinpalRes = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id:"c7c41e8a-918f-4741-bcd5-58f3bc51db73",
merchant_id: 'c7c41e8a-918f-4741-bcd5-58f3bc51db73',
amount,
description: `ارسال آفر به ${receiver.first_name}`,
callback_url: callbackUrl
@@ -965,216 +909,73 @@ const initiateOfferPaymentWeb = async (req, res) => {
// callback_url: `${process.env.APP_SITE_CallBack}/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`,
},
{ headers: { 'Content-Type': 'application/json' } }
);
)
// console.log(`${process.env.APP_SITE_CallBack}/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`);
const result = zarinpalRes.data.data;
console.log(result);
console.log("ZarinPal Callback URL:", callbackUrl);
console.log("Initiate payment request:", { userId, receiverId, offerType, price });
const result = zarinpalRes.data.data
console.log(result)
console.log('ZarinPal Callback URL:', callbackUrl)
console.log('Initiate payment request:', { userId, receiverId, offerType, price })
if (result.code === 100) {
return res.json({
authority: result.authority,
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`,
});
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
})
}
return res.status(500).json({ error: 'ZarinPal error' });
return res.status(500).json({ error: 'ZarinPal error' })
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Server error' });
console.error(err)
return res.status(500).json({ error: 'Server error' })
}
};
// try {
// const { receiverId, offerType, userId, Authority, Status } = req.query;
// // ❗ چک اولیه
// if (Status !== 'OK' || !Authority) {
// return res.status(400).json({
// success: false,
// message: 'پرداخت ناموفق یا لغو شد'
// });
// }
// if (!receiverId || !offerType || !userId) {
// return res.status(400).json({
// success: false,
// message: 'پارامترهای ناقص'
// });
// }
// // پیدا کردن کاربرها
// const user = await UserModel.findById(userId);
// const receiverUser = await UserModel.findById(receiverId);
// if (!user || !receiverUser) {
// return res.status(404).json({
// success: false,
// message: 'کاربر یافت نشد'
// });
// }
// const price = await getOfferPriceFromDatabase(offerType);
// const verifyResponse = await axios.post(
// 'https://api.zarinpal.com/pg/v4/payment/verify.json',
// {
// merchant_id: process.env.ZARINPAL_MERCHANT_ID, // یا MERCHENT_CODE
// authority: Authority,
// amount: price, // به ریال
// },
// { headers: { 'Content-Type': 'application/json' } }
// );
// const verifyData = verifyResponse.data.data;
// if (verifyData.code === 100) {
// // ✅ پرداخت موفق
// const payment = new PaymentModel({
// amount: Number(price),
// status: 'successful',
// authority: Authority,
// ref_id: verifyData.ref_id,
// user_id: userId,
// type: 'offer',
// });
// await payment.save();
// const offer = new OfferModel({
// sender: userId,
// receiver: receiverId,
// transaction_id: payment._id,
// });
// await offer.save();
// // نوتیفیکیشن و SMS (همون کد قبلی)
// const notification = new NotificationModel({
// user_id: receiverId,
// project_post_id: offer._id,
// type: 'new_offer',
// title: 'درخواست همکاری جدید',
// description: `${user.first_name} یک درخواست همکاری برای شما ارسال کرده است`,
// });
// await notification.save();
// const data = JSON.stringify({
// mobile: receiverUser?.mobile,
// templateId: '569006',
// parameters: [
// { name: 'FIRSTNAME', value: receiverUser?.first_name },
// { name: 'LASTNAME', value: receiverUser?.last_name },
// ],
// });
// const config = {
// method: 'post',
// url: 'https://api.sms.ir/v1/send/verify',
// headers: {
// 'Content-Type': 'application/json',
// Accept: 'text/plain',
// 'x-api-key': process.env.SMS_VERIFY_KEY,
// },
// data,
// };
// await axios(config).catch((error) => {
// console.error('SMS error:', error.message);
// });
// if ('eventEmitter' in req.app && status === 'OK') {
// req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Payment successful' });
// } else {
// req.app.get('eventEmitter')?.emit('paymentFailed', { message: 'Payment canceled or failed' });
// }
// res.redirect(`https://modstagram.com/offer/payment/success?userId=${userId}`);
// return res.status(200).json({
// success: true,
// message: 'پرداخت و آفر با موفقیت ثبت شد',
// ref_id: verifyData.ref_id,
// offerId: offer._id
// });
// } else {
// // پرداخت ناموفق
// const payment = new PaymentModel({
// amount: Number(price),
// status: 'failed',
// authority: Authority,
// user_id: userId,
// type: 'offer',
// });
// await payment.save();
// res.redirect(`https://modstagram.com/offer/payment/failed?userId=${userId}`);
// return res.status(400).json({
// success: false,
// message: `خطا در تأیید پرداخت: ${verifyData.code}`
// });
// }
// } catch (error) {
// console.error('❌ Callback Error:', error.message);
// return res.status(500).json({
// success: false,
// message: 'خطای سرور'
// });
// }
// };
}
const handleOfferPaymentCallback = async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
console.log('🔔 CALLBACK QUERY:', req.query);
console.log('🔔 CALLBACK QUERY:', req.query)
const isAndroid = req.query.platform === 'android';
const isAndroid = req.query.platform === 'android'
try {
const { Authority, Status, receiverId, offerType, userId } = req.query;
const { Authority, Status, receiverId, offerType, userId } = req.query
if (Status !== 'OK')
if (Status !== 'OK') {
return isAndroid
? res.redirect(`modstagram://FailedPay?type=offer`)
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`);
? res.redirect('modstagram://FailedPay?type=offer')
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`)
}
const price = await getOfferPriceFromDatabase(offerType); // ریال
const amount = Number(price) * 10;
const price = await getOfferPriceFromDatabase(offerType) // ریال
const amount = Number(price) * 10
const verify = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
merchant_id: "c7c41e8a-918f-4741-bcd5-58f3bc51db73",
merchant_id: 'c7c41e8a-918f-4741-bcd5-58f3bc51db73',
authority: Authority,
amount,
amount
},
{ headers: { 'Content-Type': 'application/json' } }
);
)
const data = verify.data.data;
console.log('🔔payment:', data.code);
const data = verify.data.data
console.log('🔔payment:', data.code)
if (data.code === 100) {
const payment = await PaymentModel.create({
amount : (amount / 10),
amount: (amount / 10),
status: 'successful',
authority: Authority,
ref_id: data.ref_id,
user_id: userId,
type: 'offer',
});
type: 'offer'
})
console.log('🔔payment:', payment);
console.log('🔔payment:', payment)
const offerPriceToman = amount / 10
const receiverShare = Math.round(offerPriceToman * OFFER_COMMISSION_RATE)
@@ -1185,31 +986,26 @@ const handleOfferPaymentCallback = async (req, res) => {
sender: userId,
receiver: receiverId,
transaction_id: payment._id,
pendingCommissionAmount: receiverShare,
});
pendingCommissionAmount: receiverShare
})
return isAndroid
? res.redirect(`modstagram://SuccessPay?type=offer&offerId=${offer._id}`)
: res.redirect(
`${process.env.APP_SITE}/offer/payment/success?offerId=${offer._id}`
);
`${process.env.APP_SITE}/offer/payment/success?offerId=${offer._id}`
)
}
return isAndroid
? res.redirect(`modstagram://FailedPay?type=offer`)
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`);
? res.redirect('modstagram://FailedPay?type=offer')
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`)
} catch (err) {
console.error(err);
console.error(err)
return isAndroid
? res.redirect(`modstagram://FailedPay?type=offer`)
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`);
? res.redirect('modstagram://FailedPay?type=offer')
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`)
}
};
}
module.exports = {
initiatePaymentWeb,
@@ -1223,5 +1019,5 @@ module.exports = {
republishAdvertisingPaymentWeb,
handleAdvertisingRepublishPaymentCallbackWeb,
initiateOfferPaymentWeb,
handleOfferPaymentCallback,
};
handleOfferPaymentCallback
}