diff --git a/public/assets/css/account.css b/public/assets/css/account.css index dbb4739..42a8dbe 100644 --- a/public/assets/css/account.css +++ b/public/assets/css/account.css @@ -31,7 +31,8 @@ border-radius: 100%; background: var(--btn-secondary); } -.account-sidebar .user #download-cemu-files { +.account-sidebar .user #download-cemu-files, +.account-sidebar .user #account-upgrade { display: flex; flex-flow: column; align-items: center; @@ -40,7 +41,8 @@ margin: 24px 0 0; text-decoration: none; } -.account-sidebar .user #download-cemu-files p.download-caption { +.account-sidebar .user #download-cemu-files p.caption, +.account-sidebar .user #account-upgrade p.caption { margin: 15px 0 0; } .account-sidebar .user p.cemu-warning { @@ -152,7 +154,7 @@ fieldset:disabled form label { color: var(--text); } -.setting-card #remove-discord-connection { +.setting-card #link-discord-account { width: 100%; padding: 12px 48px; cursor: pointer; diff --git a/src/routers/account.js b/src/routers/account.js index e6c6873..427e097 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -29,7 +29,7 @@ router.get('/', async (request, response) => { } const { upgrade_success } = request.query; - console.log(upgrade_success); + const stripe = {}; if (upgrade_success === 'true') { stripe.showNotice = true; @@ -193,6 +193,8 @@ router.get('/', async (request, response) => { renderData.discordAuthURL = discordAuthURL; } + renderData.isTester = false; + response.render('account/account', renderData); }); @@ -616,16 +618,19 @@ router.post('/checkout/:priceId', async (request, response) => { await database.PNID.updateOne({ pid }, { $set: { - connections: { - stripe: { - customer_id: customer.id // ensure PNID always has latest customer ID - } - } + 'connections.stripe.customer_id': customer.id // ensure PNID always has latest customer ID } }, { upsert: true }).exec(); const priceId = request.params.priceId; + const pnid = await database.PNID.findOne({ pid }); + + if (pnid.get('access_level') >= 2) { + response.cookie('error', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' }); + return response.redirect('/account'); + } + try { const session = await stripe.checkout.sessions.create({ line_items: [ @@ -643,7 +648,7 @@ router.post('/checkout/:priceId', async (request, response) => { return response.redirect(303, session.url); } catch (error) { // Maybe we need a dedicated error page? - // O handle this as not cookies? + // Or handle this as not cookies? response.cookie('error', error.message, { domain: '.pretendo.network' }); return response.redirect('/account'); } diff --git a/src/schema/pnid.js b/src/schema/pnid.js index 8e87a3f..bce4f8e 100644 --- a/src/schema/pnid.js +++ b/src/schema/pnid.js @@ -11,6 +11,7 @@ const PNIDSchema = new Schema({ connections: { stripe: { customer_id: String, + subscription_id: String, price_id: String, tier_level: Number, latest_webhook_timestamp: Number diff --git a/src/util.js b/src/util.js index da2edae..e7ca8f2 100644 --- a/src/util.js +++ b/src/util.js @@ -80,7 +80,7 @@ async function handleStripeEvent(event) { 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') { + 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`); @@ -139,14 +139,30 @@ async function handleStripeEvent(event) { return; } - const updateData = { - connections: { - stripe: { - price_id: subscription.plan.id, - tier_level: Number(product.metadata.tier_level || 0), - latest_webhook_timestamp: event.created + const currentSubscriptionId = pnid.get('connections.stripe.subscription_id'); + + if (subscription.status === 'canceled' && currentSubscriptionId && subscription.id !== currentSubscriptionId) { + // Canceling old subscription, do nothing but update webhook date + + const updateData = { + 'connections.stripe.latest_webhook_timestamp': event.created + }; + + await database.PNID.updateOne({ + pid, + 'connections.stripe.latest_webhook_timestamp': { + $lte: event.created } - } + }, { $set: updateData }).exec(); + + return; + } + + const updateData = { + 'connections.stripe.subscription_id': subscription.status === 'active' ? subscription.id : null, + 'connections.stripe.price_id': subscription.status === 'active' ? subscription.plan.id : null, + 'connections.stripe.tier_level': subscription.status === 'active' ? Number(product.metadata.tier_level || 0) : 0, + 'connections.stripe.latest_webhook_timestamp': event.created, }; if (product.metadata.beta === 'true') { @@ -167,11 +183,36 @@ async function handleStripeEvent(event) { default: break; } - - await database.PNID.updateOne({ pid }, { $set: updateData }, { upsert: true }).exec(); } + await database.PNID.updateOne({ + pid, + 'connections.stripe.latest_webhook_timestamp': { + $lte: event.created + } + }, { $set: updateData }).exec(); + if (subscription.status === 'active') { + // Get all the customers active subscriptions + const { data: activeSubscriptions } = await stripe.subscriptions.list({ + limit: 100, + status: 'active', + customer: customer.id + }); + + // Order subscriptions by creation time and remove the latest one + const orderedActiveSubscriptions = activeSubscriptions.sort((a, b) => b.created - a.created); + const pastSubscriptions = orderedActiveSubscriptions.slice(1); + + // Remove any old past subscriptions that might still be hanging around + for (const pastSubscription of pastSubscriptions) { + try { + await stripe.subscriptions.del(pastSubscription.id); + } catch (error) { + logger.error(`Error canceling old user subscription | ${customer.id}, ${pid}, ${pastSubscription.id} | - ${error.message}`); + } + } + try { await mailer.sendMail({ to: customer.email, @@ -206,7 +247,6 @@ async function handleStripeEvent(event) { logger.error(`Error sending email | ${customer.id}, ${customer.email}, ${pid} | - ${error.message}`); } } - } } diff --git a/views/account/account.handlebars b/views/account/account.handlebars index 870300b..b39ad53 100644 --- a/views/account/account.handlebars +++ b/views/account/account.handlebars @@ -12,9 +12,13 @@

PNID: {{account.username}}

-

Download account files

+

Download account files

(will not work on Nintendo Network)

+ + +

Upgrade Account

+
@@ -52,8 +56,8 @@
-

Beta access

- {{#if isTester}} +

Server Environment

+ {{#if isTester}}
@@ -81,10 +85,13 @@

Connected to Discord as {{ discordUser.username }}#{{ discordUser.discriminator }}

{{else}} -

To gain access to the developer chats, run /command in the server.

+ + + {{/if}} {{else}} -

Beta servers are exclusive to testers. To upgrade to a higher tier, click here.

+

Account is not a beta tester, locked to production servers.

+

Beta servers are exclusive to beta testers.
To become a beta tester, upgrade to a higher account tier.

{{/if}}
diff --git a/views/account/upgrade.handlebars b/views/account/upgrade.handlebars index 55e7d23..e269420 100644 --- a/views/account/upgrade.handlebars +++ b/views/account/upgrade.handlebars @@ -26,43 +26,43 @@ - +

Upgrade

Subscribing to a tier gives you access to our developer chats and, depending on the tier, to our beta servers. Please someone reword this, it sucks. No fr.

- - {{#each tiers}} + + {{#each tiers}} - + {{/each}}
- - + +