Skip to content

Commit f4572ce

Browse files
authored
Bugfix: Make sure amount displayed on campaign page is dynamically calculated (#1008)
1 parent 3633007 commit f4572ce

File tree

5 files changed

+20
-46
lines changed

5 files changed

+20
-46
lines changed

admin/src/collections/Campaigns.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,45 +69,35 @@ export const campaignsCollection = buildAuditedCollection<Campaign>({
6969
multiline: true,
7070
},
7171
link_website: {
72-
title: 'Website Link',
72+
name: 'Website Link',
7373
dataType: 'string',
7474
validation: { required: false },
7575
description: 'The link to the website (optional)',
7676
},
7777
link_instagram: {
78-
title: 'Instagram Link',
78+
name: 'Instagram Link',
7979
dataType: 'string',
8080
validation: { required: false },
8181
description: 'The link to the Instagram profile (optional)',
8282
},
8383
link_tiktok: {
84-
title: 'TikTok Link',
84+
name: 'TikTok Link',
8585
dataType: 'string',
8686
validation: { required: false },
8787
description: 'The link to the TikTok profile (optional)',
8888
},
8989
link_facebook: {
90-
title: 'Facebook Link',
90+
name: 'Facebook Link',
9191
dataType: 'string',
9292
validation: { required: false },
9393
description: 'The link to the Facebook profile (optional)',
9494
},
9595
link_x: {
96-
title: 'X (formerly Twitter) Link',
96+
name: 'X (formerly Twitter) Link',
9797
dataType: 'string',
9898
validation: { required: false },
9999
description: 'The link to the X profile (optional)',
100100
},
101-
amount_collected_chf: {
102-
dataType: 'number',
103-
name: 'Collected amount in CHF',
104-
readOnly: true,
105-
},
106-
contributions: {
107-
dataType: 'number',
108-
name: 'Contributions',
109-
readOnly: true,
110-
},
111101
goal: {
112102
dataType: 'number',
113103
name: 'Optional Fundraising Goal',
@@ -152,7 +142,7 @@ export const campaignsCollection = buildAuditedCollection<Campaign>({
152142
validation: {
153143
required: true,
154144
matches: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
155-
matchMessage: 'Slug must contain only lowercase letters, numbers, and hyphens',
145+
matchesMessage: 'Slug must contain only lowercase letters, numbers, and hyphens',
156146
},
157147
description:
158148
'URL-friendly version of the title. Must be unique and contain only lowercase letters, numbers, and hyphens.',

shared/src/stripe/StripeEventHandler.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -139,26 +139,6 @@ export class StripeEventHandler {
139139
}
140140
};
141141

142-
/**
143-
* Increments the total donations of a campaign if the charge is associated with a campaignId.
144-
*/
145-
maybeUpdateCampaign = async (contribution: StripeContribution): Promise<void> => {
146-
if (contribution.campaign_path) {
147-
try {
148-
const campaign = await contribution.campaign_path.get();
149-
const current_contributions = campaign.data()?.contributions ?? 0;
150-
const current_amount_chf = campaign.data()?.amount_collected_chf ?? 0;
151-
await contribution.campaign_path.update({
152-
contributions: current_contributions + 1,
153-
amount_collected_chf: current_amount_chf + contribution.amount_chf,
154-
});
155-
console.log(`Campaign amount ${contribution.campaign_path} updated.`);
156-
} catch (error) {
157-
console.error(`Error updating campaign amount ${contribution.campaign_path}.`, error);
158-
}
159-
}
160-
};
161-
162142
constructStatus = (status: Stripe.Charge.Status) => {
163143
switch (status) {
164144
case 'succeeded':
@@ -207,7 +187,6 @@ export class StripeEventHandler {
207187
).doc(charge.id);
208188
await contributionRef.set(contribution, { merge: true });
209189
console.info(`Updated contribution document: ${contributionRef.path}`);
210-
await this.maybeUpdateCampaign(contribution);
211190
return contributionRef;
212191
};
213192
}

shared/src/types/campaign.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ export type Campaign = {
1717
link_tiktok?: string;
1818
link_facebook?: string;
1919
link_x?: string;
20-
amount_collected_chf: number; // automatically updated by incoming payments.
21-
contributions: number; // automatically updated by incoming payments.
2220
goal?: number;
2321
goal_currency?: Currency;
2422
end_date: Timestamp;

shared/src/types/contribution.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,24 @@ export enum StatusKey {
2222

2323
export type Contribution = StripeContribution | BankWireContribution;
2424

25+
/**
26+
* Represents a contribution to Social Income. The amount that ends up on our account is amount_chf - fees_chf.
27+
*/
2528
type BaseContribution = {
2629
source: ContributionSourceKey;
2730
status: StatusKey;
2831
created: Timestamp;
2932
amount: number;
30-
amount_chf: number;
31-
fees_chf: number;
33+
amount_chf: number; // Amount donated in CHF, including fees
34+
fees_chf: number; // Transaction fees in CHF
3235
currency: Currency;
3336
campaign_path?: DocumentReference;
3437
};
3538

3639
export type StripeContribution = BaseContribution & {
3740
source: ContributionSourceKey.STRIPE;
3841
monthly_interval: number;
39-
reference_id: string; // stripe charge id
42+
reference_id: string; // The stripe charge id, see: https://docs.stripe.com/api/charges
4043
};
4144

4245
export type BankWireContribution = BaseContribution & {

website/src/app/[lang]/[region]/(website)/campaign/[campaign]/page.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { firestoreAdmin } from '@/firebase-admin';
66
import { WebsiteLanguage, WebsiteRegion } from '@/i18n';
77
import { getMetadata } from '@/metadata';
88
import { Campaign, CAMPAIGN_FIRESTORE_PATH, CampaignStatus } from '@socialincome/shared/src/types/campaign';
9+
import { Contribution, CONTRIBUTION_FIRESTORE_PATH } from '@socialincome/shared/src/types/contribution';
910
import { daysUntilTs } from '@socialincome/shared/src/utils/date';
1011
import { getLatestExchangeRate } from '@socialincome/shared/src/utils/exchangeRates';
1112
import { Translator } from '@socialincome/shared/src/utils/i18n';
@@ -85,8 +86,11 @@ export default async function Page({ params }: CampaignPageProps) {
8586
? await getLatestExchangeRate(firestoreAdmin, campaign.goal_currency)
8687
: 1.0;
8788

88-
const contributions = campaign.contributions ?? 0;
89-
const amountCollected = Math.round((campaign.amount_collected_chf ?? 0) * exchangeRate);
89+
const contributions = await firestoreAdmin
90+
.collectionGroup<Contribution>(CONTRIBUTION_FIRESTORE_PATH)
91+
.where('campaign_path', '==', firestoreAdmin.firestore.doc([CAMPAIGN_FIRESTORE_PATH, params.campaign].join('/')))
92+
.get();
93+
const amountCollected = (contributions.docs.reduce((sum, c) => sum + c.data().amount_chf, 0) ?? 0) * exchangeRate;
9094
const percentageCollected = campaign.goal ? Math.round((amountCollected / campaign.goal) * 100) : undefined;
9195
const daysLeft = daysUntilTs(campaign.end_date.toDate());
9296

@@ -122,7 +126,7 @@ export default async function Page({ params }: CampaignPageProps) {
122126
<Typography size="2xl" weight="medium" color="secondary">
123127
{translator?.t('campaign.without-goal.collected', {
124128
context: {
125-
count: contributions,
129+
count: contributions.docs.length,
126130
amount: amountCollected,
127131
currency: campaign.goal_currency,
128132
total: campaign.goal,
@@ -157,7 +161,7 @@ export default async function Page({ params }: CampaignPageProps) {
157161
<Typography size="md" color="primary">
158162
{translator.t('campaign.with-goal.collected-amount', {
159163
context: {
160-
count: contributions,
164+
count: contributions.docs.length,
161165
amount: amountCollected,
162166
currency: campaign.goal_currency,
163167
},

0 commit comments

Comments
 (0)