feat: add recharge create and status APIs

This commit is contained in:
QiuSW
2026-07-03 09:34:07 +08:00
parent c15a07a5c2
commit fa87359a2c
18 changed files with 808 additions and 22 deletions
+90 -2
View File
@@ -1,8 +1,9 @@
from __future__ import annotations
import secrets
from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal
from decimal import Decimal, InvalidOperation
from django.db import transaction
from django.db.models import Sum
@@ -11,6 +12,7 @@ from django.utils import timezone
from apps.users.models import UserWallet
from .models import CallRecord, PointsLedger, RechargeOrder, normalize_resolution
from .pricing import quote_recharge_points
class BillingOperationError(RuntimeError):
@@ -71,6 +73,18 @@ class InvalidRechargeOrderStateError(RechargeCallbackError):
super().__init__("Recharge order cannot be paid from its current state.")
class RechargeOrderCreateError(BillingOperationError):
code = "bad_request"
class InvalidRechargePayMethodError(RechargeOrderCreateError):
code = "bad_request"
def __init__(self, pay_method: str):
self.pay_method = pay_method
super().__init__("Unsupported recharge payment method.")
@dataclass(frozen=True)
class CallCharge:
call_record: CallRecord
@@ -124,13 +138,87 @@ def _locked_wallet_for_user(user) -> UserWallet:
def _normalize_money(value) -> Decimal:
return Decimal(str(value)).quantize(Decimal("0.01"))
try:
return Decimal(str(value)).quantize(Decimal("0.01"))
except (InvalidOperation, TypeError, ValueError) as exc:
raise RechargeOrderCreateError("Invalid recharge amount.") from exc
def _normalize_pay_method(value: str) -> str:
return str(value or "").strip().lower()
def _validate_recharge_pay_method(value: str) -> str:
pay_method = _normalize_pay_method(value)
if pay_method not in {
RechargeOrder.PayMethod.WEIXIN,
RechargeOrder.PayMethod.ALIPAY,
}:
raise InvalidRechargePayMethodError(pay_method)
return pay_method
def _generate_recharge_order_no() -> str:
for _attempt in range(10):
timestamp = timezone.now().strftime("%Y%m%d%H%M%S")
order_no = f"R{timestamp}{secrets.token_hex(4).upper()}"
if not RechargeOrder.objects.filter(order_no=order_no).exists():
return order_no
raise RechargeOrderCreateError("Could not generate unique recharge order number.")
def create_recharge_order(
*,
user,
amount,
pay_method: str,
currency: str = "CNY",
payment_order_func=None,
) -> RechargeOrder:
normalized_amount = _normalize_money(amount)
if normalized_amount <= 0:
raise RechargeOrderCreateError("Recharge amount must be greater than zero.")
normalized_pay_method = _validate_recharge_pay_method(pay_method)
quote = quote_recharge_points(normalized_amount, currency=currency)
if quote.points_granted <= 0:
raise RechargeOrderCreateError(
"Recharge amount is too small for the current exchange rate."
)
order = RechargeOrder.objects.create(
user=user,
order_no=_generate_recharge_order_no(),
amount_money=quote.amount,
currency=quote.currency,
pay_method=normalized_pay_method,
exchange_rate=quote.points_per_unit,
points_granted=quote.points_granted,
status=RechargeOrder.Status.PENDING,
)
if payment_order_func is None:
from .payment_gateways import create_payment_order
payment_order_func = create_payment_order
try:
payment_order = payment_order_func(order)
code_url = str(getattr(payment_order, "code_url", "") or "").strip()
expires_at = getattr(payment_order, "expires_at", None)
if not code_url:
raise RechargeOrderCreateError("Payment gateway did not return a QR code URL.")
except Exception:
order.status = RechargeOrder.Status.FAILED
order.save(update_fields=("status", "updated_at"))
raise
order.code_url = code_url
order.expires_at = expires_at
order.save(update_fields=("code_url", "expires_at", "updated_at"))
return order
def get_balance_snapshot(user) -> BalanceSnapshot:
points_balance = (
UserWallet.objects.filter(user=user)