feat(pay): 卡密兑换入口收敛到三个购买弹窗并对齐参考主题
- 移除余额卡片独立「卡密兑换」通用弹窗,兑换入口分布到充值/积分/会员三个弹窗的支付方式中 - 充值弹窗补齐卡密 logo 与仅卡密时的金额选择隐藏逻辑 - 积分购买弹窗 1:1 复刻参考主题 points-pay modal(商品档位/自定义/支付方式/立即支付),REST 改为 product+custom 契约 - 会员弹窗注入卡密渠道:视图模型透传 card_pass,前端渲染卡密盒并直兑 /pay/card-pass/redeem type=vip - 后台卡密兑换拆分为余额/积分/会员三个独立开关,VIP 段旧会员卡密组清理 - REST type 改为必填(balance/vip/credits),三类型独立闸门 - 测试重写为 card-pass-modal-redeem 契约,321 项全部通过
This commit is contained in:
1 parent
74e43aaa76
commit
b88eab0bfe
18 files changed
+746
-472
No files matched your search
+112
-16
@@ -102,7 +102,7 @@
|
||||
append(parent, wrap);
|
||||
}
|
||||
|
||||
function renderMethods(parent, methods) {
|
||||
function renderMethods(parent, methods, cardPass) {
|
||||
methods = Array.isArray(methods) ? methods : [];
|
||||
if (!methods.length) {
|
||||
append(parent, node("div", "muted-2-color text-center padding-10", "暂无可用支付方式"));
|
||||
@@ -115,23 +115,60 @@
|
||||
one.value = text(methods[0].id || "balance");
|
||||
one.setAttribute("data-aksrt-vip-method-fixed", "");
|
||||
append(parent, one);
|
||||
return;
|
||||
} else {
|
||||
var wrap = node("div", "uc-vip-shop__methods mb10");
|
||||
wrap.setAttribute("data-aksrt-vip-methods", "");
|
||||
append(wrap, node("span", "uc-vip-shop__methods-label", "支付方式"));
|
||||
methods.forEach(function (method, index) {
|
||||
var label = node("label", "uc-vip-shop__method");
|
||||
var input = node("input");
|
||||
input.type = "radio";
|
||||
input.name = "aksrt_vip_pay_method";
|
||||
input.value = text(method.id || "balance");
|
||||
input.checked = index === 0;
|
||||
append(label, input);
|
||||
append(label, node("span", "", method.name || method.id || ""));
|
||||
append(wrap, label);
|
||||
});
|
||||
append(parent, wrap);
|
||||
}
|
||||
var wrap = node("div", "uc-vip-shop__methods mb10");
|
||||
wrap.setAttribute("data-aksrt-vip-methods", "");
|
||||
append(wrap, node("span", "uc-vip-shop__methods-label", "支付方式"));
|
||||
methods.forEach(function (method, index) {
|
||||
var label = node("label", "uc-vip-shop__method");
|
||||
var input = node("input");
|
||||
input.type = "radio";
|
||||
input.name = "aksrt_vip_pay_method";
|
||||
input.value = text(method.id || "balance");
|
||||
input.checked = index === 0;
|
||||
append(label, input);
|
||||
append(label, node("span", "", method.name || method.id || ""));
|
||||
append(wrap, label);
|
||||
// 卡密渠道输入盒(契约与参考主题一致:data-controller="payment_method")。
|
||||
if (cardPass) {
|
||||
var box = node("div", "uc-vip-shop__card-pass mb10");
|
||||
box.setAttribute("data-controller", "payment_method");
|
||||
box.setAttribute("data-condition", "==");
|
||||
box.setAttribute("data-value", "card_pass");
|
||||
box.style.display = "none";
|
||||
if (cardPass.desc) append(box, node("div", "muted-box muted-2-color padding-10 mb10 em09", cardPass.desc));
|
||||
append(box, node("div", "muted-2-color em09 mb6", cardPass.only_password ? "请输入卡密" : "请输入卡号和密码"));
|
||||
if (!cardPass.only_password) {
|
||||
var cardInput = node("input", "form-control mb6");
|
||||
cardInput.type = "text";
|
||||
cardInput.name = "card_pass[card]";
|
||||
cardInput.placeholder = "卡号";
|
||||
cardInput.autocomplete = "off";
|
||||
append(box, cardInput);
|
||||
}
|
||||
var pwInput = node("input", "form-control");
|
||||
pwInput.type = "text";
|
||||
pwInput.name = "card_pass[password]";
|
||||
pwInput.placeholder = "卡密";
|
||||
pwInput.autocomplete = "off";
|
||||
append(box, pwInput);
|
||||
append(parent, box);
|
||||
}
|
||||
}
|
||||
|
||||
// 卡密渠道显隐:选中 card_pass 时隐藏套餐(.charge-box)、显示卡密输入盒。
|
||||
function applyVipMethodVisibility(root) {
|
||||
var pane = root.closest ? root.closest(".aksrt-vip-pane") : null;
|
||||
pane = pane || root;
|
||||
var value = selectedMethod(pane);
|
||||
var cardBox = pane.querySelector('[data-controller="payment_method"][data-value="card_pass"]');
|
||||
if (cardBox) cardBox.style.display = value === "card_pass" ? "" : "none";
|
||||
pane.querySelectorAll(".charge-box").forEach(function (el) {
|
||||
el.style.display = value === "card_pass" ? "none" : "";
|
||||
});
|
||||
append(parent, wrap);
|
||||
}
|
||||
|
||||
function renderPointsModal(body, view) {
|
||||
@@ -248,6 +285,7 @@
|
||||
var levels = levelMap(view.levels);
|
||||
var groups = Array.isArray(catalog.level_packages) ? catalog.level_packages : [];
|
||||
var methods = Array.isArray(view.payment_methods) ? view.payment_methods : [];
|
||||
var cardPass = view.card_pass && view.card_pass.enabled ? view.card_pass : null;
|
||||
var allowed = view.allowed_actions || {};
|
||||
var reasons = view.action_reasons || {};
|
||||
var modal = node("div", "box-body payvip-modal");
|
||||
@@ -354,6 +392,11 @@
|
||||
append(pointsWrap, pointsLink);
|
||||
append(modal, pointsWrap);
|
||||
}
|
||||
|
||||
// 卡密渠道初始显隐(选中 card_pass 时隐藏套餐、显示卡密输入盒)。
|
||||
modal.querySelectorAll(".aksrt-vip-pane").forEach(function (pane) {
|
||||
applyVipMethodVisibility(pane);
|
||||
});
|
||||
append(body, modal);
|
||||
}
|
||||
|
||||
@@ -444,9 +487,62 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 卡密兑换会员(type=vip):直兑,不创建支付订单。
|
||||
function redeemVipCardPass(pane, button) {
|
||||
var pwEl = pane.querySelector('input[name="card_pass[password]"]');
|
||||
var cardEl = pane.querySelector('input[name="card_pass[card]"]');
|
||||
var password = pwEl ? String(pwEl.value || "").trim() : "";
|
||||
if (!password) {
|
||||
notyf("请输入卡密", "danger");
|
||||
if (pwEl) pwEl.focus();
|
||||
return;
|
||||
}
|
||||
if (!button || button.disabled) return;
|
||||
var request = window.AksrtRequest;
|
||||
if (!request || typeof request.request !== "function") {
|
||||
notyf("会员兑换暂不可用", "danger");
|
||||
return;
|
||||
}
|
||||
button.disabled = true;
|
||||
button.classList.add("is-loading");
|
||||
notyf("正在兑换会员…", "load", "", "aksrt-vip-purchase-toast");
|
||||
var key = typeof request.createIdempotencyKey === "function" ? request.createIdempotencyKey() : "";
|
||||
request.request("/pay/card-pass/redeem", {
|
||||
method: "POST",
|
||||
headers: key ? { "Idempotency-Key": key } : {},
|
||||
body: JSON.stringify({
|
||||
type: "vip",
|
||||
card: cardEl ? String(cardEl.value || "").trim() : "",
|
||||
password: password,
|
||||
}),
|
||||
}).then(function (response) {
|
||||
button.disabled = false;
|
||||
button.classList.remove("is-loading");
|
||||
if (!response.ok || (response.json && response.json.code && !response.json.success)) {
|
||||
notyf((response.json && response.json.message) || "兑换失败", "danger", 5, "aksrt-vip-purchase-toast");
|
||||
return;
|
||||
}
|
||||
var data = response.data || (response.json && response.json.data) || {};
|
||||
if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === "function") {
|
||||
window.AksrtModalRuntime.invalidate(["vip", "orders"]);
|
||||
}
|
||||
notyf(data.message || "兑换成功,正在刷新…", "success", 5, "aksrt-vip-purchase-toast");
|
||||
window.setTimeout(function () { window.location.reload(); }, 800);
|
||||
}).catch(function () {
|
||||
button.disabled = false;
|
||||
button.classList.remove("is-loading");
|
||||
notyf("网络错误,请重试", "danger", 5, "aksrt-vip-purchase-toast");
|
||||
});
|
||||
}
|
||||
|
||||
function bindModalUi(root) {
|
||||
if (!root || root.dataset.aksrtVipBound === "1") return;
|
||||
root.dataset.aksrtVipBound = "1";
|
||||
root.addEventListener("change", function (event) {
|
||||
var input = event.target.closest ? event.target.closest('input[name="aksrt_vip_pay_method"]') : null;
|
||||
if (!input) return;
|
||||
applyVipMethodVisibility(input);
|
||||
});
|
||||
root.addEventListener("click", function (event) {
|
||||
var tab = event.target.closest("[data-vip-tab]");
|
||||
if (tab) {
|
||||
|
||||
Reference in new issue
Block a user