mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 19:44:47 +00:00
feat: e-signature acceptance — append-only journal with SHA-256 doc hash (63-FZ)
This commit is contained in:
parent
d378d47421
commit
1b333debd9
@ -117,6 +117,19 @@ def init_db():
|
|||||||
created_at TEXT,
|
created_at TEXT,
|
||||||
FOREIGN KEY(project_id) REFERENCES projects(id)
|
FOREIGN KEY(project_id) REFERENCES projects(id)
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS acceptances (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
project_id INTEGER NOT NULL,
|
||||||
|
doc TEXT NOT NULL, -- 'offer' | 'pep' | 'pdn'
|
||||||
|
doc_version TEXT NOT NULL,
|
||||||
|
doc_hash TEXT NOT NULL, -- SHA-256 текста документа (что подписано)
|
||||||
|
identifier TEXT, -- телефон/email подписанта
|
||||||
|
code TEXT, -- код подтверждения
|
||||||
|
ip TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
payment_id TEXT, -- акцепт оплатой
|
||||||
|
accepted_at TEXT NOT NULL -- append-only, не редактируется
|
||||||
|
);
|
||||||
""")
|
""")
|
||||||
con.commit()
|
con.commit()
|
||||||
con.close()
|
con.close()
|
||||||
@ -631,6 +644,56 @@ def update_crm():
|
|||||||
save_artifact(proj["id"], "crm", crm)
|
save_artifact(proj["id"], "crm", crm)
|
||||||
return jsonify({"ok": True, "crm": crm})
|
return jsonify({"ok": True, "crm": crm})
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# Версии и тексты юридических документов (хеш фиксируется при акцепте)
|
||||||
|
LEGAL_DOCS = {"offer": "1.0", "pep": "1.0", "pdn": "1.0"}
|
||||||
|
def _doc_hash(doc):
|
||||||
|
"""SHA-256 текста документа — для доказательства что подписано."""
|
||||||
|
path = os.path.join(BASE, "legal", {"offer": "dogovor_oferta.md", "pep": "soglashenie_pep.md", "pdn": "politika_pdn.md"}.get(doc, ""))
|
||||||
|
try:
|
||||||
|
return hashlib.sha256(open(path, "rb").read()).hexdigest()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@app.route("/api/accept", methods=["POST"])
|
||||||
|
def accept_documents():
|
||||||
|
"""Фиксация акцепта (ПЭП) в append-only журнал с хешем документов."""
|
||||||
|
data = request.get_json(force=True) or {}
|
||||||
|
proj = get_project(data.get("token"))
|
||||||
|
if not proj:
|
||||||
|
return jsonify({"error": "project not found"}), 404
|
||||||
|
docs = data.get("docs", ["offer", "pep"]) # какие документы акцептованы
|
||||||
|
identifier = data.get("identifier", "") # телефон/email
|
||||||
|
code = data.get("code", "")
|
||||||
|
payment_id = data.get("payment_id", "")
|
||||||
|
ip = request.headers.get("X-Real-IP") or request.headers.get("X-Forwarded-For", request.remote_addr or "")
|
||||||
|
ua = request.headers.get("User-Agent", "")[:300]
|
||||||
|
con = db()
|
||||||
|
recorded = []
|
||||||
|
for doc in docs:
|
||||||
|
if doc not in LEGAL_DOCS:
|
||||||
|
continue
|
||||||
|
h = _doc_hash(doc)
|
||||||
|
con.execute(
|
||||||
|
"INSERT INTO acceptances (project_id, doc, doc_version, doc_hash, identifier, code, ip, user_agent, payment_id, accepted_at) VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(proj["id"], doc, LEGAL_DOCS[doc], h, identifier, code, ip, ua, payment_id, now())
|
||||||
|
)
|
||||||
|
recorded.append({"doc": doc, "version": LEGAL_DOCS[doc], "hash": h[:16] + "..."})
|
||||||
|
con.commit()
|
||||||
|
return jsonify({"ok": True, "accepted": recorded, "at": now()})
|
||||||
|
|
||||||
|
@app.route("/api/acceptances/<token>")
|
||||||
|
def get_acceptances(token):
|
||||||
|
"""Выписка из журнала акцептов (доказательная база)."""
|
||||||
|
proj = get_project(token)
|
||||||
|
if not proj:
|
||||||
|
return jsonify({"error": "not found"}), 404
|
||||||
|
rows = db().execute(
|
||||||
|
"SELECT doc, doc_version, doc_hash, identifier, code, ip, payment_id, accepted_at FROM acceptances WHERE project_id=? ORDER BY id", (proj["id"],)
|
||||||
|
).fetchall()
|
||||||
|
return jsonify({"acceptances": [dict(r) for r in rows]})
|
||||||
|
|
||||||
@app.route("/api/payment/create", methods=["POST"])
|
@app.route("/api/payment/create", methods=["POST"])
|
||||||
def payment_create():
|
def payment_create():
|
||||||
"""Создаёт платёж. method: card | sbp | cash."""
|
"""Создаёт платёж. method: card | sbp | cash."""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user