btcpayserver/BTCPayServer/Controllers/InvoiceController.UI.cs

712 lines
32 KiB
C#
Raw Normal View History

using System;
2017-09-13 08:47:34 +02:00
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Mime;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Filters;
2018-12-10 07:34:48 +01:00
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Changelly;
2018-12-11 12:47:38 +01:00
using BTCPayServer.Payments.CoinSwitch;
2018-03-30 07:34:14 +02:00
using BTCPayServer.Payments.Lightning;
2018-04-29 19:33:42 +02:00
using BTCPayServer.Security;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Invoices.Export;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using NBitcoin;
using NBitpayClient;
using NBXplorer;
using Newtonsoft.Json.Linq;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Controllers
{
public partial class InvoiceController
{
[HttpGet]
[Route("invoices/{invoiceId}")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
public async Task<IActionResult> Invoice(string invoiceId)
{
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
{
InvoiceId = invoiceId,
UserId = GetUserId(),
2018-01-14 13:48:23 +01:00
IncludeAddresses = true,
IncludeEvents = true
})).FirstOrDefault();
if (invoice == null)
return NotFound();
var prodInfo = invoice.ProductInformation;
var store = await _StoreRepository.FindStore(invoice.StoreId);
var model = new InvoiceDetailsModel()
{
StoreName = store.StoreName,
StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }),
Id = invoice.Id,
State = invoice.GetInvoiceState().ToString(),
2018-05-13 08:09:17 +02:00
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" :
2018-05-11 15:12:45 +02:00
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
"low",
RefundEmail = invoice.RefundMail,
CreatedDate = invoice.InvoiceTime,
ExpirationDate = invoice.ExpirationTime,
MonitoringDate = invoice.MonitoringExpiration,
OrderId = invoice.OrderId,
BuyerInformation = invoice.BuyerInformation,
Fiat = _CurrencyNameTable.DisplayFormatCurrency(prodInfo.Price, prodInfo.Currency),
TaxIncluded = _CurrencyNameTable.DisplayFormatCurrency(prodInfo.TaxIncluded, prodInfo.Currency),
NotificationEmail = invoice.NotificationEmail,
NotificationUrl = invoice.NotificationURL,
2018-01-14 07:01:09 +01:00
RedirectUrl = invoice.RedirectURL,
ProductInformation = invoice.ProductInformation,
2018-01-14 13:48:23 +01:00
StatusException = invoice.ExceptionStatus,
Events = invoice.Events,
PosData = PosDataParser.ParsePosData(invoice.PosData),
StatusMessage = StatusMessage
};
model.Addresses = invoice.HistoricalAddresses.Select(h =>
new InvoiceDetailsModel.AddressModel
{
Destination = h.GetAddress(),
PaymentMethod = h.GetPaymentMethodId().ToPrettyString(),
Current = !h.UnAssigned.HasValue
}).ToArray();
2019-05-07 16:32:47 +02:00
var details = InvoicePopulatePayments(invoice);
model.CryptoPayments = details.CryptoPayments;
model.OnChainPayments = details.OnChainPayments;
model.OffChainPayments = details.OffChainPayments;
return View(model);
}
2019-05-07 16:32:47 +02:00
private InvoiceDetailsModel InvoicePopulatePayments(InvoiceEntity invoice)
{
var model = new InvoiceDetailsModel();
foreach (var data in invoice.GetPaymentMethods())
{
var accounting = data.Calculate();
var paymentMethodId = data.GetId();
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
cryptoPayment.PaymentMethod = paymentMethodId.ToPrettyString();
cryptoPayment.Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
var paymentMethodDetails = data.GetPaymentMethodDetails();
cryptoPayment.Address = paymentMethodDetails.GetPaymentDestination();
cryptoPayment.Rate = ExchangeRate(data);
model.CryptoPayments.Add(cryptoPayment);
}
2019-05-07 16:32:47 +02:00
foreach (var payment in invoice.GetPayments())
{
var paymentData = payment.GetCryptoPaymentData();
//TODO: abstract
2019-05-07 16:32:47 +02:00
if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData)
{
2019-05-07 16:32:47 +02:00
var m = new InvoiceDetailsModel.Payment();
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
m.DepositAddress = onChainPaymentData.GetDestination();
2019-05-07 16:32:47 +02:00
int confirmationCount = onChainPaymentData.ConfirmationCount;
if (confirmationCount >= payment.Network.MaxTrackedConfirmation)
2019-05-07 16:32:47 +02:00
{
m.Confirmations = "At least " + (payment.Network.MaxTrackedConfirmation);
}
else
{
2019-05-07 16:32:47 +02:00
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
2019-05-07 16:32:47 +02:00
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
m.ReceivedTime = payment.ReceivedTime;
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, payment.Network.BlockExplorerLink, m.TransactionId);
2019-05-07 16:32:47 +02:00
m.Replaced = !payment.Accounted;
model.OnChainPayments.Add(m);
}
else
{
var lightningPaymentData = (LightningLikePaymentData)paymentData;
model.OffChainPayments.Add(new InvoiceDetailsModel.OffChainPayment()
{
Crypto = payment.Network.CryptoCode,
2019-05-07 16:32:47 +02:00
BOLT11 = lightningPaymentData.BOLT11
});
}
}
return model;
}
2017-09-13 08:47:34 +02:00
[HttpGet]
[Route("i/{invoiceId}")]
[Route("i/{invoiceId}/{paymentMethodId}")]
[Route("invoice")]
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
[XFrameOptionsAttribute(null)]
2018-07-11 19:38:08 +02:00
[ReferrerPolicyAttribute("origin")]
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string paymentMethodId = null,
[FromQuery]string view = null)
{
//Keep compatibility with Bitpay
invoiceId = invoiceId ?? id;
id = invoiceId;
2019-03-31 20:46:38 +02:00
//
2017-10-13 09:07:57 +02:00
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
if (model == null)
return NotFound();
if (view == "modal")
model.IsModal = true;
2018-07-12 10:38:21 +02:00
_CSP.Add(new ConsentSecurityPolicy("script-src", "'unsafe-eval'")); // Needed by Vue
2018-07-14 05:35:34 +02:00
if (!string.IsNullOrEmpty(model.CustomCSSLink) &&
2018-07-12 10:38:21 +02:00
Uri.TryCreate(model.CustomCSSLink, UriKind.Absolute, out var uri))
{
_CSP.Clear();
}
if (!string.IsNullOrEmpty(model.CustomLogoLink) &&
Uri.TryCreate(model.CustomLogoLink, UriKind.Absolute, out uri))
{
_CSP.Clear();
}
return View(nameof(Checkout), model);
}
2019-03-31 20:46:38 +02:00
[HttpGet]
[Route("invoice-noscript")]
public async Task<IActionResult> CheckoutNoScript(string invoiceId, string id = null, string paymentMethodId = null)
{
//Keep compatibility with Bitpay
invoiceId = invoiceId ?? id;
id = invoiceId;
//
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
if (model == null)
return NotFound();
2019-04-05 04:48:52 +02:00
return View(model);
2019-03-31 20:46:38 +02:00
}
//TODO: abstract
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
{
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
if (invoice == null)
return null;
2018-01-09 03:41:07 +01:00
var store = await _StoreRepository.FindStore(invoice.StoreId);
bool isDefaultPaymentId = false;
if (paymentMethodId == null)
{
paymentMethodId = store.GetDefaultPaymentId(_NetworkProvider);
isDefaultPaymentId = true;
}
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
if (network == null && isDefaultPaymentId)
{
//TODO: need to look into a better way for this as it does not scale
network = _NetworkProvider.GetAll().OfType<BTCPayNetwork>().FirstOrDefault();
paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
}
if (invoice == null || network == null)
return null;
if (!invoice.Support(paymentMethodId))
{
if (!isDefaultPaymentId)
return null;
var paymentMethodTemp = invoice.GetPaymentMethods()
.Where(c => paymentMethodId.CryptoCode == c.GetId().CryptoCode)
2018-08-08 07:45:46 +02:00
.FirstOrDefault();
if (paymentMethodTemp == null)
paymentMethodTemp = invoice.GetPaymentMethods().First();
network = paymentMethodTemp.Network;
paymentMethodId = paymentMethodTemp.GetId();
}
2018-01-09 03:41:07 +01:00
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
var dto = invoice.EntityToDTO();
var cryptoInfo = dto.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
2018-03-23 09:27:48 +01:00
var storeBlob = store.GetStoreBlob();
2017-12-03 14:14:08 +01:00
var currency = invoice.ProductInformation.Currency;
var accounting = paymentMethod.Calculate();
ChangellySettings changelly = (storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled &&
storeBlob.ChangellySettings.IsConfigured())
? storeBlob.ChangellySettings
: null;
2018-12-11 12:47:38 +01:00
CoinSwitchSettings coinswitch = (storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled &&
storeBlob.CoinSwitchSettings.IsConfigured())
? storeBlob.CoinSwitchSettings
: null;
var changellyAmountDue = changelly != null
? (accounting.Due.ToDecimal(MoneyUnit.BTC) *
(1m + (changelly.AmountMarkupPercentage / 100m)))
: (decimal?)null;
2019-06-04 03:11:52 +02:00
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
var model = new PaymentModel()
{
2018-01-09 03:41:07 +01:00
CryptoCode = network.CryptoCode,
RootPath = this.Request.PathBase.Value.WithTrailingSlash(),
OrderId = invoice.OrderId,
InvoiceId = invoice.Id,
DefaultLang = storeBlob.DefaultLang ?? "en",
2018-05-03 23:51:04 +02:00
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
CustomCSSLink = storeBlob.CustomCSS?.AbsoluteUri,
CustomLogoLink = storeBlob.CustomLogo?.AbsoluteUri,
2019-05-31 08:00:32 +02:00
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
BtcDue = accounting.Due.ToString(),
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ToString(),
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation),
CustomerEmail = invoice.RefundMail,
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
ItemDesc = invoice.ProductInformation.ItemDesc,
Rate = ExchangeRate(paymentMethod),
MerchantRefLink = invoice.RedirectURL ?? "/",
RedirectAutomatically = invoice.RedirectAutomatically,
StoreName = store.StoreName,
2018-03-30 07:34:14 +02:00
PeerInfo = (paymentMethodDetails as LightningLikePaymentMethodDetails)?.NodeInfo,
TxCount = accounting.TxRequired,
BtcPaid = accounting.Paid.ToString(),
#pragma warning disable CS0618 // Type or member is obsolete
Status = invoice.StatusString,
#pragma warning restore CS0618 // Type or member is obsolete
2019-01-07 07:35:18 +01:00
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
ChangellyEnabled = changelly != null,
ChangellyMerchantId = changelly?.ChangellyMerchantId,
ChangellyAmountDue = changellyAmountDue,
2018-12-11 12:47:38 +01:00
CoinSwitchEnabled = coinswitch != null,
CoinSwitchAmountMarkupPercentage = coinswitch?.AmountMarkupPercentage ?? 0,
2018-12-11 12:47:38 +01:00
CoinSwitchMerchantId = coinswitch?.MerchantId,
2018-12-18 19:01:58 +01:00
CoinSwitchMode = coinswitch?.Mode,
StoreId = store.Id,
AvailableCryptos = invoice.GetPaymentMethods()
.Where(i => i.Network != null)
.Select(kv =>
{
var availableCryptoPaymentMethodId = kv.GetId();
2019-06-04 03:11:52 +02:00
var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
return new PaymentModel.AvailableCrypto()
{
PaymentMethodId = kv.GetId().ToString(),
CryptoCode = kv.GetId().CryptoCode,
PaymentMethodName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId),
IsLightning =
kv.GetId().PaymentType == PaymentTypes.LightningLike,
2019-05-31 08:00:32 +02:00
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
Link = Url.Action(nameof(Checkout),
new
{
invoiceId = invoiceId,
paymentMethodId = kv.GetId().ToString()
})
};
}).Where(c => c.CryptoImage != "/")
.OrderByDescending(a => a.CryptoCode == "BTC").ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
2018-07-14 05:35:34 +02:00
.ToList()
};
2017-09-13 08:47:34 +02:00
2019-05-31 08:00:32 +02:00
paymentMethodHandler.PreparePaymentModel(model, dto);
model.PaymentMethodId = paymentMethodId.ToString();
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
2018-04-18 11:23:39 +02:00
model.TimeLeft = expiration.PrettyPrint();
return model;
}
2017-09-13 08:47:34 +02:00
private string OrderAmountFromInvoice(string cryptoCode, ProductInformation productInformation)
{
// if invoice source currency is the same as currently display currency, no need for "order amount from invoice"
if (cryptoCode == productInformation.Currency)
return null;
return _CurrencyNameTable.DisplayFormatCurrency(productInformation.Price, productInformation.Currency);
}
private string ExchangeRate(PaymentMethod paymentMethod)
{
string currency = paymentMethod.ParentEntity.ProductInformation.Currency;
return _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate, currency);
}
[HttpGet]
[Route("i/{invoiceId}/status")]
[Route("i/{invoiceId}/{paymentMethodId}/status")]
[Route("invoice/{invoiceId}/status")]
[Route("invoice/{invoiceId}/{paymentMethodId}/status")]
[Route("invoice/status")]
public async Task<IActionResult> GetStatus(string invoiceId, string paymentMethodId = null)
{
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
if (model == null)
return NotFound();
return Json(model);
}
2017-09-13 08:47:34 +02:00
[HttpGet]
[Route("i/{invoiceId}/status/ws")]
[Route("i/{invoiceId}/{paymentMethodId}/status/ws")]
[Route("invoice/{invoiceId}/status/ws")]
[Route("invoice/{invoiceId}/{paymentMethodId}/status")]
[Route("invoice/status/ws")]
public async Task<IActionResult> GetStatusWebSocket(string invoiceId)
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
return NotFound();
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
if (invoice == null || invoice.Status == InvoiceStatus.Complete || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired)
return NotFound();
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
CompositeDisposable leases = new CompositeDisposable();
try
{
2018-01-07 13:07:06 +01:00
leases.Add(_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
leases.Add(_EventAggregator.Subscribe<Events.InvoiceNewAddressEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async o => await NotifySocket(webSocket, o.Invoice.Id, invoiceId)));
while (true)
{
var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken));
if (message.MessageType == WebSocketMessageType.Close)
break;
}
}
finally
{
leases.Dispose();
2018-02-12 19:27:36 +01:00
await webSocket.CloseSocket();
}
return new EmptyResult();
}
ArraySegment<Byte> DummyBuffer = new ArraySegment<Byte>(new Byte[1]);
private async Task NotifySocket(WebSocket webSocket, string invoiceId, string expectedId)
{
if (invoiceId != expectedId || webSocket.State != WebSocketState.Open)
return;
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
try
{
await webSocket.SendAsync(DummyBuffer, WebSocketMessageType.Binary, true, cts.Token);
}
catch { try { webSocket.Dispose(); } catch { } }
}
[HttpPost]
[Route("i/{invoiceId}/UpdateCustomer")]
[Route("invoice/UpdateCustomer")]
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await _InvoiceRepository.UpdateInvoice(invoiceId, data).ConfigureAwait(false);
return Ok("{}");
}
2017-09-13 08:47:34 +02:00
[HttpGet]
[Route("invoices")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int timezoneOffset = 0)
{
var model = new InvoicesModel
{
SearchTerm = searchTerm,
Skip = skip,
Count = count,
StatusMessage = StatusMessage,
TimezoneOffset = timezoneOffset
};
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
2019-01-17 15:40:47 +01:00
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
invoiceQuery.Count = count;
invoiceQuery.Skip = skip;
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
foreach (var invoice in list)
{
2018-12-10 07:34:48 +01:00
var state = invoice.GetInvoiceState();
model.Invoices.Add(new InvoiceModel()
{
2019-05-01 22:33:46 +02:00
Status = invoice.Status,
StatusString = state.ToString(),
ShowCheckout = invoice.Status == InvoiceStatus.New,
Date = invoice.InvoiceTime,
InvoiceId = invoice.Id,
OrderId = invoice.OrderId ?? string.Empty,
RedirectUrl = invoice.RedirectURL ?? string.Empty,
AmountCurrency = _CurrencyNameTable.DisplayFormatCurrency(invoice.ProductInformation.Price, invoice.ProductInformation.Currency),
2018-12-10 07:34:48 +01:00
CanMarkInvalid = state.CanMarkInvalid(),
CanMarkComplete = state.CanMarkComplete(),
2019-05-07 16:32:47 +02:00
Details = InvoicePopulatePayments(invoice)
});
}
2019-01-17 15:40:47 +01:00
model.Total = await counting;
return View(model);
}
2017-09-13 08:47:34 +02:00
private InvoiceQuery GetInvoiceQuery(string searchTerm = null, int timezoneOffset = 0)
{
var fs = new SearchString(searchTerm);
var invoiceQuery = new InvoiceQuery()
{
TextSearch = fs.TextSearch,
UserId = GetUserId(),
Unusual = fs.GetFilterBool("unusual"),
Status = fs.GetFilterArray("status"),
ExceptionStatus = fs.GetFilterArray("exceptionstatus"),
StoreId = fs.GetFilterArray("storeid"),
ItemCode = fs.GetFilterArray("itemcode"),
OrderId = fs.GetFilterArray("orderid"),
StartDate = fs.GetFilterDate("startdate", timezoneOffset),
EndDate = fs.GetFilterDate("enddate", timezoneOffset)
};
return invoiceQuery;
}
2018-11-30 08:22:39 +01:00
[HttpGet]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> Export(string format, string searchTerm = null, int timezoneOffset = 0)
2018-11-30 08:22:39 +01:00
{
var model = new InvoiceExport(_CurrencyNameTable);
2018-11-30 08:22:39 +01:00
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
invoiceQuery.Skip = 0;
invoiceQuery.Count = int.MaxValue;
var invoices = await _InvoiceRepository.GetInvoices(invoiceQuery);
2018-11-30 08:22:39 +01:00
var res = model.Process(invoices, format);
var cd = new ContentDisposition
{
FileName = $"btcpay-export-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.{format}",
Inline = true
};
Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("X-Content-Type-Options", "nosniff");
2018-11-30 08:22:39 +01:00
return Content(res, "application/" + format);
}
private SelectList GetPaymentMethodsSelectList()
{
return new SelectList(_paymentMethodHandlerDictionary.Distinct().SelectMany(handler =>
handler.GetSupportedPaymentMethods()
.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString()))),
nameof(SelectListItem.Value),
nameof(SelectListItem.Text));
}
[HttpGet]
[Route("invoices/create")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> CreateInvoice()
{
2018-04-29 19:33:42 +02:00
var stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id), nameof(StoreData.StoreName), null);
if (stores.Count() == 0)
{
StatusMessage = "Error: You need to create at least one store before creating a transaction";
2018-03-23 08:24:57 +01:00
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
return View(new CreateInvoiceModel() { Stores = stores, AvailablePaymentMethods = GetPaymentMethodsSelectList() });
}
2017-09-13 08:47:34 +02:00
[HttpPost]
[Route("invoices/create")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
{
2018-04-29 19:33:42 +02:00
var stores = await _StoreRepository.GetStoresByUserId(GetUserId());
model.Stores = new SelectList(stores, nameof(StoreData.Id), nameof(StoreData.StoreName), model.StoreId);
model.AvailablePaymentMethods = GetPaymentMethodsSelectList();
2018-04-29 19:33:42 +02:00
var store = stores.FirstOrDefault(s => s.Id == model.StoreId);
2018-05-13 08:09:17 +02:00
if (store == null)
2018-04-29 19:33:42 +02:00
{
ModelState.AddModelError(nameof(model.StoreId), "Store not found");
}
if (!ModelState.IsValid)
{
return View(model);
}
2018-03-23 08:24:57 +01:00
StatusMessage = null;
if (!store.HasClaim(Policies.CanCreateInvoice.Key))
2018-03-23 08:24:57 +01:00
{
ModelState.AddModelError(nameof(model.StoreId), "You need to be owner of this store to create an invoice");
return View(model);
2018-03-23 08:24:57 +01:00
}
if (store.GetSupportedPaymentMethods(_NetworkProvider).Count() == 0)
{
ModelState.AddModelError(nameof(model.StoreId), "You need to configure the derivation scheme in order to create an invoice");
return View(model);
2018-03-23 08:24:57 +01:00
}
2018-03-23 08:24:57 +01:00
if (StatusMessage != null)
2018-03-23 08:24:57 +01:00
{
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{
storeId = store.Id
});
}
try
{
var result = await CreateInvoiceCore(new CreateInvoiceRequest()
{
Price = model.Amount.Value,
Currency = model.Currency,
PosData = model.PosData,
OrderId = model.OrderId,
//RedirectURL = redirect + "redirect",
NotificationEmail = model.NotificationEmail,
NotificationURL = model.NotificationUrl,
ItemDesc = model.ItemDesc,
FullNotifications = true,
BuyerEmail = model.BuyerEmail,
SupportedTransactionCurrencies = model.SupportedTransactionCurrencies?.ToDictionary(s => s, s => new InvoiceSupportedTransactionCurrency()
{
Enabled = true
})
}, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
StatusMessage = $"Invoice {result.Data.Id} just created!";
return RedirectToAction(nameof(ListInvoices));
}
2018-05-03 18:46:52 +02:00
catch (BitpayHttpException ex)
{
2018-05-03 18:46:52 +02:00
ModelState.TryAddModelError(nameof(model.Currency), $"Error: {ex.Message}");
return View(model);
}
}
2017-09-13 08:47:34 +02:00
[HttpPost]
2018-12-10 07:34:48 +01:00
[Route("invoices/{invoiceId}/changestate/{newState}")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ChangeInvoiceState(string invoiceId, string newState)
{
2018-12-06 09:08:28 +01:00
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
{
InvoiceId = invoiceId,
UserId = GetUserId()
})).FirstOrDefault();
var model = new InvoiceStateChangeModel();
if (invoice == null)
{
model.NotFound = true;
return NotFound(model);
}
2018-12-10 07:34:48 +01:00
if (newState == "invalid")
{
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
_EventAggregator.Publish(new InvoiceEvent(invoice, 1008, InvoiceEvent.MarkedInvalid));
model.StatusString = new InvoiceState("invalid", "marked").ToString();
2018-12-10 07:34:48 +01:00
}
else if (newState == "complete")
2018-12-10 07:34:48 +01:00
{
await _InvoiceRepository.UpdatePaidInvoiceToComplete(invoiceId);
_EventAggregator.Publish(new InvoiceEvent(invoice, 2008, InvoiceEvent.MarkedCompleted));
model.StatusString = new InvoiceState("complete", "marked").ToString();
2018-12-10 07:34:48 +01:00
}
return Json(model);
}
public class InvoiceStateChangeModel
{
public bool NotFound { get; set; }
public string StatusString { get; set; }
}
[TempData]
public string StatusMessage
{
get;
set;
}
2017-09-13 08:47:34 +02:00
private string GetUserId()
{
return _UserManager.GetUserId(User);
}
public class PosDataParser
{
public static Dictionary<string, object> ParsePosData(string posData)
{
var result = new Dictionary<string, object>();
if (string.IsNullOrEmpty(posData))
{
return result;
}
try
{
var jObject = JObject.Parse(posData);
foreach (var item in jObject)
{
switch (item.Value.Type)
{
case JTokenType.Array:
var items = item.Value.AsEnumerable().ToList();
for (var i = 0; i < items.Count(); i++)
{
result.Add($"{item.Key}[{i}]", ParsePosData(items[i].ToString()));
}
break;
case JTokenType.Object:
result.Add(item.Key, ParsePosData(item.Value.ToString()));
break;
default:
result.Add(item.Key, item.Value.ToString());
break;
}
}
}
catch
{
result.Add(string.Empty, posData);
}
return result;
}
}
}
2017-09-13 08:47:34 +02:00
}