171 lines
4.4 KiB
Svelte
171 lines
4.4 KiB
Svelte
|
<script>
|
|||
|
import { Styles,
|
|||
|
Container,
|
|||
|
Row,
|
|||
|
Col,
|
|||
|
Card, CardBody, CardHeader, CardTitle,
|
|||
|
Button,
|
|||
|
Tooltip,
|
|||
|
Icon,
|
|||
|
InputGroup,
|
|||
|
Input,
|
|||
|
Nav, NavItem, NavLink,
|
|||
|
Modal, ModalHeader, ModalBody, ModalFooter,
|
|||
|
} from 'sveltestrap';
|
|||
|
|
|||
|
import InputMask from './InputMask.svelte';
|
|||
|
import QR from './QR.svelte';
|
|||
|
import * as pf from './payflow.js';
|
|||
|
import CTC from './CTC.svelte';
|
|||
|
import Tipped from './Tipped.svelte';
|
|||
|
|
|||
|
export let lnurl = undefined;
|
|||
|
let isOpen;
|
|||
|
let res1;
|
|||
|
let res2;
|
|||
|
let memo;
|
|||
|
let err;
|
|||
|
let bolt;
|
|||
|
|
|||
|
let confirmAmount;
|
|||
|
let amount;
|
|||
|
|
|||
|
let minSendSat=0, maxSendSat=0;
|
|||
|
|
|||
|
$: minSendSat = res1 && (Math.round((res1.minSendable+999)/1000))
|
|||
|
$: maxSendSat = res1 && (Math.round(res1.maxSendable/1000))
|
|||
|
|
|||
|
$: if (lnurl && !isOpen) {
|
|||
|
res1 = res2 = err = memo = confirmAmount = undefined;
|
|||
|
isOpen = true;
|
|||
|
fetchInvoice()
|
|||
|
}
|
|||
|
|
|||
|
let stage;
|
|||
|
$: if (!res1) {
|
|||
|
stage = err || "Connecting to server…";
|
|||
|
} else if (confirmAmount) {
|
|||
|
stage = "Amount confirmation";
|
|||
|
} else if (!res2) {
|
|||
|
stage = err || "Requesting invoice";
|
|||
|
} else {
|
|||
|
stage = err || "Invoice ready.";
|
|||
|
}
|
|||
|
|
|||
|
async function fetchInvoice() {
|
|||
|
try {
|
|||
|
let url = pf.decodeLnurl(lnurl)
|
|||
|
//await new Promise(r => setTimeout(r, 5000));
|
|||
|
res1 = await pf.payStep1(url)
|
|||
|
if (res1.status == "ERROR") {
|
|||
|
throw new Error(res1.reason)
|
|||
|
}
|
|||
|
|
|||
|
if (!isOpen) { return }
|
|||
|
if (res1.minSendable != res1.maxSendable) {
|
|||
|
amount = Math.round((res1.minSendable+999)/1000).toString();
|
|||
|
await new Promise((resolve, reject)=>{confirmAmount = resolve})
|
|||
|
confirmAmount = undefined;
|
|||
|
} else {
|
|||
|
amount = (res1.minSendable/1000).toString();
|
|||
|
}
|
|||
|
res2 = await pf.payStep2(url,res1,amount*1000)
|
|||
|
if (!isOpen) { return }
|
|||
|
if (res2.status == "ERROR") {
|
|||
|
throw new Error(res2.reason)
|
|||
|
}
|
|||
|
} catch(e) {
|
|||
|
err = e.toString()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function mdText() {
|
|||
|
for (let md of res1.decodedMetadata) {
|
|||
|
if (md[0]=="text/plain") {
|
|||
|
return md[1]
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
const canShare = !!navigator.share;
|
|||
|
|
|||
|
</script>
|
|||
|
|
|||
|
<Modal {isOpen} size='lg'>
|
|||
|
<ModalHeader>BOLT11 invoice: {stage}</ModalHeader>
|
|||
|
<ModalBody>
|
|||
|
{#if confirmAmount}
|
|||
|
<div class="row">
|
|||
|
<label class="form-label mb-3">
|
|||
|
Amount: {minSendSat} – {maxSendSat} satoshi
|
|||
|
<div class="input-group">
|
|||
|
{#key minSendSat}
|
|||
|
<InputMask bind:value={amount}
|
|||
|
autofocus
|
|||
|
unmask
|
|||
|
imask={{
|
|||
|
mask: Number, scale:0,
|
|||
|
min: minSendSat , max: maxSendSat,
|
|||
|
normalizeZeros: true}}
|
|||
|
inputmode={"numeric"}
|
|||
|
class="form-control"/>
|
|||
|
{/key}
|
|||
|
<button class="btn btn-outline-secondary"
|
|||
|
type="button"
|
|||
|
on:click={()=> amount=minSendSat.toString()}>Min
|
|||
|
</button>
|
|||
|
<button class="btn btn-outline-secondary"
|
|||
|
type="button"
|
|||
|
on:click={()=> amount=maxSendSat.toString()}>Max
|
|||
|
</button>
|
|||
|
<button class="btn btn-secondary"
|
|||
|
type="button"
|
|||
|
disabled={!(amount && amount <= maxSendSat
|
|||
|
&& amount >= minSendSat)}
|
|||
|
on:click={()=> confirmAmount()}>OK
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
</label>
|
|||
|
</div>
|
|||
|
{/if}
|
|||
|
{#if (res2&&res2.pr)}
|
|||
|
<div class="row">
|
|||
|
<CTC let:id let:action force text={res2.pr}>
|
|||
|
<div class="form-text user-select-all mb-2 mt-2"
|
|||
|
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
|||
|
on:click={action} id={id}>{res2.pr.toUpperCase()}
|
|||
|
</div>
|
|||
|
</CTC>
|
|||
|
<div class="d-flex justify-content-center">
|
|||
|
<a href="lightning:{res2.pr}">
|
|||
|
<QR value="{res2.pr}" size="230"/>
|
|||
|
</a>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
{/if}
|
|||
|
</ModalBody>
|
|||
|
<ModalFooter>
|
|||
|
<div class="btn-group flex-wrap">
|
|||
|
{#if res2 && res2.pr}
|
|||
|
<CTC let:id let:action text={res2.pr}>
|
|||
|
<button type="button" class="btn btn-outline-secondary"
|
|||
|
id={id} on:click={action}>Copy</button>
|
|||
|
</CTC>
|
|||
|
{#if canShare}
|
|||
|
<button type="button" class="btn btn-outline-secondary"
|
|||
|
on:click={()=>{ navigator.share({text:res2.pr})}}>Share
|
|||
|
</button>
|
|||
|
{/if}
|
|||
|
<Tipped let:id>
|
|||
|
<a slot="thing" role="button" class="btn btn-outline-secondary"
|
|||
|
href="lightning:{res2.pr}" {id}>Pay</a>
|
|||
|
<div slot="tip">open invoice in your wallet</div>
|
|||
|
</Tipped>
|
|||
|
{/if}
|
|||
|
<button
|
|||
|
class="btn btn-outline-primary"
|
|||
|
type="button" on:click="{()=>{isOpen=false; lnurl = null}}">Cancel</button>
|
|||
|
</div>
|
|||
|
|
|||
|
</ModalFooter>
|
|||
|
</Modal>
|