Files
rnpay/upi.html
2026-04-12 02:49:31 +08:00

354 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UPI Pay Test</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f0f2f5;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 32px 16px;
gap: 20px;
}
h2 { color: #1a1a2e; font-size: 20px; }
.card {
background: #fff;
border-radius: 16px;
padding: 20px;
width: 100%;
max-width: 380px;
box-shadow: 0 4px 12px rgba(0,0,0,.08);
display: flex;
flex-direction: column;
gap: 14px;
}
label { font-size: 13px; color: #888; display: block; margin-bottom: 6px; }
input, select {
border: 1.5px solid #e0e0e0;
border-radius: 10px;
padding: 10px 14px;
font-size: 15px;
width: 100%;
outline: none;
background: #fafafa;
appearance: none;
}
input:focus, select:focus { border-color: #5f259f; }
.badge {
display: inline-block;
font-size: 10px;
padding: 2px 7px;
border-radius: 20px;
font-weight: 600;
margin-left: 6px;
vertical-align: middle;
}
.badge-biz { background: #e8f5e9; color: #2e7d32; }
.badge-pers { background: #e3f2fd; color: #1565c0; }
.link-box {
background: #f5f0ff;
border-radius: 10px;
padding: 12px;
font-size: 11px;
color: #5f259f;
word-break: break-all;
line-height: 1.7;
min-height: 44px;
}
.warn-box {
background: #fff3e0;
border-radius: 10px;
padding: 10px 12px;
font-size: 12px;
color: #e65100;
display: none;
}
.pay-btn {
display: block;
width: 100%;
padding: 14px;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 700;
color: #fff;
cursor: pointer;
text-align: center;
text-decoration: none;
transition: opacity .15s, transform .1s;
}
.pay-btn:active { opacity: .85; transform: scale(.98); }
.c-paytm { background: linear-gradient(135deg, #002970, #00baf2); }
.c-phonepe { background: linear-gradient(135deg, #5f259f, #7b3fc4); }
.c-mobikwik { background: linear-gradient(135deg, #1565c0, #42a5f5); }
.c-freecharge { background: linear-gradient(135deg, #00897b, #4db6ac); }
.c-upi { background: linear-gradient(135deg, #37474f, #78909c); }
.qr-wrap { text-align: center; }
.qr-wrap p { font-size: 13px; color: #888; margin-bottom: 10px; }
#qrcode {
display: inline-block;
background: #fff;
padding: 12px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,.1);
}
</style>
</head>
<body>
<h2>UPI Pay Test</h2>
<div class="card">
<div>
<label>Wallet</label>
<select id="wallet">
<optgroup label="── Business ──">
<option value="gpay_biz">Google Pay Business</option>
<option value="paytm_biz">Paytm Business</option>
<option value="phonepe_biz">PhonePe Business</option>
<option value="bharatpe_biz">BharatPe Business</option>
</optgroup>
<optgroup label="── Personal ──">
<option value="paytm_per">Paytm Personal</option>
<option value="phonepe_per">PhonePe Personal</option>
<option value="mobikwik_per">MobiKwik Personal</option>
<option value="freecharge_per">Freecharge Personal</option>
</optgroup>
</select>
</div>
<div>
<label>Payment App</label>
<select id="app">
<option value="paytm">Paytm</option>
<option value="phonepe">PhonePe (native p2p)</option>
<option value="phonepe_pay">PhonePe (pay)</option>
<option value="mobikwik">MobiKwik</option>
<option value="freecharge">Freecharge</option>
<option value="upi">Other UPI</option>
</select>
</div>
<div>
<label>Amount ₹</label>
<input id="amount" type="number" value="51" min="50" step="1">
</div>
<div style="background:#f9f9f9;border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:6px;">
<div style="display:flex;justify-content:space-between;font-size:13px;">
<span style="color:#888;">Receiver (Payee)</span>
<span id="info-pn" style="font-weight:600;color:#1a1a2e;text-align:right;max-width:60%;"></span>
</div>
<div style="display:flex;justify-content:space-between;font-size:13px;">
<span style="color:#888;">UPI ID</span>
<span id="info-pa" style="font-weight:600;color:#5f259f;font-size:12px;text-align:right;max-width:60%;word-break:break-all;"></span>
</div>
<div style="display:flex;justify-content:space-between;font-size:13px;">
<span style="color:#888;">Amount</span>
<span id="info-am" style="font-weight:600;color:#1a1a2e;"></span>
</div>
</div>
<div id="warn-box" class="warn-box"></div>
<div>
<label>Deep Link</label>
<div class="link-box" id="link-display"></div>
</div>
<a class="pay-btn c-paytm" id="pay-btn" href="#">Pay Now</a>
</div>
<div class="qr-wrap">
<p>UPI QR</p>
<div id="qrcode"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
<script>
const WALLETS = {
gpay_biz: {
label: 'Google Pay Business',
pa: '7528905079@okbizaxis', pn: 'Kularenterprises',
extra: (p) => `&tr=BCR2DN4TXXWO5PRF&tn=tH0yMnoPPH`,
warn: null,
},
paytm_biz: {
label: 'Paytm Business',
pa: 'paytm.s20dk3t@pty', pn: 'Paytm',
extra: () => '',
warn: null,
},
phonepe_biz: {
label: 'PhonePe Business',
pa: 'Q465518940@ybl', pn: 'PhonePeMerchant',
extra: () => '&mc=0000&mode=02&purpose=00',
warn: 'QR-only merchant — link payment may be rejected. Scan QR instead.',
},
bharatpe_biz: {
label: 'BharatPe Business',
pa: 'BHARATPE.8A0T1R2O5E77327@fbpe', pn: 'BharatPe Merchant',
extra: () => '&tn=Pay%20To%20BharatPe%20Merchant',
warn: null,
},
paytm_per: {
label: 'Paytm Personal',
pa: 'optimism2@ptyes', pn: 'Gurvir Singh',
extra: () => '',
warn: null,
},
phonepe_per: {
label: 'PhonePe Personal',
pa: '7528905079@ybl', pn: 'Gurvir Singh',
extra: () => '',
warn: null,
},
mobikwik_per: {
label: 'MobiKwik Personal',
pa: '7528905079-1@ikwik', pn: 'Gurvir Singh',
extra: () => '',
warn: null,
},
freecharge_per: {
label: 'Freecharge Personal',
pa: 'simple6812@freecharge', pn: 'Gurvir Singh',
extra: () => '',
warn: null,
},
};
function phonepeNative(pa, pn, am, note) {
const payload = {
contact: { cbsName: '', nickName: pn, vpa: pa, type: 'VPA' },
p2pPaymentCheckoutParams: {
note: note || 'payment',
isByDefaultKnownContact: true,
enableSpeechToText: false,
allowAmountEdit: false,
showQrCodeOption: false,
disableViewHistory: true,
shouldShowUnsavedContactBanner: false,
isRecurring: false,
checkoutType: 'DEFAULT',
transactionContext: 'p2p',
initialAmount: Math.round(parseFloat(am) * 100), // paise
disableNotesEdit: true,
showKeyboard: true,
currency: 'INR',
shouldShowMaskedNumber: true,
},
};
return `phonepe://native?data=${encodeURIComponent(btoa(JSON.stringify(payload)))}&id=p2ppayment`;
}
const APP_SCHEMES = {
paytm: (pa, pn, am, extra) => `paytmmp://cash_wallet?featuretype=money_transfer&pa=${pa}&pn=${pn}&am=${am}&cu=INR&mc=0000&mode=02&purpose=00&orgid=159002${extra}`,
phonepe: (pa, pn, am) => phonepeNative(pa, pn, am, 'payment'),
phonepe_pay: (pa, pn, am, extra) => `phonepe://pay?pa=${pa}&pn=${pn}&am=${am}&cu=INR${extra}`,
mobikwik: (pa, pn, am, extra) => { const tn = (extra.match(/[?&]tn=([^&]*)/) || [])[1] || 'payment'; return `mobikwik://moneytransfer/upi/verifyVpa?vpa=${encodeURIComponent(pa)}&amount=${am}&note=${tn}`; },
freecharge: (pa, pn, am, extra) => `freechargeupi://pay?pa=${pa}&pn=${pn}&am=${am}&cu=INR${extra}`,
// fallback 在 scheduleAutoClick 里处理
upi: (pa, pn, am, extra) => `upi://pay?pa=${pa}&pn=${pn}&am=${am}&cu=INR${extra}`,
};
const APP_COLORS = {
paytm: 'c-paytm', phonepe: 'c-phonepe', phonepe_pay: 'c-phonepe',
mobikwik: 'c-mobikwik', freecharge: 'c-freecharge', upi: 'c-upi',
};
const PHONEPE_WALLETS = new Set(['phonepe_biz', 'phonepe_per']);
function updateAppOptions(walletKey) {
const isPhonePeWallet = PHONEPE_WALLETS.has(walletKey);
const appSel = document.getElementById('app');
Array.from(appSel.options).forEach(opt => {
const isPhonePeOpt = opt.value === 'phonepe' || opt.value === 'phonepe_pay';
opt.disabled = isPhonePeOpt && !isPhonePeWallet;
});
// auto-switch away from disabled option
if (appSel.options[appSel.selectedIndex].disabled) {
appSel.value = 'paytm';
}
// auto-select recommended PhonePe scheme
if (isPhonePeWallet && appSel.value !== 'phonepe' && appSel.value !== 'phonepe_pay') {
appSel.value = walletKey === 'phonepe_biz' ? 'phonepe_pay' : 'phonepe';
}
}
function update() {
updateAppOptions(document.getElementById('wallet').value);
const walletKey = document.getElementById('wallet').value;
const app = document.getElementById('app').value;
const am = document.getElementById('amount').value || '51';
const w = WALLETS[walletKey];
const warnEl = document.getElementById('warn-box');
let warnMsg = w.warn || '';
if (walletKey === 'phonepe_biz' && app === 'phonepe') {
warnMsg = (warnMsg ? warnMsg + '\n' : '') + '提示PhonePe Business 建议用 PhonePe (pay) 而非 native p2p。';
}
if (walletKey === 'phonepe_per' && app === 'phonepe_pay') {
warnMsg = (warnMsg ? warnMsg + '\n' : '') + '提示PhonePe 个人收款建议用 PhonePe (native p2p)。';
}
if (warnMsg) {
warnEl.textContent = warnMsg.startsWith('⚠') ? warnMsg : '⚠ ' + warnMsg;
warnEl.style.display = 'block';
} else {
warnEl.style.display = 'none';
}
const link = w.pa
? APP_SCHEMES[app](w.pa, encodeURIComponent(w.pn), am, w.extra())
: '';
document.getElementById('info-pn').textContent = w.pn || '—';
document.getElementById('info-pa').textContent = w.pa || '—';
document.getElementById('info-am').textContent = w.pa ? `${am}` : '—';
document.getElementById('link-display').textContent = link || '—';
const btn = document.getElementById('pay-btn');
btn.href = link || '#';
btn.className = `pay-btn ${APP_COLORS[app]}`;
const el = document.getElementById('qrcode');
el.innerHTML = '';
if (link) {
new QRCode(el, { text: link, width: 200, height: 200, correctLevel: QRCode.CorrectLevel.M });
}
}
let autoTimer = null;
function scheduleAutoClick() {
if (autoTimer) clearTimeout(autoTimer);
autoTimer = setTimeout(() => {
const btn = document.getElementById('pay-btn');
if (!btn.href || btn.href === '#' || btn.href.endsWith('#')) return;
const app = document.getElementById('app').value;
if (app === 'freecharge') {
// freechargeupi:// 不可用时 fallback 到 upi://
const fallbackTimer = setTimeout(() => {
const w = WALLETS[document.getElementById('wallet').value];
const am = document.getElementById('amount').value || '51';
const fallback = `upi://pay?pa=${w.pa}&pn=${encodeURIComponent(w.pn)}&am=${am}&cu=INR${w.extra()}`;
window.location.href = fallback;
}, 2500);
window.location.href = btn.href;
// 如果页面还在没跳走说明跳转失败fallback 会触发
window.addEventListener('blur', () => clearTimeout(fallbackTimer), { once: true });
} else {
btn.click();
}
}, 1000);
}
document.getElementById('wallet').addEventListener('change', () => { update(); scheduleAutoClick(); });
document.getElementById('app').addEventListener('change', () => { update(); scheduleAutoClick(); });
document.getElementById('amount').addEventListener('input', update);
update();
</script>
</body>
</html>