[Feature Request] Waffo Pancake gateway — full integration with subscription support + admin catalog binding flow (#4935)
This commit is contained in:
+7
-22
@@ -53,16 +53,9 @@ const PaymentSetting = () => {
|
||||
StripeMinTopUp: 1,
|
||||
StripePromotionCodesEnabled: false,
|
||||
|
||||
WaffoPancakeEnabled: false,
|
||||
WaffoPancakeSandbox: false,
|
||||
WaffoPancakeMerchantID: '',
|
||||
WaffoPancakePrivateKey: '',
|
||||
WaffoPancakeStoreID: '',
|
||||
WaffoPancakeProductID: '',
|
||||
WaffoPancakeReturnURL: '',
|
||||
WaffoPancakeCurrency: 'USD',
|
||||
WaffoPancakeUnitPrice: 1.0,
|
||||
WaffoPancakeMinTopUp: 1,
|
||||
'payment_setting.compliance_confirmed': false,
|
||||
'payment_setting.compliance_terms_version': '',
|
||||
'payment_setting.compliance_confirmed_at': 0,
|
||||
@@ -171,21 +164,13 @@ const PaymentSetting = () => {
|
||||
case 'MinTopUp':
|
||||
case 'StripeUnitPrice':
|
||||
case 'StripeMinTopUp':
|
||||
case 'WaffoPancakeUnitPrice':
|
||||
case 'WaffoPancakeMinTopUp':
|
||||
newInputs[item.key] = parseFloat(item.value);
|
||||
break;
|
||||
case 'WaffoPancakeMerchantID':
|
||||
case 'WaffoPancakePrivateKey':
|
||||
case 'WaffoPancakeStoreID':
|
||||
case 'WaffoPancakeProductID':
|
||||
case 'WaffoPancakeReturnURL':
|
||||
case 'WaffoPancakeCurrency':
|
||||
newInputs[item.key] = item.value;
|
||||
break;
|
||||
case 'WaffoPancakeSandbox':
|
||||
newInputs[item.key] = toBoolean(item.value);
|
||||
break;
|
||||
default:
|
||||
if (item.key.endsWith('Enabled')) {
|
||||
newInputs[item.key] = toBoolean(item.value);
|
||||
@@ -320,6 +305,13 @@ const PaymentSetting = () => {
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Waffo Pancake 设置')} itemKey='waffo-pancake'>
|
||||
<SettingsPaymentGatewayWaffoPancake
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Waffo 设置')} itemKey='waffo'>
|
||||
<SettingsPaymentGatewayWaffo
|
||||
options={inputs}
|
||||
@@ -327,13 +319,6 @@ const PaymentSetting = () => {
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
{/*<Tabs.TabPane tab={t('Waffo Pancake 设置')} itemKey='waffo-pancake'>*/}
|
||||
{/* <SettingsPaymentGatewayWaffoPancake*/}
|
||||
{/* options={inputs}*/}
|
||||
{/* refresh={onRefresh}*/}
|
||||
{/* hideSectionTitle*/}
|
||||
{/* />*/}
|
||||
{/*</Tabs.TabPane>*/}
|
||||
</Tabs>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
+14
-3
@@ -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
@@ -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('支付请求失败'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user