核心链路: - purchase_realtime:算价→余额预检→计费写用量→实时复式记账→开权益,失败回滚 - _grant_subscription 续费修复:未到期从 end_date 顺延不丢剩余天数,已到期重算 - check_subscription_valid 到期门禁:挂到 execute_product,到期/未购买阻断使用 - get_my_subscriptions 我的权益列表(到期状态) - UI:purchase_confirm 确认页(余额+容量/月数输入)、purchase_realtime 支付、 my_subscriptions 权益列表+续费、usermenu.ui 挂菜单
120 lines
5.3 KiB
Plaintext
120 lines
5.3 KiB
Plaintext
# 我的权益:订阅列表 + 到期状态 + 续费入口
|
|
from ahserver.serverenv import ServerEnv
|
|
userid = await get_user()
|
|
result = {'widgettype': 'Text', 'options': {'text': '加载中...'}}
|
|
|
|
if not userid:
|
|
result = {'widgettype': 'Text', 'options': {'text': '请先登录', 'color': '#999'}}
|
|
else:
|
|
try:
|
|
env = ServerEnv()
|
|
subs = await env.get_my_subscriptions()
|
|
|
|
if not subs:
|
|
result = {
|
|
'widgettype': 'VBox',
|
|
'options': {'width': '100%', 'padding': '24px', 'alignItems': 'center'},
|
|
'subwidgets': [
|
|
{'widgettype': 'Text', 'options': {
|
|
'text': '暂无已购权益,请先前往产品商城购买',
|
|
'fontSize': '14px', 'color': '#999'}}
|
|
]
|
|
}
|
|
else:
|
|
cards = []
|
|
for s in subs:
|
|
expired = s.get('expired', False)
|
|
end_date = s.get('end_date', '')
|
|
status_color = '#e74c3c' if expired else '#27ae60'
|
|
quota = s.get('quota_total', 0)
|
|
quota_unit = s.get('quota_unit', '')
|
|
quota_text = ('配额: %g %s' % (quota, quota_unit)) if quota else ''
|
|
|
|
renew_btn = {
|
|
'widgettype': 'Button',
|
|
'options': {
|
|
'label': '续费' if expired else '续费(延长)',
|
|
'bgcolor': '#3498db' if expired else '#95a5a6',
|
|
'color': '#fff',
|
|
'padding': '6px 18px',
|
|
'borderRadius': '4px',
|
|
'fontSize': '13px'
|
|
},
|
|
'binds': [{
|
|
'wid': 'self',
|
|
'event': 'click',
|
|
'actiontype': 'urlwidget',
|
|
'target': 'PopupWindow',
|
|
'popup_options': {'title': '确认续费', 'cwidth': 36, 'cheight': 24},
|
|
'options': {
|
|
'method': 'POST',
|
|
'url': '{{entire_url("./purchase_confirm.dspy")}}',
|
|
'params': {'product_id': s.get('product_id', '')}
|
|
}
|
|
}]
|
|
}
|
|
|
|
info_rows = [
|
|
{'label': '有效期', 'value': '%s ~ %s' % (s.get('start_date', ''), end_date)},
|
|
]
|
|
if quota_text:
|
|
info_rows.append({'label': '配额', 'value': quota_text})
|
|
|
|
card_widgets = [
|
|
{'widgettype': 'HBox',
|
|
'options': {'gap': '12px', 'alignItems': 'center', 'marginBottom': '6px'},
|
|
'subwidgets': [
|
|
{'widgettype': 'Text', 'options': {
|
|
'text': s.get('product_name', ''), 'fontWeight': '700',
|
|
'fontSize': '15px', 'color': '#222'}},
|
|
{'widgettype': 'Text', 'options': {
|
|
'text': s.get('status_text', ''), 'fontSize': '12px',
|
|
'color': '#fff', 'bgcolor': status_color,
|
|
'padding': '2px 8px', 'borderRadius': '10px'}}
|
|
]},
|
|
]
|
|
for row in info_rows:
|
|
card_widgets.append({
|
|
'widgettype': 'HBox',
|
|
'options': {'gap': '8px', 'padding': '2px 0'},
|
|
'subwidgets': [
|
|
{'widgettype': 'Text', 'options': {
|
|
'text': row['label'] + ':', 'width': '60px',
|
|
'fontSize': '13px', 'color': '#666'}},
|
|
{'widgettype': 'Text', 'options': {
|
|
'text': str(row['value']), 'fontSize': '13px'}}
|
|
]
|
|
})
|
|
card_widgets.append({
|
|
'widgettype': 'HBox',
|
|
'options': {'marginTop': '10px', 'justifyContent': 'flex-end'},
|
|
'subwidgets': [renew_btn]
|
|
})
|
|
|
|
cards.append({
|
|
'widgettype': 'VBox',
|
|
'options': {
|
|
'width': '100%', 'padding': '16px',
|
|
'border': '1px solid #e0e0e0', 'borderRadius': '8px',
|
|
'marginBottom': '12px',
|
|
'bgcolor': '#fff' if not expired else '#fafafa'
|
|
},
|
|
'subwidgets': card_widgets
|
|
})
|
|
|
|
result = {
|
|
'widgettype': 'VBox',
|
|
'options': {'width': '100%', 'padding': '16px', 'overflowY': 'auto'},
|
|
'subwidgets': [
|
|
{'widgettype': 'Title4', 'options': {
|
|
'text': '我的权益 (%d 项)' % len(subs), 'fontWeight': '700',
|
|
'marginBottom': '16px'}},
|
|
{'widgettype': 'VBox', 'options': {'width': '100%'}, 'subwidgets': cards}
|
|
]
|
|
}
|
|
except Exception as e:
|
|
debug(f'my_subscriptions error: {format_exc()}')
|
|
result = {'widgettype': 'Text', 'options': {'text': str(e), 'color': '#e74c3c'}}
|
|
|
|
return json.dumps(result, ensure_ascii=False)
|