Files
wordpress-theme-aksrt/assets/js/credits.js
T
kate522 d0b8f7805f feat(pay): VIP package purchase service and user-center open flow
Add AKSRT_Vip_Service (GET/POST /pay/vip), activate aksrt_vip_level/expires on
order paid, package UI in credits panel, and deep-link VIP CTA to credits tab.
2026-07-14 18:16:15 +08:00

415 lines
14 KiB
JavaScript

/**
* AKSRT Credits Frontend
*
* 功能:
* - 签到 panel:拉取 /signin/today,按钮触发 POST /signin
* - 邀请 panel:拉取 /invite/me,复制邀请链接
*
* 仅在 page-profile 页面 + 启用积分时加载。
*/
(function () {
'use strict';
var aksrt = window.aksrtData;
if (!aksrt || !aksrt.siteUrl) {
return;
}
// 协议修正:页面是 HTTPS 但 siteUrl 是 HTTP 时,强制使用 HTTPS(避免混合内容拦截)
var siteUrl = aksrt.siteUrl;
if (window.location.protocol === 'https:' && siteUrl.substring(0, 5) === 'http:') {
siteUrl = 'https:' + siteUrl.substring(5);
}
var REST = siteUrl.replace(/\/$/, '') + '/wp-json/aksrt/v1';
var NONCE_HEADER = aksrt.restNonce ? { 'X-WP-Nonce': aksrt.restNonce } : (aksrt.nonce ? { 'X-WP-Nonce': aksrt.nonce } : {});
function api(path, options) {
var merged = Object.assign({
credentials: 'same-origin',
headers: Object.assign({ 'Content-Type': 'application/json' }, NONCE_HEADER),
}, options || {});
return fetch(REST + path, merged).then(function (r) {
// 401 时去掉 nonce 重试(页面缓存可能导致 nonce 过期,GET 请求可用 cookie 认证)
if (r.status === 401 && (!options || !options.method || options.method === 'GET')) {
var retry = Object.assign({}, merged, {
headers: { 'Content-Type': 'application/json' }
});
return fetch(REST + path, retry).then(function (r2) {
return r2.json().catch(function () {
return { message: '服务器返回了非 JSON 响应 (HTTP ' + r2.status + ')' };
}).then(function (j) { return { ok: r2.ok, status: r2.status, json: j }; });
});
}
return r.json().catch(function () {
return { message: '服务器返回了非 JSON 响应 (HTTP ' + r.status + ')' };
}).then(function (j) { return { ok: r.ok, status: r.status, json: j }; });
});
}
// ===== 签到 =====
function initSignin() {
var root = document.querySelector('[data-aksrt-signin]');
if (!root) return;
var status = root.querySelector('[data-signin-status]');
var btn = root.querySelector('[data-signin-btn]');
var tip = root.querySelector('[data-signin-tip]');
if (!btn) return;
function setStatus(text) { if (status) status.textContent = text; }
function setStatusHtml(html) { if (status) status.innerHTML = html; }
function setTip(text) { if (tip) tip.textContent = text; }
function refresh() {
api('/signin/today').then(function (r) {
if (!r.ok) {
setStatus((r.json && r.json.message) || '加载失败 (HTTP ' + r.status + ')');
return;
}
var j = r.json;
if (!j.enabled) {
setStatus('签到功能未启用');
btn.disabled = true;
setTip('');
return;
}
if (j.signed) {
setStatusHtml('已签到 <strong>' + j.streak + '</strong> 天 +' + j.reward_today);
btn.disabled = true;
btn.innerHTML = '已签到';
setTip('');
} else {
setStatusHtml('连续 <strong>' + j.streak + '</strong> 天,+' + j.reward_if_signin);
btn.disabled = false;
setTip('');
}
}).catch(function (err) {
if (window.console) console.error('[AKSRT Credits] /signin/today 请求失败:', err);
setStatus('加载失败,请刷新重试');
});
}
function collectCaptcha() {
var el = document.querySelector('[data-signin-captcha]');
if (!el) return {};
var out = {};
var keys = [
'captcha_verified', 'cf_turnstile_response',
'tencent_ticket', 'tencent_randstr', 'tencent_sid',
'geetest_lot_number', 'geetest_captcha_output', 'geetest_pass_token', 'geetest_gen_time',
'aliyun_captcha_verify_param',
];
keys.forEach(function (k) {
var inp = el.querySelector('input[name="' + k + '"]');
if (inp && inp.value !== '') out[k] = inp.value;
});
return out;
}
function resetCaptcha() {
try {
var state = window.AKSRTCaptchaState;
var scene = state && state.scenes && state.scenes.signin;
if (scene && typeof scene.reset === 'function') scene.reset();
} catch (_) {}
}
function triggerCaptcha(callback) {
var state = window.AKSRTCaptchaState;
var scene = state && state.scenes && state.scenes.signin;
if (scene && typeof scene.verify === 'function') {
scene.verify(callback);
return;
}
callback();
}
btn.addEventListener('click', function () {
btn.disabled = true;
btn.innerHTML = '…';
setTip('');
var captchaDone = false;
var timeoutId = setTimeout(function () {
if (!captchaDone) {
captchaDone = true;
doSignin();
}
}, 30000);
triggerCaptcha(function () {
clearTimeout(timeoutId);
if (captchaDone) return;
captchaDone = true;
doSignin();
});
function doSignin() {
api('/signin', {
method: 'POST',
body: JSON.stringify({ captcha: collectCaptcha() })
}).then(function (r) {
if (!r.ok) {
resetCaptcha();
setTip((r.json && r.json.message) || '签到失败');
btn.disabled = false;
btn.innerHTML = '签到';
return;
}
setTip('签到成功,获得 ' + r.json.reward + ' 积分(连续 ' + r.json.streak + ' 天)');
var balanceEls = document.querySelectorAll('[data-credits-value]');
balanceEls.forEach(function (el) { el.textContent = r.json.balance.toLocaleString(); });
refresh();
}).catch(function () {
resetCaptcha();
setTip('网络错误,请重试');
btn.disabled = false;
btn.innerHTML = '签到';
});
}
});
refresh();
}
// ===== 邀请 =====
function initInvite() {
var root = document.querySelector('[data-aksrt-invite]');
if (!root) return;
var linkInput = root.querySelector('[data-invite-link]');
var copyBtn = root.querySelector('[data-invite-copy]');
var stats = root.querySelector('[data-invite-stats]');
var codeEl = root.querySelector('[data-invite-code]');
var listEl = root.querySelector('[data-invite-list]');
var totalEl = root.querySelector('[data-invite-total]');
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function formatDate(value) {
if (!value || value === '0000-00-00 00:00:00') return '';
var date = new Date(String(value).replace(' ', 'T') + 'Z');
if (isNaN(date.getTime())) return String(value).slice(0, 16);
function pad(n) { return n < 10 ? '0' + n : String(n); }
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes());
}
function statusMeta(status) {
if (status === 'rewarded') {
return { label: '已奖励', text: '奖励已发放', className: 'is-rewarded' };
}
if (status === 'verified') {
return { label: '已验证', text: '等待奖励发放', className: 'is-verified' };
}
return { label: '已注册', text: '等待完成验证', className: 'is-registered' };
}
function setCopyText(text) {
if (!copyBtn) return;
var label = copyBtn.querySelector('span');
if (label) label.textContent = text;
else copyBtn.textContent = text;
}
function renderInviteList(items, total) {
if (!listEl) return;
listEl.classList.remove('is-loading');
if (totalEl) {
if (total > items.length) totalEl.textContent = '最近 ' + items.length + ' 条 / 共 ' + total + ' 条';
else totalEl.textContent = total ? '共 ' + total + ' 条' : '暂无记录';
}
if (!items.length) {
listEl.innerHTML = '<div class="aksrt-invite-record-empty">分享链接后,好友注册记录会显示在这里。</div>';
return;
}
listEl.innerHTML = items.map(function (item) {
var name = item.invitee_name || (item.invitee_id ? '用户 #' + item.invitee_id : '受邀用户');
var first = name ? name.charAt(0).toUpperCase() : '?';
var meta = statusMeta(item.status);
var createdAt = formatDate(item.created_at);
var rewardedAt = formatDate(item.rewarded_at);
var statusText = meta.text;
if (item.status === 'rewarded' && rewardedAt) statusText = '奖励于 ' + rewardedAt;
var avatar = item.invitee_avatar
? '<img src="' + escapeHtml(item.invitee_avatar) + '" alt="">'
: '<span>' + escapeHtml(first) + '</span>';
return '<div class="aksrt-invite-record ' + meta.className + '">' +
'<div class="aksrt-invite-record__avatar">' + avatar + '</div>' +
'<div class="aksrt-invite-record__main">' +
'<strong>' + escapeHtml(name) + '</strong>' +
'<span>' + (createdAt ? '注册于 ' + escapeHtml(createdAt) : '注册时间未知') + '</span>' +
'</div>' +
'<div class="aksrt-invite-record__status">' +
'<span class="aksrt-invite-badge ' + meta.className + '">' + meta.label + '</span>' +
'<small>' + escapeHtml(statusText) + '</small>' +
'</div>' +
'</div>';
}).join('');
}
api('/invite/me').then(function (r) {
if (!r.ok) {
if (linkInput) linkInput.value = '';
if (copyBtn) copyBtn.disabled = true;
if (codeEl) codeEl.textContent = '--';
if (stats) {
stats.querySelectorAll('[data-stat]').forEach(function (el) {
el.textContent = '—';
});
}
return;
}
var j = r.json;
if (linkInput) linkInput.value = j.invite_url || '';
if (codeEl) codeEl.textContent = j.code || '--';
if (stats && j.stats) {
stats.querySelectorAll('[data-stat]').forEach(function (el) {
var k = el.getAttribute('data-stat');
el.textContent = (j.stats[k] || 0).toString();
});
}
}).catch(function () {
if (copyBtn) copyBtn.disabled = true;
if (codeEl) codeEl.textContent = '--';
});
if (listEl) {
api('/invite/list?per_page=8').then(function (r) {
if (!r.ok) {
listEl.classList.remove('is-loading');
listEl.innerHTML = '<div class="aksrt-invite-record-empty">邀请记录加载失败,请稍后刷新。</div>';
if (totalEl) totalEl.textContent = '加载失败';
return;
}
var j = r.json || {};
renderInviteList(j.items || [], Number(j.total || 0));
}).catch(function () {
listEl.classList.remove('is-loading');
listEl.innerHTML = '<div class="aksrt-invite-record-empty">邀请记录加载失败,请稍后刷新。</div>';
if (totalEl) totalEl.textContent = '加载失败';
});
}
if (!copyBtn) return;
var copying = false;
copyBtn.addEventListener('click', function () {
if (copying || !linkInput || !linkInput.value) return;
copying = true;
if (navigator.clipboard) {
navigator.clipboard.writeText(linkInput.value).then(function () {
setCopyText('已复制');
setTimeout(function () { setCopyText('复制链接'); copying = false; }, 2000);
}).catch(function () { copying = false; });
} else {
linkInput.select();
document.execCommand('copy');
setCopyText('已复制');
setTimeout(function () { setCopyText('复制链接'); copying = false; }, 2000);
}
});
}
// ===== VIP 开通 =====
function initVipShop() {
var root = document.querySelector('[data-aksrt-vip-shop]');
if (!root) return;
var tip = root.querySelector('[data-aksrt-vip-tip]');
var busy = false;
function setTip(text, isError) {
if (!tip) return;
tip.textContent = text || '';
tip.classList.toggle('is-error', !!isError);
}
function selectedMethod() {
var fixed = root.querySelector('[data-aksrt-vip-method-fixed]');
if (fixed && fixed.value) return fixed.value;
var checked = root.querySelector('input[name="aksrt_vip_pay_method"]:checked');
if (checked) return checked.value;
var first = root.querySelector('input[name="aksrt_vip_pay_method"]');
return first ? first.value : 'balance';
}
function pollOrder(orderNumber) {
if (!orderNumber) return;
var tries = 0;
var maxTries = 40;
var timer = setInterval(function () {
tries += 1;
api('/pay/content/order/' + encodeURIComponent(orderNumber)).then(function (r) {
if (r.ok && r.json && r.json.paid) {
clearInterval(timer);
setTip('支付成功,正在刷新…');
window.location.reload();
}
}).catch(function () { /* ignore */ });
if (tries >= maxTries) clearInterval(timer);
}, 3000);
}
root.addEventListener('click', function (e) {
var btn = e.target.closest('[data-aksrt-vip-buy]');
if (!btn || busy) return;
e.preventDefault();
var packageId = parseInt(btn.getAttribute('data-package-id') || '0', 10);
if (!packageId) return;
busy = true;
btn.classList.add('is-loading');
setTip('正在创建订单…');
api('/pay/vip', {
method: 'POST',
body: JSON.stringify({
package_id: packageId,
payment_method: selectedMethod()
})
}).then(function (r) {
busy = false;
btn.classList.remove('is-loading');
if (!r.ok || (r.json && r.json.code && !r.json.success)) {
var msg = (r.json && r.json.message) || '开通失败';
setTip(msg, true);
return;
}
var data = (r.json && r.json.data) ? r.json.data : r.json;
if (data && data.redirect && (data.payment_url || data.qr_content)) {
if (data.payment_url) {
window.open(data.payment_url, '_blank');
}
setTip(data.message || '请完成支付,支付成功后将自动刷新。');
if (data.order_number) pollOrder(data.order_number);
return;
}
setTip((data && data.message) || '开通成功,正在刷新…');
window.location.reload();
}).catch(function () {
busy = false;
btn.classList.remove('is-loading');
setTip('网络错误,请重试', true);
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
initSignin();
initInvite();
initVipShop();
});
} else {
initSignin();
initInvite();
initVipShop();
}
})();