1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Server: Allow users to cancel Stripe subscription

This commit is contained in:
Laurent Cozic 2021-07-24 16:44:50 +01:00
parent 3f993af7fd
commit b7e9848428
3 changed files with 154 additions and 96 deletions

View File

@ -0,0 +1,3 @@
#user_cancel_subscription_link {
display: none;
}

View File

@ -2,7 +2,7 @@ import { SubPath, redirect } from '../../utils/routeUtils';
import Router from '../../utils/Router';
import { RouteType } from '../../utils/types';
import { AppContext, HttpMethod } from '../../utils/types';
import { bodyFields, formParse } from '../../utils/requestUtils';
import { bodyFields, contextSessionId, formParse } from '../../utils/requestUtils';
import { ErrorForbidden, ErrorUnprocessableEntity } from '../../utils/errors';
import { User, Uuid } from '../../db';
import config from '../../config';
@ -149,9 +149,9 @@ router.get('users/:id', async (path: SubPath, ctx: AppContext, user: User = null
if (subscription) {
view.content.subscription = subscription;
view.content.showCancelSubscription = !isNew && !!owner.is_admin && owner.id !== user.id;
view.content.showCancelSubscription = !isNew;
view.content.showUpdateSubscriptionBasic = !isNew && !!owner.is_admin && user.account_type !== AccountType.Basic;
view.content.showUpdateSubscriptionPro = !isNew && !!owner.is_admin && user.account_type !== AccountType.Pro;
view.content.showUpdateSubscriptionPro = !isNew && user.account_type !== AccountType.Pro;
}
view.content.showRestoreButton = !isNew && !!owner.is_admin && !user.enabled;
@ -159,6 +159,7 @@ router.get('users/:id', async (path: SubPath, ctx: AppContext, user: User = null
view.content.canSetEmail = isNew || owner.is_admin;
view.content.canShareFolderOptions = yesNoDefaultOptions(user, 'can_share_folder');
view.jsFiles.push('zxcvbn');
view.cssFiles.push('index/user');
if (config().accountTypesEnabled) {
view.content.showAccountTypes = true;
@ -248,6 +249,7 @@ interface FormFields {
send_reset_password_email: string;
update_subscription_basic_button: string;
update_subscription_pro_button: string;
user_cancel_subscription_button: string;
}
router.post('users', async (path: SubPath, ctx: AppContext) => {
@ -272,6 +274,13 @@ router.post('users', async (path: SubPath, ctx: AppContext) => {
} else {
await userModel.save(userToSave, { isNew: false });
}
} else if (fields.user_cancel_subscription_button) {
await cancelSubscription(ctx.joplin.models, userId);
const sessionId = contextSessionId(ctx, false);
if (sessionId) {
await ctx.joplin.models.session().logout(sessionId);
return redirect(ctx, config().baseUrl);
}
} else {
if (ctx.joplin.owner.is_admin) {
if (fields.disable_button || fields.restore_button) {

View File

@ -1,111 +1,147 @@
{{> errorBanner}}
<form id="user_form" action="{{{postUrl}}}" method="POST">
<input type="hidden" name="id" value="{{user.id}}"/>
<input type="hidden" name="is_new" value="{{isNew}}"/>
<div class="field">
<label class="label">Full name</label>
<div class="control">
<input class="input" type="text" name="full_name" value="{{user.full_name}}"/>
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" name="email" value="{{user.email}}" {{^canSetEmail}}disabled{{/canSetEmail}}/>
</div>
{{^canSetEmail}}
<p class="help">For security reasons the email cannot currently be changed. To request a change please contact {{global.supportEmail}}</p>
{{/canSetEmail}}
</div>
<h1 class="title">Your profile</h1>
<form id="user_form" action="{{{postUrl}}}" method="POST">
<div class="block">
{{> errorBanner}}
<input type="hidden" name="id" value="{{user.id}}"/>
<input type="hidden" name="is_new" value="{{isNew}}"/>
<div class="field">
<label class="label">Full name</label>
<div class="control">
<input class="input" type="text" name="full_name" value="{{user.full_name}}"/>
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" name="email" value="{{user.email}}" {{^canSetEmail}}disabled{{/canSetEmail}}/>
</div>
{{^canSetEmail}}
<p class="help">For security reasons the email cannot currently be changed. To request a change please contact {{global.supportEmail}}</p>
{{/canSetEmail}}
</div>
{{#global.owner.is_admin}}
{{#showAccountTypes}}
<div class="field">
<label class="label">Account type</label>
<div class="select">
<select name="account_type">
{{#accountTypes}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{label}}</option>
{{/accountTypes}}
</select>
</div>
<p class="help">If the below properties are left to their default (empty) values, the account-specific properties will apply.</p>
</div>
{{/showAccountTypes}}
{{#global.owner.is_admin}}
{{#showAccountTypes}}
<div class="field">
<label class="label">Account type</label>
<label class="label">Max item size</label>
<div class="control">
<input class="input" type="number" placeholder="Default" name="max_item_size" value="{{user.max_item_size}}"/>
</div>
</div>
<div class="field">
<label class="label">Max total size</label>
<div class="control">
<input class="input" type="number" placeholder="Default" name="max_total_item_size" value="{{user.max_total_item_size}}"/>
</div>
</div>
<div class="field">
<label class="label">Can share notebook</label>
<div class="select">
<select name="account_type">
{{#accountTypes}}
<select name="can_share_folder">
{{#canShareFolderOptions}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{label}}</option>
{{/accountTypes}}
{{/canShareFolderOptions}}
</select>
</div>
<p class="help">If the below properties are left to their default (empty) values, the account-specific properties will apply.</p>
</div>
{{/showAccountTypes}}
<div class="field">
<label class="label">Max item size</label>
<div class="control">
<input class="input" type="number" placeholder="Default" name="max_item_size" value="{{user.max_item_size}}"/>
</div>
</div>
<div class="field">
<label class="label">Max total size</label>
<div class="control">
<input class="input" type="number" placeholder="Default" name="max_total_item_size" value="{{user.max_total_item_size}}"/>
</div>
</div>
<div class="field">
<label class="label">Can share notebook</label>
<div class="select">
<select name="can_share_folder">
{{#canShareFolderOptions}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{label}}</option>
{{/canShareFolderOptions}}
</select>
</div>
</div>
{{/global.owner.is_admin}}
<div class="field">
<label class="label">Password</label>
<div class="control">
<input id="password" class="input" type="password" name="password" autocomplete="new-password"/>
</div>
<p id="password_strength" class="help"></p>
</div>
<div class="field">
<label class="label">Repeat password</label>
<div class="control">
<input class="input" type="password" name="password2" autocomplete="new-password"/>
</div>
{{#global.owner.is_admin}}
<p class="help">When creating a new user, if no password is specified the user will have to set it by following the link in their email.</p>
{{/global.owner.is_admin}}
</div>
<div class="control block">
<input type="submit" name="post_button" class="button is-primary" value="{{buttonTitle}}" />
{{#showResetPasswordButton}}
<input type="submit" name="send_reset_password_email" class="button is-warning" value="Send reset password email" />
{{/showResetPasswordButton}}
{{#showDisableButton}}
<input type="submit" name="disable_button" class="button is-danger" value="Disable" />
{{/showDisableButton}}
{{#showRestoreButton}}
<input type="submit" name="restore_button" class="button is-danger" value="Restore" />
{{/showRestoreButton}}
<div class="field">
<label class="label">Password</label>
<div class="control">
<input id="password" class="input" type="password" name="password" autocomplete="new-password"/>
</div>
<p id="password_strength" class="help"></p>
</div>
<div class="field">
<label class="label">Repeat password</label>
<div class="control">
<input class="input" type="password" name="password2" autocomplete="new-password"/>
</div>
{{#global.owner.is_admin}}
<p class="help">When creating a new user, if no password is specified the user will have to set it by following the link in their email.</p>
{{/global.owner.is_admin}}
</div>
<div class="control block">
<input type="submit" name="post_button" class="button is-primary" value="{{buttonTitle}}" />
{{#showResetPasswordButton}}
<input type="submit" name="send_reset_password_email" class="button is-warning" value="Send reset password email" />
{{/showResetPasswordButton}}
{{#showDisableButton}}
<input type="submit" name="disable_button" class="button is-danger" value="Disable" />
{{/showDisableButton}}
{{#showRestoreButton}}
<input type="submit" name="restore_button" class="button is-danger" value="Restore" />
{{/showRestoreButton}}
</div>
</div>
<h1 class="title">Your subscription</h1>
<div class="block">
{{#global.owner.is_admin}}
{{#subscription}}
<div class="control block">
<p class="block">Stripe Subscription ID: <a href="https://dashboard.stripe.com/subscriptions/{{subscription.stripe_subscription_id}}">{{subscription.stripe_subscription_id}}</a></p>
{{#showUpdateSubscriptionBasic}}
<input type="submit" name="update_subscription_basic_button" class="button is-warning" value="Downgrade to Basic" />
{{/showUpdateSubscriptionBasic}}
{{#showUpdateSubscriptionPro}}
<input type="submit" name="update_subscription_pro_button" class="button is-warning" value="Upgrade to Pro" />
{{/showUpdateSubscriptionPro}}
{{#showCancelSubscription}}
<input type="submit" name="cancel_subscription_button" class="button is-danger" value="Cancel subscription" />
{{/showCancelSubscription}}
</div>
{{/subscription}}
{{/global.owner.is_admin}}
{{^global.owner.is_admin}}
{{#subscription}}
<div class="control block">
{{#showUpdateSubscriptionPro}}
<a href="{{{global.baseUrl}}}/upgrade" class="button is-warning block">Upgrade to Pro</a>
{{/showUpdateSubscriptionPro}}
{{#showCancelSubscription}}
<p id="user_cancel_subscription_link" class="block"><a href="#">Cancel subscription</a></p>
<input type="submit" id="user_cancel_subscription_button" name="user_cancel_subscription_button" class="button is-danger" value="Cancel subscription" />
{{/showCancelSubscription}}
</div>
{{/subscription}}
{{/global.owner.is_admin}}
</div>
{{#subscription}}
<div class="control block">
{{#showUpdateSubscriptionBasic}}
<input type="submit" name="update_subscription_basic_button" class="button is-warning" value="Downgrade to Basic" />
{{/showUpdateSubscriptionBasic}}
{{#showUpdateSubscriptionPro}}
<input type="submit" name="update_subscription_pro_button" class="button is-warning" value="Upgrade to Pro" />
{{/showUpdateSubscriptionPro}}
{{#showCancelSubscription}}
<input type="submit" name="cancel_subscription_button" class="button is-danger" value="Cancel subscription" />
{{/showCancelSubscription}}
</div>
{{/subscription}}
</form>
<script>
$(() => {
if ($('#user_cancel_subscription_link').length) {
$('#user_cancel_subscription_button').hide();
$('#user_cancel_subscription_link').show();
$('#user_cancel_subscription_link').click((event) => {
event.preventDefault();
$('#user_cancel_subscription_button').click();
});
}
document.getElementById("user_form").addEventListener('submit', function(event) {
if (event.submitter.getAttribute('name') === 'disable_button') {
const ok = confirm('Disable this account?');
@ -122,6 +158,16 @@
if (!ok) event.preventDefault();
}
if (event.submitter.getAttribute('name') === 'user_cancel_subscription_button') {
const answer = prompt('After cancelling your subscription, your account will be deleted and you will no longer be able to use it for synchronisation. This cannot be undone. If you would like to proceed please type "confirm" in the box below.');
if (answer !== 'confirm') {
event.preventDefault();
alert('The subscription was not cancelled.');
} else {
alert('Thank you. Your subscription is now going to be cancelled and you will be logged out.');
}
}
if (event.submitter.getAttribute('name') === 'update_subscription_basic_button') {
const ok = confirm('Downgrade to Basic subscription?');
if (!ok) event.preventDefault();