File size: 3,946 Bytes
c8e5eaa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
"""充值只信任持久订单;回调与主动查单共用同一到账事务。"""
import hashlib
import re
from decimal import Decimal
from sqlalchemy.orm import Session
from models_sql import RechargeOrder, Transaction
from tip_transaction import lock_wallet_accounts


def payment_amount(value):
    if not isinstance(value, str) or not re.fullmatch(r"[0-9]{1,5}(?:\.[0-9]{1,2})?", value):
        raise ValueError("invalid payment amount")
    amount = Decimal(value)
    if not 1 <= amount <= 10000 or amount != amount.to_integral_value():
        raise ValueError("invalid payment amount")
    return int(amount)


def validate_order(row):
    if (row is None or not isinstance(row.account, str) or not row.account.strip()
            or type(row.amount) is not int or not 1 <= row.amount <= 10000
            or row.status not in {"pending", "paid"}):
        raise ValueError("invalid recharge order")


def validate_ledger(tx, account, amount):
    if tx is not None and (tx.account != account or tx.tx_type != "RECHARGE"
            or type(tx.amount) is not int or tx.amount != amount):
        raise ValueError("invalid recharge ledger")


def read_order(db, order_id):
    if not isinstance(order_id, str) or not re.fullmatch(r"PAY_[0-9a-f]{32}", order_id):
        return None
    # 网络查单前的读取不在业务 Session 留下事务或钱包锁。
    with Session(bind=db.get_bind(), autoflush=False) as lookup:
        row = lookup.get(RechargeOrder, order_id)
        if row is None:
            return None
        validate_order(row)
        tx = lookup.get(Transaction, order_id)
        validate_ledger(tx, row.account, row.amount)
        if row.status == "paid" and tx is None:
            raise ValueError("paid recharge ledger missing")
        return {"account": row.account, "amount": row.amount, "credited": tx is not None}


def persist_order(db, order_id, account, amount):
    if type(amount) is not int or not 1 <= amount <= 10000 or not isinstance(account, str) or not account.strip():
        raise ValueError("invalid recharge request")
    try:
        db.add(RechargeOrder(order_id=order_id, account=account, amount=amount, status="pending"))
        db.commit()
    except Exception:
        db.rollback()
        raise


def credit_order(db, order_id, amount):
    info = read_order(db, order_id)
    if info is None or type(amount) is not int or amount != info["amount"]:
        raise ValueError("payment order mismatch")
    account = info["account"]
    try:
        wallet = lock_wallet_accounts(db, (account,), (account,))[account]
        row = db.query(RechargeOrder).filter(RechargeOrder.order_id == order_id).populate_existing().with_for_update().first()
        validate_order(row)
        if row.account != account or row.amount != amount:
            raise ValueError("payment order changed")
        tx = db.query(Transaction).filter(Transaction.tx_id == order_id).populate_existing().with_for_update().first()
        validate_ledger(tx, account, amount)
        if tx is not None:
            row.status = "paid"
            db.commit()
            return False
        if row.status != "pending" or type(wallet.balance) is not int:
            raise ValueError("invalid recharge state")
        last = db.query(Transaction).filter(Transaction.account == account).order_by(
            Transaction.created_at.desc(), Transaction.tx_id.desc()).with_for_update().first()
        previous = last.tx_hash if last else "GENESIS_HASH"
        digest = hashlib.sha256(f"{order_id}{account}RECHARGE{amount}{previous}".encode()).hexdigest()
        wallet.balance += amount
        row.status = "paid"
        db.add(Transaction(tx_id=order_id, account=account, tx_type="RECHARGE", amount=amount,
                           prev_hash=previous, tx_hash=digest, description="支付宝充值"))
        db.commit()
        return True
    except Exception:
        db.rollback()
        raise