# -*- coding: utf-8 -*- """支付子应用仓储:充值订单行级操作(不掺 HTTP 逻辑)。""" from __future__ import annotations from sqlalchemy import select from ..infrastructure.repositories import new_id, utcnow_iso from .models import ComputeRechargeOrder def _to_dict(o: ComputeRechargeOrder) -> dict: return { "id": o.id, "order_no": o.order_no, "user_id": o.user_id, "username": o.username, "engine_user_id": o.engine_user_id, "package_id": o.package_id, "amount_fen": o.amount_fen, "quota_micro": o.quota_micro, "bonus_quota_micro": o.bonus_quota_micro, "client_type": o.client_type, "appid": o.appid, "status": o.status, "prepay_id": o.prepay_id, "transaction_id": o.transaction_id, "code_url": o.code_url, "expires_at": o.expires_at, "paid_at": o.paid_at, "credited_at": o.credited_at, "notify_payload": o.notify_payload, "created_at": o.created_at, "updated_at": o.updated_at, } class ComputeRechargeOrderRepository: def __init__(self, session): self.session = session async def create(self, fields: dict) -> dict: now = utcnow_iso() row = ComputeRechargeOrder( id=new_id("cr"), created_at=now, updated_at=now, **{k: v for k, v in fields.items() if hasattr(ComputeRechargeOrder, k) and k != "id"}, ) self.session.add(row) await self.session.commit() return _to_dict(row) async def get_by_order_no(self, order_no: str) -> dict | None: row = await self.session.scalar( select(ComputeRechargeOrder).where(ComputeRechargeOrder.order_no == order_no) ) return _to_dict(row) if row else None async def find_pending_same_amount(self, user_id: str, amount_fen: int, now_iso: str) -> dict | None: """同用户同金额未过期 pending 单(下单防重复扫码复用)。""" row = await self.session.scalar( select(ComputeRechargeOrder) .where( ComputeRechargeOrder.user_id == user_id, ComputeRechargeOrder.amount_fen == amount_fen, ComputeRechargeOrder.status == "pending", ComputeRechargeOrder.expires_at > now_iso, ) .order_by(ComputeRechargeOrder.created_at.desc()) .limit(1) ) return _to_dict(row) if row else None async def list_by_user(self, user_id: str, limit: int = 50) -> list[dict]: rows = (await self.session.scalars( select(ComputeRechargeOrder) .where(ComputeRechargeOrder.user_id == user_id) .order_by(ComputeRechargeOrder.created_at.desc()) .limit(limit) )).all() return [_to_dict(r) for r in rows] async def _row(self, order_no: str) -> ComputeRechargeOrder | None: return await self.session.scalar( select(ComputeRechargeOrder).where(ComputeRechargeOrder.order_no == order_no) ) async def mark_paid(self, order_no: str, transaction_id: str, payload_json: str) -> bool: """pending → paid(幂等排他):仅当仍是 pending 才置 paid;False=已被处理(回调重放/并发)。""" row = await self._row(order_no) if row is None or row.status != "pending": return False row.status = "paid" row.transaction_id = transaction_id or row.transaction_id row.paid_at = utcnow_iso() row.notify_payload = payload_json row.updated_at = utcnow_iso() await self.session.commit() return True async def mark_credited(self, order_no: str) -> None: """paid → credited:引擎到账完成。""" row = await self._row(order_no) if row is None: return row.status = "credited" row.credited_at = utcnow_iso() row.updated_at = utcnow_iso() await self.session.commit() async def set_prepay(self, order_no: str, appid: str, prepay_id: str) -> None: """记录 JSAPI 预支付信息(pending 态,小程序扫码拉起支付时回填)。""" row = await self._row(order_no) if row is None: return row.appid = appid or row.appid row.prepay_id = prepay_id or row.prepay_id row.updated_at = utcnow_iso() await self.session.commit() async def mark_status(self, order_no: str, status: str, payload_json: str = "") -> None: """置 closed / failed 等非终态迁移。""" row = await self._row(order_no) if row is None: return row.status = status if payload_json: row.notify_payload = payload_json row.updated_at = utcnow_iso() await self.session.commit()