diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue index a5882dd..7f03ae4 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -136,8 +136,13 @@ -

服务器创建成功!

-

您的 Jsite 服务器已成功开通。

+

支付成功!

+

+ 您的订单已支付成功,服务器正在创建中。创建并启动需要 3-5 分钟,请耐心等待。 +

+

+ 创建完成后,您将收到通知,也可以在服务器列表中查看状态。 +

@@ -437,9 +442,6 @@ export default { this.isProcessingPayment = false; this.paymentSuccess = true; - console.log('余额支付成功,更新状态:', { - paymentSuccess: this.paymentSuccess - }); }, onError(error) { this.error = error.message || '余额支付处理失败'; @@ -462,6 +464,7 @@ export default { toast.success('支付页面已在新窗口打开'); + // 开始轮询支付状态 this.startPaymentCheck(); }, onError(error) { @@ -483,6 +486,7 @@ export default { this.paymentQrCode = response.payment_url; this.paymentQrCodeImage = response.qr_code_image || null; + // 开始轮询支付状态 this.startPaymentCheck(); }, onError(error) { @@ -495,13 +499,17 @@ export default { checkPaymentStatus() { return { url: 'jcloud.api.billing.check_server_order_payment_status', + params: { + order_id: this.order?.order_id + }, validate() { if (!this.order || !this.order.order_id) { throw new DashboardError('缺少订单信息'); } }, onSuccess(data) { - if (data && data.status === '交易成功') { + if (data && data.status === '已支付') { + // 支付成功,停止轮询 this.stopPaymentCheck(); const orderData = { ...this.order, ...(data.order || {}) }; @@ -511,7 +519,11 @@ export default { } } }; - } + }, + + }, + beforeUnmount() { + this.stopPaymentCheck(); }, mounted() { console.log('组件mounted,开始获取地域列表'); @@ -558,6 +570,24 @@ export default { this.isLoading = false; }, + startPaymentCheck() { + this.isProcessingPayment = false; + + this.checkInterval = setInterval(() => { + this.$resources.checkPaymentStatus.submit(); + }, 3000); + + // 15分钟后停止检查 + setTimeout(() => { + this.stopPaymentCheck(); + }, 900000); + }, + stopPaymentCheck() { + if (this.checkInterval) { + clearInterval(this.checkInterval); + this.checkInterval = null; + } + }, processPayment() { this.isProcessingPayment = true; this.error = null; @@ -588,26 +618,9 @@ export default { }); } }, - startPaymentCheck() { - this.isProcessingPayment = false; - - this.checkInterval = setInterval(() => { - this.$resources.checkPaymentStatus.submit(); - }, 3000); - - // 15分钟后停止检查 - setTimeout(() => { - this.stopPaymentCheck(); - }, 900000); - }, - stopPaymentCheck() { - if (this.checkInterval) { - clearInterval(this.checkInterval); - this.checkInterval = null; - } - }, + + close() { - this.stopPaymentCheck(); this.show = false; this.$emit('success'); }, @@ -656,9 +669,7 @@ export default { return name; } }, - beforeUnmount() { - this.stopPaymentCheck(); - } + }; diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index bbf3a0b..2ba3d14 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -373,4 +373,129 @@ def get_aliyun_images(image_type='system', region_id='cn-shanghai'): def get_aliyun_regions(): """通过阿里云SDK实时获取可用地域列表""" manager = _get_manager() - return manager.get_regions() \ No newline at end of file + return manager.get_regions() + +# 服务器订单和创建相关 + +@jingrow.whitelist() +def create_server_order(**kwargs): + """创建服务器订单""" + try: + plan_id = kwargs.get('plan_id') + image_id = kwargs.get('image_id') + period = kwargs.get('period', 1) + region_id = kwargs.get('region_id', 'cn-shanghai') + + if not plan_id or not image_id: + jingrow.throw("缺少必要参数") + + from jcloud.utils import get_current_team + team = get_current_team(True) + + # 获取套餐价格 + plans_response = get_aliyun_plans(region_id) + selected_plan = None + if plans_response and plans_response.get('success'): + plans = plans_response['data'].get('plans', []) + selected_plan = next((p for p in plans if p.get('plan_id') == plan_id), None) + + if not selected_plan: + jingrow.throw("套餐不存在") + + # 计算价格 + monthly_price = float(selected_plan.get('origin_price', 0)) + total_amount = monthly_price * period + + # 生成订单号 + from datetime import datetime + import random + order_id = datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6)) + + # 创建订单 + order = jingrow.get_pg({ + "pagetype": "Order", + "order_id": order_id, + "order_type": "新建服务器", + "team": team.name, + "status": "待支付", + "total_amount": total_amount, + "title": f"新建服务器 - {region_id}", + "description": f"{selected_plan.get('core')}核/{selected_plan.get('memory')}GB/{selected_plan.get('disk_size')}GB, {period}个月", + "server_config": { + "plan_id": plan_id, + "image_id": image_id, + "period": period, + "region_id": region_id, + "monthly_price": monthly_price + } + }) + + order.insert(ignore_permissions=True) + jingrow.db.commit() + + return {"success": True, "order": order.as_dict()} + + except Exception as e: + jingrow.log_error("创建服务器订单失败", str(e)) + return {"success": False, "message": str(e)} + +def create_server_async(order_name): + """异步创建服务器""" + try: + order = jingrow.get_pg("Order", order_name) + if not order: + raise Exception("订单不存在") + + # 获取配置 + config = order.server_config or {} + plan_id = config.get('plan_id') + image_id = config.get('image_id') + period = config.get('period', 1) + region_id = config.get('region_id', 'cn-shanghai') + + # 调用阿里云API创建实例 + result = create_aliyun_instance(plan_id, image_id, period, region_id) + + if not result or not result.get('success'): + raise Exception(f"阿里云创建失败: {result.get('message', '未知错误')}") + + instance_id = result['data']['instance_id'] + + # 创建服务器记录 + server = jingrow.get_pg({ + "pagetype": "Jsite Server", + "instance_id": instance_id, + "team": order.team, + "order": order.name, + "status": "Running", + "region_id": region_id, + "plan_id": plan_id, + "image_id": image_id, + "period": period, + "monthly_price": config.get('monthly_price', 0), + "total_amount": order.total_amount, + "payment_method": order.payment_method, + "created_at": jingrow.utils.now(), + "expires_at": jingrow.utils.add_months(jingrow.utils.nowdate(), period) + }) + + server.insert(ignore_permissions=True) + + # 更新订单状态 + order.status = "交易成功" + order.instance_id = instance_id + order.server_record = server.name + order.save(ignore_permissions=True) + + jingrow.db.commit() + + return True + + except Exception as e: + jingrow.log_error("服务器创建失败", f"订单 {order_name}: {str(e)}") + # 更新订单状态为失败 + try: + jingrow.db.set_value("Order", order_name, "status", "创建失败") + except: + pass + raise e \ No newline at end of file diff --git a/jcloud/api/billing.py b/jcloud/api/billing.py index 0adbc82..1bfc9f0 100644 --- a/jcloud/api/billing.py +++ b/jcloud/api/billing.py @@ -1088,6 +1088,9 @@ def handle_order_payment_complete(order_id): process_balance_recharge(order) elif order.order_type == "网站续费": process_site_renew(order_id) + elif order.order_type == "Jsite Server": + # 异步创建服务器 + jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) return True except Exception as e: @@ -1869,3 +1872,121 @@ def get_balance_transactions(page=1, page_size=20, search=None): "total": 0, "error": str(e) } + +# Jsite Server 相关功能 + +@jingrow.whitelist() +def process_balance_payment_for_server_order(order_id): + """使用余额支付服务器订单""" + try: + team = get_current_team(True) + order = jingrow.get_pg("Order", {"order_id": order_id}) + + if not order or order.team != team.name: + jingrow.throw("订单不存在或无权限") + + if order.status != "待支付": + return {"success": False, "message": "订单已处理"} + + balance = team.get_balance() + if balance < order.total_amount: + return {"success": False, "message": "余额不足"} + + # 扣款 + balance_transaction = jingrow.get_pg({ + "pagetype": "Balance Transaction", + "team": team.name, + "type": "Adjustment", + "source": "Prepaid Credits", + "amount": -order.total_amount, + "description": f"服务器购买: {order.title}" + }) + balance_transaction.insert(ignore_permissions=True) + balance_transaction.submit() + + # 更新订单状态 + order.status = "已支付" + order.payment_method = "余额支付" + order.save(ignore_permissions=True) + + # 异步创建服务器 + jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) + + return {"success": True, "message": "支付成功,服务器创建中"} + + except Exception as e: + jingrow.log_error("服务器支付失败", str(e)) + return {"success": False, "message": str(e)} + +@jingrow.whitelist() +def process_alipay_server_order(order_id): + """支付宝支付服务器订单""" + team = get_current_team(True) + order = jingrow.get_pg("Order", {"order_id": order_id}) + + if not order or order.team != team.name: + jingrow.throw("订单不存在或无权限") + + if order.status != "待支付": + jingrow.throw("订单已处理") + + from jcloud.api.payment.alipay import AlipayAPI + api = AlipayAPI() + + payment_url = api.generate_payment_url( + order_id=order_id, + amount=order.total_amount, + subject=order.title, + team_name=team.name + ) + + order.payment_method = "支付宝" + order.save(ignore_permissions=True) + + return {"payment_url": payment_url, "order_id": order_id} + +@jingrow.whitelist() +def process_wechatpay_server_order(order_id): + """微信支付服务器订单""" + team = get_current_team(True) + order = jingrow.get_pg("Order", {"order_id": order_id}) + + if not order or order.team != team.name: + jingrow.throw("订单不存在或无权限") + + if order.status != "待支付": + jingrow.throw("订单已处理") + + wechat_pay = WeChatPayAPI() + qr_code_url = wechat_pay.generate_payment_url( + order_id=order_id, + amount=order.total_amount, + subject=order.title, + team_name=team.name + ) + + order.payment_method = "微信支付" + order.save(ignore_permissions=True) + + return {"qr_code_url": qr_code_url, "order_id": order_id} + +@jingrow.whitelist() +def check_server_order_payment_status(order_id): + """检查服务器订单支付状态""" + try: + order = jingrow.get_pg("Order", {"order_id": order_id}) + if not order: + jingrow.throw(f"找不到订单: {order_id}") + + return { + "success": True, + "status": order.status, + "order": order.as_dict() + } + + except Exception as e: + jingrow.log_error("服务器订单错误", f"检查服务器订单状态失败: {str(e)}") + return { + "success": False, + "message": f"检查订单状态失败: {str(e)}" + } diff --git a/jcloud/jcloud/pagetype/order/order.json b/jcloud/jcloud/pagetype/order/order.json index 333313a..9508344 100644 --- a/jcloud/jcloud/pagetype/order/order.json +++ b/jcloud/jcloud/pagetype/order/order.json @@ -2,7 +2,6 @@ "actions": [], "allow_rename": 1, "creation": "2025-03-23 21:29:54.329381", - "pagetype": "PageType", "engine": "InnoDB", "field_order": [ "title", @@ -37,7 +36,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Status", - "options": "\u5f85\u652f\u4ed8\n\u5df2\u652f\u4ed8\n\u4ea4\u6613\u6210\u529f\n\u5df2\u53d6\u6d88\n\u5df2\u9000\u6b3e", + "options": "待支付\n已支付\n交易成功\n已取消\n已退款", "read_only": 1 }, { @@ -45,7 +44,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Payment Method", - "options": "\n\u652f\u4ed8\u5b9d\n\u5fae\u4fe1\u652f\u4ed8\n\u4f59\u989d\u652f\u4ed8\n\u94f6\u884c\u8f6c\u8d26\n\u5176\u4ed6", + "options": "\n支付宝\n微信支付\n余额支付\n银行转账\n其他", "read_only": 1 }, { @@ -78,18 +77,19 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Order Type", - "options": "\n\u4f59\u989d\u5145\u503c\n\u65b0\u5efa\u7f51\u7ad9\n\u7f51\u7ad9\u7eed\u8d39\n\u57df\u540d\u7eed\u8d39", + "options": "\n余额充值\n新建网站\n网站续费\n域名续费\n新建服务器\n服务器续费", "read_only": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-03-26 03:34:52.624889", + "modified": "2025-07-27 17:29:16.088188", "modified_by": "Administrator", "module": "Jcloud", "name": "Order", "owner": "Administrator", + "pagetype": "PageType", "permissions": [ { "create": 1, @@ -104,6 +104,7 @@ "write": 1 } ], + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", "states": [] diff --git a/jcloud/jcloud/pagetype/order/order.py b/jcloud/jcloud/pagetype/order/order.py index 816f515..d7d807d 100644 --- a/jcloud/jcloud/pagetype/order/order.py +++ b/jcloud/jcloud/pagetype/order/order.py @@ -16,7 +16,7 @@ class Order(Document): description: DF.Data | None order_id: DF.Data | None - order_type: DF.Literal["", "\u4f59\u989d\u5145\u503c", "\u65b0\u5efa\u7f51\u7ad9", "\u7f51\u7ad9\u7eed\u8d39", "\u57df\u540d\u7eed\u8d39"] + order_type: DF.Literal["", "\u4f59\u989d\u5145\u503c", "\u65b0\u5efa\u7f51\u7ad9", "\u7f51\u7ad9\u7eed\u8d39", "\u57df\u540d\u7eed\u8d39", "\u65b0\u5efa\u670d\u52a1\u5668", "\u670d\u52a1\u5668\u7eed\u8d39"] payment_method: DF.Literal["", "\u652f\u4ed8\u5b9d", "\u5fae\u4fe1\u652f\u4ed8", "\u4f59\u989d\u652f\u4ed8", "\u94f6\u884c\u8f6c\u8d26", "\u5176\u4ed6"] status: DF.Literal["\u5f85\u652f\u4ed8", "\u5df2\u652f\u4ed8", "\u4ea4\u6613\u6210\u529f", "\u5df2\u53d6\u6d88", "\u5df2\u9000\u6b3e"] team: DF.Link | None