|
@@ -0,0 +1,268 @@
|
|
|
|
|
+<!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>
|