mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Add checkoutLink and defaultLanguage to GreenField invoice
This commit is contained in:
parent
6cf29123f3
commit
798cf66e3f
11 changed files with 137 additions and 17 deletions
|
@ -31,9 +31,9 @@ namespace BTCPayServer.Client.Models
|
|||
public TimeSpan? Monitoring { get; set; }
|
||||
|
||||
public double? PaymentTolerance { get; set; }
|
||||
|
||||
[JsonProperty("redirectURL")]
|
||||
public string RedirectURL { get; set; }
|
||||
public string DefaultLanguage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace BTCPayServer.Client.Models
|
|||
public class InvoiceData : CreateInvoiceRequest
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string CheckoutLink { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public InvoiceStatus Status { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
|
|
|
@ -10,6 +10,7 @@ using BTCPayServer.Controllers;
|
|||
using BTCPayServer.Events;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
|
@ -244,19 +245,19 @@ namespace BTCPayServer.Tests
|
|||
Password = "afewfoiewiou",
|
||||
IsAdministrator = true
|
||||
}));
|
||||
|
||||
|
||||
// If we set DisableNonAdminCreateUserApi = true, it should always fail to create a user unless you are an admin
|
||||
await settings.UpdateSetting(new PoliciesSettings() { LockSubscription = false, DisableNonAdminCreateUserApi = true});
|
||||
await settings.UpdateSetting(new PoliciesSettings() { LockSubscription = false, DisableNonAdminCreateUserApi = true });
|
||||
await AssertHttpError(403,
|
||||
async () =>
|
||||
await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test9@gmail.com", Password = "afewfoiewiou"}));
|
||||
new CreateApplicationUserRequest() { Email = "test9@gmail.com", Password = "afewfoiewiou" }));
|
||||
await AssertHttpError(403,
|
||||
async () =>
|
||||
await user1Client.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test9@gmail.com", Password = "afewfoiewiou"}));
|
||||
new CreateApplicationUserRequest() { Email = "test9@gmail.com", Password = "afewfoiewiou" }));
|
||||
await adminClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test9@gmail.com", Password = "afewfoiewiou"});
|
||||
new CreateApplicationUserRequest() { Email = "test9@gmail.com", Password = "afewfoiewiou" });
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -964,7 +965,7 @@ namespace BTCPayServer.Tests
|
|||
var paymentMethod = paymentMethods.First();
|
||||
Assert.Equal("BTC", paymentMethod.PaymentMethod);
|
||||
Assert.Empty(paymentMethod.Payments);
|
||||
|
||||
|
||||
|
||||
//update
|
||||
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
|
@ -1025,6 +1026,42 @@ namespace BTCPayServer.Tests
|
|||
Assert.NotNull(await client.GetWebhookDelivery(evt.StoreId, evt.WebhookId, evt.DeliveryId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Currency = "USD",
|
||||
Amount = 1,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
DefaultLanguage = "it-it ",
|
||||
RedirectURL = "http://toto.com/lol"
|
||||
}
|
||||
});
|
||||
Assert.EndsWith($"/i/{newInvoice.Id}", newInvoice.CheckoutLink);
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(user.UserId, user.StoreId);
|
||||
var model = (PaymentModel)((ViewResult)await controller.Checkout(newInvoice.Id)).Model;
|
||||
Assert.Equal("it-IT", model.DefaultLang);
|
||||
Assert.Equal("http://toto.com/lol", model.MerchantRefLink);
|
||||
|
||||
var langs = tester.PayTester.GetService<LanguageService>();
|
||||
foreach (var match in new[] { "it", "it-IT", "it-LOL" })
|
||||
{
|
||||
Assert.Equal("it-IT", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
foreach (var match in new[] { "pt-BR" })
|
||||
{
|
||||
Assert.Equal("pt-BR", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
foreach (var match in new[] { "en", "en-US" })
|
||||
{
|
||||
Assert.Equal("en", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
foreach (var match in new[] { "pt", "pt-pt", "pt-PT" })
|
||||
{
|
||||
Assert.Equal("pt-PT", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,11 +7,13 @@ using BTCPayServer.Client.Models;
|
|||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
|
@ -29,11 +31,16 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
private readonly InvoiceController _invoiceController;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
||||
public GreenFieldInvoiceController(InvoiceController invoiceController, InvoiceRepository invoiceRepository)
|
||||
public LanguageService LanguageService { get; }
|
||||
|
||||
public GreenFieldInvoiceController(InvoiceController invoiceController, InvoiceRepository invoiceRepository, LinkGenerator linkGenerator, LanguageService languageService)
|
||||
{
|
||||
_invoiceController = invoiceController;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_linkGenerator = linkGenerator;
|
||||
LanguageService = languageService;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
|
@ -139,6 +146,21 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
"PaymentTolerance can only be between 0 and 100 percent", this);
|
||||
}
|
||||
|
||||
if (request.Checkout.DefaultLanguage != null)
|
||||
{
|
||||
var lang = LanguageService.FindBestMatch(request.Checkout.DefaultLanguage);
|
||||
if (lang == null)
|
||||
{
|
||||
request.AddModelError(invoiceRequest => invoiceRequest.Checkout.DefaultLanguage,
|
||||
"The requested defaultLang does not exists, Browse the ~/misc/lang page of your BTCPay Server instance to see the list of supported languages.", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure this is good case
|
||||
request.Checkout.DefaultLanguage = lang.Code;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
|
@ -287,6 +309,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
CreatedTime = entity.InvoiceTime,
|
||||
Amount = entity.Price,
|
||||
Id = entity.Id,
|
||||
CheckoutLink = _linkGenerator.CheckoutLink(entity.Id, Request.Scheme, Request.Host, Request.PathBase),
|
||||
Status = entity.Status.ToModernStatus(),
|
||||
AdditionalStatus = entity.ExceptionStatus,
|
||||
Currency = entity.Currency,
|
||||
|
@ -298,7 +321,8 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
PaymentTolerance = entity.PaymentTolerance,
|
||||
PaymentMethods =
|
||||
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
|
||||
SpeedPolicy = entity.SpeedPolicy
|
||||
SpeedPolicy = entity.SpeedPolicy,
|
||||
DefaultLanguage = entity.DefaultLanguage
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.HostedServices;
|
|||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
@ -31,15 +32,18 @@ namespace BTCPayServer.Controllers
|
|||
private readonly IFileProvider _fileProvider;
|
||||
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
public LanguageService LanguageService { get; }
|
||||
SignInManager<ApplicationUser> SignInManager { get; }
|
||||
|
||||
public HomeController(IHttpClientFactory httpClientFactory,
|
||||
CssThemeManager cachedServerSettings,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
LanguageService languageService,
|
||||
SignInManager<ApplicationUser> signInManager)
|
||||
{
|
||||
HttpClientFactory = httpClientFactory;
|
||||
_cachedServerSettings = cachedServerSettings;
|
||||
LanguageService = languageService;
|
||||
_fileProvider = webHostEnvironment.WebRootFileProvider;
|
||||
SignInManager = signInManager;
|
||||
}
|
||||
|
@ -116,6 +120,12 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return View(new BitpayTranslatorViewModel());
|
||||
}
|
||||
[Route("misc/lang")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public IActionResult Languages()
|
||||
{
|
||||
return Json(LanguageService.GetLanguages(), new JsonSerializerSettings() { Formatting = Formatting.Indented });
|
||||
}
|
||||
|
||||
[Route("swagger/v1/swagger.json")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
|
||||
|
|
|
@ -433,14 +433,14 @@ namespace BTCPayServer.Controllers
|
|||
[XFrameOptionsAttribute(null)]
|
||||
[ReferrerPolicyAttribute("origin")]
|
||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string paymentMethodId = null,
|
||||
[FromQuery] string view = null)
|
||||
[FromQuery] string view = null, [FromQuery] string lang = null)
|
||||
{
|
||||
//Keep compatibility with Bitpay
|
||||
invoiceId = invoiceId ?? id;
|
||||
id = invoiceId;
|
||||
//
|
||||
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId), lang);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
|
@ -465,21 +465,21 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("invoice-noscript")]
|
||||
public async Task<IActionResult> CheckoutNoScript(string invoiceId, string id = null, string paymentMethodId = null)
|
||||
public async Task<IActionResult> CheckoutNoScript(string invoiceId, string id = null, string paymentMethodId = null, [FromQuery] string lang = null)
|
||||
{
|
||||
//Keep compatibility with Bitpay
|
||||
invoiceId = invoiceId ?? id;
|
||||
id = invoiceId;
|
||||
//
|
||||
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId), lang);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId, string lang)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice == null)
|
||||
|
@ -534,7 +534,7 @@ namespace BTCPayServer.Controllers
|
|||
RootPath = this.Request.PathBase.Value.WithTrailingSlash(),
|
||||
OrderId = invoice.Metadata.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
DefaultLang = storeBlob.DefaultLang ?? "en",
|
||||
DefaultLang = lang ?? invoice.DefaultLanguage ?? storeBlob.DefaultLang ?? "en",
|
||||
CustomCSSLink = storeBlob.CustomCSS,
|
||||
CustomLogoLink = storeBlob.CustomLogo,
|
||||
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
|
||||
|
@ -619,9 +619,9 @@ namespace BTCPayServer.Controllers
|
|||
[Route("invoice/{invoiceId}/status")]
|
||||
[Route("invoice/{invoiceId}/{paymentMethodId}/status")]
|
||||
[Route("invoice/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId, string paymentMethodId = null)
|
||||
public async Task<IActionResult> GetStatus(string invoiceId, string paymentMethodId = null, [FromQuery] string lang = null)
|
||||
{
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId), lang);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
return Json(model);
|
||||
|
|
|
@ -172,6 +172,7 @@ namespace BTCPayServer.Controllers
|
|||
entity.Currency = invoice.Currency;
|
||||
entity.Price = invoice.Amount;
|
||||
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;
|
||||
entity.DefaultLanguage = invoice.Checkout.DefaultLanguage;
|
||||
IPaymentFilter excludeFilter = null;
|
||||
if (invoice.Checkout.PaymentMethods != null)
|
||||
{
|
||||
|
|
|
@ -54,6 +54,15 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
scheme, host, pathbase);
|
||||
}
|
||||
|
||||
public static string CheckoutLink(this LinkGenerator urlHelper, string invoiceId, string scheme, HostString host, string pathbase)
|
||||
{
|
||||
return urlHelper.GetUriByAction(
|
||||
action: nameof(InvoiceController.Checkout),
|
||||
controller: "Invoice",
|
||||
values: new { invoiceId = invoiceId },
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
|
||||
public static string PayoutLink(this LinkGenerator urlHelper, string walletId,string pullPaymentId, string scheme, HostString host, string pathbase)
|
||||
{
|
||||
return urlHelper.GetUriByAction(
|
||||
|
|
|
@ -132,6 +132,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
public string StoreId { get; set; }
|
||||
|
||||
public SpeedPolicy SpeedPolicy { get; set; }
|
||||
public string DefaultLanguage { get; set; }
|
||||
[Obsolete("Use GetPaymentMethod(network) instead")]
|
||||
public decimal Rate { get; set; }
|
||||
public DateTimeOffset InvoiceTime { get; set; }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
@ -46,5 +48,31 @@ namespace BTCPayServer.Services
|
|||
{
|
||||
return _languages;
|
||||
}
|
||||
|
||||
public Language FindBestMatch(string defaultLang)
|
||||
{
|
||||
if (defaultLang is null)
|
||||
return null;
|
||||
defaultLang = defaultLang.Trim();
|
||||
if (defaultLang.Length < 2)
|
||||
return null;
|
||||
var split = defaultLang.Split('-', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (split.Length != 1 && split.Length != 2)
|
||||
return null;
|
||||
var lang = split[0];
|
||||
var country = split.Length == 2 ? split[1] : split[0].ToUpperInvariant();
|
||||
|
||||
var langStart = lang + "-";
|
||||
var langMatches = GetLanguages()
|
||||
.Where(l => l.Code.Equals(lang, StringComparison.OrdinalIgnoreCase) ||
|
||||
l.Code.StartsWith(langStart, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var countryMatches = langMatches;
|
||||
var countryEnd = "-" + country;
|
||||
countryMatches =
|
||||
countryMatches
|
||||
.Where(l => l.Code.EndsWith(countryEnd, StringComparison.OrdinalIgnoreCase));
|
||||
return countryMatches.FirstOrDefault() ?? langMatches.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -567,6 +567,10 @@
|
|||
"type": "string",
|
||||
"description": "The identifier of the invoice"
|
||||
},
|
||||
"checkoutLink": {
|
||||
"type": "string",
|
||||
"description": "The link to the checkout page, where you can redirect the customer"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "number",
|
||||
"format": "int64",
|
||||
|
@ -667,6 +671,11 @@
|
|||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "When the customer paid the invoice, the URL where the customer will be redirected when clicking on the `return to store` button. You can use placeholders `{InvoiceId}` or `{OrderId}` in the URL, BTCPay Server will replace those with this invoice `id` or `metadata.orderId` respectively."
|
||||
},
|
||||
"defaultLanguage": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "The language code (eg. en-US, en, fr-FR...) of the language presented to your customer in the checkout page. BTCPay Server tries to match the best language available. If null or not set, will fallback on the store's default language. Browse the [/misc/lang](/misc/lang) page of your BTCPay Server instance to see the list of supported languages."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue