From 6a1a01439d8793e828182c296cb6c8adfe3c95a6 Mon Sep 17 00:00:00 2001 From: zlrkw11 Date: Fri, 22 Mar 2024 18:44:52 +1300 Subject: [PATCH 01/13] add customer mock --- server/package.json | 1 + server/src/test-config/mocks/Customer.mock.ts | 39 +++++++++++++++++++ yarn.lock | 11 ++++++ 3 files changed, 51 insertions(+) create mode 100644 server/src/test-config/mocks/Customer.mock.ts diff --git a/server/package.json b/server/package.json index cd5045f29..9edcb4293 100644 --- a/server/package.json +++ b/server/package.json @@ -10,6 +10,7 @@ "express": "^4.18.2", "firebase-admin": "^12.0.0", "helmet": "^7.1.0", + "stripe": "^14.22.0", "supertest": "^6.3.4", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", diff --git a/server/src/test-config/mocks/Customer.mock.ts b/server/src/test-config/mocks/Customer.mock.ts new file mode 100644 index 000000000..e798c60b4 --- /dev/null +++ b/server/src/test-config/mocks/Customer.mock.ts @@ -0,0 +1,39 @@ +import * as Stripe from "stripe" + +export const customerMock: Stripe.Stripe.Customer = { + id: "cus_123456789", + object: "customer", + address: { + city: "city", + country: "NZ", + line1: "line 1", + line2: "line 2", + postal_code: "90210", + state: "Auckland" + }, + balance: 0, + created: 1483565364, + currency: null, + default_source: null, + delinquent: false, + description: null, + discount: null, + email: null, + invoice_prefix: "C11F7E1", + invoice_settings: { + custom_fields: null, + default_payment_method: null, + footer: null, + rendering_options: null + }, + livemode: false, + metadata: { + order_id: "6735" + }, + name: null, + next_invoice_sequence: 1, + phone: null, + preferred_locales: [], + shipping: null, + tax_exempt: "none" +} diff --git a/yarn.lock b/yarn.lock index 217b30384..80c4c2482 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16503,6 +16503,7 @@ __metadata: firebase-admin: "npm:^12.0.0" helmet: "npm:^7.1.0" nodemon: "npm:^3.1.0" + stripe: "npm:^14.22.0" supertest: "npm:^6.3.4" swagger-jsdoc: "npm:^6.2.8" swagger-ui-express: "npm:^5.0.0" @@ -17147,6 +17148,16 @@ __metadata: languageName: node linkType: hard +"stripe@npm:^14.22.0": + version: 14.22.0 + resolution: "stripe@npm:14.22.0" + dependencies: + "@types/node": "npm:>=8.1.0" + qs: "npm:^6.11.0" + checksum: 10c0/befb3a1d517c7bfcbf6cf9bebdebd6d86e3ba1d0f581a4634ff622d95f63c37f0e85266cb35897a0f1f4ac6e6aa6485ac2ffc85a6e66bf85759704ea345f4ebf + languageName: node + linkType: hard + "strnum@npm:^1.0.5": version: 1.0.5 resolution: "strnum@npm:1.0.5" From bbe53930d4d3882103cd0b7ede7c036a9b5199a5 Mon Sep 17 00:00:00 2001 From: Jefffplays2005 Date: Thu, 28 Mar 2024 12:57:38 +1300 Subject: [PATCH 02/13] Push developement files --- client/package.json | 2 +- .../services/StripeWebhook.test.ts | 18 +++++++++ server/src/middleware/__generated__/routes.ts | 2 + .../controllers/StripeWebhook.ts | 37 +++++++++++++++++++ .../request-models/StripeWebhook.ts | 15 ++++++++ yarn.lock | 10 ++--- 6 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 server/src/business-layer/services/StripeWebhook.test.ts create mode 100644 server/src/service-layer/controllers/StripeWebhook.ts create mode 100644 server/src/service-layer/request-models/StripeWebhook.ts diff --git a/client/package.json b/client/package.json index 71fc4c5e0..b404858a8 100644 --- a/client/package.json +++ b/client/package.json @@ -33,7 +33,7 @@ "react-router-dom": "^6.10.0", "react-sweet-state": "^2.7.1", "scheduler": "^0.23.0", - "stripe": "^13.7.0", + "stripe": "^14.22.0", "vite": "^4.1.0-beta.0", "vite-tsconfig-paths": "^4.2.3", "web-vitals": "^2.1.4" diff --git a/server/src/business-layer/services/StripeWebhook.test.ts b/server/src/business-layer/services/StripeWebhook.test.ts new file mode 100644 index 000000000..f09a78bf5 --- /dev/null +++ b/server/src/business-layer/services/StripeWebhook.test.ts @@ -0,0 +1,18 @@ +import StripeService from "./StripeService" + +jest.mock("stripe", () => { + const stripe = jest.requireActual("stripe") + jest + .spyOn(stripe.resources.Customers.prototype, "post") + .mockImplementation(() => { + Promise.resolve({ id: "stripe-test-id" }) + }) + return stripe +}) + +describe("Stripe Service", () => { + it("should do this", async () => { + const customer = new StripeService().getCustomer("eufhgeufh38g") + expect(customer).toEqual(customerMock) + }) +}) diff --git a/server/src/middleware/__generated__/routes.ts b/server/src/middleware/__generated__/routes.ts index 8ca1bfb00..9ae7e70ae 100644 --- a/server/src/middleware/__generated__/routes.ts +++ b/server/src/middleware/__generated__/routes.ts @@ -4,6 +4,8 @@ import { Controller, ValidationService, FieldErrors, ValidateError, TsoaRoute, HttpStatusCodeLiteral, TsoaResponse, fetchMiddlewares } from '@tsoa/runtime'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { UsersController } from './../../service-layer/controllers/UserController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { StripeWebhook } from './../../service-layer/controllers/StripeWebhook'; import { expressAuthentication } from './../../business-layer/security/Authentication'; // @ts-ignore - no great way to install types from subpackage import type { RequestHandler, Router } from 'express'; diff --git a/server/src/service-layer/controllers/StripeWebhook.ts b/server/src/service-layer/controllers/StripeWebhook.ts new file mode 100644 index 000000000..33a58dde4 --- /dev/null +++ b/server/src/service-layer/controllers/StripeWebhook.ts @@ -0,0 +1,37 @@ +import { StripeWebhookHeader } from "service-layer/request-models/StripeWebhook" +import { Controller, SuccessResponse, Route, Post, Body, Header } from "tsoa" +import Stripe from "stripe" +const stripe = new Stripe(process.env.STRIPE_API_KEY) + +@Post() +@Route("webhook") +export class StripeWebhook extends Controller { + @SuccessResponse(200, "Webhook post received") + // public + public async receiveWebhook( + @Header() requestHeader: StripeWebhookHeader, + @Body() requestBody: any + ): Promise { + try { + const event: Stripe.Event = stripe.webhooks.constructEvent( + requestBody, + requestHeader["stripe-signature"], + process.env.STRIPE_API_SECRET + ) + switch (event.type) { + case "payment_intent.succeeded": + console.log("payment_intent.succeeded") + break + case "payment_method.attached": + console.log("payment_method.attached") + break + default: + console.log(`Unhandled event type ${event.type}.`) + } + return this.setStatus(200) // set status to 200 as success + } catch (err) { + // set status to 400 due to bad request + return this.setStatus(400) + } + } +} diff --git a/server/src/service-layer/request-models/StripeWebhook.ts b/server/src/service-layer/request-models/StripeWebhook.ts new file mode 100644 index 000000000..11eb5cc44 --- /dev/null +++ b/server/src/service-layer/request-models/StripeWebhook.ts @@ -0,0 +1,15 @@ +export interface StripeWebhookBody { + id: string + object: string + type: string + data: { + object: { + id: string + } + } + pending_webhooks: integer +} + +export interface StripeWebhookHeader { + "stripe-signature": string +} diff --git a/yarn.lock b/yarn.lock index 217b30384..c952d810a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7753,7 +7753,7 @@ __metadata: react-sweet-state: "npm:^2.7.1" scheduler: "npm:^0.23.0" storybook: "npm:^7.6.17" - stripe: "npm:^13.7.0" + stripe: "npm:^14.22.0" tailwindcss: "npm:^3.4.1" typescript: "npm:^5.3.3" vite: "npm:^4.1.0-beta.0" @@ -17137,13 +17137,13 @@ __metadata: languageName: node linkType: hard -"stripe@npm:^13.7.0": - version: 13.11.0 - resolution: "stripe@npm:13.11.0" +"stripe@npm:^14.22.0": + version: 14.22.0 + resolution: "stripe@npm:14.22.0" dependencies: "@types/node": "npm:>=8.1.0" qs: "npm:^6.11.0" - checksum: 10c0/ea9ad9397a4b5464bf5e2cc3a65683d14e035161c5db0146061370f2316851e6b919e4fe2be97ec0dc72a2856b09cceab69e7afcb4a86f9e3361ed630f2fbad2 + checksum: 10c0/befb3a1d517c7bfcbf6cf9bebdebd6d86e3ba1d0f581a4634ff622d95f63c37f0e85266cb35897a0f1f4ac6e6aa6485ac2ffc85a6e66bf85759704ea345f4ebf languageName: node linkType: hard From 4c25e4e0022459d084493a0a8741a6eff5c6e1d1 Mon Sep 17 00:00:00 2001 From: Jefffplays2005 Date: Thu, 28 Mar 2024 17:06:30 +1300 Subject: [PATCH 03/13] Update to accept post requests, payload issues now --- client/src/models/__generated__/schema.d.ts | 11 +++++++ server/src/middleware/__generated__/routes.ts | 25 ++++++++++++++++ .../src/middleware/__generated__/swagger.json | 12 ++++++++ .../controllers/StripeWebhook.ts | 29 ++++++++++--------- .../request-models/StripeWebhook.ts | 15 ---------- 5 files changed, 64 insertions(+), 28 deletions(-) delete mode 100644 server/src/service-layer/request-models/StripeWebhook.ts diff --git a/client/src/models/__generated__/schema.d.ts b/client/src/models/__generated__/schema.d.ts index 55ad7010b..4e58c9f10 100644 --- a/client/src/models/__generated__/schema.d.ts +++ b/client/src/models/__generated__/schema.d.ts @@ -17,6 +17,9 @@ export interface paths { "/users/bulk-edit": { patch: operations["EditUsers"]; }; + "/webhook": { + post: operations["ReceiveWebhook"]; + }; } export type webhooks = Record; @@ -141,4 +144,12 @@ export interface operations { }; }; }; + ReceiveWebhook: { + responses: { + /** @description Webhook post received */ + 200: { + content: never; + }; + }; + }; } diff --git a/server/src/middleware/__generated__/routes.ts b/server/src/middleware/__generated__/routes.ts index 9ae7e70ae..42b8f1ac7 100644 --- a/server/src/middleware/__generated__/routes.ts +++ b/server/src/middleware/__generated__/routes.ts @@ -188,6 +188,31 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/webhook', + ...(fetchMiddlewares(StripeWebhook)), + ...(fetchMiddlewares(StripeWebhook.prototype.receiveWebhook)), + + function StripeWebhook_receiveWebhook(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const controller = new StripeWebhook(); + + + const promise = controller.receiveWebhook.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, 200, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/server/src/middleware/__generated__/swagger.json b/server/src/middleware/__generated__/swagger.json index 3442d1460..9e472181f 100644 --- a/server/src/middleware/__generated__/swagger.json +++ b/server/src/middleware/__generated__/swagger.json @@ -285,6 +285,18 @@ } } } + }, + "/webhook": { + "post": { + "operationId": "ReceiveWebhook", + "responses": { + "200": { + "description": "Webhook post received" + } + }, + "security": [], + "parameters": [] + } } }, "servers": [ diff --git a/server/src/service-layer/controllers/StripeWebhook.ts b/server/src/service-layer/controllers/StripeWebhook.ts index 33a58dde4..17f200239 100644 --- a/server/src/service-layer/controllers/StripeWebhook.ts +++ b/server/src/service-layer/controllers/StripeWebhook.ts @@ -1,22 +1,24 @@ -import { StripeWebhookHeader } from "service-layer/request-models/StripeWebhook" -import { Controller, SuccessResponse, Route, Post, Body, Header } from "tsoa" +// import { StripeWebhookHeader } from "service-layer/request-models/StripeWebhook" +import { Controller, SuccessResponse, Route, Post, Request } from "tsoa" import Stripe from "stripe" -const stripe = new Stripe(process.env.STRIPE_API_KEY) -@Post() @Route("webhook") export class StripeWebhook extends Controller { @SuccessResponse(200, "Webhook post received") - // public - public async receiveWebhook( - @Header() requestHeader: StripeWebhookHeader, - @Body() requestBody: any - ): Promise { + @Post() + public async receiveWebhook(@Request() request: any): Promise { + const stripe = new Stripe(process.env.STRIPE_API_KEY) + + const endPointSecret = process.env.STRIPE_LOCAL + + console.log("webhook received") try { - const event: Stripe.Event = stripe.webhooks.constructEvent( - requestBody, - requestHeader["stripe-signature"], - process.env.STRIPE_API_SECRET + const event = stripe.webhooks.constructEvent( + // const event: Stripe.Event = stripe.webhooks.constructEvent( + request.body, + request.headers["stripe-signature"], + endPointSecret + // process.env.STRIPE_API_SECRET ) switch (event.type) { case "payment_intent.succeeded": @@ -30,6 +32,7 @@ export class StripeWebhook extends Controller { } return this.setStatus(200) // set status to 200 as success } catch (err) { + console.log(err) // set status to 400 due to bad request return this.setStatus(400) } diff --git a/server/src/service-layer/request-models/StripeWebhook.ts b/server/src/service-layer/request-models/StripeWebhook.ts deleted file mode 100644 index 11eb5cc44..000000000 --- a/server/src/service-layer/request-models/StripeWebhook.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface StripeWebhookBody { - id: string - object: string - type: string - data: { - object: { - id: string - } - } - pending_webhooks: integer -} - -export interface StripeWebhookHeader { - "stripe-signature": string -} From 86ad171ebe9b0871cc31d89f2bd195dd5fd1fa96 Mon Sep 17 00:00:00 2001 From: Jefffplays2005 Date: Thu, 28 Mar 2024 21:24:54 +1300 Subject: [PATCH 04/13] Allows stripe webhook receival. Open to new services being added. Updated README.md to describe how local testing can be performed on the Stripe webhook. Updated index.ts to allow for the keeping of the raw body during requests. Plans to change this to only enable for the /webhook endpoint. --- server/src/index.ts | 13 +++++++++++- .../src/service-layer/controllers/README.md | 6 ++++++ .../controllers/StripeWebhook.ts | 20 +++++++++---------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index f846c654c..1dae915e3 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -15,8 +15,19 @@ const importSwaggerJson = async () => { const app: Express = express() +function keepRawBody( + req: any, + res: any, + buf: Buffer, + encoding: BufferEncoding +) { + if (buf && buf.length) { + req.rawBody = buf + } +} + app.use(helmet()) -app.use(express.json()) +app.use(express.json({ verify: keepRawBody })) app.use(cors()) app.use("/api-docs", swaggerUi.serve, async (_req: Request, res: Response) => { diff --git a/server/src/service-layer/controllers/README.md b/server/src/service-layer/controllers/README.md index eee197126..ffd3f144d 100644 --- a/server/src/service-layer/controllers/README.md +++ b/server/src/service-layer/controllers/README.md @@ -3,3 +3,9 @@ Controller methods convert requests coming from the routes and convert to HTTP responses See the [tsoa docs](https://tsoa-community.github.io/docs/examples.html) for how to write controllers (These will automatically be converted to routes) + +## Stripe Webhook + +For local testing, ensure that your .env file has a STRIPE_LOCAL key, which stores the stripe local webhook key for testing purposes. + +Follow the current [link](https://docs.stripe.com/webhooks#test-webhook) for local testing. diff --git a/server/src/service-layer/controllers/StripeWebhook.ts b/server/src/service-layer/controllers/StripeWebhook.ts index 17f200239..ca0671266 100644 --- a/server/src/service-layer/controllers/StripeWebhook.ts +++ b/server/src/service-layer/controllers/StripeWebhook.ts @@ -1,24 +1,21 @@ -// import { StripeWebhookHeader } from "service-layer/request-models/StripeWebhook" import { Controller, SuccessResponse, Route, Post, Request } from "tsoa" import Stripe from "stripe" @Route("webhook") export class StripeWebhook extends Controller { - @SuccessResponse(200, "Webhook post received") @Post() + @SuccessResponse(200, "Webhook post received") public async receiveWebhook(@Request() request: any): Promise { - const stripe = new Stripe(process.env.STRIPE_API_KEY) + console.log("webhook received") - const endPointSecret = process.env.STRIPE_LOCAL + const stripe = new Stripe(process.env.STRIPE_API_KEY) - console.log("webhook received") try { - const event = stripe.webhooks.constructEvent( - // const event: Stripe.Event = stripe.webhooks.constructEvent( - request.body, + const event: Stripe.Event = stripe.webhooks.constructEvent( + request.rawBody, request.headers["stripe-signature"], - endPointSecret - // process.env.STRIPE_API_SECRET + // process.env.STRIPE_LOCAL // test local api secret + process.env.STRIPE_API_SECRET ) switch (event.type) { case "payment_intent.succeeded": @@ -30,9 +27,10 @@ export class StripeWebhook extends Controller { default: console.log(`Unhandled event type ${event.type}.`) } + return this.setStatus(200) // set status to 200 as success } catch (err) { - console.log(err) + console.error(err) // set status to 400 due to bad request return this.setStatus(400) } From 255a99115c0e684eac072766256e3a6bfef659da Mon Sep 17 00:00:00 2001 From: Jefffplays2005 Date: Thu, 28 Mar 2024 21:31:33 +1300 Subject: [PATCH 05/13] Removed the stripewebhook test, using manual local testing. --- .../services/StripeWebhook.test.ts | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 server/src/business-layer/services/StripeWebhook.test.ts diff --git a/server/src/business-layer/services/StripeWebhook.test.ts b/server/src/business-layer/services/StripeWebhook.test.ts deleted file mode 100644 index f09a78bf5..000000000 --- a/server/src/business-layer/services/StripeWebhook.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import StripeService from "./StripeService" - -jest.mock("stripe", () => { - const stripe = jest.requireActual("stripe") - jest - .spyOn(stripe.resources.Customers.prototype, "post") - .mockImplementation(() => { - Promise.resolve({ id: "stripe-test-id" }) - }) - return stripe -}) - -describe("Stripe Service", () => { - it("should do this", async () => { - const customer = new StripeService().getCustomer("eufhgeufh38g") - expect(customer).toEqual(customerMock) - }) -}) From 8e2cb4e5063fd77dab7695c86bc7ca040baad6bb Mon Sep 17 00:00:00 2001 From: Jefffplays2005 Date: Thu, 28 Mar 2024 21:47:48 +1300 Subject: [PATCH 06/13] Removed StripeWebhook.test.ts as testing is manual and local. Removed README.md documentations as moved the testing steps to the github wiki. Updated server workspace Stripe version. Reverted changes to client workspace back to master. Needing to move helper function out of index.ts --- client/package.json | 2 +- server/package.json | 1 + .../services/StripeWebhook.test.ts | 18 ----------------- server/src/index.ts | 13 +++++++++++- .../controllers/StripeWebhook.ts | 20 +++++++++---------- yarn.lock | 13 +++++++++++- 6 files changed, 35 insertions(+), 32 deletions(-) delete mode 100644 server/src/business-layer/services/StripeWebhook.test.ts diff --git a/client/package.json b/client/package.json index b404858a8..71fc4c5e0 100644 --- a/client/package.json +++ b/client/package.json @@ -33,7 +33,7 @@ "react-router-dom": "^6.10.0", "react-sweet-state": "^2.7.1", "scheduler": "^0.23.0", - "stripe": "^14.22.0", + "stripe": "^13.7.0", "vite": "^4.1.0-beta.0", "vite-tsconfig-paths": "^4.2.3", "web-vitals": "^2.1.4" diff --git a/server/package.json b/server/package.json index cd5045f29..9edcb4293 100644 --- a/server/package.json +++ b/server/package.json @@ -10,6 +10,7 @@ "express": "^4.18.2", "firebase-admin": "^12.0.0", "helmet": "^7.1.0", + "stripe": "^14.22.0", "supertest": "^6.3.4", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", diff --git a/server/src/business-layer/services/StripeWebhook.test.ts b/server/src/business-layer/services/StripeWebhook.test.ts deleted file mode 100644 index f09a78bf5..000000000 --- a/server/src/business-layer/services/StripeWebhook.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import StripeService from "./StripeService" - -jest.mock("stripe", () => { - const stripe = jest.requireActual("stripe") - jest - .spyOn(stripe.resources.Customers.prototype, "post") - .mockImplementation(() => { - Promise.resolve({ id: "stripe-test-id" }) - }) - return stripe -}) - -describe("Stripe Service", () => { - it("should do this", async () => { - const customer = new StripeService().getCustomer("eufhgeufh38g") - expect(customer).toEqual(customerMock) - }) -}) diff --git a/server/src/index.ts b/server/src/index.ts index f846c654c..d0e6e7e6f 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -14,9 +14,20 @@ const importSwaggerJson = async () => { } const app: Express = express() +function keepRawBody( + req: any, + res: any, + buf: Buffer, + encoding: BufferEncoding +) { + if (buf && buf.length) { + req.rawBody = buf + } +} +}) app.use(helmet()) -app.use(express.json()) +app.use(express.json({ verify: keepRawBody })) app.use(cors()) app.use("/api-docs", swaggerUi.serve, async (_req: Request, res: Response) => { diff --git a/server/src/service-layer/controllers/StripeWebhook.ts b/server/src/service-layer/controllers/StripeWebhook.ts index 17f200239..ca0671266 100644 --- a/server/src/service-layer/controllers/StripeWebhook.ts +++ b/server/src/service-layer/controllers/StripeWebhook.ts @@ -1,24 +1,21 @@ -// import { StripeWebhookHeader } from "service-layer/request-models/StripeWebhook" import { Controller, SuccessResponse, Route, Post, Request } from "tsoa" import Stripe from "stripe" @Route("webhook") export class StripeWebhook extends Controller { - @SuccessResponse(200, "Webhook post received") @Post() + @SuccessResponse(200, "Webhook post received") public async receiveWebhook(@Request() request: any): Promise { - const stripe = new Stripe(process.env.STRIPE_API_KEY) + console.log("webhook received") - const endPointSecret = process.env.STRIPE_LOCAL + const stripe = new Stripe(process.env.STRIPE_API_KEY) - console.log("webhook received") try { - const event = stripe.webhooks.constructEvent( - // const event: Stripe.Event = stripe.webhooks.constructEvent( - request.body, + const event: Stripe.Event = stripe.webhooks.constructEvent( + request.rawBody, request.headers["stripe-signature"], - endPointSecret - // process.env.STRIPE_API_SECRET + // process.env.STRIPE_LOCAL // test local api secret + process.env.STRIPE_API_SECRET ) switch (event.type) { case "payment_intent.succeeded": @@ -30,9 +27,10 @@ export class StripeWebhook extends Controller { default: console.log(`Unhandled event type ${event.type}.`) } + return this.setStatus(200) // set status to 200 as success } catch (err) { - console.log(err) + console.error(err) // set status to 400 due to bad request return this.setStatus(400) } diff --git a/yarn.lock b/yarn.lock index c952d810a..80c4c2482 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7753,7 +7753,7 @@ __metadata: react-sweet-state: "npm:^2.7.1" scheduler: "npm:^0.23.0" storybook: "npm:^7.6.17" - stripe: "npm:^14.22.0" + stripe: "npm:^13.7.0" tailwindcss: "npm:^3.4.1" typescript: "npm:^5.3.3" vite: "npm:^4.1.0-beta.0" @@ -16503,6 +16503,7 @@ __metadata: firebase-admin: "npm:^12.0.0" helmet: "npm:^7.1.0" nodemon: "npm:^3.1.0" + stripe: "npm:^14.22.0" supertest: "npm:^6.3.4" swagger-jsdoc: "npm:^6.2.8" swagger-ui-express: "npm:^5.0.0" @@ -17137,6 +17138,16 @@ __metadata: languageName: node linkType: hard +"stripe@npm:^13.7.0": + version: 13.11.0 + resolution: "stripe@npm:13.11.0" + dependencies: + "@types/node": "npm:>=8.1.0" + qs: "npm:^6.11.0" + checksum: 10c0/ea9ad9397a4b5464bf5e2cc3a65683d14e035161c5db0146061370f2316851e6b919e4fe2be97ec0dc72a2856b09cceab69e7afcb4a86f9e3361ed630f2fbad2 + languageName: node + linkType: hard + "stripe@npm:^14.22.0": version: 14.22.0 resolution: "stripe@npm:14.22.0" From 37be1839eacda06bdb62ca643ee5485c010489a7 Mon Sep 17 00:00:00 2001 From: Jefffplays2005 Date: Thu, 28 Mar 2024 21:54:37 +1300 Subject: [PATCH 07/13] Fixed duplicate functions and removed documentation in README.md file --- server/src/index.ts | 12 ------------ server/src/service-layer/controllers/README.md | 6 ------ 2 files changed, 18 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 651538722..641644366 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -14,18 +14,6 @@ const importSwaggerJson = async () => { } const app: Express = express() -function keepRawBody( - req: any, - res: any, - buf: Buffer, - encoding: BufferEncoding -) { - if (buf && buf.length) { - req.rawBody = buf - } -} -}) - function keepRawBody( req: any, res: any, diff --git a/server/src/service-layer/controllers/README.md b/server/src/service-layer/controllers/README.md index ffd3f144d..eee197126 100644 --- a/server/src/service-layer/controllers/README.md +++ b/server/src/service-layer/controllers/README.md @@ -3,9 +3,3 @@ Controller methods convert requests coming from the routes and convert to HTTP responses See the [tsoa docs](https://tsoa-community.github.io/docs/examples.html) for how to write controllers (These will automatically be converted to routes) - -## Stripe Webhook - -For local testing, ensure that your .env file has a STRIPE_LOCAL key, which stores the stripe local webhook key for testing purposes. - -Follow the current [link](https://docs.stripe.com/webhooks#test-webhook) for local testing. From 23d3b717cc0e16254ef8d3ff442b77c40a109c5e Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+bcho892@users.noreply.github.com> Date: Thu, 28 Mar 2024 22:30:32 +1300 Subject: [PATCH 08/13] add stripe env variables to server deployment (#193) * update workflows * change --env format * disable on prs --- .github/workflows/deploy-server.production.yml | 2 +- .github/workflows/deploy-server.staging.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-server.production.yml b/.github/workflows/deploy-server.production.yml index 400b3f34e..888f8f325 100644 --- a/.github/workflows/deploy-server.production.yml +++ b/.github/workflows/deploy-server.production.yml @@ -20,6 +20,6 @@ jobs: steps: - uses: actions/checkout@v3 - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only --config fly.production.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.PROD_SERVER_SERVICE_ACCOUNT }} | base64 --decode)" + - run: flyctl deploy --remote-only --config fly.production.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.PROD_SERVER_SERVICE_ACCOUNT }} | base64 --decode) STRIPE_API_SECRET=${{ secrets.PROD_SERVER_STRIPE_API_SECRET }} STRIPE_API_KEY=${{ secrets.PROD_SERVER_STRIPE_API_KEY }}" env: FLY_API_TOKEN: ${{ secrets.FLY_API_PRODUCTION_API_TOKEN }} diff --git a/.github/workflows/deploy-server.staging.yml b/.github/workflows/deploy-server.staging.yml index 8eb12de09..e4d0c79aa 100644 --- a/.github/workflows/deploy-server.staging.yml +++ b/.github/workflows/deploy-server.staging.yml @@ -23,6 +23,6 @@ jobs: steps: - uses: actions/checkout@v3 - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only --config fly.staging.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.STAGING_SERVER_SERVICE_ACCOUNT }} | base64 --decode)" + - run: flyctl deploy --remote-only --config fly.staging.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.STAGING_SERVER_SERVICE_ACCOUNT }} | base64 --decode) STRIPE_API_SECRET=${{ secrets.STAGING_SERVER_STRIPE_API_SECRET }} STRIPE_API_KEY=${{ secrets.STAGING_SERVER_STRIPE_API_KEY }}" env: FLY_API_TOKEN: ${{ secrets.FLY_API_STAGING_API_TOKEN }} From b6a5db9c52f060aa36ada593dcb325455b83ba1f Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+bcho892@users.noreply.github.com> Date: Thu, 28 Mar 2024 23:00:07 +1300 Subject: [PATCH 09/13] Fix env in workflow (#194) * update workflows * disable on prs * specify --env multiple times --- .github/workflows/deploy-server.production.yml | 2 +- .github/workflows/deploy-server.staging.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-server.production.yml b/.github/workflows/deploy-server.production.yml index 888f8f325..6db57de2a 100644 --- a/.github/workflows/deploy-server.production.yml +++ b/.github/workflows/deploy-server.production.yml @@ -20,6 +20,6 @@ jobs: steps: - uses: actions/checkout@v3 - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only --config fly.production.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.PROD_SERVER_SERVICE_ACCOUNT }} | base64 --decode) STRIPE_API_SECRET=${{ secrets.PROD_SERVER_STRIPE_API_SECRET }} STRIPE_API_KEY=${{ secrets.PROD_SERVER_STRIPE_API_KEY }}" + - run: flyctl deploy --remote-only --config fly.production.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.PROD_SERVER_SERVICE_ACCOUNT }} | base64 --decode)" --env "STRIPE_API_SECRET=${{ secrets.PROD_SERVER_STRIPE_API_SECRET }}" --env "STRIPE_API_KEY=${{ secrets.PROD_SERVER_STRIPE_API_KEY }}" env: FLY_API_TOKEN: ${{ secrets.FLY_API_PRODUCTION_API_TOKEN }} diff --git a/.github/workflows/deploy-server.staging.yml b/.github/workflows/deploy-server.staging.yml index e4d0c79aa..2e8d6ca2a 100644 --- a/.github/workflows/deploy-server.staging.yml +++ b/.github/workflows/deploy-server.staging.yml @@ -23,6 +23,6 @@ jobs: steps: - uses: actions/checkout@v3 - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only --config fly.staging.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.STAGING_SERVER_SERVICE_ACCOUNT }} | base64 --decode) STRIPE_API_SECRET=${{ secrets.STAGING_SERVER_STRIPE_API_SECRET }} STRIPE_API_KEY=${{ secrets.STAGING_SERVER_STRIPE_API_KEY }}" + - run: flyctl deploy --remote-only --config fly.staging.toml --env "GOOGLE_SERVICE_ACCOUNT_JSON=$(echo ${{ secrets.STAGING_SERVER_SERVICE_ACCOUNT }} | base64 --decode)" --env "STRIPE_API_SECRET=${{ secrets.STAGING_SERVER_STRIPE_API_SECRET }}" --env "STRIPE_API_KEY=${{ secrets.STAGING_SERVER_STRIPE_API_KEY }}" env: FLY_API_TOKEN: ${{ secrets.FLY_API_STAGING_API_TOKEN }} From d34c7d42709db10d5a32260e9ff6a416c51e801c Mon Sep 17 00:00:00 2001 From: Benson Cho <100653148+bcho892@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:03:04 +1300 Subject: [PATCH 10/13] get storybook working for signup form (#195) --- .../composite/SignUpForm/SignUpForm.story.tsx | 8 +++++ .../composite/SignUpForm/SignUpForm.tsx | 29 ------------------- 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/client/src/components/composite/SignUpForm/SignUpForm.story.tsx b/client/src/components/composite/SignUpForm/SignUpForm.story.tsx index bc8bbb0e7..6f06e10ad 100644 --- a/client/src/components/composite/SignUpForm/SignUpForm.story.tsx +++ b/client/src/components/composite/SignUpForm/SignUpForm.story.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react" import SignUpForm from "./SignUpForm" +import { BrowserRouter } from "react-router-dom" // 👇 This default export determines where your story goes in the story list const meta: Meta = { @@ -12,6 +13,13 @@ export default meta type Story = StoryObj export const FirstStory: Story = { + decorators: [ + (Story) => ( + + + + ) + ], args: { // 👇 The args you need here will depend on your component } diff --git a/client/src/components/composite/SignUpForm/SignUpForm.tsx b/client/src/components/composite/SignUpForm/SignUpForm.tsx index 2b7e4f142..392db8fe6 100644 --- a/client/src/components/composite/SignUpForm/SignUpForm.tsx +++ b/client/src/components/composite/SignUpForm/SignUpForm.tsx @@ -1,12 +1,5 @@ import React, { useState } from "react" -import { - getAuth, - createUserWithEmailAndPassword, - updateProfile -} from "firebase/auth" import { useNavigate } from "react-router-dom" -import { db } from "../../../firebase" -import { setDoc, doc } from "firebase/firestore" import Button from "@mui/material/Button" import TextField from "@mui/material/TextField" import Snackbar from "@mui/material/Snackbar" @@ -62,7 +55,6 @@ const SignUpForm = () => { setOpen(false) } - const auth = getAuth() const navigate = useNavigate() const handleSubmit = async (event: any) => { @@ -106,29 +98,8 @@ const SignUpForm = () => { } try { - const { user } = await createUserWithEmailAndPassword( - auth, - email, - password - ) console.log("User created") - await updateProfile(user, { displayName: `${firstName} ${lastName}` }) - console.log("Profile updated") - - await setDoc(doc(db, "users", user.uid), { - uid: user.uid, - firstName, - lastName, - email, - phoneNumber, - dob, - studentId, - yearLevel, - faculty, - sportType, - interestedInRacing - }) console.log("Document set in Firestore") setOpen(true) From d9ba977ebeae0a24c35a57af7ec706f106c42cb6 Mon Sep 17 00:00:00 2001 From: ZL Date: Fri, 29 Mar 2024 16:44:15 +1300 Subject: [PATCH 11/13] added product mock --- server/src/test-config/mocks/Product.mock.ts | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 server/src/test-config/mocks/Product.mock.ts diff --git a/server/src/test-config/mocks/Product.mock.ts b/server/src/test-config/mocks/Product.mock.ts new file mode 100644 index 000000000..a2ac263dd --- /dev/null +++ b/server/src/test-config/mocks/Product.mock.ts @@ -0,0 +1,23 @@ +import * as Stripe from "stripe" + +export const productMock: Stripe.Stripe.Product = { + id: "prod_NWjs8kKbJWmuuc", + object: "product", + active: true, + created: 1678833149, + default_price: null, + description: null, + images: [], + features: [], + livemode: false, + metadata: {}, + name: "Gold Plan", + package_dimensions: null, + shippable: null, + statement_descriptor: null, + tax_code: null, + unit_label: null, + updated: 1678833149, + url: null, + type: "good" +} From 3095185f5c1f72b93f851c60a2fb700afff80564 Mon Sep 17 00:00:00 2001 From: ZL Date: Fri, 29 Mar 2024 16:46:52 +1300 Subject: [PATCH 12/13] added price mock file --- README.md | 1 + server/src/test-config/mocks/Prices.mock.ts | 29 +++++++++++++++++++ .../{Product.mock.ts => Product.mock copy.ts} | 0 3 files changed, 30 insertions(+) create mode 100644 server/src/test-config/mocks/Prices.mock.ts rename server/src/test-config/mocks/{Product.mock.ts => Product.mock copy.ts} (100%) diff --git a/README.md b/README.md index f71f4f0f4..94ba964ac 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Project initiated by WDCC in 2023. - Eddie Wang (Project Manager) ## 2024 Team Members + - Kartik Malik - Logan Bellingham - Aziz Patel diff --git a/server/src/test-config/mocks/Prices.mock.ts b/server/src/test-config/mocks/Prices.mock.ts new file mode 100644 index 000000000..4d8f9af5e --- /dev/null +++ b/server/src/test-config/mocks/Prices.mock.ts @@ -0,0 +1,29 @@ +import * as Stripe from "stripe" + +export const priceMock: Stripe.Stripe.Price = { + id: "price_1MoBy5LkdIwHu7ixZhnattbh", + object: "price", + active: true, + billing_scheme: "per_unit", + created: 1679431181, + currency: "usd", + custom_unit_amount: null, + livemode: false, + lookup_key: null, + metadata: {}, + nickname: null, + product: "prod_NZKdYqrwEYx6iK", + recurring: { + aggregate_usage: null, + interval: "month", + interval_count: 1, + trial_period_days: null, + usage_type: "licensed" + }, + tax_behavior: "unspecified", + tiers_mode: null, + transform_quantity: null, + type: "recurring", + unit_amount: 1000, + unit_amount_decimal: "1000" +} diff --git a/server/src/test-config/mocks/Product.mock.ts b/server/src/test-config/mocks/Product.mock copy.ts similarity index 100% rename from server/src/test-config/mocks/Product.mock.ts rename to server/src/test-config/mocks/Product.mock copy.ts From 6cb761b0dd7aab778b260c117a5972526e81e9d1 Mon Sep 17 00:00:00 2001 From: ZL Date: Fri, 29 Mar 2024 17:10:14 +1300 Subject: [PATCH 13/13] deleted individual mock files, moved all into 1 --- server/src/test-config/mocks/Customer.mock.ts | 39 -------- server/src/test-config/mocks/Prices.mock.ts | 29 ------ .../test-config/mocks/Product.mock copy.ts | 23 ----- server/src/test-config/mocks/Stripe.mock.ts | 89 +++++++++++++++++++ 4 files changed, 89 insertions(+), 91 deletions(-) delete mode 100644 server/src/test-config/mocks/Customer.mock.ts delete mode 100644 server/src/test-config/mocks/Prices.mock.ts delete mode 100644 server/src/test-config/mocks/Product.mock copy.ts create mode 100644 server/src/test-config/mocks/Stripe.mock.ts diff --git a/server/src/test-config/mocks/Customer.mock.ts b/server/src/test-config/mocks/Customer.mock.ts deleted file mode 100644 index e798c60b4..000000000 --- a/server/src/test-config/mocks/Customer.mock.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as Stripe from "stripe" - -export const customerMock: Stripe.Stripe.Customer = { - id: "cus_123456789", - object: "customer", - address: { - city: "city", - country: "NZ", - line1: "line 1", - line2: "line 2", - postal_code: "90210", - state: "Auckland" - }, - balance: 0, - created: 1483565364, - currency: null, - default_source: null, - delinquent: false, - description: null, - discount: null, - email: null, - invoice_prefix: "C11F7E1", - invoice_settings: { - custom_fields: null, - default_payment_method: null, - footer: null, - rendering_options: null - }, - livemode: false, - metadata: { - order_id: "6735" - }, - name: null, - next_invoice_sequence: 1, - phone: null, - preferred_locales: [], - shipping: null, - tax_exempt: "none" -} diff --git a/server/src/test-config/mocks/Prices.mock.ts b/server/src/test-config/mocks/Prices.mock.ts deleted file mode 100644 index 4d8f9af5e..000000000 --- a/server/src/test-config/mocks/Prices.mock.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as Stripe from "stripe" - -export const priceMock: Stripe.Stripe.Price = { - id: "price_1MoBy5LkdIwHu7ixZhnattbh", - object: "price", - active: true, - billing_scheme: "per_unit", - created: 1679431181, - currency: "usd", - custom_unit_amount: null, - livemode: false, - lookup_key: null, - metadata: {}, - nickname: null, - product: "prod_NZKdYqrwEYx6iK", - recurring: { - aggregate_usage: null, - interval: "month", - interval_count: 1, - trial_period_days: null, - usage_type: "licensed" - }, - tax_behavior: "unspecified", - tiers_mode: null, - transform_quantity: null, - type: "recurring", - unit_amount: 1000, - unit_amount_decimal: "1000" -} diff --git a/server/src/test-config/mocks/Product.mock copy.ts b/server/src/test-config/mocks/Product.mock copy.ts deleted file mode 100644 index a2ac263dd..000000000 --- a/server/src/test-config/mocks/Product.mock copy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as Stripe from "stripe" - -export const productMock: Stripe.Stripe.Product = { - id: "prod_NWjs8kKbJWmuuc", - object: "product", - active: true, - created: 1678833149, - default_price: null, - description: null, - images: [], - features: [], - livemode: false, - metadata: {}, - name: "Gold Plan", - package_dimensions: null, - shippable: null, - statement_descriptor: null, - tax_code: null, - unit_label: null, - updated: 1678833149, - url: null, - type: "good" -} diff --git a/server/src/test-config/mocks/Stripe.mock.ts b/server/src/test-config/mocks/Stripe.mock.ts new file mode 100644 index 000000000..7e5d8c0e7 --- /dev/null +++ b/server/src/test-config/mocks/Stripe.mock.ts @@ -0,0 +1,89 @@ +import * as Stripe from "stripe" + +export const customerMock: Stripe.Stripe.Customer = { + id: "cus_123456789", + object: "customer", + address: { + city: "city", + country: "NZ", + line1: "line 1", + line2: "line 2", + postal_code: "90210", + state: "Auckland" + }, + balance: 0, + created: 1483565364, + currency: null, + default_source: null, + delinquent: false, + description: null, + discount: null, + email: null, + invoice_prefix: "C11F7E1", + invoice_settings: { + custom_fields: null, + default_payment_method: null, + footer: null, + rendering_options: null + }, + livemode: false, + metadata: { + order_id: "6735" + }, + name: null, + next_invoice_sequence: 1, + phone: null, + preferred_locales: [], + shipping: null, + tax_exempt: "none" +} + +export const productMock: Stripe.Stripe.Product = { + id: "prod_NWjs8kKbJWmuuc", + object: "product", + active: true, + created: 1678833149, + default_price: null, + description: null, + images: [], + features: [], + livemode: false, + metadata: {}, + name: "Gold Plan", + package_dimensions: null, + shippable: null, + statement_descriptor: null, + tax_code: null, + unit_label: null, + updated: 1678833149, + url: null, + type: "good" +} + +export const priceMock: Stripe.Stripe.Price = { + id: "price_1MoBy5LkdIwHu7ixZhnattbh", + object: "price", + active: true, + billing_scheme: "per_unit", + created: 1679431181, + currency: "usd", + custom_unit_amount: null, + livemode: false, + lookup_key: null, + metadata: {}, + nickname: null, + product: "prod_NZKdYqrwEYx6iK", + recurring: { + aggregate_usage: null, + interval: "month", + interval_count: 1, + trial_period_days: null, + usage_type: "licensed" + }, + tax_behavior: "unspecified", + tiers_mode: null, + transform_quantity: null, + type: "recurring", + unit_amount: 1000, + unit_amount_decimal: "1000" +}