712 lines
26 KiB
Python
712 lines
26 KiB
Python
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,
|
|
)
|
|
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,
|
|
record_device_heartbeat,
|
|
register_device,
|
|
resolve_optional_device_session,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
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 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 _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)
|
|
|
|
|
|
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)
|