(function () { 'use strict'; var POLL_TIMEOUT_MS = 5 * 60 * 1000; var PAY_STEP_LABELS = ['选择支付', '扫码付款', '激活生效']; var _displayChannels = ['alipay']; var _settingsPromise = null; var CHANNEL_UI = { alipay: { name: '支付宝', hint: '网页支付', icon: '支', iconCls: 'alipay' }, wechat: { name: '微信支付', hint: '扫码付款', icon: '微', iconCls: 'wechat' }, }; function esc(s) { if (s == null) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function apiBase() { var cfg = (typeof window !== 'undefined' && window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base); if (cfg) return String(cfg).replace(/\/+$/, ''); if (typeof window !== 'undefined' && window.location) { return window.location.origin + '/api/v1'; } return '/api/v1'; } function apiFetch(url, opts) { if (typeof window.soonAuthedFetch === 'function') return window.soonAuthedFetch(url, opts || {}); opts = opts || {}; var token = localStorage.getItem('soon_access') || ''; opts.headers = Object.assign({}, opts.headers || {}); if (token) opts.headers.Authorization = 'Bearer ' + token; return fetch(url, opts); } function parseApiJson(r) { return r.text().then(function (text) { try { return JSON.parse(text); } catch (e) { return { ok: false, message: '服务暂时不可用,请稍后重试' }; } }); } function apiGet(p) { return apiFetch(apiBase() + p).then(function (r) { if (!r.ok && r.status === 401) return { ok: false, message: '未登录或会话已过期' }; return parseApiJson(r); }); } function apiPost(p, body) { return apiFetch(apiBase() + p, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}), }).then(function (r) { if (!r.ok && r.status === 401) return { ok: false, message: '未登录或会话已过期' }; return parseApiJson(r); }); } function isSafeAlipayGateway(url) { if (!url || typeof url !== 'string') return false; try { var u = new URL(url); return u.protocol === 'https:' && (u.hostname === 'openapi.alipay.com' || u.hostname === 'openapi.alipaydev.com'); } catch (e) { return false; } } function submitAlipayForm(payForm) { if (!payForm || !payForm.gateway || !payForm.params) return false; if (!isSafeAlipayGateway(payForm.gateway)) return false; var form = document.createElement('form'); form.method = 'POST'; form.action = payForm.gateway; form.acceptCharset = 'UTF-8'; form.style.display = 'none'; Object.keys(payForm.params).forEach(function (key) { var val = payForm.params[key]; if (val == null || val === '') return; var input = document.createElement('input'); input.type = 'hidden'; input.name = key; input.value = String(val); form.appendChild(input); }); document.body.appendChild(form); form.submit(); return true; } function parseDisplayChannels(raw) { if (Array.isArray(raw)) { var list = raw.filter(function (ch) { return ch === 'alipay' || ch === 'wechat'; }); return list.length ? list : ['alipay']; } var parts = String(raw || 'alipay').split(/[\s,]+/).filter(Boolean); var out = []; parts.forEach(function (ch) { if ((ch === 'alipay' || ch === 'wechat') && out.indexOf(ch) < 0) out.push(ch); }); return out.length ? out : ['alipay']; } function loadDisplayChannels() { if (_settingsPromise) return _settingsPromise; _settingsPromise = apiGet('/settings').then(function (j) { if (j.ok && j.data) { if (j.data.payment_display_channels) { _displayChannels = parseDisplayChannels(j.data.payment_display_channels); } else if (j.data['payment.display_channels'] != null) { _displayChannels = parseDisplayChannels(j.data['payment.display_channels']); } } return _displayChannels; }).catch(function () { _displayChannels = ['alipay']; return _displayChannels; }); return _settingsPromise; } function displayChannels() { return _displayChannels.slice(); } function defaultChannel(channels) { var list = channels || _displayChannels; return list[0] || 'alipay'; } function resolveChannel(requested, channels) { var list = channels || _displayChannels; if (requested && list.indexOf(requested) >= 0) return requested; return defaultChannel(list); } function channelCardHtml(ch, active, selectable) { var ui = CHANNEL_UI[ch]; if (!ui) return ''; var cls = 'soon-pay-channel' + (active ? ' is-active' : '') + (selectable ? '' : ' is-fixed'); var inner = '' + '' + ui.icon + '' + '' + ui.name + '' + '' + ui.hint + ''; if (selectable) { return ''; } return '
' + inner + '
'; } function payChannelsHtml(activeChannel, channels, opts) { opts = opts || {}; channels = channels || _displayChannels; var showChannel = activeChannel || defaultChannel(channels); var selectable = opts.selectable !== false && channels.length > 1; var list = selectable ? channels : [showChannel]; var cards = list.map(function (ch) { return channelCardHtml(ch, ch === showChannel, selectable); }).join(''); if (!cards) return ''; return '
' + '

支付方式

' + '
' + cards + '
'; } function payStepLabel(step, channel) { if (step === 2 && channel === 'alipay') return '网页支付'; if (step === 2 && channel === 'wechat') return '扫码付款'; return PAY_STEP_LABELS[step - 1]; } function payStepperHtml(activeStep, channel) { var parts = []; for (var i = 1; i <= 3; i++) { if (i > 1) { var bridgeCls = 'soon-pay-stepper__bridge'; if (i - 1 < activeStep) bridgeCls += ' is-done'; parts.push(''); } var cls = 'soon-pay-stepper__item'; if (i < activeStep) cls += ' is-done'; else if (i === activeStep) cls += ' is-active'; parts.push('
  • ' + '' + (i < activeStep ? '✓' : String(i)) + '' + '' + payStepLabel(i, channel) + '
  • '); } return '
      ' + parts.join('') + '
    '; } function updatePayStep(root, step, channel) { if (!root) return; var html = payStepperHtml(step, channel || defaultChannel()); var old = root.querySelector('#payStepper'); if (old) old.outerHTML = html; } function payEmptyStateHtml(channel) { var ui = CHANNEL_UI[channel] || CHANNEL_UI[defaultChannel()]; return '
    ' + '' + '

    等待生成付款信息

    ' + '

    点击下方按钮,将使用' + esc(ui.name) + '(' + esc(ui.hint) + ')

    '; } function renderPayPayload(data, qrEl, orderNoEl) { if (orderNoEl && data.order_no) { orderNoEl.textContent = '订单号 ' + data.order_no; orderNoEl.style.display = ''; } if (data.pay_form && data.pay_form.gateway) { if (!isSafeAlipayGateway(data.pay_form.gateway)) return '支付网关无效'; sessionStorage.setItem('soon_pay_return', '1'); var payForm = data.pay_form; qrEl.innerHTML = '
    ' + '
    ' + '

    支付宝电脑网站支付

    ' + '

    点击下方按钮跳转至支付宝收银台,支付完成后将自动返回

    ' + '
    '; var alipayBtn = qrEl.querySelector('#goAlipay'); if (alipayBtn) { alipayBtn.onclick = function () { if (!submitAlipayForm(payForm)) { if (typeof window.soonToast === 'function') { window.soonToast('无法跳转支付宝,请检查支付配置', 'warn'); } } }; } return ''; } if (data.qrcode) { qrEl.innerHTML = '
    ' + '
    ' + '

    微信扫码支付

    ' + '
    ' + '

    请使用微信扫一扫,有效期约 2 小时

    '; if (typeof QRCode !== 'undefined') { new QRCode(qrEl.querySelector('#payQrCanvas'), { text: data.qrcode, width: 188, height: 188 }); } return ''; } return '未获取到支付信息'; } function paySheetHtml(opts) { opts = opts || {}; var resumeOrderNo = opts.orderNo || null; var channels = opts.displayChannels || _displayChannels; var channel = (resumeOrderNo && opts.channel) ? opts.channel : resolveChannel(opts.channel, channels); var embedded = opts.variant === 'embedded'; var backBtn = !embedded && opts.showBack ? '' : ''; var headBlock = embedded ? '' : '
    ' + '
    ' + '' + (resumeOrderNo ? '继续支付' : '确认激活') + '' + '

    ' + esc(opts.planName || '') + '

    ' + '
    ' + '
    ' + '¥' + '' + esc(opts.priceDisplay || '') + '
    '; var orderInBody = embedded ? '' : ''; return '
    ' + (backBtn ? '
    ' + backBtn + '
    ' : '') + headBlock + '
    ' + orderInBody + payStepperHtml(1, channel) + payChannelsHtml(channel, channels, { selectable: !resumeOrderNo && channels.length > 1 }) + '
    ' + '
    ' + payEmptyStateHtml(channel) + '
    ' + '
    ' + '

    ' + '' + '' + '
    '; } function bindPaySheet(sheetRoot, opts) { opts = opts || {}; var planId = opts.planId; var resumeOrderNo = opts.orderNo || null; var channels = opts.displayChannels || _displayChannels; var channel = (resumeOrderNo && opts.channel) ? opts.channel : resolveChannel(opts.channel, channels); var onPaid = typeof opts.onPaid === 'function' ? opts.onPaid : function () {}; var pollTimer = null; var pollStartedAt = 0; var orderNo = resumeOrderNo; var paymentReady = false; if (!sheetRoot) return { destroy: function () {} }; var manualBtn = sheetRoot.querySelector('#payManualRefresh'); var statusEl = sheetRoot.querySelector('#payStatus'); var confirmBtn = sheetRoot.querySelector('#payConfirm'); var qrEl = sheetRoot.querySelector('#payQr'); var orderNoEl = sheetRoot.querySelector('#payOrderNo'); var backBtn = sheetRoot.querySelector('#payBack'); function setStatus(text, type) { if (!statusEl) return; statusEl.textContent = text || ''; statusEl.className = 'soon-pay-status' + (type ? ' soon-pay-status--' + type : ''); statusEl.style.display = text ? 'block' : 'none'; } function setStep(step) { updatePayStep(sheetRoot, step, channel); sheetRoot.classList.toggle('soon-pay-sheet--checkout', step >= 2); var chSec = sheetRoot.querySelector('#payChannels'); if (chSec) chSec.style.display = step >= 2 ? 'none' : ''; } function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } function startPoll() { stopPoll(); pollStartedAt = Date.now(); pollTimer = setInterval(function () { if (Date.now() - pollStartedAt > POLL_TIMEOUT_MS) { stopPoll(); setStatus('自动检测超时,如已支付请点击下方按钮', 'err'); return; } checkOrderStatus(); }, 3000); } function handlePaid() { stopPoll(); setStep(3); setStatus('支付成功,正在更新会员状态…', 'ok'); setTimeout(onPaid, 600); } function checkOrderStatus() { if (!orderNo) return; apiGet('/pay/orders/' + encodeURIComponent(orderNo)).then(function (st) { if (st.ok && st.data && st.data.status === 'paid') handlePaid(); }); } if (manualBtn) manualBtn.onclick = checkOrderStatus; function resetPaymentView() { paymentReady = false; stopPoll(); setStep(1); if (manualBtn) manualBtn.style.display = 'none'; if (confirmBtn) confirmBtn.style.display = ''; qrEl.innerHTML = payEmptyStateHtml(channel); if (orderNoEl && !resumeOrderNo) { orderNoEl.textContent = ''; orderNoEl.style.display = 'none'; } setStatus(''); } function applyCheckout(data) { orderNo = data.order_no; var err = renderPayPayload(data, qrEl, orderNoEl); if (err) { setStatus(err, 'err'); return; } paymentReady = true; setStep(2); if (confirmBtn) confirmBtn.style.display = 'none'; if (manualBtn) manualBtn.style.display = ''; setStatus('等待支付结果,完成后将自动刷新', 'ok'); startPoll(); } function submitCheckout() { if (confirmBtn) confirmBtn.disabled = true; setStatus('正在生成付款信息…'); qrEl.innerHTML = '
    ' + '

    正在连接支付渠道…

    '; var req; if (resumeOrderNo || orderNo) { var existingNo = orderNo || resumeOrderNo; req = apiPost('/pay/orders/' + encodeURIComponent(existingNo) + '/checkout', { channel: channel }); } else { req = apiPost('/pay/orders', { plan_id: planId, channel: channel }); } req.then(function (j) { if (confirmBtn) confirmBtn.disabled = false; if (!j.ok) { setStatus(j.message || '下单失败', 'err'); qrEl.innerHTML = payEmptyStateHtml(channel); return; } applyCheckout(j.data); }).catch(function () { if (confirmBtn) confirmBtn.disabled = false; setStatus('网络错误,请重试', 'err'); qrEl.innerHTML = payEmptyStateHtml(channel); }); } sheetRoot.querySelectorAll('.soon-pay-channel:not(.is-fixed)').forEach(function (el) { el.onclick = function () { if (el.classList.contains('is-active')) return; sheetRoot.querySelectorAll('.soon-pay-channel').forEach(function (x) { x.classList.remove('is-active'); }); el.classList.add('is-active'); channel = el.dataset.ch; if (paymentReady) resetPaymentView(); }; }); if (confirmBtn) confirmBtn.onclick = submitCheckout; if (backBtn && typeof opts.onBack === 'function') backBtn.onclick = opts.onBack; function destroy() { stopPoll(); paymentReady = false; orderNo = resumeOrderNo; } return { destroy: destroy, reset: resetPaymentView, }; } function openPayModal(opts) { opts = opts || {}; if (typeof layer === 'undefined' || !layer.open) return null; var channels = _displayChannels; var channel = (opts.orderNo && opts.channel) ? opts.channel : resolveChannel(opts.channel, channels); var html = paySheetHtml({ planName: opts.planName, priceDisplay: opts.priceDisplay, orderNo: opts.orderNo, channel: channel, displayChannels: channels, }); var payCtrl = null; var idx = layer.open({ type: 1, skin: 'soon-layer', title: false, closeBtn: 1, shadeClose: !!opts.shadeClose, area: ['480px'], content: html, success: function (layero) { var layerEl = layero && layero[0] ? layero[0] : layero; if (layerEl && layerEl.classList) { layerEl.classList.add('soon-layer--pay'); var layerContent = layerEl.querySelector('.layui-layer-content'); if (layerContent) { layerContent.style.overflow = 'visible'; layerContent.style.maxHeight = 'none'; } } var sheet = layerEl.querySelector('.soon-pay-sheet'); payCtrl = bindPaySheet(sheet, { planId: opts.planId, planName: opts.planName, priceDisplay: opts.priceDisplay, orderNo: opts.orderNo, channel: channel, displayChannels: channels, onPaid: function () { layer.close(idx); if (typeof opts.onPaid === 'function') opts.onPaid(); }, onBack: opts.onBack, }); }, end: function () { if (payCtrl) payCtrl.destroy(); if (typeof opts.onClose === 'function') opts.onClose(); }, }); return idx; } loadDisplayChannels(); window.SoonMemberPay = { apiBase: apiBase, apiGet: apiGet, apiPost: apiPost, esc: esc, paySheetHtml: paySheetHtml, bindPaySheet: bindPaySheet, openPayModal: openPayModal, payEmptyStateHtml: payEmptyStateHtml, renderPayPayload: renderPayPayload, submitAlipayForm: submitAlipayForm, loadDisplayChannels: loadDisplayChannels, displayChannels: displayChannels, defaultChannel: defaultChannel, resolveChannel: resolveChannel, POLL_TIMEOUT_MS: POLL_TIMEOUT_MS, }; })();