feat(subscriptions): subscribe() génère et transmet le handle, mark_failed en cas d'échec
This commit is contained in:
@@ -18,8 +18,8 @@ def _customer_handle(user_id: int) -> str:
|
|||||||
@login_required
|
@login_required
|
||||||
def subscribe():
|
def subscribe():
|
||||||
plan_key = request.form.get("plan") or ""
|
plan_key = request.form.get("plan") or ""
|
||||||
handle = plans.resolve_handle(plan_key)
|
plan_handle = plans.resolve_handle(plan_key)
|
||||||
if handle is None:
|
if plan_handle is None:
|
||||||
return "Plan inconnu", 400
|
return "Plan inconnu", 400
|
||||||
|
|
||||||
base = os.getenv("APP_BASE_URL", "")
|
base = os.getenv("APP_BASE_URL", "")
|
||||||
@@ -27,11 +27,11 @@ def subscribe():
|
|||||||
return redirect(f"{base}/compte/abonnement")
|
return redirect(f"{base}/compte/abonnement")
|
||||||
|
|
||||||
cust = _customer_handle(current_user.id)
|
cust = _customer_handle(current_user.id)
|
||||||
|
meta = plans.plan_meta(plan_key)
|
||||||
|
sub_handle, subscription_id = db.create_pending(
|
||||||
|
current_user.id, cust, plan_key, meta["prix_ht"] if meta else None
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
meta = plans.plan_meta(plan_key)
|
|
||||||
db.create_pending(
|
|
||||||
current_user.id, cust, plan_key, meta["prix_ht"] if meta else None
|
|
||||||
)
|
|
||||||
no_trial = db.has_used_trial(current_user.id)
|
no_trial = db.has_used_trial(current_user.id)
|
||||||
siret = (request.form.get("siret") or "").strip()
|
siret = (request.form.get("siret") or "").strip()
|
||||||
billing: dict = {
|
billing: dict = {
|
||||||
@@ -61,7 +61,8 @@ def subscribe():
|
|||||||
|
|
||||||
if customer_exists:
|
if customer_exists:
|
||||||
url = client.create_subscription_session(
|
url = client.create_subscription_session(
|
||||||
handle,
|
plan_handle,
|
||||||
|
sub_handle,
|
||||||
f"{base}/compte/abonnement?paiement=succes",
|
f"{base}/compte/abonnement?paiement=succes",
|
||||||
f"{base}/compte/abonnement?paiement=annule",
|
f"{base}/compte/abonnement?paiement=annule",
|
||||||
customer_handle=cust,
|
customer_handle=cust,
|
||||||
@@ -72,7 +73,8 @@ def subscribe():
|
|||||||
if siret:
|
if siret:
|
||||||
create_customer["metadata"] = {"siret": siret}
|
create_customer["metadata"] = {"siret": siret}
|
||||||
url = client.create_subscription_session(
|
url = client.create_subscription_session(
|
||||||
handle,
|
plan_handle,
|
||||||
|
sub_handle,
|
||||||
f"{base}/compte/abonnement?paiement=succes",
|
f"{base}/compte/abonnement?paiement=succes",
|
||||||
f"{base}/compte/abonnement?paiement=annule",
|
f"{base}/compte/abonnement?paiement=annule",
|
||||||
create_customer=create_customer,
|
create_customer=create_customer,
|
||||||
@@ -80,6 +82,7 @@ def subscribe():
|
|||||||
)
|
)
|
||||||
except client.FrisbiiError:
|
except client.FrisbiiError:
|
||||||
logger.exception("Échec de création de session d'abonnement Frisbii")
|
logger.exception("Échec de création de session d'abonnement Frisbii")
|
||||||
|
db.mark_failed(subscription_id)
|
||||||
return redirect("/compte/abonnement?error=frisbii")
|
return redirect("/compte/abonnement?error=frisbii")
|
||||||
return redirect(url, code=303)
|
return redirect(url, code=303)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ def test_subscribe_redirects_to_hosted_url(logged_in_client, monkeypatch):
|
|||||||
frisbii_client,
|
frisbii_client,
|
||||||
"create_subscription_session",
|
"create_subscription_session",
|
||||||
lambda plan,
|
lambda plan,
|
||||||
|
handle,
|
||||||
ok,
|
ok,
|
||||||
ko,
|
ko,
|
||||||
no_trial=False,
|
no_trial=False,
|
||||||
@@ -27,25 +28,23 @@ def test_subscribe_redirects_to_hosted_url(logged_in_client, monkeypatch):
|
|||||||
resp = client.post("/subscriptions/subscribe", data={"plan": "simple"})
|
resp = client.post("/subscriptions/subscribe", data={"plan": "simple"})
|
||||||
assert resp.status_code == 303
|
assert resp.status_code == 303
|
||||||
assert resp.headers["Location"] == "https://pay.test/cs_1"
|
assert resp.headers["Location"] == "https://pay.test/cs_1"
|
||||||
assert db.get_by_user(uid)["status"] == "pending"
|
row = db.get_current(uid)
|
||||||
|
assert row["status"] == "pending"
|
||||||
|
assert row["frisbii_subscription_handle"] == f"abo-{uid}-1"
|
||||||
|
|
||||||
|
|
||||||
def test_subscribe_disables_trial_after_first_use(logged_in_client, monkeypatch):
|
def test_subscribe_disables_trial_after_first_use(logged_in_client, monkeypatch):
|
||||||
client, uid = logged_in_client
|
client, uid = logged_in_client
|
||||||
# L'utilisateur a déjà consommé un essai par le passé (abonnement maintenant expiré).
|
# L'utilisateur a déjà consommé un essai par le passé (abonnement maintenant expiré).
|
||||||
db.create_pending(uid, "colibre-%d" % uid, "simple")
|
handle, _ = db.create_pending(uid, "colibre-%d" % uid, "simple")
|
||||||
db.update_from_webhook(
|
db.update_from_webhook(handle, "trial", "2099-01-01T00:00:00+00:00")
|
||||||
"colibre-%d" % uid, "sub_42", "trial", "2099-01-01T00:00:00+00:00"
|
|
||||||
)
|
|
||||||
# L'essai est terminé : abonnement expiré, trial_used reste à 1.
|
# L'essai est terminé : abonnement expiré, trial_used reste à 1.
|
||||||
db.update_from_webhook(
|
db.update_from_webhook(handle, "expired", "2020-01-01T00:00:00+00:00")
|
||||||
"colibre-%d" % uid, "sub_42", "expired", "2020-01-01T00:00:00+00:00"
|
|
||||||
)
|
|
||||||
captured = {}
|
captured = {}
|
||||||
monkeypatch.setattr(frisbii_client, "update_customer", lambda h, d: {})
|
monkeypatch.setattr(frisbii_client, "update_customer", lambda h, d: {})
|
||||||
|
|
||||||
def fake_session(
|
def fake_session(
|
||||||
plan, ok, ko, no_trial=False, customer_handle=None, create_customer=None
|
plan, handle, ok, ko, no_trial=False, customer_handle=None, create_customer=None
|
||||||
):
|
):
|
||||||
captured["no_trial"] = no_trial
|
captured["no_trial"] = no_trial
|
||||||
return "https://pay.test/cs_9"
|
return "https://pay.test/cs_9"
|
||||||
@@ -62,8 +61,8 @@ def test_subscribe_unknown_plan(logged_in_client):
|
|||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
def test_subscribe_api_error_redirects_with_error(logged_in_client, monkeypatch):
|
def test_subscribe_api_error_marks_failed_and_redirects(logged_in_client, monkeypatch):
|
||||||
client, _ = logged_in_client
|
client, uid = logged_in_client
|
||||||
|
|
||||||
def boom(h, d):
|
def boom(h, d):
|
||||||
raise frisbii_client.FrisbiiError(500, "boom")
|
raise frisbii_client.FrisbiiError(500, "boom")
|
||||||
@@ -72,6 +71,8 @@ def test_subscribe_api_error_redirects_with_error(logged_in_client, monkeypatch)
|
|||||||
resp = client.post("/subscriptions/subscribe", data={"plan": "simple"})
|
resp = client.post("/subscriptions/subscribe", data={"plan": "simple"})
|
||||||
assert resp.status_code == 302
|
assert resp.status_code == 302
|
||||||
assert "error=frisbii" in resp.headers["Location"]
|
assert "error=frisbii" in resp.headers["Location"]
|
||||||
|
row = db.get_current(uid)
|
||||||
|
assert row["status"] == "failed"
|
||||||
|
|
||||||
|
|
||||||
def test_subscribe_requires_login(sub_app):
|
def test_subscribe_requires_login(sub_app):
|
||||||
@@ -136,10 +137,8 @@ def test_webhook_updates_subscription(sub_app, monkeypatch):
|
|||||||
def test_subscribe_skips_if_already_active(logged_in_client, monkeypatch):
|
def test_subscribe_skips_if_already_active(logged_in_client, monkeypatch):
|
||||||
"""Fix 1 : un abonné actif ne doit pas voir son statut remis à 'pending'."""
|
"""Fix 1 : un abonné actif ne doit pas voir son statut remis à 'pending'."""
|
||||||
client, uid = logged_in_client
|
client, uid = logged_in_client
|
||||||
db.create_pending(uid, "colibre-%d" % uid, "simple")
|
handle, _ = db.create_pending(uid, "colibre-%d" % uid, "simple")
|
||||||
db.update_from_webhook(
|
db.update_from_webhook(handle, "active", "2099-01-01T00:00:00+00:00")
|
||||||
"colibre-%d" % uid, "sub_42", "active", "2099-01-01T00:00:00+00:00"
|
|
||||||
)
|
|
||||||
called = []
|
called = []
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
frisbii_client, "update_customer", lambda h, d: called.append(h)
|
frisbii_client, "update_customer", lambda h, d: called.append(h)
|
||||||
@@ -150,17 +149,15 @@ def test_subscribe_skips_if_already_active(logged_in_client, monkeypatch):
|
|||||||
assert "compte/abonnement" in resp.headers["Location"]
|
assert "compte/abonnement" in resp.headers["Location"]
|
||||||
assert called == [], "Frisbii ne doit pas être appelé pour un abonné actif"
|
assert called == [], "Frisbii ne doit pas être appelé pour un abonné actif"
|
||||||
# Le statut DB ne doit pas avoir été écrasé.
|
# Le statut DB ne doit pas avoir été écrasé.
|
||||||
assert db.get_by_user(uid)["status"] == "active"
|
assert db.get_current(uid)["status"] == "active"
|
||||||
|
|
||||||
|
|
||||||
def test_subscribe_skips_if_trial_active(logged_in_client, monkeypatch):
|
def test_subscribe_skips_if_trial_active(logged_in_client, monkeypatch):
|
||||||
"""Fix 1 : un abonné en période d'essai est protégé de la même façon."""
|
"""Fix 1 : un abonné en période d'essai est protégé de la même façon."""
|
||||||
client, uid = logged_in_client
|
client, uid = logged_in_client
|
||||||
db.create_pending(uid, "colibre-%d" % uid, "simple")
|
handle, _ = db.create_pending(uid, "colibre-%d" % uid, "simple")
|
||||||
db.update_from_webhook(
|
db.update_from_webhook(handle, "trial", "2099-01-01T00:00:00+00:00")
|
||||||
"colibre-%d" % uid, "sub_42", "trial", "2099-01-01T00:00:00+00:00"
|
|
||||||
)
|
|
||||||
resp = client.post("/subscriptions/subscribe", data={"plan": "simple"})
|
resp = client.post("/subscriptions/subscribe", data={"plan": "simple"})
|
||||||
assert resp.status_code == 302
|
assert resp.status_code == 302
|
||||||
assert "compte/abonnement" in resp.headers["Location"]
|
assert "compte/abonnement" in resp.headers["Location"]
|
||||||
assert db.get_by_user(uid)["status"] == "trial"
|
assert db.get_current(uid)["status"] == "trial"
|
||||||
|
|||||||
Reference in New Issue
Block a user