This commit is contained in:
yumoqing 2025-12-20 20:47:08 +08:00
parent 5748eb7d76
commit cfd7fea195
5 changed files with 189 additions and 8 deletions

View File

@ -11,6 +11,36 @@
}, },
"editexclouded": [ "editexclouded": [
"id" "id"
],
"toolbar":{
"tools":[
{
"name":"refund",
"label":"退费",
"url":"{{entire_url('../refund.ui')}}"
}
]
},
"binds":[
"wid":"self",
"event":"refund",
"actiontype":"urlwidget",
"target":"PopupWindow",
"popup_options":{
{% if params_kw._is_mobile %}
"width":"100%",
"height":"90%",
{% else %}
"width": "40%",
"height":"80%",
{% endif %}
"archor": "cc"
},
"options":{
"url":"{{entire_url('../refund.ui')}}",
"params": {
}
}
] ]
} }
} }

View File

@ -69,7 +69,7 @@ async def create_payment(request, params_kw=None):
amount = data.amount amount = data.amount
currency = data.currency currency = data.currency
plog = await pl.new_log(userid, orgid, provider, plog = await pl.new_log(userid, orgid, provider,
payment_name, amount, amount,
fee, client_ip, currency=currency) fee, client_ip, currency=currency)
if plog: if plog:
data.out_trade_no = plog.id data.out_trade_no = plog.id
@ -109,16 +109,26 @@ async def query_payment(request, params_kw=None):
async def refund_payment(request, params_kw=None): async def refund_payment(request, params_kw=None):
if params_kw is None: if params_kw is None:
params_kw = request.params_kw params_kw = request.params_kw
data = params_kw data = await get_refundable_plog(request, params_kw.id)
provider = data.get("provider") if data is None:
return None
if params_kw.amount >= data.total_amount:
return None
provider = data.channelid
if provider not in PROVIDERS: if provider not in PROVIDERS:
return {"error":"unknown provider"} return {"error":"unknown provider"}
try: try:
plog = await new_refund_log(request, data.amount, data.id)
if PROVIDERS[provider] is None: if PROVIDERS[provider] is None:
e = Exception(f'{provider} cannot pay') e = Exception(f'{provider} cannot pay')
exception(f'{e}') exception(f'{e}')
raise e raise e
res = await PROVIDERS[provider].refund(data) plog.out_trade_no = plog.origin_id
plog.out_request_no = plog.id
plog.total_amount = params_kw.amount
plog.refund_amount = plog.total_amount
plog.notify_url = request.env.entire_url(f'notify/{provider}')
res = await PROVIDERS[provider].refund(plog)
return res return res
except Exception as e: except Exception as e:
exception(f'query_payment():{params_kw}, {e}') exception(f'query_payment():{params_kw}, {e}')

View File

@ -1,9 +1,16 @@
from sqlor.dbpools import DBPools from sqlor.dbpools import DBPools, get_sor_context
from appPublic.timeUtils import timestampstr from appPublic.timeUtils import timestampstr
from appPublic.log import debug, exception from appPublic.log import debug, exception
from appPublic.dictObject import DictObject from appPublic.dictObject import DictObject
from ahserver.serverenv import ServerEnv from ahserver.serverenv import ServerEnv
RECHARGE="充值"
REFUND ="退费"
payment_names = [
RECHARGE,
REFUND
]
class PaymentLog: class PaymentLog:
def __init__(self, env): def __init__(self, env):
self.db = DBPools() self.db = DBPools()
@ -11,14 +18,15 @@ class PaymentLog:
async def new_log(self, async def new_log(self,
userid, customerid, channel, userid, customerid, channel,
payment_name, amount, feerate, amount, feerate,
client_ip, currency='CNY'): client_ip, currency='CNY'):
dbname = self.env.get_module_dbname('unipay') dbname = self.env.get_module_dbname('unipay')
async with self.db.sqlorContext(dbname) as sor: async with self.db.sqlorContext(dbname) as sor:
return await self.sor_new_log(sor, userid, customerid, channel, return await self.sor_new_log(sor, userid, customerid, channel,
payment_name, amount, feerate, amount, feerate,
client_ip, currency=currency) client_ip, currency=currency)
return None return None
async def sor_new_log(self, sor, async def sor_new_log(self, sor,
userid, customerid, channel, userid, customerid, channel,
payment_name, amount, feerate, payment_name, amount, feerate,
@ -28,7 +36,7 @@ class PaymentLog:
"id": self.env.uuid(), "id": self.env.uuid(),
"customerid": customerid, "customerid": customerid,
"channelid": channel, "channelid": channel,
"payment_name": payment_name, "payment_name": RECHARGE,
"payer_client_ip": client_ip, "payer_client_ip": client_ip,
"amount_total": amount, "amount_total": amount,
"pay_feerate": feerate, "pay_feerate": feerate,
@ -72,6 +80,64 @@ class PaymentLog:
return None return None
return None return None
async def get_refundable_plog(request, id):
db = DBPools()
env = request._run_ns
orgid = await get_userorgid()
dbname = env.get_module_dbname('unipay')
async with db.sqlorContext(dbname) as sor:
recs = await sor.R('payment_log', {'id':id,
'customerid': orgid,
'payment_status'
})
if len(recs) < 1:
debug(f'id({id}) not exists in payment_log or not belong you')
return None
origin_plog = recs[0]
if origin_plog.payment_status == '0':
debug(f'id({id}) payment status 0')
return None
if origin_plog.origin_id:
debug(f'id({id}) is a refund plog')
return None
recs = await sor.R('payment_log', {'origin_id': id})
amt = origin_plog.total_amount
for l in recs:
amt -= l.total_amount
if amt <= 0:
return None
origin_plog.total_amount = amt
return origin_plog
exception(f'{db.except}')
return None
async def new_refund_log(request, amount, origin_id):
env = ServerEnv()
async with get_sor_context(env, 'unipay') as sor:
recs = await sor.R('payment_log', {'id': origin_id})
if len(recs) < 1:
raise Exception(f'{origin_id} not a payment_log')
ol = recs[0]
ns = {
"id": env.uuid(),
"customerid": ol.customerid,
"channelid": ol.channel,
"payment_name": REFUND,
"payer_client_ip": ol.client_ip,
"amount_total": amount,
"pay_feerate": 0.0,
"pay_fee": 0.0,
"currency": ol.currency,
"payment_status": '0',
"init_timestamp": timestampstr(),
"payed_timestamp": "2000-01-01 00:00:00.001",
"cancel_timestamp": "2000-01-01 00:00:00.001",
"userid": userid,
"origin_id": origin_id
}
await sor.C('payment_log', ns.copy())
return DictObject(**ns)
async def unipay_accounting(request, data): async def unipay_accounting(request, data):
logid = data.out_trade_no logid = data.out_trade_no
trade_id = data.trade_no trade_id = data.trade_no

20
wwwroot/refund.dspy Normal file
View File

@ -0,0 +1,20 @@
# refund.dspy
debug(f'refund.dspy: {params_kw=}')
params_kw.amount = float(params_kw.amount)
plog = await get_refundable_plog(request, parmas_kw.id)
if plog is None:
return {
"widgettype": "Text",
"options":{
"i18n":true,
"width":"100%",
"wrap":true,
"otext":"不可退费"
}
}
if params_kw.amount > plog.total_amount:
return {
}
plog.total_amount = params_kw.amount

55
wwwroot/refund.ui Normal file
View File

@ -0,0 +1,55 @@
{% if params_kw.id %}
{% set plog = get_refundable_plog(request, params_kw.id) %}
{% if plog %}
{
"widgettype":"Form",
"options":{
"description":"输入退费金额,缺省为本最多可退金额",
"width": "100%",
"fields":[
{
"name":"id",
"uitype":"hide",
"value":"{{plog.id}}"
},
{
"name": "amount",
"label": "退费金额",
"uitype":"float",
"defaultvalue": {{plog.amount}}
}
]
},
"binds":[
{
"wid":"self",
"event":"submit",
"actiontype":"urlwidget",
"target": "self",
"options":{
"url":"{{entire_url('refund.dspy')}}"
}
}
]
}
{% else %}
{
"widgettype":"Text",
"options":{
"i18n":true,
"width":"100%",
"otext":"本地充值已退费",
"wrap": true
}
}
{% endif %}
{% else %}
{
"widgettype": "Text",
"options":{
"i18n": true
"width": "100%",
"otext": "需要选定一条记录"
}
}
{% endif %}