Files
rnpay/upi.html
2026-05-17 18:49:39 +08:00

449 lines
16 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-gpay {
background: linear-gradient(135deg, #1a73e8, #34a853);
}
.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>
<optgroup label="── Other ──">
<option value="boi">BOI Bank</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="gpay">Google 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="10" min="10" 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>
<button type="button" class="pay-btn c-phonepe" id="phonepe-bank-btn" style="border:none;font:inherit;">PhonePe 银行卡测试ACCOUNT</button>
</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: { // phonepe ok
label: 'Google Pay Business',
pa: '7528905079@okbizaxis', pn: 'Kularenterprises',
extra: (p) => `&tr=BCR2DN4TXXWO5PRF&tn=tH0yMnoPPH`,
warn: null,
},
paytm_biz: { // phonepe ok
label: 'Paytm Business',
pa: 'paytm.s20dk3t@pty', pn: 'Paytm',
extra: () => '',
warn: null,
},
phonepe_biz: { // not known
label: 'PhonePe Business',
pa: 'Q631414685@ybl', pn: 'PhonePeMerchant',
extra: () => '&mc=0000&mode=02&purpose=00',
warn: 'QR-only merchant — link payment may be rejected. Scan QR instead.',
},
bharatpe_biz: { // can't work
label: 'BharatPe Business',
pa: 'BHARATPE.8000605521@fbpe', pn: 'BharatPe Merchant',
extra: () => '&tn=Pay+To+BharatPe+Merchant',
warn: null,
},
paytm_per: {
label: 'Paytm Personal',
pa: 'optimism2@ptyes', pn: 'Gurvir Singh',
extra: () => '',
warn: null,
},
phonepe_per: {
label: 'PhonePe Personal',
pa: '7796806838@ibl', pn: '',
extra: () => '',
warn: null,
},
mobikwik_per: {
label: 'MobiKwik Personal',
pa: '9707705376@mbkns', pn: 'Mr Sonaullah Ali',
extra: () => '',
warn: null,
},
freecharge_per: {
label: 'Freecharge Personal',
pa: 'levi11111@freecharge', pn: 'Niranjan Mishra',
extra: () => '',
warn: null,
},
boi: {
label: 'BOI Bank',
pa: 'boim-475447851314@boi',
pn: '',
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`;
}
function phonepeBankAccountTestLink() {
const payload = {
contact: {
type: 'ACCOUNT',
accountHolderName: 'rehana',
accountNumber: '2053426110',
ifscCode: 'KKBK0000811',
bankId: 'KKBK',
nickName: 'rehana',
isVerifiedBankAccount: false,
},
p2pPaymentCheckoutParams: {
checkoutType: 'DEFAULT',
note: 'hapock',
isByDefaultKnownContact: true,
disableViewHistory: true,
shouldShowUnsavedContactBanner: false,
shouldShowMaskedNumber: true,
showKeyboard: true,
initialAmount: 1000,
},
};
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}`,
gpay: (pa, pn, am, extra) => `gpay://upi/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',
gpay: 'c-gpay', 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 || '10';
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 });
}
}
document.getElementById('pay-btn').addEventListener('click', function (e) {
const href = this.getAttribute('href');
if (!href || href === '#') return;
e.preventDefault();
try {
window.location.href = href;
} catch (_) { }
});
document.getElementById('phonepe-bank-btn').addEventListener('click', function () {
try {
window.location.href = phonepeBankAccountTestLink();
} catch (_) { }
});
document.getElementById('wallet').addEventListener('change', update);
document.getElementById('app').addEventListener('change', update);
document.getElementById('amount').addEventListener('input', update);
update();
</script>
</body>
</html>