feat: add WeChat Pay, Redis logging, storage tiers and personal albums

Replace virtual payments with WeChat Pay API v3 and verify callbacks and order polling. Cache WeChat access tokens in Redis and write structured Zap logs. Support configurable capacity plans across the API, admin and mini program, with purchase snapshots and quota enforcement. Allow personal albums before binding and share them with the current partner. Update deployment configuration and domain routing.
This commit is contained in:
2026-11-02 23:40:49 +08:00
parent 4934a69581
commit 4c9708b562
69 changed files with 2692 additions and 1153 deletions
+23
View File
@@ -46,3 +46,26 @@ test('blank edit address uses current user location; a supplied address wins',as
p.openPhotoEditor(event({batch:0}));p.onEditPhotoInput(event({field:'location'},' '));await p.savePhotoInfo();assert.equal(e.calls[0].location,'当前用户地址');assert.equal(e.calls[0].lat,25);assert.equal(p.data.batches[0].photos[0].location,'当前用户地址');
p.openPhotoEditor(event({batch:0}));p.onEditPhotoInput(event({field:'location'},' 手填地址 '));await p.savePhotoInfo();assert.equal(e.calls[1].location,'手填地址');assert.equal(e.calls[1].lat,0);
});
function uploadSetup(auth) {
let definition;const calls=[],modals=[],toasts=[];
const wx={getStorageSync:()=>'',showToast:v=>toasts.push(v),showModal:v=>modals.push(v),showLoading(){},hideLoading(){},switchTab:v=>calls.push(['switchTab',v]),redirectTo:v=>calls.push(['redirectTo',v]),chooseImage:()=>calls.push(['picker'])};
const api={checkAuth:async()=>auth(),storageInfo:async()=>({is_unlimited:true}),albumList:async()=>({items:[]}),albumAdd:async()=>{calls.push(['albumAdd']);return {id:'c224bc8c-0848-442d-abf6-350fab34d669'};},photoAdd:async data=>{calls.push(['photoAdd',data]);return {success:true};}};
const load=createLoader({wx,console,Date,Promise,Page:p=>definition=p},{'../../utils/request':{api,getImageUrl:v=>v,API_BASE:'https://we.sundynix.cn/api',uploadFileCompat:async()=>{calls.push(['upload']);return {file_id:'6487966a-28ac-41d1-97b6-26f279b11fa9'};}},'../../utils/user-location':{currentUserLocation:async()=>({location:'',lat:0,lng:0})}});
load(path.join(__dirname,'../pages/album/album.js'));
const page={...definition,data:{...definition.data,albumId:'c224bc8c-0848-442d-abf6-350fab34d669',uploadFilePaths:['photo.jpg'],photoFormList:[{}]},setData(v){Object.assign(this.data,v);},loadData(){calls.push(['reload']);}};
return {page,calls,modals,toasts};
}
test('unbound users upload and save photos in their own UUID album',async()=>{
const e=uploadSetup(()=>({logged_in:true,user:{couple_id:null}}));await e.page.uploadAll();
assert.equal(e.calls[0][0],'upload');assert.equal(e.calls[1][0],'photoAdd');assert.equal(e.calls[1][1].path,'6487966a-28ac-41d1-97b6-26f279b11fa9');assert.equal(e.calls[2][0],'reload');assert.equal(e.page.data.showUploadModal,false);assert.equal(e.modals.length,0);
});
test('bound users keep uploading to the existing shared album',async()=>{
const e=uploadSetup(()=>({logged_in:true,user:{couple_id:'bound'}}));await e.page.uploadAll();
assert.equal(e.calls[1][0],'photoAdd');assert.equal(e.calls[1][1].album_id,'c224bc8c-0848-442d-abf6-350fab34d669');
});
test('stale demo album is replaced with a real personal album before transferring photos',async()=>{
const e=uploadSetup(()=>({logged_in:true,user:{couple_id:null}}));e.page.data.albumId=-1;await e.page.uploadAll();
assert.equal(e.calls[0][0],'albumAdd');assert.equal(e.calls[1][0],'upload');assert.equal(e.calls[2][0],'photoAdd');assert.equal(e.calls[2][1].album_id,'c224bc8c-0848-442d-abf6-350fab34d669');
});