feat(weibo): 区分充值资金来源并完善协作单记录
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
"""持久化微博 Web 充值记录并区分订单资金来源。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20261113_0082"
|
||||
down_revision: str | None = "20261113_0081"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
ORDERS = "weibo_external_orders"
|
||||
CHARGES = "weibo_recharge_records"
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if inspector.has_table(ORDERS):
|
||||
columns = {column["name"] for column in inspector.get_columns(ORDERS)}
|
||||
if "funding_source" not in columns:
|
||||
op.add_column(
|
||||
ORDERS,
|
||||
sa.Column(
|
||||
"funding_source",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
server_default="",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_weibo_external_orders_funding_source",
|
||||
ORDERS,
|
||||
["funding_source"],
|
||||
)
|
||||
if "funding_source_detail" not in columns:
|
||||
op.add_column(
|
||||
ORDERS,
|
||||
sa.Column(
|
||||
"funding_source_detail",
|
||||
sa.String(length=512),
|
||||
nullable=False,
|
||||
server_default="",
|
||||
),
|
||||
)
|
||||
if "funding_recharge_record_id" not in columns:
|
||||
op.add_column(
|
||||
ORDERS,
|
||||
sa.Column("funding_recharge_record_id", sa.Integer(), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_weibo_external_orders_funding_recharge_record_id",
|
||||
ORDERS,
|
||||
["funding_recharge_record_id"],
|
||||
)
|
||||
|
||||
if not inspector.has_table(CHARGES):
|
||||
op.create_table(
|
||||
CHARGES,
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column(
|
||||
"account_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("weibo_accounts.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("order_id", sa.String(length=64), nullable=False),
|
||||
sa.Column(
|
||||
"pay_id",
|
||||
sa.String(length=128),
|
||||
nullable=False,
|
||||
server_default="",
|
||||
),
|
||||
sa.Column("amount_yuan", sa.Integer(), nullable=False),
|
||||
sa.Column("single_reward", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"status",
|
||||
sa.String(length=24),
|
||||
nullable=False,
|
||||
server_default="created",
|
||||
),
|
||||
sa.Column(
|
||||
"sub_status",
|
||||
sa.String(length=64),
|
||||
nullable=False,
|
||||
server_default="",
|
||||
),
|
||||
sa.Column("paid_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("status_checked_at", sa.DateTime(), nullable=True),
|
||||
sa.Column(
|
||||
"created_by",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.UniqueConstraint("order_id", name="uq_weibo_recharge_records_order_id"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_weibo_recharge_records_account_id",
|
||||
CHARGES,
|
||||
["account_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_weibo_recharge_records_order_id",
|
||||
CHARGES,
|
||||
["order_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_weibo_recharge_records_pay_id",
|
||||
CHARGES,
|
||||
["pay_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_weibo_recharge_records_status",
|
||||
CHARGES,
|
||||
["status"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_weibo_recharge_records_created_at",
|
||||
CHARGES,
|
||||
["created_at"],
|
||||
)
|
||||
|
||||
if inspector.has_table(ORDERS):
|
||||
has_audit = inspector.has_table("audit_logs")
|
||||
audit_exists = (
|
||||
"""
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM audit_logs a
|
||||
WHERE a.action = 'weibo:recharge:create_order'
|
||||
AND JSON_EXTRACT(a.detail, '$.account_id') = o.account_id
|
||||
AND a.created_at >= o.created_at
|
||||
AND a.created_at <= o.recharged_at
|
||||
)
|
||||
"""
|
||||
if has_audit
|
||||
else "FALSE"
|
||||
)
|
||||
bind.execute(
|
||||
sa.text(
|
||||
f"""
|
||||
UPDATE {ORDERS} o
|
||||
SET funding_source = CASE
|
||||
WHEN {audit_exists} THEN 'legacy_web_recharge'
|
||||
ELSE 'account_balance'
|
||||
END,
|
||||
funding_source_detail = CASE
|
||||
WHEN {audit_exists}
|
||||
THEN '历史回填:核账前存在 Web 充值下单审计,但历史支付结果未持久化'
|
||||
ELSE '历史回填:核账前未发现该账号的 Web 充值下单记录'
|
||||
END
|
||||
WHERE o.recharged_at IS NOT NULL
|
||||
AND o.funding_source = ''
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
inspector = sa.inspect(op.get_bind())
|
||||
if inspector.has_table(CHARGES):
|
||||
op.drop_table(CHARGES)
|
||||
if inspector.has_table(ORDERS):
|
||||
columns = {column["name"] for column in inspector.get_columns(ORDERS)}
|
||||
if "funding_recharge_record_id" in columns:
|
||||
op.drop_column(ORDERS, "funding_recharge_record_id")
|
||||
if "funding_source_detail" in columns:
|
||||
op.drop_column(ORDERS, "funding_source_detail")
|
||||
if "funding_source" in columns:
|
||||
op.drop_column(ORDERS, "funding_source")
|
||||
@@ -1115,6 +1115,16 @@ class WeiboExternalOrder(Base):
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20), default="incomplete", server_default="incomplete", index=True
|
||||
)
|
||||
# 余额资金来源:web_recharge / account_balance / legacy_web_recharge / unknown
|
||||
funding_source: Mapped[str] = mapped_column(
|
||||
String(32), default="", server_default="", index=True
|
||||
)
|
||||
funding_source_detail: Mapped[str] = mapped_column(
|
||||
String(512), default="", server_default=""
|
||||
)
|
||||
funding_recharge_record_id: Mapped[int | None] = mapped_column(
|
||||
Integer, nullable=True, index=True
|
||||
)
|
||||
recharged_at: Mapped[datetime | None] = mapped_column(UTCDateTime, nullable=True)
|
||||
scripted_at: Mapped[datetime | None] = mapped_column(UTCDateTime, nullable=True)
|
||||
exchange_started_at: Mapped[datetime | None] = mapped_column(
|
||||
@@ -1208,6 +1218,50 @@ class WeiboExternalOrderMetric(Base):
|
||||
)
|
||||
|
||||
|
||||
class WeiboRechargeRecord(Base):
|
||||
"""微博 Web 充值下单与支付状态记录。"""
|
||||
|
||||
__tablename__ = "weibo_recharge_records"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
account_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("weibo_accounts.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
order_id: Mapped[str] = mapped_column(
|
||||
String(64), nullable=False, unique=True, index=True
|
||||
)
|
||||
pay_id: Mapped[str] = mapped_column(
|
||||
String(128), nullable=False, default="", server_default="", index=True
|
||||
)
|
||||
amount_yuan: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
single_reward: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(24),
|
||||
nullable=False,
|
||||
default="created",
|
||||
server_default="created",
|
||||
index=True,
|
||||
)
|
||||
sub_status: Mapped[str] = mapped_column(
|
||||
String(64), nullable=False, default="", server_default=""
|
||||
)
|
||||
paid_at: Mapped[datetime | None] = mapped_column(UTCDateTime, nullable=True)
|
||||
status_checked_at: Mapped[datetime | None] = mapped_column(
|
||||
UTCDateTime, nullable=True
|
||||
)
|
||||
created_by: Mapped[int | None] = mapped_column(
|
||||
ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
UTCDateTime, default=_utcnow, index=True
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
UTCDateTime, default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
|
||||
class HuyaRegisterBatch(Base):
|
||||
"""虎牙自动注册批次(持久化元数据)"""
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ from ..services.weibo_service import (
|
||||
generate_weibo_bind_qrcode,
|
||||
generate_weibo_bind_schemes,
|
||||
import_weibo_cookies,
|
||||
persist_weibo_payment_status,
|
||||
persist_weibo_recharge_order,
|
||||
query_weibo_exchange_records,
|
||||
query_weibo_mall,
|
||||
query_weibo_payment_status,
|
||||
@@ -1600,6 +1602,12 @@ def create_recharge_order(
|
||||
)
|
||||
except (WeiboError, ValueError) as exc:
|
||||
raise _manual_http_error(exc) from exc
|
||||
persist_weibo_recharge_order(
|
||||
db,
|
||||
account,
|
||||
order,
|
||||
created_by=current.id,
|
||||
)
|
||||
_record_audit_safely(
|
||||
db,
|
||||
current,
|
||||
@@ -1643,6 +1651,7 @@ def get_recharge_status(
|
||||
status = query_weibo_payment_status(db, account, pay_id=req.pay_id)
|
||||
except (WeiboError, ValueError) as exc:
|
||||
raise _manual_http_error(exc) from exc
|
||||
persist_weibo_payment_status(db, account, status)
|
||||
return WeiboPaymentStatusOut(
|
||||
pay_id=status.pay_id,
|
||||
out_pay_id=status.out_pay_id,
|
||||
|
||||
@@ -911,6 +911,24 @@ class WeiboCollabArchiveRequest(BaseModel):
|
||||
reason: str = Field("", max_length=256)
|
||||
|
||||
|
||||
class WeiboRechargeRecordOut(BaseModel):
|
||||
id: int
|
||||
order_id: str
|
||||
pay_id: str = ""
|
||||
amount_yuan: int = 0
|
||||
single_reward: int = 0
|
||||
status: str = "created"
|
||||
status_label: str = ""
|
||||
sub_status: str = ""
|
||||
is_paid: bool = False
|
||||
paid_at: datetime | None = None
|
||||
status_checked_at: datetime | None = None
|
||||
created_by: int | None = None
|
||||
created_by_name: str = ""
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
class WeiboCollabOrderOut(BaseModel):
|
||||
id: int
|
||||
order_no: str
|
||||
@@ -935,6 +953,10 @@ class WeiboCollabOrderOut(BaseModel):
|
||||
remark: str = ""
|
||||
status: str = "incomplete"
|
||||
status_label: str = ""
|
||||
funding_source: str = ""
|
||||
funding_source_label: str = ""
|
||||
funding_source_detail: str = ""
|
||||
funding_recharge_record_id: int | None = None
|
||||
next_status: str | None = None
|
||||
missing_fields: list[str] = Field(default_factory=list)
|
||||
weidou_balance: int | None = None
|
||||
@@ -970,6 +992,7 @@ class WeiboCollabOrderOut(BaseModel):
|
||||
account_cluster: dict[str, Any] | None = None
|
||||
last_metric_at: datetime | None = None
|
||||
last_metric_message: str = ""
|
||||
recharge_records: list[WeiboRechargeRecordOut] = Field(default_factory=list)
|
||||
|
||||
|
||||
class WeiboCollabOrderListOut(BaseModel):
|
||||
|
||||
@@ -26,6 +26,7 @@ from ..models import (
|
||||
WeiboCollabShop,
|
||||
WeiboExternalOrder,
|
||||
WeiboExternalOrderMetric,
|
||||
WeiboRechargeRecord,
|
||||
)
|
||||
from .audit_service import record_audit
|
||||
from .collab_common import apply_order_visibility, count_archived
|
||||
@@ -58,7 +59,7 @@ ORDER_STATUSES = (
|
||||
ORDER_STATUS_LABELS = {
|
||||
STATUS_INCOMPLETE: "待补资料",
|
||||
STATUS_TO_RECHARGE: "待充值",
|
||||
STATUS_RECHARGED: "已充值",
|
||||
STATUS_RECHARGED: "余额已核账",
|
||||
STATUS_SCRIPTED: "已上脚本",
|
||||
STATUS_DONE: "兑换成功",
|
||||
STATUS_WEIDOU_ERROR: "微豆异常",
|
||||
@@ -729,6 +730,9 @@ def set_status(
|
||||
now = _now()
|
||||
if target == STATUS_TO_RECHARGE:
|
||||
order.recharged_at = None
|
||||
order.funding_source = ""
|
||||
order.funding_source_detail = ""
|
||||
order.funding_recharge_record_id = None
|
||||
order.scripted_at = None
|
||||
order.exchange_started_at = None
|
||||
order.done_at = None
|
||||
@@ -933,6 +937,60 @@ def _metric(
|
||||
return metric
|
||||
|
||||
|
||||
FUNDING_SOURCE_WEB_RECHARGE = "web_recharge"
|
||||
FUNDING_SOURCE_ACCOUNT_BALANCE = "account_balance"
|
||||
FUNDING_SOURCE_LEGACY_WEB_RECHARGE = "legacy_web_recharge"
|
||||
FUNDING_SOURCE_UNKNOWN = "unknown"
|
||||
FUNDING_SOURCE_LABELS = {
|
||||
FUNDING_SOURCE_WEB_RECHARGE: "系统 Web 充值",
|
||||
FUNDING_SOURCE_ACCOUNT_BALANCE: "账号原有/外部充值",
|
||||
FUNDING_SOURCE_LEGACY_WEB_RECHARGE: "历史 Web 充值下单",
|
||||
FUNDING_SOURCE_UNKNOWN: "来源未知",
|
||||
}
|
||||
RECHARGE_STATUS_LABELS = {
|
||||
"created": "已创建",
|
||||
"pending": "等待支付",
|
||||
"paid": "支付成功",
|
||||
"failed": "支付失败",
|
||||
"closed": "订单关闭",
|
||||
"unknown": "状态未知",
|
||||
}
|
||||
|
||||
|
||||
def _classify_balance_funding(
|
||||
db: Session,
|
||||
account: WeiboAccount,
|
||||
group_orders: Sequence[WeiboExternalOrder],
|
||||
) -> tuple[str, str, int | None]:
|
||||
"""按账号维度和订单创建时间识别本次核账余额来源。"""
|
||||
earliest = min(
|
||||
(order.created_at for order in group_orders if order.created_at),
|
||||
default=None,
|
||||
)
|
||||
query = db.query(WeiboRechargeRecord).filter(
|
||||
WeiboRechargeRecord.account_id == account.id,
|
||||
WeiboRechargeRecord.status == "paid",
|
||||
WeiboRechargeRecord.paid_at.isnot(None),
|
||||
)
|
||||
if earliest is not None:
|
||||
query = query.filter(WeiboRechargeRecord.paid_at >= earliest)
|
||||
paid = query.order_by(WeiboRechargeRecord.paid_at.desc()).first()
|
||||
if paid is not None:
|
||||
return (
|
||||
FUNDING_SOURCE_WEB_RECHARGE,
|
||||
(
|
||||
f"核账前存在已支付的 Web 充值:{paid.amount_yuan} 元,"
|
||||
f"赠送 {paid.single_reward} 微豆,订单号 {paid.order_id}"
|
||||
),
|
||||
int(paid.id),
|
||||
)
|
||||
return (
|
||||
FUNDING_SOURCE_ACCOUNT_BALANCE,
|
||||
"核账通过,但核账前未发现已支付的系统 Web 充值记录",
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
def check_balance_groups(
|
||||
db: Session,
|
||||
current: User,
|
||||
@@ -1080,6 +1138,14 @@ def check_balance_groups(
|
||||
target = STATUS_RECHARGED
|
||||
|
||||
now = _now()
|
||||
if balance is None:
|
||||
funding_source = FUNDING_SOURCE_UNKNOWN
|
||||
funding_detail = "未拿到微豆余额,无法识别资金来源"
|
||||
funding_record_id = None
|
||||
else:
|
||||
funding_source, funding_detail, funding_record_id = (
|
||||
_classify_balance_funding(db, account, group_orders)
|
||||
)
|
||||
for order in group_orders:
|
||||
order.weidou_balance = balance
|
||||
order.weidou_required_total = required_total
|
||||
@@ -1097,7 +1163,13 @@ def check_balance_groups(
|
||||
"surplus": surplus,
|
||||
"threshold": threshold,
|
||||
"order_ids": [item.id for item in group_orders],
|
||||
"funding_source": funding_source,
|
||||
"funding_detail": funding_detail,
|
||||
"funding_recharge_record_id": funding_record_id,
|
||||
}
|
||||
order.funding_source = funding_source
|
||||
order.funding_source_detail = funding_detail[:512]
|
||||
order.funding_recharge_record_id = funding_record_id
|
||||
if target is not None:
|
||||
set_status(
|
||||
db,
|
||||
@@ -1126,6 +1198,9 @@ def check_balance_groups(
|
||||
"reason": reason,
|
||||
"role": game_role_name,
|
||||
"order_ids": [item.id for item in group_orders],
|
||||
"funding_source": funding_source,
|
||||
"funding_detail": funding_detail,
|
||||
"funding_recharge_record_id": funding_record_id,
|
||||
},
|
||||
)
|
||||
_audit(
|
||||
@@ -1141,6 +1216,9 @@ def check_balance_groups(
|
||||
"surplus": surplus,
|
||||
"threshold": threshold,
|
||||
"reason": reason,
|
||||
"funding_source": funding_source,
|
||||
"funding_detail": funding_detail,
|
||||
"funding_recharge_record_id": funding_record_id,
|
||||
},
|
||||
)
|
||||
groups.append(
|
||||
@@ -1540,6 +1618,7 @@ def serialize_order(
|
||||
accounts: Mapping[int, WeiboAccount] | None = None,
|
||||
cluster: Mapping[str, Any] | None = None,
|
||||
last_metric: WeiboExternalOrderMetric | None = None,
|
||||
recharge_records: Sequence[Mapping[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
names = users or {}
|
||||
account_map = accounts or {}
|
||||
@@ -1568,6 +1647,12 @@ def serialize_order(
|
||||
"remark": order.remark or "",
|
||||
"status": order.status,
|
||||
"status_label": ORDER_STATUS_LABELS.get(order.status, order.status),
|
||||
"funding_source": order.funding_source or "",
|
||||
"funding_source_label": FUNDING_SOURCE_LABELS.get(
|
||||
order.funding_source or "", ""
|
||||
),
|
||||
"funding_source_detail": order.funding_source_detail or "",
|
||||
"funding_recharge_record_id": order.funding_recharge_record_id,
|
||||
"next_status": NEXT_STATUS.get(order.status),
|
||||
"missing_fields": _missing_for_recharge(order),
|
||||
"weidou_balance": order.weidou_balance,
|
||||
@@ -1609,6 +1694,7 @@ def serialize_order(
|
||||
"account_cluster": dict(cluster) if cluster else None,
|
||||
"last_metric_at": last_metric.created_at if last_metric else None,
|
||||
"last_metric_message": last_metric.message if last_metric else "",
|
||||
"recharge_records": list(recharge_records or []),
|
||||
}
|
||||
|
||||
|
||||
@@ -1732,6 +1818,44 @@ def list_orders(
|
||||
}
|
||||
|
||||
|
||||
def _recharge_records_out(db: Session, account_id: int | None) -> list[dict[str, Any]]:
|
||||
if not account_id:
|
||||
return []
|
||||
rows = (
|
||||
db.query(WeiboRechargeRecord)
|
||||
.filter(WeiboRechargeRecord.account_id == account_id)
|
||||
.order_by(WeiboRechargeRecord.created_at.desc(), WeiboRechargeRecord.id.desc())
|
||||
.limit(50)
|
||||
.all()
|
||||
)
|
||||
users = _users_by_id(
|
||||
db,
|
||||
{int(row.created_by) for row in rows if row.created_by},
|
||||
)
|
||||
return [
|
||||
{
|
||||
"id": int(row.id),
|
||||
"order_id": row.order_id,
|
||||
"pay_id": row.pay_id or "",
|
||||
"amount_yuan": int(row.amount_yuan or 0),
|
||||
"single_reward": int(row.single_reward or 0),
|
||||
"status": row.status,
|
||||
"status_label": RECHARGE_STATUS_LABELS.get(row.status, row.status),
|
||||
"sub_status": row.sub_status or "",
|
||||
"is_paid": row.status == "paid",
|
||||
"paid_at": row.paid_at,
|
||||
"status_checked_at": row.status_checked_at,
|
||||
"created_by": row.created_by,
|
||||
"created_by_name": users.get(int(row.created_by), "")
|
||||
if row.created_by
|
||||
else "",
|
||||
"created_at": row.created_at,
|
||||
"updated_at": row.updated_at,
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
def serialize_one(db: Session, order: WeiboExternalOrder) -> dict[str, Any]:
|
||||
users = _users_by_id(
|
||||
db,
|
||||
@@ -1751,12 +1875,14 @@ def serialize_one(db: Session, order: WeiboExternalOrder) -> dict[str, Any]:
|
||||
)
|
||||
cluster = _account_clusters(db, [order]).get(order.id)
|
||||
metric = _latest_metrics(db, [order.id]).get(order.id)
|
||||
recharge_records = _recharge_records_out(db, order.account_id)
|
||||
return serialize_order(
|
||||
order,
|
||||
users=users,
|
||||
accounts=accounts,
|
||||
cluster=cluster,
|
||||
last_metric=metric,
|
||||
recharge_records=recharge_records,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.weibo import (
|
||||
@@ -18,7 +19,7 @@ from core.weibo import (
|
||||
normalize_weibo_cookie,
|
||||
)
|
||||
|
||||
from ..models import WeiboAccount, WeiboWorkbenchAccount
|
||||
from ..models import WeiboAccount, WeiboRechargeRecord, WeiboWorkbenchAccount
|
||||
from .weibo_image_service import embed_weibo_goods_images
|
||||
|
||||
|
||||
@@ -468,6 +469,75 @@ def create_weibo_recharge_order(
|
||||
raise
|
||||
|
||||
|
||||
def persist_weibo_recharge_order(
|
||||
db: Session,
|
||||
account: WeiboAccount,
|
||||
order: WeiboRechargeOrder,
|
||||
*,
|
||||
created_by: int | None,
|
||||
) -> WeiboRechargeRecord:
|
||||
"""持久化 Web 充值下单结果。"""
|
||||
row = (
|
||||
db.query(WeiboRechargeRecord)
|
||||
.filter(WeiboRechargeRecord.order_id == order.order_id)
|
||||
.first()
|
||||
)
|
||||
if row is None:
|
||||
row = WeiboRechargeRecord(
|
||||
account_id=account.id,
|
||||
order_id=order.order_id,
|
||||
amount_yuan=order.amount_yuan,
|
||||
single_reward=order.single_reward,
|
||||
created_by=created_by,
|
||||
)
|
||||
db.add(row)
|
||||
row.account_id = account.id
|
||||
row.pay_id = order.pay_id
|
||||
row.amount_yuan = order.amount_yuan
|
||||
row.single_reward = order.single_reward
|
||||
row.status = "created"
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return row
|
||||
|
||||
|
||||
def persist_weibo_payment_status(
|
||||
db: Session,
|
||||
account: WeiboAccount,
|
||||
status: WeiboPaymentStatus,
|
||||
) -> WeiboRechargeRecord | None:
|
||||
"""把充值支付查询结果写回对应记录。"""
|
||||
row = (
|
||||
db.query(WeiboRechargeRecord)
|
||||
.filter(
|
||||
WeiboRechargeRecord.account_id == account.id,
|
||||
or_(
|
||||
WeiboRechargeRecord.pay_id == status.pay_id,
|
||||
WeiboRechargeRecord.order_id == status.out_pay_id,
|
||||
),
|
||||
)
|
||||
.order_by(WeiboRechargeRecord.id.desc())
|
||||
.first()
|
||||
)
|
||||
if row is None:
|
||||
return None
|
||||
normalized = {
|
||||
"PAY_STATUS_SUCCESS": "paid",
|
||||
"PAY_STATUS_PENDING": "pending",
|
||||
"PAY_STATUS_FAILED": "failed",
|
||||
"PAY_STATUS_CLOSED": "closed",
|
||||
}.get(status.status, "unknown")
|
||||
now = _now()
|
||||
row.status = normalized
|
||||
row.sub_status = status.sub_status
|
||||
row.status_checked_at = now
|
||||
if normalized == "paid" and row.paid_at is None:
|
||||
row.paid_at = now
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return row
|
||||
|
||||
|
||||
def query_weibo_payment_status(
|
||||
db: Session,
|
||||
account: WeiboAccount,
|
||||
|
||||
Reference in New Issue
Block a user