444 lines
16 KiB
Python
444 lines
16 KiB
Python
#!/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) |