feat(admin): canaux IA par palier (z.ai/DeepSeek directs), emails generes par IA, reductions Stripe
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m40s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m40s
- Chaque modele d'un palier devient une route {canal, modele} : openrouter,
deepseek direct, zhipu (z.ai, nouveau canal), minimax, openai, xai ;
repli automatique sur la route suivante si la cle manque, compatibilite
ascendante avec les anciens reglages a chaines
- Page Fournisseurs refondue en sections ; interrupteurs et chaine de
secours retablis ; tests de connexion avec delai d'attente
- Emails de relance generes par IA (canal au choix, prompt construit depuis
le plan marketing, HTML sane, RTL arabe/persan, jamais d'envoi automatique)
- Codes promo : validation locale avant Stripe, Coupon+PromotionCode avec
double plafond, comptage par session (webhook+sync=1 fois), ecriture
atomique, relier-a-Stripe, suppression, validation publique au checkout
- Page Tarifs : champ code promo (?promo=) avec verification traduite
- Tests : 1357 verts (facturation par palier testee dans le worker,
point de contact providers/available, promos, generation)
This commit is contained in:
@@ -72,6 +72,9 @@ class GoogleAuthRequest(BaseModel):
|
||||
class CheckoutRequest(BaseModel):
|
||||
plan: PlanType
|
||||
billing_period: str = "monthly"
|
||||
# Code promo optionnel — validé localement AVANT toute création de session
|
||||
# Stripe (actif, forfait, expiration, plafond).
|
||||
promo_code: Optional[str] = None
|
||||
|
||||
|
||||
class CreditsCheckoutRequest(BaseModel):
|
||||
@@ -844,13 +847,19 @@ async def create_checkout_v1(request: CheckoutRequest, user=Depends(require_user
|
||||
user_id=user.id,
|
||||
plan=request.plan,
|
||||
billing_period=request.billing_period,
|
||||
promo_code=(request.promo_code or "").strip(),
|
||||
)
|
||||
if "error" in result and not result.get("demo_mode"):
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={
|
||||
"error": "CHECKOUT_FAILED",
|
||||
"error": result.get("error_code") or "CHECKOUT_FAILED",
|
||||
"message": result["error"],
|
||||
**(
|
||||
{"promo_reason": result["promo_reason"]}
|
||||
if result.get("promo_reason")
|
||||
else {}
|
||||
),
|
||||
},
|
||||
)
|
||||
return JSONResponse(status_code=200, content={"data": result, "meta": {}})
|
||||
@@ -934,6 +943,56 @@ async def get_billing_portal_v1(user=Depends(require_user)):
|
||||
return JSONResponse(status_code=200, content={"data": {"url": url}, "meta": {}})
|
||||
|
||||
|
||||
# ============== Codes promo (validation publique avant paiement) ==============
|
||||
|
||||
promos_public_router = APIRouter(prefix="/api/v1/promos", tags=["Promotions"])
|
||||
|
||||
|
||||
@promos_public_router.get(
|
||||
"/validate",
|
||||
summary="Valider un code promo",
|
||||
description=(
|
||||
"Vérifie un code promo pour un forfait donné AVANT l'ouverture du "
|
||||
"paiement : renvoie valid, reason_code (cause explicite) et le montant "
|
||||
"de la remise. Aucune authentification requise."
|
||||
),
|
||||
)
|
||||
async def validate_promo_v1(
|
||||
code: str = Query(..., min_length=1, max_length=24),
|
||||
plan: str = Query("pro", min_length=1, max_length=32),
|
||||
):
|
||||
from services.payment_service import PromoValidationError, validate_promo_code
|
||||
|
||||
wanted = (code or "").strip().upper()
|
||||
plan_id = (plan or "").strip().lower()
|
||||
try:
|
||||
promo = validate_promo_code(wanted, plan_id)
|
||||
except PromoValidationError as e:
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={
|
||||
"data": {
|
||||
"valid": False,
|
||||
"reason_code": e.reason,
|
||||
"message": e.message,
|
||||
},
|
||||
"meta": {},
|
||||
},
|
||||
)
|
||||
discount: Dict[str, Any] = {}
|
||||
if promo.get("type") == "percent":
|
||||
discount["percent_off"] = promo.get("value")
|
||||
else:
|
||||
discount["amount_off"] = promo.get("value")
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={
|
||||
"data": {"valid": True, "reason_code": None, **discount},
|
||||
"meta": {},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ============== Forgot / Reset password ==============
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user