From afe23b57ab1b8a7875fb904d172817fdd79c620d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Jun 2022 20:30:51 -0400 Subject: [PATCH 001/171] Stripe start create subscription --- example.config.json | 15 ++ package-lock.json | 431 ++++++++++++++++++++++++++++++- package.json | 1 + src/database.js | 25 ++ src/routers/account.js | 153 ++++++++++- src/schema/pnid.js | 13 + src/server.js | 7 +- views/account/upgrade.handlebars | 50 ++++ 8 files changed, 689 insertions(+), 6 deletions(-) create mode 100644 src/database.js create mode 100644 src/schema/pnid.js create mode 100644 views/account/upgrade.handlebars diff --git a/example.config.json b/example.config.json index 6e28a91..3084b75 100644 --- a/example.config.json +++ b/example.config.json @@ -17,5 +17,20 @@ "role id" ] }, + "stripe": { + "secret_key": "sk_secret", + "webhook_secret": "whsec_secret" + }, + "database": { + "account": { + "address": "127.0.0.1", + "port": 27017, + "database_name": "pretendo", + "options": { + "useNewUrlParser": true, + "useUnifiedTopology": true + } + } + }, "aes_key": "hex key here" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7dd0879..1f193e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "gray-matter": "^4.0.3", "kaitai-struct": "^0.9.0", "marked": "^4.0.10", + "mongoose": "^6.4.0", "morgan": "^1.10.0", "trello": "^0.11.0", "uuid": "^8.3.2" @@ -278,6 +279,20 @@ "@types/node": "*" } }, + "node_modules/@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -737,6 +752,40 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, + "node_modules/bson": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz", + "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/bson/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", @@ -1129,6 +1178,14 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" }, + "node_modules/denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2252,6 +2309,11 @@ "node": ">= 0.4" } }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2584,6 +2646,11 @@ "resolved": "https://registry.npmjs.org/kaitai-struct/-/kaitai-struct-0.9.0.tgz", "integrity": "sha512-mfoBu9+IGqaY3ykG1TyAy9omOAZWtheqESQOvo/HKIQVTz+gRPVCNBnhjbO+8wAQ77RD33wYvLBWmITuXIviQg==" }, + "node_modules/kareem": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz", + "integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA==" + }, "node_modules/keyv": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", @@ -2694,6 +2761,12 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -2821,6 +2894,58 @@ "node": ">= 0.8.0" } }, + "node_modules/mongodb": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.7.0.tgz", + "integrity": "sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA==", + "dependencies": { + "bson": "^4.6.3", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.5.2", + "socks": "^2.6.2" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "saslprep": "^1.0.3" + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz", + "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongoose": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.4.0.tgz", + "integrity": "sha512-eBDrueap1Zx3qFrcYylTiqTFlL5iTEaYAxoDF1MSRdipwAzChQRMJve+vxHtxPhI2q5tmf9RYHfZwXfTUHPd3g==", + "dependencies": { + "bson": "^4.6.2", + "kareem": "2.4.1", + "mongodb": "4.7.0", + "mpath": "0.9.0", + "mquery": "4.0.3", + "ms": "2.1.3", + "sift": "16.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -2844,6 +2969,46 @@ "node": ">= 0.8" } }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", + "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3151,7 +3316,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, "engines": { "node": ">=6" } @@ -3377,6 +3541,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/sax": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", @@ -3515,6 +3691,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz", + "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ==" + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -3551,6 +3732,28 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3559,6 +3762,15 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -3844,6 +4056,17 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/trello": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/trello/-/trello-0.11.0.tgz", @@ -4047,6 +4270,26 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4358,6 +4601,20 @@ "@types/node": "*" } }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -4736,6 +4993,25 @@ "pako": "~1.0.5" } }, + "bson": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz", + "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==", + "requires": { + "buffer": "^5.6.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, "buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", @@ -5065,6 +5341,11 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" }, + "denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -5931,6 +6212,11 @@ "side-channel": "^1.0.4" } }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6156,6 +6442,11 @@ "resolved": "https://registry.npmjs.org/kaitai-struct/-/kaitai-struct-0.9.0.tgz", "integrity": "sha512-mfoBu9+IGqaY3ykG1TyAy9omOAZWtheqESQOvo/HKIQVTz+gRPVCNBnhjbO+8wAQ77RD33wYvLBWmITuXIviQg==" }, + "kareem": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz", + "integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA==" + }, "keyv": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", @@ -6245,6 +6536,12 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6344,6 +6641,48 @@ "xtend": "^4.0.0" } }, + "mongodb": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.7.0.tgz", + "integrity": "sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA==", + "requires": { + "bson": "^4.6.3", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.5.2", + "saslprep": "^1.0.3", + "socks": "^2.6.2" + } + }, + "mongodb-connection-string-url": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz", + "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "mongoose": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.4.0.tgz", + "integrity": "sha512-eBDrueap1Zx3qFrcYylTiqTFlL5iTEaYAxoDF1MSRdipwAzChQRMJve+vxHtxPhI2q5tmf9RYHfZwXfTUHPd3g==", + "requires": { + "bson": "^4.6.2", + "kareem": "2.4.1", + "mongodb": "4.7.0", + "mpath": "0.9.0", + "mquery": "4.0.3", + "ms": "2.1.3", + "sift": "16.0.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -6363,6 +6702,34 @@ } } }, + "mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==" + }, + "mquery": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", + "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", + "requires": { + "debug": "4.x" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6605,8 +6972,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.7.0", @@ -6784,6 +7150,15 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "sax": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", @@ -6897,6 +7272,11 @@ "object-inspect": "^1.9.0" } }, + "sift": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz", + "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ==" + }, "simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -6913,11 +7293,34 @@ "is-fullwidth-code-point": "^3.0.0" } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -7151,6 +7554,14 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, "trello": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/trello/-/trello-0.11.0.tgz", @@ -7311,6 +7722,20 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 50df863..7a11db9 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "gray-matter": "^4.0.3", "kaitai-struct": "^0.9.0", "marked": "^4.0.10", + "mongoose": "^6.4.0", "morgan": "^1.10.0", "trello": "^0.11.0", "uuid": "^8.3.2" diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..eb664aa --- /dev/null +++ b/src/database.js @@ -0,0 +1,25 @@ +const mongoose = require('mongoose'); +const PNIDSchema = require('./schema/pnid'); +const config = require('../config.json'); + +const accountServerConfig = config.database.account; +const accountServerURI = `mongodb://${accountServerConfig.address}:${accountServerConfig.port}/${accountServerConfig.database_name}`; +let accountServerDBConnection; +let PNID; + +async function connect() { + accountServerDBConnection = await mongoose.createConnection(accountServerURI, accountServerConfig.options); + accountServerDBConnection.on('error', console.error.bind(console, 'Mongoose connection error:')); + accountServerDBConnection.on('close', () => { + accountServerDBConnection.removeAllListeners(); + }); + + PNID = accountServerDBConnection.model('PNID', PNIDSchema); + + module.exports.PNID = PNID; +} + +module.exports = { + connect, + PNID +}; \ No newline at end of file diff --git a/src/routers/account.js b/src/routers/account.js index 1e358b3..e1f13a8 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -1,10 +1,15 @@ -const { Router } = require('express'); +const express = require('express'); const crypto = require('crypto'); const DiscordOauth2 = require('discord-oauth2'); const { v4: uuidv4 } = require('uuid'); const AdmZip = require('adm-zip'); +const Stripe = require('stripe'); +const database = require('../database'); const util = require('../util'); const config = require('../../config.json'); + +const { Router } = express; +const stripe = new Stripe(config.stripe.secret_key); const router = new Router(); const aesKey = Buffer.from(config.aes_key, 'hex'); @@ -494,4 +499,150 @@ router.get('/miieditor', async (request, response) => { }); }); +router.get('/upgrade', async (request, response) => { + // Verify the user is logged in + if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) { + return response.redirect('/account/login'); + } + + const renderData = { + layout: 'main', + locale: util.getLocale(request.locale.region, request.locale.language), + localeString: request.locale.toString(), + error: request.cookies.error + }; + + const { data: prices } = await stripe.prices.list(); + const { data: products } = await stripe.products.list(); + + renderData.tiers = products.filter(product => product.active).map(product => { + const price = prices.find(price => price.product === product.id); + + return { + price_id: price.id, + thumbnail: product.images[0], + name: product.name, + description: product.description, + price: (price.unit_amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }), + }; + }); + + response.render('account/upgrade', renderData); +}); + +router.post('/checkout/:priceId', async (request, response) => { + // Verify the user is logged in + if (!request.cookies.access_token || !request.cookies.refresh_token || !request.cookies.ph) { + return response.redirect('/account/login'); + } + + // Attempt to get user data + let apiResponse = await util.apiGetRequest('/v1/user', { + 'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}` + }); + + if (apiResponse.statusCode !== 200) { + // Assume expired, refresh and retry request + apiResponse = await util.apiPostGetRequest('/v1/login', {}, { + refresh_token: request.cookies.refresh_token, + grant_type: 'refresh_token' + }); + + if (apiResponse.statusCode !== 200) { + return response.redirect('/account/login'); + } + + const tokens = apiResponse.body; + + response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' }); + response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' }); + response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' }); + + apiResponse = await util.apiGetRequest('/v1/user', { + 'Authorization': `${tokens.token_type} ${tokens.access_token}` + }); + } + + // If still failed, something went horribly wrong + if (apiResponse.statusCode !== 200) { + return response.redirect('/account/login'); + } + + // Set user account info to render data + const account = apiResponse.body; + const pid = account.pid; + + let customer; + const { data: searchResults } = await await stripe.customers.search({ + query: `metadata['pnid_pid']:'${pid}'` + }); + + if (searchResults.length !== 0) { + customer = searchResults[0]; + } else { + customer = await stripe.customers.create({ + metadata: { + pnid_pid: pid + } + }); + } + + const priceId = request.params.priceId; + + const session = await stripe.checkout.sessions.create({ + line_items: [ + { + price: priceId, + quantity: 1, + }, + ], + customer: customer.id, + mode: 'subscription', + success_url: `${config.http.base_url}/account?upgrade_success=true`, + cancel_url: `${config.http.base_url}/account?upgrade_success=false` + }); + + response.redirect(303, session.url); +}); + +router.post('/stripe-wh', express.raw({ type: 'application/json' }), async (request, response) => { + const stripeSignature = request.headers['stripe-signature']; + let event; + + try { + event = stripe.webhooks.constructEvent(request.body, stripeSignature, config.stripe.webhook_secret); + } catch (err) { + return response.status(400).send(`Webhook Error: ${err.message}`); + } + + if (event.type === 'customer.subscription.updated' || event.type === 'customer.subscription.deleted') { + const subscription = event.data.object; + const product = await stripe.products.retrieve(subscription.plan.product); + const customer = await stripe.customers.retrieve(subscription.customer); + const pid = Number(customer.metadata.pnid_pid); + const pnid = await database.PNID.findOne({ pid }); + + if (product.metadata.beta === 'true') { + switch (subscription.status) { + case 'active': + pnid.access_level = 1; + break; + + case 'canceled': // Subscription was cancled + case 'unpaid': // User missed too many payments + pnid.access_level = 0; + break; + + default: + break; + } + + await pnid.save(); + } + } + + response.json({ received: true }); +}); + + module.exports = router; \ No newline at end of file diff --git a/src/schema/pnid.js b/src/schema/pnid.js new file mode 100644 index 0000000..4ad6166 --- /dev/null +++ b/src/schema/pnid.js @@ -0,0 +1,13 @@ +const { Schema } = require('mongoose'); + +// Only define what we will be using +const PNIDSchema = new Schema({ + pid: { + type: Number, + unique: true + }, + server_access_level: String, + access_level: Number, +}); + +module.exports = PNIDSchema; \ No newline at end of file diff --git a/src/server.js b/src/server.js index 78b3a93..b7fceb0 100644 --- a/src/server.js +++ b/src/server.js @@ -6,6 +6,7 @@ const morgan = require('morgan'); const expressLocale = require('express-locale'); const cookieParser = require('cookie-parser'); const logger = require('./logger'); +const database = require('./database'); const util = require('./util'); const config = require('../config.json'); @@ -156,6 +157,8 @@ app.engine('handlebars', handlebars({ app.set('view engine', 'handlebars'); logger.info('Starting server'); -app.listen(port, () => { - logger.success(`Server listening on http://localhost:${port}`); +database.connect().then(() => { + app.listen(port, () => { + logger.success(`Server listening on http://localhost:${port}`); + }); }); diff --git a/views/account/upgrade.handlebars b/views/account/upgrade.handlebars new file mode 100644 index 0000000..5a45dec --- /dev/null +++ b/views/account/upgrade.handlebars @@ -0,0 +1,50 @@ + + +
+ +
+ + \ No newline at end of file From 895e090cef78800c09d91e71697855094e04e0bf Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Jun 2022 20:51:03 -0400 Subject: [PATCH 002/171] forgot stripe module --- package-lock.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 47 insertions(+) diff --git a/package-lock.json b/package-lock.json index 1f193e5..f9d23c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "marked": "^4.0.10", "mongoose": "^6.4.0", "morgan": "^1.10.0", + "stripe": "^9.9.0", "trello": "^0.11.0", "uuid": "^8.3.2" }, @@ -3950,6 +3951,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripe": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-9.9.0.tgz", + "integrity": "sha512-UBuHzKoEaHnTv2h65cIcYE0vse7at8CFlwjl/KS8I7piekMKa1lRTA5R2O4eXMp5wllWQbPF/UoLzTfjjcdBqA==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.10.3" + }, + "engines": { + "node": "^8.1 || >=10.*" + } + }, + "node_modules/stripe/node_modules/qs": { + "version": "6.10.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.5.tgz", + "integrity": "sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/subarg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", @@ -7462,6 +7489,25 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "stripe": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-9.9.0.tgz", + "integrity": "sha512-UBuHzKoEaHnTv2h65cIcYE0vse7at8CFlwjl/KS8I7piekMKa1lRTA5R2O4eXMp5wllWQbPF/UoLzTfjjcdBqA==", + "requires": { + "@types/node": ">=8.1.0", + "qs": "^6.10.3" + }, + "dependencies": { + "qs": { + "version": "6.10.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.5.tgz", + "integrity": "sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==", + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, "subarg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", diff --git a/package.json b/package.json index 7a11db9..2a1fd25 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "marked": "^4.0.10", "mongoose": "^6.4.0", "morgan": "^1.10.0", + "stripe": "^9.9.0", "trello": "^0.11.0", "uuid": "^8.3.2" }, From 92bf38c60fc47a4e975eaeebfc7e96efeab28928 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 20 Jun 2022 17:11:26 -0400 Subject: [PATCH 003/171] Update PNID through direct query --- src/routers/account.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/routers/account.js b/src/routers/account.js index e1f13a8..0828b5e 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -620,24 +620,25 @@ router.post('/stripe-wh', express.raw({ type: 'application/json' }), async (requ const product = await stripe.products.retrieve(subscription.plan.product); const customer = await stripe.customers.retrieve(subscription.customer); const pid = Number(customer.metadata.pnid_pid); - const pnid = await database.PNID.findOne({ pid }); + + const updateData = {}; if (product.metadata.beta === 'true') { switch (subscription.status) { case 'active': - pnid.access_level = 1; + updateData.access_level = 1; break; case 'canceled': // Subscription was cancled case 'unpaid': // User missed too many payments - pnid.access_level = 0; + updateData.access_level = 0; break; default: break; } - await pnid.save(); + await database.PNID.updateOne({ pid }, { $set: updateData }, { upsert: true }).exec(); } } From 3183e45fb2f58d5ef2c272d674e0644d9f37601c Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 20 Jun 2022 17:13:27 -0400 Subject: [PATCH 004/171] await Mongo connection --- src/database.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/database.js b/src/database.js index eb664aa..10b608c 100644 --- a/src/database.js +++ b/src/database.js @@ -14,6 +14,8 @@ async function connect() { accountServerDBConnection.removeAllListeners(); }); + await accountServerDBConnection.asPromise(); + PNID = accountServerDBConnection.model('PNID', PNIDSchema); module.exports.PNID = PNID; From ad60c7083173766862b46669593fa5c38db1e692 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 20 Jun 2022 17:16:28 -0400 Subject: [PATCH 005/171] Use a full Mongo URI instead of address and port in config --- example.config.json | 3 +-- src/database.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/example.config.json b/example.config.json index 3084b75..39ff631 100644 --- a/example.config.json +++ b/example.config.json @@ -23,8 +23,7 @@ }, "database": { "account": { - "address": "127.0.0.1", - "port": 27017, + "uri": "mongodb://127.0.0.1:27017", "database_name": "pretendo", "options": { "useNewUrlParser": true, diff --git a/src/database.js b/src/database.js index 10b608c..00e2f48 100644 --- a/src/database.js +++ b/src/database.js @@ -3,12 +3,11 @@ const PNIDSchema = require('./schema/pnid'); const config = require('../config.json'); const accountServerConfig = config.database.account; -const accountServerURI = `mongodb://${accountServerConfig.address}:${accountServerConfig.port}/${accountServerConfig.database_name}`; let accountServerDBConnection; let PNID; async function connect() { - accountServerDBConnection = await mongoose.createConnection(accountServerURI, accountServerConfig.options); + accountServerDBConnection = await mongoose.createConnection(accountServerConfig.uri, accountServerConfig.options); accountServerDBConnection.on('error', console.error.bind(console, 'Mongoose connection error:')); accountServerDBConnection.on('close', () => { accountServerDBConnection.removeAllListeners(); From 2aeca356bfdf2937ccf3da7367fc99cc92bc4522 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 21 Jun 2022 17:56:48 -0400 Subject: [PATCH 006/171] canceled not cancled --- src/routers/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routers/account.js b/src/routers/account.js index 0828b5e..57cc714 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -629,7 +629,7 @@ router.post('/stripe-wh', express.raw({ type: 'application/json' }), async (requ updateData.access_level = 1; break; - case 'canceled': // Subscription was cancled + case 'canceled': // Subscription was canceled case 'unpaid': // User missed too many payments updateData.access_level = 0; break; From b30402b9041df5439b0d5b4f93d48da02bff76ac Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 21 Jun 2022 17:57:23 -0400 Subject: [PATCH 007/171] only need one await --- src/routers/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routers/account.js b/src/routers/account.js index 57cc714..7e80884 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -573,7 +573,7 @@ router.post('/checkout/:priceId', async (request, response) => { const pid = account.pid; let customer; - const { data: searchResults } = await await stripe.customers.search({ + const { data: searchResults } = await stripe.customers.search({ query: `metadata['pnid_pid']:'${pid}'` }); From 0241fb1f103c36e72833a7f82d8f51b311120781 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 21 Jun 2022 18:00:33 -0400 Subject: [PATCH 008/171] Added perks array to tiers --- src/routers/account.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/routers/account.js b/src/routers/account.js index 7e80884..f3b0fda 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -517,12 +517,22 @@ router.get('/upgrade', async (request, response) => { renderData.tiers = products.filter(product => product.active).map(product => { const price = prices.find(price => price.product === product.id); + const perks = []; + + if (product.metadata.discord_read === 'true') { + perks.push('Read-only access to select dev channels on Discord'); + } + + if (product.metadata.beta === 'true') { + perks.push('Access the beta servers'); + } return { price_id: price.id, thumbnail: product.images[0], name: product.name, description: product.description, + perks, price: (price.unit_amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }), }; }); From 027321911b333e6f628eb2c2526030a37afe7011 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 24 Jun 2022 15:25:41 -0400 Subject: [PATCH 009/171] Check if Stripe customer has PNID and if PNID exists --- example.config.json | 4 ++++ package-lock.json | 42 ++++++++++++++++++++++++++++++++++ package.json | 1 + src/routers/account.js | 52 +++++++++++++++++++++++++++++++++++++++++- src/schema/pnid.js | 8 +++++++ 5 files changed, 106 insertions(+), 1 deletion(-) diff --git a/example.config.json b/example.config.json index 39ff631..b4b525b 100644 --- a/example.config.json +++ b/example.config.json @@ -31,5 +31,9 @@ } } }, + "gmail": { + "user": "email@gmail.com", + "pass": "app-password" + }, "aes_key": "hex key here" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f9d23c9..17e0fea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "express-handlebars": "^5.3.1", "express-locale": "^2.0.0", "fs-extra": "^9.1.0", + "gmail-send": "^1.8.14", "got": "^11.8.2", "gray-matter": "^4.0.3", "kaitai-struct": "^0.9.0", @@ -1953,6 +1954,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gmail-send": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/gmail-send/-/gmail-send-1.8.14.tgz", + "integrity": "sha512-hc+4Ej7ZJtw0G5sync10pmWkpPXIabkQ+p/a92lPPTXni3ChEU9sR2wxOvK6Hx+5Ou+2m9h1cVffWEgtR6Gzkw==", + "dependencies": { + "lodash": "^4.17.21", + "nodemailer": "^6.6.5" + } + }, "node_modules/got": { "version": "11.8.2", "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", @@ -2690,6 +2700,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -3034,6 +3049,14 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, + "node_modules/nodemailer": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.5.tgz", + "integrity": "sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -5983,6 +6006,15 @@ "type-fest": "^0.20.2" } }, + "gmail-send": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/gmail-send/-/gmail-send-1.8.14.tgz", + "integrity": "sha512-hc+4Ej7ZJtw0G5sync10pmWkpPXIabkQ+p/a92lPPTXni3ChEU9sR2wxOvK6Hx+5Ou+2m9h1cVffWEgtR6Gzkw==", + "requires": { + "lodash": "^4.17.21", + "nodemailer": "^6.6.5" + } + }, "got": { "version": "11.8.2", "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", @@ -6506,6 +6538,11 @@ "type-check": "~0.4.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -6778,6 +6815,11 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, + "nodemailer": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.5.tgz", + "integrity": "sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==" + }, "normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", diff --git a/package.json b/package.json index 2a1fd25..c9531ae 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "express-handlebars": "^5.3.1", "express-locale": "^2.0.0", "fs-extra": "^9.1.0", + "gmail-send": "^1.8.14", "got": "^11.8.2", "gray-matter": "^4.0.3", "kaitai-struct": "^0.9.0", diff --git a/src/routers/account.js b/src/routers/account.js index f3b0fda..2ecd31d 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -4,13 +4,16 @@ const DiscordOauth2 = require('discord-oauth2'); const { v4: uuidv4 } = require('uuid'); const AdmZip = require('adm-zip'); const Stripe = require('stripe'); +const gmail = require('gmail-send'); const database = require('../database'); const util = require('../util'); const config = require('../../config.json'); -const { Router } = express; const stripe = new Stripe(config.stripe.secret_key); const router = new Router(); +const sendGmail = gmail(config.gmail); + +const { Router } = express; const aesKey = Buffer.from(config.aes_key, 'hex'); // Create OAuth client @@ -591,10 +594,19 @@ router.post('/checkout/:priceId', async (request, response) => { customer = searchResults[0]; } else { customer = await stripe.customers.create({ + email: account.email.address, metadata: { pnid_pid: pid } }); + + await database.PNID.updateOne({ pid }, { $set: { + connections: { + stripe: { + customer_id: customer.id + } + } + } }, { upsert: true }).exec(); } const priceId = request.params.priceId; @@ -629,7 +641,45 @@ router.post('/stripe-wh', express.raw({ type: 'application/json' }), async (requ const subscription = event.data.object; const product = await stripe.products.retrieve(subscription.plan.product); const customer = await stripe.customers.retrieve(subscription.customer); + + if (!customer.metadata.pnid_pid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { + // No PNID PID linked to customer. Abort and refund! + await stripe.subscriptions.del(subscription.id); + + const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); + await stripe.refunds.create({ + payment_intent: invoice.payment_intent + }); + + await sendGmail({ + to: customer.email, + subject: 'Pretendo Subscription Failed - No Linked PNID', + text: `Your recent subscription to Pretendo Network has failed.\nThis is due to no PNID PID being linked to the Stripe customer account used. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}` + }); + + return response.json({ received: true }); + } + const pid = Number(customer.metadata.pnid_pid); + const pnid = await database.PNID.findOne({ pid }); + + if (!pnid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { + // PNID does not exist. Abort and refund! + await stripe.subscriptions.del(subscription.id); + + const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); + await stripe.refunds.create({ + payment_intent: invoice.payment_intent + }); + + await sendGmail({ + to: customer.email, + subject: 'Pretendo Subscription Failed - PNID Not Found', + text: `Your recent subscription to Pretendo Network has failed.\nThis is due to the provided PNID not being found. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}\nPNID PID: ${pid}` + }); + + return response.json({ received: true }); + } const updateData = {}; diff --git a/src/schema/pnid.js b/src/schema/pnid.js index 4ad6166..8e87a3f 100644 --- a/src/schema/pnid.js +++ b/src/schema/pnid.js @@ -8,6 +8,14 @@ const PNIDSchema = new Schema({ }, server_access_level: String, access_level: Number, + connections: { + stripe: { + customer_id: String, + price_id: String, + tier_level: Number, + latest_webhook_timestamp: Number + } + } }); module.exports = PNIDSchema; \ No newline at end of file From e622a1e7599e9fd3dad08cbcd2d2309e694b27b9 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 24 Jun 2022 19:28:28 -0400 Subject: [PATCH 010/171] self heal from missed webhooks --- src/database.js | 3 +- src/routers/account.js | 73 +++------------------------------------ src/server.js | 13 +++++-- src/util.js | 77 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 73 deletions(-) diff --git a/src/database.js b/src/database.js index 00e2f48..49f954e 100644 --- a/src/database.js +++ b/src/database.js @@ -3,11 +3,12 @@ const PNIDSchema = require('./schema/pnid'); const config = require('../config.json'); const accountServerConfig = config.database.account; +const { uri, database, options } = accountServerConfig; let accountServerDBConnection; let PNID; async function connect() { - accountServerDBConnection = await mongoose.createConnection(accountServerConfig.uri, accountServerConfig.options); + accountServerDBConnection = await mongoose.createConnection(`${uri}/${database}`, options); accountServerDBConnection.on('error', console.error.bind(console, 'Mongoose connection error:')); accountServerDBConnection.on('close', () => { accountServerDBConnection.removeAllListeners(); diff --git a/src/routers/account.js b/src/routers/account.js index 2ecd31d..ebc2f29 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -4,18 +4,16 @@ const DiscordOauth2 = require('discord-oauth2'); const { v4: uuidv4 } = require('uuid'); const AdmZip = require('adm-zip'); const Stripe = require('stripe'); -const gmail = require('gmail-send'); const database = require('../database'); const util = require('../util'); const config = require('../../config.json'); -const stripe = new Stripe(config.stripe.secret_key); -const router = new Router(); -const sendGmail = gmail(config.gmail); - const { Router } = express; const aesKey = Buffer.from(config.aes_key, 'hex'); +const stripe = new Stripe(config.stripe.secret_key); +const router = new Router(); + // Create OAuth client const discordOAuth = new DiscordOauth2({ clientId: config.discord.client_id, @@ -637,70 +635,7 @@ router.post('/stripe-wh', express.raw({ type: 'application/json' }), async (requ return response.status(400).send(`Webhook Error: ${err.message}`); } - if (event.type === 'customer.subscription.updated' || event.type === 'customer.subscription.deleted') { - const subscription = event.data.object; - const product = await stripe.products.retrieve(subscription.plan.product); - const customer = await stripe.customers.retrieve(subscription.customer); - - if (!customer.metadata.pnid_pid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { - // No PNID PID linked to customer. Abort and refund! - await stripe.subscriptions.del(subscription.id); - - const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); - await stripe.refunds.create({ - payment_intent: invoice.payment_intent - }); - - await sendGmail({ - to: customer.email, - subject: 'Pretendo Subscription Failed - No Linked PNID', - text: `Your recent subscription to Pretendo Network has failed.\nThis is due to no PNID PID being linked to the Stripe customer account used. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}` - }); - - return response.json({ received: true }); - } - - const pid = Number(customer.metadata.pnid_pid); - const pnid = await database.PNID.findOne({ pid }); - - if (!pnid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { - // PNID does not exist. Abort and refund! - await stripe.subscriptions.del(subscription.id); - - const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); - await stripe.refunds.create({ - payment_intent: invoice.payment_intent - }); - - await sendGmail({ - to: customer.email, - subject: 'Pretendo Subscription Failed - PNID Not Found', - text: `Your recent subscription to Pretendo Network has failed.\nThis is due to the provided PNID not being found. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}\nPNID PID: ${pid}` - }); - - return response.json({ received: true }); - } - - const updateData = {}; - - if (product.metadata.beta === 'true') { - switch (subscription.status) { - case 'active': - updateData.access_level = 1; - break; - - case 'canceled': // Subscription was canceled - case 'unpaid': // User missed too many payments - updateData.access_level = 0; - break; - - default: - break; - } - - await database.PNID.updateOne({ pid }, { $set: updateData }, { upsert: true }).exec(); - } - } + await util.handleStripeEvent(event); response.json({ received: true }); }); diff --git a/src/server.js b/src/server.js index b7fceb0..9668b1e 100644 --- a/src/server.js +++ b/src/server.js @@ -5,15 +5,16 @@ const handlebars = require('express-handlebars'); const morgan = require('morgan'); const expressLocale = require('express-locale'); const cookieParser = require('cookie-parser'); +const Stripe = require('stripe'); const logger = require('./logger'); const database = require('./database'); const util = require('./util'); const config = require('../config.json'); - const defaultLocale = require('../locales/US_en.json'); const { http: { port } } = config; const app = express(); +const stripe = new Stripe(config.stripe.secret_key); logger.info('Setting up Middleware'); app.use(morgan('dev')); @@ -158,7 +159,15 @@ app.set('view engine', 'handlebars'); logger.info('Starting server'); database.connect().then(() => { - app.listen(port, () => { + app.listen(port, async () => { + const events = await stripe.events.list({ + delivery_success: false // failed webhooks + }); + + for (const event of events.data) { + await util.handleStripeEvent(event); + } + logger.success(`Server listening on http://localhost:${port}`); }); }); diff --git a/src/util.js b/src/util.js index 5ad2251..a1ce7a3 100644 --- a/src/util.js +++ b/src/util.js @@ -1,7 +1,14 @@ const fs = require('fs-extra'); const got = require('got'); const crypto = require('crypto'); +const gmail = require('gmail-send'); +const Stripe = require('stripe'); +const database = require('./database'); const logger = require('./logger'); +const config = require('../config.json'); + +const stripe = new Stripe(config.stripe.secret_key); +const sendGmail = gmail(config.gmail); function fullUrl(request) { return `${request.protocol}://${request.hostname}${request.originalUrl}`; @@ -68,11 +75,79 @@ function nintendoPasswordHash(password, pid) { return hashed; } +async function handleStripeEvent(event) { + if (event.type === 'customer.subscription.updated' || event.type === 'customer.subscription.deleted') { + const subscription = event.data.object; + const product = await stripe.products.retrieve(subscription.plan.product); + const customer = await stripe.customers.retrieve(subscription.customer); + + if (!customer.metadata.pnid_pid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { + // No PNID PID linked to customer. Abort and refund! + await stripe.subscriptions.del(subscription.id); + + const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); + await stripe.refunds.create({ + payment_intent: invoice.payment_intent + }); + + await sendGmail({ + to: customer.email, + subject: 'Pretendo Subscription Failed - No Linked PNID', + text: `Your recent subscription to Pretendo Network has failed.\nThis is due to no PNID PID being linked to the Stripe customer account used. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}` + }); + + return; + } + + const pid = Number(customer.metadata.pnid_pid); + const pnid = await database.PNID.findOne({ pid }); + + if (!pnid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { + // PNID does not exist. Abort and refund! + await stripe.subscriptions.del(subscription.id); + + const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); + await stripe.refunds.create({ + payment_intent: invoice.payment_intent + }); + + await sendGmail({ + to: customer.email, + subject: 'Pretendo Subscription Failed - PNID Not Found', + text: `Your recent subscription to Pretendo Network has failed.\nThis is due to the provided PNID not being found. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}\nPNID PID: ${pid}` + }); + + return; + } + + const updateData = {}; + + if (product.metadata.beta === 'true') { + switch (subscription.status) { + case 'active': + updateData.access_level = 1; + break; + + case 'canceled': // Subscription was canceled + case 'unpaid': // User missed too many payments + updateData.access_level = 0; + break; + + default: + break; + } + + await database.PNID.updateOne({ pid }, { $set: updateData }, { upsert: true }).exec(); + } + } +} + module.exports = { fullUrl, getLocale, apiGetRequest, apiPostGetRequest, apiDeleteGetRequest, - nintendoPasswordHash + nintendoPasswordHash, + handleStripeEvent }; \ No newline at end of file From f32ea19630623f421c33aaac44f49993ee0f048c Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 24 Jun 2022 19:39:21 -0400 Subject: [PATCH 011/171] Check for older webhooks and ignore --- src/routers/account.js | 10 ++++++---- src/util.js | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/routers/account.js b/src/routers/account.js index ebc2f29..79ea47b 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -597,15 +597,17 @@ router.post('/checkout/:priceId', async (request, response) => { pnid_pid: pid } }); + } - await database.PNID.updateOne({ pid }, { $set: { + await database.PNID.updateOne({ pid }, { + $set: { connections: { stripe: { - customer_id: customer.id + customer_id: customer.id // ensure PNID always has latest customer ID } } - } }, { upsert: true }).exec(); - } + } + }, { upsert: true }).exec(); const priceId = request.params.priceId; diff --git a/src/util.js b/src/util.js index a1ce7a3..6e0b9be 100644 --- a/src/util.js +++ b/src/util.js @@ -102,6 +102,13 @@ async function handleStripeEvent(event) { const pid = Number(customer.metadata.pnid_pid); const pnid = await database.PNID.findOne({ pid }); + const latestWebhookTimestamp = pnid.get('connections.stripe.latest_webhook_timestamp'); + + if (latestWebhookTimestamp && latestWebhookTimestamp > event.created) { + // Do nothing, this webhook is older than the latest seen + return; + } + if (!pnid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { // PNID does not exist. Abort and refund! await stripe.subscriptions.del(subscription.id); @@ -120,7 +127,15 @@ async function handleStripeEvent(event) { return; } - const updateData = {}; + const updateData = { + connections: { + stripe: { + price_id: subscription.plan.id, + tier_level: Number(product.metadata.tier_level || 0), + latest_webhook_timestamp: event.created + } + } + }; if (product.metadata.beta === 'true') { switch (subscription.status) { From 149456059a6f16ce93f2c1d41191e069218eddcb Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 24 Jun 2022 19:44:45 -0400 Subject: [PATCH 012/171] only change access level if not staff member --- src/util.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/util.js b/src/util.js index 6e0b9be..78c87be 100644 --- a/src/util.js +++ b/src/util.js @@ -140,12 +140,16 @@ async function handleStripeEvent(event) { if (product.metadata.beta === 'true') { switch (subscription.status) { case 'active': - updateData.access_level = 1; + if (pnid.access_level < 2) { // only change access level if not staff member + updateData.access_level = 1; + } break; case 'canceled': // Subscription was canceled case 'unpaid': // User missed too many payments - updateData.access_level = 0; + if (pnid.access_level < 2) { // only change access level if not staff member + updateData.access_level = 0; + } break; default: From 3af4f9160a33fa5767d816e3121f78892d153ee4 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 24 Jun 2022 19:45:55 -0400 Subject: [PATCH 013/171] Fixed example config --- example.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.config.json b/example.config.json index b4b525b..4987193 100644 --- a/example.config.json +++ b/example.config.json @@ -24,7 +24,7 @@ "database": { "account": { "uri": "mongodb://127.0.0.1:27017", - "database_name": "pretendo", + "database": "pretendo", "options": { "useNewUrlParser": true, "useUnifiedTopology": true From b1671b41044314695b4771ec5702965f9ac4b5d8 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 24 Jun 2022 19:50:44 -0400 Subject: [PATCH 014/171] order tiers by level on upgrade page --- src/routers/account.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/routers/account.js b/src/routers/account.js index 79ea47b..10bcba8 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -516,27 +516,30 @@ router.get('/upgrade', async (request, response) => { const { data: prices } = await stripe.prices.list(); const { data: products } = await stripe.products.list(); - renderData.tiers = products.filter(product => product.active).map(product => { - const price = prices.find(price => price.product === product.id); - const perks = []; + renderData.tiers = products + .filter(product => product.active) + .sort((a, b) => +a.metadata.tier_level - +b.metadata.tier_level) + .map(product => { + const price = prices.find(price => price.product === product.id); + const perks = []; - if (product.metadata.discord_read === 'true') { - perks.push('Read-only access to select dev channels on Discord'); - } + if (product.metadata.discord_read === 'true') { + perks.push('Read-only access to select dev channels on Discord'); + } - if (product.metadata.beta === 'true') { - perks.push('Access the beta servers'); - } + if (product.metadata.beta === 'true') { + perks.push('Access the beta servers'); + } - return { - price_id: price.id, - thumbnail: product.images[0], - name: product.name, - description: product.description, - perks, - price: (price.unit_amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }), - }; - }); + return { + price_id: price.id, + thumbnail: product.images[0], + name: product.name, + description: product.description, + perks, + price: (price.unit_amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }), + }; + }); response.render('account/upgrade', renderData); }); From 4f9f1919f255554e49403d4b0b1cff0cdc828894 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 25 Jun 2022 08:40:26 -0400 Subject: [PATCH 015/171] Added better logging to util.js --- src/util.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/util.js b/src/util.js index 78c87be..c5b10a9 100644 --- a/src/util.js +++ b/src/util.js @@ -83,6 +83,8 @@ async function handleStripeEvent(event) { if (!customer.metadata.pnid_pid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { // No PNID PID linked to customer. Abort and refund! + logger.error(`Stripe user ${customer.id} has no PNID linked! Refunding order`); + await stripe.subscriptions.del(subscription.id); const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); @@ -90,11 +92,16 @@ async function handleStripeEvent(event) { payment_intent: invoice.payment_intent }); - await sendGmail({ - to: customer.email, - subject: 'Pretendo Subscription Failed - No Linked PNID', - text: `Your recent subscription to Pretendo Network has failed.\nThis is due to no PNID PID being linked to the Stripe customer account used. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}` - }); + try { + await sendGmail({ + to: customer.email, + subject: 'Pretendo Subscription Failed - No Linked PNID', + text: `Your recent subscription to Pretendo Network has failed.\nThis is due to no PNID PID being linked to the Stripe customer account used. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}` + }); + } catch (error) { + logger.error(`Error sending email | ${customer.id}, ${customer.email} | - ${error.message}`); + } + return; } @@ -111,6 +118,8 @@ async function handleStripeEvent(event) { if (!pnid && subscription.status !== 'canceled' && subscription.status !== 'unpaid') { // PNID does not exist. Abort and refund! + logger.error(`PNID PID ${pid} does not exist! Found on Stripe user ${customer.id}! Refunding order`); + await stripe.subscriptions.del(subscription.id); const invoice = await stripe.invoices.retrieve(subscription.latest_invoice); @@ -118,12 +127,16 @@ async function handleStripeEvent(event) { payment_intent: invoice.payment_intent }); - await sendGmail({ - to: customer.email, - subject: 'Pretendo Subscription Failed - PNID Not Found', - text: `Your recent subscription to Pretendo Network has failed.\nThis is due to the provided PNID not being found. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}\nPNID PID: ${pid}` - }); - + try { + await sendGmail({ + to: customer.email, + subject: 'Pretendo Subscription Failed - PNID Not Found', + text: `Your recent subscription to Pretendo Network has failed.\nThis is due to the provided PNID not being found. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}\nPNID PID: ${pid}` + }); + } catch (error) { + logger.error(`Error sending email | ${customer.id}, ${customer.email} | - ${error.message}`); + } + return; } From e15b117642a351580dfa3abe3b77d812a89a14ca Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 25 Jun 2022 08:48:00 -0400 Subject: [PATCH 016/171] Catch errors when creating checkout session --- src/routers/account.js | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/routers/account.js b/src/routers/account.js index 10bcba8..74bdfa0 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -614,20 +614,27 @@ router.post('/checkout/:priceId', async (request, response) => { const priceId = request.params.priceId; - const session = await stripe.checkout.sessions.create({ - line_items: [ - { - price: priceId, - quantity: 1, - }, - ], - customer: customer.id, - mode: 'subscription', - success_url: `${config.http.base_url}/account?upgrade_success=true`, - cancel_url: `${config.http.base_url}/account?upgrade_success=false` - }); + try { + const session = await stripe.checkout.sessions.create({ + line_items: [ + { + price: priceId, + quantity: 1, + }, + ], + customer: customer.id, + mode: 'subscription', + success_url: `${config.http.base_url}/account?upgrade_success=true`, + cancel_url: `${config.http.base_url}/account?upgrade_success=false` + }); - response.redirect(303, session.url); + return response.redirect(303, session.url); + } catch (error) { + // Maybe we need a dedicated error page? + // O handle this as not cookies? + response.cookie('error', error.message, { domain: '.pretendo.network' }); + return response.redirect('/account'); + } }); router.post('/stripe-wh', express.raw({ type: 'application/json' }), async (request, response) => { From e1a07559467a43dbcdcf5bac55e100439f7d1ab5 Mon Sep 17 00:00:00 2001 From: ash Date: Sat, 25 Jun 2022 16:14:06 +0200 Subject: [PATCH 017/171] styles: add styling for upgrade page --- public/assets/css/upgrade.css | 230 +++++++++++++++++++++++++++++++ views/account/upgrade.handlebars | 77 ++++++++--- 2 files changed, 287 insertions(+), 20 deletions(-) create mode 100644 public/assets/css/upgrade.css diff --git a/public/assets/css/upgrade.css b/public/assets/css/upgrade.css new file mode 100644 index 0000000..9f7db62 --- /dev/null +++ b/public/assets/css/upgrade.css @@ -0,0 +1,230 @@ +.wrapper { + display: flex; + justify-content: center; + text-align: center; + min-height: 100vh; +} +.wrapper::before { + position: absolute; + top: -800px; + content: ""; + background: #111531; + border-radius: 100%; + width: 1600px; + height: 1400px; +} + +.account-form-wrapper { + display: flex; + flex-flow: column; + width: min(1200px, 100%); + color: var(--text-secondary); + margin: 0 auto 48px; + z-index: 1; +} + +.account-form-wrapper .logotype { + margin: 36px auto 0; + width: fit-content; +} + +h1.title { + color: var(--text); +} +p.caption { + width: min(100%, 500px); + margin: 0 auto 72px; +} + +form { + box-sizing: border-box; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.3rem; +} + +form .tier-radio { + display: none; +} +form .tier-radio:checked + label::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-shadow: inset 0 0 0 4px #9d70f1; + border-radius: 10px; +} +form .tier-radio:checked + label::after { + content: url(/assets/images/check.svg); + display: flex; + justify-content: center; + background: #9d70f1; + width: 24px; + height: 24px; + border-radius: 100%; + position: absolute; + top: -16px; + right: -16px; + padding: 6px; +} + +label.tier { + display: flex; + flex-flow: column; + position: relative; + border-radius: 10px; + align-items: center; + padding-top: calc(50px + 1rem); + background: #393b5f; + cursor: pointer; + transition: all 150ms; + margin-top: 50px; + text-align: center; +} + +label.tier p { + margin: 0; + margin-bottom: 0.5rem; +} + +label.tier .tier-thumbnail { + height: 100px; + width: 100px; + display: flex; + align-items: center; + overflow: hidden; + border-radius: 8px; + position: absolute; + top: -50px; + z-index: 2; + background: #47496d; +} +form .tier-radio:checked + label .tier-thumbnail::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-shadow: inset 0 0 0 4px #9d70f1; + border-radius: 8px; +} + +label.tier .tier-text { + display: flex; + flex-flow: column; + margin-bottom: auto; +} + +label.tier .tier-name { + color: var(--text); + font-weight: bold; + font-size: 1.2rem; +} + +label.tier .tier-perks { + text-align: left; + width: 70%; + margin: 24px auto 48px; +} +label.tier .tier-perks div { + display: grid; + grid-template-columns: 16px auto; + gap: 8px; +} +label.tier .tier-perks svg { + stroke-width: 5px; + stroke: #59c9a5; + stroke-linecap: square; + width: 16px; + height: 16px; + vertical-align: top; + margin-top: 0.5ex; +} + +label.tier p.price { + display: flex; + width: 100%; + justify-content: center; + align-items: center; + background: #47496d; + margin: 0; + padding: 1.5rem 1rem; + box-sizing: border-box; + border-radius: 0 0 10px 10px; +} +label.tier p.price span { + font-size: 2rem; + color: var(--text); + font-weight: bold; + margin-right: 0.5ch; +} + +form .button-wrapper { + grid-column: 2 / span 1; +} +form button { + appearance: none; + -webkit-appearance: none; + display: block; + font-family: Poppins, Arial, Helvetica, sans-serif; + font-size: 1rem; + height: fit-content; + + background: var(--btn); + border: none; + border-radius: 4px; + padding: 12px; + color: var(--text); + width: 100%; + margin-top: 24px; + pointer-events: none; + filter: brightness(0.75) saturate(0.75); /* not using opacity here 'cause in the mobile layout you would see the cards under it */ + transition: filter 300ms; +} +form .tier-radio:checked ~ .button-wrapper button { + pointer-events: all; + cursor: pointer; + filter: none; +} + +@media screen and (max-width: 900px) { + .account-form-wrapper { + width: min(500px, 100%); + margin-bottom: 120px; + } + + form { + grid-template-columns: 1fr; + gap: 2.4rem; + } + + form button { + position: relative; + width: 100%; + } + form .button-wrapper { + grid-column: 1 / span 1; + position: fixed; + bottom: 24px; + width: min(500px, 90%); + z-index: 5; + } + form .button-wrapper::before { + content: ""; + position: absolute; + top: 0; + left: -100vw; + width: 200vw; + height: 150%; + background: #111531; + } +} + +@media screen and (max-width: 380px) { + label.tier .tier-perks { + width: 80%; + } +} diff --git a/views/account/upgrade.handlebars b/views/account/upgrade.handlebars index 5a45dec..306fdc0 100644 --- a/views/account/upgrade.handlebars +++ b/views/account/upgrade.handlebars @@ -1,4 +1,4 @@ - +
+ +
+ {{#each tiers}} + + + {{/each}} +
+ +
+
+
- \ No newline at end of file + \ No newline at end of file From 80fc58d81c27dba6fb5606193031127f9b76f84c Mon Sep 17 00:00:00 2001 From: ash Date: Sat, 25 Jun 2022 17:56:01 +0200 Subject: [PATCH 018/171] chore: fix bad js --- views/account/upgrade.handlebars | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/views/account/upgrade.handlebars b/views/account/upgrade.handlebars index 306fdc0..55e7d23 100644 --- a/views/account/upgrade.handlebars +++ b/views/account/upgrade.handlebars @@ -34,7 +34,7 @@
{{#each tiers}} - +