퀘스트 목록 페이지 판정 · 다시 읽기 · 캠페인 응원 수를 고유 후원자 기준으로

This commit is contained in:
Sn-Kinos
2026-10-06 02:19:40 +09:00
parent 807e446b8b
commit 82662b9b9f
8 changed files with 164 additions and 18 deletions
+5 -1
View File
@@ -1155,6 +1155,8 @@ export async function fundCampaign(input: {
const fandomId = campaign.get('fandomId');
let goal = 0;
let raised = 0;
// 처음 후원하는가 — 「응원 중」(donorCount)은 고유 후원자 수다. 후원 건수가 아니다(2026-10-06 실기기).
let firstBacker = false;
const result = await spendPop({
uid,
@@ -1178,6 +1180,8 @@ export async function fundCampaign(input: {
goal = (snap.get('targetPop') as number | undefined)
?? (snap.get('goalPop') as number | undefined) ?? 0;
raised = (snap.get('raisedPop') as number | undefined) ?? 0;
// 후원자 표식(backers/{uid})이 없으면 첫 후원이다. 읽기는 쓰기보다 먼저 — 같은 트랜잭션.
firstBacker = !(await tx.get(campaignRef.collection('backers').doc(uid))).exists;
// 잔여분으로 깎아 받지 않고 거부한다(B-34 「잔여 목표액까지만 후원 가능」).
if (goal > 0 && raised + amount > goal) {
fail(ERR.campaignOverGoal, '남은 모금 목표보다 큰 금액입니다.');
@@ -1204,7 +1208,7 @@ export async function fundCampaign(input: {
}, { merge: true });
tx.set(campaignRef, {
raisedPop: FieldValue.increment(amount),
donorCount: FieldValue.increment(1),
...(firstBacker ? { donorCount: FieldValue.increment(1) } : {}),
updatedAt: FieldValue.serverTimestamp(),
}, { merge: true });
},
@@ -315,6 +315,15 @@ describe('정산 분기', () => {
expect(short.get('refundDueAt').toMillis() - Date.now()).toBeGreaterThan(71 * 3600_000);
});
it('「응원 중」(donorCount)은 고유 후원자 수다 — 같은 사람의 두 번째 후원은 늘리지 않는다', async () => {
await fundedCampaign('cmp-donors', 1_000, [[A, 100], [B, 50]]);
await db().doc('campaigns/cmp-donors').update({ endsAt: Timestamp.fromMillis(Date.now() + 60_000) });
await fundCampaign({ uid: A, campaignId: 'cmp-donors', clientSeq: '2', amount: 30 });
const snap = await db().doc('campaigns/cmp-donors').get();
expect(snap.get('donorCount')).toBe(2);
expect(snap.get('raisedPop')).toBe(180);
});
it('마감이 지난 캠페인은 정산 전이라도 후원을 받지 않는다', async () => {
await fundedCampaign('cmp-late', 1_000, [[A, 100]]);
expect(await code(() => fundCampaign({ uid: A, campaignId: 'cmp-late', clientSeq: '2', amount: 50 })))