mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 14:22:40 +01:00
parent
701ba59bd8
commit
16f4ca5fbf
5 changed files with 370 additions and 148 deletions
|
@ -20,6 +20,17 @@ namespace BTCPayServer.Client
|
||||||
return await HandleResponse<PointOfSaleAppData>(response);
|
return await HandleResponse<PointOfSaleAppData>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual async Task<PointOfSaleAppData> PutPointOfSaleApp(string appId,
|
||||||
|
CreatePointOfSaleAppRequest request, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (request == null)
|
||||||
|
throw new ArgumentNullException(nameof(request));
|
||||||
|
var response = await _httpClient.SendAsync(
|
||||||
|
CreateHttpRequest($"api/v1/apps/pos/{appId}", bodyPayload: request,
|
||||||
|
method: HttpMethod.Put), token);
|
||||||
|
return await HandleResponse<PointOfSaleAppData>(response);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual async Task<AppDataBase> GetApp(string appId, CancellationToken token = default)
|
public virtual async Task<AppDataBase> GetApp(string appId, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (appId == null)
|
if (appId == null)
|
||||||
|
|
|
@ -195,7 +195,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
[Fact(Timeout = TestTimeout)]
|
[Fact(Timeout = TestTimeout)]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
public async Task CanCreateReadAndDeletePointOfSaleApp()
|
public async Task CanCreateReadUpdateAndDeletePointOfSaleApp()
|
||||||
{
|
{
|
||||||
using var tester = CreateServerTester();
|
using var tester = CreateServerTester();
|
||||||
await tester.StartAsync();
|
await tester.StartAsync();
|
||||||
|
@ -203,8 +203,58 @@ namespace BTCPayServer.Tests
|
||||||
await user.RegisterDerivationSchemeAsync("BTC");
|
await user.RegisterDerivationSchemeAsync("BTC");
|
||||||
var client = await user.CreateClient();
|
var client = await user.CreateClient();
|
||||||
|
|
||||||
// Test creating a POS app
|
// Test validation for creating the app
|
||||||
var app = await client.CreatePointOfSaleApp(user.StoreId, new CreatePointOfSaleAppRequest() { AppName = "test app from API" });
|
await AssertValidationError(new[] { "AppName" },
|
||||||
|
async () => await client.CreatePointOfSaleApp(user.StoreId, new CreatePointOfSaleAppRequest() {}));
|
||||||
|
await AssertValidationError(new[] { "AppName" },
|
||||||
|
async () => await client.CreatePointOfSaleApp(
|
||||||
|
user.StoreId,
|
||||||
|
new CreatePointOfSaleAppRequest()
|
||||||
|
{
|
||||||
|
AppName = "this is a really long app name this is a really long app name this is a really long app name",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await AssertValidationError(new[] { "Currency" },
|
||||||
|
async () => await client.CreatePointOfSaleApp(
|
||||||
|
user.StoreId,
|
||||||
|
new CreatePointOfSaleAppRequest()
|
||||||
|
{
|
||||||
|
AppName = "good name",
|
||||||
|
Currency = "fake currency"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await AssertValidationError(new[] { "Template" },
|
||||||
|
async () => await client.CreatePointOfSaleApp(
|
||||||
|
user.StoreId,
|
||||||
|
new CreatePointOfSaleAppRequest()
|
||||||
|
{
|
||||||
|
AppName = "good name",
|
||||||
|
Template = "lol invalid template"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await AssertValidationError(new[] { "AppName", "Currency", "Template" },
|
||||||
|
async () => await client.CreatePointOfSaleApp(
|
||||||
|
user.StoreId,
|
||||||
|
new CreatePointOfSaleAppRequest()
|
||||||
|
{
|
||||||
|
Currency = "fake currency",
|
||||||
|
Template = "lol invalid template"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test creating a POS app successfully
|
||||||
|
var app = await client.CreatePointOfSaleApp(
|
||||||
|
user.StoreId,
|
||||||
|
new CreatePointOfSaleAppRequest()
|
||||||
|
{
|
||||||
|
AppName = "test app from API",
|
||||||
|
Currency = "JPY"
|
||||||
|
}
|
||||||
|
);
|
||||||
Assert.Equal("test app from API", app.Name);
|
Assert.Equal("test app from API", app.Name);
|
||||||
Assert.Equal(user.StoreId, app.StoreId);
|
Assert.Equal(user.StoreId, app.StoreId);
|
||||||
Assert.Equal("PointOfSale", app.AppType);
|
Assert.Equal("PointOfSale", app.AppType);
|
||||||
|
@ -220,6 +270,11 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(app.StoreId, retrievedApp.StoreId);
|
Assert.Equal(app.StoreId, retrievedApp.StoreId);
|
||||||
Assert.Equal(app.AppType, retrievedApp.AppType);
|
Assert.Equal(app.AppType, retrievedApp.AppType);
|
||||||
|
|
||||||
|
// Test that we can update the app data
|
||||||
|
await client.PutPointOfSaleApp(app.Id, new CreatePointOfSaleAppRequest() { AppName = "new app name" });
|
||||||
|
retrievedApp = await client.GetApp(app.Id);
|
||||||
|
Assert.Equal("new app name", retrievedApp.Name);
|
||||||
|
|
||||||
// Make sure we return a 404 if we try to delete an app that doesn't exist
|
// Make sure we return a 404 if we try to delete an app that doesn't exist
|
||||||
await AssertHttpError(404, async () =>
|
await AssertHttpError(404, async () =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,7 @@ using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -22,33 +23,38 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
|
private readonly CurrencyNameTable _currencies;
|
||||||
|
|
||||||
public GreenfieldAppsController(
|
public GreenfieldAppsController(
|
||||||
AppService appService,
|
AppService appService,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider
|
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
|
CurrencyNameTable currencies
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
|
_currencies = currencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("~/api/v1/stores/{storeId}/apps/pos")]
|
[HttpPost("~/api/v1/stores/{storeId}/apps/pos")]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
public async Task<IActionResult> CreatePointOfSaleApp(string storeId, CreatePointOfSaleAppRequest request)
|
public async Task<IActionResult> CreatePointOfSaleApp(string storeId, CreatePointOfSaleAppRequest request)
|
||||||
{
|
{
|
||||||
var validationResult = Validate(request);
|
var store = await _storeRepository.FindStore(storeId);
|
||||||
|
if (store == null)
|
||||||
|
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||||
|
|
||||||
|
// This is not obvious but we must have a non-null currency or else request validation may work incorrectly
|
||||||
|
request.Currency = request.Currency ?? store.GetStoreBlob().DefaultCurrency;
|
||||||
|
|
||||||
|
var validationResult = ValidatePOSAppRequest(request);
|
||||||
if (validationResult != null)
|
if (validationResult != null)
|
||||||
{
|
{
|
||||||
return validationResult;
|
return validationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
var store = await _storeRepository.FindStore(storeId);
|
|
||||||
if (store == null)
|
|
||||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
|
||||||
|
|
||||||
var defaultCurrency = store.GetStoreBlob().DefaultCurrency;
|
|
||||||
var appData = new AppData
|
var appData = new AppData
|
||||||
{
|
{
|
||||||
StoreDataId = storeId,
|
StoreDataId = storeId,
|
||||||
|
@ -56,36 +62,55 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
AppType = AppType.PointOfSale.ToString()
|
AppType = AppType.PointOfSale.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
appData.SetSettings(new PointOfSaleSettings
|
appData.SetSettings(ToPointOfSaleSettings(request));
|
||||||
{
|
|
||||||
Title = request.Title,
|
|
||||||
DefaultView = (Services.Apps.PosViewType)request.DefaultView,
|
|
||||||
ShowCustomAmount = request.ShowCustomAmount,
|
|
||||||
ShowDiscount = request.ShowDiscount,
|
|
||||||
EnableTips = request.EnableTips,
|
|
||||||
Currency = request.Currency ?? defaultCurrency,
|
|
||||||
Template = request.Template,
|
|
||||||
ButtonText = request.FixedAmountPayButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF,
|
|
||||||
CustomButtonText = request.CustomAmountPayButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
|
|
||||||
CustomTipText = request.TipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
|
|
||||||
CustomCSSLink = request.CustomCSSLink,
|
|
||||||
NotificationUrl = request.NotificationUrl,
|
|
||||||
RedirectUrl = request.RedirectUrl,
|
|
||||||
Description = request.Description,
|
|
||||||
EmbeddedCSS = request.EmbeddedCSS,
|
|
||||||
RedirectAutomatically = request.RedirectAutomatically,
|
|
||||||
RequiresRefundEmail = request.RequiresRefundEmail == true ?
|
|
||||||
RequiresRefundEmail.On :
|
|
||||||
request.RequiresRefundEmail == false ?
|
|
||||||
RequiresRefundEmail.Off :
|
|
||||||
RequiresRefundEmail.InheritFromStore,
|
|
||||||
});
|
|
||||||
|
|
||||||
await _appService.UpdateOrCreateApp(appData);
|
await _appService.UpdateOrCreateApp(appData);
|
||||||
|
|
||||||
return Ok(ToModel(appData));
|
return Ok(ToModel(appData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPut("~/api/v1/apps/pos/{appId}")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
public async Task<IActionResult> PutPointOfSaleApp(string appId, CreatePointOfSaleAppRequest request)
|
||||||
|
{
|
||||||
|
var app = await _appService.GetApp(appId, AppType.PointOfSale);
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
return AppNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||||
|
|
||||||
|
// This is not obvious but we must have a non-null currency or else request validation may work incorrectly
|
||||||
|
request.Currency = request.Currency ?? settings.Currency;
|
||||||
|
|
||||||
|
var validationResult = ValidatePOSAppRequest(request);
|
||||||
|
if (validationResult != null)
|
||||||
|
{
|
||||||
|
return validationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Name = request.AppName;
|
||||||
|
app.SetSettings(ToPointOfSaleSettings(request));
|
||||||
|
|
||||||
|
await _appService.UpdateOrCreateApp(app);
|
||||||
|
|
||||||
|
return Ok(ToModel(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequiresRefundEmail? BoolToRequiresRefundEmail(bool? requiresRefundEmail)
|
||||||
|
{
|
||||||
|
switch (requiresRefundEmail)
|
||||||
|
{
|
||||||
|
case true:
|
||||||
|
return RequiresRefundEmail.On;
|
||||||
|
case false:
|
||||||
|
return RequiresRefundEmail.Off;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("~/api/v1/apps/{appId}")]
|
[HttpGet("~/api/v1/apps/{appId}")]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
public async Task<IActionResult> GetApp(string appId)
|
public async Task<IActionResult> GetApp(string appId)
|
||||||
|
@ -118,19 +143,73 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return this.CreateAPIError(404, "app-not-found", "The app with specified ID was not found");
|
return this.CreateAPIError(404, "app-not-found", "The app with specified ID was not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PointOfSaleSettings ToPointOfSaleSettings(CreatePointOfSaleAppRequest request)
|
||||||
|
{
|
||||||
|
return new PointOfSaleSettings()
|
||||||
|
{
|
||||||
|
Title = request.Title,
|
||||||
|
DefaultView = (Services.Apps.PosViewType)request.DefaultView,
|
||||||
|
ShowCustomAmount = request.ShowCustomAmount,
|
||||||
|
ShowDiscount = request.ShowDiscount,
|
||||||
|
EnableTips = request.EnableTips,
|
||||||
|
Currency = request.Currency,
|
||||||
|
Template = request.Template != null ? _appService.SerializeTemplate(_appService.Parse(request.Template, request.Currency)) : null,
|
||||||
|
ButtonText = request.FixedAmountPayButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF,
|
||||||
|
CustomButtonText = request.CustomAmountPayButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
|
||||||
|
CustomTipText = request.TipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
|
||||||
|
CustomCSSLink = request.CustomCSSLink,
|
||||||
|
NotificationUrl = request.NotificationUrl,
|
||||||
|
RedirectUrl = request.RedirectUrl,
|
||||||
|
Description = request.Description,
|
||||||
|
EmbeddedCSS = request.EmbeddedCSS,
|
||||||
|
RedirectAutomatically = request.RedirectAutomatically,
|
||||||
|
RequiresRefundEmail = BoolToRequiresRefundEmail(request.RequiresRefundEmail) ?? RequiresRefundEmail.InheritFromStore,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private PointOfSaleAppData ToModel(AppData appData)
|
private PointOfSaleAppData ToModel(AppData appData)
|
||||||
{
|
{
|
||||||
|
var settings = appData.GetSettings<PointOfSaleSettings>();
|
||||||
|
|
||||||
return new PointOfSaleAppData
|
return new PointOfSaleAppData
|
||||||
{
|
{
|
||||||
Id = appData.Id,
|
Id = appData.Id,
|
||||||
AppType = appData.AppType,
|
AppType = appData.AppType,
|
||||||
Name = appData.Name,
|
Name = appData.Name,
|
||||||
StoreId = appData.StoreDataId,
|
StoreId = appData.StoreDataId,
|
||||||
Created = appData.Created
|
Created = appData.Created,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private IActionResult? Validate(CreateAppRequest request)
|
private IActionResult? ValidatePOSAppRequest(CreatePointOfSaleAppRequest request)
|
||||||
|
{
|
||||||
|
var validationResult = ValidateCreateAppRequest(request);
|
||||||
|
if (request.Currency != null && _currencies.GetCurrencyData(request.Currency, false) == null)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(request.Currency), "Invalid currency");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Template != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_appService.SerializeTemplate(_appService.Parse(request.Template, request.Currency));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(request.Template), "Invalid template");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
validationResult = this.CreateValidationError(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IActionResult? ValidateCreateAppRequest(CreateAppRequest request)
|
||||||
{
|
{
|
||||||
if (request is null)
|
if (request is null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1103,6 +1103,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
await GetController<GreenfieldAppsController>().CreatePointOfSaleApp(storeId, request));
|
await GetController<GreenfieldAppsController>().CreatePointOfSaleApp(storeId, request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<PointOfSaleAppData> PutPointOfSaleApp(
|
||||||
|
string appId,
|
||||||
|
CreatePointOfSaleAppRequest request, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return GetFromActionResult<PointOfSaleAppData>(
|
||||||
|
await GetController<GreenfieldAppsController>().PutPointOfSaleApp(appId, request));
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<AppDataBase> GetApp(string appId, CancellationToken token = default)
|
public override async Task<AppDataBase> GetApp(string appId, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return GetFromActionResult<AppDataBase>(
|
return GetFromActionResult<AppDataBase>(
|
||||||
|
|
|
@ -17,128 +17,83 @@
|
||||||
"summary": "Create a new Point of Sale app",
|
"summary": "Create a new Point of Sale app",
|
||||||
"description": "Point of Sale apps allows accepting payments for items in a virtual store",
|
"description": "Point of Sale apps allows accepting payments for items in a virtual store",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
"x-name": "request",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/components/schemas/CreatePointOfSaleAppRequest"
|
||||||
"properties": {
|
}
|
||||||
"appName": {
|
}
|
||||||
"type": "string",
|
},
|
||||||
"description": "The name of the app (shown in admin UI)",
|
"required": true,
|
||||||
"nullable": false
|
"x-position": 1
|
||||||
},
|
},
|
||||||
"title": {
|
"responses": {
|
||||||
"type": "string",
|
"200": {
|
||||||
"description": "The title of the app (shown to the user)",
|
"description": "Created app details",
|
||||||
"nullable": true
|
"content": {
|
||||||
},
|
"application/json": {
|
||||||
"description": {
|
"schema": {
|
||||||
"type": "string",
|
"$ref": "#/components/schemas/PointOfSaleAppData"
|
||||||
"description": "The description of the app",
|
}
|
||||||
"nullable": true
|
}
|
||||||
},
|
}
|
||||||
"template": {
|
},
|
||||||
"type": "string",
|
"422": {
|
||||||
"description": "Template for items available in the app",
|
"description": "Unable to validate the request",
|
||||||
"nullable": true
|
"content": {
|
||||||
},
|
"application/json": {
|
||||||
"defaultView": {
|
"schema": {
|
||||||
"type": "string",
|
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||||
"description": "Template for items available in the app",
|
|
||||||
"nullable": true,
|
|
||||||
"x-enumNames": [
|
|
||||||
"Static",
|
|
||||||
"Cart",
|
|
||||||
"Light",
|
|
||||||
"Print"
|
|
||||||
],
|
|
||||||
"enum": [
|
|
||||||
"Static",
|
|
||||||
"Cart",
|
|
||||||
"Light",
|
|
||||||
"Print"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Currency to use for the app. Defaults to the currency used by the store if not specified",
|
|
||||||
"example": "BTC",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"showCustomAmount": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether to include a special item in the store which allows user to input a custom payment amount",
|
|
||||||
"default": true,
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"showDiscount": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether to allow user to input a discount amount. Applies to Cart view only. Not recommended for customer self-checkout",
|
|
||||||
"default": true,
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"enableTips": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether to allow user to input a tip amount. Applies to Cart and Light views only",
|
|
||||||
"default": true,
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"customAmountPayButtonText": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Payment button text which appears for items which allow user to input a custom amount",
|
|
||||||
"default": "Pay",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"fixedAmountPayButtonText": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Payment button text which appears for items which have a fixed price",
|
|
||||||
"default": "Buy for {PRICE_HERE}",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"tipText": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Prompt which appears next to the tip amount field if tipping is enabled",
|
|
||||||
"default": "Do you want to leave a tip?",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"customCSSLink": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Link to a custom CSS stylesheet to be used in the app",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"embeddedCSS": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Custom CSS to embed into the app",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"notificationUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Callback notification url to POST to once when invoice is paid for and once when there are enough blockchain confirmations",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"redirectUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "URL to redirect user to once invoice is paid",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"redirectAutomatically": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether to redirect user to redirect URL automatically once invoice is paid. Defaults to what is set in the store settings",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"requiresRefundEmail": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether to redirect user to redirect URL automatically once invoice is paid. Defaults to what is set in the store settings",
|
|
||||||
"nullable": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tags": [
|
||||||
|
"Apps"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.store.canmodifystoresettings"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/apps/pos/{appId}": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "appId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "App ID",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"patch": {
|
||||||
|
"operationId": "Apps_PatchPointOfSaleApp",
|
||||||
|
"summary": "Update a Point of Sale app",
|
||||||
|
"description": "Use this endpoint for updating the properties of a POS app",
|
||||||
|
"requestBody": {
|
||||||
|
"x-name": "request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePointOfSaleAppRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"x-position": 1
|
||||||
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Created app details",
|
"description": "App details",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -291,6 +246,120 @@
|
||||||
"description": "Type of the app which was created"
|
"description": "Type of the app which was created"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"CreatePointOfSaleAppRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"appName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the app (shown in admin UI)",
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The title of the app (shown to the user)",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The description of the app",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Template for items available in the app",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"defaultView": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Template for items available in the app",
|
||||||
|
"nullable": true,
|
||||||
|
"x-enumNames": [
|
||||||
|
"Static",
|
||||||
|
"Cart",
|
||||||
|
"Light",
|
||||||
|
"Print"
|
||||||
|
],
|
||||||
|
"enum": [
|
||||||
|
"Static",
|
||||||
|
"Cart",
|
||||||
|
"Light",
|
||||||
|
"Print"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Currency to use for the app. Defaults to the currency used by the store if not specified",
|
||||||
|
"example": "BTC",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"showCustomAmount": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to include a special item in the store which allows user to input a custom payment amount",
|
||||||
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"showDiscount": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to allow user to input a discount amount. Applies to Cart view only. Not recommended for customer self-checkout",
|
||||||
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"enableTips": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to allow user to input a tip amount. Applies to Cart and Light views only",
|
||||||
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"customAmountPayButtonText": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Payment button text which appears for items which allow user to input a custom amount",
|
||||||
|
"default": "Pay",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"fixedAmountPayButtonText": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Payment button text which appears for items which have a fixed price",
|
||||||
|
"default": "Buy for {PRICE_HERE}",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"tipText": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Prompt which appears next to the tip amount field if tipping is enabled",
|
||||||
|
"default": "Do you want to leave a tip?",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"customCSSLink": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Link to a custom CSS stylesheet to be used in the app",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"embeddedCSS": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Custom CSS to embed into the app",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"notificationUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Callback notification url to POST to once when invoice is paid for and once when there are enough blockchain confirmations",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"redirectUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL to redirect user to once invoice is paid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"redirectAutomatically": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to redirect user to redirect URL automatically once invoice is paid. Defaults to what is set in the store settings",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"requiresRefundEmail": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to redirect user to redirect URL automatically once invoice is paid. Defaults to what is set in the store settings",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue