Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix license generation on vercel #158

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"build": "next build",
"gen:license": "ts-node -P scripts/tsconfig.json scripts/license.ts",
"lint": "tsc --noEmit && eslint . --ext js,ts,tsx",
"start": "next start"
"start": "next start",
"test": "jest"
},
"dependencies": {
"@geist-ui/react": "^2.1.5",
Expand Down Expand Up @@ -43,7 +44,11 @@
},
"devDependencies": {
"@amaurym/config": "^1.3.6",
"@types/jest": "^29.0.0",
"dotenv": "^16.0.2",
"eslint-config-next": "^12.2.5",
"jest": "^29.0.1",
"ts-jest": "^28.0.8",
"typescript": "^4.8.2",
"vercel": "^28.2.0"
}
Expand Down
73 changes: 73 additions & 0 deletions src/pages/api/stripe/webhooks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import axios from 'axios';
import dotenv from 'dotenv';
import path from 'path';
import Stripe from 'stripe';

import { getURL } from '../../../util/helpers';
import { stripe } from '../../../util/stripeServer';

const invoiceSucceededEvent: Stripe.Event = {
id: 'evt_foobar',
object: 'event',
api_version: '2020-08-27',
created: 1661681895,
data: {
object: {
created: 1661681895,
customer: 'cus_foobar',
customer_address: {
city: 'Foo City',
country: 'Bar Country',
line1: 'Foobar Street 1',
line2: '',
postal_code: 'FO123',
state: '',
},
customer_email: '[email protected]',
customer_name: 'Mr. Foo Bar',
total: 39900,
},
},
livemode: false,
pending_webhooks: 0,
request: {
id: null,
idempotency_key: null,
},
type: 'invoice.payment_succeeded',
};

describe('/api/stripe/webhooks', () => {
/**
* This test requires a server running on `getURL()`. Skipped by default.
*/
it.skip('sends an email on invoice payment succeeded', async () => {
dotenv.config({
path: path.resolve(process.cwd(), '.env.development.local'),
});

// https://stackoverflow.com/questions/65306706/writing-unit-tests-for-stripe-webhooks-stripe-signature
const payloadString = JSON.stringify(invoiceSucceededEvent, null, 2);
const secret = process.env.STRIPE_WEBHOOK_SECRET_LIVE as string;
const header = stripe.webhooks.generateTestHeaderString({
payload: payloadString,
secret,
});

// This sends to localhost:3000 on your local machine, but to the Vercel
// deployment during CI.
const { data } = await axios.post<{
received: boolean;
error?: string;
}>(`${getURL()}/api/stripe/webhooks`, payloadString, {
headers: {
'stripe-signature': header,
},
});

expect(data.error).toBeUndefined();
expect(data.received).toEqual(true);
});
});

export {};
30 changes: 22 additions & 8 deletions src/pages/api/stripe/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,7 @@ const webhookHandler = async (
return;
}

if (!invoice.customer_name) {
throw new Error(
'customer_name is empty in invoice'
);
}
const customerName = await getCustomerName(invoice);

// Generate PDF with the given info.
const stripeBuyDate = new Date(invoice.created * 1000);
Expand All @@ -153,7 +149,7 @@ const webhookHandler = async (
license_end_date: licenseEndDate,
number_devs: 8,
stripe_buy_date: stripeBuyDate,
stripe_buyer_name: invoice.customer_name,
stripe_buyer_name: customerName,
stripe_buyer_email: invoice.customer_email,
stripe_buyer_address: stripeAddressToString(
invoice.customer_address
Expand All @@ -163,12 +159,12 @@ const webhookHandler = async (
// Send the email with the attached PDF.
const data = {
from: 'Amaury <[email protected]>',
to: 'amaury@reacher.email',
to: 'test@reacher.email', // TODO Change to real recipient
subject: `Reacher Commercial License: ${format(
stripeBuyDate,
'dd/MM/yyyy'
)} to ${format(licenseEndDate, 'dd/MM/yyyy')}`,
text: `Hello ${invoice.customer_name},
text: `Hello ${customerName},

Thank you for using Reacher. You will find attached the Commercial License for the period of ${format(
stripeBuyDate,
Expand Down Expand Up @@ -243,4 +239,22 @@ function stripeAddressToString(addr: Stripe.Address | null): string {
.join(', ');
}

// / Stripe invoice object doesn't always include the customer name. If it's
// not present, we make an additional API call.
async function getCustomerName(invoice: Stripe.Invoice): Promise<string> {
if (invoice.customer_name) {
return invoice.customer_name;
}

try {
const c = (await stripe.customers.retrieve(
invoice.customer as string
)) as Stripe.Customer;

return c.name || 'Reacher customer';
} catch (e) {
return 'Reacher customer';
}
}

export default withSentry(webhookHandler);
2 changes: 1 addition & 1 deletion src/pages/api/v0/check_email.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CheckEmailInput, CheckEmailOutput } from '@reacherhq/api';
import { withSentry } from '@sentry/nextjs';
import { User } from '@supabase/supabase-js';
import axios, { AxiosError, AxiosRequestHeaders } from 'axios';
import axios, { AxiosError } from 'axios';
import Cors from 'cors';
import { addMonths, differenceInMilliseconds, parseISO } from 'date-fns';
import { NextApiRequest, NextApiResponse } from 'next';
Expand Down
4 changes: 2 additions & 2 deletions src/util/license.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';
import { format } from 'date-fns';
import mdPdf from 'markdown-pdf';
import { render } from 'mustache';
import mustache from 'mustache';

const LICENSE_TEMPLATE =
'https://raw.githubusercontent.com/reacherhq/policies/master/license/commercial.en.md';
Expand Down Expand Up @@ -58,7 +58,7 @@ export async function generateLicense(
const { data: template } = await axios.get<string>(LICENSE_TEMPLATE);

// Format date nicely.
const filledMd = render(template, {
const filledMd = mustache.render(template, {
...metadata,
license_end_date: format(metadata.license_end_date, 'MMMM dd yyyy'),
number_devs: '8 (eight)', // For now we hardcode to 8.
Expand Down
Loading