Merge pull request #1823 from littlewrite/feat_subscribe_sp1

新增 creem 支付
This commit is contained in:
Seefs
2025-10-28 18:37:32 +09:00
committed by GitHub
12 changed files with 1109 additions and 5 deletions

View File

@@ -22,6 +22,7 @@ import { Card, Spin } from '@douyinfe/semi-ui';
import SettingsGeneralPayment from '../../pages/Setting/Payment/SettingsGeneralPayment';
import SettingsPaymentGateway from '../../pages/Setting/Payment/SettingsPaymentGateway';
import SettingsPaymentGatewayStripe from '../../pages/Setting/Payment/SettingsPaymentGatewayStripe';
import SettingsPaymentGatewayCreem from '../../pages/Setting/Payment/SettingsPaymentGatewayCreem';
import { API, showError, toBoolean } from '../../helpers';
import { useTranslation } from 'react-i18next';
@@ -142,6 +143,9 @@ const PaymentSetting = () => {
<Card style={{ marginTop: '10px' }}>
<SettingsPaymentGatewayStripe options={inputs} refresh={onRefresh} />
</Card>
<Card style={{ marginTop: '10px' }}>
<SettingsPaymentGatewayCreem options={inputs} refresh={onRefresh} />
</Card>
</Spin>
</>
);

View File

@@ -52,6 +52,9 @@ const RechargeCard = ({
t,
enableOnlineTopUp,
enableStripeTopUp,
enableCreemTopUp,
creemProducts,
creemPreTopUp,
presetAmounts,
selectedPreset,
selectPresetAmount,
@@ -84,6 +87,7 @@ const RechargeCard = ({
const onlineFormApiRef = useRef(null);
const redeemFormApiRef = useRef(null);
const showAmountSkeleton = useMinimumLoadingTime(amountLoading);
console.log(' enabled screem ?', enableCreemTopUp, ' products ?', creemProducts);
return (
<Card className='!rounded-2xl shadow-sm border-0'>
{/* 卡片头部 */}
@@ -216,7 +220,7 @@ const RechargeCard = ({
<div className='py-8 flex justify-center'>
<Spin size='large' />
</div>
) : enableOnlineTopUp || enableStripeTopUp ? (
) : enableOnlineTopUp || enableStripeTopUp || enableCreemTopUp ? (
<Form
getFormApi={(api) => (onlineFormApiRef.current = api)}
initValues={{ topUpCount: topUpCount }}
@@ -480,6 +484,32 @@ const RechargeCard = ({
</div>
</Form.Slot>
)}
{/* Creem 充值区域 */}
{enableCreemTopUp && creemProducts.length > 0 && (
<Form.Slot label={t('Creem 充值')}>
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3'>
{creemProducts.map((product, index) => (
<Card
key={index}
onClick={() => creemPreTopUp(product)}
className='cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300'
bodyStyle={{ textAlign: 'center', padding: '16px' }}
>
<div className='font-medium text-lg mb-2'>
{product.name}
</div>
<div className='text-sm text-gray-600 mb-2'>
{t('充值额度')}: {product.quota}
</div>
<div className='text-lg font-semibold text-blue-600'>
{product.currency === 'EUR' ? '€' : '$'}{product.price}
</div>
</Card>
))}
</div>
</Form.Slot>
)}
</div>
</Form>
) : (

View File

@@ -63,6 +63,12 @@ const TopUp = () => {
);
const [statusLoading, setStatusLoading] = useState(true);
// Creem 相关状态
const [creemProducts, setCreemProducts] = useState([]);
const [enableCreemTopUp, setEnableCreemTopUp] = useState(false);
const [creemOpen, setCreemOpen] = useState(false);
const [selectedCreemProduct, setSelectedCreemProduct] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [open, setOpen] = useState(false);
const [payWay, setPayWay] = useState('');
@@ -248,6 +254,55 @@ const TopUp = () => {
}
};
const creemPreTopUp = async (product) => {
if (!enableCreemTopUp) {
showError(t('管理员未开启 Creem 充值!'));
return;
}
setSelectedCreemProduct(product);
setCreemOpen(true);
};
const onlineCreemTopUp = async () => {
if (!selectedCreemProduct) {
showError(t('请选择产品'));
return;
}
// Validate product has required fields
if (!selectedCreemProduct.productId) {
showError(t('产品配置错误,请联系管理员'));
return;
}
setConfirmLoading(true);
try {
const res = await API.post('/api/user/creem/pay', {
product_id: selectedCreemProduct.productId,
payment_method: 'creem',
});
if (res !== undefined) {
const { message, data } = res.data;
if (message === 'success') {
processCreemCallback(data);
} else {
showError(data);
}
} else {
showError(res);
}
} catch (err) {
console.log(err);
showError(t('支付请求失败'));
} finally {
setCreemOpen(false);
setConfirmLoading(false);
}
};
const processCreemCallback = (data) => {
// 与 Stripe 保持一致的实现方式
window.open(data.checkout_url, '_blank');
};
const getUserQuota = async () => {
let res = await API.get(`/api/user/self`);
const { success, message, data } = res.data;
@@ -322,6 +377,7 @@ const TopUp = () => {
setPayMethods(payMethods);
const enableStripeTopUp = data.enable_stripe_topup || false;
const enableOnlineTopUp = data.enable_online_topup || false;
const enableCreemTopUp = data.enable_creem_topup || false;
const minTopUpValue = enableOnlineTopUp
? data.min_topup
: enableStripeTopUp
@@ -329,9 +385,20 @@ const TopUp = () => {
: 1;
setEnableOnlineTopUp(enableOnlineTopUp);
setEnableStripeTopUp(enableStripeTopUp);
setEnableCreemTopUp(enableCreemTopUp);
setMinTopUp(minTopUpValue);
setTopUpCount(minTopUpValue);
// 设置 Creem 产品
try {
console.log(' data is ?', data);
console.log(' creem products is ?', data.creem_products);
const products = JSON.parse(data.creem_products || '[]');
setCreemProducts(products);
} catch (e) {
setCreemProducts([]);
}
// 如果没有自定义充值数量选项,根据最小充值金额生成预设充值额度选项
if (topupInfo.amount_options.length === 0) {
setPresetAmounts(generatePresetAmounts(minTopUpValue));
@@ -500,6 +567,11 @@ const TopUp = () => {
setOpenHistory(false);
};
const handleCreemCancel = () => {
setCreemOpen(false);
setSelectedCreemProduct(null);
};
// 选择预设充值额度
const selectPresetAmount = (preset) => {
setTopUpCount(preset.value);
@@ -563,6 +635,33 @@ const TopUp = () => {
t={t}
/>
{/* Creem 充值确认模态框 */}
<Modal
title={t('确定要充值 $')}
visible={creemOpen}
onOk={onlineCreemTopUp}
onCancel={handleCreemCancel}
maskClosable={false}
size='small'
centered
confirmLoading={confirmLoading}
>
{selectedCreemProduct && (
<>
<p>
{t('产品名称')}{selectedCreemProduct.name}
</p>
<p>
{t('价格')}{selectedCreemProduct.currency === 'EUR' ? '€' : '$'}{selectedCreemProduct.price}
</p>
<p>
{t('充值额度')}{selectedCreemProduct.quota}
</p>
<p>{t('是否确认充值?')}</p>
</>
)}
</Modal>
{/* 用户信息头部 */}
<div className='space-y-6'>
<div className='grid grid-cols-1 lg:grid-cols-12 gap-6'>
@@ -572,6 +671,9 @@ const TopUp = () => {
t={t}
enableOnlineTopUp={enableOnlineTopUp}
enableStripeTopUp={enableStripeTopUp}
enableCreemTopUp={enableCreemTopUp}
creemProducts={creemProducts}
creemPreTopUp={creemPreTopUp}
presetAmounts={presetAmounts}
selectedPreset={selectedPreset}
selectPresetAmount={selectPresetAmount}