mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-03-21 17:24:28 -05:00
Cancel subscription, donation cache, minor account page edits
This commit is contained in:
parent
d3b0f100bc
commit
a4fdcd72ee
|
|
@ -25,6 +25,29 @@
|
|||
.account-sidebar .user .username {
|
||||
margin: 0px;
|
||||
}
|
||||
.account-sidebar .user .tier-name {
|
||||
margin: 0px;
|
||||
line-height: 1.2em;
|
||||
border-radius: 1.2em;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
}
|
||||
.account-sidebar .user .tier-level-0 {
|
||||
color:gray;
|
||||
border-color: gray;
|
||||
}
|
||||
.account-sidebar .user .tier-level-1 {
|
||||
color:red;
|
||||
border-color: red;
|
||||
}
|
||||
.account-sidebar .user .tier-level-2 {
|
||||
color:darkorange;
|
||||
border-color: darkorange;
|
||||
}
|
||||
.account-sidebar .user .tier-level-3 {
|
||||
color:gold;
|
||||
border-color: gold;
|
||||
}
|
||||
.account-sidebar .user .mii {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const buttons = {
|
|||
},
|
||||
};
|
||||
|
||||
const currentTierID = document.querySelector('form').dataset.currentTier;
|
||||
const currentTierID = document.querySelector('form').dataset.currentTier || undefined;
|
||||
|
||||
const currentTierElement = document.querySelector(`#${currentTierID}`) || undefined;
|
||||
|
||||
|
|
@ -32,6 +32,19 @@ function conditionalSubmitButton(condition, target) {
|
|||
}
|
||||
}
|
||||
|
||||
function submitForm(cancel) {
|
||||
const form = document.querySelector('form');
|
||||
|
||||
if (cancel) {
|
||||
form.action = '/account/stripe/unsubscribe';
|
||||
} else {
|
||||
const selectedTier = form.querySelector('input[type="radio"]:checked').value;
|
||||
form.action = `/account/stripe/checkout/${selectedTier}`;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// If the currect tier exists, select it from the list and disable the submit button.
|
||||
if (currentTierElement) {
|
||||
currentTierElement.click();
|
||||
|
|
@ -59,10 +72,7 @@ buttons.submit.addEventListener('click', function(e) {
|
|||
|
||||
document.querySelector('.switch-tier-modal-wrapper').classList.remove('hidden');
|
||||
} else {
|
||||
const form = document.querySelector('form');
|
||||
const selectedTier = form.querySelector('input[type="radio"]:checked').value;
|
||||
form.action = `/account/checkout/${selectedTier}`;
|
||||
form.submit();
|
||||
submitForm();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -84,8 +94,7 @@ buttons.unsubModal.close.addEventListener('click', function(e) {
|
|||
buttons.unsubModal.confirm.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
/* unsub logic here */
|
||||
alert('lol unsubbed');
|
||||
submitForm(true);
|
||||
});
|
||||
|
||||
buttons.switchTierModal.close.addEventListener('click', function(e) {
|
||||
|
|
@ -97,6 +106,5 @@ buttons.switchTierModal.close.addEventListener('click', function(e) {
|
|||
buttons.switchTierModal.confirm.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
/* tier switching logic here */
|
||||
alert('lol switched tier');
|
||||
submitForm(false);
|
||||
});
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
const Trello =require('trello');
|
||||
const Trello = require('trello');
|
||||
const Stripe = require('stripe');
|
||||
const got = require('got');
|
||||
const config = require('../config.json');
|
||||
|
||||
const trello = new Trello(config.trello.api_key, config.trello.api_token);
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
|
||||
const VALID_LIST_NAMES = ['Not Started', 'Started', 'Completed'];
|
||||
let cache;
|
||||
let trelloCache;
|
||||
let stripeDonationCache;
|
||||
|
||||
async function getTrelloCache() {
|
||||
const available = await trelloAPIAvailable();
|
||||
|
|
@ -15,19 +19,19 @@ async function getTrelloCache() {
|
|||
};
|
||||
}
|
||||
|
||||
if (!cache) {
|
||||
cache = await updateTrelloCache();
|
||||
if (!trelloCache) {
|
||||
trelloCache = await updateTrelloCache();
|
||||
}
|
||||
|
||||
if (cache.update_time < Date.now() - (1000 * 60 * 60)) {
|
||||
cache = await updateTrelloCache();
|
||||
if (trelloCache.update_time < Date.now() - (1000 * 60 * 60)) {
|
||||
trelloCache = await updateTrelloCache();
|
||||
}
|
||||
|
||||
return cache;
|
||||
return trelloCache;
|
||||
}
|
||||
|
||||
async function updateTrelloCache() {
|
||||
const progressData = {
|
||||
const progressCache = {
|
||||
update_time: Date.now(),
|
||||
sections: []
|
||||
};
|
||||
|
|
@ -70,11 +74,11 @@ async function updateTrelloCache() {
|
|||
}
|
||||
|
||||
if (meta.progress.not_started.length !== 0 || meta.progress.started.length !== 0 || meta.progress.completed.length !== 0) {
|
||||
progressData.sections.push(meta);
|
||||
progressCache.sections.push(meta);
|
||||
}
|
||||
}
|
||||
|
||||
return progressData;
|
||||
return progressCache;
|
||||
}
|
||||
|
||||
async function trelloAPIAvailable() {
|
||||
|
|
@ -82,7 +86,55 @@ async function trelloAPIAvailable() {
|
|||
return status.description === 'All Systems Operational';
|
||||
}
|
||||
|
||||
async function getStripeDonationCache() {
|
||||
if (!stripeDonationCache) {
|
||||
stripeDonationCache = await updateStripeDonationCache();
|
||||
}
|
||||
|
||||
if (stripeDonationCache.update_time < Date.now() - (1000 * 60 * 60)) {
|
||||
stripeDonationCache = await updateStripeDonationCache();
|
||||
}
|
||||
|
||||
return stripeDonationCache;
|
||||
}
|
||||
|
||||
async function updateStripeDonationCache() {
|
||||
const donationCache = {
|
||||
update_time: Date.now(),
|
||||
goal: config.stripe.goal_cents,
|
||||
total: 0,
|
||||
donators: 0,
|
||||
completed_real: 0,
|
||||
completed_capped: 0
|
||||
};
|
||||
|
||||
let hasMore = true;
|
||||
let lastId;
|
||||
|
||||
while (hasMore) {
|
||||
const { data: activeSubscriptions, has_more } = await stripe.subscriptions.list({
|
||||
limit: 100,
|
||||
status: 'active',
|
||||
starting_after: lastId
|
||||
});
|
||||
|
||||
donationCache.donators += activeSubscriptions.length;
|
||||
|
||||
for (const subscription of activeSubscriptions) {
|
||||
donationCache.total += subscription.plan.amount;
|
||||
lastId = subscription.id;
|
||||
}
|
||||
|
||||
hasMore = has_more;
|
||||
}
|
||||
|
||||
donationCache.completed_real = Math.floor((donationCache.total / donationCache.goal) * 100); // real completion amount
|
||||
donationCache.completed_capped = Math.max(0, Math.min(donationCache.completed_real, 100)); // capped at 100
|
||||
|
||||
return donationCache;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTrelloCache,
|
||||
updateTrelloCache
|
||||
getStripeDonationCache
|
||||
};
|
||||
|
|
@ -5,7 +5,9 @@ const { v4: uuidv4 } = require('uuid');
|
|||
const AdmZip = require('adm-zip');
|
||||
const Stripe = require('stripe');
|
||||
const database = require('../database');
|
||||
const cache = require('../cache');
|
||||
const util = require('../util');
|
||||
const logger = require('../logger');
|
||||
const config = require('../../config.json');
|
||||
|
||||
const { Router } = express;
|
||||
|
|
@ -28,31 +30,28 @@ router.get('/', async (request, response) => {
|
|||
return response.redirect('/account/login');
|
||||
}
|
||||
|
||||
const { upgrade_success } = request.query;
|
||||
|
||||
const stripe = {};
|
||||
if (upgrade_success === 'true') {
|
||||
stripe.showNotice = true;
|
||||
stripe.success = true;
|
||||
} else if (upgrade_success === 'false') {
|
||||
stripe.showNotice = true;
|
||||
stripe.error = true;
|
||||
}
|
||||
|
||||
// Setup the data to be sent to the handlebars renderer
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
linked: request.cookies.linked,
|
||||
error: request.cookies.error,
|
||||
stripe: stripe
|
||||
success: request.cookies.success,
|
||||
error: request.cookies.error
|
||||
};
|
||||
|
||||
// Reset message cookies
|
||||
response.clearCookie('linked', { domain: '.pretendo.network' });
|
||||
response.clearCookie('success', { domain: '.pretendo.network' });
|
||||
response.clearCookie('error', { domain: '.pretendo.network' });
|
||||
|
||||
// Check for Stripe messages
|
||||
const { upgrade_success } = request.query;
|
||||
|
||||
if (upgrade_success === 'true') {
|
||||
renderData.success = 'Account upgraded successfully';
|
||||
} else if (upgrade_success === 'false') {
|
||||
renderData.error = 'Account upgrade failed';
|
||||
}
|
||||
|
||||
// Attempt to get user data
|
||||
let apiResponse = await util.apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
|
|
@ -93,7 +92,12 @@ router.get('/', async (request, response) => {
|
|||
|
||||
// Set user account info to render data
|
||||
const account = apiResponse.body;
|
||||
const pid = account.pid;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
renderData.tierName = pnid.get('connections.stripe.tier_name');
|
||||
renderData.tierLevel = pnid.get('connections.stripe.tier_level');
|
||||
renderData.account = account;
|
||||
renderData.isTester = account.access_level > 0;
|
||||
|
||||
|
|
@ -364,7 +368,7 @@ router.get('/connect/discord', async (request, response) => {
|
|||
}
|
||||
}
|
||||
|
||||
response.cookie('linked', 'Discord', { domain: '.pretendo.network' }).redirect('/account');
|
||||
response.cookie('success', 'Discord account linked successfully', { domain: '.pretendo.network' }).redirect('/account');
|
||||
});
|
||||
|
||||
router.get('/online-files', async (request, response) => {
|
||||
|
|
@ -518,12 +522,51 @@ router.get('/upgrade', async (request, response) => {
|
|||
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;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
const renderData = {
|
||||
layout: 'main',
|
||||
locale: util.getLocale(request.locale.region, request.locale.language),
|
||||
localeString: request.locale.toString(),
|
||||
error: request.cookies.error,
|
||||
currentTier: 'price_1LBnZADOJlJAaQQ3pEUjNWbY' // To be replaced
|
||||
currentTier: pnid.get('connections.stripe.price_id'),
|
||||
donationCache: await cache.getStripeDonationCache()
|
||||
};
|
||||
|
||||
const { data: prices } = await stripe.prices.list();
|
||||
|
|
@ -557,7 +600,7 @@ router.get('/upgrade', async (request, response) => {
|
|||
response.render('account/upgrade', renderData);
|
||||
});
|
||||
|
||||
router.post('/checkout/:priceId', async (request, response) => {
|
||||
router.post('/stripe/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');
|
||||
|
|
@ -626,8 +669,8 @@ router.post('/checkout/:priceId', async (request, response) => {
|
|||
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');
|
||||
//response.cookie('error', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' });
|
||||
//return response.redirect('/account');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -653,7 +696,80 @@ router.post('/checkout/:priceId', async (request, response) => {
|
|||
}
|
||||
});
|
||||
|
||||
router.post('/stripe-wh', express.raw({ type: 'application/json' }), async (request, response) => {
|
||||
router.post('/stripe/unsubscribe', 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;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
const subscriptionId = pnid.get('connections.stripe.subscription_id');
|
||||
const tierName = pnid.get('connections.stripe.tier_name');
|
||||
|
||||
if (subscriptionId) {
|
||||
try {
|
||||
await stripe.subscriptions.del(subscriptionId);
|
||||
|
||||
const updateData = {
|
||||
'connections.stripe.subscription_id': null,
|
||||
'connections.stripe.price_id': null,
|
||||
'connections.stripe.tier_level': 0,
|
||||
'connections.stripe.tier_name': null,
|
||||
};
|
||||
|
||||
if (pnid.get('access_level') < 2) {
|
||||
// Fail-safe for if staff members reach here
|
||||
// Mostly only useful during testing
|
||||
updateData.access_level = 0;
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({ pid }, { $set: updateData }).exec();
|
||||
} catch (error) {
|
||||
logger.error(`Error canceling old user subscription | ${pnid.get('connections.stripe.customer_id')}, ${pid}, ${subscriptionId} | - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
response.cookie('success', `Unsubscribed from ${tierName}`, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
});
|
||||
|
||||
router.post('/stripe/webhook', express.raw({ type: 'application/json' }), async (request, response) => {
|
||||
const stripeSignature = request.headers['stripe-signature'];
|
||||
let event;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const util = require('../util');
|
|||
const { boards } = require('../../boards/boards.json');
|
||||
const router = new Router();
|
||||
|
||||
const { getTrelloCache } = require('../trello');
|
||||
const { getTrelloCache } = require('../cache');
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const util = require('../util');
|
|||
const { boards } = require('../../boards/boards.json');
|
||||
const router = new Router();
|
||||
|
||||
const { getTrelloCache } = require('../trello');
|
||||
const { getTrelloCache } = require('../cache');
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const PNIDSchema = new Schema({
|
|||
subscription_id: String,
|
||||
price_id: String,
|
||||
tier_level: Number,
|
||||
tier_name: String,
|
||||
latest_webhook_timestamp: Number
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
src/util.js
39
src/util.js
|
|
@ -80,27 +80,31 @@ 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') {
|
||||
// No PNID PID linked to customer. Abort and refund!
|
||||
logger.error(`Stripe user ${customer.id} has no PNID linked! Refunding order`);
|
||||
if (!customer?.metadata?.pnid_pid) {
|
||||
// No PNID PID linked to customer
|
||||
if (subscription.status !== 'canceled' && subscription.status !== 'unpaid') {
|
||||
// Abort and refund!
|
||||
logger.error(`Stripe user ${customer.id} has no PNID linked! Refunding order`);
|
||||
|
||||
await stripe.subscriptions.del(subscription.id);
|
||||
await stripe.subscriptions.del(subscription.id);
|
||||
|
||||
const invoice = await stripe.invoices.retrieve(subscription.latest_invoice);
|
||||
await stripe.refunds.create({
|
||||
payment_intent: invoice.payment_intent
|
||||
});
|
||||
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
subject: 'Pretendo Network 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}`
|
||||
const invoice = await stripe.invoices.retrieve(subscription.latest_invoice);
|
||||
await stripe.refunds.create({
|
||||
payment_intent: invoice.payment_intent
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email | ${customer.id}, ${customer.email} | - ${error.message}`);
|
||||
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
subject: 'Pretendo Network 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}`);
|
||||
}
|
||||
} else {
|
||||
logger.error(`Stripe user ${customer.id} has no PNID linked!`);
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -162,6 +166,7 @@ async function handleStripeEvent(event) {
|
|||
'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.tier_name': subscription.status === 'active' ? product.name : null,
|
||||
'connections.stripe.latest_webhook_timestamp': event.created,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@
|
|||
<img src="{{account.mii.image_url}}" class="mii" />
|
||||
<p class="miiname">{{account.mii.name}}</p>
|
||||
<p class="username" value="{{account.username}}">PNID: {{account.username}}</p>
|
||||
{{#if tierName}}
|
||||
<p class="tier-name tier-level-{{tierLevel}}" value="{{tierName}}">{{tierName}}</p>
|
||||
{{else}}
|
||||
<p class="tier-name tier-level-0" value="Default">Default</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="button secondary" id="download-cemu-files" href="/account/online-files" download>
|
||||
|
|
@ -145,18 +150,16 @@
|
|||
<label for="marketing">Receive project updates via email (you can opt-out at any time)</label>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{> footer }}
|
||||
</div>
|
||||
|
||||
|
||||
{{#if linked}}
|
||||
{{#if success}}
|
||||
<div class="banner-notice success">
|
||||
<div>
|
||||
<p>{{ linked }} account linked successfully</p>
|
||||
<p>{{success}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
@ -164,27 +167,9 @@
|
|||
{{#if error}}
|
||||
<div class="banner-notice error">
|
||||
<div>
|
||||
<p>{{ error }}</p>
|
||||
<p>{{error}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if stripe.showNotice}}
|
||||
{{#if stripe.success}}
|
||||
<div class="banner-notice success">
|
||||
<div>
|
||||
<p>Account upgraded successfully</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if stripe.error}}
|
||||
<div class="banner-notice error">
|
||||
<div>
|
||||
<p>Account upgrade failed</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<script src="/assets/js/account.js"></script>
|
||||
|
|
@ -6,8 +6,8 @@
|
|||
<span>Back</span>
|
||||
</a>
|
||||
|
||||
<div class="account-form-wrapper">
|
||||
<a class="logotype" href="/">
|
||||
<div class="account-form-wrapper">
|
||||
<a class="logotype" href="/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
|
|
@ -60,36 +60,36 @@
|
|||
</label>
|
||||
{{/each}}
|
||||
|
||||
<div class="button-wrapper">
|
||||
<button class="disabled" type="submit" id="submitButton">Select a tier</button>
|
||||
<button class="unsubscribe hidden" id="unsubModalShowButton">Unsubscribe</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="button-wrapper">
|
||||
<button class="disabled" type="submit" id="submitButton">Select a tier</button>
|
||||
<button class="unsubscribe hidden" id="unsubModalShowButton">Unsubscribe</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="unsub-modal-wrapper hidden">
|
||||
<div class="unsub-modal">
|
||||
<h1 class="title dot">Unsubscribe</h1>
|
||||
<p class="unsub-modal-caption">Are you sure you want to unsubscribe from <span>tiername</span>?
|
||||
<div class="unsub-modal-wrapper hidden">
|
||||
<div class="unsub-modal">
|
||||
<h1 class="title dot">Unsubscribe</h1>
|
||||
<p class="unsub-modal-caption">Are you sure you want to unsubscribe from <span>tiername</span>?
|
||||
You will lose access to the perks associated with that tier.</p>
|
||||
<div class="unsub-modal-button-wrapper">
|
||||
<button class="cancel" id="unsubModalCloseButton">Cancel</button>
|
||||
<button class="confirm" id="unsubModalConfirmButton">Unsubscribe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unsub-modal-button-wrapper">
|
||||
<button class="cancel" id="unsubModalCloseButton">Cancel</button>
|
||||
<button class="confirm" id="unsubModalConfirmButton">Unsubscribe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-tier-modal-wrapper hidden">
|
||||
<div class="switch-tier-modal">
|
||||
<h1 class="title dot">Change tier</h1>
|
||||
<p class="switch-tier-modal-caption">Are you sure you want to unsubscribe
|
||||
<div class="switch-tier-modal">
|
||||
<h1 class="title dot">Change tier</h1>
|
||||
<p class="switch-tier-modal-caption">Are you sure you want to unsubscribe
|
||||
from <span class="oldtier">oldtiername</span> and subscribe to <span class="newtier">newtiername</span>?</p>
|
||||
<div class="switch-tier-modal-button-wrapper">
|
||||
<button class="cancel" id="switchTierCloseButton">Cancel</button>
|
||||
<button class="confirm" id="switchTierConfirmButton">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="switch-tier-modal-button-wrapper">
|
||||
<button class="cancel" id="switchTierCloseButton">Cancel</button>
|
||||
<button class="confirm" id="switchTierConfirmButton">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/upgrade.js" />
|
||||
Loading…
Reference in New Issue
Block a user