mirror of
https://gitplac.si/aljaxus/upn-qr.git
synced 2025-12-16 19:50:57 +00:00
365 lines
16 KiB
Plaintext
365 lines
16 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>UPN-QR | Open generator for UPN QR codes</title>
|
|
<link rel="stylesheet" href="/public/style.css">
|
|
</head>
|
|
<style>
|
|
section div {
|
|
padding-left: 25px;
|
|
display: grid;
|
|
}
|
|
|
|
.credits {
|
|
display: grid;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
}
|
|
.maker {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
row-gap: .5rem;
|
|
column-gap: .5rem;
|
|
}
|
|
.code {
|
|
grid-column: 1;
|
|
background-size: contain;
|
|
background-position: top center;
|
|
background-repeat: no-repeat;
|
|
background-image: url("/public/invalid-content.png");
|
|
min-height: 210px;
|
|
min-width: 210px;
|
|
}
|
|
.maker button,
|
|
.maker input[type="submit"] {
|
|
margin-bottom: auto;
|
|
}
|
|
</style>
|
|
<body>
|
|
<section>
|
|
<h1>About</h1>
|
|
<p>
|
|
This API was developed for personal use, but modified and later released for public use.
|
|
It is an unofficial application with the sole purpose of simplifying QR code generation for universal payment ordes (<a href="https://upn-qr.si">https://upn-qr.si</a>).
|
|
You can read the specification <a href="/public/NavodilaZaProgramerjeUPNQR.pdf">here</a>.
|
|
The application does not keep any long-term logs.
|
|
</p>
|
|
</section>
|
|
<section>
|
|
<h1 id="maker"><a href="#maker">🔗</a> Form maker</h1>
|
|
<span>Fill in the fields with reciever's bank information and leave empty the ones that user has to fill out.</span>
|
|
<form action="/form" method="get" class="maker">
|
|
<div>
|
|
<label for="title">Form title</label>
|
|
<input type="text" placeholder="First half of the bike" name="title">
|
|
</div>
|
|
<div>
|
|
<label for="client-name">Client name</label>
|
|
<input type="text" placeholder="Peter Novak" name="client-name">
|
|
</div>
|
|
<div>
|
|
<label for="client-address">Client address</label>
|
|
<input type="text" placeholder="Ravna ulica 13 a" name="client-address">
|
|
</div>
|
|
<div>
|
|
<label for="client-city">Client city</label>
|
|
<input type="text" placeholder="1000 Ljubljana" name="client-city">
|
|
</div>
|
|
<div>
|
|
<label for="amount">Amount</label>
|
|
<input type="number" placeholder="00000001132" min="0" max="99999999999" name="amount">
|
|
</div>
|
|
<div>
|
|
<label for="deadline">Deadline</label>
|
|
<input type="text" placeholder="01.02.2034" min=0 max=10 name="deadline">
|
|
</div>
|
|
<div>
|
|
<label for="code">Purpose code</label>
|
|
<input type="text" placeholder="OTHR" name="code">
|
|
</div>
|
|
<div>
|
|
<label for="purpose">Payment purpose</label>
|
|
<input type="text" placeholder="Moutain bike first half" name="purpose">
|
|
</div>
|
|
<div>
|
|
<label for="iban">IBAN</label>
|
|
<input type="text" placeholder="SI56047500000280672" name="iban">
|
|
</div>
|
|
<div>
|
|
<label for="reference">Reference</label>
|
|
<input type="text" placeholder="SI121234567890120" name="reference">
|
|
</div>
|
|
<div>
|
|
<label for="issuer-name">Issuer name</label>
|
|
<input type="text" placeholder="Kolesarstvo Hrib" name="issuer-name">
|
|
</div>
|
|
<div>
|
|
<label for="issuer-address">Issuer address</label>
|
|
<input type="text" placeholder="Za deveto smreko 15 k" name="issuer-address">
|
|
</div>
|
|
<div>
|
|
<label for="issuer-city">Issuer city</label>
|
|
<input type="text" placeholder="1000 Ljubljana" name="issuer-city">
|
|
</div>
|
|
<div class="code" id="qrcode"></div>
|
|
<div>
|
|
<button id="copyurl" type="button">Copy image URL</button>
|
|
</div>
|
|
<div>
|
|
<input type="submit" value="Create form">
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<script>
|
|
function val (name) { return encodeURIComponent(document.getElementsByName(name)?.[0]?.value) || "" }
|
|
function getNewUrl () {
|
|
const qstring = [
|
|
["client_name", val("client-name")],
|
|
["client_address", val("client-address")],
|
|
["client_city", val("client-city")],
|
|
["amount", val("amount").padStart(11, "0")],
|
|
["purpose_code", val("code")],
|
|
["payment_purpose", val("purpose")],
|
|
["iban", val("iban")],
|
|
["reference", val("reference")],
|
|
["issuer_name", val("issuer-name")],
|
|
["issuer_address", val("issuer-address")],
|
|
["issuer_city", val("issuer-city")],
|
|
["deadline", val("deadline")],
|
|
].map(v => `${v[0]}=${v[1]}`).join("&")
|
|
return encodeURI(`${window.location.origin}/api/qrcode?${qstring}`)
|
|
}
|
|
const copyBtn = document.getElementById("copyurl")
|
|
copyBtn.addEventListener("click", () => copyUrl())
|
|
function copyUrl () {
|
|
try {
|
|
const dummy = document.createElement('input')
|
|
document.body.appendChild(dummy)
|
|
dummy.value = getNewUrl()
|
|
dummy.select()
|
|
document.execCommand('copy')
|
|
document.body.removeChild(dummy)
|
|
|
|
copyBtn.innerText = "Copied!"
|
|
copyBtn.classList.add("success")
|
|
setTimeout(() => {
|
|
copyBtn.innerText = "Copy image URL"
|
|
copyBtn.classList.remove("success")
|
|
}, 750)
|
|
} catch (error) {
|
|
console.error(error)
|
|
copyBtn.innerText = "Failed!"
|
|
copyBtn.classList.add("error")
|
|
setTimeout(() => {
|
|
copyBtn.innerText = "Copy image URL"
|
|
copyBtn.classList.remove("error")
|
|
}, 750)
|
|
}
|
|
}
|
|
|
|
|
|
const qrcode = document.getElementById("qrcode")
|
|
function val (name) { return document.getElementsByName(name)?.[0]?.value || "" }
|
|
let debounce = null
|
|
function updateQRdeb () {
|
|
clearTimeout(debounce)
|
|
debounce = setTimeout(() => {
|
|
clearTimeout(debounce)
|
|
updateQR()
|
|
}, 500);
|
|
}
|
|
function updateQR (e) {
|
|
try {
|
|
const preloadImg = new Image();
|
|
const newurl = getNewUrl()
|
|
preloadImg.addEventListener("load", () => qrcode.style.setProperty('background-image', `url("${newurl}"), url("/public/invalid-content.png")`))
|
|
preloadImg.addEventListener("error", () => qrcode.style.setProperty('background-image', `url("/public/invalid-content.png")`))
|
|
preloadImg.src=newurl;
|
|
} catch (error) {
|
|
setImgUrl(`url("/public/invalid-content.png")`)
|
|
}
|
|
}
|
|
|
|
// Add input listener to all inputs
|
|
for (el of document.getElementsByTagName("input")) {
|
|
el.addEventListener("input", updateQRdeb)
|
|
}
|
|
|
|
updateQR()
|
|
</script>
|
|
<section>
|
|
<h1 id="api"><a href="#api">🔗</a> API</h1>
|
|
<h4 id="api-qrcode"><a href="#api-qrcode">🔗</a> <code>GET /api/qrcode</code></h4>
|
|
<div>
|
|
<span>Returns a <code>image/png</code> image if successful (OK 200), else returns the following JSON <code>{ ok: false, errors: String[] }</code> where <code>errors</code> is an array of descriptive strings.</span>
|
|
<br>
|
|
<pre>
|
|
<!-- Meant to be used as direct image source, for example -->
|
|
<img src="https://upn-qr.gitapp.si/api/qrcode?client_name=Dobri človek&client_address=Kristanova ulica 1&client_city=1000 Ljubljana&amount=00000001000&deadline=01.02.2034&payment_purpose=Donacija&iban=SI56021400015556761&reference=SI99&issuer_name=Slovenska Karitas&issuer_address=Kristanova ulica 1&issuer_city=1000 Ljubljana">
|
|
</pre>
|
|
<img src="/api/qrcode?client_name=Dobri človek&client_address=Kristanova ulica 1&client_city=1000 Ljubljana&amount=00000001000&deadline=01.02.2034&payment_purpose=Donacija&iban=SI56021400015556761&reference=SI99&issuer_name=Slovenska Karitas&issuer_address=Kristanova ulica 1&issuer_city=1000 Ljubljana">
|
|
</div>
|
|
<br>
|
|
|
|
<div>
|
|
<b>Query parameters:</b>
|
|
<p>Following the specification from official documentation - <a href="https://www.upn-qr.si/uploads/files/NavodilaZaProgramerjeUPNQR.pdf">NavodilaZaProgramerjeUPNQR.pdf</a>, section <code>4. Vsebina kode QR</code>.</p>
|
|
<h5 id="api-qrcode-client_name"><a href="#api-qrcode-client_name">🔗</a> <code>client_name</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[a-zA-Z0-9ČŠŽĐ'](?:[A-Z0-9 ČŠŽĐ']{0,31}[A-Z0-9ČŠŽĐ'])?$/i</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/pNmOI0/1">regex101.com/r/pNmOI0/1</a></span>
|
|
<br>
|
|
<span>Description: Name and surname of the client. Max length 33 characters including spaces and numbers.</span>
|
|
<br>
|
|
<span>Example: Peter Novak</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-client_address"><a href="#api-qrcode-client_address">🔗</a> <code>client_address</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[a-zA-Z0-9ČŠŽĐ](?:[A-Z0-9 ČŠŽĐ]{0,31}[A-Z0-9ČŠŽĐ])?$/i</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/JA4wmM/1">regex101.com/r/JA4wmM/1</a></span>
|
|
<br>
|
|
<span>Description: Full client address in long form. Max length 33 characters including spaces and numbers.</span>
|
|
<br>
|
|
<span>Example: Ravna ulica 13 a</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-client_city"><a href="#api-qrcode-client_city">🔗</a> <code>client_city</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[a-zA-Z0-9ČŠŽĐ](?:[A-Z0-9 ČŠŽĐ]{0,31}[A-Z0-9ČŠŽĐ])?$/i</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/pK3oEm/1">regex101.com/r/5QMpTn/1</a></span>
|
|
<br>
|
|
<span>Description: Client's city including postal code</span>
|
|
<br>
|
|
<span>Example: 1000 Ljubljana</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-amount"><a href="#api-qrcode-amount">🔗</a> <code>amount</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^(?=.{11}$)[0]{1,11}[0-9]{0,11}$</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/Tyq5S1/1">regex101.com/r/Tyq5S1/1</a></span>
|
|
<br>
|
|
<span>Description: Total amount to pay. Must contain 11 numbers. Last two numbers are decimal places (cents).</span>
|
|
<br>
|
|
<span>Example (11,32€): 00000001132</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-deadline"><a href="#api-qrcode-deadline">🔗</a> <code>deadline</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/JDZT9P/1">regex101.com/r/JDZT9P/1</a></span>
|
|
<br>
|
|
<span>Description: Payment deadline. Field is optional. Format DD.MM.YYYY</span>
|
|
<br>
|
|
<span>Example: 01.02.2034</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-purpose_code"><a href="#api-qrcode-purpose_code">🔗</a> <code>purpose_code</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[A-Z]{4}$</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/TsiZQJ/1">regex101.com/r/TsiZQJ/1</a></span>
|
|
<br>
|
|
<span>Default: OTHR</span>
|
|
<br>
|
|
<span>Description: Must contain 4 uppercase characters compliant with public purpose code library <a href="https://www.nlb.si/sepa-koda-namena-prebivalstvo">www.nlb.si/sepa-koda-namena-prebivalstvo</a></span>
|
|
<br>
|
|
<span>Example: OTHR</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-payment_purpose"><a href="#api-qrcode-payment_purpose">🔗</a> <code>payment_purpose</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[A-Z0-9ČŠŽĐ](?:[A-Z0-9 ČŠŽĐ\-:;_'"]{0,40}[A-Z0-9ČŠŽĐ])?$</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/egl24t/1">regex101.com/r/egl24t/1</a></span>
|
|
<br>
|
|
<span>Description: Can contain any character including ŠČŽĐ and space. Max length 33 characters including space.</span>
|
|
<br>
|
|
<span>Example: Moutain bike first half</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-iban"><a href="#api-qrcode-iban">🔗</a> <code>iban</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[A-Z]{2}\d{17}$</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/8bXDvh/1">regex101.com/r/8bXDvh/1</a></span>
|
|
<br>
|
|
<span>Description: Must contain country code (exp. SI56). No spaces allowed. Max length 34 characters</span>
|
|
<br>
|
|
<span>Example: SI56047500000280672</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-reference"><a href="#api-qrcode-reference">🔗</a> <code>reference</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[A-Z]{2}[0-9\-]{1,24}$</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/2tSYMw/1">regex101.com/r/2tSYMw/1</a></span>
|
|
<br>
|
|
<span>Description: Must contain reference model (exp. SI00) and reference (exp. 1234-2002). Max length 4+22 numbers and minus symbols. No spaces allowed. Reference model and structure must comply with <a href="https://www.nlb.si/navodila-upn">standard reference model.</a></span>
|
|
<br>
|
|
<span>Example: SI121234567890120</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-issuer_name"><a href="#api-qrcode-issuer_name">🔗</a> <code>issuer_name</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[a-zA-Z0-9ČŠŽĐ.'](?:[A-Z0-9 ČŠŽĐ.'\-]{0,31}[A-Z0-9ČŠŽĐ.'])?$/i</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/ND8T1c/1">regex101.com/r/ubDgZL/1</a></span>
|
|
<br>
|
|
<span>Description: Name and surname of the issuer or company name. Max length 33 characters including spaces and numbers.</span>
|
|
<br>
|
|
<span>Example: Kolesarstvo Hrib</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-issuer_address"><a href="#api-qrcode-issuer_address">🔗</a> <code>issuer_address</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[a-zA-Z0-9ČŠŽĐ](?:[A-Z0-9 ČŠŽĐ\-.]{0,31}[A-Z0-9ČŠŽĐ])?$/i</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/qgkRMO/1">regex101.com/r/5oKk0T/1</a></span>
|
|
<br>
|
|
<span>Description: Full issuer address in long form. Max length 33 characters including spaces, numbers, minus and dot.</span>
|
|
<br>
|
|
<span>Example: Za deveto smreko 15-k p.p. 15</span>
|
|
</div>
|
|
|
|
<h5 id="api-qrcode-issuer_city"><a href="#api-qrcode-issuer_city">🔗</a> <code>issuer_city</code></h5>
|
|
<div>
|
|
<span>Regex: <code>^[a-zA-Z0-9ČŠŽĐ](?:[A-Z0-9 ČŠŽĐ]{0,31}[A-Z0-9ČŠŽĐ])?$/i</code></span>
|
|
<br>
|
|
<span>Demo: <a href="https://regex101.com/r/JfuNU1/1">regex101.com/r/JfuNU1/1</a></span>
|
|
<br>
|
|
<span>Description: Issuer city including postal code</span>
|
|
<br>
|
|
<span>Example: 1000 Ljubljana</span>
|
|
</div>
|
|
|
|
<!-- <h5><code>query</code></h5>
|
|
<div>
|
|
Regex: <code></code>
|
|
<br>
|
|
Demo: <a href=""></a>
|
|
</div> -->
|
|
</div>
|
|
|
|
<div class="credits">
|
|
<span>-- - --</span>
|
|
Development and hosting: <a href="https://aljaxus.eu">Aljaž Starc</a>
|
|
Initial insight: <a href="http://www.valentincic.eu/">Tjaž Valentinčič</a>
|
|
<span>-</span>
|
|
<a href="https://gitplac.si/aljaxus/upn-qr">gitplac.si/aljaxus/upn-qr</a>
|
|
<span>-</span>
|
|
</div>
|
|
|
|
</section>
|
|
</body>
|
|
</html>
|