Files
tuhui.cloud/backend/app/ysm_sdk/tmp/web_demo.py
2026-03-08 19:28:32 +08:00

444 lines
16 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.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
易收米支付SDK Web服务演示
这个演示展示了如何在Web应用中集成易收米支付
1. 创建支付页面
2. 处理支付回调
3. 查询订单状态
运行前请安装依赖:
pip install fastapi uvicorn jinja2 python-multipart
运行方式:
python web_demo.py
然后访问: http://localhost:8000
"""
import json
import time
import asyncio
from fastapi import FastAPI, Request, Form, Response
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pay import create_payment
from query import query_order
from notify import PaymentNotify
# 创建FastAPI应用
app = FastAPI(title="易收米支付演示", description="易收米支付SDK Web演示")
# 配置信息 - 请替换为你的真实配置
APPID = 'YSMcd16b45d'
APPSECRET = '899850e778e8d2b53e4c4a4e88695688'
# 回调地址 - 实际使用时应该是你的域名
NOTIFY_URL = "http://localhost:8000/notify"
NOPAY_URL = "http://localhost:8000/cancel"
CALLBACK_URL = "http://localhost:8000/success"
# 模板引擎
templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
"""首页 - 支付表单"""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>易收米支付演示</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
.form-group { margin: 15px 0; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
.result { margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 4px; }
.success { border-left: 4px solid #28a745; }
.error { border-left: 4px solid #dc3545; }
</style>
</head>
<body>
<h1>🚀 易收米支付演示</h1>
<h2>创建支付订单</h2>
<form action="/create_payment" method="post">
<div class="form-group">
<label>商品名称:</label>
<input type="text" name="description" value="演示商品 - 快充数据线" required>
</div>
<div class="form-group">
<label>支付金额(元):</label>
<input type="number" name="amount" value="1.00" step="0.01" min="0.01" required>
</div>
<div class="form-group">
<label>支付类型:</label>
<select name="pay_type">
<option value="1">微信内支付</option>
<option value="2">微信扫码支付</option>
<option value="3">微信H5支付</option>
<option value="11">支付宝H5支付</option>
</select>
</div>
<button type="submit">💳 创建支付</button>
</form>
<h2>查询订单状态</h2>
<form action="/query_order" method="post">
<div class="form-group">
<label>订单号:</label>
<input type="text" name="order_id" placeholder="输入订单号" required>
</div>
<button type="submit">🔍 查询订单</button>
</form>
<h2>📚 接口说明</h2>
<ul>
<li><strong>支付回调:</strong> POST /notify</li>
<li><strong>支付成功:</strong> GET /success</li>
<li><strong>支付取消:</strong> GET /cancel</li>
<li><strong>订单列表:</strong> GET /orders</li>
</ul>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@app.post("/create_payment")
async def create_payment_endpoint(
description: str = Form(...),
amount: float = Form(...),
pay_type: int = Form(...)
):
"""创建支付订单"""
try:
# 生成订单号
order_id = f"demo_{int(time.time())}"
# 转换金额为分
amount_cents = int(amount * 100)
# 创建支付
pay_url = await create_payment(
appid=APPID,
appsecret=APPSECRET,
order_id=order_id,
description=description,
amount=amount_cents,
notify_url=NOTIFY_URL,
nopay_url=NOPAY_URL,
callback_url=CALLBACK_URL,
pay_type=pay_type
)
if pay_url:
# 保存订单信息(实际应用中应该保存到数据库)
order_info = {
'order_id': order_id,
'description': description,
'amount': amount,
'pay_type': pay_type,
'pay_url': pay_url,
'status': 'created',
'created_at': time.time()
}
result_html = f"""
<!DOCTYPE html>
<html>
<head>
<title>支付订单创建成功</title>
<meta charset="utf-8">
<style>
body {{ font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }}
.success {{ background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 15px; border-radius: 4px; }}
.info {{ background: #f8f9fa; border: 1px solid #dee2e6; padding: 15px; border-radius: 4px; margin: 15px 0; }}
.button {{ display: inline-block; background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin: 5px; }}
.button:hover {{ background: #0056b3; }}
</style>
</head>
<body>
<h1>✅ 支付订单创建成功</h1>
<div class="success">
支付订单已成功创建!请点击下方链接进行支付。
</div>
<div class="info">
<strong>订单信息:</strong><br>
订单号: {order_id}<br>
商品: {description}<br>
金额: ¥{amount}<br>
支付类型: {pay_type}
</div>
<a href="{pay_url}" class="button" target="_blank">🚀 立即支付</a>
<a href="/" class="button">🏠 返回首页</a>
<a href="/query_order_page?order_id={order_id}" class="button">🔍 查询订单</a>
</body>
</html>
"""
return HTMLResponse(content=result_html)
else:
return JSONResponse(
content={"error": "创建支付失败"},
status_code=400
)
except Exception as e:
return JSONResponse(
content={"error": f"创建支付时出错: {str(e)}"},
status_code=500
)
@app.post("/query_order")
async def query_order_endpoint(order_id: str = Form(...)):
"""查询订单状态"""
try:
# 查询订单
order_info = await query_order(
appid=APPID,
mch_orderid=order_id
)
if order_info:
status_map = {
'SUCCESS': '✅ 支付成功',
'REFUND': '🔄 转入退款',
'NOTPAY': '⏳ 未支付',
'CLOSED': '❌ 已关闭',
}
state = order_info.get('state', 'UNKNOWN')
status_text = status_map.get(state, f'未知状态({state})')
result_html = f"""
<!DOCTYPE html>
<html>
<head>
<title>订单查询结果</title>
<meta charset="utf-8">
<style>
body {{ font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }}
.info {{ background: #f8f9fa; border: 1px solid #dee2e6; padding: 15px; border-radius: 4px; margin: 15px 0; }}
.success {{ color: #155724; }}
.pending {{ color: #856404; }}
.failed {{ color: #721c24; }}
pre {{ background: #f8f9fa; padding: 10px; border-radius: 4px; overflow-x: auto; }}
.button {{ display: inline-block; background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin: 5px; }}
</style>
</head>
<body>
<h1>🔍 订单查询结果</h1>
<div class="info">
<strong>订单号:</strong> {order_id}<br>
<strong>状态:</strong> <span class="{'success' if state == 'SUCCESS' else 'pending' if state == 'NOTPAY' else 'failed'}">{status_text}</span>
</div>
<h3>📋 详细信息:</h3>
<pre>{json.dumps(order_info, ensure_ascii=False, indent=2)}</pre>
<a href="/" class="button">🏠 返回首页</a>
</body>
</html>
"""
return HTMLResponse(content=result_html)
else:
return JSONResponse(
content={"error": "订单不存在或查询失败"},
status_code=404
)
except Exception as e:
return JSONResponse(
content={"error": f"查询订单时出错: {str(e)}"},
status_code=500
)
@app.post("/notify")
async def payment_notify(request: Request):
"""支付成功回调处理"""
try:
# 获取请求数据
request_data = await request.body()
# 创建通知处理器
notify_handler = PaymentNotify(APPID, APPSECRET)
# 处理通知
success, message = await notify_handler.process(request_data)
if success:
# 这里应该更新数据库中的订单状态
print(f"💰 支付成功回调处理: {message}")
return Response(content=message)
else:
print(f"❌ 支付回调处理失败: {message}")
return JSONResponse(content={"error": message}, status_code=400)
except Exception as e:
print(f"❌ 处理支付回调时出错: {str(e)}")
return JSONResponse(content={"error": str(e)}, status_code=500)
@app.get("/success")
async def payment_success(request: Request):
"""支付成功页面"""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>支付成功</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; text-align: center; }
.success { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 30px; border-radius: 4px; }
.button { display: inline-block; background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin: 10px; }
</style>
</head>
<body>
<div class="success">
<h1>🎉 支付成功!</h1>
<p>您的订单已支付成功,感谢您的购买!</p>
</div>
<a href="/" class="button">🏠 返回首页</a>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@app.get("/cancel")
async def payment_cancel(request: Request):
"""支付取消页面"""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>支付取消</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; text-align: center; }
.warning { background: #fff3cd; border: 1px solid #ffeaa7; color: #856404; padding: 30px; border-radius: 4px; }
.button { display: inline-block; background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin: 10px; }
</style>
</head>
<body>
<div class="warning">
<h1>⚠️ 支付已取消</h1>
<p>您取消了支付,如有需要可以重新发起支付。</p>
</div>
<a href="/" class="button">🏠 返回首页</a>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@app.get("/query_order_page")
async def query_order_page(request: Request, order_id: str = None):
"""订单查询页面"""
if order_id:
# 直接查询订单
try:
order_info = await query_order(appid=APPID, mch_orderid=order_id)
if order_info:
status_map = {
'SUCCESS': '✅ 支付成功',
'REFUND': '🔄 转入退款',
'NOTPAY': '⏳ 未支付',
'CLOSED': '❌ 已关闭',
}
state = order_info.get('state', 'UNKNOWN')
status_text = status_map.get(state, f'未知状态({state})')
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<title>订单详情</title>
<meta charset="utf-8">
<style>
body {{ font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }}
.info {{ background: #f8f9fa; border: 1px solid #dee2e6; padding: 15px; border-radius: 4px; margin: 15px 0; }}
pre {{ background: #f8f9fa; padding: 10px; border-radius: 4px; }}
.button {{ display: inline-block; background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin: 5px; }}
</style>
</head>
<body>
<h1>📋 订单详情</h1>
<div class="info">
<strong>订单号:</strong> {order_id}<br>
<strong>状态:</strong> {status_text}
</div>
<pre>{json.dumps(order_info, ensure_ascii=False, indent=2)}</pre>
<a href="/" class="button">🏠 返回首页</a>
</body>
</html>
"""
return HTMLResponse(content=html_content)
except:
pass
# 显示查询表单
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>订单查询</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
.form-group { margin: 15px 0; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<h1>🔍 订单查询</h1>
<form action="/query_order" method="post">
<div class="form-group">
<label>订单号:</label>
<input type="text" name="order_id" required>
</div>
<button type="submit">查询订单</button>
</form>
<a href="/">🏠 返回首页</a>
</body>
</html>
"""
return HTMLResponse(content=html_content)
if __name__ == "__main__":
import uvicorn
print("🚀 启动易收米支付演示服务...")
print("📱 访问地址: http://localhost:8000")
print("📋 配置信息:")
print(f" AppID: {APPID}")
print(f" AppSecret: {APPSECRET[:8]}***{APPSECRET[-8:]}")
print("=" * 50)
uvicorn.run(app, host="0.0.0.0", port=8000)