import logging from django.http import HttpResponse from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from rest_framework.authentication import SessionAuthentication from rest_framework.exceptions import AuthenticationFailed from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework import status from rest_framework.views import APIView from apps.api.authentication import ApiKeyAuthentication from apps.api.errors import api_error from apps.api.generation import ( ApiRequestError, analyze_images_response, generate_image_response, generate_title_response, ) from apps.api.image_tasks import ( create_image_generation_task, task_detail_response, task_submit_response, ) from apps.api.models import ImageGenerationTask from apps.api.serializers import ( AnalyzeImagesRequestSerializer, DeviceRegistrationRequestSerializer, GenerateImageRequestSerializer, GenerateTitleRequestSerializer, RechargeCreateRequestSerializer, RechargeStatusRequestSerializer, SoftwareOrderStatusRequestSerializer, ) from apps.api.telemetry import ( log_generation_route_usage, request_alias, telemetry_elapsed_ms, telemetry_start_time, ) from apps.api.throttles import GenerateRateThrottle, throttle_api_auth_failure from apps.ai.catalog import get_public_model_catalog from apps.billing.models import RechargeOrder from apps.billing.payment_gateways import ( PaymentOrderCreateError, PaymentQueryUnavailableError, PaymentVerificationError, query_payment_order, verify_alipay_callback, verify_wechat_callback, ) from apps.billing.pricing import NoExchangeRateError from apps.billing.services import ( InvalidRechargeOrderStateError, RechargeAmountMismatchError, RechargeCallbackError, RechargeOrderCreateError, RechargeOrderNotFoundError, RechargePayMethodMismatchError, apply_recharge_payment, create_recharge_order, get_balance_snapshot, query_and_apply_recharge_payment, ) from apps.portal.models import DownloadRelease from apps.licensing.authentication import DeviceSessionAuthentication from apps.licensing.services import ( DeviceRegistrationError, DeviceSessionValidationError, LicensingError, SoftwareOrderAmountMismatchError, SoftwareOrderError, SoftwareOrderNotFoundError, SoftwareOrderPayMethodMismatchError, apply_software_payment, create_migration_request, expire_software_order, query_and_apply_software_payment, evaluate_subscription_access, software_subscription_status, record_device_heartbeat, register_device, resolve_optional_device_session, ) from apps.licensing.models import MigrationRequest, SoftwareOrder logger = logging.getLogger(__name__) authorization_logger = logging.getLogger("cmhub.licensing.authorization") class ExternalApiView(APIView): authentication_classes = (ApiKeyAuthentication,) permission_classes = (IsAuthenticated,) def permission_denied(self, request, message=None, code=None): if request.authenticators and not request.successful_authenticator: throttle_api_auth_failure(request) raise AuthenticationFailed(api_error("unauthorized", "缺失或无效 API Key")) super().permission_denied(request, message=message, code=code) def optional_client_device(self, request): raw_token = request.headers.get("X-Device-Session", "") request.device_session_present = bool(raw_token.strip()) try: session = resolve_optional_device_session( user=request.user, raw_token=raw_token, ) except DeviceSessionValidationError as exc: http_status = ( status.HTTP_401_UNAUTHORIZED if exc.code == "device_session_invalid" else status.HTTP_403_FORBIDDEN ) raise ApiRequestError(exc.code, exc.message, http_status) from exc request.client_device = session.device if session is not None else None return request.client_device class GenerateTitleView(ExternalApiView): throttle_classes = (GenerateRateThrottle,) def post(self, request): started = telemetry_start_time() alias = request_alias(request.data) try: client_device = self.optional_client_device(request) except ApiRequestError as exc: log_generation_route_usage( route_type="title", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) serializer = GenerateTitleRequestSerializer(data=request.data) if not serializer.is_valid(): log_generation_route_usage( route_type="title", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code="bad_request", http_status=status.HTTP_400_BAD_REQUEST, ) return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) try: data = generate_title_response( user=request.user, api_key=request.auth, client_device=client_device, request_data=serializer.validated_data, ) except ApiRequestError as exc: log_generation_route_usage( route_type="title", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) log_generation_route_usage( route_type="title", request=request, alias=data.get("alias") or alias, status="success", latency_ms=telemetry_elapsed_ms(started), http_status=status.HTTP_200_OK, ) return Response(data, status=status.HTTP_200_OK) class AnalyzeImagesView(ExternalApiView): throttle_classes = (GenerateRateThrottle,) def post(self, request): started = telemetry_start_time() alias = request_alias(request.data) try: client_device = self.optional_client_device(request) except ApiRequestError as exc: log_generation_route_usage( route_type="vision", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) serializer = AnalyzeImagesRequestSerializer(data=request.data) if not serializer.is_valid(): log_generation_route_usage( route_type="vision", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code="bad_request", http_status=status.HTTP_400_BAD_REQUEST, ) return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) try: data = analyze_images_response( user=request.user, api_key=request.auth, client_device=client_device, request_data=serializer.validated_data, ) except ApiRequestError as exc: log_generation_route_usage( route_type="vision", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) log_generation_route_usage( route_type="vision", request=request, alias=data.get("alias") or alias, status="success", latency_ms=telemetry_elapsed_ms(started), http_status=status.HTTP_200_OK, ) return Response(data, status=status.HTTP_200_OK) class GenerateImageView(ExternalApiView): throttle_classes = (GenerateRateThrottle,) def post(self, request): started = telemetry_start_time() alias = request_alias(request.data) try: client_device = self.optional_client_device(request) except ApiRequestError as exc: log_generation_route_usage( route_type="sync", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) serializer = GenerateImageRequestSerializer(data=request.data) if not serializer.is_valid(): log_generation_route_usage( route_type="sync", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code="bad_request", http_status=status.HTTP_400_BAD_REQUEST, ) return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) alias = serializer.validated_data.get("model") or alias try: data = generate_image_response( user=request.user, api_key=request.auth, client_device=client_device, request_data=serializer.validated_data, image_url_builder=request.build_absolute_uri, ) except ApiRequestError as exc: log_generation_route_usage( route_type="sync", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) log_generation_route_usage( route_type="sync", request=request, alias=data.get("alias") or alias, status="success", latency_ms=telemetry_elapsed_ms(started), http_status=status.HTTP_200_OK, ) return Response(data, status=status.HTTP_200_OK) class GenerateImageTaskSubmitView(ExternalApiView): throttle_classes = (GenerateRateThrottle,) def post(self, request): started = telemetry_start_time() alias = request_alias(request.data) try: client_device = self.optional_client_device(request) except ApiRequestError as exc: log_generation_route_usage( route_type="async", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) serializer = GenerateImageRequestSerializer(data=request.data) if not serializer.is_valid(): log_generation_route_usage( route_type="async", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code="bad_request", http_status=status.HTTP_400_BAD_REQUEST, ) return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) alias = serializer.validated_data.get("model") or alias try: task, _created = create_image_generation_task( user=request.user, api_key=request.auth, client_device=client_device, request_data=serializer.validated_data, idempotency_key=request.headers.get("Idempotency-Key", ""), ) except ApiRequestError as exc: log_generation_route_usage( route_type="async", request=request, alias=alias, status="error", latency_ms=telemetry_elapsed_ms(started), error_code=exc.code, http_status=exc.http_status, ) return Response(exc.as_response_data(), status=exc.http_status) log_generation_route_usage( route_type="async", request=request, alias=task.call_record.alias, status="success", latency_ms=telemetry_elapsed_ms(started), http_status=status.HTTP_202_ACCEPTED, ) return Response(task_submit_response(task), status=status.HTTP_202_ACCEPTED) class GenerateImageTaskDetailView(ExternalApiView): def get(self, request, task_id): task = ( ImageGenerationTask.objects.select_related("call_record") .filter(task_id=task_id, user=request.user) .first() ) if task is None: return Response( api_error("task_not_found", "图片生成任务不存在"), status=status.HTTP_404_NOT_FOUND, ) return Response(task_detail_response(task), status=status.HTTP_200_OK) class CmshopeeSubscriptionAuthorizationMixin: product_code = "cmshopee" def optional_client_device(self, request): try: device = super().optional_client_device(request) except ApiRequestError: # A stale device session must not block an account-based subscription. request.device_session_present = bool( request.headers.get("X-Device-Session", "").strip() ) device = None decision = evaluate_subscription_access( user=request.user, product_code=self.product_code, ) event = { "event": "cmshopee_subscription_authorization", "product_code": self.product_code, "user_id": request.user.id, "client_device_id": getattr(device, "id", None), "would_reject": bool(decision.entitlement_code), "would_reject_code": decision.entitlement_code, "final_allowed": decision.allowed, "final_code": decision.code, "subscription_mode": decision.mode, "access_source": decision.access_source, "entitlement_status": decision.entitlement_status, "enforcement": decision.mode == "enforce", } authorization_logger.info( "%s %s", event["event"], event, extra={"cmshopee_authorization": event}, ) if decision.would_reject: message = ( "当前账号没有有效的软件订阅" if decision.code == "subscription_required" else "软件订阅已到期,请先续订" ) raise ApiRequestError(decision.code, message, status.HTTP_403_FORBIDDEN) request.cmshopee_authorization = decision return device class CmshopeeGenerateTitleView(CmshopeeSubscriptionAuthorizationMixin, GenerateTitleView): pass class CmshopeeAnalyzeImagesView(CmshopeeSubscriptionAuthorizationMixin, AnalyzeImagesView): pass class CmshopeeGenerateImageTaskSubmitView( CmshopeeSubscriptionAuthorizationMixin, GenerateImageTaskSubmitView, ): pass class CmshopeeGenerateImageTaskDetailView(GenerateImageTaskDetailView): pass class CmshopeeSubscriptionStatusView(ExternalApiView): def get(self, request): return Response( software_subscription_status( user=request.user, product_code="cmshopee", ), status=status.HTTP_200_OK, ) class BalanceView(ExternalApiView): def get(self, request): balance = get_balance_snapshot(request.user) username = request.user.get_username() display_name = request.user.get_full_name() or username return Response( { "user": username, "points_balance": balance.points_balance, "account": { "username": username, "display_name": display_name, }, }, status=status.HTTP_200_OK, ) class ModelsView(ExternalApiView): def get(self, request): return Response( {"models": get_public_model_catalog()}, status=status.HTTP_200_OK, ) def _device_response(device) -> dict: return { "product_code": device.product_code, "platform": device.platform, "client_version": device.client_version, "status": device.status, "first_seen_at": device.first_seen_at.isoformat(), "last_seen_at": device.last_seen_at.isoformat(), } class DeviceRegistrationView(ExternalApiView): def post(self, request): serializer = DeviceRegistrationRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) data = serializer.validated_data try: result = register_device( user=request.user, api_key=request.auth, product_code=data["product_code"], device_id_version=data["device_id_version"], device_id=data["device_id"], public_key=data["installation_public_key"], platform=data["platform"], client_version=data["client_version"], ) except DeviceRegistrationError as exc: return Response( api_error(exc.code, exc.message), status=status.HTTP_403_FORBIDDEN, ) return Response( { "device": _device_response(result.device), "device_session_token": result.session_token, "expires_at": result.session.expires_at.isoformat(), }, status=status.HTTP_201_CREATED if result.created else status.HTTP_200_OK, ) class DeviceSessionApiView(APIView): authentication_classes = (DeviceSessionAuthentication,) permission_classes = (IsAuthenticated,) def permission_denied(self, request, message=None, code=None): if request.authenticators and not request.successful_authenticator: raise DeviceSessionAuthentication.authentication_failed(request) super().permission_denied(request, message=message, code=code) class DeviceHeartbeatView(DeviceSessionApiView): def post(self, request): try: updated = record_device_heartbeat(request.auth) except DeviceRegistrationError as exc: return Response( api_error(exc.code, exc.message), status=status.HTTP_403_FORBIDDEN, ) request.auth.device.refresh_from_db() return Response( { "device": _device_response(request.auth.device), "activity_updated": updated, "expires_at": request.auth.expires_at.isoformat(), }, status=status.HTTP_200_OK, ) def _migration_request_response(migration_request, *, credential_token=None, confirmation_url=""): data = { "request_id": str(migration_request.request_id), "status": migration_request.status, "product_code": migration_request.device.product_code, "expires_at": migration_request.expires_at.isoformat(), "confirmed_at": ( migration_request.confirmed_at.isoformat() if migration_request.confirmed_at else None ), "confirmation_url": confirmation_url, } if credential_token is not None: data["device_credential_token"] = credential_token return data class MigrationRequestCreateView(ExternalApiView): def post(self, request): try: device = self.optional_client_device(request) except ApiRequestError as exc: return Response(exc.as_response_data(), status=exc.http_status) if device is None: return Response( api_error("device_session_required", "必须提供当前设备会话"), status=status.HTTP_401_UNAUTHORIZED, ) try: migration_request, credential_token = create_migration_request( user=request.user, device=device, ) except LicensingError as exc: response_status = ( status.HTTP_403_FORBIDDEN if exc.code in {"migration_not_eligible", "license_expired"} else status.HTTP_400_BAD_REQUEST ) return Response(api_error(exc.code, exc.message), status=response_status) confirmation_url = request.build_absolute_uri( f"/migration/confirm/{migration_request.request_id}" ) return Response( _migration_request_response( migration_request, credential_token=credential_token, confirmation_url=confirmation_url, ), status=status.HTTP_201_CREATED, ) class MigrationRequestDetailView(ExternalApiView): def get(self, request, request_id): try: device = self.optional_client_device(request) except ApiRequestError as exc: return Response(exc.as_response_data(), status=exc.http_status) if device is None: return Response( api_error("device_session_required", "必须提供当前设备会话"), status=status.HTTP_401_UNAUTHORIZED, ) migration_request = ( MigrationRequest.objects.select_related("device") .filter(request_id=request_id, user=request.user, device=device) .first() ) if migration_request is None: return Response( api_error("migration_request_not_found", "迁移请求不存在"), status=status.HTTP_404_NOT_FOUND, ) return Response( _migration_request_response(migration_request), status=status.HTTP_200_OK, ) def _release_unpublished_response(platform: str) -> dict: return { "platform": platform, "release": None, "message": "暂未发布", } def _absolute_download_url(request, release: DownloadRelease) -> str: download_url = release.download_url if not download_url: return "" if download_url.startswith(("http://", "https://")): return download_url return request.build_absolute_uri(download_url) class ClientLatestReleaseView(APIView): authentication_classes = () permission_classes = (AllowAny,) def get(self, request): platform = ( request.query_params.get("platform") or DownloadRelease.Platform.WINDOWS ).strip().lower() if platform not in DownloadRelease.Platform.values: return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) release = ( DownloadRelease.objects.filter(platform=platform, is_current=True) .order_by("-created_at", "-id") .first() ) if release is None: return Response( _release_unpublished_response(platform), status=status.HTTP_200_OK, ) download_url = _absolute_download_url(request, release) if not download_url: return Response( _release_unpublished_response(platform), status=status.HTTP_200_OK, ) return Response( { "platform": platform, "release": { "version": release.version, "download_url": download_url, "sha256": release.sha256, "release_notes": release.release_notes, "force_update": release.force_update, "size_bytes": release.size_bytes, "published_at": timezone.localtime(release.updated_at).isoformat(), }, }, status=status.HTTP_200_OK, ) class PortalSessionApiView(APIView): authentication_classes = (SessionAuthentication,) permission_classes = (IsAuthenticated,) def _recharge_order_response(order: RechargeOrder) -> dict: is_expired = bool( order.status == RechargeOrder.Status.PENDING and order.expires_at is not None and order.expires_at <= timezone.now() ) return { "order_no": order.order_no, "amount": f"{order.amount_money:.2f}", "currency": order.currency, "exchange_rate": f"{order.exchange_rate:.4f}", "points_granted": order.points_granted, "pay_method": order.pay_method, "status": order.status, "code_url": order.code_url, "expires_at": order.expires_at.isoformat() if order.expires_at else None, "paid_at": order.paid_at.isoformat() if order.paid_at else None, "is_expired": is_expired, } class RechargeCreateView(PortalSessionApiView): def post(self, request): serializer = RechargeCreateRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) try: order = create_recharge_order( user=request.user, amount=serializer.validated_data["amount"], pay_method=serializer.validated_data["pay_method"], ) except NoExchangeRateError: return Response( api_error("no_exchange_rate", "未配置当前币种汇率"), status=status.HTTP_400_BAD_REQUEST, ) except RechargeOrderCreateError: return Response( api_error("bad_request", "充值下单参数错误"), status=status.HTTP_400_BAD_REQUEST, ) except PaymentOrderCreateError: return Response( api_error("payment_order_create_failed", "支付下单失败"), status=status.HTTP_502_BAD_GATEWAY, ) return Response(_recharge_order_response(order), status=status.HTTP_201_CREATED) class RechargeStatusView(PortalSessionApiView): def get(self, request): serializer = RechargeStatusRequestSerializer(data=request.query_params) if not serializer.is_valid(): return Response( api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST, ) order_no = serializer.validated_data["order_no"] order = RechargeOrder.objects.filter( user=request.user, order_no=order_no, ).first() if order is None: return Response( api_error("order_not_found", "充值订单不存在"), status=status.HTTP_404_NOT_FOUND, ) if order.status == RechargeOrder.Status.PENDING: try: result = query_and_apply_recharge_payment(order.order_no, query_payment_order) order = result.order except (PaymentQueryUnavailableError, PaymentOrderCreateError): order.refresh_from_db() except RechargeCallbackError as exc: logger.warning( "Rejected active recharge query result for %s: %s", order.order_no, exc.__class__.__name__, ) return Response( api_error(exc.code, "支付查单结果与本地订单不一致"), status=status.HTTP_400_BAD_REQUEST, ) except Exception as exc: # pragma: no cover - external gateway/runtime. logger.warning( "Active recharge query failed for %s: %s", order.order_no, exc.__class__.__name__, ) order.refresh_from_db() return Response(_recharge_order_response(order), status=status.HTTP_200_OK) def _software_order_response(order: SoftwareOrder) -> dict: return { "order_no": order.order_no, "product_code": order.product_code, "plan_name": order.plan_name, "amount": f"{order.amount_money:.2f}", "currency": order.currency, "pay_method": order.pay_method, "status": order.status, "code_url": order.code_url, "expires_at": order.expires_at.isoformat() if order.expires_at else None, "paid_at": order.paid_at.isoformat() if order.paid_at else None, "fulfilled_at": order.fulfilled_at.isoformat() if order.fulfilled_at else None, } class SoftwareOrderStatusView(PortalSessionApiView): def get(self, request): serializer = SoftwareOrderStatusRequestSerializer(data=request.query_params) if not serializer.is_valid(): return Response(api_error("bad_request", "参数错误"), status=status.HTTP_400_BAD_REQUEST) order = SoftwareOrder.objects.filter( user=request.user, order_no=serializer.validated_data["order_no"], ).first() if order is None: return Response( api_error("software_order_not_found", "软件订单不存在"), status=status.HTTP_404_NOT_FOUND, ) if order.status == SoftwareOrder.Status.PENDING: order = expire_software_order(order=order) if order.status == SoftwareOrder.Status.PENDING: try: result = query_and_apply_software_payment(order.order_no, query_payment_order) order = result.order except PaymentQueryUnavailableError: order.refresh_from_db() except SoftwareOrderError as exc: logger.warning( "Rejected active software order query for %s: %s", order.order_no, exc.code, ) return Response( api_error(exc.code, "支付查单结果与本地软件订单不一致"), status=status.HTTP_400_BAD_REQUEST, ) except Exception as exc: # pragma: no cover - external gateway/runtime. logger.warning( "Active software order query failed for %s: %s", order.order_no, exc.__class__.__name__, ) order.refresh_from_db() return Response(_software_order_response(order), status=status.HTTP_200_OK) class RechargeCallbackView(APIView): authentication_classes = () permission_classes = () @staticmethod def _error_response(exc: Exception): if isinstance(exc, PaymentVerificationError): return ( api_error("signature_invalid", "支付回调验签失败"), status.HTTP_400_BAD_REQUEST, ) if isinstance(exc, RechargeAmountMismatchError): return ( api_error("amount_mismatch", "支付回调金额与本地订单金额不一致"), status.HTTP_400_BAD_REQUEST, ) if isinstance(exc, RechargeCallbackError): return api_error(exc.code, "支付回调处理失败"), status.HTTP_400_BAD_REQUEST return api_error("bad_request", "支付回调处理失败"), status.HTTP_400_BAD_REQUEST @method_decorator(csrf_exempt, name="dispatch") class WechatRechargeCallbackView(RechargeCallbackView): def post(self, request): try: payment = verify_wechat_callback(request.headers, request.body) apply_recharge_payment(payment) except ( PaymentVerificationError, RechargeAmountMismatchError, InvalidRechargeOrderStateError, RechargeOrderNotFoundError, RechargePayMethodMismatchError, ) as exc: logger.warning("Rejected WeChat recharge callback: %s", exc.__class__.__name__) data, http_status = self._error_response(exc) return Response(data, status=http_status) return Response({"code": "SUCCESS", "message": "成功"}, status=status.HTTP_200_OK) @method_decorator(csrf_exempt, name="dispatch") class AlipayRechargeCallbackView(RechargeCallbackView): def post(self, request): try: payload = request.data.dict() if hasattr(request.data, "dict") else dict(request.data) payment = verify_alipay_callback(payload) apply_recharge_payment(payment) except ( PaymentVerificationError, RechargeAmountMismatchError, InvalidRechargeOrderStateError, RechargeOrderNotFoundError, RechargePayMethodMismatchError, ) as exc: logger.warning("Rejected Alipay recharge callback: %s", exc.__class__.__name__) return HttpResponse("fail", status=status.HTTP_400_BAD_REQUEST) return HttpResponse("success", content_type="text/plain", status=status.HTTP_200_OK) @method_decorator(csrf_exempt, name="dispatch") class WechatSoftwareOrderCallbackView(APIView): authentication_classes = () permission_classes = () def post(self, request): try: payment = verify_wechat_callback(request.headers, request.body) apply_software_payment(payment) except PaymentVerificationError: logger.warning("Rejected WeChat software order callback: signature_invalid") return Response( api_error("signature_invalid", "支付回调验签失败"), status=status.HTTP_400_BAD_REQUEST, ) except SoftwareOrderAmountMismatchError: logger.warning("Rejected WeChat software order callback: amount_mismatch") return Response( api_error("amount_mismatch", "支付回调金额与本地软件订单金额不一致"), status=status.HTTP_400_BAD_REQUEST, ) except ( SoftwareOrderNotFoundError, SoftwareOrderPayMethodMismatchError, SoftwareOrderError, ) as exc: logger.warning("Rejected WeChat software order callback: %s", exc.code) return Response( api_error(exc.code, "支付回调处理失败"), status=status.HTTP_400_BAD_REQUEST, ) return Response({"code": "SUCCESS", "message": "成功"}, status=status.HTTP_200_OK)