feat: add software subscription orders
This commit is contained in:
+103
-2
@@ -33,6 +33,7 @@ from apps.api.serializers import (
|
||||
GenerateTitleRequestSerializer,
|
||||
RechargeCreateRequestSerializer,
|
||||
RechargeStatusRequestSerializer,
|
||||
SoftwareOrderStatusRequestSerializer,
|
||||
)
|
||||
from apps.api.telemetry import (
|
||||
log_generation_route_usage,
|
||||
@@ -67,16 +68,23 @@ from apps.billing.services import (
|
||||
from apps.portal.models import DownloadRelease
|
||||
from apps.licensing.authentication import DeviceSessionAuthentication
|
||||
from apps.licensing.services import (
|
||||
LicensingError,
|
||||
DeviceRegistrationError,
|
||||
DeviceSessionValidationError,
|
||||
LicensingError,
|
||||
SoftwareOrderAmountMismatchError,
|
||||
SoftwareOrderError,
|
||||
SoftwareOrderNotFoundError,
|
||||
SoftwareOrderPayMethodMismatchError,
|
||||
apply_software_payment,
|
||||
create_migration_request,
|
||||
evaluate_device_authorization,
|
||||
expire_software_order,
|
||||
query_and_apply_software_payment,
|
||||
record_device_heartbeat,
|
||||
register_device,
|
||||
resolve_optional_device_session,
|
||||
)
|
||||
from apps.licensing.models import MigrationRequest
|
||||
from apps.licensing.models import MigrationRequest, SoftwareOrder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
authorization_logger = logging.getLogger("cmhub.licensing.authorization")
|
||||
@@ -790,6 +798,65 @@ class RechargeStatusView(PortalSessionApiView):
|
||||
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 = ()
|
||||
@@ -847,3 +914,37 @@ class AlipayRechargeCallbackView(RechargeCallbackView):
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user