Add checkoutLink and defaultLanguage to GreenField invoice

This commit is contained in:
nicolas.dorier 2020-12-10 23:34:50 +09:00
parent 6cf29123f3
commit 798cf66e3f
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
11 changed files with 137 additions and 17 deletions

View file

@ -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; }
}
}
}

View file

@ -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))]

View file

@ -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);
}
}
}

View file

@ -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
}
};
}

View file

@ -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)]

View file

@ -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);

View file

@ -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)
{

View file

@ -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(

View file

@ -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; }

View file

@ -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();
}
}
}

View file

@ -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."
}
}
},