mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Add ability to require refund email from app level (#3013)
* Add ability to require refund email from app level * Add ability request refund email when creating invoice manually * Adjust labels * Add UI tests * Add Greenfield API support * Rename RequiresRefundEmailType to RequiresRefundEmail * Fix build Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
0b5d0349d4
commit
8f117b5079
21 changed files with 137 additions and 16 deletions
|
@ -1,9 +1,5 @@
|
|||
using System;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace BTCPayServer.Client.Models
|
|||
public string RedirectURL { get; set; }
|
||||
|
||||
public bool? RedirectAutomatically { get; set; }
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public string DefaultLanguage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
|
@ -73,6 +71,68 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanHandleRefundEmailForm2()
|
||||
{
|
||||
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
// Prepare user account and store
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
|
||||
// Now create an invoice that requires a refund email
|
||||
var invoice = s.CreateInvoice(store.storeName, 100, "USD", "", null, true);
|
||||
s.GoToInvoiceCheckout(invoice);
|
||||
|
||||
var emailInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
|
||||
Assert.True(emailInput.Displayed);
|
||||
|
||||
emailInput.SendKeys("a@g.com");
|
||||
|
||||
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
|
||||
actionButton.Click();
|
||||
try // Sometimes the click only take the focus, without actually really clicking on it...
|
||||
{
|
||||
actionButton.Click();
|
||||
}
|
||||
catch { }
|
||||
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
|
||||
s.GoToHome();
|
||||
|
||||
// Now create an invoice that doesn't require a refund email
|
||||
s.CreateInvoice(store.storeName, 100, "USD", "", null, false);
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.Navigate().Back();
|
||||
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
|
||||
s.GoToHome();
|
||||
|
||||
// Now create an invoice that requires refund email but already has one set, email input shouldn't show up
|
||||
s.CreateInvoice(store.storeName, 100, "USD", "a@g.com", null, true);
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.Navigate().Back();
|
||||
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseLanguageDropdown()
|
||||
{
|
||||
|
|
|
@ -1094,11 +1094,13 @@ namespace BTCPayServer.Tests
|
|||
Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"),
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
RedirectAutomatically = true
|
||||
RedirectAutomatically = true,
|
||||
RequiresRefundEmail = true
|
||||
},
|
||||
AdditionalSearchTerms = new string[] { "Banana" }
|
||||
});
|
||||
Assert.True(newInvoice.Checkout.RedirectAutomatically);
|
||||
Assert.True(newInvoice.Checkout.RequiresRefundEmail);
|
||||
Assert.Equal(user.StoreId, newInvoice.StoreId);
|
||||
//list
|
||||
var invoices = await viewOnly.GetInvoices(user.StoreId);
|
||||
|
|
|
@ -364,6 +364,7 @@ namespace BTCPayServer.Tests
|
|||
string currency = "USD",
|
||||
string refundEmail = "",
|
||||
string defaultPaymentMethod = null,
|
||||
bool? requiresRefundEmail = null,
|
||||
StatusMessageModel.StatusSeverity expectedSeverity = StatusMessageModel.StatusSeverity.Success
|
||||
)
|
||||
{
|
||||
|
@ -378,6 +379,8 @@ namespace BTCPayServer.Tests
|
|||
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
|
||||
if (defaultPaymentMethod is string)
|
||||
new SelectElement(Driver.FindElement(By.Name("DefaultPaymentMethod"))).SelectByValue(defaultPaymentMethod);
|
||||
if (requiresRefundEmail is bool)
|
||||
new SelectElement(Driver.FindElement(By.Name("RequiresRefundEmail"))).SelectByValue(requiresRefundEmail == true ? "1" : "2");
|
||||
Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
var statusElement = FindAlertMessage(expectedSeverity);
|
||||
|
|
|
@ -1344,7 +1344,7 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false);
|
||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||
}, false);
|
||||
s.CreateInvoice(store.storeName, 0.0000001m, "BTC","",null, StatusMessageModel.StatusSeverity.Error);
|
||||
s.CreateInvoice(store.storeName, 0.0000001m, "BTC","",null, expectedSeverity: StatusMessageModel.StatusSeverity.Error);
|
||||
|
||||
|
||||
i = s.CreateInvoice(store.storeName, null, "BTC");
|
||||
|
|
|
@ -56,6 +56,7 @@ namespace BTCPayServer.Controllers
|
|||
ShowCustomAmount = true;
|
||||
ShowDiscount = true;
|
||||
EnableTips = true;
|
||||
RequiresRefundEmail = RequiresRefundEmail.InheritFromStore;
|
||||
}
|
||||
public string Title { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
@ -65,6 +66,7 @@ namespace BTCPayServer.Controllers
|
|||
public bool ShowCustomAmount { get; set; }
|
||||
public bool ShowDiscount { get; set; }
|
||||
public bool EnableTips { get; set; }
|
||||
public RequiresRefundEmail RequiresRefundEmail { get; set; }
|
||||
|
||||
public const string BUTTON_TEXT_DEF = "Buy for {0}";
|
||||
public string ButtonText { get; set; } = BUTTON_TEXT_DEF;
|
||||
|
@ -118,7 +120,8 @@ namespace BTCPayServer.Controllers
|
|||
NotificationUrl = settings.NotificationUrl,
|
||||
RedirectUrl = settings.RedirectUrl,
|
||||
SearchTerm = $"storeid:{app.StoreDataId}",
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : ""
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : "",
|
||||
RequiresRefundEmail = settings.RequiresRefundEmail
|
||||
};
|
||||
if (HttpContext?.Request != null)
|
||||
{
|
||||
|
@ -202,7 +205,8 @@ namespace BTCPayServer.Controllers
|
|||
RedirectUrl = vm.RedirectUrl,
|
||||
Description = vm.Description,
|
||||
EmbeddedCSS = vm.EmbeddedCSS,
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically)
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically),
|
||||
RequiresRefundEmail = vm.RequiresRefundEmail,
|
||||
});
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
|
|
|
@ -101,7 +101,8 @@ namespace BTCPayServer.Controllers
|
|||
CustomLogoLink = storeBlob.CustomLogo,
|
||||
AppId = appId,
|
||||
Description = settings.Description,
|
||||
EmbeddedCSS = settings.EmbeddedCSS
|
||||
EmbeddedCSS = settings.EmbeddedCSS,
|
||||
RequiresRefundEmail = settings.RequiresRefundEmail
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -120,7 +121,9 @@ namespace BTCPayServer.Controllers
|
|||
string notificationUrl,
|
||||
string redirectUrl,
|
||||
string choiceKey,
|
||||
string posData = null, CancellationToken cancellationToken = default)
|
||||
string posData = null,
|
||||
RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var app = await _AppService.GetApp(appId, AppType.PointOfSale);
|
||||
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
||||
|
@ -226,6 +229,9 @@ namespace BTCPayServer.Controllers
|
|||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
||||
RedirectAutomatically = settings.RedirectAutomatically,
|
||||
SupportedTransactionCurrencies = paymentMethods,
|
||||
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
||||
? store.GetStoreBlob().RequiresRefundEmail
|
||||
: requiresRefundEmail == RequiresRefundEmail.On,
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||
new List<string>() { AppService.GetAppInternalTag(appId) },
|
||||
cancellationToken);
|
||||
|
|
|
@ -420,6 +420,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
SpeedPolicy = entity.SpeedPolicy,
|
||||
DefaultLanguage = entity.DefaultLanguage,
|
||||
RedirectAutomatically = entity.RedirectAutomatically,
|
||||
RequiresRefundEmail = entity.RequiresRefundEmail,
|
||||
RedirectURL = entity.RedirectURLTemplate
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,8 +18,8 @@ using BTCPayServer.HostedServices;
|
|||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Invoices.Export;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
@ -30,7 +30,6 @@ using Microsoft.AspNetCore.Routing;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.RPC;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
@ -564,7 +563,7 @@ namespace BTCPayServer.Controllers
|
|||
IsUnsetTopUp = invoice.IsUnsetTopUp(),
|
||||
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
||||
RequiresRefundEmail = invoice.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,
|
||||
|
@ -873,7 +872,10 @@ namespace BTCPayServer.Controllers
|
|||
}),
|
||||
DefaultPaymentMethod = model.DefaultPaymentMethod,
|
||||
NotificationEmail = model.NotificationEmail,
|
||||
ExtendedNotifications = model.NotificationEmail != null
|
||||
ExtendedNotifications = model.NotificationEmail != null,
|
||||
RequiresRefundEmail = model.RequiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
||||
? store.GetStoreBlob().RequiresRefundEmail
|
||||
: model.RequiresRefundEmail == RequiresRefundEmail.On
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Invoice {result.Data.Id} just created!";
|
||||
|
|
|
@ -127,6 +127,7 @@ namespace BTCPayServer.Controllers
|
|||
entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite;
|
||||
entity.RedirectAutomatically =
|
||||
invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically);
|
||||
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
IPaymentFilter? excludeFilter = null;
|
||||
|
@ -151,6 +152,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
entity.DefaultPaymentMethod = invoice.DefaultPaymentMethod;
|
||||
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -178,6 +180,7 @@ namespace BTCPayServer.Controllers
|
|||
entity.DefaultLanguage = invoice.Checkout.DefaultLanguage;
|
||||
entity.DefaultPaymentMethod = invoice.Checkout.DefaultPaymentMethod;
|
||||
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
|
||||
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
||||
IPaymentFilter? excludeFilter = null;
|
||||
if (invoice.Checkout.PaymentMethods != null)
|
||||
{
|
||||
|
@ -188,6 +191,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
entity.PaymentTolerance = invoice.Checkout.PaymentTolerance ?? storeBlob.PaymentTolerance;
|
||||
entity.RedirectURLTemplate = invoice.Checkout.RedirectURL?.Trim();
|
||||
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
||||
if (additionalTags != null)
|
||||
entity.InternalTags.AddRange(additionalTags);
|
||||
return await CreateInvoiceCoreRaw(entity, store, excludeFilter, invoice.AdditionalSearchTerms, cancellationToken);
|
||||
|
|
|
@ -89,5 +89,7 @@ namespace BTCPayServer.Models.AppViewModels
|
|||
[Display(Name = "Custom CSS Code")]
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public string Description { get; set; }
|
||||
[Display(Name = "Require refund email on checkout")]
|
||||
public RequiresRefundEmail RequiresRefundEmail { get; set; } = RequiresRefundEmail.InheritFromStore;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,5 +64,6 @@ namespace BTCPayServer.Models.AppViewModels
|
|||
public string Description { get; set; }
|
||||
[Display(Name = "Custom CSS Code")]
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public RequiresRefundEmail RequiresRefundEmail { get; set; } = RequiresRefundEmail.InheritFromStore;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,8 @@ namespace BTCPayServer.Models
|
|||
|
||||
[JsonProperty(PropertyName = "redirectAutomatically", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public bool? RedirectAutomatically { get; set; }
|
||||
[JsonProperty(PropertyName = "requiresRefundEmail", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public bool? RequiresRefundEmail { get; set; }
|
||||
|
||||
//Bitpay compatibility: create invoice in btcpay uses this instead of supportedTransactionCurrencies
|
||||
[JsonProperty(PropertyName = "paymentCurrencies", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
|
@ -87,5 +88,11 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Require Refund Email")]
|
||||
public RequiresRefundEmail RequiresRefundEmail
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,4 +18,14 @@ namespace BTCPayServer.Services.Apps
|
|||
[Display(Name = "Keypad only")]
|
||||
Light
|
||||
}
|
||||
|
||||
public enum RequiresRefundEmail
|
||||
{
|
||||
[Display(Name = "Inherit from store settings")]
|
||||
InheritFromStore,
|
||||
[Display(Name = "On")]
|
||||
On,
|
||||
[Display(Name = "Off")]
|
||||
Off
|
||||
}
|
||||
}
|
||||
|
|
|
@ -378,6 +378,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
#pragma warning restore CS0618
|
||||
public bool Refundable { get; set; }
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public string RefundMail { get; set; }
|
||||
[JsonProperty("redirectURL")]
|
||||
public string RedirectURLTemplate { get; set; }
|
||||
|
|
|
@ -57,6 +57,11 @@
|
|||
<input asp-for="ButtonText" class="form-control" required />
|
||||
<span asp-validation-for="ButtonText" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="RequiresRefundEmail" class="form-check-label"></label>
|
||||
<select asp-for="RequiresRefundEmail" asp-items="@Html.GetEnumSelectList<RequiresRefundEmail>()" class="form-select" required></select>
|
||||
<span asp-validation-for="RequiresRefundEmail" class="text-danger"></span>
|
||||
</div>
|
||||
<section id="discounts" class="p-0">
|
||||
<h4 class="mt-5 mb-4">Discounts</h4>
|
||||
<div class="form-check mb-4">
|
||||
|
|
|
@ -37,12 +37,14 @@
|
|||
{
|
||||
<form method="post" asp-controller="AppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
||||
<input type="hidden" name="choicekey" value="@item.Id"/>
|
||||
<input type="hidden" name="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
||||
@{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Value, item.Price.Value);}
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" asp-controller="AppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false">
|
||||
<input type="hidden" name="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
||||
<button type="submit" name="choiceKey" class="js-add-cart btn btn-primary" value="@item.Id">
|
||||
@Safe.Raw(buttonText)
|
||||
</button>
|
||||
|
@ -94,6 +96,7 @@
|
|||
{
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">@Model.CurrencySymbol</span>
|
||||
<input type="hidden" name="requiresRefundEmail" value="@Model.RequiresRefundEmail.ToString()" />
|
||||
<input class="form-control" type="number" min="@(minPriceValue ?? 0)" step="@Model.Step" name="amount" placeholder="Amount" value="@priceValue">
|
||||
<button class="btn btn-primary text-nowrap" type="submit">@buttonText</button>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@model BTCPayServer.Models.InvoicingModels.CreateInvoiceModel
|
||||
@using BTCPayServer.Services.Apps
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(InvoiceNavPages.Create, "Create an invoice");
|
||||
}
|
||||
|
@ -64,6 +65,11 @@
|
|||
<input asp-for="BuyerEmail" class="form-control" />
|
||||
<span asp-validation-for="BuyerEmail" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="RequiresRefundEmail" class="form-check-label"></label>
|
||||
<select asp-for="RequiresRefundEmail" asp-items="@Html.GetEnumSelectList<RequiresRefundEmail>()" class="form-select"></select>
|
||||
<span asp-validation-for="RequiresRefundEmail" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="NotificationUrl" class="form-label"></label>
|
||||
<input asp-for="NotificationUrl" class="form-control" />
|
||||
|
|
|
@ -1094,6 +1094,11 @@
|
|||
"nullable": true,
|
||||
"description": "When the customer has paid the invoice, and a `redirectURL` is set, the checkout is redirected to `redirectURL` automatically if `redirectAutomatically` is true. Defaults to the store's settings. (The default store settings is false)"
|
||||
},
|
||||
"requiresRefundEmail": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "Invoice will require user to provide a refund email if this option is set to `true`. Has no effect if `buyerEmail` metadata is set as there is no email to collect in this case."
|
||||
},
|
||||
"defaultLanguage": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
|
|
Loading…
Add table
Reference in a new issue