Ejemplo n.º 1
0
        public async Task <InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, BTCPayNetworkProvider networkProvider)
        {
            List <string> textSearch = new List <string>();

            invoice          = Clone(invoice);
            invoice.Id       = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
            invoice.Payments = new List <PaymentEntity>();
            invoice.StoreId  = storeId;
            using (var context = _ContextFactory.CreateContext())
            {
                context.Invoices.Add(new InvoiceData()
                {
                    StoreDataId   = storeId,
                    Id            = invoice.Id,
                    Created       = invoice.InvoiceTime,
                    Blob          = ToBytes(invoice),
                    OrderId       = invoice.OrderId,
                    Status        = invoice.Status,
                    ItemCode      = invoice.ProductInformation.ItemCode,
                    CustomerEmail = invoice.RefundMail
                });

                foreach (var cryptoData in invoice.GetCryptoData().Values)
                {
                    var network = networkProvider.GetNetwork(cryptoData.CryptoCode);
                    if (network == null)
                    {
                        throw new InvalidOperationException("CryptoCode unsupported");
                    }
                    context.AddressInvoices.Add(new AddressInvoiceData()
                    {
                        Address       = BitcoinAddress.Create(cryptoData.DepositAddress, network.NBitcoinNetwork).ScriptPubKey.Hash.ToString(),
                        InvoiceDataId = invoice.Id,
                        CreatedTime   = DateTimeOffset.UtcNow,
                    });
                    context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
                    {
                        InvoiceDataId = invoice.Id,
                        Address       = cryptoData.DepositAddress,
#pragma warning disable CS0618
                        CryptoCode = cryptoData.CryptoCode,
#pragma warning restore CS0618
                        Assigned = DateTimeOffset.UtcNow
                    });
                    textSearch.Add(cryptoData.DepositAddress);
                    textSearch.Add(cryptoData.Calculate().TotalDue.ToString());
                }
                context.PendingInvoices.Add(new PendingInvoiceData()
                {
                    Id = invoice.Id
                });
                await context.SaveChangesAsync().ConfigureAwait(false);
            }

            textSearch.Add(invoice.Id);
            textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
            textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
            textSearch.Add(invoice.OrderId);
            textSearch.Add(ToString(invoice.BuyerInformation));
            textSearch.Add(ToString(invoice.ProductInformation));
            textSearch.Add(invoice.StoreId);

            AddToTextSearch(invoice.Id, textSearch.ToArray());

            return(invoice);
        }
Ejemplo n.º 2
0
        public async Task <InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, InvoiceLogs creationLogs, BTCPayNetworkProvider networkProvider)
        {
            List <string> textSearch = new List <string>();

            invoice    = Clone(invoice, null);
            invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
#pragma warning disable CS0618
            invoice.Payments = new List <PaymentEntity>();
#pragma warning restore CS0618
            invoice.StoreId = storeId;
            using (var context = _ContextFactory.CreateContext())
            {
                context.Invoices.Add(new Data.InvoiceData()
                {
                    StoreDataId = storeId,
                    Id          = invoice.Id,
                    Created     = invoice.InvoiceTime,
                    Blob        = ToBytes(invoice, null),
                    OrderId     = invoice.OrderId,
#pragma warning disable CS0618 // Type or member is obsolete
                    Status = invoice.StatusString,
#pragma warning restore CS0618 // Type or member is obsolete
                    ItemCode      = invoice.ProductInformation.ItemCode,
                    CustomerEmail = invoice.RefundMail
                });

                foreach (var paymentMethod in invoice.GetPaymentMethods(networkProvider))
                {
                    if (paymentMethod.Network == null)
                    {
                        throw new InvalidOperationException("CryptoCode unsupported");
                    }
                    var paymentDestination = paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();

                    string address = GetDestination(paymentMethod, paymentMethod.Network.NBitcoinNetwork);
                    context.AddressInvoices.Add(new AddressInvoiceData()
                    {
                        InvoiceDataId = invoice.Id,
                        CreatedTime   = DateTimeOffset.UtcNow,
                    }.Set(address, paymentMethod.GetId()));

                    context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
                    {
                        InvoiceDataId = invoice.Id,
                        Assigned      = DateTimeOffset.UtcNow
                    }.SetAddress(paymentDestination, paymentMethod.GetId().ToString()));
                    textSearch.Add(paymentDestination);
                    textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
                }
                context.PendingInvoices.Add(new PendingInvoiceData()
                {
                    Id = invoice.Id
                });

                foreach (var log in creationLogs.ToList())
                {
                    context.InvoiceEvents.Add(new InvoiceEventData()
                    {
                        InvoiceDataId = invoice.Id,
                        Message       = log.Log,
                        Timestamp     = log.Timestamp,
                        UniqueId      = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
                    });
                }
                await context.SaveChangesAsync().ConfigureAwait(false);
            }

            textSearch.Add(invoice.Id);
            textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
            textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
            textSearch.Add(invoice.OrderId);
            textSearch.Add(ToString(invoice.BuyerInformation, null));
            textSearch.Add(ToString(invoice.ProductInformation, null));
            textSearch.Add(invoice.StoreId);

            AddToTextSearch(invoice.Id, textSearch.ToArray());

            return(invoice);
        }
Ejemplo n.º 3
0
        public async Task <InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, string[] additionalSearchTerms = null)
        {
            var textSearch = new HashSet <string>();

            invoice          = Clone(invoice);
            invoice.Networks = _btcPayNetworkProvider;
            invoice.Id       = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
#pragma warning disable CS0618
            invoice.Payments = new List <PaymentEntity>();
#pragma warning restore CS0618
            invoice.StoreId = storeId;
            using (var context = _applicationDbContextFactory.CreateContext())
            {
                var invoiceData = new Data.InvoiceData()
                {
                    StoreDataId = storeId,
                    Id          = invoice.Id,
                    Created     = invoice.InvoiceTime,
                    Blob        = ToBytes(invoice, null),
                    OrderId     = invoice.Metadata.OrderId,
#pragma warning disable CS0618 // Type or member is obsolete
                    Status = invoice.StatusString,
#pragma warning restore CS0618 // Type or member is obsolete
                    ItemCode      = invoice.Metadata.ItemCode,
                    CustomerEmail = invoice.RefundMail,
                    Archived      = false
                };
                await context.Invoices.AddAsync(invoiceData);


                foreach (var paymentMethod in invoice.GetPaymentMethods())
                {
                    if (paymentMethod.Network == null)
                    {
                        throw new InvalidOperationException("CryptoCode unsupported");
                    }
                    var details = paymentMethod.GetPaymentMethodDetails();
                    if (!details.Activated)
                    {
                        continue;
                    }
                    var    paymentDestination = details.GetPaymentDestination();
                    string address            = GetDestination(paymentMethod);
                    await context.AddressInvoices.AddAsync(new AddressInvoiceData()
                    {
                        InvoiceDataId = invoice.Id,
                        CreatedTime   = DateTimeOffset.UtcNow,
                    }.Set(address, paymentMethod.GetId()));

                    await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
                    {
                        InvoiceDataId = invoice.Id,
                        Assigned      = DateTimeOffset.UtcNow
                    }.SetAddress(paymentDestination, paymentMethod.GetId().ToString()));

                    textSearch.Add(paymentDestination);
                    textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
                }
                await context.PendingInvoices.AddAsync(new PendingInvoiceData()
                {
                    Id = invoice.Id
                });

                textSearch.Add(invoice.Id);
                textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
                if (!invoice.IsUnsetTopUp())
                {
                    textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
                }
                textSearch.Add(invoice.Metadata.OrderId);
                textSearch.Add(invoice.StoreId);
                textSearch.Add(invoice.Metadata.BuyerEmail);

                if (additionalSearchTerms != null)
                {
                    textSearch.AddRange(additionalSearchTerms);
                }
                AddToTextSearch(context, invoiceData, textSearch.ToArray());

                await context.SaveChangesAsync().ConfigureAwait(false);
            }


            return(invoice);
        }
 private static string GetHttpJobId(InvoiceEntity invoice)
 {
     return($"{invoice.Id}-{invoice.Status}-HTTP");
 }
Ejemplo n.º 5
0
        private async Task <IEnumerable <(PaymentEntity Payment, TransactionResult Transaction)> > GetPaymentsWithTransaction(InvoiceEntity invoice)
        {
            var getPayments = invoice.Payments
                              .Select(async o => (Payment: o, Transaction: await _ExplorerClient.GetTransactionAsync(o.Outpoint.Hash, _Cts.Token)))
                              .ToArray();
            await Task.WhenAll(getPayments).ConfigureAwait(false);

            var transactions = getPayments.Select(c => (Payment: c.Result.Payment, Transaction: c.Result.Transaction));

            return(transactions);
        }
Ejemplo n.º 6
0
        private async Task <(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice)
        {
            bool needSave = false;
            //Fetch unknown payments
            var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);

            changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false);

            var utxos      = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
            var invoiceIds = utxos.Select(u => _InvoiceRepository.GetInvoiceIdFromScriptPubKey(u.Output.ScriptPubKey)).ToArray();

            utxos =
                utxos
                .Where((u, i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id)
                .ToArray();

            List <Coin> receivedCoins = new List <Coin>();

            foreach (var received in utxos)
            {
                if (received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey)
                {
                    receivedCoins.Add(new Coin(received.Outpoint, received.Output));
                }
            }

            var            alreadyAccounted = new HashSet <OutPoint>(invoice.Payments.Select(p => p.Outpoint));
            BitcoinAddress generatedAddress = null;
            bool           dirtyAddress     = false;

            foreach (var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint)))
            {
                var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false);

                invoice.Payments.Add(payment);
                if (coin.ScriptPubKey == invoice.DepositAddress.ScriptPubKey && generatedAddress == null)
                {
                    dirtyAddress = true;
                }
            }
            //////

            if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
            {
                needSave = true;
                await _InvoiceRepository.UnaffectAddress(invoice.Id);

                invoice.Status = "expired";
            }

            if (invoice.Status == "new" || invoice.Status == "expired")
            {
                var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum();
                if (totalPaid >= invoice.GetTotalCryptoDue())
                {
                    if (invoice.Status == "new")
                    {
                        invoice.Status = "paid";
                        if (invoice.FullNotifications)
                        {
                            _NotificationManager.Notify(invoice);
                        }
                        invoice.ExceptionStatus = null;
                        await _InvoiceRepository.UnaffectAddress(invoice.Id);

                        needSave = true;
                    }
                    else if (invoice.Status == "expired")
                    {
                        invoice.ExceptionStatus = "paidLate";
                        needSave = true;
                    }
                }

                if (totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver")
                {
                    invoice.ExceptionStatus = "paidOver";
                    await _InvoiceRepository.UnaffectAddress(invoice.Id);

                    needSave = true;
                }

                if (totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
                {
                    Logs.PayServer.LogInformation("Paid to " + invoice.DepositAddress);
                    invoice.ExceptionStatus = "paidPartial";
                    needSave = true;
                    if (dirtyAddress)
                    {
                        var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy));

                        Logs.PayServer.LogInformation("Generate new " + address);
                        await _InvoiceRepository.NewAddress(invoice.Id, address);
                    }
                }
            }

            if (invoice.Status == "paid")
            {
                if (!invoice.MonitoringExpiration.HasValue || invoice.MonitoringExpiration > DateTimeOffset.UtcNow)
                {
                    var transactions = await GetPaymentsWithTransaction(invoice);

                    if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
                    {
                        transactions = transactions.Where(t => !t.Transaction.Transaction.RBF);
                    }
                    else if (invoice.SpeedPolicy == SpeedPolicy.MediumSpeed)
                    {
                        transactions = transactions.Where(t => t.Transaction.Confirmations >= 1);
                    }
                    else if (invoice.SpeedPolicy == SpeedPolicy.LowSpeed)
                    {
                        transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
                    }

                    var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
                    if (totalConfirmed >= invoice.GetTotalCryptoDue())
                    {
                        await _InvoiceRepository.UnaffectAddress(invoice.Id);

                        invoice.Status = "confirmed";
                        _NotificationManager.Notify(invoice);
                        needSave = true;
                    }
                }
                else
                {
                    await _InvoiceRepository.UnaffectAddress(invoice.Id);

                    invoice.Status = "invalid";
                    needSave       = true;
                }
            }

            if (invoice.Status == "confirmed")
            {
                var transactions = await GetPaymentsWithTransaction(invoice);

                transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
                var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
                if (totalConfirmed >= invoice.GetTotalCryptoDue())
                {
                    invoice.Status = "complete";
                    if (invoice.FullNotifications)
                    {
                        _NotificationManager.Notify(invoice);
                    }
                    needSave = true;
                }
            }

            return(needSave, changes);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Add a payment to an invoice
        /// </summary>
        /// <param name="invoiceId"></param>
        /// <param name="date"></param>
        /// <param name="paymentData"></param>
        /// <param name="cryptoCode"></param>
        /// <param name="accounted"></param>
        /// <returns>The PaymentEntity or null if already added</returns>
        public async Task <PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetworkBase network, bool accounted = false)
        {
            await using var context = _applicationDbContextFactory.CreateContext();
            var invoice = await context.Invoices.FindAsync(invoiceId);

            if (invoice == null)
            {
                return(null);
            }
            InvoiceEntity         invoiceEntity        = invoice.GetBlob(_btcPayNetworkProvider);
            PaymentMethod         paymentMethod        = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()));
            IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
            PaymentEntity         entity = new PaymentEntity
            {
                Version = 1,
#pragma warning disable CS0618
                CryptoCode = network.CryptoCode,
#pragma warning restore CS0618
                ReceivedTime = date.UtcDateTime,
                Accounted    = accounted,
                NetworkFee   = paymentMethodDetails.GetNextNetworkFee(),
                Network      = network
            };

            entity.SetCryptoPaymentData(paymentData);
            //TODO: abstract
            if (paymentMethodDetails is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod &&
                bitcoinPaymentMethod.NetworkFeeMode == NetworkFeeMode.MultiplePaymentsOnly &&
                bitcoinPaymentMethod.NextNetworkFee == Money.Zero)
            {
                bitcoinPaymentMethod.NextNetworkFee = bitcoinPaymentMethod.NetworkFeeRate.GetFee(100); // assume price for 100 bytes
                paymentMethod.SetPaymentMethodDetails(bitcoinPaymentMethod);
                invoiceEntity.SetPaymentMethod(paymentMethod);
                invoice.Blob = InvoiceRepository.ToBytes(invoiceEntity, network);
            }
            PaymentData data = new PaymentData
            {
                Id            = paymentData.GetPaymentId(),
                Blob          = InvoiceRepository.ToBytes(entity, entity.Network),
                InvoiceDataId = invoiceId,
                Accounted     = accounted
            };

            await context.Payments.AddAsync(data);

            InvoiceRepository.AddToTextSearch(context, invoice, paymentData.GetSearchTerms());
            var alreadyExists = false;

            try
            {
                await context.SaveChangesAsync().ConfigureAwait(false);
            }
            catch (DbUpdateException) { alreadyExists = true; }

            if (alreadyExists)
            {
                return(null);
            }

            if (paymentData.PaymentConfirmed(entity, invoiceEntity.SpeedPolicy))
            {
                _eventAggregator.Publish(new InvoiceEvent(invoiceEntity, InvoiceEvent.PaymentSettled)
                {
                    Payment = entity
                });
            }
            return(entity);
        }