lnurl-pay.me/src/PayFlow.svelte

171 lines
4.4 KiB
Svelte
Raw Normal View History

2021-07-17 13:44:07 +03:00
<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>