mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-01-04 14:44:30 +01:00
If pull payment opened in mobile, use deeplink to setup card (#5613)
* If pull payment opened in mobile, use deeplink to setup card * Allow passing LNURLW to register boltcard * debug * debug * debug * Only show setup/reset when the page is fully loaded * Apply suggestions from code review --------- Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Text;
|
|||||||
using NBitcoin.JsonConverters;
|
using NBitcoin.JsonConverters;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Client.Models
|
namespace BTCPayServer.Client.Models
|
||||||
{
|
{
|
||||||
@@ -14,11 +15,15 @@ namespace BTCPayServer.Client.Models
|
|||||||
}
|
}
|
||||||
public class RegisterBoltcardRequest
|
public class RegisterBoltcardRequest
|
||||||
{
|
{
|
||||||
|
[JsonProperty("LNURLW")]
|
||||||
|
public string LNURLW { get; set; }
|
||||||
[JsonConverter(typeof(HexJsonConverter))]
|
[JsonConverter(typeof(HexJsonConverter))]
|
||||||
[JsonProperty("UID")]
|
[JsonProperty("UID")]
|
||||||
public byte[] UID { get; set; }
|
public byte[] UID { get; set; }
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public OnExistingBehavior? OnExisting { get; set; }
|
public OnExistingBehavior? OnExisting { get; set; }
|
||||||
|
[JsonExtensionData]
|
||||||
|
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
|
||||||
}
|
}
|
||||||
public class RegisterBoltcardResponse
|
public class RegisterBoltcardResponse
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using BTCPayServer.Controllers;
|
|||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Models.InvoicingModels;
|
using BTCPayServer.Models.InvoicingModels;
|
||||||
|
using BTCPayServer.NTag424;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.PayoutProcessors;
|
using BTCPayServer.PayoutProcessors;
|
||||||
@@ -24,6 +25,7 @@ using BTCPayServer.Services.Stores;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@@ -1135,6 +1137,35 @@ namespace BTCPayServer.Tests
|
|||||||
OnExisting = OnExistingBehavior.KeepVersion
|
OnExisting = OnExistingBehavior.KeepVersion
|
||||||
});
|
});
|
||||||
Assert.Equal(card2.Version, card3.Version);
|
Assert.Equal(card2.Version, card3.Version);
|
||||||
|
var p = new byte[] { 0xc7 }.Concat(uid).Concat(new byte[8]).ToArray();
|
||||||
|
var card4 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||||
|
{
|
||||||
|
OnExisting = OnExistingBehavior.KeepVersion,
|
||||||
|
LNURLW = card2.LNURLW + $"?p={Encoders.Hex.EncodeData(AESKey.Parse(card2.K1).Encrypt(p))}"
|
||||||
|
});
|
||||||
|
Assert.Equal(card2.Version, card4.Version);
|
||||||
|
Assert.Equal(card2.K4, card4.K4);
|
||||||
|
// Can't define both properties
|
||||||
|
await AssertValidationError(["LNURLW"], () => client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||||
|
{
|
||||||
|
OnExisting = OnExistingBehavior.KeepVersion,
|
||||||
|
UID = uid,
|
||||||
|
LNURLW = card2.LNURLW + $"?p={Encoders.Hex.EncodeData(AESKey.Parse(card2.K1).Encrypt(p))}"
|
||||||
|
}));
|
||||||
|
// p is malformed
|
||||||
|
await AssertValidationError(["LNURLW"], () => client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||||
|
{
|
||||||
|
OnExisting = OnExistingBehavior.KeepVersion,
|
||||||
|
UID = uid,
|
||||||
|
LNURLW = card2.LNURLW + $"?p=lol"
|
||||||
|
}));
|
||||||
|
// p is invalid
|
||||||
|
p[0] = 0;
|
||||||
|
await AssertValidationError(["LNURLW"], () => client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
|
||||||
|
{
|
||||||
|
OnExisting = OnExistingBehavior.KeepVersion,
|
||||||
|
LNURLW = card2.LNURLW + $"?p={Encoders.Hex.EncodeData(AESKey.Parse(card2.K1).Encrypt(p))}"
|
||||||
|
}));
|
||||||
// Test with SATS denomination values
|
// Test with SATS denomination values
|
||||||
var testSats = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
|
var testSats = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO.IsolatedStorage;
|
using System.IO.IsolatedStorage;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -12,6 +14,7 @@ using BTCPayServer.Client;
|
|||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
|
using BTCPayServer.Logging;
|
||||||
using BTCPayServer.NTag424;
|
using BTCPayServer.NTag424;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
@@ -22,8 +25,11 @@ using Microsoft.AspNetCore.Cors;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Org.BouncyCastle.Bcpg.OpenPgp;
|
||||||
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.Greenfield
|
namespace BTCPayServer.Controllers.Greenfield
|
||||||
@@ -43,6 +49,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly SettingsRepository _settingsRepository;
|
private readonly SettingsRepository _settingsRepository;
|
||||||
private readonly BTCPayServerEnvironment _env;
|
private readonly BTCPayServerEnvironment _env;
|
||||||
|
private readonly Logs _logs;
|
||||||
|
|
||||||
public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService,
|
public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService,
|
||||||
LinkGenerator linkGenerator,
|
LinkGenerator linkGenerator,
|
||||||
@@ -53,7 +60,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
SettingsRepository settingsRepository,
|
SettingsRepository settingsRepository,
|
||||||
BTCPayServerEnvironment env)
|
BTCPayServerEnvironment env, Logs logs)
|
||||||
{
|
{
|
||||||
_pullPaymentService = pullPaymentService;
|
_pullPaymentService = pullPaymentService;
|
||||||
_linkGenerator = linkGenerator;
|
_linkGenerator = linkGenerator;
|
||||||
@@ -65,6 +72,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_settingsRepository = settingsRepository;
|
_settingsRepository = settingsRepository;
|
||||||
_env = env;
|
_env = env;
|
||||||
|
_logs = logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("~/api/v1/stores/{storeId}/pull-payments")]
|
[HttpGet("~/api/v1/stores/{storeId}/pull-payments")]
|
||||||
@@ -200,13 +208,39 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("~/api/v1/pull-payments/{pullPaymentId}/boltcards")]
|
[Route("~/api/v1/pull-payments/{pullPaymentId}/boltcards")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> RegisterBoltcard(string pullPaymentId, RegisterBoltcardRequest request)
|
public async Task<IActionResult> RegisterBoltcard(string pullPaymentId, RegisterBoltcardRequest request, string? onExisting = null)
|
||||||
{
|
{
|
||||||
if (pullPaymentId is null)
|
if (pullPaymentId is null)
|
||||||
return PullPaymentNotFound();
|
return PullPaymentNotFound();
|
||||||
|
this._logs.PayServer.LogInformation($"RegisterBoltcard: onExisting queryParam: {onExisting}");
|
||||||
|
this._logs.PayServer.LogInformation($"{JsonConvert.SerializeObject(request)}");
|
||||||
var pp = await _pullPaymentService.GetPullPayment(pullPaymentId, false);
|
var pp = await _pullPaymentService.GetPullPayment(pullPaymentId, false);
|
||||||
if (pp is null)
|
if (pp is null)
|
||||||
return PullPaymentNotFound();
|
return PullPaymentNotFound();
|
||||||
|
var issuerKey = await _settingsRepository.GetIssuerKey(_env);
|
||||||
|
|
||||||
|
// LNURLW is used by deeplinks
|
||||||
|
if (request?.LNURLW is not null)
|
||||||
|
{
|
||||||
|
if (request.UID is not null)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(request.LNURLW), "You should pass either LNURLW or UID but not both");
|
||||||
|
return this.CreateValidationError(ModelState);
|
||||||
|
}
|
||||||
|
var p = ExtractP(request.LNURLW);
|
||||||
|
if (p is null)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(request.LNURLW), "The LNURLW should contains a 'p=' parameter");
|
||||||
|
return this.CreateValidationError(ModelState);
|
||||||
|
}
|
||||||
|
if (issuerKey.TryDecrypt(p) is not BoltcardPICCData picc)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(request.LNURLW), "The LNURLW 'p=' parameter cannot be decrypted");
|
||||||
|
return this.CreateValidationError(ModelState);
|
||||||
|
}
|
||||||
|
request.UID = picc.Uid;
|
||||||
|
}
|
||||||
|
|
||||||
if (request?.UID is null || request.UID.Length != 7)
|
if (request?.UID is null || request.UID.Length != 7)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(request.UID), "The UID is required and should be 7 bytes");
|
ModelState.AddModelError(nameof(request.UID), "The UID is required and should be 7 bytes");
|
||||||
@@ -217,15 +251,28 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
return this.CreateAPIError(400, "lnurl-not-supported", "This pull payment currency should be BTC or SATS and accept lightning");
|
return this.CreateAPIError(400, "lnurl-not-supported", "This pull payment currency should be BTC or SATS and accept lightning");
|
||||||
}
|
}
|
||||||
|
|
||||||
var issuerKey = await _settingsRepository.GetIssuerKey(_env);
|
// Passing onExisting as a query parameter is used by deeplink
|
||||||
|
request.OnExisting = onExisting switch
|
||||||
|
{
|
||||||
|
nameof(OnExistingBehavior.UpdateVersion) => OnExistingBehavior.UpdateVersion,
|
||||||
|
nameof(OnExistingBehavior.KeepVersion) => OnExistingBehavior.KeepVersion,
|
||||||
|
_ => request.OnExisting
|
||||||
|
};
|
||||||
|
|
||||||
|
this._logs.PayServer.LogInformation($"After");
|
||||||
|
this._logs.PayServer.LogInformation($"{JsonConvert.SerializeObject(request)}");
|
||||||
|
|
||||||
var version = await _dbContextFactory.LinkBoltcardToPullPayment(pullPaymentId, issuerKey, request.UID, request.OnExisting);
|
var version = await _dbContextFactory.LinkBoltcardToPullPayment(pullPaymentId, issuerKey, request.UID, request.OnExisting);
|
||||||
|
this._logs.PayServer.LogInformation($"Version: " + version);
|
||||||
|
this._logs.PayServer.LogInformation($"ID: " + Encoders.Hex.EncodeData(issuerKey.GetId(request.UID)));
|
||||||
|
|
||||||
var keys = issuerKey.CreatePullPaymentCardKey(request.UID, version, pullPaymentId).DeriveBoltcardKeys(issuerKey);
|
var keys = issuerKey.CreatePullPaymentCardKey(request.UID, version, pullPaymentId).DeriveBoltcardKeys(issuerKey);
|
||||||
|
|
||||||
var boltcardUrl = Url.Action(nameof(UIBoltcardController.GetWithdrawRequest), "UIBoltcard");
|
var boltcardUrl = Url.Action(nameof(UIBoltcardController.GetWithdrawRequest), "UIBoltcard");
|
||||||
boltcardUrl = Request.GetAbsoluteUri(boltcardUrl);
|
boltcardUrl = Request.GetAbsoluteUri(boltcardUrl);
|
||||||
boltcardUrl = Regex.Replace(boltcardUrl, "^https?://", "lnurlw://");
|
boltcardUrl = Regex.Replace(boltcardUrl, "^https?://", "lnurlw://");
|
||||||
|
|
||||||
return Ok(new RegisterBoltcardResponse()
|
var resp = new RegisterBoltcardResponse()
|
||||||
{
|
{
|
||||||
LNURLW = boltcardUrl,
|
LNURLW = boltcardUrl,
|
||||||
Version = version,
|
Version = version,
|
||||||
@@ -234,7 +281,25 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
K2 = Encoders.Hex.EncodeData(keys.AuthenticationKey.ToBytes()).ToUpperInvariant(),
|
K2 = Encoders.Hex.EncodeData(keys.AuthenticationKey.ToBytes()).ToUpperInvariant(),
|
||||||
K3 = Encoders.Hex.EncodeData(keys.K3.ToBytes()).ToUpperInvariant(),
|
K3 = Encoders.Hex.EncodeData(keys.K3.ToBytes()).ToUpperInvariant(),
|
||||||
K4 = Encoders.Hex.EncodeData(keys.K4.ToBytes()).ToUpperInvariant(),
|
K4 = Encoders.Hex.EncodeData(keys.K4.ToBytes()).ToUpperInvariant(),
|
||||||
});
|
};
|
||||||
|
this._logs.PayServer.LogInformation($"Response");
|
||||||
|
this._logs.PayServer.LogInformation($"{JsonConvert.SerializeObject(resp)}");
|
||||||
|
|
||||||
|
return Ok(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ExtractP(string? url)
|
||||||
|
{
|
||||||
|
if (url is null || !Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
||||||
|
return null;
|
||||||
|
int num = uri.AbsoluteUri.IndexOf('?');
|
||||||
|
if (num == -1)
|
||||||
|
return null;
|
||||||
|
string input = uri.AbsoluteUri.Substring(num);
|
||||||
|
Match match = Regex.Match(input, "p=([a-f0-9A-F]{32})");
|
||||||
|
if (!match.Success)
|
||||||
|
return null;
|
||||||
|
return match.Groups[1].Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("~/api/v1/pull-payments/{pullPaymentId}")]
|
[HttpGet("~/api/v1/pull-payments/{pullPaymentId}")]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using BTCPayServer.Abstractions.Extensions;
|
|||||||
using BTCPayServer.Abstractions.Models;
|
using BTCPayServer.Abstractions.Models;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Controllers.Greenfield;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
@@ -127,13 +128,27 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
if (_pullPaymentHostedService.SupportsLNURL(blob))
|
if (_pullPaymentHostedService.SupportsLNURL(blob))
|
||||||
{
|
{
|
||||||
var url = Url.Action("GetLNURLForPullPayment", "UILNURL", new { cryptoCode = _networkProvider.DefaultNetwork.CryptoCode, pullPaymentId = vm.Id }, Request.Scheme, Request.Host.ToString());
|
var url = Url.Action(nameof(UILNURLController.GetLNURLForPullPayment), "UILNURL", new { cryptoCode = _networkProvider.DefaultNetwork.CryptoCode, pullPaymentId = vm.Id }, Request.Scheme, Request.Host.ToString());
|
||||||
vm.LnurlEndpoint = url != null ? new Uri(url) : null;
|
vm.LnurlEndpoint = url != null ? new Uri(url) : null;
|
||||||
|
vm.SetupDeepLink = $"boltcard://program?url={GetBoltcardDeeplinkUrl(vm, OnExistingBehavior.UpdateVersion)}";
|
||||||
|
vm.ResetDeepLink = $"boltcard://reset?url={GetBoltcardDeeplinkUrl(vm, OnExistingBehavior.KeepVersion)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return View(nameof(ViewPullPayment), vm);
|
return View(nameof(ViewPullPayment), vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetBoltcardDeeplinkUrl(ViewPullPaymentModel vm, OnExistingBehavior onExisting)
|
||||||
|
{
|
||||||
|
var registerUrl = Url.Action(nameof(GreenfieldPullPaymentController.RegisterBoltcard), "GreenfieldPullPayment",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
pullPaymentId = vm.Id,
|
||||||
|
onExisting = onExisting.ToString()
|
||||||
|
}, Request.Scheme, Request.Host.ToString());
|
||||||
|
registerUrl = Uri.EscapeDataString(registerUrl);
|
||||||
|
return registerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("stores/{storeId}/pull-payments/edit/{pullPaymentId}")]
|
[HttpGet("stores/{storeId}/pull-payments/edit/{pullPaymentId}")]
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public async Task<IActionResult> EditPullPayment(string storeId, string pullPaymentId)
|
public async Task<IActionResult> EditPullPayment(string storeId, string pullPaymentId)
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ namespace BTCPayServer.Models
|
|||||||
|
|
||||||
public PaymentMethodId[] PaymentMethods { get; set; }
|
public PaymentMethodId[] PaymentMethods { get; set; }
|
||||||
|
|
||||||
|
public string SetupDeepLink { get; set; }
|
||||||
|
public string ResetDeepLink { get; set; }
|
||||||
|
|
||||||
public string HubPath { get; set; }
|
public string HubPath { get; set; }
|
||||||
public string ResetIn { get; set; }
|
public string ResetIn { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
|||||||
@@ -202,15 +202,15 @@
|
|||||||
</p>
|
</p>
|
||||||
@if (Model.LnurlEndpoint is not null)
|
@if (Model.LnurlEndpoint is not null)
|
||||||
{
|
{
|
||||||
<p>
|
<p id="BoltcardActions" style="visibility:hidden">
|
||||||
<a asp-action="SetupBoltcard" asp-controller="UIPullPayment" asp-route-pullPaymentId="@Model.Id" asp-route-command="configure-boltcard">
|
<a id="SetupBoltcard" asp-action="SetupBoltcard" asp-controller="UIPullPayment" asp-route-pullPaymentId="@Model.Id" asp-route-command="configure-boltcard">
|
||||||
Setup Boltcard
|
Setup Boltcard
|
||||||
</a>
|
</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a asp-action="SetupBoltcard" asp-controller="UIPullPayment" asp-route-pullPaymentId="@Model.Id" asp-route-command="reset-boltcard">
|
<a id="ResetBoltcard" asp-action="SetupBoltcard" asp-controller="UIPullPayment" asp-route-pullPaymentId="@Model.Id" asp-route-command="reset-boltcard">
|
||||||
Reset Boltcard
|
Reset Boltcard
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
|
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
|
||||||
Powered by <partial name="_StoreFooterLogo" />
|
Powered by <partial name="_StoreFooterLogo" />
|
||||||
@@ -226,6 +226,15 @@
|
|||||||
<script src="~/vendor/ur-registry/urlib.min.js" asp-append-version="true"></script>
|
<script src="~/vendor/ur-registry/urlib.min.js" asp-append-version="true"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const isAndroid = /(android)/i.test(navigator.userAgent);
|
||||||
|
if (isAndroid) {
|
||||||
|
document.getElementById("SetupBoltcard").setAttribute('target', '_blank');
|
||||||
|
document.getElementById("SetupBoltcard").setAttribute('href', @Safe.Json(@Model.SetupDeepLink));
|
||||||
|
document.getElementById("ResetBoltcard").setAttribute('target', '_blank');
|
||||||
|
document.getElementById("ResetBoltcard").setAttribute('href', @Safe.Json(@Model.ResetDeepLink));
|
||||||
|
}
|
||||||
|
document.getElementById("BoltcardActions").style.visibility = "visible";
|
||||||
|
|
||||||
window.qrApp = initQRShow({});
|
window.qrApp = initQRShow({});
|
||||||
delegate('click', 'button[page-qr]', event => {
|
delegate('click', 'button[page-qr]', event => {
|
||||||
qrApp.title = "Pull Payment QR";
|
qrApp.title = "Pull Payment QR";
|
||||||
|
|||||||
Reference in New Issue
Block a user