mirror of
https://git.teknik.io/Teknikode/Teknik.git
synced 2023-08-02 14:16:22 +02:00
Added billing/subscriptions
This commit is contained in:
parent
63ef371a4a
commit
543df28eb7
@ -9,7 +9,7 @@ namespace Teknik.BillingCore
|
||||
{
|
||||
public static class BillingFactory
|
||||
{
|
||||
public static BillingService GetStorageService(BillingConfig config)
|
||||
public static BillingService GetBillingService(BillingConfig config)
|
||||
{
|
||||
switch (config.Type)
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -18,7 +19,7 @@ namespace Teknik.BillingCore
|
||||
}
|
||||
|
||||
public abstract object GetCustomer(string id);
|
||||
public abstract bool CreateCustomer(string email);
|
||||
public abstract string CreateCustomer(string username, string email);
|
||||
|
||||
public abstract List<Product> GetProductList();
|
||||
public abstract Product GetProduct(string productId);
|
||||
@ -28,10 +29,15 @@ namespace Teknik.BillingCore
|
||||
|
||||
public abstract List<Subscription> GetSubscriptionList(string customerId);
|
||||
public abstract Subscription GetSubscription(string subscriptionId);
|
||||
public abstract Tuple<bool, string, string> CreateSubscription(string customerId, string priceId);
|
||||
public abstract bool EditSubscription(Subscription subscription);
|
||||
public abstract bool RemoveSubscription(string subscriptionId);
|
||||
public abstract Subscription CreateSubscription(string customerId, string priceId);
|
||||
public abstract Subscription EditSubscriptionPrice(string subscriptionId, string priceId);
|
||||
public abstract bool CancelSubscription(string subscriptionId);
|
||||
|
||||
public abstract void SyncSubscriptions();
|
||||
public abstract CheckoutSession CreateCheckoutSession(string customerId, string priceId, string successUrl, string cancelUrl);
|
||||
public abstract CheckoutSession GetCheckoutSession(string sessionId);
|
||||
|
||||
public abstract Task<Event> ParseEvent(HttpRequest request);
|
||||
public abstract CheckoutSession ProcessCheckoutCompletedEvent(Event e);
|
||||
public abstract Customer ProcessCustomerEvent(Event e);
|
||||
}
|
||||
}
|
||||
|
17
BillingCore/Models/CheckoutResult.cs
Normal file
17
BillingCore/Models/CheckoutResult.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.BillingCore.Models
|
||||
{
|
||||
public class CheckoutResult
|
||||
{
|
||||
public string CustomerId { get; set; }
|
||||
|
||||
public string SubscriptionId { get; set; }
|
||||
|
||||
public PaymentStatus PaymentStatus { get; set; }
|
||||
}
|
||||
}
|
17
BillingCore/Models/CheckoutSession.cs
Normal file
17
BillingCore/Models/CheckoutSession.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.BillingCore.Models
|
||||
{
|
||||
public class CheckoutSession
|
||||
{
|
||||
public string PaymentIntentId { get; set; }
|
||||
public string CustomerId { get; set; }
|
||||
public string SubscriptionId { get; set; }
|
||||
public PaymentStatus PaymentStatus { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
20
BillingCore/Models/Customer.cs
Normal file
20
BillingCore/Models/Customer.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.BillingCore.Models
|
||||
{
|
||||
public class Customer
|
||||
{
|
||||
public string CustomerId { get; set; }
|
||||
|
||||
public List<Subscription> Subscriptions { get; set; }
|
||||
|
||||
public Customer()
|
||||
{
|
||||
Subscriptions = new List<Subscription>();
|
||||
}
|
||||
}
|
||||
}
|
15
BillingCore/Models/Event.cs
Normal file
15
BillingCore/Models/Event.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.BillingCore.Models
|
||||
{
|
||||
public class Event
|
||||
{
|
||||
public EventType EventType { get; set; }
|
||||
|
||||
public object Data { get; set; }
|
||||
}
|
||||
}
|
16
BillingCore/Models/EventType.cs
Normal file
16
BillingCore/Models/EventType.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.BillingCore.Models
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
Unknown,
|
||||
CheckoutComplete,
|
||||
SubscriptionDeleted,
|
||||
SubscriptionUpdated
|
||||
}
|
||||
}
|
15
BillingCore/Models/PaymentStatus.cs
Normal file
15
BillingCore/Models/PaymentStatus.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.BillingCore.Models
|
||||
{
|
||||
public enum PaymentStatus
|
||||
{
|
||||
Paid,
|
||||
Unpaid,
|
||||
NoPaymentRequired
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ namespace Teknik.BillingCore.Models
|
||||
public decimal? Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public long Storage { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
public Interval Interval { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ namespace Teknik.BillingCore.Models
|
||||
public string CustomerId { get; set; }
|
||||
public SubscriptionStatus Status { get; set; }
|
||||
public List<Price> Prices { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
using Stripe;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Stripe;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Teknik.BillingCore.Models;
|
||||
using Teknik.Configuration;
|
||||
|
||||
@ -16,21 +19,33 @@ namespace Teknik.BillingCore
|
||||
StripeConfiguration.ApiKey = config.StripeSecretApiKey;
|
||||
}
|
||||
|
||||
public override object GetCustomer(string id)
|
||||
public override string GetCustomer(string email)
|
||||
{
|
||||
var service = new CustomerService();
|
||||
return service.Get(id);
|
||||
if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
var service = new CustomerService();
|
||||
var foundCustomer = service.Get(email);
|
||||
if (foundCustomer != null)
|
||||
return foundCustomer.Id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool CreateCustomer(string email)
|
||||
public override string CreateCustomer(string username, string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(username))
|
||||
throw new ArgumentNullException("username");
|
||||
|
||||
var options = new CustomerCreateOptions
|
||||
{
|
||||
Name = username,
|
||||
Email = email,
|
||||
Description = $"Customer for account {username}"
|
||||
};
|
||||
var service = new CustomerService();
|
||||
var customer = service.Create(options);
|
||||
return customer != null;
|
||||
return customer.Id;
|
||||
}
|
||||
|
||||
public override List<Models.Product> GetProductList()
|
||||
@ -52,10 +67,13 @@ namespace Teknik.BillingCore
|
||||
|
||||
public override Models.Product GetProduct(string productId)
|
||||
{
|
||||
var productService = new ProductService();
|
||||
Stripe.Product product = productService.Get(productId);
|
||||
if (product != null)
|
||||
return ConvertProduct(product);
|
||||
if (!string.IsNullOrEmpty(productId))
|
||||
{
|
||||
var productService = new ProductService();
|
||||
Stripe.Product product = productService.Get(productId);
|
||||
if (product != null)
|
||||
return ConvertProduct(product);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -63,31 +81,39 @@ namespace Teknik.BillingCore
|
||||
public override List<Models.Price> GetPriceList(string productId)
|
||||
{
|
||||
var foundPrices = new List<Models.Price>();
|
||||
var options = new PriceListOptions
|
||||
if (!string.IsNullOrEmpty(productId))
|
||||
{
|
||||
Active = true,
|
||||
Product = productId
|
||||
};
|
||||
|
||||
var priceService = new PriceService();
|
||||
var priceList = priceService.List(options);
|
||||
if (priceList != null)
|
||||
{
|
||||
foreach (var price in priceList)
|
||||
var options = new PriceListOptions
|
||||
{
|
||||
foundPrices.Add(ConvertPrice(price));
|
||||
Active = true,
|
||||
Product = productId
|
||||
};
|
||||
options.AddExpand("data.product");
|
||||
|
||||
var priceService = new PriceService();
|
||||
var priceList = priceService.List(options);
|
||||
if (priceList != null)
|
||||
{
|
||||
foreach (var price in priceList)
|
||||
{
|
||||
foundPrices.Add(ConvertPrice(price));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundPrices;
|
||||
}
|
||||
|
||||
public override Models.Price GetPrice(string priceId)
|
||||
{
|
||||
|
||||
var priceService = new PriceService();
|
||||
var price = priceService.Get(priceId);
|
||||
if (price != null)
|
||||
return ConvertPrice(price);
|
||||
if (!string.IsNullOrEmpty(priceId))
|
||||
{
|
||||
var options = new PriceGetOptions();
|
||||
var priceService = new PriceService();
|
||||
var price = priceService.Get(priceId, options);
|
||||
if (price != null)
|
||||
return ConvertPrice(price);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -118,62 +144,176 @@ namespace Teknik.BillingCore
|
||||
|
||||
public override Models.Subscription GetSubscription(string subscriptionId)
|
||||
{
|
||||
var subService = new SubscriptionService();
|
||||
var sub = subService.Get(subscriptionId);
|
||||
if (sub != null)
|
||||
return ConvertSubscription(sub);
|
||||
if (!string.IsNullOrEmpty(subscriptionId))
|
||||
{
|
||||
var subService = new SubscriptionService();
|
||||
var sub = subService.Get(subscriptionId);
|
||||
if (sub != null)
|
||||
return ConvertSubscription(sub);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override Tuple<bool, string, string> CreateSubscription(string customerId, string priceId)
|
||||
public override Models.Subscription CreateSubscription(string customerId, string priceId)
|
||||
{
|
||||
// Create the subscription. Note we're expanding the Subscription's
|
||||
// latest invoice and that invoice's payment_intent
|
||||
// so we can pass it to the front end to confirm the payment
|
||||
var subscriptionOptions = new SubscriptionCreateOptions
|
||||
if (!string.IsNullOrEmpty(customerId) &&
|
||||
!string.IsNullOrEmpty(priceId))
|
||||
{
|
||||
Customer = customerId,
|
||||
Items = new List<SubscriptionItemOptions>
|
||||
// Create the subscription. Note we're expanding the Subscription's
|
||||
// latest invoice and that invoice's payment_intent
|
||||
// so we can pass it to the front end to confirm the payment
|
||||
var subscriptionOptions = new SubscriptionCreateOptions
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
Customer = customerId,
|
||||
Items = new List<SubscriptionItemOptions>
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Price = priceId,
|
||||
},
|
||||
},
|
||||
PaymentBehavior = "default_incomplete",
|
||||
CancelAtPeriodEnd = false
|
||||
};
|
||||
subscriptionOptions.AddExpand("latest_invoice.payment_intent");
|
||||
var subscriptionService = new SubscriptionService();
|
||||
var subscription = subscriptionService.Create(subscriptionOptions);
|
||||
|
||||
return ConvertSubscription(subscription);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override Models.Subscription EditSubscriptionPrice(string subscriptionId, string priceId)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(subscriptionId))
|
||||
{
|
||||
var subscriptionService = new SubscriptionService();
|
||||
var subscription = subscriptionService.Get(subscriptionId);
|
||||
if (subscription != null)
|
||||
{
|
||||
var subscriptionOptions = new SubscriptionUpdateOptions()
|
||||
{
|
||||
Items = new List<SubscriptionItemOptions>
|
||||
{
|
||||
new SubscriptionItemOptions
|
||||
{
|
||||
Id = subscription.Items.Data[0].Id,
|
||||
Price = priceId,
|
||||
},
|
||||
},
|
||||
CancelAtPeriodEnd = false,
|
||||
ProrationBehavior = "create_prorations"
|
||||
};
|
||||
subscriptionOptions.AddExpand("latest_invoice.payment_intent");
|
||||
var result = subscriptionService.Update(subscriptionId, subscriptionOptions);
|
||||
if (result != null)
|
||||
return ConvertSubscription(result);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool CancelSubscription(string subscriptionId)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(subscriptionId))
|
||||
{
|
||||
var cancelOptions = new SubscriptionCancelOptions()
|
||||
{
|
||||
InvoiceNow = true
|
||||
};
|
||||
var subscriptionService = new SubscriptionService();
|
||||
var subscription = subscriptionService.Cancel(subscriptionId, cancelOptions);
|
||||
return subscription.Status == "canceled";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override CheckoutSession CreateCheckoutSession(string customerId, string priceId, string successUrl, string cancelUrl)
|
||||
{
|
||||
// Modify Success URL to include session ID variable
|
||||
var uriBuilder = new UriBuilder(successUrl);
|
||||
var paramValues = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
paramValues.Add("session_id", "{CHECKOUT_SESSION_ID}");
|
||||
uriBuilder.Query = paramValues.ToString();
|
||||
successUrl = uriBuilder.Uri.ToString();
|
||||
|
||||
var checkoutService = new Stripe.Checkout.SessionService();
|
||||
var sessionOptions = new Stripe.Checkout.SessionCreateOptions()
|
||||
{
|
||||
LineItems = new List<Stripe.Checkout.SessionLineItemOptions>()
|
||||
{
|
||||
new Stripe.Checkout.SessionLineItemOptions()
|
||||
{
|
||||
Price = priceId,
|
||||
},
|
||||
Quantity = 1
|
||||
}
|
||||
},
|
||||
PaymentBehavior = "default_incomplete",
|
||||
PaymentMethodTypes = new List<string>()
|
||||
{
|
||||
"card"
|
||||
},
|
||||
Mode = "subscription",
|
||||
SuccessUrl = successUrl,
|
||||
CancelUrl = cancelUrl,
|
||||
Customer = customerId
|
||||
};
|
||||
subscriptionOptions.AddExpand("latest_invoice.payment_intent");
|
||||
var subscriptionService = new SubscriptionService();
|
||||
sessionOptions.AddExpand("customer");
|
||||
var session = checkoutService.Create(sessionOptions);
|
||||
return ConvertCheckoutSession(session);
|
||||
}
|
||||
|
||||
public override async Task<Models.Event> ParseEvent(HttpRequest request)
|
||||
{
|
||||
var json = await new StreamReader(request.Body).ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
Stripe.Subscription subscription = subscriptionService.Create(subscriptionOptions);
|
||||
var stripeEvent = EventUtility.ConstructEvent(
|
||||
json,
|
||||
request.Headers["Stripe-Signature"],
|
||||
Config.StripeWebhookSecret
|
||||
);
|
||||
|
||||
return new Tuple<bool, string, string>(true, subscription.Id, subscription.LatestInvoice.PaymentIntent.ClientSecret);
|
||||
return ConvertEvent(stripeEvent);
|
||||
}
|
||||
catch (StripeException e)
|
||||
catch (StripeException)
|
||||
{
|
||||
return new Tuple<bool, string, string>(false, $"Failed to create subscription. {e}", null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool EditSubscription(Models.Subscription subscription)
|
||||
public override CheckoutSession ProcessCheckoutCompletedEvent(Models.Event ev)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Handle the checkout.session.completed event
|
||||
var session = ev.Data as Stripe.Checkout.Session;
|
||||
|
||||
return ConvertCheckoutSession(session);
|
||||
}
|
||||
|
||||
public override bool RemoveSubscription(string subscriptionId)
|
||||
public override Models.Customer ProcessCustomerEvent(Models.Event ev)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Handle the checkout.session.completed event
|
||||
var customer = ev.Data as Stripe.Customer;
|
||||
|
||||
return ConvertCustomer(customer);
|
||||
}
|
||||
|
||||
public override void SyncSubscriptions()
|
||||
public override CheckoutSession GetCheckoutSession(string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var checkoutService = new Stripe.Checkout.SessionService();
|
||||
var sessionOptions = new Stripe.Checkout.SessionGetOptions();
|
||||
sessionOptions.AddExpand("customer");
|
||||
var session = checkoutService.Get(sessionId, sessionOptions);
|
||||
|
||||
return ConvertCheckoutSession(session);
|
||||
}
|
||||
|
||||
private Models.Product ConvertProduct(Stripe.Product product)
|
||||
{
|
||||
if (product == null)
|
||||
return null;
|
||||
return new Models.Product()
|
||||
{
|
||||
ProductId = product.Id,
|
||||
@ -185,6 +325,8 @@ namespace Teknik.BillingCore
|
||||
|
||||
private Models.Price ConvertPrice(Stripe.Price price)
|
||||
{
|
||||
if (price == null)
|
||||
return null;
|
||||
var interval = Interval.Once;
|
||||
if (price.Type == "recurring")
|
||||
{
|
||||
@ -216,11 +358,15 @@ namespace Teknik.BillingCore
|
||||
convPrice.Amount = price.UnitAmountDecimal / 100;
|
||||
if (price.Metadata.ContainsKey("storage"))
|
||||
convPrice.Storage = long.Parse(price.Metadata["storage"]);
|
||||
if (price.Metadata.ContainsKey("fileSize"))
|
||||
convPrice.FileSize = long.Parse(price.Metadata["fileSize"]);
|
||||
return convPrice;
|
||||
}
|
||||
|
||||
private Models.Subscription ConvertSubscription(Stripe.Subscription subscription)
|
||||
{
|
||||
if (subscription == null)
|
||||
return null;
|
||||
var status = SubscriptionStatus.Incomplete;
|
||||
switch (subscription.Status)
|
||||
{
|
||||
@ -259,7 +405,77 @@ namespace Teknik.BillingCore
|
||||
Id = subscription.Id,
|
||||
CustomerId = subscription.CustomerId,
|
||||
Status = status,
|
||||
Prices = prices
|
||||
Prices = prices,
|
||||
ClientSecret = subscription.LatestInvoice?.PaymentIntent?.ClientSecret
|
||||
};
|
||||
}
|
||||
|
||||
private CheckoutSession ConvertCheckoutSession(Stripe.Checkout.Session session)
|
||||
{
|
||||
if (session == null)
|
||||
return null;
|
||||
|
||||
|
||||
var paymentStatus = PaymentStatus.Unpaid;
|
||||
switch (session.PaymentStatus)
|
||||
{
|
||||
case "paid":
|
||||
paymentStatus = PaymentStatus.Paid;
|
||||
break;
|
||||
case "unpaid":
|
||||
paymentStatus = PaymentStatus.Unpaid;
|
||||
break;
|
||||
case "no_payment_required":
|
||||
paymentStatus = PaymentStatus.NoPaymentRequired;
|
||||
break;
|
||||
}
|
||||
|
||||
return new CheckoutSession()
|
||||
{
|
||||
PaymentIntentId = session.PaymentIntentId,
|
||||
CustomerId = session.Customer.Id,
|
||||
SubscriptionId = session.SubscriptionId,
|
||||
PaymentStatus = paymentStatus,
|
||||
Url = session.Url
|
||||
};
|
||||
}
|
||||
|
||||
private Models.Customer ConvertCustomer(Stripe.Customer customer)
|
||||
{
|
||||
var returnCust = new Models.Customer()
|
||||
{
|
||||
CustomerId = customer.Id
|
||||
};
|
||||
|
||||
if (customer.Subscriptions.Any())
|
||||
returnCust.Subscriptions = customer.Subscriptions.Select(s => ConvertSubscription(s)).ToList();
|
||||
|
||||
return returnCust;
|
||||
}
|
||||
|
||||
private Models.Event ConvertEvent(Stripe.Event ev)
|
||||
{
|
||||
if (ev == null)
|
||||
return null;
|
||||
|
||||
var eventType = EventType.Unknown;
|
||||
switch (ev.Type)
|
||||
{
|
||||
case Events.CheckoutSessionCompleted:
|
||||
eventType = EventType.CheckoutComplete;
|
||||
break;
|
||||
case Events.CustomerSubscriptionDeleted:
|
||||
eventType = EventType.SubscriptionDeleted;
|
||||
break;
|
||||
case Events.CustomerSubscriptionUpdated:
|
||||
eventType = EventType.SubscriptionUpdated;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Models.Event()
|
||||
{
|
||||
EventType = eventType,
|
||||
Data = ev.Data.Object
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CommandLine;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Teknik.Areas.Users.Models;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.BillingCore;
|
||||
using Teknik.BillingCore.Models;
|
||||
using Teknik.Configuration;
|
||||
using Teknik.Data;
|
||||
using Teknik.Utilities;
|
||||
@ -43,6 +48,7 @@ namespace Teknik.BillingService
|
||||
if (options.SyncSubscriptions)
|
||||
{
|
||||
// Sync subscription information
|
||||
SyncSubscriptions(config, db);
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +71,44 @@ namespace Teknik.BillingService
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void SyncSubscriptions(Config config, TeknikEntities db)
|
||||
{
|
||||
// Get Biling Service
|
||||
var billingService = BillingFactory.GetBillingService(config.BillingConfig);
|
||||
|
||||
foreach (var user in db.Users)
|
||||
{
|
||||
string email = UserHelper.GetUserEmailAddress(config, user.Username);
|
||||
if (user.BillingCustomer != null)
|
||||
{
|
||||
// get the subscriptions for this user
|
||||
var subscriptions = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId);
|
||||
var uploadPrice = subscriptions.SelectMany(s => s.Prices).FirstOrDefault(p => p.ProductId == config.BillingConfig.UploadProductId);
|
||||
if (uploadPrice != null)
|
||||
{
|
||||
// Process Upload Settings
|
||||
user.UploadSettings.MaxUploadStorage = uploadPrice.Storage;
|
||||
user.UploadSettings.MaxUploadFileSize = uploadPrice.FileSize;
|
||||
}
|
||||
var emailPrice = subscriptions.SelectMany(s => s.Prices).FirstOrDefault(p => p.ProductId == config.BillingConfig.EmailProductId);
|
||||
if (emailPrice != null)
|
||||
{
|
||||
UserHelper.EnableUserEmail(config, email);
|
||||
UserHelper.EditUserEmailMaxSize(config, email, (int)emailPrice.Storage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No customer, so let's reset their info
|
||||
user.UploadSettings.MaxUploadStorage = config.UploadConfig.MaxStorage;
|
||||
user.UploadSettings.MaxUploadFileSize = config.UploadConfig.MaxUploadFileSize;
|
||||
UserHelper.DisableUserEmail(config, email);
|
||||
}
|
||||
db.Entry(user).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Output(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
|
@ -11,6 +11,7 @@ namespace Teknik.Configuration
|
||||
public BillingType Type { get; set; }
|
||||
public string StripePublishApiKey { get; set; }
|
||||
public string StripeSecretApiKey { get; set; }
|
||||
public string StripeWebhookSecret { get; set; }
|
||||
|
||||
public string UploadProductId { get; set; }
|
||||
public string EmailProductId { get; set; }
|
||||
@ -20,6 +21,7 @@ namespace Teknik.Configuration
|
||||
Type = BillingType.Stripe;
|
||||
StripePublishApiKey = null;
|
||||
StripeSecretApiKey = null;
|
||||
StripeWebhookSecret = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,18 +7,12 @@ namespace Teknik.Configuration
|
||||
{
|
||||
public bool UploadEnabled { get; set; }
|
||||
public bool DownloadEnabled { get; set; }
|
||||
// Max upload size in bytes
|
||||
public long MaxUploadSize { get; set; }
|
||||
// Max Upload Size for basic users
|
||||
public long MaxUploadSizeBasic { get; set; }
|
||||
// Max Upload Size for premium users
|
||||
public long MaxUploadSizePremium { get; set; }
|
||||
// Gets the maximum download size before they are forced to the download page
|
||||
public long MaxDownloadSize { get; set; }
|
||||
// Maximum total size for basic users
|
||||
public long MaxTotalSizeBasic { get; set; }
|
||||
// Maximum total size for basic users
|
||||
public long MaxTotalSizePremium { get; set; }
|
||||
// Max upload size in bytes for free/anonymous users
|
||||
public long MaxUploadFileSize { get; set; }
|
||||
// Maximum file size before they are forced to the download page
|
||||
public long MaxDownloadFileSize { get; set; }
|
||||
// Maximum storage for free users
|
||||
public long MaxStorage { get; set; }
|
||||
public int UrlLength { get; set; }
|
||||
public int DeleteKeyLength { get; set; }
|
||||
public int KeySize { get; set; }
|
||||
@ -45,12 +39,9 @@ namespace Teknik.Configuration
|
||||
{
|
||||
UploadEnabled = true;
|
||||
DownloadEnabled = true;
|
||||
MaxUploadSize = 100000000;
|
||||
MaxUploadSizeBasic = 100000000;
|
||||
MaxUploadSizePremium = 100000000;
|
||||
MaxDownloadSize = 100000000;
|
||||
MaxTotalSizeBasic = 1000000000;
|
||||
MaxTotalSizePremium = 5000000000;
|
||||
MaxUploadFileSize = 1073741824;
|
||||
MaxDownloadFileSize = 1073741824;
|
||||
MaxStorage = 5368709120;
|
||||
UrlLength = 5;
|
||||
DeleteKeyLength = 24;
|
||||
KeySize = 256;
|
||||
|
@ -163,15 +163,12 @@ namespace Teknik.ServiceWorker
|
||||
byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV);
|
||||
|
||||
|
||||
long maxUploadSize = config.UploadConfig.MaxUploadSize;
|
||||
long maxUploadSize = config.UploadConfig.MaxUploadFileSize;
|
||||
if (upload.User != null)
|
||||
{
|
||||
maxUploadSize = config.UploadConfig.MaxUploadSizeBasic;
|
||||
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(config, upload.User.Username);
|
||||
if (userInfo.AccountType == AccountType.Premium)
|
||||
{
|
||||
maxUploadSize = config.UploadConfig.MaxUploadSizePremium;
|
||||
}
|
||||
// Check account total limits
|
||||
if (upload.User.UploadSettings.MaxUploadFileSize != null)
|
||||
maxUploadSize = upload.User.UploadSettings.MaxUploadFileSize.Value;
|
||||
}
|
||||
|
||||
using (AesCounterStream aesStream = new AesCounterStream(fileStream, false, keyBytes, ivBytes))
|
||||
|
@ -3,12 +3,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.Areas.Paste;
|
||||
using Teknik.Areas.Paste.Models;
|
||||
using Teknik.Configuration;
|
||||
using Teknik.Data;
|
||||
using Teknik.StorageService;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Utilities.Cryptography;
|
||||
|
||||
namespace Teknik.ServiceWorker
|
||||
{
|
||||
@ -17,51 +20,9 @@ namespace Teknik.ServiceWorker
|
||||
public static bool RunMigration(TeknikEntities db, Config config)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
MigratePastes(db, config);
|
||||
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public static void MigratePastes(TeknikEntities db, Config config)
|
||||
{
|
||||
if (!Directory.Exists(config.PasteConfig.PasteDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(config.PasteConfig.PasteDirectory);
|
||||
}
|
||||
|
||||
var pastes = db.Pastes.Select(p => p.PasteId).ToList();
|
||||
|
||||
foreach (var pasteId in pastes)
|
||||
{
|
||||
var paste = db.Pastes.Where(p => p.PasteId == pasteId).FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(paste.Content) && string.IsNullOrEmpty(paste.FileName) && string.IsNullOrEmpty(paste.HashedPassword))
|
||||
{
|
||||
// Generate a unique file name that does not currently exist
|
||||
string filePath = FileHelper.GenerateRandomFileName(config.PasteConfig.PasteDirectory, config.PasteConfig.FileExtension, 10);
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
|
||||
string key = PasteHelper.GenerateKey(config.PasteConfig.KeySize);
|
||||
string iv = PasteHelper.GenerateIV(config.PasteConfig.BlockSize);
|
||||
|
||||
// Encrypt the contents to the file
|
||||
PasteHelper.EncryptContents(paste.Content, filePath, null, key, iv, config.PasteConfig.KeySize, config.PasteConfig.ChunkSize);
|
||||
|
||||
// Generate a deletion key
|
||||
paste.DeleteKey = StringHelper.RandomString(config.PasteConfig.DeleteKeyLength);
|
||||
|
||||
paste.Key = key;
|
||||
paste.KeySize = config.PasteConfig.KeySize;
|
||||
paste.IV = iv;
|
||||
paste.BlockSize = config.PasteConfig.BlockSize;
|
||||
|
||||
paste.FileName = fileName;
|
||||
paste.Content = string.Empty;
|
||||
|
||||
db.Entry(paste).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +187,116 @@
|
||||
"action": "Index"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Index",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "Index"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Subscriptions",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "Subscriptions",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "ViewSubscriptions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.ViewPaymentInfo",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "PaymentInfo",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "ViewPaymentInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Subscribe",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "Subscribe/{priceId}/{subscriptionId?}",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "Subscribe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Checkout",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "Checkout/{priceId}",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "Checkout"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.CheckoutComplete",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "CheckoutComplete",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "CheckoutComplete"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.EditSubscription",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "EditSubscription/{priceId}",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "EditSubscription"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.CancelSubscription",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "CancelSubscription/{subscriptionId}/{productId}",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "CancelSubscription"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.SubscriptionSuccess",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "SubscriptionSuccess/{priceId}",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "SubscriptionSuccess"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Action",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "Action/{action}",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "Index"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Blog.Blog",
|
||||
"HostTypes": [ "Full" ],
|
||||
@ -902,6 +1012,17 @@
|
||||
"action": "AccountSettings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "User.BillingSettings",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "account" ],
|
||||
"Pattern": "Settings/Billing",
|
||||
"Area": "User",
|
||||
"Defaults": {
|
||||
"controller": "User",
|
||||
"action": "BillingSettings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "User.SecuritySettings",
|
||||
"HostTypes": [ "Full" ],
|
||||
@ -1045,50 +1166,6 @@
|
||||
"action": "ViewRawPGP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Index",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "Index"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Subscriptions",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "Subscriptions",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "ViewSubscriptions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.ViewPaymentInfo",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "PaymentInfo",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "ViewPaymentInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Billing.Action",
|
||||
"HostTypes": [ "Full" ],
|
||||
"SubDomains": [ "billing" ],
|
||||
"Pattern": "Action/{action}",
|
||||
"Area": "Billing",
|
||||
"Defaults": {
|
||||
"controller": "Billing",
|
||||
"action": "Index"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Vault.Index",
|
||||
"HostTypes": [ "Full" ],
|
||||
|
104
Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs
Normal file
104
Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.Areas.Users.Models;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.BillingCore;
|
||||
using Teknik.BillingCore.Models;
|
||||
using Teknik.Configuration;
|
||||
using Teknik.Data;
|
||||
using Teknik.Logging;
|
||||
|
||||
namespace Teknik.Areas.API.V1.Controllers
|
||||
{
|
||||
public class BillingAPIv1Controller : APIv1Controller
|
||||
{
|
||||
public BillingAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
|
||||
|
||||
public async Task<IActionResult> HandleCheckoutCompleteEvent()
|
||||
{
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var billingEvent = await billingService.ParseEvent(Request);
|
||||
|
||||
if (billingEvent == null)
|
||||
return BadRequest();
|
||||
|
||||
var session = billingService.ProcessCheckoutCompletedEvent(billingEvent);
|
||||
if (session.PaymentStatus == PaymentStatus.Paid)
|
||||
{
|
||||
var subscription = billingService.GetSubscription(session.SubscriptionId);
|
||||
|
||||
ProcessSubscription(session.CustomerId, subscription);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> HandleSubscriptionChange()
|
||||
{
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var billingEvent = await billingService.ParseEvent(Request);
|
||||
|
||||
if (billingEvent == null)
|
||||
return BadRequest();
|
||||
|
||||
var customerEvent = billingService.ProcessCustomerEvent(billingEvent);
|
||||
|
||||
foreach (var subscription in customerEvent.Subscriptions)
|
||||
{
|
||||
ProcessSubscription(customerEvent.CustomerId, subscription);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private void ProcessSubscription(string customerId, Subscription subscription)
|
||||
{
|
||||
// They have paid, so let's get their subscription and update their user settings
|
||||
var user = _dbContext.Users.FirstOrDefault(u => u.BillingCustomer != null &&
|
||||
u.BillingCustomer.CustomerId == customerId);
|
||||
if (user != null)
|
||||
{
|
||||
var isActive = subscription.Status == SubscriptionStatus.Active;
|
||||
foreach (var price in subscription.Prices)
|
||||
{
|
||||
ProcessPrice(user, price, isActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPrice(User user, Price price, bool active)
|
||||
{
|
||||
// What type of subscription is this
|
||||
if (_config.BillingConfig.UploadProductId == price.ProductId)
|
||||
{
|
||||
// Process Upload Settings
|
||||
user.UploadSettings.MaxUploadStorage = active ? price.Storage : _config.UploadConfig.MaxStorage;
|
||||
user.UploadSettings.MaxUploadFileSize = active ? price.FileSize : _config.UploadConfig.MaxUploadFileSize;
|
||||
_dbContext.Entry(user).State = EntityState.Modified;
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
else if (_config.BillingConfig.EmailProductId == price.ProductId)
|
||||
{
|
||||
// Process an email subscription
|
||||
string email = UserHelper.GetUserEmailAddress(_config, user.Username);
|
||||
if (active)
|
||||
{
|
||||
UserHelper.EnableUserEmail(_config, email);
|
||||
UserHelper.EditUserEmailMaxSize(_config, email, (int)price.Storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserHelper.DisableUserEmail(_config, email);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -40,22 +40,17 @@ namespace Teknik.Areas.API.V1.Controllers
|
||||
{
|
||||
if (model.file != null)
|
||||
{
|
||||
long maxUploadSize = _config.UploadConfig.MaxUploadSize;
|
||||
long maxUploadSize = _config.UploadConfig.MaxUploadFileSize;
|
||||
long maxTotalSize = _config.UploadConfig.MaxUploadFileSize;
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic;
|
||||
long maxTotalSize = _config.UploadConfig.MaxTotalSizeBasic;
|
||||
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name);
|
||||
if (userInfo.AccountType == AccountType.Premium)
|
||||
{
|
||||
maxUploadSize = _config.UploadConfig.MaxUploadSizePremium;
|
||||
maxTotalSize = _config.UploadConfig.MaxTotalSizePremium;
|
||||
}
|
||||
|
||||
// Check account total limits
|
||||
var user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
if (user.UploadSettings.MaxUploadStorage != null)
|
||||
maxTotalSize = user.UploadSettings.MaxUploadStorage.Value;
|
||||
if (user.UploadSettings.MaxUploadFileSize != null)
|
||||
maxUploadSize = user.UploadSettings.MaxUploadFileSize.Value;
|
||||
|
||||
var userUploadSize = user.Uploads.Sum(u => u.ContentLength);
|
||||
if (userUploadSize + model.file.Length > maxTotalSize)
|
||||
{
|
||||
|
@ -6,12 +6,16 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Teknik.Areas.About.ViewModels;
|
||||
using Teknik.Areas.Billing.ViewModels;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.BillingCore;
|
||||
using Teknik.Configuration;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Data;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Logging;
|
||||
using Teknik.Utilities.Routing;
|
||||
|
||||
namespace Teknik.Areas.About.Controllers
|
||||
{
|
||||
@ -27,8 +31,123 @@ namespace Teknik.Areas.About.Controllers
|
||||
{
|
||||
ViewBag.Title = "About";
|
||||
ViewBag.Description = "What is Teknik?";
|
||||
var vm = new AboutViewModel();
|
||||
|
||||
return View(new AboutViewModel());
|
||||
// Get Biling Service
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var subVM = new SubscriptionsViewModel();
|
||||
|
||||
// Get current subscriptions
|
||||
var curPrices = new Dictionary<string, List<string>>();
|
||||
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
var user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
if (user.BillingCustomer != null)
|
||||
{
|
||||
var currentSubs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId);
|
||||
foreach (var curSub in currentSubs)
|
||||
{
|
||||
foreach (var price in curSub.Prices)
|
||||
{
|
||||
if (!curPrices.ContainsKey(price.ProductId))
|
||||
curPrices[price.ProductId] = new List<string>();
|
||||
curPrices[price.ProductId].Add(price.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool hasUploadProduct = curPrices.ContainsKey(_config.BillingConfig.UploadProductId);
|
||||
bool hasEmailProduct = curPrices.ContainsKey(_config.BillingConfig.EmailProductId);
|
||||
var curUploadPrice = string.Empty;
|
||||
if (curPrices.ContainsKey(_config.BillingConfig.UploadProductId))
|
||||
curUploadPrice = curPrices[_config.BillingConfig.UploadProductId].FirstOrDefault();
|
||||
var curEmailPrice = string.Empty;
|
||||
if (curPrices.ContainsKey(_config.BillingConfig.EmailProductId))
|
||||
curEmailPrice = curPrices[_config.BillingConfig.EmailProductId].FirstOrDefault();
|
||||
|
||||
// Show Free Subscription
|
||||
subVM.UploadSubscriptions.Add(new SubscriptionViewModel()
|
||||
{
|
||||
CurrentSubMonthly = !hasUploadProduct && User.Identity.IsAuthenticated,
|
||||
SubscribeText = "Free",
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("about", "About.Index"),
|
||||
BaseStorage = _config.UploadConfig.MaxStorage
|
||||
});
|
||||
|
||||
// Get Upload Prices
|
||||
var uploadProduct = billingService.GetProduct(_config.BillingConfig.UploadProductId);
|
||||
if (uploadProduct != null)
|
||||
{
|
||||
bool handledFirst = false;
|
||||
foreach (var priceGrp in uploadProduct.Prices.GroupBy(p => p.Storage).OrderBy(p => p.Key))
|
||||
{
|
||||
// Get Monthly prices
|
||||
var priceMonth = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Month);
|
||||
|
||||
// Get Yearly prices
|
||||
var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year);
|
||||
|
||||
var curPrice = priceGrp.FirstOrDefault(p => p.Id == curUploadPrice);
|
||||
subVM.UploadSubscriptions.Add(new SubscriptionViewModel()
|
||||
{
|
||||
Recommended = !handledFirst,
|
||||
CurrentSubMonthly = curPrice?.Id == priceMonth?.Id,
|
||||
CurrentSubYearly = curPrice?.Id == priceYear?.Id,
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("billing",
|
||||
hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceMonth?.Id }),
|
||||
SubscribeUrlYearly = Url.SubRouteUrl("billing",
|
||||
hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceYear?.Id }),
|
||||
BaseStorage = priceMonth?.Storage,
|
||||
BasePriceMonthly = priceMonth?.Amount,
|
||||
BasePriceYearly = priceYear?.Amount
|
||||
});
|
||||
handledFirst = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get Email Prices
|
||||
var emailProduct = billingService.GetProduct(_config.BillingConfig.EmailProductId);
|
||||
if (emailProduct != null)
|
||||
{
|
||||
bool handledFirst = false;
|
||||
foreach (var priceGrp in emailProduct.Prices.GroupBy(p => p.Storage).OrderBy(p => p.Key))
|
||||
{
|
||||
// Get Monthly prices
|
||||
var priceMonth = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Month);
|
||||
|
||||
// Get Yearly prices
|
||||
var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year);
|
||||
|
||||
var curPrice = priceGrp.FirstOrDefault(p => p.Id == curEmailPrice);
|
||||
var emailSub = new SubscriptionViewModel()
|
||||
{
|
||||
Recommended = !handledFirst,
|
||||
CurrentSubMonthly = curPrice?.Id == priceMonth?.Id,
|
||||
CurrentSubYearly = curPrice?.Id == priceYear?.Id,
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("billing",
|
||||
hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceMonth?.Id }),
|
||||
SubscribeUrlYearly = Url.SubRouteUrl("billing",
|
||||
hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceYear?.Id }),
|
||||
BaseStorage = priceMonth?.Storage,
|
||||
BasePriceMonthly = priceMonth?.Amount,
|
||||
BasePriceYearly = priceYear?.Amount
|
||||
};
|
||||
if (!handledFirst)
|
||||
emailSub.PanelOffset = "3";
|
||||
subVM.EmailSubscriptions.Add(emailSub);
|
||||
handledFirst = true;
|
||||
}
|
||||
}
|
||||
|
||||
vm.Subscriptions = subVM;
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.Areas.Billing.ViewModels;
|
||||
using Teknik.ViewModels;
|
||||
|
||||
namespace Teknik.Areas.About.ViewModels
|
||||
{
|
||||
public class AboutViewModel : ViewModelBase
|
||||
{
|
||||
public SubscriptionsViewModel Subscriptions { get; set; }
|
||||
|
||||
public AboutViewModel()
|
||||
{
|
||||
Subscriptions = new SubscriptionsViewModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
@model Teknik.Areas.About.ViewModels.AboutViewModel
|
||||
|
||||
@{
|
||||
string lifetimeAccount = "The account will not get deleted for inactivity as defined in the ToS";
|
||||
string apiAccountAccess = "Passing user credentials allows linking for Uploads/Pastes/Shortened URLs and other account specific features";
|
||||
@{
|
||||
string extraUsage = string.Empty;
|
||||
}
|
||||
|
||||
<div class="container">
|
||||
@ -16,57 +15,35 @@
|
||||
<p>
|
||||
You can view our complete activity and statistics by visiting the <a href="@Url.SubRouteUrl("stats", "Stats.Index")">statistics</a> page.
|
||||
</p>
|
||||
|
||||
<br />
|
||||
<hr>
|
||||
<hr />
|
||||
|
||||
<h2 class="text-center">What can you do with Teknik?</h2>
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
<table class="table table-bordered">
|
||||
<!-- Heading -->
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center"><a href="@Url.SubRouteUrl("account", "User.GetPremium")">Premium Account</a></th>
|
||||
<th class="text-center"><a href="@Url.SubRouteUrl("account", "User.Register")">Basic Account</a></th>
|
||||
<th class="text-center">No Account</th>
|
||||
<th class="text-center"><a href="@Url.SubRouteUrl("account", "User.Register")">Free Account</a></th>
|
||||
<th class="text-center">Anonymous</th>
|
||||
</tr>
|
||||
<!-- Prices -->
|
||||
<tr>
|
||||
<td class="text-left"></td>
|
||||
<td class="text-center">$@Config.UserConfig.PremiumAccountPrice @Config.UserConfig.PaymentType</td>
|
||||
<td class="text-center">Free</td>
|
||||
<td class="text-center">Free</td>
|
||||
</tr>
|
||||
<!-- Premium Features -->
|
||||
<tr>
|
||||
<td class="text-left"><span data-toggle="tooltip" data-placement="top" title="@lifetimeAccount">Lifetime Account <small>*</small></span></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Email Account</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
</tr>
|
||||
<!-- Basic Features -->
|
||||
<tr>
|
||||
<td class="text-left">Git Repositories</td>
|
||||
<td class="text-center">Unlimited Public & Private Repos</td>
|
||||
<td class="text-center">Unlimited Public & Private Repos</td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Personal Blog</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Vault and Paste Editing</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -81,101 +58,72 @@
|
||||
</ul>
|
||||
</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-times fa-2x text-danger"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left"><span data-toggle="tooltip" data-placement="top">Service API</span></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-danger"></i></td>
|
||||
</tr>
|
||||
<!-- No Account Features -->
|
||||
<tr>
|
||||
<td class="text-left">Pastebin</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">File Uploads</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Max Upload Filesize</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizePremium)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizeBasic)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSize)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadFileSize)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadFileSize)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Max Embedded Filesize</td>
|
||||
<td class="text-center">Unlimited</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadSize)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadSize)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadFileSize)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadFileSize)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Upload Storage</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxTotalSizePremium)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxTotalSizeBasic)</td>
|
||||
<td class="text-center">@StringHelper.GetBytesReadable(Config.UploadConfig.MaxStorage)</td>
|
||||
<td class="text-center">No Limit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Upload Expiration</td>
|
||||
<td class="text-center">Never</td>
|
||||
<td class="text-center">Never</td>
|
||||
<td class="text-center">24 Hours</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Url Shortening</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Vault Creation</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Technical Podcasts</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">Mumble Server</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left">IRC Server</td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left"><span data-toggle="tooltip" data-placement="top" title="@apiAccountAccess">Service API <small>**</small></span></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
<td class="text-center"><i class="fa fa-check fa-2x text-success"></i></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
<p>
|
||||
<small>
|
||||
* @lifetimeAccount
|
||||
<br />
|
||||
** @apiAccountAccess
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
@await Html.PartialAsync("../../Areas/Billing/Views/Billing/ViewSubscriptions", Model.Subscriptions);
|
||||
|
||||
<br />
|
||||
<hr>
|
||||
|
||||
<h2 class="text-center">Want to help?</h2>
|
||||
<br />
|
||||
<p>
|
||||
<p class="text-center">
|
||||
Teknik's source code can be located on our <a href="https://git.teknik.io/Teknikode/">Git Repository</a> as well as all our internal tools and projects.
|
||||
<br />
|
||||
<br />
|
||||
|
@ -1,11 +1,15 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.Areas.Billing.Models;
|
||||
using Teknik.Areas.Billing.ViewModels;
|
||||
using Teknik.Areas.Users.Models;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.BillingCore;
|
||||
using Teknik.Configuration;
|
||||
using Teknik.Controllers;
|
||||
@ -15,6 +19,7 @@ using Teknik.Utilities.Routing;
|
||||
|
||||
namespace Teknik.Areas.Billing.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Area("Billing")]
|
||||
public class BillingController : DefaultController
|
||||
{
|
||||
@ -32,39 +37,47 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
var subVM = new SubscriptionsViewModel();
|
||||
|
||||
// Get Biling Service
|
||||
var billingService = BillingFactory.GetStorageService(_config.BillingConfig);
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
// Get current subscriptions
|
||||
string curSubId = null;
|
||||
var curSubs = new Dictionary<string, List<string>>();
|
||||
var curPrices = new Dictionary<string, List<string>>();
|
||||
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
var currentSubs = billingService.GetSubscriptionList(User.Identity.Name);
|
||||
foreach (var curSub in currentSubs)
|
||||
var user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
if (user.BillingCustomer != null)
|
||||
{
|
||||
foreach (var price in curSub.Prices)
|
||||
var currentSubs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId);
|
||||
foreach (var curSub in currentSubs)
|
||||
{
|
||||
if (!curSubs.ContainsKey(price.ProductId))
|
||||
curSubs[price.ProductId] = new List<string>();
|
||||
curSubs[price.ProductId].Add(price.Id);
|
||||
foreach (var price in curSub.Prices)
|
||||
{
|
||||
if (!curPrices.ContainsKey(price.ProductId))
|
||||
curPrices[price.ProductId] = new List<string>();
|
||||
curPrices[price.ProductId].Add(price.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool hasUploadProduct = curPrices.ContainsKey(_config.BillingConfig.UploadProductId);
|
||||
bool hasEmailProduct = curPrices.ContainsKey(_config.BillingConfig.EmailProductId);
|
||||
var curUploadPrice = string.Empty;
|
||||
if (curPrices.ContainsKey(_config.BillingConfig.UploadProductId))
|
||||
curUploadPrice = curPrices[_config.BillingConfig.UploadProductId].FirstOrDefault();
|
||||
var curEmailPrice = string.Empty;
|
||||
if (curPrices.ContainsKey(_config.BillingConfig.EmailProductId))
|
||||
curEmailPrice = curPrices[_config.BillingConfig.EmailProductId].FirstOrDefault();
|
||||
|
||||
// Show Free Subscription
|
||||
subVM.UploadSubscriptions.Add(new SubscriptionViewModel()
|
||||
{
|
||||
CurrentPlan = curSubId == null,
|
||||
CurrentSubMonthly = !hasUploadProduct && User.Identity.IsAuthenticated,
|
||||
SubscribeText = "Free",
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("account", "User.Register"),
|
||||
BaseStorage = _config.UploadConfig.MaxUploadSizeBasic
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("about", "About.Index"),
|
||||
BaseStorage = _config.UploadConfig.MaxStorage
|
||||
});
|
||||
|
||||
// Get Upload Prices
|
||||
var curUploadSubs = new List<string>();
|
||||
if (curSubs.ContainsKey(_config.BillingConfig.UploadProductId))
|
||||
curUploadSubs = curSubs[_config.BillingConfig.UploadProductId];
|
||||
var uploadProduct = billingService.GetProduct(_config.BillingConfig.UploadProductId);
|
||||
if (uploadProduct != null)
|
||||
{
|
||||
@ -77,13 +90,18 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
// Get Yearly prices
|
||||
var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year);
|
||||
|
||||
var isCurrent = curUploadSubs.Exists(s => priceGrp.FirstOrDefault(p => p.ProductId == s) != null);
|
||||
var curPrice = priceGrp.FirstOrDefault(p => p.Id == curUploadPrice);
|
||||
subVM.UploadSubscriptions.Add(new SubscriptionViewModel()
|
||||
{
|
||||
Recommended = !handledFirst,
|
||||
CurrentPlan = isCurrent,
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceMonth?.Id }),
|
||||
SubscribeUrlYearly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceYear?.Id }),
|
||||
CurrentSubMonthly = curPrice?.Id == priceMonth?.Id,
|
||||
CurrentSubYearly = curPrice?.Id == priceYear?.Id,
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("billing",
|
||||
hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceMonth?.Id }),
|
||||
SubscribeUrlYearly = Url.SubRouteUrl("billing",
|
||||
hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceYear?.Id }),
|
||||
BaseStorage = priceMonth?.Storage,
|
||||
BasePriceMonthly = priceMonth?.Amount,
|
||||
BasePriceYearly = priceYear?.Amount
|
||||
@ -93,9 +111,6 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
}
|
||||
|
||||
// Get Email Prices
|
||||
var curEmailSubs = new List<string>();
|
||||
if (curSubs.ContainsKey(_config.BillingConfig.EmailProductId))
|
||||
curEmailSubs = curSubs[_config.BillingConfig.EmailProductId];
|
||||
var emailProduct = billingService.GetProduct(_config.BillingConfig.EmailProductId);
|
||||
if (emailProduct != null)
|
||||
{
|
||||
@ -108,13 +123,18 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
// Get Yearly prices
|
||||
var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year);
|
||||
|
||||
var isCurrent = curUploadSubs.Exists(s => priceGrp.FirstOrDefault(p => p.ProductId == s) != null);
|
||||
var curPrice = priceGrp.FirstOrDefault(p => p.Id == curEmailPrice);
|
||||
var emailSub = new SubscriptionViewModel()
|
||||
{
|
||||
Recommended = !handledFirst,
|
||||
CurrentPlan = isCurrent,
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceMonth?.Id }),
|
||||
SubscribeUrlYearly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceYear?.Id }),
|
||||
CurrentSubMonthly = curPrice?.Id == priceMonth?.Id,
|
||||
CurrentSubYearly = curPrice?.Id == priceYear?.Id,
|
||||
SubscribeUrlMonthly = Url.SubRouteUrl("billing",
|
||||
hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceMonth?.Id }),
|
||||
SubscribeUrlYearly = Url.SubRouteUrl("billing",
|
||||
hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout",
|
||||
new { priceId = priceYear?.Id }),
|
||||
BaseStorage = priceMonth?.Storage,
|
||||
BasePriceMonthly = priceMonth?.Amount,
|
||||
BasePriceYearly = priceYear?.Amount
|
||||
@ -136,13 +156,152 @@ namespace Teknik.Areas.Billing.Controllers
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
public IActionResult Subscribe(string priceId)
|
||||
public IActionResult Subscribe(string priceId, string subscriptionId)
|
||||
{
|
||||
// Get Subscription Info
|
||||
var billingService = BillingFactory.GetStorageService(_config.BillingConfig);
|
||||
var price = billingService.GetPrice(priceId);
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
return View(new SubscriptionViewModel());
|
||||
var vm = new SubscribeViewModel();
|
||||
vm.Price = billingService.GetPrice(priceId);
|
||||
vm.Subscription = billingService.GetSubscription(subscriptionId);
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
public IActionResult Checkout(string priceId)
|
||||
{
|
||||
// Get Subscription Info
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var price = billingService.GetPrice(priceId);
|
||||
if (price == null)
|
||||
throw new ArgumentException("Invalid Price ID", "priceId");
|
||||
|
||||
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
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,
|
||||
priceId,
|
||||
Url.SubRouteUrl("billing", "Billing.CheckoutComplete", new { productId = price.ProductId }),
|
||||
Url.SubRouteUrl("billing", "Billing.Subscriptions"));
|
||||
return Redirect(session.Url);
|
||||
}
|
||||
|
||||
public IActionResult CheckoutComplete(string productId, string session_id)
|
||||
{
|
||||
// Get Checkout Info
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
var checkoutSession = billingService.GetCheckoutSession(session_id);
|
||||
if (checkoutSession != null)
|
||||
{
|
||||
var subscription = billingService.GetSubscription(checkoutSession.SubscriptionId);
|
||||
if (subscription != null)
|
||||
{
|
||||
foreach (var price in subscription.Prices)
|
||||
{
|
||||
if (price.ProductId == productId)
|
||||
{
|
||||
return Redirect(Url.SubRouteUrl("billing", "Billing.SubscriptionSuccess", new { priceId = price.Id }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect(Url.SubRouteUrl("billing", "Billing.ViewSubscriptions"));
|
||||
}
|
||||
|
||||
public IActionResult EditSubscription(string priceId)
|
||||
{
|
||||
// Get Subscription Info
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var price = billingService.GetPrice(priceId);
|
||||
if (price == null)
|
||||
throw new ArgumentException("Invalid Price ID", "priceId");
|
||||
|
||||
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
if (user == null)
|
||||
throw new UnauthorizedAccessException();
|
||||
|
||||
if (user.BillingCustomer == null)
|
||||
{
|
||||
return Checkout(priceId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentSubs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId);
|
||||
foreach (var curSub in currentSubs)
|
||||
{
|
||||
if (curSub.Prices.Exists(p => p.ProductId == price.ProductId))
|
||||
{
|
||||
billingService.EditSubscriptionPrice(curSub.Id, price.Id);
|
||||
return Redirect(Url.SubRouteUrl("billing", "Billing.SubscriptionSuccess", new { priceId = price.Id }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Get Subscription Info
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
|
||||
var price = billingService.GetPrice(priceId);
|
||||
if (price == null)
|
||||
throw new ArgumentException("Invalid Price ID", "priceId");
|
||||
|
||||
var product = billingService.GetProduct(price.ProductId);
|
||||
vm.ProductName = product.Name;
|
||||
vm.Price = price.Amount;
|
||||
vm.Interval = price.Interval.ToString();
|
||||
vm.Storage = price.Storage;
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
Teknik/Areas/Billing/Models/Customer.cs
Normal file
19
Teknik/Areas/Billing/Models/Customer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.Areas.Users.Models;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Billing.Models
|
||||
{
|
||||
public class Customer
|
||||
{
|
||||
[CaseSensitive]
|
||||
public string CustomerId { get; set; }
|
||||
|
||||
public int UserId { get; set; }
|
||||
|
||||
public virtual User User { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.ViewModels;
|
||||
|
||||
namespace Teknik.Areas.Billing.ViewModels
|
||||
{
|
||||
public class CancelSubscriptionViewModel : ViewModelBase
|
||||
{
|
||||
public string ProductName { get; set; }
|
||||
}
|
||||
}
|
17
Teknik/Areas/Billing/ViewModels/PriceViewModel.cs
Normal file
17
Teknik/Areas/Billing/ViewModels/PriceViewModel.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.ViewModels;
|
||||
|
||||
namespace Teknik.Areas.Billing.ViewModels
|
||||
{
|
||||
public class PriceViewModel : ViewModelBase
|
||||
{
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public long Storage { get; set; }
|
||||
}
|
||||
}
|
15
Teknik/Areas/Billing/ViewModels/SubscribeViewModel.cs
Normal file
15
Teknik/Areas/Billing/ViewModels/SubscribeViewModel.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.BillingCore.Models;
|
||||
using Teknik.ViewModels;
|
||||
|
||||
namespace Teknik.Areas.Billing.ViewModels
|
||||
{
|
||||
public class SubscribeViewModel : ViewModelBase
|
||||
{
|
||||
public Subscription Subscription { get; set; }
|
||||
public Price Price { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.BillingCore.Models;
|
||||
using Teknik.ViewModels;
|
||||
|
||||
namespace Teknik.Areas.Billing.ViewModels
|
||||
{
|
||||
public class SubscriptionSuccessViewModel : ViewModelBase
|
||||
{
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal? Price { get; set; }
|
||||
|
||||
public string Interval { get; set; }
|
||||
|
||||
public long Storage { get; set; }
|
||||
}
|
||||
}
|
@ -9,7 +9,8 @@ namespace Teknik.Areas.Billing.ViewModels
|
||||
public class SubscriptionViewModel : ViewModelBase
|
||||
{
|
||||
public bool Recommended { get; set; }
|
||||
public bool CurrentPlan { get; set; }
|
||||
public bool CurrentSubMonthly { get; set; }
|
||||
public bool CurrentSubYearly { get; set; }
|
||||
public string SubscriptionId { get; set; }
|
||||
public decimal? BasePriceMonthly { get; set; }
|
||||
public decimal? BasePriceYearly { get; set; }
|
||||
@ -28,7 +29,8 @@ namespace Teknik.Areas.Billing.ViewModels
|
||||
public SubscriptionViewModel()
|
||||
{
|
||||
Recommended = false;
|
||||
CurrentPlan = false;
|
||||
CurrentSubMonthly = false;
|
||||
CurrentSubYearly = false;
|
||||
OverageAllowed = false;
|
||||
}
|
||||
}
|
||||
|
21
Teknik/Areas/Billing/Views/Billing/CancelSubscription.cshtml
Normal file
21
Teknik/Areas/Billing/Views/Billing/CancelSubscription.cshtml
Normal file
@ -0,0 +1,21 @@
|
||||
@model Teknik.Areas.Billing.ViewModels.CancelSubscriptionViewModel
|
||||
|
||||
<div class="container">
|
||||
@if (Model.Error)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<h2>@Model.ErrorMessage</h2>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<h2>You have successfully canceled your subscription to <strong>@Model.ProductName</strong></h2>
|
||||
<p><a href="@Url.SubRouteUrl("account", "User.BillingSettings")">View Active Subscriptions</a></p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
45
Teknik/Areas/Billing/Views/Billing/Subscribe.cshtml
Normal file
45
Teknik/Areas/Billing/Views/Billing/Subscribe.cshtml
Normal file
@ -0,0 +1,45 @@
|
||||
@model Teknik.Areas.Billing.ViewModels.SubscribeViewModel
|
||||
|
||||
@{
|
||||
var price = Model.Price.Amount != null ? $"${Model.Price.Amount:0.00}" : "Free";
|
||||
var interval = Model.Price.Interval.ToString();
|
||||
var priceText = Model.Price.Amount != null ? $"{price}/{interval}" : "Free";
|
||||
var storageText = $"{StringHelper.GetBytesReadable(Model.Price.Storage)}";
|
||||
|
||||
if (Model.Subscription != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div id="subscribeStatus">
|
||||
@if (Model.Error)
|
||||
{
|
||||
<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>@Model.ErrorMessage</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-5 col-sm-offset-1 text-left">@storageText</div>
|
||||
<div class="col-sm-5 text-right text-primary">@priceText</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-5 col-sm-offset-6 text-right text-muted">no tax</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
<p><strong>You'll be charged @price automatically every @interval until you cancel. Your price may change as described in the <a href="@Url.SubRouteUrl("tos", "TOS.Index")" target="_blank">Teknik Terms of Service</a>.</strong></p>
|
||||
<p>Cancel anytime in your <a href="@Url.SubRouteUrl("account", "Account.Subscriptions")" target="_blank">Subscriptions Settings</a>.</p>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<button class="btn btn-default" id="subscribeCancel" type="button" name="Subscription.Cancel">Cancel</button>
|
||||
<a href="@Url.SubRouteUrl("billing", "Billing.Checkout", new { priceId = Model.Price.Id, subscriptionId = Model.Subscription?.Id })" class="btn btn-primary" role="button">Subscribe</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,21 @@
|
||||
@model Teknik.Areas.Billing.ViewModels.SubscriptionSuccessViewModel
|
||||
|
||||
<div class="container">
|
||||
@if (Model.Error)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<h2>@Model.ErrorMessage</h2>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<h2>Thank you for subscribing to @Model.ProductName: <strong>@(StringHelper.GetBytesReadable(Model.Storage))</strong> for <strong>@($"${Model.Price:0.00} / {Model.Interval}")</strong></h2>
|
||||
<p><a href="@Url.SubRouteUrl("account", "User.BillingSettings")">View Active Subscriptions</a></p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
@ -13,10 +13,10 @@
|
||||
|
||||
var btnClass = "";
|
||||
var subscribeText = Model.BasePriceMonthly.HasValue ? $"${Model.BasePriceMonthly:0.00} / month" : "Free";
|
||||
if (Model.CurrentPlan)
|
||||
if (Model.CurrentSubMonthly)
|
||||
{
|
||||
btnClass = "disabled";
|
||||
subscribeText = "Current plan";
|
||||
subscribeText = "Current Plan";
|
||||
}
|
||||
|
||||
var panelColor = "default";
|
||||
@ -53,12 +53,19 @@
|
||||
<p><a href="@Model.SubscribeUrlMonthly" class="btn btn-@buttonColor center-block @btnClass" role="button">@subscribeText</a></p>
|
||||
@if (Model.BasePriceYearly != null)
|
||||
{
|
||||
var yearBtnClass = "";
|
||||
var yearSubscribeText = $"${Model.BasePriceYearly:0.00} / year";
|
||||
if (Model.CurrentSubYearly)
|
||||
{
|
||||
yearBtnClass = "disabled";
|
||||
yearSubscribeText = "Current Plan";
|
||||
}
|
||||
var discount = 1 - (Model.BasePriceYearly.Value / (Model.BasePriceMonthly.Value * 12));
|
||||
<p>
|
||||
Or prepay annually<br />
|
||||
(save @Math.Round(100 * discount)%):
|
||||
</p>
|
||||
<p><a href="@Model.SubscribeUrlYearly" class="btn btn-default btn-no-border btn-text-primary center-block" role="button">@($"${Model.BasePriceYearly:0.00}") / year</a></p>
|
||||
<p><a href="@Model.SubscribeUrlYearly" class="btn btn-default btn-no-border btn-text-primary center-block @yearBtnClass" role="button">@yearSubscribeText</a></p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,10 +8,7 @@
|
||||
@if (Model.UploadSubscriptions.Any())
|
||||
{
|
||||
<h2 class="text-center">Upload Storage Subscriptions</h2>
|
||||
<p class="text-muted text-center">
|
||||
By subscribing to a Teknik Service plan, you agree to the <a href="@Url.SubRouteUrl("tos", "TOS.Index")">Teknik Terms of Service</a>.<br />
|
||||
<strong>Note:</strong> The <a href="@Url.SubRouteUrl("privacy", "Privacy.Index")">Teknik Privacy Policy</a> describes how data is handled in this service.
|
||||
</p>
|
||||
<p class="text-center">Want some extra storage? Upgrade with one of the storage subscriptions!</p>
|
||||
<br />
|
||||
<div class="row">
|
||||
@foreach (var subVM in Model.UploadSubscriptions)
|
||||
@ -22,24 +19,13 @@
|
||||
@await Html.PartialAsync("../../Areas/Billing/Views/Billing/ViewSubscription", subVM)
|
||||
}
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(extraUsage))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p>
|
||||
<small>
|
||||
* @extraUsage
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (Model.EmailSubscriptions.Any())
|
||||
{
|
||||
<br />
|
||||
<h2 class="text-center">Email Subscriptions</h2>
|
||||
<p class="text-center">Get a personal email address, complete with web mail client.</p>
|
||||
<br />
|
||||
<div class="row">
|
||||
@foreach (var subVM in Model.EmailSubscriptions)
|
||||
@ -51,4 +37,25 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p class="text-muted text-center">
|
||||
By subscribing to a Teknik Service plan, you agree to the <a href="@Url.SubRouteUrl("tos", "TOS.Index")" target="_blank">Teknik Terms of Service</a>.<br />
|
||||
<strong>Note:</strong> The <a href="@Url.SubRouteUrl("privacy", "Privacy.Index")" target="_blank">Teknik Privacy Policy</a> describes how data is handled in this service.
|
||||
</p>
|
||||
@if (!string.IsNullOrEmpty(extraUsage))
|
||||
{
|
||||
<p>
|
||||
<small>
|
||||
* @extraUsage
|
||||
</small>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bundle src="js/billing.min.js" append-version="true"></bundle>
|
@ -119,7 +119,7 @@
|
||||
</div>
|
||||
<div id="collapse200" class="panel-collapse collapse" role="tabpanel">
|
||||
<div class="panel-body">
|
||||
The max file size for everyone is <strong>@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSize)</strong>, basic accounts are <strong>@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizeBasic)</strong>, and Premium accounts are <strong>@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizePremium)</strong>.
|
||||
The max file size for everyone is <strong>@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadFileSize)</strong>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -118,18 +118,14 @@ namespace Teknik.Areas.Help.Controllers
|
||||
ViewBag.Title = "Upload Service Help";
|
||||
UploadHelpViewModel model = new UploadHelpViewModel();
|
||||
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadSize;
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadFileSize;
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
if (user != null)
|
||||
{
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadSizeBasic;
|
||||
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name);
|
||||
if (userInfo.AccountType == AccountType.Premium)
|
||||
{
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadSizePremium;
|
||||
}
|
||||
if (user.UploadSettings.MaxUploadFileSize != null)
|
||||
model.MaxUploadSize = user.UploadSettings.MaxUploadFileSize.Value;
|
||||
}
|
||||
}
|
||||
return View("~/Areas/Help/Views/Help/Upload.cshtml", model);
|
||||
|
@ -131,7 +131,7 @@
|
||||
</div>
|
||||
<br />
|
||||
<hr />
|
||||
<h2 class="text-center"><a href="@Url.SubRouteUrl("account", "User.GetPremium")">Upgrade to Premium</a> for even more features!</h2>
|
||||
<h2 class="text-center"><a href="@Url.SubRouteUrl("billing", "Billing.Subscriptions")">Subscribe</a> for even more features!</h2>
|
||||
<div class="row">
|
||||
<a conditional href="@Url.SubRouteUrl("help", "Help.Mail")" asp-condition="@User.Identity.IsAuthenticated">
|
||||
<div class="col-md-3 text-center text-primary">
|
||||
@ -154,18 +154,18 @@
|
||||
<a conditional href="@Url.SubRouteUrl("upload", "Upload.Index")" asp-condition="@User.Identity.IsAuthenticated">
|
||||
<div class="col-md-3 text-center text-primary">
|
||||
<br />
|
||||
<i class="fa fa-film fa-5x"></i>
|
||||
<i class="fa fa-hdd-o fa-5x"></i>
|
||||
<div class="caption">
|
||||
<h3>No Embed Limits</h3>
|
||||
<h3>Additional Storage</h3>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a conditional href="@Url.SubRouteUrl("about", "About.Index")" asp-condition="@User.Identity.IsAuthenticated">
|
||||
<a conditional href="@Url.SubRouteUrl("upload", "Upload.Index")" asp-condition="@User.Identity.IsAuthenticated">
|
||||
<div class="col-md-3 text-center text-primary">
|
||||
<br />
|
||||
<i class="fa fa-heart fa-5x"></i>
|
||||
<i class="fa fa-film fa-5x"></i>
|
||||
<div class="caption">
|
||||
<h3>Lifetime Account</h3>
|
||||
<h3>No Embed Limits</h3>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -93,7 +93,7 @@ namespace Teknik.Areas.Paste.Controllers
|
||||
string hash = string.Empty;
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
hash = PasteHelper.HashPassword(paste.Key, password);
|
||||
hash = Crypto.HashPassword(paste.Key, password);
|
||||
keyBytes = AesCounterManaged.CreateKey(password, ivBytes, paste.KeySize);
|
||||
}
|
||||
if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword)
|
||||
@ -242,7 +242,7 @@ namespace Teknik.Areas.Paste.Controllers
|
||||
string hash = string.Empty;
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
hash = PasteHelper.HashPassword(paste.Key, password);
|
||||
hash = Crypto.HashPassword(paste.Key, password);
|
||||
keyBytes = AesCounterManaged.CreateKey(password, ivBytes, paste.KeySize);
|
||||
}
|
||||
if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword)
|
||||
@ -307,7 +307,7 @@ namespace Teknik.Areas.Paste.Controllers
|
||||
string hash = string.Empty;
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
hash = PasteHelper.HashPassword(paste.Key, password);
|
||||
hash = Crypto.HashPassword(paste.Key, password);
|
||||
}
|
||||
if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword)
|
||||
{
|
||||
@ -333,8 +333,8 @@ namespace Teknik.Areas.Paste.Controllers
|
||||
// Generate a unique file name that does not currently exist
|
||||
string fileName = storageService.GetUniqueFileName();
|
||||
|
||||
string key = PasteHelper.GenerateKey(_config.PasteConfig.KeySize);
|
||||
string iv = PasteHelper.GenerateIV(_config.PasteConfig.BlockSize);
|
||||
string key = Crypto.GenerateKey(_config.PasteConfig.KeySize);
|
||||
string iv = Crypto.GenerateIV(_config.PasteConfig.BlockSize);
|
||||
|
||||
PasteHelper.EncryptContents(storageService, model.Content, fileName, password, key, iv, _config.PasteConfig.KeySize, _config.PasteConfig.ChunkSize);
|
||||
|
||||
@ -344,7 +344,7 @@ namespace Teknik.Areas.Paste.Controllers
|
||||
paste.BlockSize = _config.PasteConfig.BlockSize;
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
paste.HashedPassword = PasteHelper.HashPassword(paste.Key, password);
|
||||
paste.HashedPassword = Crypto.HashPassword(paste.Key, password);
|
||||
paste.FileName = fileName;
|
||||
paste.Title = model.Title;
|
||||
paste.Syntax = model.Syntax;
|
||||
|
@ -59,15 +59,14 @@ namespace Teknik.Areas.Paste
|
||||
break;
|
||||
}
|
||||
|
||||
string key = GenerateKey(config.PasteConfig.KeySize);
|
||||
string iv = GenerateIV(config.PasteConfig.BlockSize);
|
||||
string key = Crypto.GenerateKey(config.PasteConfig.KeySize);
|
||||
string iv = Crypto.GenerateIV(config.PasteConfig.BlockSize);
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
paste.HashedPassword = HashPassword(key, password);
|
||||
paste.HashedPassword = Crypto.HashPassword(key, password);
|
||||
}
|
||||
|
||||
|
||||
// Generate a unique file name that does not currently exist
|
||||
var storageService = StorageServiceFactory.GetStorageService(config.PasteConfig.StorageConfig);
|
||||
var fileName = storageService.GetUniqueFileName();
|
||||
@ -102,21 +101,6 @@ namespace Teknik.Areas.Paste
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GenerateKey(int keySize)
|
||||
{
|
||||
return StringHelper.RandomString(keySize / 8);
|
||||
}
|
||||
|
||||
public static string GenerateIV(int ivSize)
|
||||
{
|
||||
return StringHelper.RandomString(ivSize / 16);
|
||||
}
|
||||
|
||||
public static string HashPassword(string key, string password)
|
||||
{
|
||||
return SHA384.Hash(key, password).ToHex();
|
||||
}
|
||||
|
||||
public static void EncryptContents(IStorageService storageService, string content, string fileName, string password, string key, string iv, int keySize, int chunkSize)
|
||||
{
|
||||
byte[] ivBytes = Encoding.Unicode.GetBytes(iv);
|
||||
|
@ -45,7 +45,7 @@ namespace Teknik.Areas.Upload.Controllers
|
||||
model.Encrypt = false;
|
||||
model.ExpirationLength = 1;
|
||||
model.ExpirationUnit = ExpirationUnit.Days;
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadSize;
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadFileSize;
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
User user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
@ -58,16 +58,12 @@ namespace Teknik.Areas.Upload.Controllers
|
||||
|
||||
model.CurrentTotalSize = user.Uploads.Sum(u => u.ContentLength);
|
||||
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadSizeBasic;
|
||||
model.MaxTotalSize = _config.UploadConfig.MaxTotalSizeBasic;
|
||||
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name);
|
||||
if (userInfo.AccountType == AccountType.Premium)
|
||||
{
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadSizePremium;
|
||||
model.MaxTotalSize = _config.UploadConfig.MaxTotalSizePremium;
|
||||
}
|
||||
model.MaxUploadSize = _config.UploadConfig.MaxUploadFileSize;
|
||||
model.MaxTotalSize = _config.UploadConfig.MaxStorage;
|
||||
if (user.UploadSettings.MaxUploadStorage != null)
|
||||
model.MaxTotalSize = user.UploadSettings.MaxUploadStorage.Value;
|
||||
if (user.UploadSettings.MaxUploadFileSize != null)
|
||||
model.MaxUploadSize = user.UploadSettings.MaxUploadFileSize.Value;
|
||||
|
||||
if (model.CurrentTotalSize >= model.MaxTotalSize)
|
||||
{
|
||||
@ -88,22 +84,17 @@ namespace Teknik.Areas.Upload.Controllers
|
||||
{
|
||||
if (_config.UploadConfig.UploadEnabled)
|
||||
{
|
||||
long maxUploadSize = _config.UploadConfig.MaxUploadSize;
|
||||
long maxUploadSize = _config.UploadConfig.MaxUploadFileSize;
|
||||
long maxTotalSize = _config.UploadConfig.MaxStorage;
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic;
|
||||
long maxTotalSize = _config.UploadConfig.MaxTotalSizeBasic;
|
||||
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name);
|
||||
if (userInfo.AccountType == AccountType.Premium)
|
||||
{
|
||||
maxUploadSize = _config.UploadConfig.MaxUploadSizePremium;
|
||||
maxTotalSize = _config.UploadConfig.MaxTotalSizePremium;
|
||||
}
|
||||
|
||||
// Check account total limits
|
||||
var user = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||
if (user.UploadSettings.MaxUploadStorage != null)
|
||||
maxTotalSize = user.UploadSettings.MaxUploadStorage.Value;
|
||||
if (user.UploadSettings.MaxUploadFileSize != null)
|
||||
maxUploadSize = user.UploadSettings.MaxUploadFileSize.Value;
|
||||
|
||||
var userUploadSize = user.Uploads.Sum(u => u.ContentLength);
|
||||
if (userUploadSize + uploadFile.file.Length > maxTotalSize)
|
||||
{
|
||||
@ -232,7 +223,6 @@ namespace Teknik.Areas.Upload.Controllers
|
||||
string iv = string.Empty;
|
||||
string contentType = string.Empty;
|
||||
long contentLength = 0;
|
||||
bool premiumAccount = false;
|
||||
DateTime dateUploaded = new DateTime();
|
||||
|
||||
Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault();
|
||||
@ -256,16 +246,6 @@ namespace Teknik.Areas.Upload.Controllers
|
||||
contentType = upload.ContentType;
|
||||
contentLength = upload.ContentLength;
|
||||
dateUploaded = upload.DateUploaded;
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name);
|
||||
premiumAccount = userInfo.AccountType == AccountType.Premium;
|
||||
}
|
||||
if (!premiumAccount && upload.UserId != null)
|
||||
{
|
||||
IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, upload.User.Username);
|
||||
premiumAccount = userInfo.AccountType == AccountType.Premium;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -285,7 +265,7 @@ namespace Teknik.Areas.Upload.Controllers
|
||||
|
||||
return View(model);
|
||||
}
|
||||
else if (!premiumAccount && _config.UploadConfig.MaxDownloadSize < contentLength)
|
||||
else if (_config.UploadConfig.MaxDownloadFileSize < contentLength)
|
||||
{
|
||||
// We want to force them to the dl page due to them being over the max download size for embedded content
|
||||
DownloadViewModel model = new DownloadViewModel();
|
||||
|
@ -37,6 +37,7 @@ using System.IdentityModel.Tokens.Jwt;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using IdentityServer4.Models;
|
||||
using Teknik.Utilities.Routing;
|
||||
using Teknik.BillingCore;
|
||||
|
||||
namespace Teknik.Areas.Users.Controllers
|
||||
{
|
||||
@ -357,6 +358,51 @@ namespace Teknik.Areas.Users.Controllers
|
||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
[TrackPageView]
|
||||
public IActionResult BillingSettings()
|
||||
{
|
||||
string username = User.Identity.Name;
|
||||
User user = UserHelper.GetUser(_dbContext, username);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
ViewBag.Title = "Billing Settings";
|
||||
ViewBag.Description = "Your " + _config.Title + " Billing Settings";
|
||||
|
||||
BillingSettingsViewModel model = new BillingSettingsViewModel();
|
||||
model.Page = "Billing";
|
||||
model.UserID = user.UserId;
|
||||
model.Username = user.Username;
|
||||
|
||||
if (user.BillingCustomer != null)
|
||||
{
|
||||
var billingService = BillingFactory.GetBillingService(_config.BillingConfig);
|
||||
var subs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId);
|
||||
foreach (var sub in subs)
|
||||
{
|
||||
foreach (var price in sub.Prices)
|
||||
{
|
||||
var product = billingService.GetProduct(price.ProductId);
|
||||
var subView = new SubscriptionViewModel()
|
||||
{
|
||||
SubscriptionId = sub.Id,
|
||||
ProductId = product.ProductId,
|
||||
ProductName = product.Name,
|
||||
Storage = price.Storage,
|
||||
Price = price.Amount,
|
||||
Interval = price.Interval.ToString()
|
||||
};
|
||||
model.Subscriptions.Add(subView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return View("/Areas/User/Views/User/Settings/BillingSettings.cshtml", model);
|
||||
}
|
||||
|
||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
[TrackPageView]
|
||||
public async Task<IActionResult> SecuritySettings()
|
||||
{
|
||||
|
@ -21,14 +21,20 @@ namespace Teknik.Areas.Users.Models
|
||||
public ExpirationUnit ExpirationUnit { get; set; }
|
||||
|
||||
[Column("MaxUploadStorage")]
|
||||
// Maximum Allotted total upload storage
|
||||
public long? MaxUploadStorage { get; set; }
|
||||
|
||||
[Column("MaxUploadFileSize")]
|
||||
// Maximum allowed file size
|
||||
public long? MaxUploadFileSize { get; set; }
|
||||
|
||||
public UploadSettings()
|
||||
{
|
||||
Encrypt = false;
|
||||
ExpirationLength = 1;
|
||||
ExpirationUnit = ExpirationUnit.Never;
|
||||
MaxUploadStorage = null;
|
||||
MaxUploadFileSize = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using Teknik.Attributes;
|
||||
using Teknik.Models;
|
||||
using Teknik.Utilities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Teknik.Areas.Billing.Models;
|
||||
|
||||
namespace Teknik.Areas.Users.Models
|
||||
{
|
||||
@ -15,6 +16,8 @@ namespace Teknik.Areas.Users.Models
|
||||
|
||||
public string Username { get; set; }
|
||||
|
||||
public virtual Customer BillingCustomer { get; set; }
|
||||
|
||||
public virtual ICollection<UserFeature> Features { get; set; }
|
||||
|
||||
public virtual InviteCode ClaimedInviteCode { get; set; }
|
||||
|
18
Teknik/Areas/User/ViewModels/BillingSettingsViewModel.cs
Normal file
18
Teknik/Areas/User/ViewModels/BillingSettingsViewModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.BillingCore.Models;
|
||||
|
||||
namespace Teknik.Areas.Users.ViewModels
|
||||
{
|
||||
public class BillingSettingsViewModel : SettingsViewModel
|
||||
{
|
||||
public List<SubscriptionViewModel> Subscriptions { get; set; }
|
||||
|
||||
public BillingSettingsViewModel()
|
||||
{
|
||||
Subscriptions = new List<SubscriptionViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
23
Teknik/Areas/User/ViewModels/SubscriptionViewModel.cs
Normal file
23
Teknik/Areas/User/ViewModels/SubscriptionViewModel.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Teknik.ViewModels;
|
||||
|
||||
namespace Teknik.Areas.Users.ViewModels
|
||||
{
|
||||
public class SubscriptionViewModel : SettingsViewModel
|
||||
{
|
||||
public string SubscriptionId { get; set; }
|
||||
|
||||
public string ProductId { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal? Price { get; set; }
|
||||
|
||||
public string Interval { get; set; }
|
||||
|
||||
public long Storage { get; set; }
|
||||
}
|
||||
}
|
32
Teknik/Areas/User/Views/User/Settings/BillingSettings.cshtml
Normal file
32
Teknik/Areas/User/Views/User/Settings/BillingSettings.cshtml
Normal file
@ -0,0 +1,32 @@
|
||||
@model Teknik.Areas.Users.ViewModels.BillingSettingsViewModel
|
||||
|
||||
@using Teknik.BillingCore.Models
|
||||
|
||||
@{
|
||||
Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
<br />
|
||||
<label for="activeSubscriptions"><h4>Active Subscriptions</h4></label>
|
||||
<div id="activeSubscriptions" style="overflow-y: auto; max-height: 400px;">
|
||||
<ul class="list-group" id="activeSubscriptionList">
|
||||
@if (Model.Subscriptions.Any())
|
||||
{
|
||||
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>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="list-group-item text-center">No Active Subscriptions</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -17,6 +17,7 @@
|
||||
<div class="panel-heading text-center"><strong>Personal Settings</strong></div>
|
||||
<a href="@Url.SubRouteUrl("account", "User.ProfileSettings")" class="list-group-item @(Model.Page == "Profile" ? "active" : string.Empty)">Profile</a>
|
||||
<a href="@Url.SubRouteUrl("account", "User.AccountSettings")" class="list-group-item @(Model.Page == "Account" ? "active" : string.Empty)">Account</a>
|
||||
<a href="@Url.SubRouteUrl("account", "User.BillingSettings")" class="list-group-item @(Model.Page == "Billing" ? "active" : string.Empty)">Billing</a>
|
||||
<a href="@Url.SubRouteUrl("account", "User.SecuritySettings")" class="list-group-item @(Model.Page == "Security" ? "active" : string.Empty)">Security</a>
|
||||
<a href="@Url.SubRouteUrl("account", "User.InviteSettings")" class="list-group-item @(Model.Page == "Invite" ? "active" : string.Empty)">Invite Codes</a>
|
||||
<a href="@Url.SubRouteUrl("account", "User.BlogSettings")" class="list-group-item @(Model.Page == "Blog" ? "active" : string.Empty)">Blogging</a>
|
||||
|
1057
Teknik/Data/Migrations/20211009192142_AddBillingCustomers.Designer.cs
generated
Normal file
1057
Teknik/Data/Migrations/20211009192142_AddBillingCustomers.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
Teknik/Data/Migrations/20211009192142_AddBillingCustomers.cs
Normal file
40
Teknik/Data/Migrations/20211009192142_AddBillingCustomers.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Teknik.Data.Migrations
|
||||
{
|
||||
public partial class AddBillingCustomers : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Customers",
|
||||
columns: table => new
|
||||
{
|
||||
CustomerId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
UserId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Customers", x => x.CustomerId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Customers_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Customers_UserId",
|
||||
table: "Customers",
|
||||
column: "UserId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Customers");
|
||||
}
|
||||
}
|
||||
}
|
1061
Teknik/Data/Migrations/20211113052031_RenameUploadSettings.Designer.cs
generated
Normal file
1061
Teknik/Data/Migrations/20211113052031_RenameUploadSettings.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Teknik.Data.Migrations
|
||||
{
|
||||
public partial class RenameUploadSettings : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "MaxUploadFileSize",
|
||||
table: "Users",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxUploadFileSize",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ using Teknik.Areas.Vault.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Teknik.Models;
|
||||
using System.Configuration;
|
||||
using Teknik.Areas.Billing.Models;
|
||||
|
||||
namespace Teknik.Data
|
||||
{
|
||||
@ -25,6 +26,8 @@ namespace Teknik.Data
|
||||
public DbSet<UserSettings> UserSettings { get; set; }
|
||||
public DbSet<BlogSettings> BlogSettings { get; set; }
|
||||
public DbSet<UploadSettings> UploadSettings { get; set; }
|
||||
// Billing Customers
|
||||
public DbSet<Customer> Customers { get; set; }
|
||||
// Features
|
||||
public DbSet<Feature> Features { get; set; }
|
||||
// Blogs
|
||||
@ -72,6 +75,7 @@ namespace Teknik.Data
|
||||
modelBuilder.Entity<User>().OwnsOne(u => u.UserSettings, us => us.ToTable("Users"));
|
||||
modelBuilder.Entity<User>().OwnsOne(u => u.BlogSettings, bs => bs.ToTable("Users"));
|
||||
modelBuilder.Entity<User>().OwnsOne(u => u.UploadSettings, us => us.ToTable("Users"));
|
||||
modelBuilder.Entity<User>().HasOne(u => u.BillingCustomer).WithOne(i => i.User);
|
||||
modelBuilder.Entity<User>().HasMany(u => u.Features).WithOne(u => u.User);
|
||||
modelBuilder.Entity<User>().HasMany(u => u.Uploads).WithOne(u => u.User);
|
||||
modelBuilder.Entity<User>().HasMany(u => u.Pastes).WithOne(u => u.User);
|
||||
@ -86,6 +90,9 @@ namespace Teknik.Data
|
||||
//modelBuilder.Entity<InviteCode>().HasOne(t => t.ClaimedUser).WithOne(u => u.ClaimedInviteCode).HasPrincipalKey("ClaimedUserId").HasForeignKey("ClaimedUser_UserId"); // Legacy???
|
||||
//modelBuilder.Entity<InviteCode>().HasOne(t => t.Owner).WithMany(u => u.OwnedInviteCodes).HasPrincipalKey("ClaimedUserId").HasForeignKey("Owner_UserId"); // Legacy???
|
||||
|
||||
// Billing Customers
|
||||
modelBuilder.Entity<Customer>().HasOne(u => u.User);
|
||||
|
||||
// Features
|
||||
modelBuilder.Entity<UserFeature>().HasOne(f => f.Feature);
|
||||
modelBuilder.Entity<UserFeature>().HasOne(f => f.User);
|
||||
@ -119,6 +126,8 @@ namespace Teknik.Data
|
||||
//modelBuilder.Entity<UserSettings>().ToTable("Users");
|
||||
//modelBuilder.Entity<BlogSettings>().ToTable("Users");
|
||||
//modelBuilder.Entity<UploadSettings>().ToTable("Users");
|
||||
// Billing Customers
|
||||
modelBuilder.Entity<Customer>().ToTable("Customers");
|
||||
// Features
|
||||
modelBuilder.Entity<Feature>().ToTable("Features");
|
||||
// Blogs
|
||||
|
BIN
Teknik/Images/logo-blue-1920.png
Normal file
BIN
Teknik/Images/logo-blue-1920.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
Teknik/Images/logo-blue-2400.png
Normal file
BIN
Teknik/Images/logo-blue-2400.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
49
Teknik/Scripts/Billing/Subscriptions.js
Normal file
49
Teknik/Scripts/Billing/Subscriptions.js
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#subscribeModal').on('shown.bs.modal', function () {
|
||||
$("#subscribeStatus").css('display', 'none', 'important');
|
||||
$("#subscribeStatus").html('');
|
||||
});
|
||||
|
||||
$("#subscribeSubmit").click(function () {
|
||||
// Reset register status messages
|
||||
$("#subscribeStatus").css('display', 'none', 'important');
|
||||
$("#subscribeStatus").html('');
|
||||
|
||||
// Disable the register button
|
||||
disableButton('#subscribeSubmit', 'Subscribing...');
|
||||
|
||||
var form = $('#registrationForm');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: form.attr('action'),
|
||||
data: form.serialize(),
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
success: function (html) {
|
||||
if (html.success) {
|
||||
$('#registerModal').modal('hide');
|
||||
|
||||
$("#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>Registration Successful. Redirecting...</div>');
|
||||
|
||||
window.location = html.redirectUrl;
|
||||
}
|
||||
else {
|
||||
$("#registerStatus").css('display', 'inline', 'important');
|
||||
$("#registerStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(html) + '</div>');
|
||||
}
|
||||
},
|
||||
error: function (response) {
|
||||
$("#registerStatus").css('display', 'inline', 'important');
|
||||
$("#registerStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response.responseText) + '</div>');
|
||||
}
|
||||
}).always(function () {
|
||||
enableButton('#registerSubmit', 'Sign Up');
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
26
Utilities/Cryptography/Crypto.cs
Normal file
26
Utilities/Cryptography/Crypto.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Teknik.Utilities.Cryptography
|
||||
{
|
||||
public static class Crypto
|
||||
{
|
||||
public static string GenerateKey(int keySize)
|
||||
{
|
||||
return StringHelper.RandomString(keySize / 8);
|
||||
}
|
||||
|
||||
public static string GenerateIV(int ivSize)
|
||||
{
|
||||
return StringHelper.RandomString(ivSize / 16);
|
||||
}
|
||||
|
||||
public static string HashPassword(string key, string password)
|
||||
{
|
||||
return SHA384.Hash(key, password).ToHex();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user