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();
}