1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-23 03:01:23 +01:00

paymentservice with stripe & braintree implem.

This commit is contained in:
Kyle Spearrin 2017-07-28 00:17:31 -04:00
parent c991d48cbc
commit 2dc9c196c4
13 changed files with 236 additions and 72 deletions

View File

@ -3,6 +3,9 @@
"baseVaultUri": "https://vault.bitwarden.com/#",
"u2f": {
"appId": "https://vault.bitwarden.com/app-id.json"
},
"braintree": {
"production": true
}
}
}

View File

@ -40,6 +40,12 @@
},
"u2f": {
"appId": "https://localhost:4001/app-id.json"
},
"braintree": {
"production": false,
"merchantId": "SECRET",
"publicKey": "SECRET",
"privateKey": "SECRET"
}
},
"IpRateLimitOptions": {

View File

@ -1,5 +1,8 @@
{
"globalSettings": {
"baseVaultUri": "https://vault.bitwarden.com/#"
"baseVaultUri": "https://vault.bitwarden.com/#",
"braintree": {
"production": true
}
}
}

View File

@ -30,5 +30,11 @@
},
"billingSettings": {
"stripeWebhookKey": "SECRET"
},
"braintree": {
"production": false,
"merchantId": "SECRET",
"publicKey": "SECRET",
"privateKey": "SECRET"
}
}

View File

@ -34,6 +34,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Braintree" Version="3.8.0" />
<PackageReference Include="IdentityServer4" Version="1.3.1" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureStorage" Version="1.0.2" />

View File

@ -16,6 +16,7 @@
public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings();
public virtual DuoSettings Duo { get; set; } = new DuoSettings();
public virtual U2fSettings U2f { get; set; } = new U2fSettings();
public virtual BraintreeSettings Braintree { get; set; } = new BraintreeSettings();
public class SqlServerSettings
{
@ -86,5 +87,13 @@
{
public string AppId { get; set; }
}
public class BraintreeSettings
{
public bool Production { get; set; }
public string MerchantId { get; set; }
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
}
}
}

View File

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Bit.Core.Models.Table;
namespace Bit.Core.Services
{
public interface IPaymentService
{
Task PurchasePremiumAsync(User user, string paymentToken, short additionalStorageGb);
}
}

View File

@ -0,0 +1,72 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Table;
using Braintree;
namespace Bit.Core.Services
{
public class BraintreePaymentService : IPaymentService
{
private readonly BraintreeGateway _gateway;
public BraintreePaymentService(
GlobalSettings globalSettings)
{
_gateway = new BraintreeGateway
{
Environment = globalSettings.Braintree.Production ?
Braintree.Environment.PRODUCTION : Braintree.Environment.SANDBOX,
MerchantId = globalSettings.Braintree.MerchantId,
PublicKey = globalSettings.Braintree.PublicKey,
PrivateKey = globalSettings.Braintree.PrivateKey
};
}
public async Task PurchasePremiumAsync(User user, string paymentToken, short additionalStorageGb)
{
var customerResult = await _gateway.Customer.CreateAsync(new CustomerRequest
{
PaymentMethodNonce = paymentToken,
Email = user.Email
});
if(!customerResult.IsSuccess())
{
// error, throw something
}
var subId = "u" + user.Id.ToString("N").ToLower() +
Utilities.CoreHelpers.RandomString(3, upper: false, numeric: false);
var subRequest = new SubscriptionRequest
{
Id = subId,
PaymentMethodToken = paymentToken,
PlanId = "premium-annually"
};
if(additionalStorageGb > 0)
{
subRequest.AddOns.Add = new AddAddOnRequest[]
{
new AddAddOnRequest
{
InheritedFromId = "storage-gb-annually",
Quantity = additionalStorageGb
}
};
}
var subResult = await _gateway.Subscription.CreateAsync(subRequest);
if(!subResult.IsSuccess())
{
await _gateway.Customer.DeleteAsync(customerResult.Target.Id);
// error, throw something
}
user.StripeCustomerId = customerResult.Target.Id;
user.StripeSubscriptionId = subResult.Target.Id;
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Table;
using Stripe;
using System.Collections.Generic;
namespace Bit.Core.Services
{
public class StripePaymentService : IPaymentService
{
private const string PremiumPlanId = "premium-annually";
private const string StoragePlanId = "storage-gb-annually";
public StripePaymentService(
GlobalSettings globalSettings)
{
}
public async Task PurchasePremiumAsync(User user, string paymentToken, short additionalStorageGb)
{
var customerService = new StripeCustomerService();
var customer = await customerService.CreateAsync(new StripeCustomerCreateOptions
{
Description = user.Name,
Email = user.Email,
SourceToken = paymentToken
});
var subCreateOptions = new StripeSubscriptionCreateOptions
{
Items = new List<StripeSubscriptionItemOption>(),
Metadata = new Dictionary<string, string>
{
["userId"] = user.Id.ToString()
}
};
subCreateOptions.Items.Add(new StripeSubscriptionItemOption
{
PlanId = PremiumPlanId,
Quantity = 1
});
if(additionalStorageGb > 0)
{
subCreateOptions.Items.Add(new StripeSubscriptionItemOption
{
PlanId = StoragePlanId,
Quantity = additionalStorageGb
});
}
StripeSubscription subscription = null;
try
{
var subscriptionService = new StripeSubscriptionService();
subscription = await subscriptionService.CreateAsync(customer.Id, subCreateOptions);
}
catch(StripeException)
{
await customerService.DeleteAsync(customer.Id);
throw;
}
user.StripeCustomerId = customer.Id;
user.StripeSubscriptionId = subscription.Id;
}
}
}

View File

@ -514,55 +514,12 @@ namespace Bit.Core.Services
throw new BadRequestException("Already a premium user.");
}
var customerService = new StripeCustomerService();
var customer = await customerService.CreateAsync(new StripeCustomerCreateOptions
{
Description = user.Name,
Email = user.Email,
SourceToken = paymentToken
});
var subCreateOptions = new StripeSubscriptionCreateOptions
{
Items = new List<StripeSubscriptionItemOption>(),
Metadata = new Dictionary<string, string>
{
["userId"] = user.Id.ToString()
}
};
subCreateOptions.Items.Add(new StripeSubscriptionItemOption
{
PlanId = PremiumPlanId,
Quantity = 1
});
if(additionalStorageGb > 0)
{
subCreateOptions.Items.Add(new StripeSubscriptionItemOption
{
PlanId = StoragePlanId,
Quantity = additionalStorageGb
});
}
StripeSubscription subscription = null;
try
{
var subscriptionService = new StripeSubscriptionService();
subscription = await subscriptionService.CreateAsync(customer.Id, subCreateOptions);
}
catch(StripeException)
{
await customerService.DeleteAsync(customer.Id);
throw;
}
IPaymentService paymentService = new StripePaymentService(_globalSettings);
await paymentService.PurchasePremiumAsync(user, paymentToken, additionalStorageGb);
user.Premium = true;
user.MaxStorageGb = (short)(1 + additionalStorageGb);
user.RevisionDate = DateTime.UtcNow;
user.StripeCustomerId = customer.Id;
user.StripeSubscriptionId = subscription.Id;
try
{
@ -570,7 +527,7 @@ namespace Bit.Core.Services
}
catch
{
await BillingHelpers.CancelAndRecoverChargesAsync(subscription.Id, customer.Id);
await BillingHelpers.CancelAndRecoverChargesAsync(user.StripeSubscriptionId, user.StripeCustomerId);
throw;
}
}

View File

@ -5,6 +5,7 @@ using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
@ -16,6 +17,7 @@ namespace Bit.Core.Utilities
{
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly Random _random = new Random();
/// <summary>
/// Generate sequential Guid for Sql Server.
@ -128,34 +130,21 @@ namespace Bit.Core.Utilities
return globalSettings.U2f.AppId;
}
public static string RandomString(int length, bool alpha = true, bool upper = true, bool lower = true,
bool numeric = true, bool special = false)
{
return RandomString(length, RandomStringCharacters(alpha, upper, lower, numeric, special));
}
public static string RandomString(int length, string characters)
{
return new string(Enumerable.Repeat(characters, length).Select(s => s[_random.Next(s.Length)]).ToArray());
}
public static string SecureRandomString(int length, bool alpha = true, bool upper = true, bool lower = true,
bool numeric = true, bool special = false)
{
var characters = string.Empty;
if(alpha)
{
if(upper)
{
characters += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
if(lower)
{
characters += "abcdefghijklmnopqrstuvwxyz";
}
}
if(numeric)
{
characters += "0123456789";
}
if(special)
{
characters += "!@#$%^*&";
}
return SecureRandomString(length, characters);
return SecureRandomString(length, RandomStringCharacters(alpha, upper, lower, numeric, special));
}
// ref https://stackoverflow.com/a/8996788/1090359 with modifications
@ -205,6 +194,35 @@ namespace Bit.Core.Utilities
}
}
private static string RandomStringCharacters(bool alpha, bool upper, bool lower, bool numeric, bool special)
{
var characters = string.Empty;
if(alpha)
{
if(upper)
{
characters += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
if(lower)
{
characters += "abcdefghijklmnopqrstuvwxyz";
}
}
if(numeric)
{
characters += "0123456789";
}
if(special)
{
characters += "!@#$%^*&";
}
return characters;
}
// ref: https://stackoverflow.com/a/11124118/1090359
// Returns the human-readable file size for an arbitrary 64-bit file size .
// The format is "0.## XB", ex: "4.2 KB" or "1.43 GB"

View File

@ -3,6 +3,9 @@
"baseVaultUri": "https://vault.bitwarden.com/#",
"u2f": {
"appId": "https://vault.bitwarden.com/app-id.json"
},
"braintree": {
"production": true
}
}
}

View File

@ -36,6 +36,12 @@
},
"u2f": {
"appId": "https://localhost:4001/app-id.json"
},
"braintree": {
"production": false,
"merchantId": "SECRET",
"publicKey": "SECRET",
"privateKey": "SECRET"
}
}
}