Skip to content

Commit

Permalink
feature(functions): first payout email function (initial version) (#789)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkue authored Mar 31, 2024
1 parent 9f9a645 commit 97bbb30
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 56 deletions.
15 changes: 5 additions & 10 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,18 @@ jobs:
- name: Add secrets to functions/.env file
working-directory: functions
run: |
echo POSTFINANCE_EMAIL_PASSWORD=${{ secrets.POSTFINANCE_EMAIL_PASSWORD }} > .env
echo POSTFINANCE_PAYMENTS_FILES_BUCKET=${{ (inputs.project == 'social-income-prod' && vars.POSTFINANCE_PAYMENTS_FILES_BUCKET) || vars.POSTFINANCE_PAYMENTS_FILES_BUCKET_STAGING }} >> .env
echo EXCHANGE_RATES_API=${{ secrets.EXCHANGE_RATES_API }} >> .env
echo POSTFINANCE_FTP_HOST=${{ vars.POSTFINANCE_FTP_HOST }} >> .env
echo POSTFINANCE_FTP_PORT=${{ vars.POSTFINANCE_FTP_PORT }} >> .env
echo POSTFINANCE_FTP_USER=${{ vars.POSTFINANCE_FTP_USER }} >> .env
echo POSTFINANCE_FTP_RSA_PRIVATE_KEY_BASE64=${{ secrets.POSTFINANCE_FTP_RSA_PRIVATE_KEY_BASE64 }} >> .env
echo POSTFINANCE_FTP_USER=${{ vars.POSTFINANCE_FTP_USER }} >> .env
echo POSTFINANCE_PAYMENTS_FILES_BUCKET=${{ (inputs.project == 'social-income-prod' && vars.POSTFINANCE_PAYMENTS_FILES_BUCKET) || vars.POSTFINANCE_PAYMENTS_FILES_BUCKET_STAGING }} >> .env
echo SENDGRID_API_KEY=${{ secrets.SENDGRID_API_KEY }} >> .env
echo STRIPE_API_READ_KEY=${{ (inputs.project == 'social-income-prod' && secrets.STRIPE_API_READ_KEY) || secrets.STRIPE_API_READ_KEY_STAGING }} >> .env
echo STRIPE_WEBHOOK_SECRET=${{ (inputs.project == 'social-income-prod' && secrets.STRIPE_WEBHOOK_SECRET) || secrets.STRIPE_WEBHOOK_SECRET_STAGING }} >> .env
echo NOTIFICATION_EMAIL_USER=${{ secrets.NOTIFICATION_EMAIL_USER }} >> .env
echo NOTIFICATION_EMAIL_PASSWORD=${{ secrets.NOTIFICATION_EMAIL_PASSWORD }} >> .env
echo NOTIFICATION_EMAIL_USER_KERRIN=${{ secrets.NOTIFICATION_EMAIL_USER_KERRIN }} >> .env
echo NOTIFICATION_EMAIL_PASSWORD_KERRIN=${{ secrets.NOTIFICATION_EMAIL_PASSWORD_KERRIN }} >> .env
echo TWILIO_SENDER_PHONE=${{ secrets.TWILIO_SENDER_PHONE }} >> .env
echo TWILIO_SID=${{ secrets.TWILIO_SID }} >> .env
echo TWILIO_TOKEN=${{ secrets.TWILIO_TOKEN }} >> .env
echo TWILIO_SENDER_PHONE=${{ secrets.TWILIO_SENDER_PHONE }} >> .env
echo POSTFINANCE_EMAIL_USER=${{ secrets.POSTFINANCE_EMAIL_USER }} >> .env
echo EXCHANGE_RATES_API=${{ secrets.EXCHANGE_RATES_API }} >> .env
- name: Build functions
run: npm run functions:build
Expand Down
2 changes: 0 additions & 2 deletions functions/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ GCLOUD_PROJECT="social-income-staging"
FIRESTORE_EMULATOR_HOST=127.0.0.1:8080
FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099

POSTFINANCE_EMAIL_USER=[email protected]
POSTFINANCE_EMAIL_PASSWORD=password
POSTFINANCE_PAYMENTS_FILES_BUCKET=postfinance-payments-files
POSTFINANCE_FTP_HOST=ftp.postfinance.example
POSTFINANCE_FTP_PORT=21
Expand Down
1 change: 1 addition & 0 deletions functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"typescript": "^5.4.3"
},
"dependencies": {
"@sendgrid/mail": "^8.1.1",
"@types/ssh2-sftp-client": "^9.0.3",
"@xmldom/xmldom": "^0.8.10",
"axios": "^1.6.8",
Expand Down
8 changes: 0 additions & 8 deletions functions/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,12 @@ export const ASSET_DIR = path.join(__dirname, '..', '..', 'shared', 'assets');
export const STRIPE_API_READ_KEY = process.env.STRIPE_API_READ_KEY!;
export const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;

export const POSTFINANCE_EMAIL_USER = process.env.POSTFINANCE_EMAIL_USER!;
export const POSTFINANCE_EMAIL_PASSWORD = process.env.POSTFINANCE_EMAIL_PASSWORD!;

export const POSTFINANCE_PAYMENTS_FILES_BUCKET = process.env.POSTFINANCE_PAYMENTS_FILES_BUCKET!;
export const POSTFINANCE_FTP_RSA_PRIVATE_KEY_BASE64 = process.env.POSTFINANCE_FTP_RSA_PRIVATE_KEY_BASE64!;
export const POSTFINANCE_FTP_HOST = process.env.POSTFINANCE_FTP_HOST!;
export const POSTFINANCE_FTP_PORT = process.env.POSTFINANCE_FTP_PORT!;
export const POSTFINANCE_FTP_USER = process.env.POSTFINANCE_FTP_USER!;

export const NOTIFICATION_EMAIL_USER = process.env.NOTIFICATION_EMAIL_USER!;
export const NOTIFICATION_EMAIL_PASSWORD = process.env.NOTIFICATION_EMAIL_PASSWORD!;
export const NOTIFICATION_EMAIL_USER_KERRIN = process.env.NOTIFICATION_EMAIL_USER_KERRIN!;
export const NOTIFICATION_EMAIL_PASSWORD_KERRIN = process.env.NOTIFICATION_EMAIL_PASSWORD_KERRIN!;

export const TWILIO_SID = process.env.TWILIO_SID!;
export const TWILIO_TOKEN = process.env.TWILIO_TOKEN!;
export const TWILIO_SENDER_PHONE = process.env.TWILIO_SENDER_PHONE!;
Expand Down
73 changes: 73 additions & 0 deletions functions/src/cron/first-payout-email/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { FirestoreAdmin } from '@socialincome/shared/src/firebase/admin/FirestoreAdmin';
import { toFirebaseAdminTimestamp } from '@socialincome/shared/src/firebase/admin/utils';
import { Contribution, CONTRIBUTION_FIRESTORE_PATH } from '@socialincome/shared/src/types/contribution';
import { User, USER_FIRESTORE_PATH } from '@socialincome/shared/src/types/user';
import { onSchedule } from 'firebase-functions/v2/scheduler';
import { DateTime } from 'luxon';
import { FirstPayoutEmailTemplateData, SendgridMailClient } from '../../../../shared/src/sendgrid/SendgridMailClient';

export const getFirstPayoutEmailReceivers = async (
firestoreAdmin: FirestoreAdmin,
from: DateTime,
to: DateTime,
): Promise<
{
email: string;
templateData: FirstPayoutEmailTemplateData;
}[]
> => {
const users = await firestoreAdmin.collection<User>(USER_FIRESTORE_PATH).get();
return (
await Promise.all(
users.docs
.filter((userDoc) => !userDoc.get('test_user'))
.map(async (userDoc) => {
const user = userDoc.data();
const firstContribution = await firestoreAdmin
.collection(`${USER_FIRESTORE_PATH}/${userDoc.id}/${CONTRIBUTION_FIRESTORE_PATH}`)
.orderBy('created', 'asc')
.limit(1)
.get();

if (firstContribution.empty) return [];
const contribution = firstContribution.docs[0].data() as Contribution;
if (!contribution) return [];
if (
contribution.created >= toFirebaseAdminTimestamp(from) &&
contribution.created < toFirebaseAdminTimestamp(to)
) {
return [
{
email: user.email,
templateData: {
email: user.email,
first_name: user.personal?.name,
donation_amount: contribution.amount,
currency: contribution.currency,
},
},
];
}
return [];
}),
)
).flat();
};

// Run on the 16th of every month at 00:00
export default onSchedule('0 0 16 * *', async () => {
const sendgridClient = new SendgridMailClient(process.env.SENDGRID_API_KEY!);
const firestoreAdmin = new FirestoreAdmin();

const now = DateTime.now();
const fromDate = DateTime.fromObject({ year: now.year, month: now.month - 1, day: 16, hour: 0 }, { zone: 'utc' });
const toDate = DateTime.fromObject({ year: now.year, month: now.month, day: 16, hour: 0 }, { zone: 'utc' });
const firstPayoutEmailReceivers = await getFirstPayoutEmailReceivers(firestoreAdmin, fromDate, toDate);

await Promise.all(
firstPayoutEmailReceivers.map(async (entry) => {
const { email, templateData } = entry;
await sendgridClient.sendFirstPayoutEmail(email, templateData);
}),
);
});
28 changes: 1 addition & 27 deletions functions/src/webhooks/admin/donation-certificates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,33 +59,7 @@ export default onCall<CreateDonationCertificatesFunctionProps, Promise<string>>(
console.info(`Donation certificate document written for user ${userId}`);
successCount += 1;

// if (request.data.sendEmails) {
// await sendEmail({
// to: user.email,
// subject: translator.t('email-subject'),
// // TODO: Use renderEmailTemplate() instead of renderTemplate()
// content: await renderTemplate({
// language: user.language || 'de',
// translationNamespace: 'donation-certificate',
// hbsTemplatePath: 'email/donation-certificate.hbs',
// context: {
// title: translator.t('title', { context: { year } }),
// signature: translator.t('title', { context: { year } }),
// firstname: user.personal?.name,
// year: year,
// },
// }),
// attachments: [
// {
// filename: translator.t('filename', { context: { year } }),
// path: path,
// },
// ],
// from: NOTIFICATION_EMAIL_USER_KERRIN,
// user: NOTIFICATION_EMAIL_USER_KERRIN,
// password: NOTIFICATION_EMAIL_PASSWORD_KERRIN,
// });
// }
// TODO: Send email via Sendgrid
});
} catch (e) {
usersWithFailures.push(userId);
Expand Down
108 changes: 103 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions shared/src/sendgrid/SendgridMailClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MailService } from '@sendgrid/mail';
import { Currency } from '../types/currency';

export type FirstPayoutEmailTemplateData = {
first_name?: string;
donation_amount: number;
currency: Currency;
};

export class SendgridMailClient extends MailService {
constructor(apiKey: string) {
super();
this.setApiKey(apiKey);
}

sendFirstPayoutEmail = async (email: string, data: FirstPayoutEmailTemplateData) => {
await this.send({
to: email,
from: '[email protected]',
templateId: 'd-4e616d721b0240509f468c1e5ff22e6d',
dynamicTemplateData: data,
});
};
}
10 changes: 6 additions & 4 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"tailwindcss": "^3.4.3"
},
"dependencies": {
"@types/react": ">=18.0.0",
"@headlessui/react": "^1.7.18",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.3.4",
Expand All @@ -40,18 +39,21 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/react": ">=18.0.0",
"class-variance-authority": "^0.7.0",
"classnames": "^2.5.1",
"clsx": "^2.1.0",
"cmdk": "^1.0.0",
"embla-carousel-autoplay": "^8.0.0",
"embla-carousel-react": "^8.0.0",
"lucide-react": "^0.363.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"react-hook-form": "^7.51.2",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.22.4",
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
"zod": "^3.22.4"
}
}

0 comments on commit 97bbb30

Please sign in to comment.