forked from Tadrisino/MathApp_frontend
Payment from the Android app: reach Zarinpal through pay.html so it carries our Referer
The app hands every outside URL to the phone browser, which sends no Referer, and Zarinpal's StartPay refuses a request without our domain as Referer («دسترسی از این دامنه مجاز نمیباشد»). Checked against production on a pending order: no Referer → refused; Referer https://math.tadrisino2.ir/ → forwards to the bank (sep.shaparak.ir). - pay.html + src/pay.ts: opened in the phone browser, forwards only to our own /api/payment/orders/<id>/pay link (anything else is refused), so the Referer is math.tadrisino2.ir. The app opens it as http:// (the WebView keeps only https on its own host); ArvanCloud moves it to https, and the link rides in the fragment, which never reaches a server. - MainActivity.kt (next APK): payment pages (our subdomains, zarinpal, shaparak) stay inside the WebView and `paysInApp()` tells the frontend to navigate directly. Not built. Not checked: a full payment on a real phone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
50
src/pay.ts
Normal file
50
src/pay.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { resolveApiBaseUrl } from '@/config';
|
||||
|
||||
/**
|
||||
* صفحهی واسطهی پرداخت (pay.html)، برای اپِ اندرویدی که درگاه را در مرورگرِ گوشی باز میکند.
|
||||
*
|
||||
* زرینپال صفحهی StartPay را فقط وقتی باز میکند که Referer از دامنهی ثبتشده باشد؛ بیآن میگوید «دسترسی از این
|
||||
* دامنه مجاز نمیباشد» (روی سرور با درخواستِ بیReferer دیده شد، ۱۴۰۵-۰۷). مرورگری که اپِ دیگری بازش کرده Referer
|
||||
* نمیفرستد. پس اپ اول همین صفحه را در مرورگر باز میکند و این صفحه کاربر را به لینکِ پرداخت میفرستد؛ حالا Referer
|
||||
* از math.tadrisino2.ir است، درست مثلِ پرداخت از سایت.
|
||||
*
|
||||
* فقط لینکِ پرداختِ API خودمان پذیرفته میشود (پیشوندِ آدرسِ API + /payment/orders/<id>/pay)، تا این صفحه
|
||||
* ریدایرکتِ باز به هر جایی نشود.
|
||||
*/
|
||||
const msg = document.getElementById('msg')!;
|
||||
const spin = document.getElementById('spin')!;
|
||||
const go = document.getElementById('go') as HTMLAnchorElement;
|
||||
|
||||
function fail(text: string) {
|
||||
spin.style.display = 'none';
|
||||
msg.textContent = text;
|
||||
}
|
||||
|
||||
// در fragment میآید، نه query: اپ این صفحه را با http:// باز میکند (پایینِ SubscriptionScreen) و fragment هرگز
|
||||
// به سرور فرستاده نمیشود، پس امضای لینکِ پرداخت روی آن یک گامِ بیرمز دیده نمیشود.
|
||||
const raw = new URLSearchParams(location.hash.slice(1)).get('u') ?? '';
|
||||
let target: URL | null = null;
|
||||
try {
|
||||
target = new URL(raw);
|
||||
} catch {
|
||||
target = null;
|
||||
}
|
||||
const api = new URL(resolveApiBaseUrl() + '/', location.origin);
|
||||
const ok =
|
||||
target !== null &&
|
||||
target.protocol === 'https:' &&
|
||||
target.origin === api.origin &&
|
||||
new RegExp(`^${api.pathname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}payment/orders/\\d+/pay$`).test(target.pathname);
|
||||
|
||||
if (!ok || !target) {
|
||||
fail('این لینکِ پرداخت معتبر نیست. به اپ برگرد و دوباره «پرداخت» رو بزن.');
|
||||
} else {
|
||||
go.href = target.href;
|
||||
location.replace(target.href);
|
||||
// اگر مرورگر جابهجا نشد (مثلاً جلوی ریدایرکت را گرفت)، دکمهی دستی.
|
||||
setTimeout(() => {
|
||||
spin.style.display = 'none';
|
||||
msg.textContent = 'اگه صفحهی پرداخت باز نشد، این دکمه رو بزن:';
|
||||
go.style.display = 'inline-block';
|
||||
}, 2500);
|
||||
}
|
||||
Reference in New Issue
Block a user