1
0
mirror of https://git.teknik.io/Teknikode/Teknik.git synced 2023-08-02 14:16:22 +02:00

WIP billing

This commit is contained in:
Uncled1023 2021-08-11 18:44:15 -07:00
parent 3d7fbd6054
commit 84e41499be
35 changed files with 681 additions and 14 deletions

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AssemblyName>Teknik.BillingCore</AssemblyName>
<RootNamespace>Teknik.BillingCore</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Stripe.net" Version="39.62.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Configuration\Configuration.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace Teknik.BillingCore
{
public abstract class BillingService
{
protected readonly BillingConfig Config;
public BillingService(BillingConfig billingConfig)
{
Config = billingConfig;
}
public abstract bool CreateCustomer(string email);
public abstract Tuple<bool, string, string> CreateSubscription(string customerId, string priceId);
public abstract bool EditSubscription();
public abstract Subscription GetSubscription(string subscriptionId);
public abstract bool RemoveSubscription();
public abstract void SyncSubscriptions();
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.BillingCore
{
public interface IBillingService
{
public bool AddSubscription();
public bool EditSubscription();
public bool RemoveSubscription();
public void GetSubscription();
public void SyncSubscriptions();
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.BillingCore
{
public enum IntervalType
{
Monthly,
Yearly
}
}

15
BillingCore/Product.cs Normal file
View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.BillingCore
{
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
}

View File

@ -0,0 +1,84 @@
using Stripe;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace Teknik.BillingCore
{
public class StripeService : BillingService
{
public StripeService(BillingConfig config) : base(config)
{ }
public override bool CreateCustomer(string email)
{
var options = new CustomerCreateOptions
{
Email = email,
};
var service = new CustomerService();
var customer = service.Create(options);
return customer != null;
}
public override Subscription GetSubscription(string subscriptionId)
{
throw new NotImplementedException();
}
public override Tuple<bool, string, string> 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
{
Customer = customerId,
Items = new List<SubscriptionItemOptions>
{
new SubscriptionItemOptions
{
Price = priceId,
},
},
PaymentBehavior = "default_incomplete",
};
subscriptionOptions.AddExpand("latest_invoice.payment_intent");
var subscriptionService = new SubscriptionService();
try
{
Stripe.Subscription subscription = subscriptionService.Create(subscriptionOptions);
return new Tuple<bool, string, string>(true, subscription.Id, subscription.LatestInvoice.PaymentIntent.ClientSecret);
}
catch (StripeException e)
{
return new Tuple<bool, string, string>(false, $"Failed to create subscription. {e}", null);
}
}
public override bool EditSubscription()
{
throw new NotImplementedException();
}
public override bool RemoveSubscription()
{
throw new NotImplementedException();
}
public override void SyncSubscriptions()
{
throw new NotImplementedException();
}
private Customer GetCustomer(string id)
{
var service = new CustomerService();
return service.Get(id);
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.BillingCore
{
public class Subscription
{
public int SubscriptionId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Amount { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.BillingCore
{
public class UserSubscription
{
public int UserId { get; set; }
public int SubscriptionId { get; set; }
public Subscription Subscription { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using CommandLine;
using System;
using System.Collections.Generic;
using System.Text;
namespace Teknik.BillingService
{
public class ArgumentOptions
{
[Option('s', "sync", Default = false, Required = false, HelpText = "Syncs the current subscriptions with the invoice system")]
public bool SyncSubscriptions { get; set; }
[Option('c', "config", Required = false, HelpText = "The path to the teknik config file")]
public string Config { get; set; }
// Omitting long name, default --verbose
[Option(HelpText = "Prints all messages to standard output.")]
public bool Verbose { get; set; }
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>Teknik.BillingService</RootNamespace>
<AssemblyName>Teknik.BillingService</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser.NS20" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Configuration\Configuration.csproj" />
<ProjectReference Include="..\Teknik\Teknik.csproj" />
</ItemGroup>
</Project>

77
BillingService/Program.cs Normal file
View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using CommandLine;
using Microsoft.EntityFrameworkCore;
using Teknik.Configuration;
using Teknik.Data;
using Teknik.Utilities;
namespace Teknik.BillingService
{
public class Program
{
private static string currentPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
private static string errorFile = Path.Combine(currentPath, "errorLogs.txt");
private static string configPath = currentPath;
private static readonly object dbLock = new object();
private static readonly object scanStatsLock = new object();
public static event Action<string> OutputEvent;
public static int Main(string[] args)
{
try
{
Parser.Default.ParseArguments<ArgumentOptions>(args).WithParsed(options =>
{
if (!string.IsNullOrEmpty(options.Config))
configPath = options.Config;
if (Directory.Exists(configPath))
{
Config config = Config.Load(configPath);
Output(string.Format("[{0}] Started Billing Service Process.", DateTime.Now));
var optionsBuilder = new DbContextOptionsBuilder<TeknikEntities>();
optionsBuilder.UseSqlServer(config.DbConnection);
using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options))
{
if (options.SyncSubscriptions)
{
// Sync subscription information
}
}
Output(string.Format("[{0}] Finished Billing Service Process.", DateTime.Now));
}
else
{
string msg = string.Format("[{0}] Config File does not exist.", DateTime.Now);
File.AppendAllLines(errorFile, new List<string> { msg });
Output(msg);
}
});
}
catch (Exception ex)
{
string msg = string.Format("[{0}] Exception: {1}", DateTime.Now, ex.GetFullMessage(true));
File.AppendAllLines(errorFile, new List<string> { msg });
Output(msg);
}
return -1;
}
public static void Output(string message)
{
Console.WriteLine(message);
if (OutputEvent != null)
{
OutputEvent(message);
}
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.Configuration
{
public class BillingConfig
{
public string StripePublishApiKey { get; set; }
public string StripeSecretApiKey { get; set; }
public BillingConfig()
{
StripePublishApiKey = null;
StripeSecretApiKey = null;
}
}
}

View File

@ -52,6 +52,7 @@ namespace Teknik.Configuration
private LoggingConfig _LoggingConfig; private LoggingConfig _LoggingConfig;
private PiwikConfig _PiwikConfig; private PiwikConfig _PiwikConfig;
private IRCConfig _IRCConfig; private IRCConfig _IRCConfig;
private BillingConfig _BillingConfig;
public bool DevEnvironment { get { return _DevEnvironment; } set { _DevEnvironment = value; } } public bool DevEnvironment { get { return _DevEnvironment; } set { _DevEnvironment = value; } }
public bool Migrate { get { return _Migrate; } set { _Migrate = value; } } public bool Migrate { get { return _Migrate; } set { _Migrate = value; } }
@ -120,9 +121,12 @@ namespace Teknik.Configuration
// Piwik Configuration // Piwik Configuration
public PiwikConfig PiwikConfig { get { return _PiwikConfig; } set { _PiwikConfig = value; } } public PiwikConfig PiwikConfig { get { return _PiwikConfig; } set { _PiwikConfig = value; } }
// Piwik Configuration // IRC Configuration
public IRCConfig IRCConfig { get { return _IRCConfig; } set { _IRCConfig = value; } } public IRCConfig IRCConfig { get { return _IRCConfig; } set { _IRCConfig = value; } }
// Billing Configuration
public BillingConfig BillingConfig { get { return _BillingConfig; } set { _BillingConfig = value; } }
public Config() public Config()
{ {
_ConfigRWLock = new ReaderWriterLockSlim(); _ConfigRWLock = new ReaderWriterLockSlim();
@ -167,6 +171,7 @@ namespace Teknik.Configuration
LoggingConfig = new LoggingConfig(); LoggingConfig = new LoggingConfig();
PiwikConfig = new PiwikConfig(); PiwikConfig = new PiwikConfig();
IRCConfig = new IRCConfig(); IRCConfig = new IRCConfig();
BillingConfig = new BillingConfig();
} }
public static Config Deserialize(string text) public static Config Deserialize(string text)

View File

@ -1,7 +1,6 @@
using CommandLine; using CommandLine;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using nClam; using nClam;
using StorageService;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -17,6 +16,7 @@ using Teknik.Areas.Users.Models;
using Teknik.Areas.Users.Utility; using Teknik.Areas.Users.Utility;
using Teknik.Configuration; using Teknik.Configuration;
using Teknik.Data; using Teknik.Data;
using Teknik.StorageService;
using Teknik.Utilities; using Teknik.Utilities;
using Teknik.Utilities.Cryptography; using Teknik.Utilities.Cryptography;

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using Teknik.Configuration; using Teknik.Configuration;
namespace StorageService namespace Teknik.StorageService
{ {
public interface IStorageService public interface IStorageService
{ {

View File

@ -8,7 +8,7 @@ using Teknik.Configuration;
using Teknik.Utilities; using Teknik.Utilities;
using Teknik.Utilities.Cryptography; using Teknik.Utilities.Cryptography;
namespace StorageService namespace Teknik.StorageService
{ {
public class LocalStorageService : StorageService public class LocalStorageService : StorageService
{ {

View File

@ -8,7 +8,7 @@ using Teknik.Configuration;
using Teknik.Utilities; using Teknik.Utilities;
using Teknik.Utilities.Cryptography; using Teknik.Utilities.Cryptography;
namespace StorageService namespace Teknik.StorageService
{ {
public class MemoryStorageService : StorageService public class MemoryStorageService : StorageService
{ {

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using Teknik.Configuration; using Teknik.Configuration;
namespace StorageService namespace Teknik.StorageService
{ {
public abstract class StorageService : IStorageService public abstract class StorageService : IStorageService
{ {

View File

@ -1,7 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<RootNamespace>Teknik.StorageService</RootNamespace>
<AssemblyName>Teknik.StorageService</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -5,7 +5,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Teknik.Configuration; using Teknik.Configuration;
namespace StorageService namespace Teknik.StorageService
{ {
public static class StorageServiceFactory public static class StorageServiceFactory
{ {

View File

@ -34,7 +34,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContentScanningService", "C
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebCommon", "WebCommon\WebCommon.csproj", "{32E85A7F-871A-437C-9BA3-00499AAB442C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebCommon", "WebCommon\WebCommon.csproj", "{32E85A7F-871A-437C-9BA3-00499AAB442C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StorageService", "StorageService\StorageService.csproj", "{4A600C17-C772-462F-A37F-307E7893B2DB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageService", "StorageService\StorageService.csproj", "{4A600C17-C772-462F-A37F-307E7893B2DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BillingService", "BillingService\BillingService.csproj", "{AF417E48-8137-4677-9058-E4DD2745FEC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BillingCore", "BillingCore\BillingCore.csproj", "{A9ED275B-4004-4A5B-8F1C-134A29B999E7}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -115,6 +119,18 @@ Global
{4A600C17-C772-462F-A37F-307E7893B2DB}.Release|Any CPU.Build.0 = Release|Any CPU {4A600C17-C772-462F-A37F-307E7893B2DB}.Release|Any CPU.Build.0 = Release|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Test|Any CPU.ActiveCfg = Debug|Any CPU {4A600C17-C772-462F-A37F-307E7893B2DB}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Test|Any CPU.Build.0 = Debug|Any CPU {4A600C17-C772-462F-A37F-307E7893B2DB}.Test|Any CPU.Build.0 = Debug|Any CPU
{AF417E48-8137-4677-9058-E4DD2745FEC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF417E48-8137-4677-9058-E4DD2745FEC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF417E48-8137-4677-9058-E4DD2745FEC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF417E48-8137-4677-9058-E4DD2745FEC5}.Release|Any CPU.Build.0 = Release|Any CPU
{AF417E48-8137-4677-9058-E4DD2745FEC5}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{AF417E48-8137-4677-9058-E4DD2745FEC5}.Test|Any CPU.Build.0 = Debug|Any CPU
{A9ED275B-4004-4A5B-8F1C-134A29B999E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9ED275B-4004-4A5B-8F1C-134A29B999E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9ED275B-4004-4A5B-8F1C-134A29B999E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9ED275B-4004-4A5B-8F1C-134A29B999E7}.Release|Any CPU.Build.0 = Release|Any CPU
{A9ED275B-4004-4A5B-8F1C-134A29B999E7}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{A9ED275B-4004-4A5B-8F1C-134A29B999E7}.Test|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1045,6 +1045,50 @@
"action": "ViewRawPGP" "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": "Subscriptions"
}
},
{
"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", "Name": "Vault.Index",
"HostTypes": [ "Full" ], "HostTypes": [ "Full" ],

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Areas.Billing.ViewModels;
using Teknik.Configuration;
using Teknik.Controllers;
using Teknik.Data;
using Teknik.Logging;
namespace Teknik.Areas.Billing.Controllers
{
[Area("Billing")]
public class BillingController : DefaultController
{
public BillingController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
public IActionResult Index()
{
return View(new BillingViewModel() { StripePublishKey = _config.BillingConfig.StripePublishApiKey });
}
[AllowAnonymous]
public IActionResult Subscriptions()
{
return View(new BillingViewModel() { StripePublishKey = _config.BillingConfig.StripePublishApiKey });
}
[AllowAnonymous]
public IActionResult ViewPaymentInfo()
{
return View(new PaymentViewModel() { StripePublishKey = _config.BillingConfig.StripePublishApiKey });
}
}
}

View File

@ -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 BillingViewModel : ViewModelBase
{
public string StripePublishKey { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.ViewModels;
namespace Teknik.Areas.Billing.ViewModels
{
public class PaymentViewModel : ViewModelBase
{
public string StripePublishKey { get; set; }
public string PaymentName { get; set; }
public string CreditCardNumber { get; set; }
public string ExpirationMonth { get; set; }
public string ExpirationYear { get; set; }
public string CCV { get; set; }
public string ZipCode { get; set; }
}
}

View File

@ -0,0 +1,54 @@
@model Teknik.Areas.Billing.ViewModels.PaymentViewModel
<script>
var stripePublishKey = '@Model.StripePublishKey';
var editURL = '@Url.SubRouteUrl("billing", "Billing.Action", new { action = "UpdatePayment" })';
</script>
@{
<div class="row">
<div class="col-sm-12 text-center">
<div id="paymentStatus">
@if (Model.Error)
{
<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>@Model.ErrorMessage</div>
}
</div>
</div>
</div>
<form id="paymentForm" action="@Url.SubRouteUrl("billing", "Billing.UpdatePayment" )" method="post" accept-charset="UTF-8">
<div class="form-group">
<label for="paymentName">Full Name</label>
<input type="text" class="form-control" id="paymentName" value="@Model.PaymentName" placeholder="Joe Smith" name="Payment.FullName" data-val-required="The Full Name of the card holder is required." data-val="true" />
</div>
<div class="form-group">
<label for="paymentCardNumber">Credit Card Number</label>
<input type="text" class="form-control" id="paymentCardNumber" value="@Model.CreditCardNumber" placeholder="XXXX-XXXX-XXXX-XXXX" name="Payment.CardNumber" data-val-required="The Credit Card Number is required." data-val="true" />
</div>
<div class="form-group">
<label for="paymentExpMonth">Expiration Month</label>
<input type="text" class="form-control" id="paymentExpMonth" value="@Model.ExpirationMonth" placeholder="MM" name="Payment.ExpMonth" data-val-required="The Expiration Month is required." data-val="true" />
</div>
<div class="form-group">
<label for="paymentExpYear">Expiration Year</label>
<input type="text" class="form-control" id="paymentExpYear" value="@Model.ExpirationYear" placeholder="YY" name="Payment.ExpYear" data-val-required="The Expiration Year is required." data-val="true" />
</div>
<div class="form-group">
<label for="paymentCCV">CCV</label>
<input type="text" class="form-control" id="paymentCCV" value="@Model.CCV" placeholder="123" name="Payment.CCV" data-val-required="The CCV is required." data-val="true" />
</div>
<div class="form-group">
<label for="paymentZip">Zip Code</label>
<input type="text" class="form-control" id="paymentZip" value="@Model.ZipCode" placeholder="99999" name="Payment.Zip" data-val-required="The Zip is required." data-val="true" />
</div>
<p class="text-center">
By registering for Teknik, you agree to the <a href="@Url.SubRouteUrl(" tos", "TOS.Index" )" target="_blank">Terms of Service</a>.
</p>
<div class="form-group text-center">
<button class="btn btn-primary" id="paymentSubmit" type="submit" name="Payment.Submit">Sign Up</button>
</div>
</form>
}
<script src="https://js.stripe.com/v3/"></script>
<bundle src="js/billing.min.js" append-version="true"></bundle>

View File

@ -0,0 +1,89 @@
@model Teknik.Areas.Billing.ViewModels.BillingViewModel
@{
string extraUsage = "If you need more than 100 GB of monthly upload bandwidth, please contact support for assistance.";
}
<div class="container">
<h2 class="text-center">Upload Bandwidth Subscriptions</h2>
<br />
<div class="row">
<div class="col-sm-6 col-md-3">
<div class="panel panel-info">
<div class="panel-heading">
<h2 class="panel-title">Basic Account</h2>
</div>
<div class="panel-body">
<h2>Free</h2>
<h5>
<small>No Overage Allowed</small>
</h5>
<p>
<strong>5 GB</strong> Uploads <small class="text-muted">/ month</small>
</p>
<p><a href="@Url.SubRouteUrl("account", "User.Register")" class="btn btn-primary center-block" role="button">Sign up for free</a></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h2 class="panel-title">Standalone 10 GB</h2>
</div>
<div class="panel-body">
<h2>$0.99 <small>/ month</small></h2>
<h5>
<small>Overage at $0.30 / GB <span data-toggle="tooltip" data-placement="top" title="@extraUsage">(Up to 100 GB)*</span></small>
</h5>
<p>
<strong>10 GB</strong> Uploads <small class="text-muted">/ month</small>
</p>
<p><a href="@Url.SubRouteUrl("billing", "Billing.Subscribe", new { plan = "10gb" })" class="btn btn-primary center-block" role="button">Subscribe</a></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h2 class="panel-title">Standalone 50 GB</h2>
</div>
<div class="panel-body">
<h2>$3.99 <small>/ month</small></h2>
<h5>
<small>Overage at $0.30 / GB <span data-toggle="tooltip" data-placement="top" title="@extraUsage">(Up to 100 GB)*</span></small>
</h5>
<p>
<strong>50 GB</strong> Uploads <small class="text-muted">/ month</small>
</p>
<p><a href="@Url.SubRouteUrl("billing", "Billing.Subscribe", new { plan = "50gb" })" class="btn btn-primary center-block" role="button">Subscribe</a></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h2 class="panel-title">By Upload Usage</h2>
</div>
<div class="panel-body">
<h2>$0.15 <small>/ GB</small></h2>
<h5>
<small>No Overage Fee</small>
</h5>
<p>
<strong><span data-toggle="tooltip" data-placement="top" title="@extraUsage">Up to 100 GB*</span></strong> Uploads <small class="text-muted">/ month</small>
</p>
<p><a href="@Url.SubRouteUrl("billing", "Billing.Subscribe", new { plan = "usage" })" class="btn btn-primary center-block" role="button">Subscribe</a></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<p>
<small>
* @extraUsage
</small>
</p>
</div>
</div>
</div>

View File

@ -0,0 +1,14 @@
@model Teknik.Areas.Billing.ViewModels.PaymentViewModel
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="text-center">
<h1>Payment Information</h1>
<div class="col-md-4 col-md-offset-4">
@await Html.PartialAsync("../../Areas/Billing/Views/Billing/PaymentDetails", Model)
</div>
</div>
</div>
</div>
</div>

View File

@ -22,7 +22,7 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
using Teknik.Utilities.Routing; using Teknik.Utilities.Routing;
using StorageService; using Teknik.StorageService;
namespace Teknik.Areas.Paste.Controllers namespace Teknik.Areas.Paste.Controllers
{ {

View File

@ -9,7 +9,7 @@ using Teknik.Models;
using Teknik.Utilities.Cryptography; using Teknik.Utilities.Cryptography;
using Teknik.Data; using Teknik.Data;
using System.IO; using System.IO;
using StorageService; using Teknik.StorageService;
using Teknik.Logging; using Teknik.Logging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -24,7 +24,7 @@ using Teknik.Logging;
using Teknik.Areas.Users.Models; using Teknik.Areas.Users.Models;
using Teknik.ContentScanningService; using Teknik.ContentScanningService;
using Teknik.Utilities.Routing; using Teknik.Utilities.Routing;
using StorageService; using Teknik.StorageService;
namespace Teknik.Areas.Upload.Controllers namespace Teknik.Areas.Upload.Controllers
{ {

View File

@ -9,7 +9,7 @@ using Teknik.Utilities;
using System.Text; using System.Text;
using Teknik.Utilities.Cryptography; using Teknik.Utilities.Cryptography;
using Teknik.Data; using Teknik.Data;
using StorageService; using Teknik.StorageService;
using Teknik.Logging; using Teknik.Logging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using StorageService; using Teknik.StorageService;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;

View File

@ -0,0 +1,20 @@
// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/apikeys
let stripe = Stripe(stripePublishKey);
let elements = stripe.elements();
let card = elements.create('card');
card.mount('#card-element');
card.on('change', function (event) {
displayError(event);
});
function displayError(event) {
changeLoadingStatePrices(false);
let displayError = document.getElementById('card-element-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
}

View File

@ -291,6 +291,12 @@
"./wwwroot/lib/bootstrap/css/bootstrap-switch.css" "./wwwroot/lib/bootstrap/css/bootstrap-switch.css"
] ]
}, },
{
"outputFileName": "./wwwroot/js/billing.min.js",
"inputFiles": [
"./wwwroot/js/app/Billing/Billing.js"
]
},
{ {
"outputFileName": "./wwwroot/js/vault.min.js", "outputFileName": "./wwwroot/js/vault.min.js",
"inputFiles": [ "inputFiles": [