From fdaa9504d5de8a9deb279fdb0bc159ade591829a Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 21 Feb 2019 22:43:37 -0500 Subject: [PATCH] bitpay invoice api --- src/Api/Controllers/MiscController.cs | 19 ++++++ src/Api/Startup.cs | 2 + src/Api/appsettings.Production.json | 3 + src/Api/appsettings.json | 4 ++ src/Billing/Startup.cs | 3 + src/Billing/appsettings.Production.json | 3 + src/Billing/appsettings.json | 4 ++ src/Core/Core.csproj | 1 + src/Core/GlobalSettings.cs | 7 ++ .../Api/Request/BitPayInvoiceRequestModel.cs | 65 +++++++++++++++++++ src/Core/Utilities/BitPayClient.cs | 33 ++++++++++ 11 files changed, 144 insertions(+) create mode 100644 src/Core/Models/Api/Request/BitPayInvoiceRequestModel.cs create mode 100644 src/Core/Utilities/BitPayClient.cs diff --git a/src/Api/Controllers/MiscController.cs b/src/Api/Controllers/MiscController.cs index b01cd1a600..a56d8f105f 100644 --- a/src/Api/Controllers/MiscController.cs +++ b/src/Api/Controllers/MiscController.cs @@ -1,11 +1,21 @@ using System; using Microsoft.AspNetCore.Mvc; using Bit.Core.Models.Api; +using System.Threading.Tasks; +using Bit.Core.Utilities; +using Microsoft.AspNetCore.Authorization; namespace Bit.Api.Controllers { public class MiscController : Controller { + private readonly BitPayClient _bitPayClient; + + public MiscController(BitPayClient bitPayClient) + { + _bitPayClient = bitPayClient; + } + [HttpGet("~/alive")] [HttpGet("~/now")] public DateTime Get() @@ -28,5 +38,14 @@ namespace Bit.Api.Controllers Headers = HttpContext.Request?.Headers, }); } + + [Authorize("Application")] + [HttpPost("~/bitpay-invoice")] + [SelfHosted(NotSelfHostedOnly = true)] + public async Task PostBitPayInvoice([FromBody]BitPayInvoiceRequestModel model) + { + var invoice = await _bitPayClient.CreateInvoiceAsync(model.ToBitpayClientInvoice()); + return invoice.Url; + } } } diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 9fe98cb54a..d9ed03b23e 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -63,6 +63,8 @@ namespace Bit.Api // Rate limiting services.AddSingleton(); services.AddSingleton(); + // BitPay + services.AddSingleton(); } // Identity diff --git a/src/Api/appsettings.Production.json b/src/Api/appsettings.Production.json index 5ea6892d03..c4be398fc6 100644 --- a/src/Api/appsettings.Production.json +++ b/src/Api/appsettings.Production.json @@ -14,6 +14,9 @@ }, "braintree": { "production": true + }, + "bitPay": { + "production": true } } } diff --git a/src/Api/appsettings.json b/src/Api/appsettings.json index 948c211f2e..cb3cbcd99b 100644 --- a/src/Api/appsettings.json +++ b/src/Api/appsettings.json @@ -62,6 +62,10 @@ "merchantId": "SECRET", "publicKey": "SECRET", "privateKey": "SECRET" + }, + "bitPay": { + "production": false, + "base58Secret": "SECRET" } }, "IpRateLimitOptions": { diff --git a/src/Billing/Startup.cs b/src/Billing/Startup.cs index fb54f7747b..ab1a7b3e25 100644 --- a/src/Billing/Startup.cs +++ b/src/Billing/Startup.cs @@ -43,6 +43,9 @@ namespace Bit.Billing services.AddSingleton(); services.AddSingleton(); + // BitPay Client + services.AddSingleton(); + // Context services.AddScoped(); diff --git a/src/Billing/appsettings.Production.json b/src/Billing/appsettings.Production.json index ac9bb60681..27fc647468 100644 --- a/src/Billing/appsettings.Production.json +++ b/src/Billing/appsettings.Production.json @@ -14,6 +14,9 @@ }, "braintree": { "production": true + }, + "bitPay": { + "production": true } }, "billingSettings": { diff --git a/src/Billing/appsettings.json b/src/Billing/appsettings.json index 15ebb7e169..c52a94e2b9 100644 --- a/src/Billing/appsettings.json +++ b/src/Billing/appsettings.json @@ -51,6 +51,10 @@ "merchantId": "SECRET", "publicKey": "SECRET", "privateKey": "SECRET" + }, + "bitPay": { + "production": false, + "base58Secret": "SECRET" } }, "billingSettings": { diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index adfe7f15c0..3940b9cc65 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index 95e324b21c..b535f3d93c 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -31,6 +31,7 @@ namespace Bit.Core public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings(); public virtual DuoSettings Duo { get; set; } = new DuoSettings(); public virtual BraintreeSettings Braintree { get; set; } = new BraintreeSettings(); + public virtual BitPaySettings BitPay { get; set; } = new BitPaySettings(); public class BaseServiceUriSettings { @@ -186,6 +187,12 @@ namespace Bit.Core public string PrivateKey { get; set; } } + public class BitPaySettings + { + public bool Production { get; set; } + public string Base58Secret { get; set; } + } + public class InstallationSettings { public Guid Id { get; set; } diff --git a/src/Core/Models/Api/Request/BitPayInvoiceRequestModel.cs b/src/Core/Models/Api/Request/BitPayInvoiceRequestModel.cs new file mode 100644 index 0000000000..4cd8264467 --- /dev/null +++ b/src/Core/Models/Api/Request/BitPayInvoiceRequestModel.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.Models.Api +{ + public class BitPayInvoiceRequestModel : IValidatableObject + { + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + public bool Credit { get; set; } + [Required] + public decimal? Amount { get; set; } + public string ReturnUrl { get; set; } + public string Name { get; set; } + public string Email { get; set; } + + public NBitpayClient.Invoice ToBitpayClientInvoice() + { + var inv = new NBitpayClient.Invoice + { + Price = Amount.Value, + Currency = "USD", + RedirectURL = ReturnUrl, + BuyerEmail = Email, + Buyer = new NBitpayClient.Buyer + { + email = Email, + Name = Name + } + }; + + var posData = string.Empty; + if(UserId.HasValue) + { + posData = "userId:" + UserId.Value; + } + else if(OrganizationId.HasValue) + { + posData = "organizationId:" + OrganizationId.Value; + } + + if(Credit) + { + posData += ",accountCredit:1"; + inv.ItemDesc = "Bitwarden Account Credit"; + } + else + { + inv.ItemDesc = "Bitwarden"; + } + + inv.PosData = posData; + return inv; + } + + public IEnumerable Validate(ValidationContext validationContext) + { + if(!UserId.HasValue && !OrganizationId.HasValue) + { + yield return new ValidationResult("User or Ooganization is required."); + } + } + } +} diff --git a/src/Core/Utilities/BitPayClient.cs b/src/Core/Utilities/BitPayClient.cs new file mode 100644 index 0000000000..5db08f0437 --- /dev/null +++ b/src/Core/Utilities/BitPayClient.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; + +namespace Bit.Core.Utilities +{ + public class BitPayClient + { + private readonly NBitpayClient.Bitpay _bpClient; + + public BitPayClient(GlobalSettings globalSettings) + { + var btcSecret = new NBitcoin.BitcoinSecret(globalSettings.BitPay.Base58Secret, + globalSettings.BitPay.Production ? null : NBitcoin.Network.TestNet); + _bpClient = new NBitpayClient.Bitpay(btcSecret.PrivateKey, + new Uri(globalSettings.BitPay.Production ? "https://bitpay.com/" : "https://test.bitpay.com/")); + } + + public Task TestAccessAsync() + { + return _bpClient.TestAccessAsync(NBitpayClient.Facade.Merchant); + } + + public Task GetInvoiceAsync(string id) + { + return _bpClient.GetInvoiceAsync(id); + } + + public Task CreateInvoiceAsync(NBitpayClient.Invoice invoice) + { + return _bpClient.CreateInvoiceAsync(invoice); + } + } +}