mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-23 00:57:21 -05:00
Merge pull request #117 from PretendoNetwork/feature-stripe-tiers-change-tier
Added the ability to change tiers
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
62
src/util.js
62
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,13 @@
|
||||
<p class="username" value="{{account.username}}">PNID: {{account.username}}</p>
|
||||
<a class="button secondary" id="download-cemu-files" href="/account/online-files" download>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
<p class="download-caption">Download account files</p>
|
||||
<p class="caption">Download account files</p>
|
||||
<p class="cemu-warning">(will not work on Nintendo Network)</p>
|
||||
</a>
|
||||
|
||||
<a class="button secondary" id="account-upgrade" href="/account/upgrade">
|
||||
<p class="caption">Upgrade Account</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,8 +56,8 @@
|
||||
</div>
|
||||
|
||||
<div class="setting-card">
|
||||
<h2 class="header">Beta access</h2>
|
||||
{{#if isTester}}
|
||||
<h2 class="header">Server Environment</h2>
|
||||
{{#if isTester}}
|
||||
<fieldset>
|
||||
<form class="server-selection" id="server">
|
||||
<input type="radio" id="prod" name="server_selection" value="prod" checked="{{ eq account.server_access_level 'prod'}}">
|
||||
@@ -81,10 +85,13 @@
|
||||
<p>Connected to Discord as {{ discordUser.username }}#{{ discordUser.discriminator }}</p>
|
||||
<button class="button secondary" id="remove-discord-connection">Remove Discord account</button>
|
||||
{{else}}
|
||||
<p>To gain access to the developer chats, run /command in the server.</p>
|
||||
<a href="{{ discordAuthURL }}">
|
||||
<button class="button secondary" id="link-discord-account">Link Discord account</button>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<p>Beta servers are exclusive to testers. To upgrade to a higher tier, click <a href="/account/upgrade">here</a>.</p>
|
||||
<p>Account is not a beta tester, locked to production servers.</p>
|
||||
<p>Beta servers are exclusive to beta testers.<br>To become a beta tester, upgrade to a higher account tier.</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,43 +26,43 @@
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
|
||||
<h1 class="title dot">Upgrade</h1>
|
||||
<p class="caption">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.</p>
|
||||
|
||||
|
||||
<form method="post">
|
||||
{{#each tiers}}
|
||||
<form method="post">
|
||||
{{#each tiers}}
|
||||
<input type="radio" class="tier-radio" data-tier-name="{{this.name}}" name="tier" value="{{this.price_id}}" id="{{this.price_id}}" />
|
||||
<label class="tier" for="{{this.price_id}}">
|
||||
<label class="tier" for="{{this.price_id}}">
|
||||
<div class="tier-thumbnail">
|
||||
<img src="{{this.thumbnail}}" width="100%" height="auto" alt="Tier icon" />
|
||||
<img src="{{this.thumbnail}}" width="100%" height="auto" alt="Tier icon" />
|
||||
</div>
|
||||
<div class="tier-text">
|
||||
<div class="tier-text">
|
||||
<p class="tier-name">{{this.name}}</p>
|
||||
<div class="tier-perks">
|
||||
{{#each this.perks}}
|
||||
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
<p>{{this}}</p>
|
||||
<p>{{this}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="price">
|
||||
<span>{{this.price}}</span> / month
|
||||
</p>
|
||||
</label>
|
||||
{{/each}}
|
||||
</label>
|
||||
{{/each}}
|
||||
<div class="button-wrapper">
|
||||
<button type="submit" id="submitButton">
|
||||
Select a tier
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user