fix: stabilize production payment flow

This commit is contained in:
QiuSW
2026-07-06 08:56:30 +08:00
parent 0d6b97f44d
commit e4565de0c4
14 changed files with 270 additions and 13 deletions
-1
View File
@@ -41,7 +41,6 @@ class RechargeCreateForm(forms.Form):
label="支付方式",
choices=(
(RechargeOrder.PayMethod.WEIXIN, "微信"),
(RechargeOrder.PayMethod.ALIPAY, "支付宝"),
),
initial=RechargeOrder.PayMethod.WEIXIN,
widget=forms.RadioSelect(attrs={"class": "form-check-input"}),
@@ -87,6 +87,12 @@
<span class="badge text-bg-secondary" id="recharge-status-badge">{{ current_order.status }}</span>
</div>
{% if is_mock_payment_mode %}
<div class="alert alert-warning" role="alert">
当前为支付测试模式。下方二维码只用于联调订单创建、页面渲染和状态轮询,不能使用微信或支付宝真实付款。
</div>
{% endif %}
<div class="row g-4 align-items-start">
<div class="col-md-5">
<canvas id="recharge-qr" class="border rounded bg-white p-2" width="240" height="240"></canvas>
+49 -1
View File
@@ -666,6 +666,8 @@ class PortalAccountFlowTests(TestCase):
self.assertEqual(response.context["balance"].points_balance, 25)
self.assertContains(response, "充值金额")
self.assertContains(response, "创建订单")
self.assertContains(response, "微信")
self.assertNotContains(response, "支付宝")
self.assertContains(response, "portal/vendor/bootstrap/bootstrap.min.css")
self.assertContains(response, "portal/vendor/qrcode/qrcode.js")
self.assertContains(response, "QRCode.toCanvas")
@@ -675,6 +677,7 @@ class PortalAccountFlowTests(TestCase):
)
self.assertIsNotNone(finders.find("portal/vendor/qrcode/qrcode.js"))
@override_settings(PAYMENT_CALLBACK_MODE="mock")
def test_recharge_page_post_creates_pending_order_without_crediting_wallet_or_ledger(self):
user = self.create_verified_user()
UserWallet.objects.create(user=user, points_balance=5)
@@ -714,9 +717,35 @@ class PortalAccountFlowTests(TestCase):
self.assertEqual(follow_response.context["current_order"], order)
self.assertContains(follow_response, order.order_no)
self.assertContains(follow_response, "weixin://wxpay/cmhub-mock")
self.assertTrue(follow_response.context["is_mock_payment_mode"])
self.assertContains(follow_response, "当前为支付测试模式")
self.assertContains(follow_response, "不能使用微信或支付宝真实付款")
self.assertContains(follow_response, 'data-status-url="/api/v1/recharge/status"')
self.assertContains(follow_response, "data-recharge-order")
@override_settings(PAYMENT_CALLBACK_MODE="sdk")
def test_recharge_page_does_not_show_mock_notice_in_sdk_mode(self):
user = self.create_verified_user()
UserWallet.objects.create(user=user, points_balance=0)
order = self.create_recharge_order(
user,
order_no="R-SDK-RECHARGE-PAGE-504",
amount="20.00",
points=200,
status=RechargeOrder.Status.PENDING,
)
order.code_url = "weixin://wxpay/bizpayurl?pr=real-native-ticket"
order.save(update_fields=("code_url", "updated_at"))
self.client.force_login(user)
response = self.client.get(f"/recharge?order_no={order.order_no}")
self.assertEqual(response.status_code, 200)
self.assertFalse(response.context["is_mock_payment_mode"])
self.assertContains(response, "weixin://wxpay/bizpayurl")
self.assertNotContains(response, "当前为支付测试模式")
self.assertNotContains(response, "不能使用微信或支付宝真实付款")
def test_recharge_page_current_order_only_shows_current_user_order(self):
user = self.create_verified_user()
other_user = self.create_verified_user()
@@ -749,13 +778,32 @@ class PortalAccountFlowTests(TestCase):
response = self.client.post(
"/recharge",
{"amount": "100.01", "pay_method": RechargeOrder.PayMethod.ALIPAY},
{"amount": "100.01", "pay_method": RechargeOrder.PayMethod.WEIXIN},
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "单笔充值金额不能超过 100.00 CNY")
self.assertFalse(RechargeOrder.objects.filter(user=user).exists())
def test_recharge_page_rejects_hidden_alipay_submit(self):
user = self.create_verified_user()
UserWallet.objects.create(user=user, points_balance=0)
ExchangeRate.objects.create(
currency="CNY",
points_per_unit=Decimal("10.0000"),
effective_from=timezone.now(),
)
self.client.force_login(user)
response = self.client.post(
"/recharge",
{"amount": "20.00", "pay_method": RechargeOrder.PayMethod.ALIPAY},
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "选择一个有效的选项")
self.assertFalse(RechargeOrder.objects.filter(user=user).exists())
def test_recharge_page_post_is_csrf_protected(self):
user = self.create_verified_user()
csrf_client = Client(enforce_csrf_checks=True)
+4 -1
View File
@@ -8,7 +8,7 @@ from django.views import View
from django.views.generic import FormView, TemplateView
from apps.ai.catalog import get_public_model_catalog
from apps.billing.payment_gateways import PaymentOrderCreateError
from apps.billing.payment_gateways import PaymentOrderCreateError, payment_callback_mode
from apps.billing.pricing import NoExchangeRateError
from apps.billing.models import PointsLedger, RechargeOrder
from apps.billing.services import (
@@ -164,7 +164,10 @@ class RechargePageView(LoginRequiredMixin, FormView):
).first()
context["balance"] = get_balance_snapshot(self.request.user)
context.update(get_portal_account_summary(self.request.user))
current_payment_mode = payment_callback_mode()
context["current_order"] = current_order
context["payment_callback_mode"] = current_payment_mode
context["is_mock_payment_mode"] = current_payment_mode == "mock"
context["recent_recharge_orders"] = get_recharge_orders_for_user(
self.request.user
)[:5]