Files
xmrpay.link/app.min.js
2026-03-27 10:47:26 +01:00

1 line
18 KiB
JavaScript

!function(){"use strict";const t=/^[48][1-9A-HJ-NP-Za-km-z]{94}$/,e=/^4[1-9A-HJ-NP-Za-km-z]{105}$/;let n=null,a=0,o=null,r=null,i=!1,s=null,c=null,l=null;const d=t=>document.querySelector(t),u=d("#addr"),f=d("#amount"),m=d("#currency"),p=d("#desc"),v=d("#timerCustom"),h=d("#deadlineBadges");let g=0,y=null;const x=d("#generate"),C=d("#result"),L=d("#qr"),w=d("#uri"),S=d("#openWallet"),_=d("#copyUri"),I=d("#copyAddr"),b=d("#countdown"),F=d("#fiatHint"),T=d("#toast"),E=d("#shareLink"),k=d("#copyShareLink"),R=d("#useShortLink"),j=d("#newRequest"),H=d("#homeLink");let U=null;const N=d("#proofToggle"),M=d("#proofPanel"),P=d("#txHash"),X=d("#txKey"),D=d("#verifyProof"),q=d("#proofResult"),z=d("#paymentStatus"),A=d("#paymentSummary"),O=d("#downloadPdf");let B=!1,J=!1,W=null;var G,K;function Z(){u.value="",f.value="",m.value="EUR",p.value="",g=0,y=null,v.value="",h.querySelectorAll(".badge").forEach(function(t){t.classList.remove("active")}),F.textContent="",F.classList.remove("error"),u.classList.remove("valid","invalid"),x.disabled=!0,C.classList.remove("visible"),o&&clearInterval(o),L.innerHTML="",L.classList.remove("paid","confirming"),w.textContent="",E.value="",R.checked=!1,U=null,s=null,ft(),M.classList.remove("open"),P.value="",X.value="",D.disabled=!0,q.innerHTML="",q.className="proof-result",z.innerHTML="",z.className="payment-status",A.innerHTML="",document.title="xmrpay.link — Monero Invoice Generator",history.replaceState(null,"",location.pathname),window.scrollTo({top:0,behavior:"smooth"}),u.focus()}function Y(n){return t.test(n)||e.test(n)}function Q(){const t=u.value.trim();u.classList.remove("valid","invalid"),0!==t.length&&(Y(t)?u.classList.add("valid"):t.length>=10&&u.classList.add("invalid"),function(){const t=u.value.trim();x.disabled=!Y(t)}())}function V(){const t=parseFloat(f.value),e=m.value;if(!t||t<=0)return F.textContent="",void F.classList.remove("error");if("XMR"!==e&&!n)return F.textContent=i?I18n.t("rates_offline"):"",void F.classList.toggle("error",i);if(F.classList.remove("error"),"XMR"===e)if(n){const e=(t*n.eur).toFixed(2);F.textContent="≈ "+e+" EUR"}else F.textContent="";else{const a=n[e.toLowerCase()];if(a&&a>0){const e=(t/a).toFixed(8);F.textContent="≈ "+e+" XMR"}}}function $(){const t=parseFloat(f.value),e=m.value;if(!t||t<=0)return null;if("XMR"===e)return t;if(n){const a=n[e.toLowerCase()];if(a&&a>0)return t/a}return null}function tt(){const t=u.value.trim();if(!Y(t))return;const e=$(),n=p.value.trim(),a=g,o=function(t,e,n){let a="monero:"+t;const o=[];return e&&o.push("tx_amount="+e.toFixed(12)),n&&o.push("tx_description="+encodeURIComponent(n)),o.length&&(a+="?"+o.join("&")),a}(t,e,n);C.classList.add("visible"),w.textContent=o,S.onclick=function(){window.location.href=o},nt(e,n,a),at(e,n);var r=null;a&&a>0&&(y||(y=Date.now()+864e5*a),r=Math.floor(y/1e3));const i=function(t,e,n,a,o){const r=new URLSearchParams;return r.set("a",t),e&&r.set("x",e.toFixed(12)),n&&r.set("d",n),a&&r.set("t",a),o&&r.set("te",o),r.toString()}(t,e,n,a,r);U=i,et(i),L.innerHTML="",new QRCode(L,{text:o,width:256,height:256,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRCode.CorrectLevel.M});const s=document.createElement("div");s.className="qr-hint",s.textContent=I18n.t("qr_hint"),L.appendChild(s),ot(),function(t){try{localStorage.setItem("xmrpay_addr",t)}catch(t){}}(t),C.scrollIntoView({behavior:"smooth",block:"start"})}function et(t){var e=location.origin+"/#"+t;E.value=e,R.checked&&(s?E.value=location.origin+"/s/"+s:async function(t){try{let e=null;g&&g>0&&(e=Math.floor((Date.now()+864e5*g)/1e3));const n=await fetch("/api/shorten.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({hash:t,expiry_ts:e})});if(!n.ok)throw new Error("HTTP "+n.status);const a=await n.json();return s||(s=a.code),location.origin+"/s/"+a.code}catch(t){return console.warn("Short URL failed:",t),null}}(t).then(function(e){e&&R.checked&&U===t&&(E.value=e)}))}function nt(t,e,n){var a="";if(t){a+='<div class="summary-amount">'+t.toFixed(8)+" XMR</div>";var o=parseFloat(f.value),r=m.value;"XMR"!==r&&o&&(a+='<div class="summary-fiat">≈ '+o.toFixed(2)+" "+r+"</div>")}e&&(a+='<div class="summary-desc">'+e.replace(/</g,"&lt;")+"</div>"),A.innerHTML=a,A.classList.remove("paid-confirmed"),function(){var t=document.getElementById("favicon");if(!t)return;t.href=pt()}()}function at(t,e){var n=[];t&&n.push(t.toFixed(4)+" XMR"),e&&n.push(e),n.length&&(document.title=n.join(" — ")+" | xmrpay.link")}function ot(){if(o&&clearInterval(o),b.textContent="",b.className="countdown",(!g||g<=0)&&!y)return;const t=y||Date.now()+864e5*g;function e(){const e=t-Date.now();if(e<=0)return clearInterval(o),b.textContent=I18n.t("countdown_expired"),void(b.className="countdown expired");const n=Math.floor(e/864e5),a=Math.floor(e%864e5/36e5),r=Math.floor(e%36e5/6e4);b.textContent=n>0?I18n.t("countdown_remaining_days").replace("{d}",n).replace("{h}",a):I18n.t("countdown_remaining_hours").replace("{h}",rt(a)).replace("{m}",rt(r))}y=t,b.classList.add("active"),r=e,e(),o=setInterval(e,6e4)}function rt(t){return t<10?"0"+t:""+t}function it(t){navigator.clipboard.writeText(t).then(()=>{st(I18n.t("toast_copied"))})}function st(t){T.textContent=t,T.classList.add("show"),setTimeout(()=>T.classList.remove("show"),2e3)}function ct(t){return/^[0-9a-fA-F]{64}$/.test(t)}function lt(){const t=P.value.trim(),e=X.value.trim();D.disabled=!(ct(t)&&ct(e))}function dt(t){t.verified_at||(t=Object.assign({},t,{verified_at:Math.floor(Date.now()/1e3)})),z.className="payment-status paid",L.classList.add("paid");var e=L.querySelector(".paid-stamp");if(e)e.textContent=I18n.t("status_paid");else{var n=document.createElement("div");n.className="paid-stamp",n.textContent=I18n.t("status_paid"),L.appendChild(n)}var a=L.querySelector(".qr-hint");if(a){var o="";if(t.verified_at)o=" — "+new Date(1e3*t.verified_at).toLocaleDateString("de"===I18n.getLang()?"de-CH":"en-US",{year:"numeric",month:"long",day:"numeric"});a.textContent="TX "+t.tx_hash.substring(0,8)+"..."+o,a.className="qr-hint paid-info"}z.innerHTML="",W=t,A.classList.add("paid-confirmed"),S.style.display="none",document.getElementById("copyAddr").style.display="none";var r=document.getElementById("proofSection");r&&(r.style.display="none"),vt("#4caf50")}function ut(t){var e=t.confirmations||0;z.className="payment-status pending",A.classList.remove("paid-confirmed"),L.classList.add("confirming"),vt("#f59e0b");var n=L.querySelector(".paid-stamp");if(!n){var a=document.createElement("div");a.className="paid-stamp pending-stamp",L.appendChild(a),n=a}n.textContent=0===e?I18n.t("status_pending"):e+"/10";var o=L.querySelector(".qr-hint");o&&(o.textContent="TX "+t.tx_hash.substring(0,8)+"... — "+(0===e?I18n.t("status_pending"):e+"/10"),o.className="qr-hint pending-info")}function ft(){c&&(clearInterval(c),c=null),l=null}async function mt(){if(l){var t=l.txHash,e=l.xmrAmount;try{var n=await fetch("/api/node.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({method:"gettransactions",params:{txs_hashes:[t]}})}),a=(await n.json()).txs||[];if(0===a.length)return;var o=a[0].confirmations||0;o>=10?(ft(),q.className="proof-result active success",q.textContent=I18n.t("proof_verified").replace("{amount}",e.toFixed(6)),s&&await fetch("/api/verify.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:s,tx_hash:t,amount:e,confirmations:o,status:"paid"})}),dt({amount:e,tx_hash:t,confirmations:o})):(ut({amount:e,tx_hash:t,confirmations:o}),q.className="proof-result active warning",q.textContent=I18n.t("proof_confirmed_pending").replace("{amount}",e.toFixed(6)).replace("{n}",o))}catch(t){}}}function pt(){var t=document.getElementById("favicon");if(!t)return"favicon.svg";var e=t.getAttribute("data-base-href")||t.getAttribute("href")||"favicon.svg";return t.getAttribute("data-base-href")||t.setAttribute("data-base-href",e),e}function vt(t){var e=document.createElement("canvas");e.width=32,e.height=32;var n=e.getContext("2d"),a=new Image;a.onload=function(){n.drawImage(a,0,0,32,32),n.beginPath(),n.arc(25,25,7,0,2*Math.PI),n.fillStyle="#fff",n.fill(),n.beginPath(),n.arc(25,25,5.5,0,2*Math.PI),n.fillStyle=t,n.fill(),document.getElementById("favicon").href=e.toDataURL("image/png")},a.src=pt()}G=d("#selfHostBanner"),K=d("#dismissBanner"),G&&-1!==["xmrpay.link","mc6wfeaqc7oijgdcudrr5zsotmwok3jzk3tu2uezzyjisn7nzzjjizyd.onion"].indexOf(location.hostname)&&(sessionStorage.getItem("banner_dismissed")||(G.hidden=!1,K.addEventListener("click",function(){G.hidden=!0,sessionStorage.setItem("banner_dismissed","1")}))),function(){for(var t={de:"EUR",fr:"EUR",it:"EUR",es:"EUR",pt:"EUR",nl:"EUR","de-CH":"CHF","fr-CH":"CHF","it-CH":"CHF","de-AT":"EUR","en-GB":"GBP","en-US":"USD",en:"USD",ja:"JPY",ru:"RUB","pt-BR":"BRL"},e=navigator.languages||[navigator.language||"en"],n=0;n<e.length;n++){var a=e[n];if(t[a])return void(m.value=t[a]);var o=a.substring(0,2).toLowerCase();if(t[o])return void(m.value=t[o])}}(),async function t(){if(n&&Date.now()-a<6e4)return;try{const t=await fetch("/api/rates.php?c=eur,usd,chf,gbp,jpy,rub,brl");if(!t.ok)throw new Error("HTTP "+t.status);const e=await t.json();n=e.monero,a=Date.now(),i=!1,V()}catch(e){console.warn("Kurse konnten nicht geladen werden:",e),i=!0,V(),setTimeout(t,1e4)}}(),function(){const t=location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t),n=e.get("a");if(!n||!Y(n))return!1;u.value=n,Q();const a=e.get("x");a&&(f.value=parseFloat(a),m.value="XMR");const o=e.get("d");o&&(p.value=o);const r=e.get("t");if(r&&parseInt(r)>0){g=parseInt(r);const t=h.querySelector('.badge[data-days="'+g+'"]');t?t.classList.add("active"):v.value=g}const i=parseInt(e.get("te")||"0");i>0&&(y=1e3*i);const l=e.get("c");l&&(s=l,R.checked=!0,setTimeout(function(){!function(t,e){fetch("/api/check-short.php?code="+encodeURIComponent(t)).then(function(t){if(!t.ok)throw new Error("Integrity check failed");return t.json()}).then(function(n){if(n.hash){n.expiry_ts&&parseInt(n.expiry_ts)>0&&(y=1e3*parseInt(n.expiry_ts),C.classList.contains("visible")&&ot());var a=new URLSearchParams(e);a.delete("c");var o=a.toString();n.hash!==o&&(console.warn("xmrpay: Short URL hash mismatch detected for code",t),st(I18n.t("toast_integrity_warning")))}}).catch(function(t){console.warn("xmrpay: Could not verify short URL integrity:",t)})}(l,t),function(t){fetch("/api/verify.php?code="+encodeURIComponent(t)).then(function(t){return t.json()}).then(function(e){e.verified&&("pending"===e.status?(ut(e),c=setInterval(function(){fetch("/api/verify.php?code="+encodeURIComponent(t)).then(function(t){return t.json()}).then(function(t){t.verified&&("paid"===t.status?(ft(),dt(t)):ut(t))}).catch(function(){})},6e4)):dt(e))}).catch(function(){})}(l)},200));return setTimeout(tt,100),!0}()||function(){try{const t=localStorage.getItem("xmrpay_addr");t&&(u.value=t,Q())}catch(t){}}(),"serviceWorker"in navigator&&navigator.serviceWorker.register("sw.js").catch(function(){}),I18n.onChange(function(){var t=L.querySelector(".qr-hint");t&&(t.textContent=I18n.t("qr_hint"));var e=L.querySelector(".paid-stamp");if(e&&(e.textContent=I18n.t("status_paid")),W&&dt(W),C.classList.contains("visible")){var n=$(),a=p.value.trim();nt(n,a,g),at(n,a)}r&&r()}),u.addEventListener("input",Q),f.addEventListener("input",V),m.addEventListener("change",V),x.addEventListener("click",tt),_.addEventListener("click",()=>it(w.textContent)),I.addEventListener("click",()=>it(u.value.trim())),k.addEventListener("click",()=>it(E.value)),R.addEventListener("change",function(){U&&et(U)}),L.addEventListener("click",function(){const t=L.querySelector("canvas");if(!t)return;const e=document.createElement("a");e.download="xmrpay-qr.png",e.href=t.toDataURL("image/png"),e.click()}),j.addEventListener("click",Z),H.addEventListener("click",function(t){t.preventDefault(),Z()}),h.querySelectorAll(".badge").forEach(function(t){t.addEventListener("click",function(){const e=parseInt(t.getAttribute("data-days"));t.classList.contains("active")?(t.classList.remove("active"),g=0,y=null,v.value=""):(h.querySelectorAll(".badge").forEach(function(t){t.classList.remove("active")}),t.classList.add("active"),g=e,y=null,v.value="")})}),v.addEventListener("input",function(){h.querySelectorAll(".badge").forEach(function(t){t.classList.remove("active")}),g=parseInt(v.value)||0,y=null}),O.addEventListener("click",async function(){await new Promise(function(t,e){if(window.jspdf)t();else{var n=document.createElement("script");n.src="lib/jspdf.min.js",n.integrity="sha384-GwHhSt8QjC7J+v0zZ0Flfho/T76YHEcCL9w4rvjTIUHauh6gWJeBSIi3vWXxNhtA",n.crossOrigin="anonymous",n.onload=function(){J=!0,t()},n.onerror=function(){e(new Error("Failed to load jsPDF"))},document.head.appendChild(n)}});var t=new(0,window.jspdf.jsPDF)({orientation:"portrait",unit:"mm",format:"a4"}),e=u.value.trim(),n=$(),a=p.value.trim(),o=parseFloat(f.value),r=m.value,i=t.internal.pageSize.getWidth(),s=20,c=i-40,l=s;t.setFillColor(242,104,33),t.rect(0,0,i,8,"F"),l=22,t.setFont("helvetica","bold"),t.setFontSize(22),t.setTextColor(242,104,33),t.text(I18n.t("pdf_title"),s,l),t.setFont("helvetica","normal"),t.setFontSize(10),t.setTextColor(120,120,120);var d=(new Date).toLocaleDateString("de"===I18n.getLang()?"de-CH":"en-US",{year:"numeric",month:"long",day:"numeric"});t.text(I18n.t("pdf_date")+": "+d,i-s,l,{align:"right"}),l+=6,t.setDrawColor(220,220,220),t.setLineWidth(.3),t.line(s,l,i-s,l);var v=L.querySelector("canvas"),h=50,y=i-s-h,x=l+6;if(v){var C=v.toDataURL("image/png");t.addImage(C,"PNG",y,x,h,h),t.setFontSize(7),t.setTextColor(150,150,150),t.text(I18n.t("pdf_scan_qr"),y+25,x+h+4,{align:"center"})}var S=y-s-10;function _(e,n){t.setFont("helvetica","normal"),t.setFontSize(9),t.setTextColor(150,150,150),t.text(e,20,l),l+=5,t.setFont("helvetica","bold"),t.setFontSize(11),t.setTextColor(40,40,40);var a=t.splitTextToSize(n,S);t.text(a,20,l),l+=5*a.length+4}if(l+=14,n){var I=n.toFixed(8)+" XMR";"XMR"!==r&&o&&(I+=" (~ "+o.toFixed(2)+" "+r+")"),_(I18n.t("pdf_amount"),I)}a&&_(I18n.t("pdf_desc"),a);if(g>0){var b=new Date(Date.now()+864e5*g).toLocaleDateString("de"===I18n.getLang()?"de-CH":"en-US",{year:"numeric",month:"long",day:"numeric"});_(I18n.t("pdf_deadline"),b+" ("+I18n.t("pdf_deadline_days").replace("{d}",g)+")")}l=Math.max(l,x+h+12),t.setFont("helvetica","normal"),t.setFontSize(9),t.setTextColor(150,150,150),t.text(I18n.t("pdf_address"),s,l),l+=5,t.setFillColor(245,245,245),t.roundedRect(s,l-3.5,c,10,2,2,"F"),t.setFont("courier","normal"),t.setFontSize(8),t.setTextColor(60,60,60),t.text(e,23,l+2.5),l+=14;var F=w.textContent;if(F){t.setFillColor(245,245,245),t.roundedRect(s,l-3.5,c,10,2,2,"F"),t.setFont("courier","normal"),t.setFontSize(6.5),t.setTextColor(100,100,100);var T=t.splitTextToSize(F,c-6);t.text(T,23,l+2),l+=3*T.length+10}if(W){l+=4;var k="";if(W.verified_at)k=new Date(1e3*W.verified_at).toLocaleDateString("de"===I18n.getLang()?"de-CH":"en-US",{year:"numeric",month:"long",day:"numeric"});var R=W.amount.toFixed(6)+" XMR — TX "+W.tx_hash.substring(0,8)+"..."+(k?" — "+k:"");t.setFillColor(76,175,80),t.roundedRect(s,l-4,c,16,2,2,"F"),t.setFont("helvetica","bold"),t.setFontSize(12),t.setTextColor(255,255,255),t.text(I18n.t("status_paid").toUpperCase(),s+c/2,l+1,{align:"center"}),t.setFont("helvetica","normal"),t.setFontSize(7.5),t.text(R,s+c/2,l+7,{align:"center"}),l+=22}t.setDrawColor(220,220,220),t.setLineWidth(.3);var j=t.internal.pageSize.getHeight()-15;t.line(s,j,i-s,j),t.setFont("helvetica","normal"),t.setFontSize(7),t.setTextColor(180,180,180),t.text(I18n.t("pdf_footer"),i/2,j+5,{align:"center"});var H=E.value;H&&t.text(H,i/2,j+9,{align:"center"});var U="xmrpay-"+(a?a.replace(/[^a-zA-Z0-9]/g,"-").substring(0,30):"invoice")+".pdf";t.save(U)}),N.addEventListener("click",function(){if(M.classList.contains("open"))return void M.classList.remove("open");if(!B&&!window.XmrCrypto)return void new Promise(function(t,e){if(window.XmrCrypto)return void t();const n=document.createElement("script");n.src="lib/xmr-crypto.bundle.js",n.integrity="sha384-ta9IpDZOod8WcA7TprKyb/TxmOSNfkG0fCjhWssiSmpft9MLXAtSO8L8YmnH3DCY",n.crossOrigin="anonymous",n.onload=t,n.onerror=function(){e(new Error("Failed to load crypto module"))},document.head.appendChild(n)}).then(function(){B=!0,M.classList.add("open"),P.focus()});M.classList.add("open"),P.focus()}),P.addEventListener("input",lt),X.addEventListener("input",lt),D.addEventListener("click",async function(){const t=P.value.trim(),e=X.value.trim(),n=u.value.trim();if(!ct(t)||!ct(e)||!Y(n))return;D.disabled=!0,q.className="proof-result active",q.textContent=I18n.t("proof_verifying");try{var a=await fetch("/api/node.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({method:"gettransactions",params:{txs_hashes:[t],decode_as_json:!0}})}),o=(await a.json()).txs||[];if(0===o.length)return q.className="proof-result active error",q.textContent=I18n.t("proof_tx_not_found"),void(D.disabled=!1);for(var r=o[0],i=JSON.parse(r.as_json),d=XmrCrypto.getKeysFromAddress(n),f=d.publicViewKey,m=d.publicSpendKey,p=XmrCrypto.bytesToScalar(XmrCrypto.hexToBytes(e)),v=XmrCrypto.Point.fromHex(f).multiply(p).multiply(8n).toBytes(),h=XmrCrypto.Point.fromHex(m),g=i.vout||[],y=i.rct_signatures&&i.rct_signatures.ecdhInfo||[],x=0n,C=!1,L=0;L<g.length;L++){var w=g[L],S=w.target&&w.target.tagged_key?w.target.tagged_key.key:w.target&&w.target.key;if(S){var _=XmrCrypto.encodeVarint(L),I=XmrCrypto.hashToScalar(XmrCrypto.concat(v,_)),b=XmrCrypto.bytesToScalar(I),F=XmrCrypto.Point.BASE.multiply(b).add(h);if(XmrCrypto.bytesToHex(F.toBytes())===S)if(C=!0,y[L]&&y[L].amount)x+=XmrCrypto.decodeRctAmount(y[L].amount,v,L)}}if(C){var T=Number(x)/1e12,E=r.confirmations||0;E>=10?(q.className="proof-result active success",q.textContent=I18n.t("proof_verified").replace("{amount}",T.toFixed(6)),s&&await fetch("/api/verify.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:s,tx_hash:t,amount:T,confirmations:E,status:"paid"})}),dt({amount:T,tx_hash:t,confirmations:E})):(q.className="proof-result active warning",q.textContent=I18n.t("proof_confirmed_pending").replace("{amount}",T.toFixed(6)).replace("{n}",E),s&&await fetch("/api/verify.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:s,tx_hash:t,amount:T,confirmations:E,status:"pending"})}),ut({amount:T,tx_hash:t,confirmations:E}),function(t,e){ft(),l={txHash:t,xmrAmount:e},c=setInterval(mt,6e4)}(t,T))}else q.className="proof-result active error",q.textContent=I18n.t("proof_no_match")}catch(t){q.className="proof-result active error",q.textContent=I18n.t("proof_error")}D.disabled=!1})}();