Files
2026-10-15 20:36:59 +08:00

145 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""后台退款(积分+返利资产扣回)与 iOS 支付开关"""
import uuid
from datetime import datetime, timedelta
from decimal import Decimal
from types import SimpleNamespace
import pytest
from sqlalchemy import select
from app.core.response import BizException
from app.models.admin import AdminLog
from app.models.billing import RechargeOrder
from app.models.common import SystemConfig
from app.models.incentive import AssetTransaction, AssetWallet
from app.models.user import User, UserWallet
from app.services import admin_service, payment_service
async def _user(db, *, points=0):
u = User(password_hash="x", register_type=1, invite_code=uuid.uuid4().hex[:8].upper())
db.add(u)
await db.flush()
db.add(UserWallet(user_id=u.id, points=points))
await db.commit()
return u
async def _paid_order(db, user_id, order_no="R20261015001", *, status=1, points=100):
o = RechargeOrder(
order_no=order_no, user_id=user_id, amount_fen=1000, points=points,
pay_method=1, status=status, paid_at=datetime.now(),
expire_at=datetime.now() + timedelta(minutes=30),
)
db.add(o)
await db.commit()
return o
def _admin():
return SimpleNamespace(admin_id=1, role="super")
# ---------- 退款 ----------
@pytest.mark.asyncio
async def test_refund_claws_back_points_and_rebate(db):
buyer = await _user(db, points=150)
inviter = await _user(db)
db.add(AssetWallet(user_id=inviter.id, assets=Decimal(5)))
db.add(AssetTransaction(
user_id=inviter.id, change_amount=Decimal(5), balance_after=Decimal(5),
biz_type=1, biz_id="R20261015001", remark="充值返利",
))
order = await _paid_order(db, buyer.id)
r = await admin_service.refund_order(
db, _admin(), order.order_no, SimpleNamespace(reason="用户申请退款"), "127.0.0.1"
)
assert r["order_no"] == order.order_no
await db.refresh(order)
assert order.status == 3 # 已退款
# 积分扣回(biz_type=7)
assert (await db.get(UserWallet, buyer.id)).points == 50
# 返利资产扣回(biz_type=6 反向流水)
assert (await db.get(AssetWallet, inviter.id)).assets == Decimal(0)
clawback = (await db.execute(select(AssetTransaction).where(
AssetTransaction.biz_type == 6, AssetTransaction.biz_id == order.order_no
))).scalar_one()
assert clawback.change_amount == Decimal(-5)
# 操作日志落库
log = (await db.execute(select(AdminLog).where(AdminLog.action == "order_refund"))).scalar_one()
assert log.target == order.order_no
@pytest.mark.asyncio
async def test_refund_repeat_rejected(db):
buyer = await _user(db, points=150)
order = await _paid_order(db, buyer.id)
await admin_service.refund_order(db, _admin(), order.order_no, SimpleNamespace(reason="退款"), "")
with pytest.raises(BizException) as exc:
await admin_service.refund_order(db, _admin(), order.order_no, SimpleNamespace(reason="再退"), "")
assert exc.value.code == 3002 # ORDER_STATUS_ERROR
@pytest.mark.asyncio
async def test_refund_unpaid_order_rejected(db):
buyer = await _user(db)
order = await _paid_order(db, buyer.id, order_no="R_UNPAID", status=0)
with pytest.raises(BizException) as exc:
await admin_service.refund_order(db, _admin(), order.order_no, SimpleNamespace(reason="退款"), "")
assert exc.value.code == 3002
@pytest.mark.asyncio
async def test_refund_not_found(db):
with pytest.raises(BizException) as exc:
await admin_service.refund_order(db, _admin(), "NO_SUCH", SimpleNamespace(reason="退款"), "")
assert exc.value.code == 3001 # ORDER_NOT_FOUND
@pytest.mark.asyncio
async def test_refund_points_can_go_negative(db):
"""积分已被消费时扣回允许余额为负(对账兜底)"""
buyer = await _user(db, points=30)
order = await _paid_order(db, buyer.id) # 该单发了 100 积分
await admin_service.refund_order(db, _admin(), order.order_no, SimpleNamespace(reason="退款"), "")
assert (await db.get(UserWallet, buyer.id)).points == -70
# ---------- iOS 支付开关 ----------
def _recharge_req():
return SimpleNamespace(amount_yuan=6, pay_method=3) # USDT 渠道(离线可建单)
@pytest.mark.asyncio
async def test_ios_blocked_when_switch_off(db, redis):
db.add(SystemConfig(config_key="ios_payment.enabled", config_value="0"))
await db.commit()
with pytest.raises(BizException) as exc:
await payment_service.create_recharge(db, redis, 1, _recharge_req(), platform="ios")
assert "iOS" in exc.value.message
@pytest.mark.asyncio
async def test_android_not_affected_by_ios_switch(db, redis):
db.add(SystemConfig(config_key="ios_payment.enabled", config_value="0"))
await db.commit()
r = await payment_service.create_recharge(db, redis, 1, _recharge_req(), platform="android")
assert r["order_no"] and r["pay_method"] == 3
@pytest.mark.asyncio
async def test_ios_allowed_when_switch_on(db, redis):
db.add(SystemConfig(config_key="ios_payment.enabled", config_value="1"))
await db.commit()
r = await payment_service.create_recharge(db, redis, 1, _recharge_req(), platform="ios")
assert r["order_no"]
@pytest.mark.asyncio
async def test_ios_default_allowed_without_config(db, redis):
r = await payment_service.create_recharge(db, redis, 1, _recharge_req(), platform="ios")
assert r["order_no"]