pay
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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: 'پرداخت آفر از کیف پول'
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user