175 lines
7.4 KiB
Plaintext
175 lines
7.4 KiB
Plaintext
# 购买确认页:算价预览 + 余额显示 + 参数输入 + 确认支付
|
||
from ahserver.serverenv import ServerEnv
|
||
product_id = params_kw.get('product_id', '')
|
||
userid = await get_user()
|
||
result = {'widgettype': 'Text', 'options': {'text': '加载中...'}}
|
||
|
||
if not product_id:
|
||
result = {'widgettype': 'Text', 'options': {'text': '缺少产品ID', 'color': '#e74c3c'}}
|
||
elif not userid:
|
||
result = {'widgettype': 'Text', 'options': {'text': '请先登录后购买', 'color': '#e74c3c'}}
|
||
else:
|
||
userorgid = await get_userorgid()
|
||
dbname = get_module_dbname('product_management')
|
||
|
||
try:
|
||
async with DBPools().sqlorContext(dbname) as sor:
|
||
recs = await sor.sqlExe(
|
||
"SELECT * FROM product WHERE id=${id}$ AND status='1'",
|
||
{'id': product_id})
|
||
if not recs:
|
||
raise Exception('产品不存在或已下架')
|
||
p = recs[0]
|
||
product_type = getattr(p, 'product_type', '') or ''
|
||
|
||
# 余额
|
||
balance = None
|
||
try:
|
||
env = ServerEnv()
|
||
acc_dbname = env.get_module_dbname('accounting')
|
||
async with DBPools().sqlorContext(acc_dbname) as asor:
|
||
balance = await env.getCustomerBalance(asor, userorgid)
|
||
except Exception:
|
||
pass
|
||
|
||
rows = []
|
||
|
||
def _row(label, value, color='#333', bold=False):
|
||
return {
|
||
'widgettype': 'HBox',
|
||
'options': {'padding': '8px 0', 'gap': '8px'},
|
||
'subwidgets': [
|
||
{'widgettype': 'Text', 'options': {
|
||
'text': label, 'width': '90px', 'fontSize': '13px',
|
||
'color': '#666', 'fontWeight': '600' if bold else '400'}},
|
||
{'widgettype': 'Text', 'options': {
|
||
'text': str(value), 'fontSize': '13px', 'color': color,
|
||
'fontWeight': '700' if bold else '400'}}
|
||
]
|
||
}
|
||
|
||
rows.append(_row('产品名称', getattr(p, 'product_name', ''), '#222', True))
|
||
|
||
# 参数输入区(存储需容量+月数;账号走默认月租)
|
||
param_widgets = []
|
||
if product_type == 'workspace_storage':
|
||
param_widgets.append({
|
||
'widgettype': 'HBox',
|
||
'options': {'padding': '8px 0', 'gap': '8px', 'alignItems': 'center'},
|
||
'subwidgets': [
|
||
{'widgettype': 'Text', 'options': {
|
||
'text': '购买容量(GB)', 'width': '90px', 'fontSize': '13px', 'color': '#666'}},
|
||
{'widgettype': 'InputNumber', 'id': 'buy_storage_gb',
|
||
'options': {'value': 100, 'min': 1, 'step': 10, 'width': '120px'}}
|
||
]
|
||
})
|
||
param_widgets.append({
|
||
'widgettype': 'HBox',
|
||
'options': {'padding': '8px 0', 'gap': '8px', 'alignItems': 'center'},
|
||
'subwidgets': [
|
||
{'widgettype': 'Text', 'options': {
|
||
'text': '购买月数', 'width': '90px', 'fontSize': '13px', 'color': '#666'}},
|
||
{'widgettype': 'InputNumber', 'id': 'buy_valid_months',
|
||
'options': {'value': 1, 'min': 1, 'max': 12, 'step': 1, 'width': '120px'}}
|
||
]
|
||
})
|
||
rows.append(_row('计费方式', '按容量直购(元/GB月)', '#666'))
|
||
elif product_type == 'account':
|
||
rows.append(_row('计费方式', '订阅制(月/年/试用)', '#666'))
|
||
else:
|
||
rows.append(_row('计费方式', '按量计费', '#666'))
|
||
|
||
# 余额展示
|
||
if balance is None:
|
||
balance_text = '未开户'
|
||
balance_color = '#e67e22'
|
||
else:
|
||
balance_text = '¥ %.2f' % balance
|
||
balance_color = '#27ae60' if balance > 0 else '#e74c3c'
|
||
rows.append(_row('账户余额', balance_text, balance_color, True))
|
||
|
||
# 确认支付按钮:script+fetch 提交,确保读取输入框当前值
|
||
is_storage = (product_type == 'workspace_storage')
|
||
pay_script = (
|
||
"var pid='" + product_id + "';"
|
||
"var params=new URLSearchParams();"
|
||
"params.append('product_id',pid);"
|
||
)
|
||
if is_storage:
|
||
pay_script += (
|
||
"var gb=bricks.getWidgetById('buy_storage_gb');"
|
||
"var mo=bricks.getWidgetById('buy_valid_months');"
|
||
"var gbv=gb?gb.value:100; var mov=mo?mo.value:1;"
|
||
"if(!gbv||gbv<=0){alert('请输入购买容量');return;}"
|
||
"params.append('storage_gb',gbv);"
|
||
"params.append('valid_months',mov);"
|
||
)
|
||
pay_script += (
|
||
"var btn=this; btn.disabled=true;"
|
||
"fetch('/product_management/storefront/api/purchase_realtime.dspy',"
|
||
"{method:'POST',body:params}).then(function(r){return r.json()})"
|
||
".then(function(msg){btn.disabled=false;"
|
||
"if(msg.widgettype){bricks.widgetBuild(msg).then(function(w){if(w)w.open()})}"
|
||
"else{alert(msg.message||'支付完成')}})"
|
||
".catch(function(e){btn.disabled=false;alert('支付请求失败: '+e)})"
|
||
)
|
||
|
||
pay_btn = {
|
||
'widgettype': 'Button',
|
||
'options': {
|
||
'label': '确认支付',
|
||
'bgcolor': '#e74c3c',
|
||
'color': '#fff',
|
||
'padding': '10px 36px',
|
||
'borderRadius': '6px',
|
||
'fontWeight': '600',
|
||
'marginTop': '16px'
|
||
},
|
||
'binds': [{
|
||
'wid': 'self',
|
||
'event': 'click',
|
||
'actiontype': 'script',
|
||
'target': 'self',
|
||
'script': pay_script
|
||
}]
|
||
}
|
||
|
||
cancel_btn = {
|
||
'widgettype': 'Button',
|
||
'options': {
|
||
'label': '取消',
|
||
'bgcolor': '#f0f0f0',
|
||
'color': '#666',
|
||
'padding': '10px 24px',
|
||
'borderRadius': '6px',
|
||
'marginTop': '16px'
|
||
},
|
||
'binds': [{
|
||
'wid': 'self', 'event': 'click', 'actiontype': 'script',
|
||
'target': 'self', 'script': 'if(window.closePopup) closePopup()'
|
||
}]
|
||
}
|
||
|
||
result = {
|
||
'widgettype': 'VBox',
|
||
'options': {'width': '100%', 'padding': '16px'},
|
||
'subwidgets': [
|
||
{'widgettype': 'Title4', 'options': {'text': '确认购买', 'fontWeight': '700', 'marginBottom': '12px'}},
|
||
{'widgettype': 'VBox', 'options': {'gap': '0', 'marginBottom': '8px'}, 'subwidgets': rows},
|
||
{'widgettype': 'VBox', 'options': {'gap': '0', 'marginBottom': '8px'}, 'subwidgets': param_widgets},
|
||
{
|
||
'widgettype': 'Text',
|
||
'options': {
|
||
'text': '支付将从账户余额实时扣款并完成记账,购买成功后权益立即生效。',
|
||
'fontSize': '12px', 'color': '#999', 'marginTop': '8px'}
|
||
},
|
||
{'widgettype': 'HBox', 'options': {'gap': '12px', 'marginTop': '8px'},
|
||
'subwidgets': [pay_btn, cancel_btn]}
|
||
]
|
||
}
|
||
except Exception as e:
|
||
debug(f'purchase_confirm error: {format_exc()}')
|
||
result = {'widgettype': 'Text', 'options': {'text': str(e), 'color': '#e74c3c'}}
|
||
|
||
return json.dumps(result, ensure_ascii=False)
|