Compare commits

...

10 Commits

Author SHA1 Message Date
0b95da1760 Update changelog 2021-11-15 18:26:51 +09:00
7aa92e7012 Add crypto code for invoice and pull payment payout API response (#3099)
* Add "cryptoCode" for invoice payment method API endpoint response

* Add "cryptoCode" for pull payment payout API endpoint response

* Add "#nullable enable" to GreenFieldInvoiceController

* Add "#nullable enable" to GreenfieldPullPaymentController
2021-11-15 17:43:37 +09:00
3bb9e38773 Checkout page of 0 amount invoices shouldn't crash, but 404 2021-11-15 17:42:28 +09:00
6597fe7dff Prevent creation of on-chain invoices below the dust limit (#3082)
* Prevent creation of on-chain invoices below the dust limit

Fixes #3071.

* Apply suggestions from code review

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>

* small fix

* Fix selenium test

0.000000012 BTC (whether rounded or not) is below the dust threshold, causing this test to fail.

* fix CanCreateTopupInvoices test

Don't apply dust threshold conditional for topup invoices.

* Fix test, and minor changes

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2021-11-15 17:42:22 +09:00
f5677747a4 Fix type of property cryptoCode (#3088) 2021-11-15 17:42:13 +09:00
74967dc849 Fix bug with fraction amount display in crowdfund app (#3098)
* Update formatting

* Adjust formatting

* Adjust formatting

* Fix bug with BTC fraction amount display
2021-11-15 17:42:08 +09:00
4aef93985d Fix typo: "Prioriy" -> "Priority" (#3103) 2021-11-15 17:41:49 +09:00
e5dc4fa854 Update Swagger docs for webhook event types (#3104)
close #2999
2021-11-15 17:41:43 +09:00
1830d5db64 Fix label display issue 2021-11-15 17:40:08 +09:00
6f2d29f4e6 Fix payout/pull payment crash when no payment method bug
Fixes #3084.
2021-11-15 17:39:33 +09:00
24 changed files with 181 additions and 41 deletions

View File

@ -27,7 +27,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="6.0.15" />
<PackageReference Include="NBitcoin" Version="6.0.17" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

View File

@ -33,6 +33,8 @@ namespace BTCPayServer.Client.Models
public List<Payment> Payments { get; set; }
public string PaymentMethod { get; set; }
public string CryptoCode { get; set; }
public class Payment
{
public string Id { get; set; }

View File

@ -21,6 +21,7 @@ namespace BTCPayServer.Client.Models
public string PullPaymentId { get; set; }
public string Destination { get; set; }
public string PaymentMethod { get; set; }
public string CryptoCode { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(NumericStringJsonConverter))]

View File

@ -161,7 +161,7 @@ namespace BTCPayServer.Rating
public ExpressionSyntax FindBestCandidate(CurrencyPair p)
{
var invP = p.Inverse();
var candidates = new List<(CurrencyPair Pair, int Prioriy, ExpressionSyntax Expression, bool Inverse)>();
var candidates = new List<(CurrencyPair Pair, int Priority, ExpressionSyntax Expression, bool Inverse)>();
foreach (var pair in new[]
{
(Pair: p, Priority: 0, Inverse: false),
@ -181,7 +181,7 @@ namespace BTCPayServer.Rating
if (candidates.Count == 0)
return CreateExpression($"ERR_NO_RULE_MATCH({p})");
var best = candidates
.OrderBy(c => c.Prioriy)
.OrderBy(c => c.Priority)
.ThenBy(c => c.Expression.Span.Start)
.First();
return best.Inverse

View File

@ -437,6 +437,8 @@ namespace BTCPayServer.Tests
Assert.Equal(payout.Id, payout2.Id);
Assert.Equal(destination, payout2.Destination);
Assert.Equal(PayoutState.AwaitingApproval, payout.State);
Assert.Equal("BTC", payout2.PaymentMethod);
Assert.Equal("BTC", payout2.CryptoCode);
Assert.Null(payout.PaymentMethodAmount);
Logs.Tester.LogInformation("Can't overdraft");
@ -1186,6 +1188,7 @@ namespace BTCPayServer.Tests
Assert.Single(paymentMethods);
var paymentMethod = paymentMethods.First();
Assert.Equal("BTC", paymentMethod.PaymentMethod);
Assert.Equal("BTC", paymentMethod.CryptoCode);
Assert.Empty(paymentMethod.Payments);

View File

@ -493,7 +493,7 @@ namespace BTCPayServer.Tests
var client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
await client.CreateInvoiceAsync(
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
new NBitpayClient.Invoice() { Price = 1.000000012m, Currency = "USD", FullNotifications = true },
NBitpayClient.Facade.Merchant);
client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
@ -503,7 +503,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
await client.CreateInvoiceAsync(
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
new NBitpayClient.Invoice() { Price = 1.000000012m, Currency = "USD", FullNotifications = true },
NBitpayClient.Facade.Merchant);
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));

View File

@ -3004,15 +3004,31 @@ namespace BTCPayServer.Tests
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task CanCreateStrangeInvoice()
{
using (var tester = ServerTester.Create())
{
tester.ActivateLightning();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.GrantAccess(true);
user.RegisterDerivationScheme("BTC");
DateTimeOffset expiration = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(21);
// This should fail, the amount is too low to be above the dust limit of bitcoin
var ex = Assert.Throws<BitPayException>(() => user.BitPay.CreateInvoice(
new Invoice()
{
Price = 0.000000012m,
Currency = "USD",
FullNotifications = true,
ExpirationTime = expiration
}, Facade.Merchant));
Assert.Contains("dust threshold", ex.Message);
await user.RegisterLightningNodeAsync("BTC");
var invoice1 = user.BitPay.CreateInvoice(
new Invoice()
{
@ -3021,6 +3037,7 @@ namespace BTCPayServer.Tests
FullNotifications = true,
ExpirationTime = expiration
}, Facade.Merchant);
Assert.Equal(expiration.ToUnixTimeSeconds(), invoice1.ExpirationTime.ToUnixTimeSeconds());
var invoice2 = user.BitPay.CreateInvoice(new Invoice() { Price = 0.000000019m, Currency = "USD" },
Facade.Merchant);

View File

@ -1,4 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />

View File

@ -1,8 +1,8 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Globalization;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
@ -49,14 +49,14 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.CanViewInvoices,
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/invoices")]
public async Task<IActionResult> GetInvoices(string storeId, [FromQuery] string[] orderId = null, [FromQuery] string[] status = null,
public async Task<IActionResult> GetInvoices(string storeId, [FromQuery] string[]? orderId = null, [FromQuery] string[]? status = null,
[FromQuery]
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
DateTimeOffset? startDate = null,
[FromQuery]
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
DateTimeOffset? endDate = null,
[FromQuery] string textSearch = null,
[FromQuery] string? textSearch = null,
[FromQuery] bool includeArchived = false,
[FromQuery] int? skip = null,
[FromQuery] int? take = null
@ -354,6 +354,7 @@ namespace BTCPayServer.Controllers.GreenField
{
Activated = details.Activated,
PaymentMethod = method.GetId().ToStringNormalized(),
CryptoCode = method.GetId().CryptoCode,
Destination = details.GetPaymentDestination(),
Rate = method.Rate,
Due = accounting.DueUncapped.ToDecimal(MoneyUnit.BTC),

View File

@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
@ -98,7 +99,7 @@ namespace BTCPayServer.Controllers.GreenField
{
ModelState.AddModelError(nameof(request.Period), $"The period should be positive");
}
PaymentMethodId[] paymentMethods = null;
PaymentMethodId?[]? paymentMethods = null;
if (request.PaymentMethods is { } paymentMethodsStr)
{
paymentMethods = paymentMethodsStr.Select(s =>
@ -218,6 +219,7 @@ namespace BTCPayServer.Controllers.GreenField
};
model.Destination = blob.Destination;
model.PaymentMethod = p.PaymentMethodId;
model.CryptoCode = p.GetPaymentMethodId().CryptoCode;
return model;
}
@ -245,7 +247,7 @@ namespace BTCPayServer.Controllers.GreenField
if (pp is null)
return PullPaymentNotFound();
var ppBlob = pp.GetBlob();
var destination = await payoutHandler.ParseClaimDestination(paymentMethodId,request.Destination, true);
var destination = await payoutHandler.ParseClaimDestination(paymentMethodId, request!.Destination, true);
if (destination.destination is null)
{
ModelState.AddModelError(nameof(request.Destination), destination.error??"The destination is invalid for the payment specified");
@ -338,7 +340,7 @@ namespace BTCPayServer.Controllers.GreenField
var payout = await ctx.Payouts.GetPayout(payoutId, storeId, true, true);
if (payout is null)
return PayoutNotFound();
RateResult rateResult = null;
RateResult? rateResult = null;
try
{
rateResult = await _pullPaymentService.GetRate(payout, approvePayoutRequest?.RateRule, cancellationToken);
@ -357,7 +359,7 @@ namespace BTCPayServer.Controllers.GreenField
var result = await _pullPaymentService.Approve(new PullPaymentHostedService.PayoutApproval()
{
PayoutId = payoutId,
Revision = revision.Value,
Revision = revision!.Value,
Rate = rateResult.BidAsk.Ask
});
var errorMessage = PullPaymentHostedService.PayoutApproval.GetErrorMessage(result);

View File

@ -516,10 +516,12 @@ namespace BTCPayServer.Controllers
{
paymentMethodId = enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == "BTC" && e.PaymentType == PaymentTypes.BTCLike) ??
enabledPaymentIds.FirstOrDefault(e => e.CryptoCode == "BTC" && e.PaymentType == PaymentTypes.LightningLike) ??
enabledPaymentIds.First();
enabledPaymentIds.FirstOrDefault();
}
isDefaultPaymentId = true;
}
if (paymentMethodId is null)
return null;
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
if (network is null || !invoice.Support(paymentMethodId))
{
@ -529,7 +531,9 @@ namespace BTCPayServer.Controllers
.GetPaymentMethods()
.FirstOrDefault(c => paymentMethodId.CryptoCode == c.GetId().CryptoCode);
if (paymentMethodTemp == null)
paymentMethodTemp = invoice.GetPaymentMethods().First();
paymentMethodTemp = invoice.GetPaymentMethods().FirstOrDefault();
if (paymentMethodTemp is null)
return null;
network = paymentMethodTemp.Network;
paymentMethodId = paymentMethodTemp.GetId();
}

View File

@ -65,14 +65,23 @@ namespace BTCPayServer.Controllers
if (CurrentStore is null)
return NotFound();
var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
if (!paymentMethods.Any())
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = "You must enable at least one payment method before creating a pull payment.",
Severity = StatusMessageModel.StatusSeverity.Error
});
return RedirectToAction("PaymentMethods", "Stores", new { storeId });
}
return View(new NewPullPaymentModel
{
Name = "",
Currency = "BTC",
CustomCSSLink = "",
EmbeddedCSS = "",
PaymentMethodItems = paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true))
PaymentMethodItems = paymentMethods.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true))
});
}
@ -406,6 +415,16 @@ namespace BTCPayServer.Controllers
int skip = 0, int count = 50)
{
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
if (!paymentMethods.Any())
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = "You must enable at least one payment method before creating a payout.",
Severity = StatusMessageModel.StatusSeverity.Error
});
return RedirectToAction("PaymentMethods", "Stores", new { storeId });
}
var vm = this.ParseListQuery(new PayoutsModel
{
PaymentMethods = paymentMethods,

View File

@ -136,7 +136,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
{
txout.ScriptPubKey = bitcoinLikeClaimDestination.Address.ScriptPubKey;
return Task.FromResult(txout.GetDustThreshold(new FeeRate(1.0m)).ToDecimal(MoneyUnit.BTC));
return Task.FromResult(txout.GetDustThreshold().ToDecimal(MoneyUnit.BTC));
}
return Task.FromResult(0m);

View File

@ -190,6 +190,15 @@ namespace BTCPayServer.Payments.Bitcoin
}
var reserved = await prepare.ReserveAddress;
if (paymentMethod.ParentEntity.Type != InvoiceType.TopUp)
{
var txOut = network.NBitcoinNetwork.Consensus.ConsensusFactory.CreateTxOut();
txOut.ScriptPubKey = reserved.Address.ScriptPubKey;
var dust = txOut.GetDustThreshold();
var amount = paymentMethod.Calculate().Due;
if (amount < dust)
throw new PaymentMethodUnavailableException("Amount below the dust threshold. For amounts of this size, it is recommended to enable an off-chain (Lightning) payment method");
}
onchainMethod.DepositAddress = reserved.Address.ToString();
onchainMethod.KeyPath = reserved.KeyPath;
onchainMethod.PayjoinEnabled = blob.PayJoinEnabled &&

View File

@ -424,7 +424,7 @@ namespace BTCPayServer.Payments.PayJoin
{
var outputContribution = Money.Min(additionalFee, -due);
outputContribution = Money.Min(outputContribution,
newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee));
newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold());
newTx.Outputs[i].Value -= outputContribution;
additionalFee -= outputContribution;
due += outputContribution;
@ -437,7 +437,7 @@ namespace BTCPayServer.Payments.PayJoin
{
var outputContribution = Money.Min(additionalFee, feeOutput.Value);
outputContribution = Money.Min(outputContribution,
feeOutput.Value - feeOutput.GetDustThreshold(minRelayTxFee));
feeOutput.Value - feeOutput.GetDustThreshold());
outputContribution = Money.Min(outputContribution, allowedSenderFeeContribution);
feeOutput.Value -= outputContribution;
additionalFee -= outputContribution;

View File

@ -65,7 +65,7 @@
{
<span v-if="srvModel.targetAmount" class="mt-3" id="crowdfund-header-target">
<h3 class="d-inline-block">
<span class="badge bg-info px-3" v-text="`${new Intl.NumberFormat().format(srvModel.targetAmount)} ${targetCurrency}`">@Math.Round(Model.TargetAmount.GetValueOrDefault(0)) @Model.TargetCurrency</span>
<span class="badge bg-info px-3" v-text="`${targetAmount} ${targetCurrency}`">@Math.Round(Model.TargetAmount.GetValueOrDefault(0)) @Model.TargetCurrency</span>
</h3>
@if (Model.ResetEveryAmount > 0 && !Model.NeverReset)
{
@ -123,7 +123,7 @@
<div class="card-body">
<div class="row py-2 text-center crowdfund-stats">
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-raised-amount">
<h3 v-text="`${new Intl.NumberFormat().format(raisedAmount)} ${targetCurrency}`">@Math.Round(Model.Info.CurrentAmount + Model.Info.CurrentPendingAmount) @Model.TargetCurrency</h3>
<h3 v-text="`${raisedAmount} ${targetCurrency}`">@Math.Round(Model.Info.CurrentAmount + Model.Info.CurrentPendingAmount, Model.CurrencyData.Divisibility) @Model.TargetCurrency</h3>
<h5 class="text-muted fst-italic mb-0">Raised</h5>
<b-tooltip target="crowdfund-body-raised-amount" v-if="paymentStats && paymentStats.length > 0" class="only-for-js">
<ul class="p-0 text-uppercase">
@ -287,8 +287,8 @@
<template id="perks-template">
<div class="perks-container">
<perk v-if="!perks || perks.length ===0"
:perk="{title: 'Donate Custom Amount', price: { type: 0, value: null}}"
<perk v-if="!perks || perks.length === 0"
:perk="{title: 'Donate Custom Amount', price: { type: 0, value: null}}"
:target-currency="targetCurrency"
:active="active"
:loading="loading"
@ -324,10 +324,10 @@
<img v-if="perk.image && perk.image != 'null'" class="card-img-top" :src="perk.image" />
<div class="card-body">
<div class="card-title d-flex justify-content-between">
<span class="h5">{{perk.title? perk.title : perk.id}}</span>
<span class="h5">{{perk.title ? perk.title : perk.id}}</span>
<span class="text-muted">
<template v-if="perk.price && perk.price.value">
{{new Intl.NumberFormat().format(perk.price.value.noExponents())}}
{{formatAmount(perk.price.value.noExponents(), srvModel.currencyData.divisibility)}}
{{targetCurrency}}
<template v-if="perk.price.type == 1">or more</template>
</template>

View File

@ -1,4 +1,4 @@
@model ListTransactionsViewModel
@model ListTransactionsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePageAndTitle(WalletsNavPages.Transactions, $"{Model.CryptoCode} Transactions", Context.GetStoreData().StoreName);
@ -35,7 +35,6 @@
.badge-container {
display: flex;
align-items: center;
flex-wrap: nowrap;
}

View File

@ -48,8 +48,6 @@ document.addEventListener("DOMContentLoaded",function (ev) {
this.amount = this.perk.price.type === 0? null : (amount || 0).noExponents();
this.expanded = false;
}
},
mounted: function () {
this.setAmount(this.perk.price.value);
@ -63,7 +61,6 @@ document.addEventListener("DOMContentLoaded",function (ev) {
}
}
}
});
app = new Vue({
@ -82,14 +79,17 @@ document.addEventListener("DOMContentLoaded",function (ev) {
active: true,
animation: true,
sound: true,
lastUpdated:"",
lastUpdated: "",
loading: false,
timeoutState: 0
}
},
computed: {
raisedAmount: function(){
return parseFloat(this.srvModel.info.currentAmount + this.srvModel.info.currentPendingAmount ).toFixed(this.srvModel.currencyData.divisibility) ;
return this.formatAmount(this.srvModel.info.currentAmount + this.srvModel.info.currentPendingAmount);
},
targetAmount: function(){
return this.formatAmount(this.srvModel.targetAmount);
},
percentageRaisedAmount: function(){
return parseFloat(this.srvModel.info.progressPercentage + this.srvModel.info.pendingProgressPercentage ).toFixed(2);
@ -203,6 +203,9 @@ document.addEventListener("DOMContentLoaded",function (ev) {
if(this.timeoutState){
clearTimeout(this.timeoutState);
}
},
formatAmount: function(amount) {
return formatAmount(amount, this.srvModel.currencyData.divisibility)
}
},
mounted: function () {
@ -315,3 +318,18 @@ document.addEventListener("DOMContentLoaded",function (ev) {
});
});
/**
* Formats input string as a number according to browser locale
* with correctly displayed fraction amount (e.g. 0.012345 for BTC instead of just 0.0123)
*
* @param {number | string} amount Amount to format
* @param {number} divisibility Currency divisibility (e.g., 8 for BTC)
* @returns String formatted as a number according to current browser locale and correct fraction amount
*/
function formatAmount(amount, divisibility) {
var parsedAmount = parseFloat(amount).toFixed(divisibility);
var [wholeAmount, fractionAmount] = parsedAmount.split('.');
var formattedWholeAmount = new Intl.NumberFormat().format(parseInt(wholeAmount, 10));
return formattedWholeAmount + (fractionAmount ? '.' + fractionAmount : '');
}

View File

@ -1152,7 +1152,11 @@
"properties": {
"paymentMethod": {
"type": "string",
"description": "The payment method"
"description": "Payment method available for the invoice (e.g., \"BTC\" or \"BTC-LightningNetwork\" or \"BTC-LNURLPAY\")"
},
"cryptoCode": {
"type": "string",
"description": "Crypto code of the payment method (e.g., \"BTC\" or \"LTC\")"
},
"destination": {
"type": "string",

View File

@ -313,6 +313,46 @@
"security": []
}
},
"/api/v1/pull-payments/{pullPaymentId}/payouts/{payoutId}": {
"parameters": [
{
"name": "pullPaymentId",
"in": "path",
"required": true,
"description": "The ID of the pull payment",
"schema": { "type": "string" }
},
{
"name": "payoutId",
"in": "path",
"required": true,
"description": "The ID of the pull payment payout",
"schema": { "type": "string" }
}
],
"get": {
"summary": "Get Payout",
"operationId": "PullPayments_GetPayout",
"description": "Get payout",
"responses": {
"200": {
"description": "A specific payout of a pull payment",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PayoutData"
}
}
}
},
"404": {
"description": "Pull payment payout not found"
}
},
"tags": [ "Pull payments (Public)", "Pull payments payout (Public)" ],
"security": []
}
},
"/api/v1/stores/{storeId}/payouts/{payoutId}": {
"parameters": [
{
@ -554,7 +594,12 @@
"paymentMethod": {
"type": "string",
"example": "BTC",
"description": "The payment method of the payout"
"description": "The payment method of the payout (e.g., \"BTC\" or \"BTC_LightningLike\""
},
"cryptoCode": {
"type": "string",
"example": "BTC",
"description": "Crypto code of the payment method of the payout (e.g., \"BTC\" or \"LTC\")"
},
"paymentMethodAmount": {
"type": "string",

View File

@ -65,7 +65,7 @@
"description": "Whether the payment method is enabled"
},
"cryptoCode": {
"type": "boolean",
"type": "string",
"description": "The currency code of the payment method"
},
"data": {

View File

@ -625,7 +625,7 @@
"type": {
"type": "string",
"nullable": false,
"description": "The type of this event, current available are `InvoiceCreated`, `InvoiceReceivedPayment`, `InvoicePaidInFull`, `InvoiceExpired`, `InvoiceSettled`, and `InvoiceInvalid`."
"description": "The type of this event, current available are `InvoiceCreated`, `InvoiceReceivedPayment`, `InvoiceProcessing`, `InvoiceExpired`, `InvoiceSettled`, `InvoiceInvalid`, and `InvoicePaymentSettled`."
},
"timestamp": {
"description": "The timestamp when this delivery has been created",
@ -899,9 +899,9 @@
}
}
},
"InvoicePaidInFull": {
"InvoiceProcessing": {
"post": {
"summary": "InvoicePaidInFull",
"summary": "InvoiceProcessing",
"description": "Triggers when an invoice is fully paid, but doesn't have the required amount of confirmations on the blockchain yet according to your store's settings.",
"parameters": [
{

View File

@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<Version>1.3.4</Version>
<Version>1.3.5</Version>
</PropertyGroup>
</Project>

View File

@ -1,5 +1,21 @@
# Changelog
## 1.3.5
### Bug fixes:
* Fix: Checkout page of for invoices of 0 amount shouldn't crash, but 404 @NicolasDorier
* Swagger doc: Fix type of property cryptoCode (#3088) @ndeet
* Fix bug with fraction amount display in crowdfund app (#3098) @bolatovumar
* Swagger doc: Update Swagger docs for webhook event types (#3104) @bolatovumar
* Payout/pull payment page would crash if no payment method are set on the store @satwo
### Improvements:
* Add crypto code for invoice and pull payment payout API response (#3099) @bolatovumar
* Prevent creation of on-chain invoices below the dust limit (#3082) @satwo
## 1.3.4
### Bug fixes: