Skip to content

Commit

Permalink
Merge pull request #186 from alphamanuscript/master
Browse files Browse the repository at this point in the history
Remove country code from donate link
  • Loading branch information
BambanzaJuniorThe2nd authored Dec 2, 2020
2 parents a63d707 + b98b9ce commit 69e21d5
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 32 deletions.
4 changes: 2 additions & 2 deletions server/src/core/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ export function loadAppConfigFromEnv(env: { [key: string]: string }): AppConfig
distributionPeriodLength: (env.DISTRIBUTION_PERIOD_LENGTH && Number(env.DISTRIBUTION_PERIOD_LENGTH)) || 30,
distributionInterval: (env.DISTRIBUTION_INTERVAL) || `0 */2 * * * *`,
vettedDistributionInterval: (env.VETTED_DISTRIBUTION_INTERVAL) || `0 */5 * * * *`,
dailyDistributionReportingInterval: (env.DAILY_DISTRIBUTION_REPORTING_INTERVAL) || `0 18 * * * *`,
monthlyDistributionReportingInterval: (env.MONTHLY_DISTRIBUTION_REPORTING_INTERVAL) || `0 6 1 * * *`,
dailyDistributionReportingInterval: (env.DAILY_DISTRIBUTION_REPORTING_INTERVAL) || `0 0 18 * * *`,
monthlyDistributionReportingInterval: (env.MONTHLY_DISTRIBUTION_REPORTING_INTERVAL) || `0 0 10 1 * *`,
statsComputationInterval: (env.STATS_COMPUTATION_INTERVAL && Number(env.STATS_COMPUTATION_INTERVAL)) || 1,
googleClientId: env.GOOGLE_CLIENT_ID,
sendgridApiKey: env.SENDGRID_API_KEY || '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const REPORT_TYPE_DAILY = 'daily';
export const REPORT_TYPE_MONTHLY = 'monthly';
const DEFAULT_DONATION_AMOUNT = 2000;

export interface DISTRIBUTION_REPORT_ENHANCED {
export interface EnhancedDistributionReport {
donorId: string,
donor: User,
beneficiaryIds: string[],
Expand Down Expand Up @@ -116,7 +116,7 @@ export class DistributionReports implements DistributionReportService {

const amount = report.totalDistributedAmountFromDonor < DEFAULT_DONATION_AMOUNT ? DEFAULT_DONATION_AMOUNT : report.totalDistributedAmountFromDonor;
const donateLink = await this.args.links.getUserDonateLink(donor, amount);
const reportEnhanced: DISTRIBUTION_REPORT_ENHANCED = {
const reportEnhanced: EnhancedDistributionReport = {
...report,
donorId: report.donor,
donor,
Expand Down
2 changes: 1 addition & 1 deletion server/src/core/distribution-report/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './types';
export { DistributionReports, REPORT_TYPE_DAILY, REPORT_TYPE_MONTHLY, DISTRIBUTION_REPORT_ENHANCED } from './distribution-report-service';
export { DistributionReports, REPORT_TYPE_DAILY, REPORT_TYPE_MONTHLY, EnhancedDistributionReport } from './distribution-report-service';
5 changes: 1 addition & 4 deletions server/src/core/link-generator/bitly-link-shortener.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';
import { LinkShortener } from './types';
import { rethrowIfAppError, createBitlyApiError, createLinkShorteningFailedError } from '../error';

export interface DeepLink {
Expand Down Expand Up @@ -34,10 +35,6 @@ export interface BitlyArgs {
apiLink: string;
}

export interface LinkShortener {
shortenLink(link: string): Promise<string>;
}

export class BitlyLinkShortener implements LinkShortener {
private apiKey: string;
private apiLink: string;
Expand Down
3 changes: 2 additions & 1 deletion server/src/core/link-generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './types';
export { Links } from './link-generator-service';
export { BitlyLinkShortener, LinkShortener } from './bitly-link-shortener';
export { BitlyLinkShortener } from './bitly-link-shortener';
44 changes: 33 additions & 11 deletions server/src/core/link-generator/link-generator-service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { LinkGeneratorService, DonateLinkArgs } from "./types";
import { LinkShortener } from './bitly-link-shortener';
import { LinkGeneratorService, DonateLinkArgs, LinkShortener } from './types';
import { User } from '../user';
import * as queryString from 'querystring';
import { rethrowIfAppError } from '../error';
import { removePhoneCountryCode } from '../util';

interface LinksArgs {
baseUrl: string;
Expand All @@ -18,21 +18,43 @@ export class Links implements LinkGeneratorService {

async getUserDonateLink(user: User, amount: number, shorten: boolean = true): Promise<string> {
const { name, email, phone } = user;
const shortPhone = removePhoneCountryCode(phone);

try {
const longLink = this.getDonateLink({ name, email, phone, amount });
if (shorten) {
const shortLink = await this.args.shortener.shortenLink(longLink);
return shortLink;
}
const longLink = await this.getDonateLink({ name, email, phone: shortPhone, amount }, shorten);
return longLink;
}
catch (e) {
rethrowIfAppError(e);
}
}
getDonateLink(args: DonateLinkArgs): string {
const { name, email, phone, amount } = args;
const nameQuery = queryString.stringify({ n: name}); // n=first%20last
return `${this.args.baseUrl}?donate=true&${nameQuery}&e=${email}&p=${phone}&a=${amount}`;

async getDonateLink(args: DonateLinkArgs, shorten: boolean = true): Promise<string> {
const query: any = {};

if (args.name) {
query.n = args.name;
}

if (args.email) {
query.e = args.email;
}

if (args.phone) {
query.p = args.phone;
}

if (args.amount) {
query.a = args.amount;
}

const encodedQuery = queryString.stringify(query);
const link = `${this.args.baseUrl}?donate=1&${encodedQuery}`;

if (shorten) {
return this.args.shortener.shortenLink(link);
}

return link;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Links } from '../link-generator-service';
import { User } from '../../user';
import { LinkShortener } from '../types';

describe('LinkGeneratorService tests', () => {
const baseUrl = 'https://test.com';
let shortener: LinkShortener;

function getService() {
return new Links({ shortener, baseUrl: baseUrl });
}

beforeEach(() => {
shortener = {
shortenLink: (link) => Promise.resolve(`shorten_${link}`)
};
});


describe('getDonateLink', () => {
test('should generate shortened donate link with arguments url encoded', async () => {
const service = getService();
const link = await service.getDonateLink({
name: 'John Doe',
phone: '711000222',
email: '[email protected]',
amount: 3000
});

expect(link).toEqual('shorten_https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com&p=711000222&a=3000');
});

test('should omit missing values from generated link', async () => {
const service = getService();
const link = await service.getDonateLink({
name: 'John Doe',
email: '[email protected]'
});

expect(link).toEqual('shorten_https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com');
});

test('should not shorten link if shorten option is false', async () => {
const service = getService();
const link = await service.getDonateLink({
name: 'John Doe',
phone: '711000222',
email: '[email protected]',
amount: 3000
}, false);

expect(link).toEqual('https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com&p=711000222&a=3000');
});
});

describe('getUserDonateLink', () => {
test('should generate a shortened link for the specified user', async () => {
const service = getService();
const link = await service.getUserDonateLink({
_id: 'u1',
name: 'John Doe',
email: '[email protected]',
phone: '254711000222',
addedBy: '',
donors: [],
roles: [],
createdAt: new Date(),
updatedAt: new Date()
}, 3000);

expect(link).toEqual('shorten_https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com&p=711000222&a=3000');
});
});
});
45 changes: 38 additions & 7 deletions server/src/core/link-generator/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
import { User } from '../user';

export interface DonateLinkArgs {
name: string,
email: string,
phone: string,
amount: number
/**
* name of the donor
*/
name?: string,
/**
* email of the donor
*/
email?: string,
/**
* phone number of the donor (should not include country code)
*/
phone?: string,
/**
* The amount to donate
*/
amount?: number,
/**
* Whether to shorten the link using a LinkShortener
*/
shorten?: boolean
}

export interface LinkGeneratorService {
getUserDonateLink(user: User, amount: number): Promise<string>;
getDonateLink(args: DonateLinkArgs): string;
}
/**
* Generates a donation link for the specified user
* @param user the user to generate a link for
* @param amount the amount to donate
* @param shorten whether to shorten the link (defaults to true)
*/
getUserDonateLink(user: User, amount: number, shorten?: boolean): Promise<string>;
/**
* Generates a donation link with the specified arguments
* @param args
* @param shorten whether to shorten link (defaults to true)
*/
getDonateLink(args: DonateLinkArgs, shorten?: boolean): Promise<string>;
}

export interface LinkShortener {
shortenLink(link: string): Promise<string>;
}
6 changes: 3 additions & 3 deletions server/src/core/message/message.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DISTRIBUTION_REPORT_ENHANCED } from '../distribution-report';
import { EnhancedDistributionReport } from '../distribution-report';
import { DistributionReport, MonthlyDistributionReport } from '../payment';
import { User } from '../user';
import { extractFirstName } from '../util';
Expand All @@ -21,11 +21,11 @@ export function createDailyDistributionReportEmailMessage(report: DistributionRe
</p>`;
}

export function createMonthlyDistributionReportSmsMessageForContributingDonor(donorsReport: DISTRIBUTION_REPORT_ENHANCED, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
export function createMonthlyDistributionReportSmsMessageForContributingDonor(donorsReport: EnhancedDistributionReport, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
return `Hello ${extractFirstName(donorsReport.donor.name)}, last month a total of Ksh ${systemWideReport.totalDonations} was donated by ${systemWideReport.distributionReports.length} Social Relief donors to ${systemWideReport.totalBeneficiaries} beneficiaries. Ksh ${donorsReport.totalDistributedAmountFromDonor} was sent from your donations to ${beneficiariesAndAmountReceived(donorsReport.beneficiaries, donorsReport.receivedAmount, 'sms')}. Thank you for your contribution. To donate again, click ${donateLink}`;
}

export function createMonthlyDistributionReportEmailMessageForContributingDonor(donorsReport: DISTRIBUTION_REPORT_ENHANCED, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
export function createMonthlyDistributionReportEmailMessageForContributingDonor(donorsReport: EnhancedDistributionReport, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
return `<p>
Hello ${extractFirstName(donorsReport.donor.name)}, <br><br>
Last month a total of Ksh ${systemWideReport.totalDonations} was donated by ${systemWideReport.distributionReports.length} Social Relief donors to ${systemWideReport.totalBeneficiaries} beneficiaries. Ksh ${donorsReport.totalDistributedAmountFromDonor} was sent from your donations to:<br>
Expand Down
10 changes: 10 additions & 0 deletions server/src/core/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,14 @@ export async function verifyGoogleIdToken(token: string): Promise<GoogleUserData
*/
export function extractFirstName(fullName: string) {
return fullName && fullName.split(' ')[0];
}

/**
* Removes the leading country code from a phone number
* Assumes the first 3 digits make up the country code
* 254711222333 -> 711222333
* @param phone
*/
export function removePhoneCountryCode(phone: string) {
return phone.substr(3);
}
1 change: 0 additions & 1 deletion server/src/worker/monthly-distribution-report-worker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { App } from '../core';
import { CronJob } from 'cron';
import { REPORT_TYPE_MONTHLY } from '../core/distribution-report';

export function runMonthlyDistributionReportingWorker(app: App, interval: string) {
const job = new CronJob(interval, async () => {
Expand Down

0 comments on commit 69e21d5

Please sign in to comment.