public virtual async Task <IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice, CancellationToken cancellationToken = default) { var lightningClient = await GetLightningClient(cryptoCode, true); var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode); if (lightningInvoice?.BOLT11 is null || !BOLT11PaymentRequest.TryParse(lightningInvoice.BOLT11, out _, network.NBitcoinNetwork)) { ModelState.AddModelError(nameof(lightningInvoice.BOLT11), "The BOLT11 invoice was invalid."); } if (!ModelState.IsValid) { return(this.CreateValidationError(ModelState)); } var param = lightningInvoice?.MaxFeeFlat != null || lightningInvoice?.MaxFeePercent != null ? new PayInvoiceParams { MaxFeePercent = lightningInvoice.MaxFeePercent, MaxFeeFlat = lightningInvoice.MaxFeeFlat } : null; var result = await lightningClient.Pay(lightningInvoice.BOLT11, param, cancellationToken); return(result.Result switch { PayResult.CouldNotFindRoute => this.CreateAPIError("could-not-find-route", "Impossible to find a route to the peer"), PayResult.Error => this.CreateAPIError("generic-error", result.ErrorDetail), PayResult.Ok => Ok(new LightningPaymentData { TotalAmount = result.Details?.TotalAmount, FeeAmount = result.Details?.FeeAmount }), _ => throw new NotSupportedException("Unsupported Payresult") });
public virtual async Task <IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice) { var lightningClient = await GetLightningClient(cryptoCode, true); var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode); if (lightningInvoice?.BOLT11 is null || !BOLT11PaymentRequest.TryParse(lightningInvoice.BOLT11, out _, network.NBitcoinNetwork)) { ModelState.AddModelError(nameof(lightningInvoice.BOLT11), "The BOLT11 invoice was invalid."); } if (!ModelState.IsValid) { return(this.CreateValidationError(ModelState)); } var result = await lightningClient.Pay(lightningInvoice.BOLT11); switch (result.Result) { case PayResult.CouldNotFindRoute: return(this.CreateAPIError("could-not-find-route", "Impossible to find a route to the peer")); case PayResult.Error: return(this.CreateAPIError("generic-error", result.ErrorDetail)); case PayResult.Ok: return(Ok()); default: throw new NotSupportedException("Unsupported Payresult"); } }
public BoltInvoiceClaimDestination(string bolt11, BOLT11PaymentRequest paymentRequest) { Bolt11 = bolt11 ?? throw new ArgumentNullException(nameof(bolt11)); PaymentRequest = paymentRequest; PaymentHash = paymentRequest.Hash; Amount = paymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC); }
public async Task <(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, bool validate) { destination = destination.Trim(); var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode); try { string lnurlTag = null; var lnurl = EmailValidator.IsEmail(destination) ? LNURL.LNURL.ExtractUriFromInternetIdentifier(destination) : LNURL.LNURL.Parse(destination, out lnurlTag); if (lnurlTag is null) { var info = (LNURLPayRequest)(await LNURL.LNURL.FetchInformation(lnurl, CreateClient(lnurl))); lnurlTag = info.Tag; } if (lnurlTag.Equals("payRequest", StringComparison.InvariantCultureIgnoreCase)) { return(new LNURLPayClaimDestinaton(destination), null); } } catch (FormatException) { } catch { return(null, "The LNURL / Lightning Address provided was not online."); } var result = BOLT11PaymentRequest.TryParse(destination, out var invoice, network.NBitcoinNetwork) ? new BoltInvoiceClaimDestination(destination, invoice) : null; if (result == null) { return(null, "A valid BOLT11 invoice (with 30+ day expiry) or LNURL Pay or Lightning address was not provided."); } if (validate && (invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days < 30) { return(null, $"The BOLT11 invoice must have an expiry date of at least 30 days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days})."); } if (invoice.ExpiryDate.UtcDateTime < DateTime.UtcNow) { return(null, "The BOLT11 invoice submitted has expired."); } return(result, null); }
//we group per store and init the transfers by each async Task <bool> TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest) { var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC); if (boltAmount != payoutBlob.CryptoAmount) { return(false); } var result = await lightningClient.Pay(bolt11PaymentRequest.ToString()); return(result.Result == PayResult.Ok); }
public async Task <IActionResult> PayInvoice(string invoice) { try { BOLT11PaymentRequest.TryParse(invoice, out var bolt11PaymentRequest, GetNetwork()); } catch (Exception e) { ModelState.AddModelError(nameof(invoice), "The BOLT11 invoice was invalid."); } if (CheckValidation(out var errorActionResult)) { return(errorActionResult); } return(Json(await GetLightningClient().Pay(invoice))); }
public async Task <LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken)) { InvoiceResponse result = null; try { result = await _eclairClient.GetInvoice(invoiceId, cancellation); } catch (EclairClient.EclairApiException ex) when(ex.Error.Error == "Not found" || ex.Error.Error.Contains("Invalid hexadecimal", StringComparison.OrdinalIgnoreCase)) { return(null); } GetReceivedInfoResponse info = null; try { info = await _eclairClient.GetReceivedInfo(invoiceId, null, cancellation); } catch (EclairClient.EclairApiException) { } var parsed = BOLT11PaymentRequest.Parse(result.Serialized, _network); var lnInvoice = new LightningInvoice() { Id = result.PaymentHash, Amount = parsed.MinimumAmount, ExpiresAt = parsed.ExpiryDate, BOLT11 = result.Serialized }; if (DateTimeOffset.UtcNow >= parsed.ExpiryDate) { lnInvoice.Status = LightningInvoiceStatus.Expired; } if (info != null && info.Status.Type == "received") { lnInvoice.AmountReceived = info.Status.Amount; lnInvoice.Status = info.Status.Amount >= parsed.MinimumAmount ? LightningInvoiceStatus.Paid : LightningInvoiceStatus.Unpaid; lnInvoice.PaidAt = info.Status.ReceivedAt; } return(lnInvoice); }
public async Task CanCreateInvoiceWithDescriptionHash() { var hashToUse = new uint256(new SHA256Managed().ComputeHash(Encoding.UTF8.GetBytes("CanCreateInvoiceWithDescriptionHash"))); async Task <LightningInvoice> CreateWithHash(ILightningClient lightningClient) { return(await lightningClient.CreateInvoice(new CreateInvoiceParams(10000, hashToUse, TimeSpan.FromMinutes(5)))); } await WaitServersAreUp(); foreach (var client in Tester.GetLightningClients()) { switch (client.Client) { case CLightningClient _: case LndClient _: Logs.Tester.LogInformation($"{client.Name}: {nameof(CanCreateInvoiceWithDescriptionHash)}"); var createdInvoice = await CreateWithHash(client.Client); var retrievedInvoice = await client.Client.GetInvoice(createdInvoice.Id); Logs.Tester.LogInformation(JObject.FromObject(createdInvoice).ToString()); Logs.Tester.LogInformation(JObject.FromObject(retrievedInvoice).ToString()); AssertUnpaid(createdInvoice); AssertUnpaid(retrievedInvoice); var createdInvoiceBOLT = BOLT11PaymentRequest.Parse(createdInvoice.BOLT11, Network.RegTest); var retrievedInvoiceeBOLT = BOLT11PaymentRequest.Parse(retrievedInvoice.BOLT11, Network.RegTest); Assert.Equal(createdInvoiceBOLT.PaymentHash, retrievedInvoiceeBOLT.PaymentHash); Assert.Equal(hashToUse, createdInvoiceBOLT.DescriptionHash); break; default: await Assert.ThrowsAsync <NotSupportedException>(async() => { await CreateWithHash(client.Client); }); break; } } }
public virtual async Task <IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice) { var lightningClient = await GetLightningClient(cryptoCode, true); var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode); if (lightningClient == null || network == null) { return(NotFound()); } try { BOLT11PaymentRequest.TryParse(lightningInvoice.Invoice, out var bolt11PaymentRequest, network.NBitcoinNetwork); } catch (Exception) { ModelState.AddModelError(nameof(lightningInvoice), "The BOLT11 invoice was invalid."); } if (CheckValidation(out var errorActionResult)) { return(errorActionResult); } var result = await lightningClient.Pay(lightningInvoice.Invoice); switch (result.Result) { case PayResult.Ok: return(Ok()); case PayResult.CouldNotFindRoute: ModelState.AddModelError(nameof(lightningInvoice.Invoice), "Could not find route"); break; case PayResult.Error: ModelState.AddModelError(nameof(lightningInvoice.Invoice), result.ErrorDetail); break; } return(BadRequest(new ValidationProblemDetails(ModelState))); }
public async Task <LightningInvoice> CreateInvoice(LightMoney amount, string description, TimeSpan expiry, CancellationToken cancellation = default(CancellationToken)) { var result = await _eclairClient.CreateInvoice( description, amount.MilliSatoshi, Convert.ToInt32(expiry.TotalSeconds), null, cancellation); var parsed = BOLT11PaymentRequest.Parse(result.Serialized, _network); var invoice = new LightningInvoice() { BOLT11 = result.Serialized, Amount = amount, Id = result.PaymentHash, Status = LightningInvoiceStatus.Unpaid, ExpiresAt = parsed.ExpiryDate }; return(invoice); }
internal LightningInvoice GetLightningInvoiceObject(ListInvoiceResultResponse invoice, Network network) { var parsed = BOLT11PaymentRequest.Parse(invoice.Bolt11, network); var lightningInvoice = new LightningInvoice() { Id = invoice.Hash, Amount = invoice.AmountMsat, AmountReceived = invoice.AmountMsat, BOLT11 = invoice.Bolt11, Status = ToStatus(invoice.State), PaidAt = null, ExpiresAt = parsed.ExpiryDate }; if (invoice.State == "used") { lightningInvoice.PaidAt = parsed.ExpiryDate; } return(lightningInvoice); }
public async Task <LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken)) { var result = await _eclairClient.GetInvoice(invoiceId, cancellation); GetReceivedInfoResponse info; try { info = await _eclairClient.GetReceivedInfo(invoiceId, null, cancellation); } catch (EclairClient.EclairApiException) { info = new GetReceivedInfoResponse() { AmountMsat = 0, ReceivedAt = 0, PaymentHash = invoiceId }; } var parsed = BOLT11PaymentRequest.Parse(result.Serialized, _network); return(new LightningInvoice() { Id = result.PaymentHash, Amount = parsed.MinimumAmount, ExpiresAt = parsed.ExpiryDate, BOLT11 = result.Serialized, AmountReceived = info.AmountMsat, Status = info.AmountMsat >= parsed.MinimumAmount ? LightningInvoiceStatus.Paid : DateTime.Now >= parsed.ExpiryDate ? LightningInvoiceStatus.Expired : LightningInvoiceStatus.Unpaid, PaidAt = info.ReceivedAt == 0 ? (DateTimeOffset?)null : DateTimeOffset.FromUnixTimeMilliseconds(info.ReceivedAt) }); }
public async Task <LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken)) { var result = await _eclairClient.GetInvoice(invoiceId, cancellation); GetReceivedInfoResponse info = null; try { info = await _eclairClient.GetReceivedInfo(invoiceId, null, cancellation); } catch (EclairClient.EclairApiException) { } var parsed = BOLT11PaymentRequest.Parse(result.Serialized, _network); var lnInvoice = new LightningInvoice() { Id = result.PaymentHash, Amount = parsed.MinimumAmount, ExpiresAt = parsed.ExpiryDate, BOLT11 = result.Serialized }; if (DateTimeOffset.UtcNow >= parsed.ExpiryDate) { lnInvoice.Status = LightningInvoiceStatus.Expired; } if (info != null && info.Status.Type == "received") { lnInvoice.AmountReceived = info.Status.Amount; lnInvoice.Status = info.Status.Amount >= parsed.MinimumAmount ? LightningInvoiceStatus.Paid : LightningInvoiceStatus.Unpaid; lnInvoice.PaidAt = info.Status.ReceivedAt; } return(lnInvoice); }
public BOLT11PaymentRequest ParsePaymentRequest(string payReq) { return(BOLT11PaymentRequest.Parse(payReq, _network)); }
public static async Task <ResultVM> TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest, PaymentMethodId pmi) { var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC); if (boltAmount != payoutBlob.CryptoAmount) { return(new ResultVM { PayoutId = payoutData.Id, Result = PayResult.Error, Message = $"The BOLT11 invoice amount ({boltAmount} {pmi.CryptoCode}) did not match the payout's amount ({payoutBlob.CryptoAmount.GetValueOrDefault()} {pmi.CryptoCode})", Destination = payoutBlob.Destination }); } var result = await lightningClient.Pay(bolt11PaymentRequest.ToString(), new PayInvoiceParams()); if (result.Result == PayResult.Ok) { var message = result.Details?.TotalAmount != null ? $"Paid out {result.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC)}" : null; payoutData.State = PayoutState.Completed; return(new ResultVM { PayoutId = payoutData.Id, Result = result.Result, Destination = payoutBlob.Destination, Message = message }); } return(new ResultVM { PayoutId = payoutData.Id, Result = result.Result, Destination = payoutBlob.Destination, Message = result.ErrorDetail }); }
public async Task <IActionResult> GetLNURLForPullPayment(string cryptoCode, string pullPaymentId, string pr) { var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode); if (network is null || !network.SupportLightning) { return(NotFound()); } var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike); var pp = await _pullPaymentHostedService.GetPullPayment(pullPaymentId, true); if (!pp.IsRunning() || !pp.IsSupported(pmi)) { return(NotFound()); } var blob = pp.GetBlob(); if (!blob.Currency.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase)) { return(NotFound()); } var progress = _pullPaymentHostedService.CalculatePullPaymentProgress(pp, DateTimeOffset.UtcNow); var remaining = progress.Limit - progress.Completed - progress.Awaiting; var request = new LNURLWithdrawRequest() { MaxWithdrawable = LightMoney.FromUnit(remaining, LightMoneyUnit.BTC), K1 = pullPaymentId, BalanceCheck = new Uri(Request.GetCurrentUrl()), CurrentBalance = LightMoney.FromUnit(remaining, LightMoneyUnit.BTC), MinWithdrawable = LightMoney.FromUnit( Math.Min(await _lightningLikePayoutHandler.GetMinimumPayoutAmount(pmi, null), remaining), LightMoneyUnit.BTC), Tag = "withdrawRequest", Callback = new Uri(Request.GetCurrentUrl()), }; if (pr is null) { return(Ok(request)); } if (!BOLT11PaymentRequest.TryParse(pr, out var result, network.NBitcoinNetwork) || result is null) { return(BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Pr was not a valid BOLT11" })); } if (result.MinimumAmount < request.MinWithdrawable || result.MinimumAmount > request.MaxWithdrawable) { return(BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Pr was not within bounds" })); } var store = await _storeRepository.FindStore(pp.StoreId); var pm = store !.GetSupportedPaymentMethods(_btcPayNetworkProvider) .OfType <LightningSupportedPaymentMethod>() .FirstOrDefault(method => method.PaymentId == pmi); if (pm is null) { return(NotFound()); } var claimResponse = await _pullPaymentHostedService.Claim(new ClaimRequest() { Destination = new BoltInvoiceClaimDestination(pr, result), PaymentMethodId = pmi, PullPaymentId = pullPaymentId, StoreId = pp.StoreId, Value = result.MinimumAmount.ToDecimal(LightMoneyUnit.BTC) }); if (claimResponse.Result != ClaimRequest.ClaimResult.Ok) { return(BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Pr could not be paid" })); } switch (claimResponse.PayoutData.State) { case PayoutState.AwaitingPayment: { var client = _lightningLikePaymentHandler.CreateLightningClient(pm, network); PayResponse payResult; try { payResult = await client.Pay(pr); } catch (Exception e) { payResult = new PayResponse(PayResult.Error, e.Message); } switch (payResult.Result) { case PayResult.Ok: await _pullPaymentHostedService.MarkPaid(new PayoutPaidRequest() { PayoutId = claimResponse.PayoutData.Id, Proof = new ManualPayoutProof { } }); return(Ok(new LNUrlStatusResponse { Status = "OK" })); default: await _pullPaymentHostedService.Cancel( new PullPaymentHostedService.CancelRequest(new string[] { claimResponse.PayoutData.Id })); return(Ok(new LNUrlStatusResponse { Status = "ERROR", Reason = $"Pr could not be paid because {payResult.ErrorDetail}" })); } } case PayoutState.AwaitingApproval: return(Ok(new LNUrlStatusResponse { Status = "OK", Reason = "The payment request has been recorded, but still needs to be approved before execution." })); case PayoutState.InProgress: case PayoutState.Completed: return(Ok(new LNUrlStatusResponse { Status = "OK" })); case PayoutState.Cancelled: return(BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Pr could not be paid" })); } return(Ok(request)); }
public uint256 GetPaymentHash(Network network) { return(PaymentHash ?? BOLT11PaymentRequest.Parse(BOLT11, network).PaymentHash); }
public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails( InvoiceLogs logs, LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store, BTCPayNetwork network, object preparePaymentObject) { if (paymentMethod.ParentEntity.Type == InvoiceType.TopUp) { throw new PaymentMethodUnavailableException("Lightning Network payment method is not available for top-up invoices"); } if (preparePaymentObject is null) { return(new LightningLikePaymentMethodDetails() { Activated = false }); } //direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers var storeBlob = store.GetStoreBlob(); var test = GetNodeInfo(supportedPaymentMethod, network, paymentMethod.PreferOnion); var invoice = paymentMethod.ParentEntity; decimal due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility); try { due = paymentMethod.Calculate().Due.ToDecimal(MoneyUnit.BTC); } catch (Exception) { // ignored } var client = supportedPaymentMethod.CreateLightningClient(network, Options.Value, _lightningClientFactory); var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow; if (expiry < TimeSpan.Zero) { expiry = TimeSpan.FromSeconds(1); } LightningInvoice?lightningInvoice = null; string description = storeBlob.LightningDescriptionTemplate; description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{ItemDescription}", invoice.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{OrderId}", invoice.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase); using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT)) { try { var request = new CreateInvoiceParams(new LightMoney(due, LightMoneyUnit.BTC), description, expiry); request.PrivateRouteHints = storeBlob.LightningPrivateRouteHints; lightningInvoice = await client.CreateInvoice(request, cts.Token); } catch (OperationCanceledException) when(cts.IsCancellationRequested) { throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner"); } catch (Exception ex) { throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex); } } var nodeInfo = await test; return(new LightningLikePaymentMethodDetails { Activated = true, BOLT11 = lightningInvoice.BOLT11, PaymentHash = BOLT11PaymentRequest.Parse(lightningInvoice.BOLT11, network.NBitcoinNetwork).PaymentHash, InvoiceId = lightningInvoice.Id, NodeInfo = nodeInfo.First().ToString() }); }
public BoltInvoiceClaimDestination(string bolt11, BOLT11PaymentRequest invoice) { _bolt11 = bolt11 ?? throw new ArgumentNullException(nameof(bolt11)); _amount = invoice?.MinimumAmount.ToDecimal(LightMoneyUnit.BTC) ?? throw new ArgumentNullException(nameof(invoice)); }
public BoltInvoiceClaimDestination(string bolt11, Network network) { _bolt11 = bolt11 ?? throw new ArgumentNullException(nameof(bolt11)); _amount = BOLT11PaymentRequest.Parse(bolt11, network).MinimumAmount.ToDecimal(LightMoneyUnit.BTC); }
//we group per store and init the transfers by each async Task <bool> TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest) { return((await UILightningLikePayoutController.TrypayBolt(lightningClient, payoutBlob, payoutData, bolt11PaymentRequest, payoutData.GetPaymentMethodId())).Result == PayResult.Ok); }
public async Task <Transaction> Send(Wallet wallet, BOLT11PaymentRequest bolt11, string paymentRequest) { await using var dbContext = _dbContextFactory.CreateDbContext(); var amount = bolt11.MinimumAmount; if (bolt11.ExpiryDate <= DateTimeOffset.UtcNow) { throw new Exception($"Payment request already expired at {bolt11.ExpiryDate}."); } if (wallet.Balance < amount) { var balanceSats = wallet.Balance.ToUnit(LightMoneyUnit.Satoshi); var amountSats = amount.ToUnit(LightMoneyUnit.Satoshi); throw new Exception($"Insufficient balance: {balanceSats} sats, tried to send {amountSats} sats."); } // pay via the node and fall back to internal payment Transaction internalReceivingTransaction = null; try { await _btcpayService.PayLightningInvoice(new LightningInvoicePayRequest { PaymentRequest = paymentRequest }); } catch (GreenFieldAPIException ex) when(ex.APIError.Code == "could-not-find-route") { internalReceivingTransaction = await GetTransaction(new TransactionQuery { PaymentRequest = paymentRequest, HasInvoiceId = true }); if (internalReceivingTransaction == null) { throw; } } if (internalReceivingTransaction != null) { if (internalReceivingTransaction.IsExpired) { throw new Exception($"Payment request already expired at {internalReceivingTransaction.ExpiresAt}."); } if (internalReceivingTransaction.IsPaid) { throw new Exception($"Payment request has already been paid."); } } // https://docs.microsoft.com/en-us/ef/core/saving/transactions#controlling-transactions await using var dbTransaction = await dbContext.Database.BeginTransactionAsync(); var now = DateTimeOffset.UtcNow; var entry = await dbContext.Transactions.AddAsync(new Transaction { WalletId = wallet.WalletId, PaymentRequest = paymentRequest, Amount = amount, AmountSettled = new LightMoney(amount.MilliSatoshi * -1), ExpiresAt = bolt11.ExpiryDate, Description = bolt11.ShortDescription, PaidAt = now }); await dbContext.SaveChangesAsync(); if (internalReceivingTransaction != null) { await MarkTransactionPaid(internalReceivingTransaction, amount, now); } await dbTransaction.CommitAsync(); return(entry.Entity); }