解决报名支付
All checks were successful
Deploy to Server / deploy (push) Successful in 36s

This commit is contained in:
jeremygan2021
2026-02-23 16:31:34 +08:00
parent 58176c6651
commit 799965ee74
7 changed files with 87 additions and 38 deletions

View File

@@ -0,0 +1,23 @@
# Generated by Django 6.0.1 on 2026-02-23 08:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('community', '0010_activity_is_paid_activity_price_activitysignup_order'),
]
operations = [
migrations.AddField(
model_name='activity',
name='auto_confirm',
field=models.BooleanField(default=False, help_text='开启后,付费活动支付成功或免费活动报名后直接显示报名成功,不需要审核', verbose_name='无需审核'),
),
migrations.AlterField(
model_name='activitysignup',
name='status',
field=models.CharField(choices=[('unpaid', '待支付'), ('pending', '审核中'), ('confirmed', '报名成功'), ('cancelled', '已取消')], default='confirmed', max_length=20, verbose_name='状态'),
),
]

View File

@@ -19,6 +19,7 @@ class Activity(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="报名费用") price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="报名费用")
is_active = models.BooleanField(default=True, verbose_name="是否启用") is_active = models.BooleanField(default=True, verbose_name="是否启用")
auto_confirm = models.BooleanField(default=False, verbose_name="无需审核", help_text="开启后,付费活动支付成功或免费活动报名后直接显示报名成功,不需要审核")
# 常用报名信息开关 # 常用报名信息开关
ask_name = models.BooleanField(default=False, verbose_name="收集姓名") ask_name = models.BooleanField(default=False, verbose_name="收集姓名")
@@ -72,6 +73,7 @@ class ActivitySignup(models.Model):
活动报名记录 活动报名记录
""" """
STATUS_CHOICES = ( STATUS_CHOICES = (
('unpaid', '待支付'),
('pending', '审核中'), ('pending', '审核中'),
('confirmed', '报名成功'), ('confirmed', '报名成功'),
('cancelled', '已取消'), ('cancelled', '已取消'),

View File

@@ -156,13 +156,14 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
if pending_signup: if pending_signup:
pending_signup.signup_info = signup_info pending_signup.signup_info = signup_info
pending_signup.order = order pending_signup.order = order
pending_signup.status = 'unpaid' # Explicitly set to unpaid
pending_signup.save() pending_signup.save()
else: else:
ActivitySignup.objects.create( ActivitySignup.objects.create(
activity=activity, activity=activity,
user=user, user=user,
signup_info=signup_info, signup_info=signup_info,
status='pending', status='unpaid',
order=order order=order
) )
@@ -177,11 +178,14 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
return Response({'error': '支付接口调用失败', 'detail': result}, status=500) return Response({'error': '支付接口调用失败', 'detail': result}, status=500)
# Free Activity Signup # Free Activity Signup
# Check auto_confirm
status_val = 'confirmed' if activity.auto_confirm else 'pending'
signup = ActivitySignup.objects.create( signup = ActivitySignup.objects.create(
activity=activity, activity=activity,
user=user, user=user,
signup_info=signup_info, signup_info=signup_info,
status='confirmed' status=status_val
) )
serializer = ActivitySignupSerializer(signup) serializer = ActivitySignupSerializer(signup)
return Response(serializer.data, status=201) return Response(serializer.data, status=201)

View File

@@ -100,8 +100,8 @@ DATABASES = {
} }
# 从环境变量获取数据库配置 (Docker 环境会自动注入这些变量) # 从环境变量获取数据库配置 (Docker 环境会自动注入这些变量)
#DB_HOST = os.environ.get('DB_HOST', '121.43.104.161') DB_HOST = os.environ.get('DB_HOST', '121.43.104.161')
DB_HOST = os.environ.get('DB_HOST', '6.6.6.66') #DB_HOST = os.environ.get('DB_HOST', '6.6.6.66')
if DB_HOST: if DB_HOST:
DATABASES['default'] = { DATABASES['default'] = {
'ENGINE': 'django.db.backends.postgresql', 'ENGINE': 'django.db.backends.postgresql',
@@ -109,8 +109,8 @@ if DB_HOST:
'USER': os.environ.get('DB_USER', 'market'), 'USER': os.environ.get('DB_USER', 'market'),
'PASSWORD': os.environ.get('DB_PASSWORD', '123market'), 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
'HOST': DB_HOST, 'HOST': DB_HOST,
#'PORT': os.environ.get('DB_PORT', '6433'), 'PORT': os.environ.get('DB_PORT', '6433'),
'PORT': os.environ.get('DB_PORT', '5432'), #'PORT': os.environ.get('DB_PORT', '5432'),
} }

View File

@@ -515,9 +515,11 @@ def payment_finish(request):
from community.models import ActivitySignup from community.models import ActivitySignup
signup = ActivitySignup.objects.filter(order=order).first() signup = ActivitySignup.objects.filter(order=order).first()
if signup: if signup:
signup.status = 'confirmed' # Determine status based on activity setting
new_status = 'confirmed' if signup.activity.auto_confirm else 'pending'
signup.status = new_status
signup.save() signup.save()
print(f"活动报名状态已更新: {signup.id}") print(f"活动报名状态已更新: {signup.id} -> {new_status}")
except Exception as e: except Exception as e:
print(f"更新活动报名状态失败: {str(e)}") print(f"更新活动报名状态失败: {str(e)}")

View File

@@ -42,33 +42,19 @@ const ActivityDetail = () => {
queryFn: async () => { queryFn: async () => {
try { try {
const res = await getActivityDetail(id); const res = await getActivityDetail(id);
return res.data; const data = res.data;
// Map status logic
// is_signed_up is only true if status='confirmed'
// my_signup_status can be 'unpaid', 'pending', 'confirmed'
return data;
} catch (err) { } catch (err) {
throw new Error(err.response?.data?.detail || 'Failed to load activity'); throw new Error(err.response?.data?.detail || 'Failed to load activity');
} }
}, },
staleTime: 0, // Ensure fresh data staleTime: 0,
refetchOnMount: 'always', // Force refetch on mount refetchOnMount: 'always',
}); });
//// /
// Force a refresh if needed (as requested by user)
useEffect(() => {
// 1. Force React Query refetch
refetch();
// 2. Hard refresh logic after 1 second delay
const timer = setTimeout(() => {
const hasRefreshedKey = `has_refreshed_activity_${id}`;
if (!sessionStorage.getItem(hasRefreshedKey)) {
sessionStorage.setItem(hasRefreshedKey, 'true');
window.location.reload();
}
}, 0);
return () => clearTimeout(timer);
}, [id, refetch]);
// Auto-fill form fields when the signup form becomes visible // Auto-fill form fields when the signup form becomes visible
useEffect(() => { useEffect(() => {
if (signupFormVisible && user && activity?.signup_form_config) { if (signupFormVisible && user && activity?.signup_form_config) {
@@ -162,6 +148,37 @@ const ActivityDetail = () => {
return () => clearInterval(timer); return () => clearInterval(timer);
}, [paymentModalVisible, paymentInfo, id, queryClient]); }, [paymentModalVisible, paymentInfo, id, queryClient]);
const getButtonText = () => {
if (signUpMutation.isPending) return '提交中...';
if (activity.is_signed_up) return '已报名';
if (activity.my_signup_status === 'pending') return '审核中';
if (activity.my_signup_status === 'unpaid') return '去支付';
const isEnded = new Date(activity.end_time) < new Date();
if (isEnded) return '活动已结束';
const isFull = activity.max_participants > 0 && (activity.current_signups || 0) >= activity.max_participants;
if (isFull) return '名额已满';
return '立即报名';
};
const isButtonDisabled = () => {
if (signUpMutation.isPending) return true;
if (activity.is_signed_up) return true;
if (activity.my_signup_status === 'pending') return true;
// 'unpaid' is NOT disabled, allows payment retry
if (activity.my_signup_status === 'unpaid') return false;
const isEnded = new Date(activity.end_time) < new Date();
if (isEnded) return true;
const isFull = activity.max_participants > 0 && (activity.current_signups || 0) >= activity.max_participants;
if (isFull) return true;
return false;
};
const handleShare = async () => { const handleShare = async () => {
const url = window.location.href; const url = window.location.href;
if (navigator.share) { if (navigator.share) {
@@ -387,11 +404,9 @@ const ActivityDetail = () => {
variants={buttonTap} variants={buttonTap}
whileTap="tap" whileTap="tap"
onClick={handleSignUp} onClick={handleSignUp}
disabled={signUpMutation.isPending || activity.is_signed_up || (activity.my_signup_status === 'pending' && !activity.is_paid)} disabled={isButtonDisabled()}
> >
{signUpMutation.isPending ? '提交中...' : {getButtonText()}
activity.is_signed_up ? '已报名' :
(activity.my_signup_status === 'pending' ? (activity.is_paid ? '去支付' : '审核中') : '立即报名')}
</motion.button> </motion.button>
</div> </div>

View File

@@ -148,12 +148,13 @@ const ActivityDetail = () => {
const hasConfirmed = activity.has_signed_up const hasConfirmed = activity.has_signed_up
const isPending = activity.my_signup_status === 'pending' const isPending = activity.my_signup_status === 'pending'
const isUnpaid = activity.my_signup_status === 'unpaid'
const isPaid = activity.is_paid const isPaid = activity.is_paid
const canSignup = activity.is_active && !isEnded && const canSignup = activity.is_active && !isEnded &&
( (
(!hasConfirmed && !isPending && !isFull) || (!hasConfirmed && !isPending && !isUnpaid && !isFull) ||
(isPending && isPaid) (isUnpaid && isPaid)
) )
return ( return (
@@ -233,9 +234,11 @@ const ActivityDetail = () => {
> >
{submitting ? '提交中...' : ( {submitting ? '提交中...' : (
hasConfirmed ? '您已报名' : ( hasConfirmed ? '您已报名' : (
isPending ? (isPaid ? '去支付' : '审核中') : ( isPending ? '审核中' : (
isEnded ? '活动已结束' : ( isUnpaid ? '去支付' : (
isFull ? '名额已满' : '立即报名' isEnded ? '活动已结束' : (
isFull ? '名额已满' : '立即报名'
)
) )
) )
) )