mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Scan address/bip21 with camera
This commit is contained in:
@@ -120,6 +120,7 @@
|
|||||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||||
<Folder Include="wwwroot\vendor\summernote" />
|
<Folder Include="wwwroot\vendor\summernote" />
|
||||||
<Folder Include="wwwroot\vendor\u2f" />
|
<Folder Include="wwwroot\vendor\u2f" />
|
||||||
|
<Folder Include="wwwroot\vendor\vue-qrcode-reader" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -662,13 +662,26 @@ namespace BTCPayServer.Controllers
|
|||||||
uriBuilder.UnknowParameters.TryGetValue("bpu", out var vmPayJoinEndpointUrl);
|
uriBuilder.UnknowParameters.TryGetValue("bpu", out var vmPayJoinEndpointUrl);
|
||||||
vm.PayJoinEndpointUrl = vmPayJoinEndpointUrl;
|
vm.PayJoinEndpointUrl = vmPayJoinEndpointUrl;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch
|
||||||
{
|
{
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
try
|
||||||
{
|
{
|
||||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
vm.Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||||
Message = "The provided BIP21 payment URI was malformed"
|
{
|
||||||
});
|
new WalletSendModel.TransactionOutput()
|
||||||
|
{
|
||||||
|
DestinationAddress = BitcoinAddress.Create(bip21, network.NBitcoinNetwork).ToString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
|
{
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||||
|
Message = "The provided BIP21 payment URI was malformed"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelState.Clear();
|
ModelState.Clear();
|
||||||
|
|||||||
50
BTCPayServer/Views/Wallets/WalletCameraScanner.cshtml
Normal file
50
BTCPayServer/Views/Wallets/WalletCameraScanner.cshtml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
@model WalletSendModel
|
||||||
|
|
||||||
|
<link href="~/vendor/vue-qrcode-reader/vue-qrcode-reader.css" rel="stylesheet"/>
|
||||||
|
<div id="wallet-camera-app" v-cloak class="only-for-js">
|
||||||
|
<div class="modal fade" data-backdrop="static" id="scanModal">
|
||||||
|
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close" v-on:click="close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-0" v-if="loaded">
|
||||||
|
<div class="text-danger p-2" v-if="errorMessage">{{errorMessage}}</div>
|
||||||
|
<qrcode-drop-zone v-on:decode="onDecode" v-on:init="logErrors">
|
||||||
|
<qrcode-stream v-on:decode="onDecode" v-on:init="onInit" v-bind:camera="camera" v-bind:track="paint">
|
||||||
|
<div v-if="data" class="pending-action">
|
||||||
|
<span class="text-muted">{{data}}</span>
|
||||||
|
<div class="w-100 btn-group">
|
||||||
|
<button type="button" class="btn btn-primary" data-dismiss="modal" v-on:click="submitData">Submit</button>
|
||||||
|
<button type="button" class="btn btn-secondary" v-on:click="retry">Retry</button>
|
||||||
|
<button type="button" class="btn btn-danger" data-dismiss="modal" v-on:click="close">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</qrcode-stream>
|
||||||
|
</qrcode-drop-zone>
|
||||||
|
<qrcode-capture v-if="noStreamApiSupport" v-on:decode="onDecode" v-bind:camera="camera"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.pending-action {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
background-color: rgba(255, 255, 255, .8);
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@using Microsoft.AspNetCore.Mvc.ModelBinding
|
@addTagHelper *, BundlerMinifier.TagHelpers
|
||||||
|
@using Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
@model WalletSendModel
|
@model WalletSendModel
|
||||||
@{
|
@{
|
||||||
Layout = "../Shared/_NavLayout.cshtml";
|
Layout = "../Shared/_NavLayout.cshtml";
|
||||||
@@ -13,7 +14,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<partial name="WalletCameraScanner"/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="@(!Model.InputSelection && Model.Outputs.Count==1? "col-lg-6 transaction-output-form": "col-lg-8")">
|
<div class="@(!Model.InputSelection && Model.Outputs.Count==1? "col-lg-6 transaction-output-form": "col-lg-8")">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="hidden" asp-for="InputSelection" />
|
<input type="hidden" asp-for="InputSelection" />
|
||||||
@@ -198,16 +201,17 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" name="command" value="add-output" class="ml-1 btn btn-secondary">Add another destination </button>
|
<button type="submit" name="command" value="add-output" class="ml-1 btn btn-secondary" title="Add another destination"><i class="fa fa-plus"></i></button>
|
||||||
<button type="button" id="bip21parse" class="ml-1 btn btn-secondary">Parse BIP21</button>
|
<button type="button" id="bip21parse" class="ml-1 btn btn-secondary" title="Parse BIP21"><i class="fa fa-paste"></i></button>
|
||||||
|
<button type="button" id="scanqrcode" class="ml-1 btn btn-secondary only-for-js" data-toggle="modal" data-target="#scanModal" title="Scan BIP21/Address with camera"><i class="fa fa-camera"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@section Scripts
|
@section HeadScripts
|
||||||
{
|
{
|
||||||
<script src="~/js/WalletSend.js" type="text/javascript" defer="defer"></script>
|
<bundle name="wwwroot/bundles/wallet-send-bundle.min.js"></bundle>
|
||||||
<style>
|
<style>
|
||||||
.remove-destination-btn{
|
.remove-destination-btn{
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
|||||||
@@ -181,12 +181,15 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"outputFileName": "wwwroot/bundles/wallet-coin-selection-bundle.min.js",
|
"outputFileName": "wwwroot/bundles/wallet-send-bundle.min.js",
|
||||||
"inputFiles": [
|
"inputFiles": [
|
||||||
"wwwroot/vendor/vuejs/vue.min.js",
|
"wwwroot/vendor/vuejs/vue.min.js",
|
||||||
"wwwroot/vendor/babel-polyfill/polyfill.min.js",
|
"wwwroot/vendor/babel-polyfill/polyfill.min.js",
|
||||||
]
|
"wwwroot/vendor/vue-qrcode-reader/vue-qrcode-reader.browser.js",
|
||||||
}
|
"wwwroot/js/wallet/**/*.js"
|
||||||
|
],
|
||||||
|
"minify": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
77
BTCPayServer/wwwroot/js/wallet/wallet-camera-scanner.js
Normal file
77
BTCPayServer/wwwroot/js/wallet/wallet-camera-scanner.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
$(function () {
|
||||||
|
new Vue({
|
||||||
|
el: '#wallet-camera-app',
|
||||||
|
data: {
|
||||||
|
noStreamApiSupport: false,
|
||||||
|
loaded: false,
|
||||||
|
data: "",
|
||||||
|
errorMessage: ""
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
var self = this;
|
||||||
|
$("#scanqrcode").click(function () {
|
||||||
|
self.loaded = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
camera: function () {
|
||||||
|
return this.data ? "off" : "auto";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
retry: function () {
|
||||||
|
this.data = "";
|
||||||
|
},
|
||||||
|
close: function () {
|
||||||
|
this.loaded = false;
|
||||||
|
this.data = "";
|
||||||
|
this.errorMessage = "";
|
||||||
|
},
|
||||||
|
onDecode(content) {
|
||||||
|
this.data = decodeURIComponent(content);
|
||||||
|
|
||||||
|
},
|
||||||
|
submitData: function () {
|
||||||
|
$("#BIP21").val(this.data);
|
||||||
|
$("form").submit();
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
logErrors: function (promise) {
|
||||||
|
promise.catch(console.error)
|
||||||
|
},
|
||||||
|
paint: function (location, ctx) {
|
||||||
|
ctx.fillStyle = '#137547';
|
||||||
|
[
|
||||||
|
location.topLeftFinderPattern,
|
||||||
|
location.topRightFinderPattern,
|
||||||
|
location.bottomLeftFinderPattern
|
||||||
|
].forEach(({x, y}) => {
|
||||||
|
ctx.fillRect(x - 5, y - 5, 10, 10);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onInit: function (promise) {
|
||||||
|
var self = this;
|
||||||
|
promise.then(() => {
|
||||||
|
self.errorMessage = "";
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.name === 'StreamApiNotSupportedError') {
|
||||||
|
self.noStreamApiSupport = true;
|
||||||
|
} else if (error.name === 'NotAllowedError') {
|
||||||
|
self.errorMessage = 'Hey! I need access to your camera'
|
||||||
|
} else if (error.name === 'NotFoundError') {
|
||||||
|
self.errorMessage = 'Do you even have a camera on your device?'
|
||||||
|
} else if (error.name === 'NotSupportedError') {
|
||||||
|
self.errorMessage = 'Seems like this page is served in non-secure context (HTTPS, localhost or file://)'
|
||||||
|
} else if (error.name === 'NotReadableError') {
|
||||||
|
self.errorMessage = 'Couldn\'t access your camera. Is it already in use?'
|
||||||
|
} else if (error.name === 'OverconstrainedError') {
|
||||||
|
self.errorMessage = 'Constraints don\'t match any installed camera. Did you asked for the front camera although there is none?'
|
||||||
|
} else {
|
||||||
|
self.errorMessage = 'UNKNOWN ERROR: ' + error.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
1
BTCPayServer/wwwroot/vendor/vue-qrcode-reader/vue-qrcode-reader.browser.js
vendored
Normal file
1
BTCPayServer/wwwroot/vendor/vue-qrcode-reader/vue-qrcode-reader.browser.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
BTCPayServer/wwwroot/vendor/vue-qrcode-reader/vue-qrcode-reader.css
vendored
Normal file
1
BTCPayServer/wwwroot/vendor/vue-qrcode-reader/vue-qrcode-reader.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.wrapper[data-v-1f90552a]{position:relative;z-index:0;width:100%;height:100%}.overlay[data-v-1f90552a],.tracking-layer[data-v-1f90552a]{position:absolute;width:100%;height:100%;top:0;left:0}.camera[data-v-1f90552a],.pause-frame[data-v-1f90552a]{display:block;object-fit:cover;width:100%;height:100%}
|
||||||
Reference in New Issue
Block a user