course_web01 / backend /src /payment /payment.service.ts
trae-bot
Update project
426f2a4
import {
Injectable,
NotFoundException,
BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as crypto from 'crypto';
import { Payment, PaymentStatus, PayType } from '../entities/payment.entity';
import { Order, OrderStatus, OrderType } from '../entities/order.entity';
import { UserCourse } from '../entities/user-course.entity';
import { PreparePaymentDto } from './dto/prepare-payment.dto';
import { User } from '../entities/user.entity';
import { Course } from '../entities/course.entity';
@Injectable()
export class PaymentService {
constructor(
@InjectRepository(Payment)
private paymentRepository: Repository<Payment>,
@InjectRepository(Order)
private orderRepository: Repository<Order>,
@InjectRepository(UserCourse)
private userCourseRepository: Repository<UserCourse>,
@InjectRepository(User)
private userRepository: Repository<User>,
@InjectRepository(Course)
private courseRepository: Repository<Course>,
) {}
async prepare(userId: number, preparePaymentDto: PreparePaymentDto) {
const orderIdNum = parseInt(preparePaymentDto.orderId, 10);
const order = await this.orderRepository.findOne({
where: { id: orderIdNum, userId },
});
if (!order) {
throw new NotFoundException('Order not found');
}
if (order.status !== OrderStatus.PENDING) {
throw new BadRequestException('Order is not in pending status');
}
const paymentNo = `PAY${Date.now()}${Math.floor(Math.random() * 1000000)}`;
const payment = this.paymentRepository.create({
orderId: order.id,
paymentNo,
payType: preparePaymentDto.payType,
amount: order.amount,
status: PaymentStatus.PENDING,
});
await this.paymentRepository.save(payment);
// Mock payment params
if (preparePaymentDto.payType === PayType.WECHAT) {
return { payParams: 'mock_wechat_pay_params', paymentNo };
} else {
return { orderInfo: 'mock_alipay_order_string', paymentNo };
}
}
// Mock webhook handler
async handleCallback(paymentNo: string, status: 'SUCCESS' | 'FAILED') {
const payment = await this.paymentRepository.findOne({
where: { paymentNo },
relations: ['order'],
});
if (!payment || payment.status !== PaymentStatus.PENDING) {
return {
success: false,
message: 'Invalid payment or already processed',
};
}
if (status === 'SUCCESS') {
payment.status = PaymentStatus.SUCCESS;
payment.completedAt = new Date();
await this.paymentRepository.save(payment);
const order = payment.order;
order.status = OrderStatus.PAID;
order.paidAt = new Date();
await this.orderRepository.save(order);
// Only grant access if it's a purchase order
if (order.orderType === OrderType.PURCHASE) {
// Send email with drive link instead of generating access token
const user = await this.userRepository.findOne({ where: { id: order.userId } });
const course = await this.courseRepository.findOne({ where: { id: order.courseId } });
if (user && course && course.driveLink) {
// In a real application, you would use a mail service like Nodemailer here
console.log('----------------------------------------');
console.log(`[MOCK EMAIL SERVICE] Sending course materials...`);
console.log(`To: ${user.email}`);
console.log(`Subject: 您购买的课程【${course.title}】资料`);
console.log(`Body: 感谢您的购买!您的课程资料链接如下:\n${course.driveLink}`);
console.log('----------------------------------------');
}
// We still create a UserCourse record to mark that the user has purchased it,
// but the accessToken isn't strictly needed anymore for viewing the content.
const expiredAt = new Date();
expiredAt.setFullYear(expiredAt.getFullYear() + 1); // 1 year access
const accessToken = crypto.randomBytes(32).toString('hex');
const userCourse = this.userCourseRepository.create({
userId: order.userId,
courseId: order.courseId,
accessToken,
expiredAt,
});
// Handle duplicate purchase gracefully
try {
await this.userCourseRepository.save(userCourse);
} catch {
// Already purchased, maybe extend expiration
}
} else if (order.orderType === OrderType.VIP) {
// Upgrade user to VIP
const user = await this.userRepository.findOne({ where: { id: order.userId } });
if (user) {
user.isVip = true;
await this.userRepository.save(user);
}
}
} else {
payment.status = PaymentStatus.FAILED;
await this.paymentRepository.save(payment);
}
return { success: true };
}
}