mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 15:04: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,
|
||||
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.close()
|
||||
@ -631,6 +644,56 @@ def update_crm():
|
||||
save_artifact(proj["id"], "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"])
|
||||
def payment_create():
|
||||
"""Создаёт платёж. method: card | sbp | cash."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user