feat: add software subscription orders
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
{% if user.is_authenticated %}
|
||||
<a class="btn btn-sm {% if current_url == 'portal-dashboard' %}btn-primary{% else %}btn-outline-secondary{% endif %}"{% if current_url == 'portal-dashboard' %} aria-current="page"{% endif %} href="{% url 'portal-dashboard' %}">控制台</a>
|
||||
<a class="btn btn-sm {% if current_url == 'portal-recharge' %}btn-primary{% else %}btn-outline-secondary{% endif %}"{% if current_url == 'portal-recharge' %} aria-current="page"{% endif %} href="{% url 'portal-recharge' %}">充值</a>
|
||||
<a class="btn btn-sm {% if current_url == 'portal-subscription' %}btn-primary{% else %}btn-outline-secondary{% endif %}"{% if current_url == 'portal-subscription' %} aria-current="page"{% endif %} href="{% url 'portal-subscription' %}">软件订阅</a>
|
||||
<a class="btn btn-sm {% if current_url == 'portal-apikeys' %}btn-primary{% else %}btn-outline-secondary{% endif %}"{% if current_url == 'portal-apikeys' %} aria-current="page"{% endif %} href="{% url 'portal-apikeys' %}">API Key</a>
|
||||
<a class="btn btn-sm {% if current_url == 'portal-models' %}btn-primary{% else %}btn-outline-secondary{% endif %}"{% if current_url == 'portal-models' %} aria-current="page"{% endif %} href="{% url 'portal-models' %}">可用模型</a>
|
||||
<a class="btn btn-sm {% if current_url == 'portal-recharge-records' %}btn-primary{% else %}btn-outline-secondary{% endif %}"{% if current_url == 'portal-recharge-records' %} aria-current="page"{% endif %} href="{% url 'portal-recharge-records' %}">充值记录</a>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
{% extends "portal/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}软件订阅 - 虾皮圈{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex flex-column gap-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">软件订阅</h1>
|
||||
<div class="text-secondary">虾皮圈优化助手</div>
|
||||
</div>
|
||||
|
||||
<section class="cmhub-surface">
|
||||
<h2 class="h5 mb-3">当前授权</h2>
|
||||
{% if entitlement %}
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-3">套餐</dt><dd class="col-sm-9">{{ entitlement.plan_name }}</dd>
|
||||
<dt class="col-sm-3">状态</dt><dd class="col-sm-9">{{ entitlement.status }}</dd>
|
||||
<dt class="col-sm-3">到期时间</dt><dd class="col-sm-9">{{ entitlement.expires_at|date:"Y-m-d H:i" }}</dd>
|
||||
<dt class="col-sm-3">宽限截止</dt><dd class="col-sm-9">{{ entitlement.grace_expires_at|date:"Y-m-d H:i" }}</dd>
|
||||
</dl>
|
||||
{% else %}
|
||||
<div class="text-secondary">暂无有效授权记录</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section class="cmhub-surface">
|
||||
<h2 class="h5 mb-3">购买或续订</h2>
|
||||
<form method="post" class="row g-3 align-items-end" novalidate>
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="col-12"><div class="alert alert-danger mb-0">{{ form.non_field_errors|striptags }}</div></div>
|
||||
{% endif %}
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="{{ form.plan.id_for_label }}">{{ form.plan.label }}</label>
|
||||
{{ form.plan }}
|
||||
{% if form.plan.errors %}<div class="text-danger small mt-1">{{ form.plan.errors|striptags }}</div>{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-label">{{ form.pay_method.label }}</div>
|
||||
{% for radio in form.pay_method %}
|
||||
<div class="form-check">{{ radio.tag }}<label class="form-check-label" for="{{ radio.id_for_label }}">{{ radio.choice_label }}</label></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-3"><button class="btn btn-primary w-100" type="submit">创建订单</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{% if current_order %}
|
||||
<section class="cmhub-surface" data-software-order data-order-no="{{ current_order.order_no }}" data-status="{{ current_order.status }}" data-code-url="{{ current_order.code_url }}" data-status-url="{% url 'api-software-order-status' %}">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap mb-3">
|
||||
<div><h2 class="h5 mb-1">订阅订单</h2><div class="text-secondary small">订单号:<code>{{ current_order.order_no }}</code></div></div>
|
||||
<span class="badge text-bg-secondary" id="software-order-status">{{ current_order.status }}</span>
|
||||
</div>
|
||||
{% if is_mock_payment_mode %}<div class="alert alert-warning">当前为支付测试模式,二维码不能用于真实付款。</div>{% endif %}
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-md-5"><canvas id="software-order-qr" class="border rounded bg-white p-2" width="240" height="240"></canvas></div>
|
||||
<div class="col-md-7">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4">套餐</dt><dd class="col-sm-8">{{ current_order.plan_name }}</dd>
|
||||
<dt class="col-sm-4">金额</dt><dd class="col-sm-8">{{ current_order.amount_money|floatformat:2 }} {{ current_order.currency }}</dd>
|
||||
<dt class="col-sm-4">支付票据有效期</dt><dd class="col-sm-8">{{ current_order.expires_at|date:"Y-m-d H:i"|default:"-" }}</dd>
|
||||
<dt class="col-sm-4">权益发放时间</dt><dd class="col-sm-8" id="software-order-fulfilled">{{ current_order.fulfilled_at|date:"Y-m-d H:i"|default:"-" }}</dd>
|
||||
</dl>
|
||||
<div class="alert alert-info mt-3 mb-0" id="software-order-hint" aria-live="polite">正在等待支付结果。</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="cmhub-surface">
|
||||
<h2 class="h5 mb-3">最近订单</h2>
|
||||
{% if recent_orders %}
|
||||
<div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>订单号</th><th>套餐</th><th>金额</th><th>状态</th><th>创建时间</th></tr></thead><tbody>
|
||||
{% for order in recent_orders %}<tr><td><code>{{ order.order_no }}</code></td><td>{{ order.plan_name }}</td><td>{{ order.amount_money|floatformat:2 }} {{ order.currency }}</td><td>{{ order.status }}</td><td>{{ order.created_at|date:"Y-m-d H:i" }}</td></tr>{% endfor %}
|
||||
</tbody></table></div>
|
||||
{% else %}<div class="text-secondary">暂无软件订阅订单</div>{% endif %}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'portal/vendor/qrcode/qrcode.js' %}"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const panel = document.querySelector("[data-software-order]");
|
||||
if (!panel) return;
|
||||
const canvas = document.getElementById("software-order-qr");
|
||||
const statusBadge = document.getElementById("software-order-status");
|
||||
const fulfilled = document.getElementById("software-order-fulfilled");
|
||||
const hint = document.getElementById("software-order-hint");
|
||||
if (panel.dataset.codeUrl && window.QRCode && typeof window.QRCode.toCanvas === "function") {
|
||||
window.QRCode.toCanvas(canvas, panel.dataset.codeUrl, { width: 240, margin: 1 });
|
||||
}
|
||||
function poll() {
|
||||
fetch(panel.dataset.statusUrl + "?order_no=" + encodeURIComponent(panel.dataset.orderNo), { credentials: "same-origin", headers: { Accept: "application/json" } })
|
||||
.then(function (response) { return response.json().then(function (data) { if (!response.ok) throw new Error(); return data; }); })
|
||||
.then(function (data) {
|
||||
statusBadge.textContent = data.status;
|
||||
statusBadge.className = data.status === "paid" ? "badge text-bg-success" : "badge text-bg-secondary";
|
||||
if (data.fulfilled_at) fulfilled.textContent = new Date(data.fulfilled_at).toLocaleString();
|
||||
if (data.status === "paid") { hint.textContent = "订阅权益已发放,页面即将刷新。"; window.setTimeout(function () { window.location.reload(); }, 1200); return; }
|
||||
hint.textContent = data.status === "pending" ? "正在等待支付结果。" : "订单当前状态为 " + data.status + "。";
|
||||
if (data.status === "pending") window.setTimeout(poll, 1000);
|
||||
})
|
||||
.catch(function () { hint.textContent = "状态查询失败,稍后自动重试。"; window.setTimeout(poll, 3000); });
|
||||
}
|
||||
if (panel.dataset.status === "pending") window.setTimeout(poll, 1000);
|
||||
}());
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user