[Feature Request] Waffo Pancake gateway — full integration with subscription support + admin catalog binding flow (#4935)

This commit is contained in:
Hill-waffo
2026-05-22 11:00:58 +08:00
committed by GitHub
parent 8e5e89bb5b
commit 19f1821fc8
45 changed files with 2432 additions and 1086 deletions
+14 -3
View File
@@ -47,6 +47,7 @@ import {
} from 'lucide-react';
import { IconGift } from '@douyinfe/semi-icons';
import { useMinimumLoadingTime } from '../../hooks/common/useMinimumLoadingTime';
import { useActualTheme } from '../../context/Theme';
import { getCurrencyConfig } from '../../helpers/render';
import SubscriptionPlansCard from './SubscriptionPlansCard';
@@ -102,6 +103,7 @@ const RechargeCard = ({
const redeemFormApiRef = useRef(null);
const initialTabSetRef = useRef(false);
const showAmountSkeleton = useMinimumLoadingTime(amountLoading);
const actualTheme = useActualTheme();
const [activeTab, setActiveTab] = useState('topup');
const shouldShowSubscription =
!subscriptionLoading && subscriptionPlans.length > 0;
@@ -355,9 +357,18 @@ const RechargeCard = ({
}}
/>
) : payMethod.type === 'waffo_pancake' ? (
<CreditCard
size={18}
color='var(--semi-color-primary)'
<img
src={
actualTheme === 'dark'
? '/waffo-logo-dark.svg'
: '/waffo-logo-light.svg'
}
alt='Waffo'
style={{
width: 18,
height: 18,
objectFit: 'contain',
}}
/>
) : (
<CreditCard
+23 -2
View File
@@ -40,6 +40,23 @@ import TransferModal from './modals/TransferModal';
import PaymentConfirmModal from './modals/PaymentConfirmModal';
import TopupHistoryModal from './modals/TopupHistoryModal';
// Reject non-navigable schemes (e.g. javascript:, data:) and relative URLs.
// Only http / https are allowed for backend-provided redirect targets.
// Mirrors isSafeHttpCheckoutUrl in the default frontend's
// features/wallet/hooks/use-waffo-pancake-payment.ts.
function isSafeHttpCheckoutUrl(value) {
const trimmed = (value || '').trim();
if (!trimmed) {
return false;
}
try {
const u = new URL(trimmed);
return u.protocol === 'http:' || u.protocol === 'https:';
} catch {
return false;
}
}
const TopUp = () => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();
@@ -454,8 +471,12 @@ const TopUp = () => {
const { message, data } = res.data;
if (message === 'success') {
const checkoutUrl = data?.checkout_url || '';
if (checkoutUrl) {
window.open(checkoutUrl, '_blank');
if (checkoutUrl && isSafeHttpCheckoutUrl(checkoutUrl)) {
// In-tab redirect (not window.open) — popup blocker fires after
// the await loses user-gesture context.
window.location.href = checkoutUrl;
} else if (checkoutUrl) {
showError(t('支付跳转地址不安全'));
} else {
showError(t('支付请求失败'));
}