function viewBuy(){ const types=['','subs','inapp','one_time']; const all=sortPurchasesDesc((cache.purchases||[]).filter(p=>{ if(!p) return false; if(filters.buyType && p.product_type!==filters.buyType) return false; const em=emails(); return matchQ([em[p.account_id], p.package, p.sku, p.order_id, p.price, p.status, String(p.id)]); })); const info=paginate(all,'buy'); const em=emails(); const rows=info.slice.map(p=>''+ ''+esc(em[p.account_id]||('#'+p.account_id))+''+ appIcoCell(p.package)+ ''+esc(p.sku||'-')+'
'+esc(p.package||'')+'
'+ ''+pill(purchaseStatus(p))+''+ ''+esc(recordMoney(p)||'-')+''+ ''+esc(recordOffer(p)||'-')+''+ ''+purchaseTime(p)+''+ ''+sourcePill(p.source)+''+ '').join(''); $('#app').innerHTML= '
'+ filterSelect('类型', 'filters.buyType=this.value; pageNo.buy=1; render(false)', types.map(t=>'').join(''))+ '当前 '+all.length+' / '+(cache.purchases||[]).length+''+ '
'+ '
'+ (rows||'')+ '
账号商品状态金额优惠时间来源
没有购买记录。
'+pager('buy', info); } function viewSubs(){ const statuses=['','active','cancelled','replaced']; const pkgs=sortApps([...new Set((cache.subs||[]).map(s=>s.package).filter(Boolean))]); const sorts=[['purchase','购买时间'],['expiry','到期时间']]; let all=(cache.subs||[]).filter(s=>{ if(!s) return false; if(filters.subStatus && s.status!==filters.subStatus) return false; if(filters.subPkg && s.package!==filters.subPkg) return false; if(filters.subRenew==='yes' && s.auto_renewing===false) return false; if(filters.subRenew==='no' && s.auto_renewing!==false) return false; if(filters.subAck==='yes' && subscriptionAcknowledged(s)!=='已确认') return false; if(filters.subAck==='no' && subscriptionAcknowledged(s)!=='未确认') return false; const em=emails(); return matchQ([em[s.account_id], s.package, s.sku, s.order_id, s.status, s.purchase_token, String(s.id)]); }); const ts=v=>{ let n=Number(v||0); if(!n) return 0; if(n>1e12) n=Math.floor(n/1000); return n; }; all=all.slice().sort((a,b)=>{ if(filters.subSort==='expiry'){ const ae=ts(a.expiry), be=ts(b.expiry); if(!ae && !be) return 0; if(!ae) return 1; if(!be) return -1; const subDir = ae ? 1 : -1; return (ae-be)*subDir; } const ap=ts(a.purchase_time), bp=ts(b.purchase_time); if(!ap && !bp) return (Number(b.id)||0)-(Number(a.id)||0); if(!ap) return 1; if(!bp) return -1; return bp-ap || ((Number(b.id)||0)-(Number(a.id)||0)); }); const grouped={}; all.forEach(s=>{ const pkg=s.package||'(无应用)'; (grouped[pkg]=grouped[pkg]||[]).push(s); }); const pkgKeys=sortApps(Object.keys(grouped)); const info=paginate(pkgKeys,'subs'); const em=emails(); const rowOf=s=>''+ ''+esc(em[s.account_id]||('#'+s.account_id))+''+ ''+esc(s.sku||'-')+''+ ''+pill(s.status)+''+ ''+ackCell(s)+''+ ''+renewPill(s.auto_renewing)+''+ '买于 '+fmtUnix(s.purchase_time, true)+'
到期 '+fmtUnix(s.expiry, true)+(s.renew_price?(' · '+esc(s.renew_price)):'')+''+ ''+subAction(s.account_id, s)+''+ ''; const rows=info.slice.map(pkg=>{ const items=grouped[pkg]; const open=window._subOpen&&window._subOpen[pkg]; const ico=appIcoCell(pkg).replace(/<\/?td[^>]*>/g,''); return ''+(open?'▾':'▸')+''+ico+' '+esc(pkg)+' '+items.length+' 条'+ (open?items.map(rowOf).join(''):''); }).join(''); $('#app').innerHTML= '
'+ filterSelect('状态', 'filters.subStatus=this.value; pageNo.subs=1; render(false)', statuses.map(s=>'').join(''))+ filterSelect('应用', 'filters.subPkg=this.value; pageNo.subs=1; render(false)', ''+pkgs.map(p=>'').join(''))+ filterSelect('是否续订', 'filters.subRenew=this.value; pageNo.subs=1; render(false)', '')+ filterSelect('是否确认', 'filters.subAck=this.value; pageNo.subs=1; render(false)', '')+ filterSelect('排序', 'filters.subSort=this.value; pageNo.subs=1; render(false)', sorts.map(s=>'').join(''))+ ''+ ''+ '当前 '+all.length+' / '+(cache.subs||[]).length+''+ '
'+ '
'+ (rows||'')+ '
账号商品状态已确认续订时间
没有订阅。
'+pager('subs', info); document.querySelectorAll('#app tr.sub-group').forEach(tr=>{ tr.onclick=()=>{ const pkg=tr.getAttribute('data-pkg'); window._subOpen=window._subOpen||{}; window._subOpen[pkg]=!window._subOpen[pkg]; viewSubs(); }; }); } async function upgradeSub(ev, aid, token, pkg, sku){ if(ev && ev.stopPropagation) ev.stopPropagation(); await buyOne(ev, aid, {upgrade:true, oldToken:token, package:pkg, oldPackage:pkg, oldSku:sku, sku:''}); } async function renewSub(ev, aid, pkg, sku){ if(ev && ev.stopPropagation) ev.stopPropagation(); const sub=accountSubs(aid).find(s=>s.package===pkg && s.sku===sku); const body={ account_id:Number(aid), package:pkg, sku:sku, product_type:'subs', offer_token:'', upgrade:false, old_purchase_token:sub?purchaseTokenOf(sub):'', instrument_id:0 }; const ok=await showPayConfirm(body); if(!ok) return; const j=await api('/api/purchases',{method:'POST',body:JSON.stringify(body)}); if(j.error) return fail('无法续订', j.error); notice('正在续订', pkg+' / '+sku); watchAccountJob(aid, 'buy', j.job_id, '续订'); refreshList(); } function cancelableSubs(){ return (cache.subs||[]).filter(s=>s && s.status!=='cancelled' && s.auto_renewing!==false); } function unackedSubs(){ return (cache.subs||[]).filter(s=>s && subscriptionAcknowledged(s)==='未确认'); } async function cancelAllSubs(ev){ const list=cancelableSubs(); if(!list.length) return fail('没有可取消的订阅','没有仍在续订的订阅'); const ok=await modal({title:'全量取消订阅', message:'将取消 '+list.length+' 个仍在续订的订阅。', okText:'开始', cancelText:'取消'}); if(!ok) return; const jobIds=[]; for(const s of list){ const j=await api('/api/subscriptions/cancel',{method:'POST',body:JSON.stringify({account_id:Number(s.account_id), package:s.package||'', sku:s.sku||'', purchase_token:s.purchase_token||''})}); if(!j.error && j.job_id) jobIds.push(j.job_id); } if(!jobIds.length) return fail('无法取消','入队失败'); notice('已开始全量取消', '任务 '+jobIds.length+' 个'); refreshList(); } async function ackAllSubs(ev){ const list=unackedSubs(); if(!list.length) return fail('没有未确认的订阅','订阅都已确认'); const ok=await modal({title:'全量确认订阅', message:'将确认 '+list.length+' 个未确认的订阅。', okText:'开始', cancelText:'取消'}); if(!ok) return; const jobIds=[]; for(const s of list){ const j=await api('/api/subscriptions/acknowledge',{method:'POST',body:JSON.stringify({account_id:Number(s.account_id), purchase_token:s.purchase_token||'', package:s.package||'', sku:s.sku||'', order_id:s.order_id||''})}); if(!j.error && j.job_id) jobIds.push(j.job_id); } if(!jobIds.length) return fail('无法确认','入队失败'); notice('已开始全量确认', '任务 '+jobIds.length+' 个'); refreshList(); } async function canSub(ev, aid, pkg, sku, token){ if(ev && ev.stopPropagation) ev.stopPropagation(); const ok=await modal({title:'取消订阅', message:pkg+' / '+sku, okText:'取消订阅', cancelText:'返回'}); if(!ok) return; const j=await api('/api/subscriptions/cancel',{method:'POST',body:JSON.stringify({account_id:Number(aid), package:pkg, sku:sku, purchase_token:token||''})}); if(j.error) return fail('无法取消', j.error); notice('正在取消订阅', '任务 #'+(j.job_id||'')); watchAccountJob(aid, 'cancel', j.job_id, '取消订阅'); refreshList(); }