From af3539914d2eba10170cff179f1e43a6949ee87a Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Aug 2026 22:50:35 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BA=86=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=B0=8F=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/css/frontend/cms-download.css | 702 ++++++++++++++++++ assets/img/pay-alipay-logo.svg | 4 + assets/img/pay-balance-logo.svg | 6 + assets/img/pay-paypal-logo.svg | 5 + assets/img/pay-stripe-logo.svg | 3 + assets/img/pay-wechat-logo.svg | 3 + assets/js/cms-download.js | 332 +++++---- src/Core/ScriptsPart1b.php | 8 + .../widget-system/pay-card-contract.test.cjs | 28 + 9 files changed, 956 insertions(+), 135 deletions(-) create mode 100644 assets/css/frontend/cms-download.css create mode 100644 assets/img/pay-alipay-logo.svg create mode 100644 assets/img/pay-balance-logo.svg create mode 100644 assets/img/pay-paypal-logo.svg create mode 100644 assets/img/pay-stripe-logo.svg create mode 100644 assets/img/pay-wechat-logo.svg diff --git a/assets/css/frontend/cms-download.css b/assets/css/frontend/cms-download.css new file mode 100644 index 00000000..e22ca51a --- /dev/null +++ b/assets/css/frontend/cms-download.css @@ -0,0 +1,702 @@ +.aksrt-dl-modal { + position: fixed; + inset: 0; + z-index: 10000; + display: none; +} + +.aksrt-dl-modal.is-open { + display: flex; + align-items: center; + justify-content: center; +} + +.aksrt-dl-modal__backdrop { + position: absolute; + inset: 0; + background: rgb(0 0 0 / 55%); +} + +.aksrt-dl-modal__dialog { + position: relative; + z-index: 1; + width: 400px; + max-width: calc(100% - 20px); + margin: 0 auto; +} + +.aksrt-dl-modal__panel { + position: relative; + width: 100%; + max-height: calc(100vh - 20px); + overflow: hidden auto; + color: var(--main-color, #111827); + background: var(--main-bg-color, #fff); + border: 0; + border-radius: var(--main-radius, 8px); + box-shadow: 0 24px 56px rgb(15 23 42 / 24%); +} + +.aksrt-dl-modal:not(.is-cashier) .aksrt-dl-modal__panel { + padding: 26px 28px 24px; +} + +.aksrt-dl-modal__close, +.aksrt-cashier__close { + appearance: none; + position: absolute; + z-index: 9; + top: 10px; + right: 12px; + padding: 0; + color: var(--muted-color, #6b7280); + font: 400 24px/1 sans-serif; + background: transparent; + border: 0; + box-shadow: none; + cursor: pointer; +} + +.aksrt-dl-modal.is-cashier > .aksrt-dl-modal__dialog > .aksrt-dl-modal__panel > .aksrt-dl-modal__close { + display: none; +} + +.aksrt-dl-modal__title { + margin: 0 28px 18px 0; + color: var(--main-color, #111827); + font: 700 20px/1.3 inherit; +} + +.aksrt-dl-modal__msg { + margin: 0 0 18px; + color: var(--muted-color, #4b5563); + font-size: 14px; + line-height: 1.65; +} + +.aksrt-dl-modal__msg--warn { + padding: 12px 0; + color: #92400e; + background: transparent; + border: 0; + border-top: 1px solid #fde68a; + border-bottom: 1px solid #fde68a; +} + +.aksrt-dl-modal__row { + display: grid; + grid-template-columns: 64px minmax(0, 1fr) 72px; + gap: 12px; + align-items: center; + padding: 12px 0; + margin: 0 0 18px; + border-top: 1px solid var(--main-border-color, #e5e7eb); + border-bottom: 1px solid var(--main-border-color, #e5e7eb); +} + +.aksrt-dl-modal__label { + color: var(--muted-color, #6b7280); + font-size: 13px; + font-weight: 600; + white-space: nowrap; +} + +.aksrt-dl-modal__code { + display: block; + min-width: 0; + overflow: hidden; + color: var(--main-color, #111827); + font: 800 18px/1.25 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + letter-spacing: 0.06em; + text-overflow: ellipsis; + white-space: nowrap; +} + +.aksrt-dl-modal__copy { + appearance: none !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + width: 72px !important; + height: 32px !important; + padding: 0 !important; + overflow: visible !important; + color: #2563eb !important; + font-family: inherit !important; + font-size: 0 !important; + font-weight: 700 !important; + line-height: 1 !important; + text-indent: 0 !important; + white-space: nowrap !important; + cursor: pointer !important; + visibility: visible !important; + background: #eff6ff !important; + border: 1px solid #bfdbfe !important; + border-radius: 8px !important; + opacity: 1 !important; + -webkit-text-fill-color: #2563eb !important; +} + +.aksrt-dl-modal__copy::before, +.aksrt-dl-modal__copy::after { + display: none !important; + content: none !important; +} + +.aksrt-dl-modal__copy:hover { + color: #1d4ed8 !important; + background: #dbeafe !important; + border-color: #93c5fd !important; + -webkit-text-fill-color: #1d4ed8 !important; +} + +.aksrt-dl-modal__copy-text { + position: relative !important; + z-index: 2 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + color: #2563eb !important; + font-family: inherit !important; + font-size: 13px !important; + font-weight: 700 !important; + line-height: 1 !important; + text-indent: 0 !important; + white-space: nowrap !important; + visibility: visible !important; + opacity: 1 !important; + -webkit-text-fill-color: #2563eb !important; +} + +.aksrt-dl-modal__copy:hover .aksrt-dl-modal__copy-text { + color: #1d4ed8 !important; + -webkit-text-fill-color: #1d4ed8 !important; +} + +.aksrt-dl-modal__actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; + padding-top: 4px; +} + +.aksrt-dl-modal__download { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 40px; + padding: 0 18px; + text-decoration: none; + cursor: pointer; + border: 0; + border-radius: 9px; +} + +.aksrt-dl-modal__balance { + margin-left: auto; + color: var(--muted-color, #6b7280); + font-size: 13px; + white-space: nowrap; +} + +.aksrt-dl-modal.is-cashier .aksrt-dl-modal__body { + position: relative; + min-height: 120px; +} + +.aksrt-dl-modal .modal-colorful-header, +.aksrt-dl-modal .colorful-bg { + position: relative; + overflow: hidden; + color: var(--this-color); + background: var(--this-bg); + background-size: 120%; +} + +.aksrt-dl-modal .modal-colorful-header { + position: absolute; + top: 0; + right: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + height: 120px; + border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0; +} + +.aksrt-dl-modal .colorful-bg > .colorful-make, +.aksrt-dl-modal .colorful-bg > .colorful-make::before, +.aksrt-dl-modal .colorful-bg > .colorful-make::after { + position: absolute; + color: var(--this-color); + background: var(--this-bg); + border-radius: 100%; + content: ''; +} + +.aksrt-dl-modal .colorful-bg > .colorful-make { + top: 60%; + bottom: 0; + width: 323px; + height: 290px; + animation: aksrt-cashier-colorful 120s linear infinite; +} + +.aksrt-dl-modal .colorful-bg > .colorful-make::before { + top: -325px; + right: -558px; + bottom: 0; + width: 677px; + height: 300px; + opacity: 0.6; + transform: rotate(341deg); +} + +.aksrt-dl-modal .colorful-bg > .colorful-make::after { + top: -170px; + bottom: 0; + left: -262px; + width: 402px; + height: 214px; + opacity: 0.4; + transform: rotate(326deg); +} + +.aksrt-dl-modal .colorful-bg > .colorful-make + * { + position: relative; +} + +.aksrt-dl-modal .aksrt-cashier__close { + color: var(--this-color, #fff); + font-size: 18px; + opacity: 0.6; +} + +.aksrt-dl-modal .modal-pay-body { + padding: 15px; + margin-top: 120px; +} + +.aksrt-cashier__loading { + min-height: 70px; + margin: 0; + line-height: 70px; + text-align: center; +} + +.aksrt-dl-modal .text-center { + text-align: center; +} + +.aksrt-dl-modal .em2x { + font-size: 2em; +} + +.aksrt-dl-modal .em09 { + font-size: 0.9em; +} + +.aksrt-dl-modal .em12 { + font-size: 1.2em; +} + +.aksrt-dl-modal .em14 { + font-size: 1.4em; +} + +.aksrt-dl-modal .mt6 { + margin-top: 6px; +} + +.aksrt-dl-modal .mt10 { + margin-top: 10px; +} + +.aksrt-dl-modal .mb6 { + margin-bottom: 6px; +} + +.aksrt-dl-modal .mb10 { + margin-bottom: 10px; +} + +.aksrt-dl-modal .mb20 { + margin-bottom: 20px; +} + +.aksrt-dl-modal .mr6 { + margin-right: 6px; +} + +.aksrt-dl-modal .ml6 { + margin-left: 6px; +} + +.aksrt-dl-modal .ml10 { + margin-left: 10px; +} + +.aksrt-dl-modal .px12 { + font-size: 12px; +} + +.aksrt-dl-modal .padding-h6 { + padding-top: 6px; + padding-bottom: 6px; +} + +.aksrt-dl-modal .padding-h10 { + padding-top: 10px; + padding-bottom: 10px; +} + +.aksrt-dl-modal .padding-w10 { + padding-right: 10px; + padding-left: 10px; +} + +.aksrt-dl-modal .line-16 { + line-height: 1.6; +} + +.aksrt-dl-modal .flex { + display: flex; +} + +.aksrt-dl-modal .flex.jsb { + justify-content: space-between; +} + +.aksrt-dl-modal .flex.jc { + justify-content: center; +} + +.aksrt-dl-modal .flex.ac { + align-items: center; +} + +.aksrt-dl-modal .flex.ab { + align-items: baseline; +} + +.aksrt-dl-modal .flex.hh { + flex-direction: column; +} + +.aksrt-dl-modal .flex-auto { + flex: auto; +} + +.aksrt-dl-modal .pointer { + cursor: pointer; +} + +.aksrt-dl-modal .font-bold { + font-weight: 700; +} + +.aksrt-dl-modal .muted-2-color { + color: var(--muted-2-color, #8b93a7); +} + +.aksrt-dl-modal .muted-3-color { + color: var(--muted-3-color, #a9afbe); +} + +.aksrt-dl-modal .c-red { + color: #ff5473; +} + +.aksrt-dl-modal .c-green { + color: #20b759; +} + +.aksrt-dl-modal .c-blue-2 { + color: #5c7cff; +} + +.aksrt-dl-modal .muted-box { + padding: 10px; + background: var(--muted-border-color, rgb(132 145 165 / 10%)); + border-radius: 4px; +} + +.aksrt-dl-modal .pay-tag { + display: inline-block; + padding: 2px 7px; + color: #2997f7; + font-size: 12px; + line-height: 1.4; + background: rgb(41 151 247 / 10%); + border-radius: 4px; +} + +.aksrt-cashier__price-row { + gap: 12px; + min-height: 27px; +} + +.aksrt-cashier__regular-price { + display: flex; + gap: 10px; + align-items: baseline; +} + +.aksrt-dl-modal .original-price { + position: relative; + display: inline-flex; + align-items: baseline; + padding-right: 2px; + padding-left: 1px; + color: var(--muted-color, #7d8496); + font-size: 14px; +} + +.aksrt-dl-modal .original-price::before { + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 1px; + background: #ff343b; + transform: rotate(8deg); + transform-origin: center; + content: ''; +} + +.aksrt-cashier__vip-mark { + display: inline-block; + padding: 0 4px; + margin-left: 3px; + color: #866127; + font-size: 10px; + font-weight: 700; + line-height: 16px; + vertical-align: 1px; + background: linear-gradient(25deg, #eabe7b 10%, #f5e3c7 70%, #edc788 100%); + border-radius: 4px; +} + +.aksrt-cashier__asset-icon { + width: 1.2em; + margin-right: 6px; + color: #5c7cff; + text-align: center; +} + +.aksrt-dl-modal .payment-method-radio { + min-width: 0; + padding: 5px; + text-align: center; + border: 1px solid var(--main-border-color, rgb(132 145 165 / 20%)); + border-radius: 4px; +} + +.aksrt-dl-modal .payment-method-radio > * { + margin: 3px; +} + +.aksrt-dl-modal .payment-method-radio img { + width: auto; + max-width: 68px; + height: 35px; + object-fit: contain; +} + +.aksrt-dl-modal .payment-method-radio + .payment-method-radio { + margin-left: 10px; +} + +.aksrt-dl-modal .hollow-radio.active { + color: var(--focus-color, #2997f7); + background: var(--focus-color-opacity1, rgb(41 151 247 / 10%)); + border-color: var(--focus-color, #2997f7); +} + +.aksrt-dl-modal .payment-method-radio:focus { + outline: 0; + box-shadow: none; +} + +.aksrt-cashier__balance[hidden] { + display: none; +} + +.aksrt-dl-modal .form-control { + display: block; + width: 100%; + min-height: 36px; + padding: 6px 92px 6px 10px; + color: var(--main-color, #111827); + font: inherit; + background: var(--main-bg-color, #fff); + border: 1px solid var(--main-border-color, #e5e7eb); + border-radius: 4px; +} + +.aksrt-dl-modal .relative { + position: relative; +} + +.aksrt-dl-modal .abs-right { + position: absolute; + top: 50%; + right: 3px; + transform: translateY(-50%); +} + +.aksrt-dl-modal .coupon-submit { + padding: 0.3em 0.8em; +} + +.aksrt-cashier__coupon-desc, +.aksrt-cashier__coupon-ok { + margin-top: 6px; + font-size: 12px; + line-height: 1.5; +} + +.aksrt-cashier__coupon-ok { + color: #20b759; +} + +.aksrt-cashier__warn { + padding: 8px 10px; + color: #ff5473; + font-size: 13px; + line-height: 1.5; + text-align: center; + background: rgb(255 84 115 / 10%); + border-radius: 4px; +} + +.aksrt-dl-modal .but.initiate-pay { + padding: 0.6em 1em; +} + +.aksrt-dl-modal .modal-buts.but-average { + overflow: hidden; + border-top: 1px solid var(--main-border-color, #e5e7eb); + border-radius: 0 0 var(--main-radius, 8px) var(--main-radius, 8px); +} + +.aksrt-dl-modal .modal-buts.but-average > .but { + flex: auto; + padding: 0.8em; + margin: 0 !important; + background: transparent; + border-radius: 0; +} + +.aksrt-dl-modal .modal-buts.but-average > .but + .but { + border-left: 1px solid var(--main-border-color, #e5e7eb); +} + +.aksrt-cashier__recharge { + display: block; + text-decoration: none; +} + +.resource-download-box__attrs { + display: grid; + gap: 6px; + padding: 0; + margin: 10px 0 0; + list-style: none; +} + +.resource-download-box__attrs-item { + display: flex; + justify-content: space-between; + gap: 12px; + color: #6b7280; + font-size: 12px; +} + +.resource-download-box__attrs-name { + flex: 0 0 auto; +} + +.resource-download-box__attrs-value { + color: #111827; + text-align: right; +} + +.resource-download-box__paid-badge { + color: #047857 !important; + background: #ecfdf5 !important; +} + +.resource-download-box__paid-meta { + display: grid; + gap: 4px; + padding: 10px 12px; + margin: 8px 0 12px; + color: #166534; + font-size: 12px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 10px; +} + +body.aksrt-dl-modal-open { + overflow: hidden; +} + +@keyframes aksrt-cashier-colorful { + from { + transform: translate3d(0, 0, 0) rotate(0deg) scale(1); + } + + 20% { + transform: translate3d(-30px, -20px, 50px) rotate(87deg) scale(0.9); + } + + 65% { + transform: translate3d(-80px, -40px, 0) rotate(156deg) scale(0.6); + } + + to { + transform: translate3d(0, 0, 0) rotate(360deg) scale(1); + } +} + +@media (max-width: 480px) { + .aksrt-dl-modal:not(.is-cashier) .aksrt-dl-modal__panel { + padding: 24px 20px 22px; + } + + .aksrt-dl-modal__row { + grid-template-columns: 1fr 72px; + gap: 8px 12px; + } + + .aksrt-dl-modal__label { + grid-column: 1 / -1; + } + + .aksrt-dl-modal__code { + font-size: 17px; + } + + .aksrt-dl-modal__actions { + flex-direction: column; + align-items: flex-start; + } + + .aksrt-dl-modal__download { + width: 100%; + } + + .aksrt-dl-modal__balance { + margin-left: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .aksrt-dl-modal .colorful-bg > .colorful-make { + animation: none; + } +} diff --git a/assets/img/pay-alipay-logo.svg b/assets/img/pay-alipay-logo.svg new file mode 100644 index 00000000..ca409961 --- /dev/null +++ b/assets/img/pay-alipay-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/pay-balance-logo.svg b/assets/img/pay-balance-logo.svg new file mode 100644 index 00000000..403f949a --- /dev/null +++ b/assets/img/pay-balance-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/img/pay-paypal-logo.svg b/assets/img/pay-paypal-logo.svg new file mode 100644 index 00000000..72f0fcb8 --- /dev/null +++ b/assets/img/pay-paypal-logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/img/pay-stripe-logo.svg b/assets/img/pay-stripe-logo.svg new file mode 100644 index 00000000..fe5086be --- /dev/null +++ b/assets/img/pay-stripe-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/pay-wechat-logo.svg b/assets/img/pay-wechat-logo.svg new file mode 100644 index 00000000..c9c5fdd9 --- /dev/null +++ b/assets/img/pay-wechat-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/js/cms-download.js b/assets/js/cms-download.js index 38da0f35..b95fb8d4 100644 --- a/assets/js/cms-download.js +++ b/assets/js/cms-download.js @@ -15,6 +15,7 @@ if (!window.aksrtData) return; const REST_BASE = window.aksrtData.siteUrl ? window.aksrtData.siteUrl.replace(/\/$/, '') + '/wp-json/aksrt/v1' : '/wp-json/aksrt/v1'; + const PAY_CONTENT_PATH = '/pay/content/'; function tt(key, zh) { return zh; } @@ -75,14 +76,16 @@ modal.setAttribute('aria-labelledby', 'aksrt-dl-modal-title'); modal.innerHTML = '
' + - '
' + - ' ' + - '
' + + ''; document.body.appendChild(modal); modal.addEventListener('click', function (e) { - if (e.target.matches('[data-close]')) close(); + if (e.target.closest('[data-close]')) close(); }); modal.addEventListener('keydown', function (e) { if (e.key === 'Escape' && modal.classList.contains('is-open')) { @@ -95,14 +98,16 @@ return modal; } - function open(html, returnFocusEl) { + function open(html, returnFocusEl, variant) { // 记录调用方按钮以便关闭后回焦 lastFocusedElement = returnFocusEl || document.activeElement; const modal = buildModal(); const body = modal.querySelector('[data-body]'); - // 给标题加 id,与 aria-labelledby 联动 - body.innerHTML = html.replace(/aksrt-dl-modal__title/, 'aksrt-dl-modal__title" id="aksrt-dl-modal-title'); + modal.classList.toggle('is-cashier', variant === 'cashier'); + body.innerHTML = html; + const title = body.querySelector('.aksrt-dl-modal__title, .aksrt-cashier__title'); + if (title) title.id = 'aksrt-dl-modal-title'; modal.classList.add('is-open'); modal.setAttribute('aria-hidden', 'false'); document.body.classList.add('aksrt-dl-modal-open'); @@ -252,6 +257,25 @@ return card.getAttribute('data-captcha') === '1'; } + function cashierHeader() { + return ''; + } + + function openCashierContent(content, returnFocusEl) { + open( + cashierHeader() + '', + returnFocusEl, + 'cashier' + ); + } + function ensureCaptchaThen(sourceEl, callback) { if (!isCaptchaActive(sourceEl)) { callback(); return; } const state = window.AKSRTCaptchaState; @@ -362,13 +386,8 @@ return headers; } - function payApiBase(apiKind) { - return '/pay/content/'; - } - - function buildPreviewUrl(postId, priceType, couponCode, apiKind) { - apiKind = apiKind || 'content'; - var previewUrl = REST_BASE + payApiBase(apiKind) + postId; + function buildPreviewUrl(postId, priceType, couponCode) { + var previewUrl = REST_BASE + PAY_CONTENT_PATH + postId; var qs = []; if (priceType) qs.push('price_type=' + encodeURIComponent(priceType)); if (couponCode) qs.push('coupon_code=' + encodeURIComponent(couponCode)); @@ -376,8 +395,8 @@ return previewUrl; } - function fetchPreview(postId, priceType, couponCode, apiKind) { - return fetch(buildPreviewUrl(postId, priceType, couponCode, apiKind), { + function fetchPreview(postId, priceType, couponCode) { + return fetch(buildPreviewUrl(postId, priceType, couponCode), { method: 'GET', credentials: 'same-origin', headers: restHeaders(), @@ -386,18 +405,16 @@ }); } - function openCashier(postId, returnFocusEl, priceType, couponCode, apiKind) { + function openCashier(postId, returnFocusEl, priceType, couponCode) { if (!postId) return; priceType = priceType || ''; couponCode = couponCode || ''; - apiKind = apiKind || 'content'; - open( - '

' + tt('cashier_loading', '确认购买') + '

' + - '

' + tt('loading', '加载中…') + '

', + openCashierContent( + '

' + tt('loading', '加载中…') + '

', returnFocusEl ); - fetchPreview(postId, priceType, couponCode, apiKind) + fetchPreview(postId, priceType, couponCode) .then(function (r) { if (!r.ok || (r.json && r.json.code)) { const code = r.json && r.json.code ? r.json.code : ''; @@ -420,7 +437,6 @@ var prev = r.json || {}; if (priceType) prev._price_type = priceType; if (couponCode) prev._coupon_code = couponCode; - prev._api_kind = apiKind; renderCashier(prev, returnFocusEl); }) .catch(function () { @@ -428,6 +444,35 @@ }); } + function formatAssetLabel(preview, value) { + const currency = escapeHtml(preview.currency || (preview.pay_modo === 'points' ? '积分' : '¥')); + const amount = escapeHtml(value == null ? '' : value); + return preview.pay_modo === 'points' ? amount + currency : currency + amount; + } + + function paymentMethodLogo(methodId) { + const logoNames = { + balance: 'pay-balance-logo.svg', + alipay: 'pay-alipay-logo.svg', + epay_alipay: 'pay-alipay-logo.svg', + wechatpay: 'pay-wechat-logo.svg', + epay_wxpay: 'pay-wechat-logo.svg', + paypal: 'pay-paypal-logo.svg', + stripe: 'pay-stripe-logo.svg', + }; + const logo = logoNames[methodId]; + if (!logo) return ''; + const themeUrl = String(window.aksrtData.themeUrl || '').replace(/\/$/, ''); + return ''; + } + + function cashierPriceRow(label, value, className) { + return '
' + + '' + label + '' + + '
' + value + '
' + + '
'; + } + function renderCashier(preview, returnFocusEl) { if (!preview) return; if (preview.need_login && !preview.allow_guest) { @@ -450,7 +495,6 @@ const isPoints = preview.pay_modo === 'points'; const priceType = (preview._price_type || preview.price_type || ''); - const apiKind = preview._api_kind || 'content'; let amountLabel = escapeHtml(preview.amount_label || ''); let baseLabel = escapeHtml(preview.base_label || ''); @@ -470,51 +514,78 @@ let priceRows = ''; if (!preview.pay_limit) { - priceRows += '
' + tt('price', '价格') + '' + baseLabel + '
'; + let regularPrice = '' + baseLabel + ''; + if (!isPoints && Number(preview.original) > Number(preview.base)) { + regularPrice = '' + formatAssetLabel(preview, preview.original) + '' + regularPrice; + } + priceRows += cashierPriceRow(tt('price', '价格'), regularPrice, 'aksrt-cashier__regular-price'); } - // VIP list price row when VIP reduced list amount. if (preview.list_amount !== preview.base) { - priceRows += '
' + tt('vip_price', '会员价') + '' + listLabel + '
'; + priceRows += cashierPriceRow( + tt('vip_price', '会员价') + 'VIP' + escapeHtml(preview.vip_level || '') + '', + '' + listLabel + '', + 'c-red' + ); } else if (preview.amount !== preview.base && !preview.rebate_discount && !preview.coupon_discount) { - priceRows += '
' + tt('vip_price', '会员价') + '' + amountLabel + '
'; + priceRows += cashierPriceRow( + tt('vip_price', '会员价') + 'VIP' + escapeHtml(preview.vip_level || '') + '', + '' + amountLabel + '', + 'c-red' + ); } if (preview.rebate_discount > 0) { - priceRows += '
' + tt('rebate_discount', '推广折扣') + '' + escapeHtml(preview.rebate_discount_label || ('-' + preview.rebate_discount)) + '
'; + priceRows += cashierPriceRow( + tt('rebate_discount', '推广折扣'), + '' + escapeHtml(preview.rebate_discount_label || ('-' + preview.rebate_discount)) + '' + ); } if (preview.coupon_discount > 0) { - priceRows += '
' + tt('coupon_discount', '优惠码') + '' + escapeHtml(preview.coupon_discount_label || ('-' + preview.coupon_discount)) + '
'; + priceRows += cashierPriceRow( + tt('coupon_discount', '优惠码'), + '' + escapeHtml(preview.coupon_discount_label || ('-' + preview.coupon_discount)) + '' + ); + } + if (preview.amount !== preview.base || preview.rebate_discount > 0 || preview.coupon_discount > 0) { + priceRows += cashierPriceRow( + tt('pay_amount', '支付金额'), + '' + amountLabel + '', + 'font-bold c-red' + ); } - priceRows += '
' + tt('pay_amount', '支付金额') + '' + amountLabel + '
'; let body = ''; - body += '
' + typeLabel + '' + title + '
'; - body += '
' + priceRows + '
'; + body += '
' + + '' + typeLabel + '' + title + '' + + '
'; + body += '
' + priceRows + '
'; if (isPoints) { - body += '
' + tt('my_points', '我的积分') + '' + balance + '
'; + body += '
' + + '
' + + '' + tt('my_points', '我的积分') + + '
' + balance + '
'; if (!preview.balance_enough) { - body += '
' + tt('points_short', '抱歉,您的积分不足,暂时无法支付') + '
'; - body += '
'; + body += '
' + tt('points_short', '抱歉,您的积分不足,暂时无法支付') + '
'; + body += ''; - open('

' + tt('confirm_buy', '确认购买') + '

' + body, returnFocusEl); + openCashierContent(body, returnFocusEl); return; } - body += '
'; - body += ''; - body += '
'; + body += ''; } else { if (couponAllowed) { - body += '
'; - body += '
' + tt('use_coupon', '使用优惠码') + '
'; - body += '
'; - body += ''; - body += ''; + body += '
'; + body += '
' + tt('use_coupon', '使用优惠码') + '
'; + body += '
'; + body += ''; + body += ''; body += '
'; if (preview.coupon_desc) { - body += '
' + escapeHtml(preview.coupon_desc) + '
'; + body += '
' + escapeHtml(preview.coupon_desc) + '
'; } if (preview.coupon_error) { body += '
' + escapeHtml(preview.coupon_error) + '
'; @@ -524,42 +595,56 @@ body += '
'; } const walletBal = typeof preview.wallet_balance === 'string' - ? preview.wallet_balance - : (typeof preview.balance === 'string' ? preview.balance : '0.00'); - const hasBalanceMethod = methods.some(function (m) { return (m && m.id) === 'balance'; }); - if (hasBalanceMethod) { - body += '
' + tt('my_wallet', '我的余额') + '¥' + escapeHtml(walletBal) + '
'; - if (preview.balance_enough === false) { - body += '
' + tt('wallet_short', '余额不足,请先充值或选择其他支付方式') + '
'; - if (preview.credits_url) { - body += ''; - } + ? preview.wallet_balance + : (typeof preview.balance === 'string' ? preview.balance : '0.00'); + const hasBalanceMethod = methods.some(function (m) { return (m && m.id) === 'balance'; }); + let methodHtml = ''; + methods.forEach(function (m, idx) { + const rawId = String((m && m.id) || ''); + const id = escapeAttr(rawId); + const name = escapeHtml((m && m.name) || rawId); + methodHtml += ''; + }); + if (methods.length > 1) { + body += '
' + tt('pay_method', '请选择支付方式') + '
' + + '
' + methodHtml + '
'; + } + if (methods.length) { + body += ''; + } + if (hasBalanceMethod) { + const balanceSelected = methods[0] && methods[0].id === 'balance'; + body += '
' + + '
' + + '' + tt('my_wallet', '我的余额') + + '
¥' + escapeHtml(walletBal) + '
'; + if (preview.balance_enough === false) { + if (preview.credits_url) { + body += '' + + tt('wallet_short', '抱歉!余额不足,请先充值') + ' '; + } else { + body += '
' + tt('wallet_short', '抱歉!余额不足,请先充值') + '
'; } } - let methodHtml = ''; - methods.forEach(function (m, idx) { - const id = escapeAttr(m.id || ''); - const name = escapeHtml(m.name || id); - methodHtml += ''; - }); - if (methodHtml) { - body += '
' + tt('pay_method', '支付方式') + '
' + methodHtml + '
'; - } else { - body += '
' + tt('no_pay_method', '暂无可用支付方式,请联系站长配置收款接口') + '
'; - } - body += '
'; - if (methodHtml) { - body += ''; - } body += '
'; + } + if (methods.length) { + body += ''; + } else { + body += '
' + tt('no_pay_method', '暂无可用支付方式,请联系站长配置收款接口') + '
'; + } } - open('

' + tt('confirm_buy', '确认购买') + '

' + body, returnFocusEl); + openCashierContent(body, returnFocusEl); + const modal = document.getElementById('aksrt-dl-modal'); - const checkBtn = document.querySelector('[data-aksrt-check-coupon]'); + const checkBtn = modal && modal.querySelector('[data-aksrt-check-coupon]'); if (checkBtn) { checkBtn.addEventListener('click', function () { - const input = document.querySelector('input[name="aksrt_coupon_code"]'); + const input = modal.querySelector('input[name="aksrt_coupon_code"]'); const code = input ? String(input.value || '').trim() : ''; if (!code) { if (input) input.focus(); @@ -568,7 +653,7 @@ checkBtn.disabled = true; const old = checkBtn.innerHTML; checkBtn.innerHTML = tt('checking', '检查中…'); - fetchPreview(preview.post_id, priceType, code, apiKind) + fetchPreview(preview.post_id, priceType, code) .then(function (r) { checkBtn.disabled = false; checkBtn.innerHTML = old; @@ -579,7 +664,6 @@ var prev = r.json || {}; if (priceType) prev._price_type = priceType; prev._coupon_code = code; - prev._api_kind = apiKind; renderCashier(prev, returnFocusEl); }) .catch(function () { @@ -590,23 +674,47 @@ }); } - const submitBtn = document.querySelector('[data-aksrt-pay-submit]'); + if (modal) { + const methodCards = modal.querySelectorAll('[data-aksrt-payment-method]'); + const methodInput = modal.querySelector('input[name="aksrt_pay_method"]'); + const balanceBox = modal.querySelector('[data-aksrt-balance-box]'); + const selectMethod = function (card) { + const method = card.getAttribute('data-aksrt-payment-method') || ''; + methodCards.forEach(function (item) { + const active = item === card; + item.classList.toggle('active', active); + item.setAttribute('aria-checked', active ? 'true' : 'false'); + }); + if (methodInput) methodInput.value = method; + if (balanceBox) balanceBox.hidden = method !== 'balance'; + }; + methodCards.forEach(function (card) { + card.addEventListener('click', function () { selectMethod(card); }); + card.addEventListener('keydown', function (event) { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + selectMethod(card); + } + }); + }); + } + + const submitBtn = modal && modal.querySelector('[data-aksrt-pay-submit]'); if (submitBtn) { submitBtn.addEventListener('click', function () { const mode = submitBtn.getAttribute('data-mode') || (isPoints ? 'points' : 'cash'); let paymentMethod = 'balance'; - const checked = document.querySelector('input[name="aksrt_pay_method"]:checked'); - if (checked) paymentMethod = checked.value; - const input = document.querySelector('input[name="aksrt_coupon_code"]'); + const selected = modal.querySelector('input[name="aksrt_pay_method"]'); + if (selected) paymentMethod = selected.value; + const input = modal.querySelector('input[name="aksrt_coupon_code"]'); const code = input ? String(input.value || '').trim() : (preview.coupon_code || preview._coupon_code || ''); - submitPurchase(preview.post_id, mode, paymentMethod, submitBtn, returnFocusEl, code, apiKind); + submitPurchase(preview.post_id, mode, paymentMethod, submitBtn, returnFocusEl, code); }); } } - function submitPurchase(postId, mode, paymentMethod, button, returnFocusEl, couponCode, apiKind) { + function submitPurchase(postId, mode, paymentMethod, button, returnFocusEl, couponCode) { if (!postId) return; - apiKind = apiKind || 'content'; const original = button.innerHTML; button.disabled = true; button.innerHTML = tt('processing', '处理中…'); @@ -620,7 +728,7 @@ if (!button.dataset.aksrtOperationKey) { button.dataset.aksrtOperationKey = window.AksrtRequest.createIdempotencyKey(); } - fetch(REST_BASE + payApiBase(apiKind) + postId, { + fetch(REST_BASE + PAY_CONTENT_PATH + postId, { method: 'POST', credentials: 'same-origin', headers: restHeaders(button.dataset.aksrtOperationKey), @@ -687,7 +795,7 @@ 10 ); const priceType = buyBtn.getAttribute('data-price-type') || ''; - if (postId) openCashier(postId, buyBtn, priceType, '', 'content'); + if (postId) openCashier(postId, buyBtn, priceType); return; } @@ -695,7 +803,7 @@ if (cashierLink && cashierLink.hasAttribute('data-aksrt-pay-content')) { e.preventDefault(); const postId = parseInt(cashierLink.getAttribute('data-aksrt-pay-content') || '0', 10); - if (postId) openCashier(postId, cashierLink, '', '', 'content'); + if (postId) openCashier(postId, cashierLink); return; } @@ -707,54 +815,8 @@ }); // Public cashier bridge (content pay). - window.aksrtOpenCashier = function (postId, returnFocusEl, priceType, couponCode, apiKind) { - openCashier(postId, returnFocusEl || null, priceType || '', couponCode || '', apiKind || 'content'); + window.aksrtOpenCashier = function (postId, returnFocusEl, priceType, couponCode) { + openCashier(postId, returnFocusEl || null, priceType || '', couponCode || ''); }; - // ===== 注入 modal 基础样式(避免必须额外加载 CSS)===== - const style = document.createElement('style'); - style.textContent = - '.aksrt-dl-modal{position:fixed;inset:0;z-index:10000;display:none;}' + - '.aksrt-dl-modal.is-open{display:flex;align-items:center;justify-content:center;}' + - '.aksrt-dl-modal__backdrop{position:absolute;inset:0;background:rgba(0,0,0,.55);}' + - '.aksrt-dl-modal__panel{position:relative;background:#fff;border-radius:14px;padding:26px 28px 24px;width:min(420px,calc(100vw - 32px));box-shadow:0 24px 56px rgba(15,23,42,.24);color:#111827;}' + - '.aksrt-dl-modal__close{position:absolute;top:10px;right:12px;background:transparent;border:0;font-size:24px;line-height:1;cursor:pointer;color:#6b7280;}' + - '.aksrt-dl-modal__title{font:700 20px/1.3 inherit;margin:0 28px 18px 0;color:#111827;}' + - '.aksrt-dl-modal__msg{margin:0 0 18px;color:#4b5563;font-size:14px;line-height:1.65;}' + - '.aksrt-dl-modal__msg--warn{color:#92400e;background:transparent;border:0;border-top:1px solid #fde68a;border-bottom:1px solid #fde68a;padding:12px 0;}' + - '.aksrt-dl-modal__row{display:grid;grid-template-columns:64px minmax(0,1fr) 72px;align-items:center;gap:12px;margin:0 0 18px;padding:12px 0;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;}' + - '.aksrt-dl-modal__label{font-size:13px;font-weight:600;color:#6b7280;white-space:nowrap;}' + - '.aksrt-dl-modal__code{display:block;min-width:0;color:#111827;font:800 18px/1.25 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;letter-spacing:.06em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}' + - '.aksrt-dl-modal__copy{appearance:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;width:72px!important;height:32px!important;background:#eff6ff!important;border:1px solid #bfdbfe!important;border-radius:8px!important;padding:0!important;cursor:pointer!important;color:#2563eb!important;-webkit-text-fill-color:#2563eb!important;font-size:0!important;font-weight:700!important;line-height:1!important;font-family:inherit!important;text-indent:0!important;opacity:1!important;visibility:visible!important;white-space:nowrap!important;overflow:visible!important;}' + - '.aksrt-dl-modal__copy::before,.aksrt-dl-modal__copy::after{content:none!important;display:none!important;}' + - '.aksrt-dl-modal__copy:hover{background:#dbeafe!important;border-color:#93c5fd!important;color:#1d4ed8!important;-webkit-text-fill-color:#1d4ed8!important;}' + - '.aksrt-dl-modal__copy-text{display:inline-flex!important;align-items:center!important;justify-content:center!important;position:relative!important;z-index:2!important;color:#2563eb!important;-webkit-text-fill-color:#2563eb!important;font-size:13px!important;font-weight:700!important;line-height:1!important;font-family:inherit!important;text-indent:0!important;opacity:1!important;visibility:visible!important;white-space:nowrap!important;}' + - '.aksrt-dl-modal__copy:hover .aksrt-dl-modal__copy-text{color:#1d4ed8!important;-webkit-text-fill-color:#1d4ed8!important;}' + - '.aksrt-dl-modal__actions{display:flex;align-items:center;gap:12px;padding-top:4px;flex-wrap:wrap;}' + - '.aksrt-dl-modal__download{min-height:40px;padding:0 18px;border-radius:9px;display:inline-flex;align-items:center;justify-content:center;text-decoration:none;border:0;cursor:pointer;}' + - '.aksrt-dl-modal__balance{font-size:13px;color:#6b7280;margin-left:auto;white-space:nowrap;}' + - '.aksrt-cashier__product{margin:0 0 12px;font-size:14px;line-height:1.5;}' + - '.aksrt-cashier__tag{display:inline-block;margin-right:8px;padding:2px 8px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:12px;font-weight:700;}' + - '.aksrt-cashier__box{background:#f8fafc;border:1px solid #e5e7eb;border-radius:10px;padding:10px 12px;margin:0 0 12px;}' + - '.aksrt-cashier__row{display:flex;justify-content:space-between;align-items:center;gap:12px;font-size:13px;color:#6b7280;padding:4px 0;}' + - '.aksrt-cashier__row b{color:#111827;font-size:15px;}' + - '.aksrt-cashier__row b.is-sale{color:#dc2626;}' + - '.aksrt-cashier__row b.is-ok{color:#059669;}' + - '.aksrt-cashier__warn{background:#fef2f2;color:#b91c1c;border-radius:8px;padding:10px 12px;margin:0 0 12px;font-size:13px;}' + - '.aksrt-cashier__methods{margin:0 0 14px;}' + - '.aksrt-cashier__methods-title{font-size:13px;font-weight:600;color:#374151;margin:0 0 8px;}' + - '.aksrt-cashier__method{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px;margin:0 0 6px;cursor:pointer;font-size:13px;}' + - '.aksrt-cashier__coupon{margin:0 0 14px;}' + - '.aksrt-cashier__coupon-row{display:flex;gap:8px;align-items:center;}' + - '.aksrt-cashier__coupon-input{flex:1;min-width:0;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;}' + - '.aksrt-cashier__coupon-desc{margin-top:6px;font-size:12px;color:#9ca3af;line-height:1.5;}' + - '.aksrt-cashier__coupon-ok{margin-top:6px;font-size:12px;color:#059669;}' + - '.resource-download-box__attrs{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:6px;}' + - '.resource-download-box__attrs-item{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:#6b7280;}' + - '.resource-download-box__attrs-name{flex:0 0 auto;}' + - '.resource-download-box__attrs-value{color:#111827;text-align:right;}' + - '.resource-download-box__paid-badge{background:#ecfdf5!important;color:#047857!important;}.resource-download-box__paid-meta{display:grid;gap:4px;margin:8px 0 12px;padding:10px 12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;font-size:12px;color:#166534;}' + - '@media(max-width:480px){.aksrt-dl-modal__panel{padding:24px 20px 22px;}.aksrt-dl-modal__row{grid-template-columns:1fr 72px;gap:8px 12px;}.aksrt-dl-modal__label{grid-column:1 / -1;}.aksrt-dl-modal__code{font-size:17px;}.aksrt-dl-modal__actions{align-items:flex-start;flex-direction:column;}.aksrt-dl-modal__download{width:100%;}.aksrt-dl-modal__balance{margin-left:0;}}' + - 'body.aksrt-dl-modal-open{overflow:hidden;}'; - document.head.appendChild(style); })(); diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php index 4883c2b7..d5d7934a 100644 --- a/src/Core/ScriptsPart1b.php +++ b/src/Core/ScriptsPart1b.php @@ -611,6 +611,14 @@ trait ScriptsPart1b { // 单篇资源 / 付费内容 / 独立下载页:下载 + 收银台脚本 if ($is_single_resource || $is_paid_content_single || $is_download_file_page) { + $cms_download_css_path = get_template_directory() . '/assets/css/frontend/cms-download.css'; + wp_enqueue_style( + 'aksrt-cms-download', + $base_uri . '/assets/css/frontend/cms-download.css', + [$core_dep], + file_exists($cms_download_css_path) ? (string) filemtime($cms_download_css_path) : $version + ); + $cms_download_js_path = get_template_directory() . '/assets/js/cms-download.js'; wp_enqueue_script( 'aksrt-cms-download', diff --git a/tests/widget-system/pay-card-contract.test.cjs b/tests/widget-system/pay-card-contract.test.cjs index 7767da5d..c26271ec 100644 --- a/tests/widget-system/pay-card-contract.test.cjs +++ b/tests/widget-system/pay-card-contract.test.cjs @@ -79,3 +79,31 @@ test('the purchase card owns one direct stylesheet and no retired paywall path', assert.doesNotMatch(pay + download, /安全检测|支持 VIP 权益|支持积分结算/); assert.doesNotMatch(download, /function_exists\('aksrt_get_pay_service_html'\)/); }); + +test('the article cashier uses the reference modal contract and one direct asset path', () => { + const cashier = read('assets/js/cms-download.js'); + const css = read('assets/css/frontend/cms-download.css'); + const assets = read('src/Core/ScriptsPart1b.php'); + + assert.match(cashier, /modal-colorful-header colorful-bg jb-blue/); + assert.match(cashier, /modal-pay-body box-body/); + assert.match(cashier, /pay-tag badg badg-sm mr6/); + assert.match(cashier, /muted-box padding-h6 line-16/); + assert.match(cashier, /payment-method-radio hollow-radio flex-auto pointer/); + assert.match(cashier, /mt6 but jb-red initiate-pay btn-block radius/); + assert.match(cashier, /data-aksrt-payment-method/); + assert.match(cashier, /pay-(?:balance|alipay|wechat|paypal|stripe)-logo\.svg/); + assert.doesNotMatch(cashier, /document\.createElement\(['"]style['"]\)/); + assert.doesNotMatch(cashier, /apiKind|_api_kind|payApiBase/); + assert.doesNotMatch(cashier, /\/(?:shop|forum|bbs)\//i); + + assert.match(css, /\.aksrt-dl-modal \.modal-colorful-header/); + assert.match(css, /\.aksrt-dl-modal \.payment-method-radio/); + assert.match(css, /\.aksrt-dl-modal \.but\.initiate-pay/); + assert.match(assets, /assets\/css\/frontend\/cms-download\.css/); + assert.match(assets, /filemtime\(\$cms_download_css_path\)/); + + for (const logo of ['balance', 'alipay', 'wechat', 'paypal', 'stripe']) { + assert.equal(fs.existsSync(path.join(root, `assets/img/pay-${logo}-logo.svg`)), true); + } +});