Files
ai-learning-platform/backend/src/modules/orders/orders.service.ts
T
yuzhiran-dev 23edb74bce feat: 支付闭环 + 运营助手 Tool Calling + 管理后台完善
- 支付系统:微信支付 mock 自动完成、NATIVE 扫码支付、JSAPI 集成
- 运营助手:Tool Calling 架构,19 个可执行工具,AI 驱动操作
- 角色管理:表格布局 + Dialog 表单 + 权限勾选
- 配置统一:config.ts 单一数据源
- API 审计:补齐 status toggle / comments 端点
- 暗黑模式硬件编码颜色全部替换为 CSS 变量
2026-05-20 18:29:00 +08:00

90 lines
2.7 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { PaymentService } from '../payment/payment.service';
@Injectable()
export class OrdersService {
constructor(
private prisma: PrismaService,
private paymentService: PaymentService,
) {}
async create(userId: number, data: { amount: number; planType: string; payChannel?: string; tradeType?: string; openid?: string }) {
const orderNo = `YZR${Date.now()}${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
const order = await this.prisma.order.create({
data: {
orderNo,
userId,
amount: data.amount,
planType: data.planType,
payChannel: data.payChannel || 'wxpay',
},
});
// If paying via WeChat, create unified order
if (data.payChannel === 'wxpay' || !data.payChannel) {
try {
const planLabels: Record<string, string> = {
MONTHLY: '宇之然AI月卡会员',
YEARLY: '宇之然AI年卡会员',
};
const payResult = await this.paymentService.createUnifiedOrder({
description: planLabels[data.planType] || '宇之然AI会员充值',
outTradeNo: orderNo,
amount: data.amount,
tradeType: (data.tradeType as 'JSAPI' | 'NATIVE' | 'MWEB') || 'NATIVE',
openid: data.openid,
});
// Mock 模式下自动完成支付闭环(NATIVE mock 无需扫码)
if (payResult?.codeUrl === 'mock://pay') {
await this.paymentService.completeMockPayment(orderNo);
}
return { order, payResult };
} catch {
return { order, payResult: null };
}
}
return { order };
}
async findByUser(userId: number, params: { page?: number; pageSize?: number }) {
const page = Number(params.page ?? 1);
const pageSize = Number(params.pageSize ?? 20);
const [items, total] = await Promise.all([
this.prisma.order.findMany({
where: { userId },
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
this.prisma.order.count({ where: { userId } }),
]);
return { items, total, page, pageSize };
}
async findByOrderNo(orderNo: string) {
return this.prisma.order.findUnique({ where: { orderNo } });
}
async getCurrentSubscription(userId: number) {
const now = new Date();
return this.prisma.subscription.findFirst({
where: {
userId,
status: 'ACTIVE',
endDate: { gt: now },
},
orderBy: { endDate: 'desc' },
});
}
async getSubscriptions(userId: number) {
return this.prisma.subscription.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
});
}
}