pay
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user