@@ -340,6 +340,27 @@ class Message(db.Model):
340340 sent_at = db .Column (db .DateTime , default = datetime .utcnow )
341341 sender = db .relationship ("User" , foreign_keys = [sender_id ], lazy = True )
342342
343+ # ── Escrow Model ─────────────────────────────────────────────────────────────
344+ class EscrowStatus (enum .Enum ):
345+ held = "held"
346+ released = "released"
347+ refunded = "refunded"
348+
349+ class EscrowTransaction (db .Model ):
350+ __tablename__ = "escrow_transactions"
351+ id = db .Column (db .Integer , primary_key = True )
352+ conversation_id = db .Column (db .Integer , db .ForeignKey ("conversations.id" ), nullable = False )
353+ buyer_id = db .Column (db .Integer , db .ForeignKey ("users.id" ), nullable = False )
354+ seller_id = db .Column (db .Integer , db .ForeignKey ("users.id" ), nullable = False )
355+ amount_tzs = db .Column (db .BigInteger , nullable = False )
356+ status = db .Column (db .Enum (EscrowStatus ), default = EscrowStatus .held , nullable = False )
357+ reference = db .Column (db .String (32 ), unique = True , nullable = False )
358+ created_at = db .Column (db .DateTime , default = datetime .utcnow )
359+ updated_at = db .Column (db .DateTime , default = datetime .utcnow , onupdate = datetime .utcnow )
360+ buyer = db .relationship ("User" , foreign_keys = [buyer_id ], lazy = True )
361+ seller = db .relationship ("User" , foreign_keys = [seller_id ], lazy = True )
362+ conversation = db .relationship ("Conversation" , foreign_keys = [conversation_id ], lazy = True )
363+
343364# ── Login Manager ─────────────────────────────────────────────────────────────
344365
345366
@@ -2385,6 +2406,93 @@ def notification_unread_count():
23852406 ).count ()
23862407 return jsonify ({"unread" : count })
23872408
2409+ @app .route ("/api/escrow/hold" , methods = ["POST" ])
2410+ @login_required
2411+ @require_active_account
2412+ def escrow_hold ():
2413+ """Mock: buyer anaanzisha malipo ya escrow."""
2414+ import secrets
2415+ data = request .get_json (silent = True ) or {}
2416+ conv_id = data .get ("conversation_id" )
2417+ amount = data .get ("amount_tzs" )
2418+ if not conv_id or not amount :
2419+ return jsonify ({"error" : "Taarifa hazikamiliki" }), 400
2420+ conv = Conversation .query .get_or_404 (conv_id )
2421+ if conv .buyer_id != current_user .id :
2422+ return jsonify ({"error" : "Huna ruhusa" }), 403
2423+ # Angalia kama escrow ipo tayari kwa conversation hii
2424+ existing = EscrowTransaction .query .filter_by (
2425+ conversation_id = conv_id , status = EscrowStatus .held
2426+ ).first ()
2427+ if existing :
2428+ return jsonify ({"error" : "Escrow ipo tayari" , "reference" : existing .reference }), 409
2429+ ref = "ESC-" + secrets .token_hex (6 ).upper ()
2430+ tx = EscrowTransaction (
2431+ conversation_id = conv_id ,
2432+ buyer_id = conv .buyer_id ,
2433+ seller_id = conv .seller_id ,
2434+ amount_tzs = int (amount ),
2435+ status = EscrowStatus .held ,
2436+ reference = ref ,
2437+ )
2438+ db .session .add (tx )
2439+ db .session .commit ()
2440+ return jsonify ({"success" : True , "reference" : ref , "status" : "held" ,
2441+ "message" : f"TZS { int (amount ):,} imehifadhiwa salama. Ref: { ref } " })
2442+
2443+ @app .route ("/api/escrow/release" , methods = ["POST" ])
2444+ @login_required
2445+ @require_active_account
2446+ def escrow_release ():
2447+ """Mock: buyer anakubali kutoa pesa kwa seller baada ya kupokea bidhaa."""
2448+ data = request .get_json (silent = True ) or {}
2449+ ref = data .get ("reference" )
2450+ tx = EscrowTransaction .query .filter_by (reference = ref ).first_or_404 ()
2451+ if tx .buyer_id != current_user .id :
2452+ return jsonify ({"error" : "Huna ruhusa" }), 403
2453+ if tx .status != EscrowStatus .held :
2454+ return jsonify ({"error" : f"Hali ya sasa: { tx .status .value } " }), 409
2455+ tx .status = EscrowStatus .released
2456+ db .session .commit ()
2457+ return jsonify ({"success" : True , "status" : "released" ,
2458+ "message" : f"TZS { tx .amount_tzs :,} imetolewa kwa muuzaji. Asante!" })
2459+
2460+ @app .route ("/api/escrow/refund" , methods = ["POST" ])
2461+ @login_required
2462+ @require_active_account
2463+ def escrow_refund ():
2464+ """Mock: admin/seller anarudisha pesa kwa buyer."""
2465+ data = request .get_json (silent = True ) or {}
2466+ ref = data .get ("reference" )
2467+ tx = EscrowTransaction .query .filter_by (reference = ref ).first_or_404 ()
2468+ if current_user .role not in ("admin" ,) and tx .seller_id != current_user .id :
2469+ return jsonify ({"error" : "Huna ruhusa" }), 403
2470+ if tx .status != EscrowStatus .held :
2471+ return jsonify ({"error" : f"Hali ya sasa: { tx .status .value } " }), 409
2472+ tx .status = EscrowStatus .refunded
2473+ db .session .commit ()
2474+ return jsonify ({"success" : True , "status" : "refunded" ,
2475+ "message" : f"TZS { tx .amount_tzs :,} imerudishwa kwa mnunuzi." })
2476+
2477+ @app .route ("/api/escrow/status/<int:conv_id>" , methods = ["GET" ])
2478+ @login_required
2479+ def escrow_status (conv_id ):
2480+ """Angalia hali ya escrow kwa conversation."""
2481+ conv = Conversation .query .get_or_404 (conv_id )
2482+ if current_user .id not in (conv .buyer_id , conv .seller_id ):
2483+ return jsonify ({"error" : "Huna ruhusa" }), 403
2484+ tx = EscrowTransaction .query .filter_by (conversation_id = conv_id ).order_by (
2485+ EscrowTransaction .created_at .desc ()
2486+ ).first ()
2487+ if not tx :
2488+ return jsonify ({"escrow" : None })
2489+ return jsonify ({"escrow" : {
2490+ "reference" : tx .reference ,
2491+ "amount_tzs" : tx .amount_tzs ,
2492+ "status" : tx .status .value ,
2493+ "created_at" : tx .created_at .strftime ("%d %b %Y, %H:%M" ),
2494+ }})
2495+
23882496@app .route ("/api/conversations/mine" , methods = ["GET" ])
23892497@login_required
23902498@require_active_account
@@ -2436,7 +2544,17 @@ def messages_thread(conv_id):
24362544 conv = Conversation .query .get_or_404 (conv_id )
24372545 if current_user .id not in (conv .buyer_id , conv .seller_id ):
24382546 return redirect (url_for ("messages_inbox" ))
2439- return render_template ("messages/thread.html" , conv_id = conv_id )
2547+ is_buyer = current_user .id == conv .buyer_id
2548+ is_seller = current_user .id == conv .seller_id
2549+ listing_price = conv .listing .price_tzs if conv .listing else 0
2550+ listing_qty = conv .listing .quantity_kg if conv .listing else 0
2551+ return render_template ("messages/thread.html" ,
2552+ conv_id = conv_id ,
2553+ is_buyer = is_buyer ,
2554+ is_seller = is_seller ,
2555+ listing_price = listing_price ,
2556+ listing_qty = listing_qty ,
2557+ )
24402558
24412559# ════════════════════════════════════════════════════════════════════════════
24422560# B2B BUYER PORTAL
0 commit comments