| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>ZPAY - Instant UPI Links</title>
-
- <!-- Tailwind CSS -->
- <script src="https://cdn.tailwindcss.com"></script>
-
- <!-- QR Code Library -->
- <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
- <style>
- @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;600;700&display=swap');
- body { font-family: 'Plus Jakarta Sans', sans-serif; }
- .fade-in { animation: fadeIn 0.5s ease-in; }
- @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
-
- /* Custom Scrollbar for cleaner look */
- ::-webkit-scrollbar { width: 8px; }
- ::-webkit-scrollbar-track { background: #f1f5f9; }
- ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
- ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
- </style>
- </head>
- <body class="bg-slate-50 text-slate-800 min-h-screen flex flex-col">
- <!-- Navbar -->
- <nav class="bg-white border-b border-slate-200 sticky top-0 z-50 backdrop-blur-md bg-white/90">
- <div class="max-w-5xl mx-auto px-4 py-4 flex items-center justify-between">
- <div class="flex items-center space-x-2 cursor-pointer" onclick="window.location.href=window.location.pathname">
- <div class="bg-indigo-600 text-white p-1 rounded-lg">
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
- </div>
- <span class="text-2xl font-bold tracking-tight text-slate-900">ZPAY</span>
- </div>
- <button onclick="resetApp()" class="text-sm font-medium text-slate-500 hover:text-indigo-600 transition">
- New QR
- </button>
- </div>
- </nav>
- <!-- Main Content -->
- <main class="max-w-5xl mx-auto px-4 py-10 flex-grow">
-
- <!-- Header -->
- <div class="text-center mb-10" id="mainHeader">
- <h1 class="text-3xl md:text-5xl font-bold mb-4 text-slate-900">Payments made simple.</h1>
- <p class="text-slate-500 text-lg">Generate shareable smart links for your UPI ID.</p>
- </div>
- <div class="grid md:grid-cols-2 gap-8 items-start">
-
- <!-- Left Column: Input Form -->
- <div class="bg-white p-6 md:p-8 rounded-3xl shadow-xl shadow-slate-200/50 border border-slate-100 relative overflow-hidden">
- <div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"></div>
-
- <h2 class="text-xl font-bold mb-6 text-slate-800">Payment Details</h2>
- <div class="space-y-5">
- <!-- Payee Name -->
- <div>
- <label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">Payee Name <span class="text-indigo-500">*</span></label>
- <input type="text" id="upiName" placeholder="e.g. Rahul Sharma"
- class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition font-medium">
- </div>
- <!-- UPI ID -->
- <div>
- <label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">UPI ID / VPA <span class="text-indigo-500">*</span></label>
- <input type="text" id="upiId" placeholder="e.g. rahul@oksbi"
- class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition font-medium">
- <p class="text-xs text-red-500 mt-1 hidden font-medium" id="upiError">Invalid UPI ID format.</p>
- </div>
- <!-- Amount & Note Group -->
- <div class="grid grid-cols-2 gap-4">
- <div>
- <label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">Amount (₹)</label>
- <input type="number" id="upiAmount" placeholder="0.00"
- class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition font-medium">
- </div>
- <div>
- <label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">Note</label>
- <input type="text" id="upiNote" placeholder="Bill / Gift"
- class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition font-medium">
- </div>
- </div>
- <button onclick="generateZPayLink()"
- class="w-full bg-slate-900 hover:bg-indigo-600 text-white font-bold py-4 rounded-xl transition-all duration-300 shadow-lg hover:shadow-indigo-500/30 transform hover:-translate-y-1">
- Generate ZPAY Link
- </button>
- </div>
- </div>
- <!-- Right Column: Results -->
- <div class="bg-white p-6 md:p-8 rounded-3xl shadow-xl shadow-slate-200/50 border border-slate-100 relative overflow-hidden min-h-[400px] flex flex-col justify-center">
-
- <!-- Placeholder State -->
- <div id="placeholder-state" class="flex flex-col items-center justify-center text-center">
- <div class="w-20 h-20 bg-slate-50 rounded-full flex items-center justify-center mb-4">
- <svg class="w-10 h-10 text-slate-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"></path></svg>
- </div>
- <h3 class="text-lg font-semibold text-slate-800">No Link Generated Yet</h3>
- <p class="text-slate-500 text-sm mt-1 max-w-xs">Enter your payment details on the left to create a unique QR and smart link.</p>
- </div>
- <!-- Result State -->
- <div id="result-state" class="hidden fade-in w-full">
- <div class="text-center mb-6">
- <span class="inline-block px-3 py-1 bg-green-100 text-green-700 text-xs font-bold uppercase rounded-full tracking-wide">Ready to Pay</span>
- <h2 class="text-2xl font-bold mt-2 text-slate-900" id="displayPayee">Payee Name</h2>
- <p class="text-2xl font-light text-slate-500" id="displayAmount">₹0</p>
- </div>
- <!-- QR Container -->
- <div class="flex justify-center mb-8">
- <div class="p-4 bg-white rounded-2xl border-2 border-dashed border-indigo-200 shadow-sm relative group">
- <div id="qrcode"></div>
- <!-- Logo Overlay on QR (Optional visual flair) -->
- <div class="absolute inset-0 flex items-center justify-center pointer-events-none opacity-10">
- <svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24"><path d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
- </div>
- </div>
- </div>
- <!-- Shareable Link -->
- <div class="bg-slate-50 rounded-xl p-4 border border-slate-200">
- <label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">Unique Smart Link</label>
- <div class="flex items-center gap-2">
- <input type="text" id="shareableLink" readonly
- class="w-full bg-white border border-slate-200 text-slate-600 text-sm rounded-lg px-3 py-2 focus:outline-none select-all">
- <button onclick="copyLink()"
- class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition whitespace-nowrap">
- Copy
- </button>
- </div>
- <p id="copyFeedback" class="text-center text-green-600 text-xs font-bold mt-2 opacity-0 transition-opacity">Copied to clipboard!</p>
- </div>
- </div>
- </div>
- </div>
-
- <div class="mt-12 text-center">
- <p class="text-slate-400 text-sm">Build by Parv Ashwani. ZPAY runs 100% in your browser. No data is stored.</p>
- </div>
- </main>
- <!-- Application Logic -->
- <script>
- // Check for Shared URL Parameters on Load
- window.addEventListener('load', () => {
- if(window.location.hash) {
- try {
- // Extract data from hash (remove the #)
- const hash = window.location.hash.substring(1);
- // Decode Base64 string to JSON
- const data = JSON.parse(decodeURIComponent(escape(atob(hash))));
-
- // Fill inputs
- document.getElementById('upiName').value = data.pn || '';
- document.getElementById('upiId').value = data.pa || '';
- document.getElementById('upiAmount').value = data.am || '';
- document.getElementById('upiNote').value = data.tn || '';
- // Auto Generate view
- generateZPayLink(true); // true = viewing mode
- } catch (e) {
- console.error("Invalid Link Data", e);
- // Clear hash if invalid
- history.pushState("", document.title, window.location.pathname);
- }
- }
- });
- function generateZPayLink(isViewMode = false) {
- // 1. Get Values
- const name = document.getElementById('upiName').value.trim();
- const vpa = document.getElementById('upiId').value.trim();
- const amount = document.getElementById('upiAmount').value.trim();
- const note = document.getElementById('upiNote').value.trim();
- const errorMsg = document.getElementById('upiError');
- // 2. Validation
- if (!vpa || !vpa.includes('@')) {
- errorMsg.classList.remove('hidden');
- // Shake animation for input
- document.getElementById('upiId').classList.add('ring-2', 'ring-red-500');
- setTimeout(() => document.getElementById('upiId').classList.remove('ring-2', 'ring-red-500'), 500);
- return;
- }
- if(!name) {
- alert("Please enter a Payee Name");
- return;
- }
- errorMsg.classList.add('hidden');
- // 3. Construct Raw UPI String (for QR)
- // upi://pay?pa=address&pn=name&am=amount&tn=note&cu=INR
- let upiString = `upi://pay?pa=${vpa}&pn=${encodeURIComponent(name)}&cu=INR`;
- if (amount) upiString += `&am=${amount}`;
- if (note) upiString += `&tn=${encodeURIComponent(note)}`;
- // 4. Construct Unique Smart Link (Base64 Encoded Hash)
- // We encode the data object into a base64 string to create a shareable URL
- const dataObj = { pa: vpa, pn: name, am: amount, tn: note };
- // utf-8 safe base64 encoding
- const hashPayload = btoa(unescape(encodeURIComponent(JSON.stringify(dataObj))));
- const uniqueUrl = `${window.location.origin}${window.location.pathname}#${hashPayload}`;
- // 5. Update UI
- document.getElementById('displayPayee').innerText = name;
- document.getElementById('displayAmount').innerText = amount ? `₹${amount}` : 'Enter Amount';
- document.getElementById('shareableLink').value = uniqueUrl;
- // Toggle Views
- document.getElementById('placeholder-state').classList.add('hidden');
- document.getElementById('result-state').classList.remove('hidden');
- // 6. Generate QR Code
- const qrContainer = document.getElementById("qrcode");
- qrContainer.innerHTML = ""; // Clear old
-
- new QRCode(qrContainer, {
- text: upiString,
- width: 180,
- height: 180,
- colorDark : "#1e293b", // Slate-800
- colorLight : "#ffffff",
- correctLevel : QRCode.CorrectLevel.M
- });
- // If user clicked Generate, scroll to result on mobile
- if(!isViewMode && window.innerWidth < 768) {
- document.getElementById('result-state').scrollIntoView({behavior: 'smooth'});
- }
- }
- function copyLink() {
- const copyText = document.getElementById("shareableLink");
- copyText.select();
- copyText.setSelectionRange(0, 99999);
- navigator.clipboard.writeText(copyText.value).then(() => {
- const feedback = document.getElementById('copyFeedback');
- feedback.style.opacity = '1';
- setTimeout(() => feedback.style.opacity = '0', 2000);
- });
- }
- function resetApp() {
- // Clear inputs and URL hash
- document.getElementById('upiName').value = '';
- document.getElementById('upiId').value = '';
- document.getElementById('upiAmount').value = '';
- document.getElementById('upiNote').value = '';
- history.pushState("", document.title, window.location.pathname);
-
- // Reset View
- document.getElementById('result-state').classList.add('hidden');
- document.getElementById('placeholder-state').classList.remove('hidden');
- }
- </script>
- </body>
- </html>
|