mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 16:24:47 +00:00
feat: payment integration — YooKassa (card/SBP QR) + cash, webhook auto-records to ledger
This commit is contained in:
parent
a056f1a7eb
commit
e42d42f207
@ -49,6 +49,18 @@ def _key():
|
||||
env = open("/opt/zashita-api/.env").read()
|
||||
return re.search(r'ANTHROPIC_API_KEY=(\S+)', env).group(1)
|
||||
|
||||
def _yookassa_creds():
|
||||
"""shopId и secret из .env. Если нет — None (демо-режим)."""
|
||||
try:
|
||||
env = open(os.path.join(BASE, ".env")).read()
|
||||
sid = re.search(r'YOOKASSA_SHOP_ID=(\S+)', env)
|
||||
sec = re.search(r'YOOKASSA_SECRET=(\S+)', env)
|
||||
if sid and sec:
|
||||
return sid.group(1), sec.group(1)
|
||||
except Exception:
|
||||
pass
|
||||
return None, None
|
||||
|
||||
client = anthropic.Anthropic(api_key=_key())
|
||||
SYSTEM_PROMPT = open(PROMPT_PATH, encoding="utf-8").read()
|
||||
|
||||
@ -619,6 +631,92 @@ def update_crm():
|
||||
save_artifact(proj["id"], "crm", crm)
|
||||
return jsonify({"ok": True, "crm": crm})
|
||||
|
||||
@app.route("/api/payment/create", methods=["POST"])
|
||||
def payment_create():
|
||||
"""Создаёт платёж. method: card | sbp | cash."""
|
||||
import urllib.request, urllib.parse
|
||||
data = request.get_json(force=True) or {}
|
||||
proj = get_project(data.get("token"))
|
||||
if not proj:
|
||||
return jsonify({"error": "project not found"}), 404
|
||||
amount = float(data.get("amount", 0))
|
||||
method = data.get("method", "card") # card | sbp | cash
|
||||
desc = data.get("description", f"Оплата консалтинга — {proj['client_name'] or 'клиент'}")
|
||||
if amount <= 0:
|
||||
return jsonify({"error": "сумма должна быть больше 0"}), 400
|
||||
|
||||
# Наличные — не онлайн: фиксируем намерение, Руслан подтвердит вручную
|
||||
if method == "cash":
|
||||
crm = latest_artifact(proj["id"], "crm") or {}
|
||||
pending = crm.get("pending_cash", [])
|
||||
pending.append({"amount": amount, "desc": desc, "at": now()})
|
||||
crm["pending_cash"] = pending
|
||||
save_artifact(proj["id"], "crm", crm)
|
||||
return jsonify({"method": "cash", "instructions": "Оплата наличными при встрече. Консультант подтвердит получение.", "amount": amount})
|
||||
|
||||
sid, sec = _yookassa_creds()
|
||||
return_url = data.get("return_url", "https://wasrusgen1.ru/consulting/cabinet.html")
|
||||
|
||||
# Демо-режим если ключей ЮKassa нет
|
||||
if not sid:
|
||||
return jsonify({
|
||||
"method": method, "demo": True,
|
||||
"confirmation_url": return_url + "?demo_paid=" + str(int(amount)),
|
||||
"note": "ДЕМО: ключи ЮKassa не настроены. Реальная оплата заработает после добавления YOOKASSA_SHOP_ID/SECRET."
|
||||
})
|
||||
|
||||
# Реальный вызов ЮKassa API
|
||||
import base64 as b64m, json as jsonm
|
||||
payload = {
|
||||
"amount": {"value": f"{amount:.2f}", "currency": "RUB"},
|
||||
"capture": True,
|
||||
"description": desc,
|
||||
"metadata": {"token": proj["token"]},
|
||||
"confirmation": {"type": "redirect", "return_url": return_url}
|
||||
}
|
||||
if method == "sbp":
|
||||
payload["payment_method_data"] = {"type": "sbp"}
|
||||
payload["confirmation"] = {"type": "qr"}
|
||||
try:
|
||||
body = jsonm.dumps(payload).encode()
|
||||
req = urllib.request.Request("https://api.yookassa.ru/v3/payments", data=body, method="POST")
|
||||
auth = b64m.b64encode(f"{sid}:{sec}".encode()).decode()
|
||||
req.add_header("Authorization", "Basic " + auth)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
req.add_header("Idempotence-Key", secrets.token_hex(16))
|
||||
resp = urllib.request.urlopen(req, timeout=20)
|
||||
result = jsonm.loads(resp.read())
|
||||
conf = result.get("confirmation", {})
|
||||
return jsonify({
|
||||
"method": method, "payment_id": result.get("id"),
|
||||
"confirmation_url": conf.get("confirmation_url"),
|
||||
"qr": conf.get("confirmation_data") # для СБП — QR-данные
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": "ЮKassa: " + str(e)}), 500
|
||||
|
||||
@app.route("/api/payment/webhook", methods=["POST"])
|
||||
def payment_webhook():
|
||||
"""ЮKassa шлёт уведомление о статусе. При succeeded — платёж в реестр."""
|
||||
import json as jsonm
|
||||
data = request.get_json(force=True) or {}
|
||||
event = data.get("event")
|
||||
obj = data.get("object", {})
|
||||
if event == "payment.succeeded":
|
||||
token = obj.get("metadata", {}).get("token")
|
||||
proj = get_project(token) if token else None
|
||||
if proj:
|
||||
amount = float(obj.get("amount", {}).get("value", 0))
|
||||
method = obj.get("payment_method", {}).get("type", "")
|
||||
crm = latest_artifact(proj["id"], "crm") or {"payments": []}
|
||||
crm.setdefault("payments", []).append({
|
||||
"date": now()[:10], "amount": amount,
|
||||
"note": f"ЮKassa ({method})", "auto": True
|
||||
})
|
||||
crm["paid_amount"] = sum(p.get("amount", 0) for p in crm["payments"])
|
||||
save_artifact(proj["id"], "crm", crm)
|
||||
return jsonify({"ok": True}) # ЮKassa требует 200
|
||||
|
||||
@app.route("/api/project/delete", methods=["POST"])
|
||||
def delete_project():
|
||||
data = request.get_json(force=True) or {}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user