diff --git a/package-lock.json b/package-lock.json index 547702355..73400105d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "simple-vdf": "^1.1.1", "steam": "github:odota/node-steam-npm", "steam-resources": "github:odota/node-steam-resources", - "stripe": "^9.12.0", + "stripe": "^14.9.0", "uuid": "^3.3.3", "ws": "^8.14.2" }, @@ -9220,15 +9220,15 @@ } }, "node_modules/stripe": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-9.16.0.tgz", - "integrity": "sha512-Dn8K+jSoQcXjxCobRI4HXUdHjOXsiF/KszK49fJnkbeCFjZ3EZxLG2JiM/CX+Hcq27NBDtv/Sxhvy+HhTmvyaQ==", + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.9.0.tgz", + "integrity": "sha512-t2XdpNbRH4x3MYEoxNWhwUPl9D80aUd5OHds0zhDiwRYPZ0H7MrUI/dj9wOSYlzycD3xdvjn0q7pWeFWljtMUQ==", "dependencies": { "@types/node": ">=8.1.0", - "qs": "^6.10.3" + "qs": "^6.11.0" }, "engines": { - "node": "^8.1 || >=10.*" + "node": ">=12.*" } }, "node_modules/strnum": { @@ -17432,12 +17432,12 @@ "dev": true }, "stripe": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-9.16.0.tgz", - "integrity": "sha512-Dn8K+jSoQcXjxCobRI4HXUdHjOXsiF/KszK49fJnkbeCFjZ3EZxLG2JiM/CX+Hcq27NBDtv/Sxhvy+HhTmvyaQ==", + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.9.0.tgz", + "integrity": "sha512-t2XdpNbRH4x3MYEoxNWhwUPl9D80aUd5OHds0zhDiwRYPZ0H7MrUI/dj9wOSYlzycD3xdvjn0q7pWeFWljtMUQ==", "requires": { "@types/node": ">=8.1.0", - "qs": "^6.10.3" + "qs": "^6.11.0" } }, "strnum": { diff --git a/package.json b/package.json index 0cab3f2a2..f1bbb39f4 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "simple-vdf": "^1.1.1", "steam": "github:odota/node-steam-npm", "steam-resources": "github:odota/node-steam-resources", - "stripe": "^9.12.0", + "stripe": "^14.9.0", "uuid": "^3.3.3", "ws": "^8.14.2" }, diff --git a/routes/keyManagement.ts b/routes/keyManagement.ts index 784cb9829..e7624c431 100644 --- a/routes/keyManagement.ts +++ b/routes/keyManagement.ts @@ -3,13 +3,12 @@ import uuid from 'uuid'; import bodyParser from 'body-parser'; import moment from 'moment'; import async from 'async'; -import stripeLib from 'stripe'; +import { Stripe } from 'stripe'; import db from '../store/db'; import redis from '../store/redis'; import config from '../config'; -import { redisCount } from '../util/utility'; -//@ts-ignore -const stripe = stripeLib(config.STRIPE_SECRET); + +const stripe = new Stripe(config.STRIPE_SECRET); const stripeAPIPlan = config.STRIPE_API_PLAN; const keys = express.Router(); keys.use(bodyParser.json()); @@ -26,7 +25,7 @@ keys.use((req, res, next) => { } return next(); }); -// @param rows - query result from api_keys table +// @param rows - query resut from api_keys table function getActiveKey(rows: any[]) { const notCanceled = rows.filter((row) => row.is_canceled != true); return notCanceled.length > 0 ? notCanceled[0] : null; @@ -75,14 +74,14 @@ keys api_key, }; stripe.customers - .retrieve(customer_id) - .then((customer: any) => { - const source = customer.sources.data[0]; + .retrieve(customer_id, {expand: ['default_source']}) + .then((customer) => { + const source = (customer as Stripe.Customer).default_source as Stripe.Card; toReturn.credit_brand = source?.brand; toReturn.credit_last4 = source?.last4; return stripe.subscriptions.retrieve(subscription_id); }) - .then((sub: any) => { + .then((sub) => { toReturn.current_period_end = sub.current_period_end; }) .then(() => cb(null, toReturn)) @@ -94,7 +93,7 @@ keys } const customer_id = allKeyRecords[0].customer_id; getOpenInvoices(customer_id).then((invoices) => { - const processed = invoices.map((i: any) => ({ + const processed = invoices.map((i) => ({ id: i.id, amountDue: i.amount_due, paymentLink: i.hosted_invoice_url, @@ -155,7 +154,7 @@ keys } const { api_key, subscription_id } = keyRecord; // Immediately bill the customer for any unpaid usage - await stripe.subscriptions.del(subscription_id, { invoice_now: true }); + await stripe.subscriptions.cancel(subscription_id, { invoice_now: true }); await db .from('api_keys') .where({ @@ -231,7 +230,7 @@ keys source: token.id, email: token.email, metadata: { - account_id: req.user?.account_id, + account_id: req.user?.account_id ?? '', }, }); customer_id = customer.id; @@ -240,6 +239,7 @@ keys return res.status(402).json(err); } } + const apiKey = uuid.v4(); const sub = await stripe.subscriptions.create({ customer: customer_id, @@ -291,8 +291,6 @@ keys } = req.body; await stripe.customers.update(customer_id, { email, - }); - await stripe.subscriptions.update(subscription_id, { source: id, }); res.sendStatus(200); diff --git a/svc/apiadmin.ts b/svc/apiadmin.ts index 8b0fd7316..85fd237bf 100644 --- a/svc/apiadmin.ts +++ b/svc/apiadmin.ts @@ -1,15 +1,14 @@ // Runs background processes related to API keys and billing/usage import async from 'async'; import moment from 'moment'; -import stripeLib from 'stripe'; +import { Stripe } from 'stripe'; import redis from '../store/redis'; import db from '../store/db'; import config from '../config'; import type { knex } from 'knex'; import { invokeInterval } from '../util/utility'; -//@ts-ignore -const stripe = stripeLib(config.STRIPE_SECRET); +const stripe = new Stripe(config.STRIPE_SECRET); function storeUsageCounts(cursor: string | number, cb: ErrorCb) { redis.hscan('usage_count', cursor, (err, results) => { if (err) { @@ -89,7 +88,7 @@ async function updateStripeUsage(cb: ErrorCb) { // From the docs: // By default, returns a list of subscriptions that have not been canceled. // In order to list canceled subscriptions, specify status=canceled. Use all for completeness. - status: 'all', + status: 'all' as Stripe.Subscription.Status, }; let num = 0; try { @@ -108,6 +107,18 @@ async function updateStripeUsage(cb: ErrorCb) { ); continue; } + // Deactivate any keys belonging to an invalid card + const BANNED_CARDS: string[] = []; + const sourceId = sub.default_source; + if (sourceId) { + const source = await stripe.sources.retrieve(sourceId as string); + if (source.card?.fingerprint && BANNED_CARDS.includes(source.card?.fingerprint)) { + await db.raw( + `UPDATE api_keys SET is_canceled = true WHERE subscription_id = ?`, + [sub.id] + ); + } + } const startTime = moment .unix(sub.current_period_end - 1) .startOf('month'); diff --git a/svc/syncSubs.ts b/svc/syncSubs.ts index 1d5cf1243..83aec234a 100644 --- a/svc/syncSubs.ts +++ b/svc/syncSubs.ts @@ -1,11 +1,10 @@ // Syncs the list of subscribers from Stripe to the database import db from '../store/db'; import config from '../config'; -import stripeLib from 'stripe'; +import { Stripe } from 'stripe'; import { invokeIntervalAsync } from '../util/utility'; -//@ts-ignore -const stripe = stripeLib(config.STRIPE_SECRET); +const stripe = new Stripe(config.STRIPE_SECRET); async function doSyncSubs() { // Get list of current subscribers const result = []; diff --git a/svc/web.ts b/svc/web.ts index b22969992..b897e9a77 100644 --- a/svc/web.ts +++ b/svc/web.ts @@ -11,7 +11,7 @@ import passport from 'passport'; import passportSteam from 'passport-steam'; import cors from 'cors'; import bodyParser from 'body-parser'; -import stripeLib from 'stripe'; +import { Stripe } from 'stripe'; import { Redis } from 'ioredis'; import { WebSocketServer, WebSocket } from 'ws'; import keys from '../routes/keyManagement'; @@ -28,8 +28,7 @@ import { const admins = config.ADMIN_ACCOUNT_IDS.split(',').map((e) => Number(e)); const SteamStrategy = passportSteam.Strategy; -//@ts-ignore -const stripe = stripeLib(config.STRIPE_SECRET); +const stripe = new Stripe(config.STRIPE_SECRET); export const app = express(); const apiKey = config.STEAM_API_KEY.split(',')[0]; const host = config.ROOT_URL; @@ -317,8 +316,8 @@ app.route('/subscribeSuccess').get(async (req, res) => { return res.status(400).json({ error: 'no account ID' }); } // look up the checkout session id: https://stripe.com/docs/payments/checkout/custom-success-page - const session = await stripe.checkout.sessions.retrieve(req.query.session_id); - const customer = await stripe.customers.retrieve(session.customer); + const session = await stripe.checkout.sessions.retrieve(req.query.session_id as string); + const customer = await stripe.customers.retrieve(session.customer as string); const accountId = req.user.account_id; // associate the customer id with the steam account ID (req.user.account_id) await db.raw( diff --git a/test/test.ts b/test/test.ts index 39cfbf9cf..c2a8ce7bb 100644 --- a/test/test.ts +++ b/test/test.ts @@ -7,7 +7,7 @@ import type { Express } from 'express'; import nock from 'nock'; import assert from 'assert'; import supertest from 'supertest'; -import stripeLib from 'stripe'; +import { Stripe } from 'stripe'; import pg from 'pg'; import { readFileSync } from 'fs'; import util from 'util'; @@ -551,16 +551,15 @@ describe(c.blue('[TEST] api management'), () => { .delete('/keys?loggedin=1') .then(async (res) => { assert.equal(res.statusCode, 200); - //@ts-ignore - const stripe = stripeLib(STRIPE_SECRET); + const stripe = new Stripe(STRIPE_SECRET); - await stripe.invoiceItems.create({ + const invoice = await stripe.invoices.create({ customer: this.previousCustomer, - price: 'price_1Lm1siCHN72mG1oKkk3Jh1JT', // test $123 one time }); - - const invoice = await stripe.invoices.create({ + await stripe.invoiceItems.create({ customer: this.previousCustomer, + price: 'price_1Lm1siCHN72mG1oKkk3Jh1JT', // test $123 one time + invoice: invoice.id, }); await stripe.invoices.finalizeInvoice(invoice.id);