Gestion abonnement existant : ajout PM et résiliation avec confirmation #90
- Page /compte/abonnement affiche la vue de gestion si une subscription existe (pending, trial, active, cancelled), pas les offres
- Status pending : alerte + bouton "Ajouter une méthode de paiement"
- Status trial/active : bouton "Me désabonner" ouvrant une modale de confirmation
- Route add-payment crée une recurring session Frisbii (checkout-api.frisbii.com)
- Callback add-payment/callback récupère l'id du PM créé et l'associe à la subscription via POST /v1/subscription/{handle}/pm
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import httpx
|
||||
# première intégration en environnement de test. Frisbii Billing/Pay s'appuie sur
|
||||
# l'API Reepay (api.reepay.com) ; ajuster FRISBII_API_BASE_URL si besoin.
|
||||
_DEFAULT_BASE_URL = "https://api.frisbii.com"
|
||||
_DEFAULT_CHECKOUT_URL = "https://checkout-api.frisbii.com"
|
||||
_TIMEOUT = 15.0
|
||||
|
||||
|
||||
@@ -24,13 +25,24 @@ def _base_url() -> str:
|
||||
return os.getenv("FRISBII_API_BASE_URL") or _DEFAULT_BASE_URL
|
||||
|
||||
|
||||
def _call(method: str, path: str, json: dict | None = None) -> dict:
|
||||
def _checkout_url() -> str:
|
||||
return os.getenv("FRISBII_CHECKOUT_URL") or _DEFAULT_CHECKOUT_URL
|
||||
|
||||
|
||||
def _call(
|
||||
method: str,
|
||||
path: str,
|
||||
json: dict | None = None,
|
||||
base_url: str | None = None,
|
||||
params: dict | None = None,
|
||||
) -> dict:
|
||||
try:
|
||||
resp = httpx.request(
|
||||
method,
|
||||
f"{_base_url()}{path}",
|
||||
f"{base_url or _base_url()}{path}",
|
||||
auth=(_api_key(), ""),
|
||||
json=json,
|
||||
params=params,
|
||||
timeout=_TIMEOUT,
|
||||
)
|
||||
except httpx.RequestError as exc:
|
||||
@@ -74,6 +86,37 @@ def create_subscription_session(
|
||||
return data["hosted_page_links"]["payment_info"]
|
||||
|
||||
|
||||
def create_recurring_session(
|
||||
customer_handle: str, accept_url: str, cancel_url: str
|
||||
) -> str:
|
||||
data = _call(
|
||||
"POST",
|
||||
"/v1/session/recurring",
|
||||
json={
|
||||
"customer": customer_handle,
|
||||
"accept_url": accept_url,
|
||||
"cancel_url": cancel_url,
|
||||
"show_terms": False,
|
||||
},
|
||||
base_url=_checkout_url(),
|
||||
)
|
||||
return data["url"]
|
||||
|
||||
|
||||
def get_customer_payment_methods(
|
||||
customer_handle: str, reference: str | None = None
|
||||
) -> list[dict]:
|
||||
p: dict = {"customer": customer_handle, "state": "active", "size": 10}
|
||||
if reference:
|
||||
p["reference"] = reference
|
||||
data = _call("GET", "/v1/list/payment_method", params=p)
|
||||
return data.get("content", [])
|
||||
|
||||
|
||||
def set_subscription_payment_method(sub_handle: str, pm_id: str) -> None:
|
||||
_call("POST", f"/v1/subscription/{sub_handle}/pm", json={"source": pm_id})
|
||||
|
||||
|
||||
def cancel_subscription(subscription_handle: str) -> dict:
|
||||
return _call("POST", f"/v1/subscription/{subscription_handle}/cancel")
|
||||
|
||||
|
||||
@@ -84,6 +84,48 @@ def subscribe():
|
||||
return redirect(url, code=303)
|
||||
|
||||
|
||||
@subscriptions_bp.route("/subscriptions/add-payment", methods=["POST"])
|
||||
@login_required
|
||||
def add_payment():
|
||||
base = os.getenv("APP_BASE_URL", "")
|
||||
cust = _customer_handle(current_user.id)
|
||||
try:
|
||||
url = client.create_recurring_session(
|
||||
cust,
|
||||
f"{base}/subscriptions/add-payment/callback",
|
||||
f"{base}/compte/abonnement?paiement=annule",
|
||||
)
|
||||
except client.FrisbiiError:
|
||||
logger.exception("Échec de création de session de paiement Frisbii")
|
||||
return redirect("/compte/abonnement?error=frisbii")
|
||||
return redirect(url, code=303)
|
||||
|
||||
|
||||
@subscriptions_bp.route("/subscriptions/add-payment/callback")
|
||||
@login_required
|
||||
def add_payment_callback():
|
||||
session_id = request.args.get("id", "")
|
||||
cust = _customer_handle(current_user.id)
|
||||
row = db.get_by_user(current_user.id)
|
||||
sub_handle = row["frisbii_subscription_handle"] if row else None
|
||||
if not sub_handle:
|
||||
return redirect("/compte/abonnement?paiement=succes")
|
||||
try:
|
||||
pms = client.get_customer_payment_methods(cust, reference=session_id or None)
|
||||
if not pms:
|
||||
pms = client.get_customer_payment_methods(cust)
|
||||
if not pms:
|
||||
logger.warning(
|
||||
"Aucune méthode de paiement trouvée pour le customer %s", cust
|
||||
)
|
||||
return redirect("/compte/abonnement?paiement=succes")
|
||||
client.set_subscription_payment_method(sub_handle, pms[0]["id"])
|
||||
except client.FrisbiiError:
|
||||
logger.exception("Échec de l'association méthode de paiement / abonnement")
|
||||
return redirect("/compte/abonnement?error=frisbii")
|
||||
return redirect("/compte/abonnement?paiement=succes")
|
||||
|
||||
|
||||
@subscriptions_bp.route("/subscriptions/cancel", methods=["POST"])
|
||||
@login_required
|
||||
def cancel():
|
||||
|
||||
Reference in New Issue
Block a user