diff --git a/.env.docker b/.env.docker index d2107cf5a16..6337f42d950 100644 --- a/.env.docker +++ b/.env.docker @@ -14,3 +14,4 @@ ADMIN_USERS="eddiejaoude,SaraJaoude" STRIPE_SECRET_KEY="" STRIPE_PREMIUM_PRICING_ID="" STRIPE_MANAGE_PLAN_URL="" +STRIPE_WEBHOOK_SECRET="" diff --git a/.env.example b/.env.example index 5ab6d20adc0..6ad917ba940 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,4 @@ ADMIN_USERS="eddiejaoude,SaraJaoude,_test-admin-user" STRIPE_SECRET_KEY="" STRIPE_PREMIUM_PRICING_ID="" STRIPE_MANAGE_PLAN_URL="" +STRIPE_WEBHOOK_SECRET="" diff --git a/components/account/manage/Navigation.js b/components/account/manage/Navigation.js index d744f35cef6..6576209557c 100644 --- a/components/account/manage/Navigation.js +++ b/components/account/manage/Navigation.js @@ -28,7 +28,10 @@ const tabs = [ { name: "Links", href: "/account/manage/links", - match: ["/account/manage/link/[[...data]]"], + match: [ + "/account/manage/link/[[...data]]", + "/account/statistics/link/[[...data]]", + ], icon: MdOutlineLink, current: false, }, diff --git a/config/schemas/serverSchema.js b/config/schemas/serverSchema.js index dd8ed11a8ec..de81a5e7be9 100644 --- a/config/schemas/serverSchema.js +++ b/config/schemas/serverSchema.js @@ -13,7 +13,9 @@ const envSchema = z.object({ GITHUB_API_TOKEN: z.string().optional(), RANDOM_USERS: z.string().optional(), ADMIN_USERS: z.string().optional(), + NEXT_PUBLIC_VERCEL_ENV: z.string().optional(), STRIPE_SECRET_KEY: z.string().optional(), + STRIPE_WEBHOOK_SECRET: z.string().optional(), }); const serverEnv = envSchema.safeParse(process.env); diff --git a/config/stripe.js b/config/stripe.js new file mode 100644 index 00000000000..35c2ab8b4d7 --- /dev/null +++ b/config/stripe.js @@ -0,0 +1,13 @@ +import Stripe from "stripe"; + +import { serverEnv } from "@config/schemas/serverSchema"; + +const stripe = () => { + const stripe = new Stripe(serverEnv.STRIPE_SECRET_KEY, { + apiVersion: "2020-08-27", + }); + + return stripe; +}; + +export default stripe(); diff --git a/package-lock.json b/package-lock.json index 13f0a811008..3f719a1176e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,8 @@ "file-saver": "^2.0.5", "husky": "^8.0.3", "leaflet": "^1.9.4", + "micro": "^10.0.1", + "micro-cors": "^0.1.1", "mongoose": "^7.4.1", "next": "^13.4.12", "next-auth": "^4.22.3", @@ -3006,6 +3008,126 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz", + "integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz", + "integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz", + "integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz", + "integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz", + "integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz", + "integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz", + "integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz", + "integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -17602,6 +17724,109 @@ "node": ">= 0.6" } }, + "node_modules/micro": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/micro/-/micro-10.0.1.tgz", + "integrity": "sha512-9uwZSsUrqf6+4FLLpiPj5TRWQv5w5uJrJwsx1LR/TjqvQmKC1XnGQ9OHrFwR3cbZ46YqPqxO/XJCOpWnqMPw2Q==", + "dependencies": { + "arg": "4.1.0", + "content-type": "1.0.4", + "raw-body": "2.4.1" + }, + "bin": { + "micro": "dist/src/bin/micro.js" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/micro-cors": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/micro-cors/-/micro-cors-0.1.1.tgz", + "integrity": "sha512-6WqIahA5sbQR1Gjexp1VuWGFDKbZZleJb/gy1khNGk18a6iN1FdTcr3Q8twaxkV5H94RjxIBjirYbWCehpMBFw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/micro/node_modules/arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==" + }, + "node_modules/micro/node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/micro/node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micro/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micro/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micro/node_modules/raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/micro/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/micro/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micro/node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/micromark": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", @@ -24040,7 +24265,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -25003,126 +25227,6 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz", - "integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz", - "integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz", - "integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz", - "integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz", - "integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz", - "integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz", - "integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz", - "integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } }, "dependencies": { @@ -27168,6 +27272,54 @@ "integrity": "sha512-deUrbCXTMZ6ZhbOoloqecnUeNpUOupi8SE2tx4jPfNS9uyUR9zK4iXBvH65opVcA/9F5I/p8vDXSYbUlbmBjZg==", "optional": true }, + "@next/swc-darwin-x64": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz", + "integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==", + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz", + "integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==", + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz", + "integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==", + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz", + "integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==", + "optional": true + }, + "@next/swc-linux-x64-musl": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz", + "integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==", + "optional": true + }, + "@next/swc-win32-arm64-msvc": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz", + "integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==", + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz", + "integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz", + "integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==", + "optional": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -37937,6 +38089,81 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true }, + "micro": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/micro/-/micro-10.0.1.tgz", + "integrity": "sha512-9uwZSsUrqf6+4FLLpiPj5TRWQv5w5uJrJwsx1LR/TjqvQmKC1XnGQ9OHrFwR3cbZ46YqPqxO/XJCOpWnqMPw2Q==", + "requires": { + "arg": "4.1.0", + "content-type": "1.0.4", + "raw-body": "2.4.1" + }, + "dependencies": { + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + } + } + }, + "micro-cors": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/micro-cors/-/micro-cors-0.1.1.tgz", + "integrity": "sha512-6WqIahA5sbQR1Gjexp1VuWGFDKbZZleJb/gy1khNGk18a6iN1FdTcr3Q8twaxkV5H94RjxIBjirYbWCehpMBFw==" + }, "micromark": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", @@ -42572,8 +42799,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "unplugin": { "version": "1.4.0", @@ -43277,54 +43503,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" - }, - "@next/swc-darwin-x64": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz", - "integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==", - "optional": true - }, - "@next/swc-linux-arm64-gnu": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz", - "integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==", - "optional": true - }, - "@next/swc-linux-arm64-musl": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz", - "integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==", - "optional": true - }, - "@next/swc-linux-x64-gnu": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz", - "integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==", - "optional": true - }, - "@next/swc-linux-x64-musl": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz", - "integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==", - "optional": true - }, - "@next/swc-win32-arm64-msvc": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz", - "integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==", - "optional": true - }, - "@next/swc-win32-ia32-msvc": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz", - "integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==", - "optional": true - }, - "@next/swc-win32-x64-msvc": { - "version": "13.4.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz", - "integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==", - "optional": true } } } diff --git a/package.json b/package.json index bed1eec0572..77da5769c9f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "file-saver": "^2.0.5", "husky": "^8.0.3", "leaflet": "^1.9.4", + "micro": "^10.0.1", + "micro-cors": "^0.1.1", "mongoose": "^7.4.1", "next": "^13.4.12", "next-auth": "^4.22.3", diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index 97143d91751..de60277d4f1 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -3,6 +3,7 @@ import GithubProvider from "next-auth/providers/github"; import { ObjectId } from "bson"; import { serverEnv } from "@config/schemas/serverSchema"; +import stripe from "@config/stripe"; import DbAdapter from "./db-adapter"; import connectMongo from "@config/mongo"; import { Account, Profile, User } from "@models/index"; @@ -10,7 +11,6 @@ import { getAccountByProviderAccountId, associateProfileWithAccount, } from "../account/account"; -import { Stripe } from "stripe"; import logger from "@config/logger"; export const authOptions = { @@ -104,9 +104,6 @@ export const authOptions = { // Create a stripe customer for the user with their email address if (!user.stripeCustomerId) { logger.info("user stripe customer id not found for: ", user.email); - const stripe = new Stripe(serverEnv.STRIPE_SECRET_KEY, { - apiVersion: "2020-08-27", - }); const customer = await stripe.customers.create({ email: user.email, diff --git a/pages/api/stripe.js b/pages/api/stripe.js index 772791fcc97..9ff7de1d789 100644 --- a/pages/api/stripe.js +++ b/pages/api/stripe.js @@ -1,9 +1,8 @@ import { authOptions } from "./auth/[...nextauth]"; import { getServerSession } from "next-auth/next"; -import Stripe from "stripe"; import logger from "@config/logger"; -import { serverEnv } from "@config/schemas/serverSchema"; +import stripe from "@config/stripe"; import { clientEnv } from "@config/schemas/clientSchema"; export default async function handler(req, res) { @@ -18,10 +17,6 @@ export default async function handler(req, res) { return; } - const stripe = new Stripe(serverEnv.STRIPE_SECRET_KEY, { - apiVersion: "2020-08-27", - }); - const stripeSession = await stripe.checkout.sessions.create({ line_items: [ { diff --git a/pages/api/system/stripe.js b/pages/api/system/stripe.js index 6d869973ca0..f0ef5efbb3e 100644 --- a/pages/api/system/stripe.js +++ b/pages/api/system/stripe.js @@ -1,7 +1,17 @@ +import requestIp from "request-ip"; +import Cors from "micro-cors"; +import { buffer } from "micro"; + import connectMongo from "@config/mongo"; +import stripe from "@config/stripe"; +import { serverEnv } from "@config/schemas/serverSchema"; import logger from "@config/logger"; import { User } from "@models/index"; +const cors = Cors({ + allowMethods: ["POST", "HEAD"], +}); + /** * To test stripe webhooks locally, use the following steps: * 1. stripe listen --forward-to localhost:3000/api/system/stripe @@ -9,189 +19,241 @@ import { User } from "@models/index"; * 2b. stripe trigger payment_intent.payment_failed --add payment_intent:customer=cus_OPbkTYcj0NBk7g */ -export default async function handler(req, res) { +const stripeIps = [ + "3.18.12.63", + "3.130.192.231", + "13.235.14.237", + "13.235.122.149", + "18.211.135.69", + "35.154.171.200", + "52.15.183.38", + "54.88.130.119", + "54.88.130.237", + "54.187.174.169", + "54.187.205.235", + "54.187.216.72", +]; + +export const config = { + api: { + bodyParser: false, + }, +}; + +export async function webhookHandler(req, res) { if (req.method !== "POST") { return res.status(400).json({ error: "POST requests only" }); } - // TODO: verify stripe signature + // verify stripe ip + const requestingIp = requestIp.getClientIp(req); + if ( + serverEnv.NODE_ENV !== "development" && + ["preview", "production"].includes(serverEnv.NEXT_PUBLIC_VERCEL_ENV) && + !stripeIps.includes(requestingIp) + ) { + logger.error(`Invalid Stripe IP: ${requestingIp}`); + return res.status(401).json({ error: "Invalid IP" }); + } + + const buf = await buffer(req); + const sig = req.headers["stripe-signature"]; + + // verify signature + let event; + try { + event = stripe.webhooks.constructEvent( + buf.toString(), + sig, + serverEnv.STRIPE_WEBHOOK_SECRET + ); + } catch (e) { + logger.error(e, "Webhook Secret failed"); + return res + .status(401) + .send({ error: `Webhook Secret failed: ${e.message}` }); + } await connectMongo(); - const event = req.body; logger.info(`stripe: ${event.type}`); - // account.application.authorized - // account.application.deauthorized - // account.external_account.created - // account.external_account.updated - // account.updated - // application_fee.created - // application_fee.refund.updated - // application_fee.refunded - // balance.available - // billing_portal.configuration.created - // billing_portal.configuration.updated - // billing_portal.session.created - // capability.updated - // cash_balance.funds_available - // charge.captured - // charge.dispute.closed - // charge.dispute.created - // charge.dispute.funds_reinstated - // charge.dispute.funds_withdrawn - // charge.dispute.updated - // charge.expired - // charge.pending - // charge.refund.updated - // charge.refunded - // charge.updated - // checkout.session.completed - // checkout.session.expired - // coupon.created - // coupon.deleted - // coupon.updated - // credit_note.created - // credit_note.updated - // credit_note.voided - // customer_cash_balance_transaction.created - // customer.created - // customer.discount.created - // customer.discount.updated - // customer.source.created - // customer.source.deleted - // customer.source.expiring - // customer.source.updated - // customer.subscription.pending_update_applied - // customer.subscription.pending_update_expired - // customer.subscription.trial_will_end // TODO: show alert - // customer.subscription.updated // TODO: ??? - // customer.tax_id.created - // customer.tax_id.deleted - // customer.tax_id.updated - // customer.updated - // file.created - // financial_connections.account.created - // financial_connections.account.deactivated - // financial_connections.account.disconnected - // financial_connections.account.reactivated - // financial_connections.account.refreshed_balance - // identity.verification_session.canceled - // identity.verification_session.created - // identity.verification_session.processing - // identity.verification_session.redacted - // identity.verification_session.requires_input - // identity.verification_session.verified - // invoice.created - // invoice.deleted - // invoice.finalization_failed - // invoice.finalized - // invoice.marked_uncollectible - // invoice.payment_action_required - // invoice.payment_failed - // invoice.sent - // invoice.upcoming - // invoice.updated - // invoice.voided - // invoiceitem.created - // invoiceitem.deleted - // invoiceitem.updated - // issuing_authorization.created - // issuing_authorization.request - // issuing_authorization.updated - // issuing_card.created - // issuing_card.updated - // issuing_cardholder.created - // issuing_cardholder.updated - // issuing_dispute.closed - // issuing_dispute.created - // issuing_dispute.funds_reinstated - // issuing_dispute.submitted - // issuing_dispute.updated - // issuing_transaction.created - // issuing_transaction.updated - // mandate.updated - // payment_intent.amount_capturable_updated - // payment_intent.canceled - // payment_intent.created - // payment_intent.partially_funded - // payment_intent.processing - // payment_intent.requires_action - // payment_link.created - // payment_link.updated - // payment_method.attached - // payment_method.automatically_updated - // payment_method.detached - // payment_method.updated - // payout.canceled - // payout.created - // payout.failed - // payout.paid - // payout.reconciliation_completed - // payout.updated - // person.created - // person.deleted - // person.updated - // plan.created - // plan.deleted - // plan.updated - // price.created - // price.deleted - // price.updated - // product.created - // product.deleted - // product.updated - // promotion_code.created - // promotion_code.updated - // quote.accepted - // quote.canceled - // quote.created - // quote.finalized - // radar.early_fraud_warning.created - // radar.early_fraud_warning.updated - // recipient.created - // recipient.deleted - // recipient.updated - // refund.created - // refund.updated - // reporting.report_run.failed - // reporting.report_run.succeeded - // reporting.report_type.updated - // review.closed - // review.opened - // setup_intent.canceled - // setup_intent.created - // setup_intent.requires_action - // setup_intent.setup_failed - // setup_intent.succeeded - // sigma.scheduled_query_run.created - // sku.created - // sku.deleted - // sku.updated - // source.canceled - // source.chargeable - // source.failed - // source.mandate_notification - // source.refund_attributes_required - // source.transaction.created - // source.transaction.updated - // subscription_schedule.aborted - // subscription_schedule.canceled - // subscription_schedule.completed - // subscription_schedule.created - // subscription_schedule.expiring - // subscription_schedule.updated - // tax_rate.created - // tax_rate.updated - // tax.settings.updated - // terminal.reader.action_failed - // terminal.reader.action_succeeded - // topup.canceled - // topup.created - // topup.failed - // topup.reversed - // topup.succeeded - // transfer.created - // transfer.reversed - // transfer.updated + + /** + * Stripe events not used + * + * account.application.authorized + * account.application.deauthorized + * account.external_account.created + * account.external_account.updated + * account.updated + * application_fee.created + * application_fee.refund.updated + * application_fee.refunded + * balance.available + * billing_portal.configuration.created + * billing_portal.configuration.updated + * billing_portal.session.created + * capability.updated + * cash_balance.funds_available + * charge.captured + * charge.dispute.closed + * charge.dispute.created + * charge.dispute.funds_reinstated + * charge.dispute.funds_withdrawn + * charge.dispute.updated + * charge.expired + * charge.pending + * charge.refund.updated + * charge.refunded + * charge.updated + * checkout.session.completed + * checkout.session.expired + * coupon.created + * coupon.deleted + * coupon.updated + * credit_note.created + * credit_note.updated + * credit_note.voided + * customer_cash_balance_transaction.created + * customer.created + * customer.discount.created + * customer.discount.updated + * customer.source.created + * customer.source.deleted + * customer.source.expiring + * customer.source.updated + * customer.subscription.pending_update_applied + * customer.subscription.pending_update_expired + * customer.subscription.trial_will_end * TODO: show alert + * customer.subscription.updated * TODO: ??? + * customer.tax_id.created + * customer.tax_id.deleted + * customer.tax_id.updated + * customer.updated + * file.created + * financial_connections.account.created + * financial_connections.account.deactivated + * financial_connections.account.disconnected + * financial_connections.account.reactivated + * financial_connections.account.refreshed_balance + * identity.verification_session.canceled + * identity.verification_session.created + * identity.verification_session.processing + * identity.verification_session.redacted + * identity.verification_session.requires_input + * identity.verification_session.verified + * invoice.created + * invoice.deleted + * invoice.finalization_failed + * invoice.finalized + * invoice.marked_uncollectible + * invoice.payment_action_required + * invoice.payment_failed + * invoice.sent + * invoice.upcoming + * invoice.updated + * invoice.voided + * invoiceitem.created + * invoiceitem.deleted + * invoiceitem.updated + * issuing_authorization.created + * issuing_authorization.request + * issuing_authorization.updated + * issuing_card.created + * issuing_card.updated + * issuing_cardholder.created + * issuing_cardholder.updated + * issuing_dispute.closed + * issuing_dispute.created + * issuing_dispute.funds_reinstated + * issuing_dispute.submitted + * issuing_dispute.updated + * issuing_transaction.created + * issuing_transaction.updated + * mandate.updated + * payment_intent.amount_capturable_updated + * payment_intent.canceled + * payment_intent.created + * payment_intent.partially_funded + * payment_intent.processing + * payment_intent.requires_action + * payment_link.created + * payment_link.updated + * payment_method.attached + * payment_method.automatically_updated + * payment_method.detached + * payment_method.updated + * payout.canceled + * payout.created + * payout.failed + * payout.paid + * payout.reconciliation_completed + * payout.updated + * person.created + * person.deleted + * person.updated + * plan.created + * plan.deleted + * plan.updated + * price.created + * price.deleted + * price.updated + * product.created + * product.deleted + * product.updated + * promotion_code.created + * promotion_code.updated + * quote.accepted + * quote.canceled + * quote.created + * quote.finalized + * radar.early_fraud_warning.created + * radar.early_fraud_warning.updated + * recipient.created + * recipient.deleted + * recipient.updated + * refund.created + * refund.updated + * reporting.report_run.failed + * reporting.report_run.succeeded + * reporting.report_type.updated + * review.closed + * review.opened + * setup_intent.canceled + * setup_intent.created + * setup_intent.requires_action + * setup_intent.setup_failed + * setup_intent.succeeded + * sigma.scheduled_query_run.created + * sku.created + * sku.deleted + * sku.updated + * source.canceled + * source.chargeable + * source.failed + * source.mandate_notification + * source.refund_attributes_required + * source.transaction.created + * source.transaction.updated + * subscription_schedule.aborted + * subscription_schedule.canceled + * subscription_schedule.completed + * subscription_schedule.created + * subscription_schedule.expiring + * subscription_schedule.updated + * tax_rate.created + * tax_rate.updated + * tax.settings.updated + * terminal.reader.action_failed + * terminal.reader.action_succeeded + * topup.canceled + * topup.created + * topup.failed + * topup.reversed + * topup.succeeded + * transfer.created + * transfer.reversed + * transfer.updated + */ switch (event.type) { case "payment_intent.succeeded": @@ -202,7 +264,6 @@ export default async function handler(req, res) { case "customer.subscription.resumed": case "invoice.paid": case "invoice.payment_succeeded": - case "payment_intent.succeeded": // successful payment await User.findOneAndUpdate( { stripeCustomerId: event.data.object.customer }, @@ -218,7 +279,6 @@ export default async function handler(req, res) { case "customer.discount.deleted": case "customer.subscription.paused": case "invoice.payment_failed": - case "payment_intent.payment_failed": case "subscription_schedule.released": // failed payment await User.findOneAndUpdate( @@ -233,3 +293,5 @@ export default async function handler(req, res) { // Return a response to acknowledge receipt of the event return res.status(200).json({ received: true }); } + +export default cors(webhookHandler); diff --git a/pages/index.js b/pages/index.js index f03d4008a51..54fb376280f 100644 --- a/pages/index.js +++ b/pages/index.js @@ -31,7 +31,7 @@ export async function getStaticProps() { let alerts = structuredClone(config.alerts); if ( - process.env.NEXT_PUBLIC_VERCEL_ENV !== "production" && + serverEnv.NEXT_PUBLIC_VERCEL_ENV !== "production" && serverEnv.NODE_ENV === "development" && totalStats.users === 0 ) {