mirror of
https://git.teknik.io/Teknikode/Teknik.git
synced 2023-08-02 14:16:22 +02:00
Added bettter UX for managing subscription
This commit is contained in:
parent
ed9a94cba1
commit
243db31442
@ -8,10 +8,12 @@ namespace Teknik.Configuration
|
||||
{
|
||||
public class BillingConfig
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public BillingType Type { get; set; }
|
||||
public string StripePublishApiKey { get; set; }
|
||||
public string StripeSecretApiKey { get; set; }
|
||||
public string StripeCheckoutWebhookSecret { get; set; }
|
||||
public string StripeSubscriptionWebhookSecret { get; set; }
|
||||
public string StripeCustomerWebhookSecret { get; set; }
|
||||
|
||||
public string UploadProductId { get; set; }
|
||||
@ -19,10 +21,12 @@ namespace Teknik.Configuration
|
||||
|
||||
public BillingConfig()
|
||||
{
|
||||
Enabled = false;
|
||||
Type = BillingType.Stripe;
|
||||
StripePublishApiKey = null;
|
||||
StripeSecretApiKey = null;
|
||||
StripeCheckoutWebhookSecret = null;
|
||||
StripeSubscriptionWebhookSecret = null;
|
||||
StripeCustomerWebhookSecret = null;
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +154,17 @@
|
||||
"action": "HandleSubscriptionChange"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "API.v1.HandleCustomerDeletion",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "api" ],
|
||||
"Pattern": "v1/Billing/HandleCustomerDeletion",
|
||||
"Area": "API",
|
||||
"Defaults": {
|
||||
"controller": "BillingAPIv1",
|
||||
"action": "HandleCustomerDeletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "API.v1.Claims",
|
||||
"HostTypes": [ "Full" ],
|
||||
|
@ -183,21 +183,7 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
if (user == null)
|
||||
throw new UnauthorizedAccessException();
|
||||
|
||||
if (user.BillingCustomer == null)
|
||||
{
|
||||
var custId = billingService.CreateCustomer(user.Username, null);
|
||||
var customer = new Customer()
|
||||
{
|
||||
CustomerId = custId,
|
||||
User = user
|
||||
};
|
||||
_dbContext.Customers.Add(customer);
|
||||
user.BillingCustomer = customer;
|
||||
_dbContext.Entry(user).State = EntityState.Modified;
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
var session = billingService.CreateCheckoutSession(user.BillingCustomer.CustomerId,
|
||||
var session = billingService.CreateCheckoutSession(user.BillingCustomer?.CustomerId,
|
||||
priceId,
|
||||
Url.SubRouteUrl("billing", "Billing.CheckoutComplete", new { productId = price.ProductId }),
|
||||
Url.SubRouteUrl("billing", "Billing.Subscriptions"));
|
||||
@ -211,6 +197,15 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
var checkoutSession = billingService.GetCheckoutSession(session_id);
|
||||
if (checkoutSession != null)
|
||||
{
|
||||
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
if (user == null)
|
||||
throw new UnauthorizedAccessException();
|
||||
|
||||
if (user.BillingCustomer == null)
|
||||
{
|
||||
BillingHelper.CreateCustomer(_dbContext, user, checkoutSession.CustomerId);
|
||||
}
|
||||
|
||||
var subscription = billingService.GetSubscription(checkoutSession.SubscriptionId);
|
||||
if (subscription != null)
|
||||
{
|
||||
@ -260,32 +255,6 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
return Redirect(Url.SubRouteUrl("billing", "Billing.ViewSubscriptions"));
|
||||
}
|
||||
|
||||
public IActionResult CancelSubscription(string subscriptionId, string productId)
|
||||
{
|
||||
// Get Subscription Info
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var subscription = billingService.GetSubscription(subscriptionId);
|
||||
if (subscription == null)
|
||||
throw new ArgumentException("Invalid Subscription Id", "subscriptionId");
|
||||
|
||||
if (!subscription.Prices.Exists(p => p.ProductId == productId))
|
||||
throw new ArgumentException("Subscription does not relate to product", "productId");
|
||||
|
||||
var product = billingService.GetProduct(productId);
|
||||
if (product == null)
|
||||
throw new ArgumentException("Product does not exist", "productId");
|
||||
|
||||
var result = billingService.CancelSubscription(subscriptionId);
|
||||
|
||||
var vm = new CancelSubscriptionViewModel()
|
||||
{
|
||||
ProductName = product.Name
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
public IActionResult SubscriptionSuccess(string priceId)
|
||||
{
|
||||
var vm = new SubscriptionSuccessViewModel();
|
||||
|
@ -1493,5 +1493,31 @@ namespace Teknik.Areas.Users.Controllers
|
||||
}
|
||||
return Json(new { error = "Invalid Type" });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult CancelSubscription(string subscriptionId, string productId)
|
||||
{
|
||||
// Get Subscription Info
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var subscription = billingService.GetSubscription(subscriptionId);
|
||||
if (subscription == null)
|
||||
return Json(new { error = "Invalid Subscription Id" });
|
||||
|
||||
if (!subscription.Prices.Exists(p => p.ProductId == productId))
|
||||
return Json(new { error = "Subscription does not relate to product" });
|
||||
|
||||
var product = billingService.GetProduct(productId);
|
||||
if (product == null)
|
||||
return Json(new { error = "Product does not exist" });
|
||||
|
||||
var result = billingService.CancelSubscription(subscriptionId);
|
||||
|
||||
if (result)
|
||||
return Json(new { result = true });
|
||||
|
||||
return Json(new { error = "Unable to cancel subscription" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,18 +6,24 @@
|
||||
Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h2>Billing Information</h2>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-12">
|
||||
<a href="@Model.PortalUrl">Click here</a> to view/modify your billing information and invoices.</>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var cancelSubscriptionURL = '@Url.SubRouteUrl("billing", "User.Action", new { action = "CancelSubscription" })';
|
||||
</script>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.PortalUrl))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h2>Billing Information</h2>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-12">
|
||||
<a href="@Model.PortalUrl">Click here</a> to view/modify your billing information and invoices.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h2>Active Subscriptions</h2>
|
||||
@ -32,9 +38,10 @@
|
||||
{
|
||||
foreach (var subscription in Model.Subscriptions)
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<h4 class="list-group-item-heading">@subscription.ProductName: <strong>@(StringHelper.GetBytesReadable(subscription.Storage))</strong> for <strong>@($"${subscription.Price:0.00} / {subscription.Interval}")</strong></h4>
|
||||
<p><a href="@(Url.SubRouteUrl("billing", "Billing.CancelSubscription", new { subscriptionId = subscription.SubscriptionId, productId = subscription.ProductId }))">Cancel Subscription</a></p>
|
||||
<li class="list-group-item" id="@subscription.SubscriptionId">
|
||||
<h4 class="list-group-item-heading pull-left">@subscription.ProductName: <strong>@(StringHelper.GetBytesReadable(subscription.Storage))</strong> for <strong>@($"${subscription.Price:0.00} / {subscription.Interval}")</strong></h4>
|
||||
<button role="button" class="btn btn-info cancel-subscription-button pull-right" data-subscription-id="@subscription.SubscriptionId" data-product-id="@subscription.ProductId">Cancel Subscription</button>
|
||||
<div class="clearfix"></div>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@ -46,3 +53,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bundle src="js/user.settings.billing.min.js" append-version="true"></bundle>
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Oidc": "readonly",
|
||||
|
||||
// Common.js Functions
|
||||
"confirmDialog": "readonly",
|
||||
"deleteConfirm": "readonly",
|
||||
"disableButton": "readonly",
|
||||
"enableButton": "readonly",
|
||||
|
43
Teknik/Scripts/User/BillingSettings.js
Normal file
43
Teknik/Scripts/User/BillingSettings.js
Normal file
@ -0,0 +1,43 @@
|
||||
/* globals cancelSubscriptionURL */
|
||||
$(document).ready(function () {
|
||||
$('.cancel-subscription-button').click(function () {
|
||||
disableButton('#cancel_subscription', 'Canceling Subscription...');
|
||||
|
||||
var subscriptionId = $(this).data('subscription-id');
|
||||
var productId = $(this).data('product-id');
|
||||
var element = $('#activeSubscriptionList [id="' + subscriptionId + '"');
|
||||
|
||||
confirmDialog('Confirm', 'Back', 'Are you sure you want to cancel your subscription?', function (result) {
|
||||
if (result) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: cancelSubscriptionURL,
|
||||
data: AddAntiForgeryToken({ subscriptionId: subscriptionId, productId: productId }),
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.result) {
|
||||
element.remove();
|
||||
$("#top_msg").css('display', 'inline', 'important');
|
||||
$("#top_msg").html('<div class="alert alert-success alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>Subscription successfully canceled.</div>');
|
||||
}
|
||||
else {
|
||||
$("#top_msg").css('display', 'inline', 'important');
|
||||
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response) + '</div>');
|
||||
}
|
||||
},
|
||||
error: function (response) {
|
||||
$("#top_msg").css('display', 'inline', 'important');
|
||||
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response.responseText) + '</div>');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
enableButton('#cancel_subscription', 'Cancel Subscription');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -117,15 +117,19 @@ $(function () {
|
||||
});
|
||||
|
||||
function deleteConfirm(message, callback) {
|
||||
confirmDialog('Delete', 'Cancel', message, callback);
|
||||
}
|
||||
|
||||
function confirmDialog(confirmLabel, cancelLabel, message, callback) {
|
||||
bootbox.confirm({
|
||||
message: "<h3>" + message + "</h3>",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Delete',
|
||||
label: confirmLabel,
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
label: cancelLabel,
|
||||
className: 'btn-default'
|
||||
}
|
||||
},
|
||||
|
@ -260,6 +260,12 @@
|
||||
"./wwwroot/js/app/User/AccountSettings.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "./wwwroot/js/user.settings.billing.min.js",
|
||||
"inputFiles": [
|
||||
"./wwwroot/js/app/User/BillingSettings.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "./wwwroot/js/user.settings.security.min.js",
|
||||
"inputFiles": [
|
||||
|
Loading…
Reference in New Issue
Block a user