결제, 구독 기능
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const { Feature, ClubFeature, Club } = require('../models');
|
||||
const { Feature, ClubFeature, Club, ClubSubscription, SubscriptionPlan, SubscriptionPlanFeature } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 전체 기능 목록 조회
|
||||
@@ -20,7 +20,7 @@ exports.getFeatures = async (req, res) => {
|
||||
// 사용 가능한 기능 목록 조회
|
||||
exports.getAvailableFeatures = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.query;
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
@@ -47,10 +47,40 @@ exports.getAvailableFeatures = async (req, res) => {
|
||||
|
||||
const activeFeatureIds = activeFeatures.map(af => af.featureId);
|
||||
|
||||
// 기능 목록에 활성화 상태 추가
|
||||
// 현재 구독 중인 플랜에 포함된 기능 조회
|
||||
const { ClubSubscription, SubscriptionPlan, SubscriptionPlanFeature } = require('../models');
|
||||
|
||||
// 현재 활성화된 구독 조회
|
||||
const activeSubscription = await ClubSubscription.findOne({
|
||||
where: {
|
||||
clubId,
|
||||
status: 'active',
|
||||
endDate: {
|
||||
[Op.gt]: new Date()
|
||||
}
|
||||
},
|
||||
attributes: ['planId']
|
||||
});
|
||||
|
||||
let planFeatureIds = [];
|
||||
|
||||
// 현재 구독 플랜이 있는 경우, 해당 플랜에 포함된 기능 ID 목록 조회
|
||||
if (activeSubscription) {
|
||||
const planFeatures = await SubscriptionPlanFeature.findAll({
|
||||
where: {
|
||||
planId: activeSubscription.planId
|
||||
},
|
||||
attributes: ['featureId']
|
||||
});
|
||||
|
||||
planFeatureIds = planFeatures.map(pf => pf.featureId);
|
||||
}
|
||||
|
||||
// 기능 목록에 활성화 상태 및 플랜 포함 여부 추가
|
||||
const formattedFeatures = features.map(feature => ({
|
||||
...feature.toJSON(),
|
||||
isActive: activeFeatureIds.includes(feature.id)
|
||||
isActive: activeFeatureIds.includes(feature.id),
|
||||
isIncludedInPlan: planFeatureIds.includes(feature.id)
|
||||
}));
|
||||
|
||||
res.json(formattedFeatures);
|
||||
@@ -63,7 +93,7 @@ exports.getAvailableFeatures = async (req, res) => {
|
||||
// 활성화된 기능 목록 조회
|
||||
exports.getActiveFeatures = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.query;
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
@@ -80,10 +110,19 @@ exports.getActiveFeatures = async (req, res) => {
|
||||
model: Feature,
|
||||
attributes: ['name', 'description']
|
||||
}],
|
||||
attributes: ['id', 'featureId', 'purchaseDate', 'expiryDate']
|
||||
attributes: ['id', 'featureId', 'createdAt', 'expiryDate']
|
||||
});
|
||||
|
||||
res.json(activeFeatures);
|
||||
// createdAt을 purchaseDate로 매핑
|
||||
const formattedFeatures = activeFeatures.map(feature => ({
|
||||
id: feature.id,
|
||||
featureId: feature.featureId,
|
||||
purchaseDate: feature.createdAt,
|
||||
expiryDate: feature.expiryDate,
|
||||
Feature: feature.Feature
|
||||
}));
|
||||
|
||||
res.json(formattedFeatures);
|
||||
} catch (error) {
|
||||
console.error('활성화된 기능 목록 조회 오류:', error);
|
||||
res.status(500).json({ message: '활성화된 기능 목록을 가져오는 중 오류가 발생했습니다.' });
|
||||
@@ -93,7 +132,7 @@ exports.getActiveFeatures = async (req, res) => {
|
||||
// 기능 구매 내역 조회
|
||||
exports.getFeaturePayments = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.query;
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
@@ -105,29 +144,41 @@ exports.getFeaturePayments = async (req, res) => {
|
||||
},
|
||||
include: [{
|
||||
model: Feature,
|
||||
attributes: ['name']
|
||||
attributes: ['name', 'price']
|
||||
}],
|
||||
attributes: [
|
||||
'id',
|
||||
'featureId',
|
||||
'purchaseDate',
|
||||
'createdAt',
|
||||
'expiryDate',
|
||||
'amount',
|
||||
'duration',
|
||||
'status'
|
||||
'enabled'
|
||||
],
|
||||
order: [['purchaseDate', 'DESC']]
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const formattedPayments = payments.map(payment => ({
|
||||
id: payment.id,
|
||||
featureName: payment.feature.name,
|
||||
purchaseDate: payment.purchaseDate,
|
||||
expiryDate: payment.expiryDate,
|
||||
amount: payment.amount,
|
||||
duration: payment.duration,
|
||||
status: payment.status
|
||||
}));
|
||||
// 기간 계산 (월 단위)
|
||||
const calculateDuration = (startDate, endDate) => {
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
const diffTime = Math.abs(end - start);
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
return Math.ceil(diffDays / 30); // 대략적인 월 수 계산
|
||||
};
|
||||
|
||||
const formattedPayments = payments.map(payment => {
|
||||
const duration = calculateDuration(payment.createdAt, payment.expiryDate);
|
||||
const amount = payment.Feature.price * duration;
|
||||
|
||||
return {
|
||||
id: payment.id,
|
||||
featureName: payment.Feature.name,
|
||||
purchaseDate: payment.createdAt,
|
||||
expiryDate: payment.expiryDate,
|
||||
amount: amount,
|
||||
duration: duration,
|
||||
status: payment.enabled ? 'active' : 'inactive'
|
||||
};
|
||||
});
|
||||
|
||||
res.json(formattedPayments);
|
||||
} catch (error) {
|
||||
@@ -172,27 +223,32 @@ exports.purchaseFeature = async (req, res) => {
|
||||
return res.status(400).json({ message: '이미 활성화된 기능입니다.' });
|
||||
}
|
||||
|
||||
// 구매 금액 계산 (duration에 따른 할인 적용 가능)
|
||||
const amount = feature.price * duration;
|
||||
|
||||
// 기능 구매 기록 생성
|
||||
const purchaseDate = new Date();
|
||||
// 만료일 계산
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setMonth(expiryDate.getMonth() + duration);
|
||||
expiryDate.setMonth(expiryDate.getMonth() + parseInt(duration));
|
||||
|
||||
const clubFeature = await ClubFeature.create({
|
||||
clubId,
|
||||
featureId,
|
||||
purchaseDate,
|
||||
expiryDate,
|
||||
amount,
|
||||
duration,
|
||||
status: 'active'
|
||||
enabled: true
|
||||
});
|
||||
|
||||
// 응답용 객체 생성
|
||||
const purchaseInfo = {
|
||||
id: clubFeature.id,
|
||||
clubId: clubFeature.clubId,
|
||||
featureId: clubFeature.featureId,
|
||||
purchaseDate: clubFeature.createdAt,
|
||||
expiryDate: clubFeature.expiryDate,
|
||||
amount: feature.price * parseInt(duration),
|
||||
duration: parseInt(duration),
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
res.json({
|
||||
message: '기능 구매가 완료되었습니다.',
|
||||
purchaseInfo: clubFeature
|
||||
purchaseInfo
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('기능 구매 오류:', error);
|
||||
|
||||
@@ -21,6 +21,17 @@ exports.publicEventEntry = async (req, res) => {
|
||||
if (event.status === '취소') {
|
||||
return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
||||
}
|
||||
|
||||
// 클럽의 구독 활성 여부 확인
|
||||
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
|
||||
where: { clubId: event.clubId, status: 'active' },
|
||||
include: [{ model: sequelize.models.SubscriptionPlan }]
|
||||
});
|
||||
|
||||
// 구독이 비활성화된 경우 접근 제한
|
||||
if (!clubSubscription) {
|
||||
return res.status(403).json({ message: '클럽의 구독이 비활성화되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
|
||||
}
|
||||
|
||||
let token = null;
|
||||
let isAuthenticated = false;
|
||||
@@ -248,6 +259,26 @@ exports.addPublicGuest = async (req, res) => {
|
||||
const event = await Event.findOne({ where: { publicHash } });
|
||||
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||
if (event.status === '취소') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
||||
|
||||
// 클럽의 회원 수와 구독 플랜의 최대 회원 수 확인
|
||||
const memberCount = await Member.count({ where: { clubId: event.clubId } });
|
||||
|
||||
// 클럽의 구독 정보 가져오기
|
||||
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
|
||||
where: { clubId: event.clubId, status: 'active' },
|
||||
include: [{ model: sequelize.models.SubscriptionPlan }]
|
||||
});
|
||||
|
||||
// 구독 플랜이 있고 회원 수가 최대치를 초과하는 경우
|
||||
if (clubSubscription && clubSubscription.SubscriptionPlan) {
|
||||
const maxMembers = clubSubscription.SubscriptionPlan.maxMembers;
|
||||
if (memberCount >= maxMembers) {
|
||||
return res.status(403).json({
|
||||
message: `회원 수가 구독 플랜의 최대 허용 인원(${maxMembers}명)을 초과했습니다. 구독 업그레이드가 필요합니다.`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// publicAuth 미들웨어로 인증됨
|
||||
const member = await Member.create({
|
||||
name,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { SubscriptionPlan, SubscriptionPlanFeature, ClubSubscription, Feature, Club, User } = require('../models');
|
||||
const { SubscriptionPlan, SubscriptionPlanFeature, ClubSubscription, Feature, Club, User, PaymentRequest } = require('../models');
|
||||
|
||||
// 전체 구독 목록 조회
|
||||
exports.getClubSubscriptions = async (req, res) => {
|
||||
@@ -45,6 +45,29 @@ exports.getSubscriptionPlans = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 플랜 상세 정보 조회 (포함된 기능 포함)
|
||||
exports.getPlanDetail = async (req, res) => {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
|
||||
const plan = await SubscriptionPlan.findByPk(planId, {
|
||||
include: [{
|
||||
model: Feature,
|
||||
through: { attributes: ['status'] }
|
||||
}]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({ message: '해당 플랜을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json(plan);
|
||||
} catch (error) {
|
||||
console.error('Error in getPlanDetail:', error);
|
||||
res.status(500).json({ message: '플랜 상세 정보 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 구독 플랜 생성
|
||||
exports.createSubscriptionPlan = async (req, res) => {
|
||||
try {
|
||||
@@ -192,7 +215,7 @@ exports.subscribeClub = async (req, res) => {
|
||||
// 클럽의 현재 구독 정보 조회
|
||||
exports.getClubSubscription = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const { clubId } = req.body;
|
||||
|
||||
const subscription = await ClubSubscription.findOne({
|
||||
where: { clubId, status: 'active' },
|
||||
@@ -238,7 +261,9 @@ exports.getCurrentSubscription = async (req, res) => {
|
||||
planId: subscription.SubscriptionPlan.id,
|
||||
status: subscription.status,
|
||||
startDate: subscription.startDate,
|
||||
endDate: subscription.expiryDate
|
||||
endDate: subscription.endDate,
|
||||
planPrice: subscription.SubscriptionPlan.price,
|
||||
price: subscription.price
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in getCurrentSubscription:', error);
|
||||
@@ -267,7 +292,368 @@ exports.getSubscriptionPayments = async (req, res) => {
|
||||
})));
|
||||
} catch (error) {
|
||||
console.error('Error in getSubscriptionPayments:', error);
|
||||
res.status(500).json({ message: '결제 내역 조회 중 오류가 발생했습니다.' });
|
||||
res.status(500).json({ message: '구독 결제 내역 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 플랜 변경과 추가 기능을 함께 처리
|
||||
exports.changeWithFeatures = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
clubId,
|
||||
currentPlanId,
|
||||
newPlanId,
|
||||
isUpgrade,
|
||||
refundAmount,
|
||||
proRatedNewPlanPrice,
|
||||
planPriceDifference,
|
||||
features,
|
||||
totalFeaturesPrice,
|
||||
finalAmount,
|
||||
remainingDays
|
||||
} = req.body;
|
||||
|
||||
// 클럽과 구독 정보 확인
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
const currentSubscription = await ClubSubscription.findOne({
|
||||
where: {
|
||||
clubId,
|
||||
planId: currentPlanId,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
if (!currentSubscription) {
|
||||
return res.status(404).json({ message: '현재 활성화된 구독을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
const newPlan = await SubscriptionPlan.findByPk(newPlanId);
|
||||
if (!newPlan) {
|
||||
return res.status(404).json({ message: '선택한 플랜을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 트랜잭션 시작
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
if (isUpgrade) {
|
||||
// 업그레이드: 현재 구독 업데이트
|
||||
currentSubscription.planId = newPlanId;
|
||||
currentSubscription.price = newPlan.price;
|
||||
await currentSubscription.save({ transaction });
|
||||
|
||||
// 결제 기록 생성
|
||||
await Payment.create({
|
||||
clubId,
|
||||
amount: planPriceDifference,
|
||||
description: `플랜 업그레이드: ${currentPlanId} -> ${newPlanId}`,
|
||||
status: 'completed',
|
||||
paymentDate: new Date()
|
||||
}, { transaction });
|
||||
} else {
|
||||
// 다운그레이드: 다음 결제일에 적용될 구독 생성
|
||||
await ClubSubscription.create({
|
||||
clubId,
|
||||
planId: newPlanId,
|
||||
status: 'pending',
|
||||
startDate: currentSubscription.endDate,
|
||||
endDate: new Date(new Date(currentSubscription.endDate).setMonth(
|
||||
new Date(currentSubscription.endDate).getMonth() + 1
|
||||
)),
|
||||
price: newPlan.price
|
||||
}, { transaction });
|
||||
}
|
||||
|
||||
// 추가 기능 처리
|
||||
if (features && features.length > 0) {
|
||||
for (const feature of features) {
|
||||
// 현재 구독 만료일 계산
|
||||
const expiryDate = new Date(currentSubscription.endDate);
|
||||
|
||||
// 1개월 초과 구독인 경우 해당 기간만큼 추가
|
||||
if (feature.duration > 1) {
|
||||
expiryDate.setMonth(expiryDate.getMonth() + feature.duration - 1);
|
||||
}
|
||||
|
||||
// 기능 구매 기록 생성
|
||||
await ClubFeature.create({
|
||||
clubId,
|
||||
featureId: feature.featureId,
|
||||
expiryDate,
|
||||
enabled: true
|
||||
}, { transaction });
|
||||
|
||||
// 기능 결제 기록 생성
|
||||
await Payment.create({
|
||||
clubId,
|
||||
amount: feature.price,
|
||||
description: `추가 기능 구매: ${feature.featureId} (${feature.duration}개월)`,
|
||||
status: 'completed',
|
||||
paymentDate: new Date()
|
||||
}, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
// 트랜잭션 커밋
|
||||
await transaction.commit();
|
||||
|
||||
res.json({
|
||||
message: '플랜 변경 및 추가 기능 구매가 완료되었습니다.',
|
||||
isUpgrade,
|
||||
planChanged: true,
|
||||
featuresAdded: features.length > 0,
|
||||
totalAmount: finalAmount
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// 트랜잭션 롤백
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('플랜 변경 및 추가 기능 구매 오류:', error);
|
||||
res.status(500).json({ message: '처리 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 토스페이먼츠 결제 요청 생성
|
||||
exports.requestPayment = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
clubId,
|
||||
planId,
|
||||
features,
|
||||
totalAmount,
|
||||
planDuration,
|
||||
planChange,
|
||||
currentPlanId,
|
||||
isUpgrade,
|
||||
successUrl,
|
||||
failUrl
|
||||
} = req.body;
|
||||
|
||||
if (!clubId || !totalAmount) {
|
||||
return res.status(400).json({ message: '필수 데이터가 누락되었습니다.' });
|
||||
}
|
||||
|
||||
// 클럽 정보 확인
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 결제 정보 저장
|
||||
const paymentInfo = await PaymentRequest.create({
|
||||
clubId,
|
||||
planId: planId || null,
|
||||
currentPlanId: currentPlanId || null,
|
||||
isUpgrade: isUpgrade || false,
|
||||
planDuration: planDuration || 1,
|
||||
features: features ? JSON.stringify(features) : null,
|
||||
totalAmount,
|
||||
status: 'pending',
|
||||
orderId: `order_${Date.now()}_${clubId}`,
|
||||
planChange: planChange || false
|
||||
});
|
||||
|
||||
// 토스페이먼츠에 전송할 데이터 구성
|
||||
const paymentData = {
|
||||
orderId: paymentInfo.orderId,
|
||||
amount: totalAmount,
|
||||
orderName: planId ? `구독 플랜: ${planDuration}개월` : '추가 기능 구매',
|
||||
customerName: club.name,
|
||||
successUrl: successUrl || `${process.env.FRONTEND_URL}/payment/success`,
|
||||
failUrl: failUrl || `${process.env.FRONTEND_URL}/payment/fail`,
|
||||
customerEmail: req.user.email
|
||||
};
|
||||
|
||||
res.json({
|
||||
paymentData,
|
||||
clientKey: process.env.TOSS_PAYMENTS_CLIENT_KEY
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('결제 요청 생성 오류:', error);
|
||||
res.status(500).json({ message: '결제 요청 생성 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 결제 성공 처리
|
||||
exports.processPaymentSuccess = async (req, res) => {
|
||||
try {
|
||||
const { orderId, paymentKey, amount } = req.query;
|
||||
|
||||
// 결제 요청 정보 조회
|
||||
const paymentRequest = await PaymentRequest.findOne({ where: { orderId } });
|
||||
if (!paymentRequest) {
|
||||
return res.status(404).json({ message: '결제 정보를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 금액 확인
|
||||
if (paymentRequest.totalAmount !== parseInt(amount)) {
|
||||
return res.status(400).json({ message: '결제 금액이 일치하지 않습니다.' });
|
||||
}
|
||||
|
||||
// 토스페이먼츠 API로 결제 승인 요청
|
||||
const tossPaymentsSecretKey = process.env.TOSS_PAYMENTS_SECRET_KEY;
|
||||
const encryptedSecretKey = Buffer.from(`${tossPaymentsSecretKey}:`).toString('base64');
|
||||
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
url: 'https://api.tosspayments.com/v1/payments/confirm',
|
||||
headers: {
|
||||
Authorization: `Basic ${encryptedSecretKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: {
|
||||
orderId,
|
||||
amount,
|
||||
paymentKey
|
||||
}
|
||||
});
|
||||
|
||||
// 결제 요청 상태 업데이트
|
||||
paymentRequest.status = 'completed';
|
||||
paymentRequest.paymentKey = paymentKey;
|
||||
paymentRequest.paymentData = JSON.stringify(response.data);
|
||||
await paymentRequest.save();
|
||||
|
||||
// 트랜잭션 시작
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 플랜 변경 처리
|
||||
if (paymentRequest.planId) {
|
||||
if (paymentRequest.planChange) {
|
||||
if (paymentRequest.isUpgrade) {
|
||||
// 업그레이드: 현재 구독 업데이트
|
||||
const currentSubscription = await ClubSubscription.findOne({
|
||||
where: {
|
||||
clubId: paymentRequest.clubId,
|
||||
planId: paymentRequest.currentPlanId,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
if (currentSubscription) {
|
||||
currentSubscription.planId = paymentRequest.planId;
|
||||
await currentSubscription.save({ transaction });
|
||||
}
|
||||
} else {
|
||||
// 다운그레이드: 다음 결제일에 적용될 구독 생성
|
||||
const currentSubscription = await ClubSubscription.findOne({
|
||||
where: {
|
||||
clubId: paymentRequest.clubId,
|
||||
planId: paymentRequest.currentPlanId,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
if (currentSubscription) {
|
||||
await ClubSubscription.create({
|
||||
clubId: paymentRequest.clubId,
|
||||
planId: paymentRequest.planId,
|
||||
status: 'pending',
|
||||
startDate: currentSubscription.endDate,
|
||||
endDate: new Date(new Date(currentSubscription.endDate).setMonth(
|
||||
new Date(currentSubscription.endDate).getMonth() + paymentRequest.planDuration
|
||||
)),
|
||||
price: (await SubscriptionPlan.findByPk(paymentRequest.planId))?.price || 0
|
||||
}, { transaction });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 새 구독 생성
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setMonth(endDate.getMonth() + paymentRequest.planDuration);
|
||||
|
||||
await ClubSubscription.create({
|
||||
clubId: paymentRequest.clubId,
|
||||
planId: paymentRequest.planId,
|
||||
status: 'active',
|
||||
startDate,
|
||||
endDate,
|
||||
price: (await SubscriptionPlan.findByPk(paymentRequest.planId))?.price || 0
|
||||
}, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
// 추가 기능 처리
|
||||
if (paymentRequest.features) {
|
||||
const features = JSON.parse(paymentRequest.features);
|
||||
|
||||
for (const feature of features) {
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setMonth(expiryDate.getMonth() + (feature.duration || paymentRequest.planDuration));
|
||||
|
||||
await ClubFeature.create({
|
||||
clubId: paymentRequest.clubId,
|
||||
featureId: feature.featureId,
|
||||
expiryDate,
|
||||
enabled: true
|
||||
}, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
// 결제 기록 생성
|
||||
await Payment.create({
|
||||
clubId: paymentRequest.clubId,
|
||||
amount: paymentRequest.totalAmount,
|
||||
description: paymentRequest.planId
|
||||
? `구독 플랜 ${paymentRequest.planDuration}개월`
|
||||
: '추가 기능 구매',
|
||||
status: 'completed',
|
||||
paymentDate: new Date(),
|
||||
paymentMethod: response.data.method || 'card',
|
||||
paymentKey
|
||||
}, { transaction });
|
||||
|
||||
// 트랜잭션 커밋
|
||||
await transaction.commit();
|
||||
|
||||
// 성공 페이지로 리디렉션
|
||||
res.redirect(`${process.env.FRONTEND_URL}/payment/success?orderId=${orderId}`);
|
||||
|
||||
} catch (error) {
|
||||
// 트랜잭션 롤백
|
||||
await transaction.rollback();
|
||||
console.error('결제 처리 오류:', error);
|
||||
res.redirect(`${process.env.FRONTEND_URL}/payment/fail?message=결제는 성공했지만 처리 중 오류가 발생했습니다.`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('결제 성공 처리 오류:', error);
|
||||
res.redirect(`${process.env.FRONTEND_URL}/payment/fail?message=결제 처리 중 오류가 발생했습니다.`);
|
||||
}
|
||||
};
|
||||
|
||||
// 결제 실패 처리
|
||||
exports.processPaymentFail = async (req, res) => {
|
||||
try {
|
||||
const { orderId, message } = req.query;
|
||||
|
||||
// 결제 요청 정보 조회 및 업데이트
|
||||
if (orderId) {
|
||||
const paymentRequest = await PaymentRequest.findOne({ where: { orderId } });
|
||||
if (paymentRequest) {
|
||||
paymentRequest.status = 'failed';
|
||||
paymentRequest.failReason = message || '사용자가 결제를 취소했습니다.';
|
||||
await paymentRequest.save();
|
||||
}
|
||||
}
|
||||
|
||||
// 실패 페이지로 리디렉션
|
||||
res.redirect(`${process.env.FRONTEND_URL}/payment/fail?message=${encodeURIComponent(message || '결제가 취소되었습니다.')}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('결제 실패 처리 오류:', error);
|
||||
res.redirect(`${process.env.FRONTEND_URL}/payment/fail?message=결제 취소 처리 중 오류가 발생했습니다.`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user