Browse Source
* Revise BitPay controller * Run dotnet format * Kyle's feedback * Run dotnet format * Temporary logging * Whoops * Undo temporary loggingpull/6510/head
15 changed files with 508 additions and 322 deletions
@ -1,45 +0,0 @@
@@ -1,45 +0,0 @@
|
||||
using Bit.Api.Models.Request; |
||||
using Bit.Core.Settings; |
||||
using Bit.Core.Utilities; |
||||
using Microsoft.AspNetCore.Authorization; |
||||
using Microsoft.AspNetCore.Mvc; |
||||
using Stripe; |
||||
|
||||
namespace Bit.Api.Controllers; |
||||
|
||||
public class MiscController : Controller |
||||
{ |
||||
private readonly BitPayClient _bitPayClient; |
||||
private readonly GlobalSettings _globalSettings; |
||||
|
||||
public MiscController( |
||||
BitPayClient bitPayClient, |
||||
GlobalSettings globalSettings) |
||||
{ |
||||
_bitPayClient = bitPayClient; |
||||
_globalSettings = globalSettings; |
||||
} |
||||
|
||||
[Authorize("Application")] |
||||
[HttpPost("~/bitpay-invoice")] |
||||
[SelfHosted(NotSelfHostedOnly = true)] |
||||
public async Task<string> PostBitPayInvoice([FromBody] BitPayInvoiceRequestModel model) |
||||
{ |
||||
var invoice = await _bitPayClient.CreateInvoiceAsync(model.ToBitpayInvoice(_globalSettings)); |
||||
return invoice.Url; |
||||
} |
||||
|
||||
[Authorize("Application")] |
||||
[HttpPost("~/setup-payment")] |
||||
[SelfHosted(NotSelfHostedOnly = true)] |
||||
public async Task<string> PostSetupPayment() |
||||
{ |
||||
var options = new SetupIntentCreateOptions |
||||
{ |
||||
Usage = "off_session" |
||||
}; |
||||
var service = new SetupIntentService(); |
||||
var setupIntent = await service.CreateAsync(options); |
||||
return setupIntent.ClientSecret; |
||||
} |
||||
} |
||||
@ -1,73 +0,0 @@
@@ -1,73 +0,0 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below |
||||
#nullable disable |
||||
|
||||
using System.ComponentModel.DataAnnotations; |
||||
using Bit.Core.Settings; |
||||
|
||||
namespace Bit.Api.Models.Request; |
||||
|
||||
public class BitPayInvoiceRequestModel : IValidatableObject |
||||
{ |
||||
public Guid? UserId { get; set; } |
||||
public Guid? OrganizationId { get; set; } |
||||
public Guid? ProviderId { 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 BitPayLight.Models.Invoice.Invoice ToBitpayInvoice(GlobalSettings globalSettings) |
||||
{ |
||||
var inv = new BitPayLight.Models.Invoice.Invoice |
||||
{ |
||||
Price = Convert.ToDouble(Amount.Value), |
||||
Currency = "USD", |
||||
RedirectUrl = ReturnUrl, |
||||
Buyer = new BitPayLight.Models.Invoice.Buyer |
||||
{ |
||||
Email = Email, |
||||
Name = Name |
||||
}, |
||||
NotificationUrl = globalSettings.BitPay.NotificationUrl, |
||||
FullNotifications = true, |
||||
ExtendedNotifications = true |
||||
}; |
||||
|
||||
var posData = string.Empty; |
||||
if (UserId.HasValue) |
||||
{ |
||||
posData = "userId:" + UserId.Value; |
||||
} |
||||
else if (OrganizationId.HasValue) |
||||
{ |
||||
posData = "organizationId:" + OrganizationId.Value; |
||||
} |
||||
else if (ProviderId.HasValue) |
||||
{ |
||||
posData = "providerId:" + ProviderId.Value; |
||||
} |
||||
|
||||
if (Credit) |
||||
{ |
||||
posData += ",accountCredit:1"; |
||||
inv.ItemDesc = "Bitwarden Account Credit"; |
||||
} |
||||
else |
||||
{ |
||||
inv.ItemDesc = "Bitwarden"; |
||||
} |
||||
|
||||
inv.PosData = posData; |
||||
return inv; |
||||
} |
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) |
||||
{ |
||||
if (!UserId.HasValue && !OrganizationId.HasValue && !ProviderId.HasValue) |
||||
{ |
||||
yield return new ValidationResult("User, Organization or Provider is required."); |
||||
} |
||||
} |
||||
} |
||||
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
namespace Bit.Billing.Constants; |
||||
|
||||
public static class BitPayInvoiceStatus |
||||
{ |
||||
public const string Confirmed = "confirmed"; |
||||
public const string Complete = "complete"; |
||||
} |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
namespace Bit.Core.Billing.Constants; |
||||
|
||||
public static class BitPayConstants |
||||
{ |
||||
public static class InvoiceStatuses |
||||
{ |
||||
public const string Complete = "complete"; |
||||
} |
||||
|
||||
public static class PosDataKeys |
||||
{ |
||||
public const string AccountCredit = "accountCredit:1"; |
||||
} |
||||
} |
||||
@ -1,30 +0,0 @@
@@ -1,30 +0,0 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below |
||||
#nullable disable |
||||
|
||||
using Bit.Core.Settings; |
||||
|
||||
namespace Bit.Core.Utilities; |
||||
|
||||
public class BitPayClient |
||||
{ |
||||
private readonly BitPayLight.BitPay _bpClient; |
||||
|
||||
public BitPayClient(GlobalSettings globalSettings) |
||||
{ |
||||
if (CoreHelpers.SettingHasValue(globalSettings.BitPay.Token)) |
||||
{ |
||||
_bpClient = new BitPayLight.BitPay(globalSettings.BitPay.Token, |
||||
globalSettings.BitPay.Production ? BitPayLight.Env.Prod : BitPayLight.Env.Test); |
||||
} |
||||
} |
||||
|
||||
public Task<BitPayLight.Models.Invoice.Invoice> GetInvoiceAsync(string id) |
||||
{ |
||||
return _bpClient.GetInvoice(id); |
||||
} |
||||
|
||||
public Task<BitPayLight.Models.Invoice.Invoice> CreateInvoiceAsync(BitPayLight.Models.Invoice.Invoice invoice) |
||||
{ |
||||
return _bpClient.CreateInvoice(invoice); |
||||
} |
||||
} |
||||
@ -0,0 +1,391 @@
@@ -0,0 +1,391 @@
|
||||
using Bit.Billing.Controllers; |
||||
using Bit.Billing.Models; |
||||
using Bit.Core.AdminConsole.Entities; |
||||
using Bit.Core.AdminConsole.Entities.Provider; |
||||
using Bit.Core.AdminConsole.Repositories; |
||||
using Bit.Core.Billing.Constants; |
||||
using Bit.Core.Billing.Payment.Clients; |
||||
using Bit.Core.Billing.Services; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.Services; |
||||
using Bit.Core.Settings; |
||||
using BitPayLight.Models.Invoice; |
||||
using Microsoft.AspNetCore.Mvc; |
||||
using Microsoft.Extensions.Logging; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
using Transaction = Bit.Core.Entities.Transaction; |
||||
|
||||
namespace Bit.Billing.Test.Controllers; |
||||
|
||||
using static BitPayConstants; |
||||
|
||||
public class BitPayControllerTests |
||||
{ |
||||
private readonly GlobalSettings _globalSettings = new(); |
||||
private readonly IBitPayClient _bitPayClient = Substitute.For<IBitPayClient>(); |
||||
private readonly ITransactionRepository _transactionRepository = Substitute.For<ITransactionRepository>(); |
||||
private readonly IOrganizationRepository _organizationRepository = Substitute.For<IOrganizationRepository>(); |
||||
private readonly IUserRepository _userRepository = Substitute.For<IUserRepository>(); |
||||
private readonly IProviderRepository _providerRepository = Substitute.For<IProviderRepository>(); |
||||
private readonly IMailService _mailService = Substitute.For<IMailService>(); |
||||
private readonly IPaymentService _paymentService = Substitute.For<IPaymentService>(); |
||||
|
||||
private readonly IPremiumUserBillingService _premiumUserBillingService = |
||||
Substitute.For<IPremiumUserBillingService>(); |
||||
|
||||
private const string _validWebhookKey = "valid-webhook-key"; |
||||
private const string _invalidWebhookKey = "invalid-webhook-key"; |
||||
|
||||
public BitPayControllerTests() |
||||
{ |
||||
var bitPaySettings = new GlobalSettings.BitPaySettings { WebhookKey = _validWebhookKey }; |
||||
_globalSettings.BitPay = bitPaySettings; |
||||
} |
||||
|
||||
private BitPayController CreateController() => new( |
||||
_globalSettings, |
||||
_bitPayClient, |
||||
_transactionRepository, |
||||
_organizationRepository, |
||||
_userRepository, |
||||
_providerRepository, |
||||
_mailService, |
||||
_paymentService, |
||||
Substitute.For<ILogger<BitPayController>>(), |
||||
_premiumUserBillingService); |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_InvalidKey_BadRequest() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _invalidWebhookKey); |
||||
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result); |
||||
Assert.Equal("Invalid key", badRequestResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_NullKey_ThrowsException() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => controller.PostIpn(eventModel, null!)); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_EmptyKey_BadRequest() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
|
||||
var result = await controller.PostIpn(eventModel, string.Empty); |
||||
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result); |
||||
Assert.Equal("Invalid key", badRequestResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_NonUsdCurrency_BadRequest() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var invoice = CreateValidInvoice(currency: "EUR"); |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result); |
||||
Assert.Equal("Cannot process non-USD payments", badRequestResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_NullPosData_BadRequest() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var invoice = CreateValidInvoice(posData: null!); |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result); |
||||
Assert.Equal("Invalid POS data", badRequestResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_EmptyPosData_BadRequest() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var invoice = CreateValidInvoice(posData: ""); |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result); |
||||
Assert.Equal("Invalid POS data", badRequestResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_PosDataWithoutAccountCredit_BadRequest() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var invoice = CreateValidInvoice(posData: "organizationId:550e8400-e29b-41d4-a716-446655440000"); |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result); |
||||
Assert.Equal("Invalid POS data", badRequestResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_PosDataWithoutValidId_BadRequest() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var invoice = CreateValidInvoice(posData: PosDataKeys.AccountCredit); |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result); |
||||
Assert.Equal("Invalid POS data", badRequestResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_IncompleteInvoice_Ok() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var invoice = CreateValidInvoice(status: "paid"); |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
var okResult = Assert.IsType<OkObjectResult>(result); |
||||
Assert.Equal("Waiting for invoice to be completed", okResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_ExistingTransaction_Ok() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var invoice = CreateValidInvoice(); |
||||
var existingTransaction = new Transaction { GatewayId = invoice.Id }; |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
_transactionRepository.GetByGatewayIdAsync(GatewayType.BitPay, invoice.Id).Returns(existingTransaction); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
var okResult = Assert.IsType<OkObjectResult>(result); |
||||
Assert.Equal("Invoice already processed", okResult.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_ValidOrganizationTransaction_Success() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var organizationId = Guid.NewGuid(); |
||||
var invoice = CreateValidInvoice(posData: $"organizationId:{organizationId},{PosDataKeys.AccountCredit}"); |
||||
var organization = new Organization { Id = organizationId, BillingEmail = "billing@example.com" }; |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
_transactionRepository.GetByGatewayIdAsync(GatewayType.BitPay, invoice.Id).Returns((Transaction)null); |
||||
_organizationRepository.GetByIdAsync(organizationId).Returns(organization); |
||||
_paymentService.CreditAccountAsync(organization, Arg.Any<decimal>()).Returns(true); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
Assert.IsType<OkResult>(result); |
||||
await _transactionRepository.Received(1).CreateAsync(Arg.Is<Transaction>(t => |
||||
t.OrganizationId == organizationId && |
||||
t.Type == TransactionType.Credit && |
||||
t.Gateway == GatewayType.BitPay && |
||||
t.PaymentMethodType == PaymentMethodType.BitPay)); |
||||
await _organizationRepository.Received(1).ReplaceAsync(organization); |
||||
await _mailService.Received(1).SendAddedCreditAsync("billing@example.com", 100.00m); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_ValidUserTransaction_Success() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var userId = Guid.NewGuid(); |
||||
var invoice = CreateValidInvoice(posData: $"userId:{userId},{PosDataKeys.AccountCredit}"); |
||||
var user = new User { Id = userId, Email = "user@example.com" }; |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
_transactionRepository.GetByGatewayIdAsync(GatewayType.BitPay, invoice.Id).Returns((Transaction)null); |
||||
_userRepository.GetByIdAsync(userId).Returns(user); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
Assert.IsType<OkResult>(result); |
||||
await _transactionRepository.Received(1).CreateAsync(Arg.Is<Transaction>(t => |
||||
t.UserId == userId && |
||||
t.Type == TransactionType.Credit && |
||||
t.Gateway == GatewayType.BitPay && |
||||
t.PaymentMethodType == PaymentMethodType.BitPay)); |
||||
await _premiumUserBillingService.Received(1).Credit(user, 100.00m); |
||||
await _mailService.Received(1).SendAddedCreditAsync("user@example.com", 100.00m); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task PostIpn_ValidProviderTransaction_Success() |
||||
{ |
||||
var controller = CreateController(); |
||||
var eventModel = CreateValidEventModel(); |
||||
var providerId = Guid.NewGuid(); |
||||
var invoice = CreateValidInvoice(posData: $"providerId:{providerId},{PosDataKeys.AccountCredit}"); |
||||
var provider = new Provider { Id = providerId, BillingEmail = "provider@example.com" }; |
||||
|
||||
_bitPayClient.GetInvoice(eventModel.Data.Id).Returns(invoice); |
||||
_transactionRepository.GetByGatewayIdAsync(GatewayType.BitPay, invoice.Id).Returns((Transaction)null); |
||||
_providerRepository.GetByIdAsync(providerId).Returns(Task.FromResult(provider)); |
||||
_paymentService.CreditAccountAsync(provider, Arg.Any<decimal>()).Returns(true); |
||||
|
||||
var result = await controller.PostIpn(eventModel, _validWebhookKey); |
||||
|
||||
Assert.IsType<OkResult>(result); |
||||
await _transactionRepository.Received(1).CreateAsync(Arg.Is<Transaction>(t => |
||||
t.ProviderId == providerId && |
||||
t.Type == TransactionType.Credit && |
||||
t.Gateway == GatewayType.BitPay && |
||||
t.PaymentMethodType == PaymentMethodType.BitPay)); |
||||
await _providerRepository.Received(1).ReplaceAsync(provider); |
||||
await _mailService.Received(1).SendAddedCreditAsync("provider@example.com", 100.00m); |
||||
} |
||||
|
||||
[Fact] |
||||
public void GetIdsFromPosData_ValidOrganizationId_ReturnsCorrectId() |
||||
{ |
||||
var controller = CreateController(); |
||||
var organizationId = Guid.NewGuid(); |
||||
var invoice = CreateValidInvoice(posData: $"organizationId:{organizationId},{PosDataKeys.AccountCredit}"); |
||||
|
||||
var result = controller.GetIdsFromPosData(invoice); |
||||
|
||||
Assert.Equal(organizationId, result.OrganizationId); |
||||
Assert.Null(result.UserId); |
||||
Assert.Null(result.ProviderId); |
||||
} |
||||
|
||||
[Fact] |
||||
public void GetIdsFromPosData_ValidUserId_ReturnsCorrectId() |
||||
{ |
||||
var controller = CreateController(); |
||||
var userId = Guid.NewGuid(); |
||||
var invoice = CreateValidInvoice(posData: $"userId:{userId},{PosDataKeys.AccountCredit}"); |
||||
|
||||
var result = controller.GetIdsFromPosData(invoice); |
||||
|
||||
Assert.Null(result.OrganizationId); |
||||
Assert.Equal(userId, result.UserId); |
||||
Assert.Null(result.ProviderId); |
||||
} |
||||
|
||||
[Fact] |
||||
public void GetIdsFromPosData_ValidProviderId_ReturnsCorrectId() |
||||
{ |
||||
var controller = CreateController(); |
||||
var providerId = Guid.NewGuid(); |
||||
var invoice = CreateValidInvoice(posData: $"providerId:{providerId},{PosDataKeys.AccountCredit}"); |
||||
|
||||
var result = controller.GetIdsFromPosData(invoice); |
||||
|
||||
Assert.Null(result.OrganizationId); |
||||
Assert.Null(result.UserId); |
||||
Assert.Equal(providerId, result.ProviderId); |
||||
} |
||||
|
||||
[Fact] |
||||
public void GetIdsFromPosData_InvalidGuid_ReturnsNull() |
||||
{ |
||||
var controller = CreateController(); |
||||
var invoice = CreateValidInvoice(posData: "organizationId:invalid-guid,{PosDataKeys.AccountCredit}"); |
||||
|
||||
var result = controller.GetIdsFromPosData(invoice); |
||||
|
||||
Assert.Null(result.OrganizationId); |
||||
Assert.Null(result.UserId); |
||||
Assert.Null(result.ProviderId); |
||||
} |
||||
|
||||
[Fact] |
||||
public void GetIdsFromPosData_NullPosData_ReturnsNull() |
||||
{ |
||||
var controller = CreateController(); |
||||
var invoice = CreateValidInvoice(posData: null!); |
||||
|
||||
var result = controller.GetIdsFromPosData(invoice); |
||||
|
||||
Assert.Null(result.OrganizationId); |
||||
Assert.Null(result.UserId); |
||||
Assert.Null(result.ProviderId); |
||||
} |
||||
|
||||
[Fact] |
||||
public void GetIdsFromPosData_EmptyPosData_ReturnsNull() |
||||
{ |
||||
var controller = CreateController(); |
||||
var invoice = CreateValidInvoice(posData: ""); |
||||
|
||||
var result = controller.GetIdsFromPosData(invoice); |
||||
|
||||
Assert.Null(result.OrganizationId); |
||||
Assert.Null(result.UserId); |
||||
Assert.Null(result.ProviderId); |
||||
} |
||||
|
||||
private static BitPayEventModel CreateValidEventModel(string invoiceId = "test-invoice-id") |
||||
{ |
||||
return new BitPayEventModel |
||||
{ |
||||
Event = new BitPayEventModel.EventModel { Code = 1005, Name = "invoice_confirmed" }, |
||||
Data = new BitPayEventModel.InvoiceDataModel { Id = invoiceId } |
||||
}; |
||||
} |
||||
|
||||
private static Invoice CreateValidInvoice(string invoiceId = "test-invoice-id", string status = "complete", |
||||
string currency = "USD", decimal price = 100.00m, |
||||
string posData = "organizationId:550e8400-e29b-41d4-a716-446655440000,accountCredit:1") |
||||
{ |
||||
return new Invoice |
||||
{ |
||||
Id = invoiceId, |
||||
Status = status, |
||||
Currency = currency, |
||||
Price = (double)price, |
||||
PosData = posData, |
||||
CurrentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), |
||||
Transactions = |
||||
[ |
||||
new InvoiceTransaction |
||||
{ |
||||
Type = null, |
||||
Confirmations = "1", |
||||
ReceivedTime = DateTime.UtcNow.ToString("O") |
||||
} |
||||
] |
||||
}; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue