update
This commit is contained in:
parent
1e22bc4587
commit
66db8f0a1e
@ -51,7 +51,7 @@ async def wechat_phone_login(
|
||||
user_code = generate_user_code(db)
|
||||
|
||||
user = UserDB(
|
||||
username=f"user_{phone[-4:]}",
|
||||
nickname=f"user_{phone[-4:]}",
|
||||
phone=phone,
|
||||
user_code=user_code,
|
||||
referral_code=request.referral_code,
|
||||
@ -90,152 +90,4 @@ async def wechat_phone_login(
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return error_response(code=500, message=f"登录失败: {str(e)}")
|
||||
|
||||
@router.post("/pay/order", response_model=ResponseModel)
|
||||
async def create_wechat_pay_order(
|
||||
orderid: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""创建微信支付订单"""
|
||||
# 查询订单
|
||||
order = db.query(ShippingOrderDB).filter(
|
||||
ShippingOrderDB.orderid == orderid,
|
||||
ShippingOrderDB.userid == current_user.userid,
|
||||
ShippingOrderDB.status == OrderStatus.UNPAID # 只能支付新创建的订单
|
||||
).first()
|
||||
|
||||
if not order:
|
||||
return error_response(code=404, message="订单不存在或状态不正确")
|
||||
|
||||
# 检查是否已经支付
|
||||
if order.pay_status:
|
||||
return error_response(code=400, message="订单已支付")
|
||||
|
||||
try:
|
||||
# 获取微信支付客户端
|
||||
wx_pay_client = WeChatClient()
|
||||
|
||||
# 创建支付订单
|
||||
resp_data = wx_pay_client.create_jsapi_payment(
|
||||
orderid=orderid,
|
||||
amount=int(order.final_amount * 100),
|
||||
openid=current_user.openid,
|
||||
description=f"蜂快到家-配送订单{orderid}"
|
||||
)
|
||||
|
||||
# 更新订单支付信息
|
||||
order.prepay_id = resp_data.get("prepay_id")
|
||||
db.commit()
|
||||
|
||||
# 生成支付参数
|
||||
timestamp = str(int(time.time()))
|
||||
nonce_str = generate_random_string()
|
||||
package = f"prepay_id={resp_data.get('prepay_id')}"
|
||||
|
||||
# 构建签名数据
|
||||
sign_data = f"{settings.WECHAT_APPID}\n{timestamp}\n{nonce_str}\n{package}\n"
|
||||
signature = wx_pay_client.sign(sign_data.encode())
|
||||
|
||||
return success_response(data={
|
||||
"orderid": orderid,
|
||||
"payment_params": {
|
||||
"appId": settings.WECHAT_APPID,
|
||||
"timeStamp": timestamp,
|
||||
"nonceStr": nonce_str,
|
||||
"package": package,
|
||||
"signType": "RSA",
|
||||
"paySign": signature
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return error_response(code=500, message=f"创建支付订单失败: {str(e)}")
|
||||
|
||||
@router.post("/pay/notify")
|
||||
async def wechat_pay_notify(request: Request):
|
||||
"""微信支付回调通知"""
|
||||
try:
|
||||
# 获取微信支付客户端
|
||||
wx_pay_client = WeChatClient()
|
||||
|
||||
# 读取原始请求数据
|
||||
body = await request.body()
|
||||
|
||||
# 验证签名
|
||||
headers = request.headers
|
||||
signature = headers.get("Wechatpay-Signature")
|
||||
timestamp = headers.get("Wechatpay-Timestamp")
|
||||
nonce = headers.get("Wechatpay-Nonce")
|
||||
serial_no = headers.get("Wechatpay-Serial")
|
||||
|
||||
if not all([signature, timestamp, nonce, serial_no]):
|
||||
return error_response(code=400, message="缺少必要的请求头")
|
||||
|
||||
# 验证签名
|
||||
sign_str = f"{timestamp}\n{nonce}\n{body.decode()}\n"
|
||||
if not wx_pay_client.verify_signature(
|
||||
sign_str.encode(),
|
||||
signature,
|
||||
serial_no
|
||||
):
|
||||
return error_response(code=401, message="签名验证失败")
|
||||
|
||||
# 解密数据
|
||||
data = json.loads(body)
|
||||
resource = data.get("resource")
|
||||
if not resource:
|
||||
return error_response(code=400, message="缺少资源数据")
|
||||
|
||||
# 解密回调数据
|
||||
decrypted_data = wx_pay_client.decrypt_callback(
|
||||
resource.get("associated_data", ""),
|
||||
resource.get("nonce", ""),
|
||||
resource.get("ciphertext", "")
|
||||
)
|
||||
|
||||
# 解析解密后的数据
|
||||
notify_data = json.loads(decrypted_data)
|
||||
|
||||
# 获取订单信息
|
||||
trade_state = notify_data.get("trade_state")
|
||||
orderid = notify_data.get("out_trade_no")
|
||||
transaction_id = notify_data.get("transaction_id")
|
||||
|
||||
# 处理支付结果
|
||||
if trade_state == "SUCCESS":
|
||||
# 获取数据库会话
|
||||
db = next(get_db())
|
||||
try:
|
||||
# 查询并更新订单
|
||||
order = db.query(ShippingOrderDB).filter(
|
||||
ShippingOrderDB.orderid == orderid,
|
||||
ShippingOrderDB.pay_status == False # 避免重复处理
|
||||
).first()
|
||||
|
||||
if order:
|
||||
# 更新订单支付状态
|
||||
order.pay_status = True
|
||||
order.pay_time = datetime.now(timezone.utc)
|
||||
order.transaction_id = transaction_id
|
||||
|
||||
db.commit()
|
||||
|
||||
return success_response(message="支付成功")
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
# 记录错误日志
|
||||
print(f"处理支付回调失败: {str(e)}")
|
||||
return error_response(code=500, message=f"处理失败: {str(e)}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
return success_response(message="回调处理成功")
|
||||
|
||||
except Exception as e:
|
||||
# 记录错误日志
|
||||
print(f"支付回调异常: {str(e)}")
|
||||
return error_response(code=500, message=f"回调处理异常: {str(e)}")
|
||||
return error_response(code=500, message=f"登录失败: {str(e)}")
|
||||
@ -23,60 +23,6 @@ class WeChatClient:
|
||||
def __init__(self):
|
||||
self.appid = settings.WECHAT_APPID
|
||||
self.secret = settings.WECHAT_SECRET
|
||||
self.mchid = settings.WECHAT_MCH_ID
|
||||
self.private_key = self._load_private_key()
|
||||
self.cert_serial_no = settings.WECHAT_CERT_SERIAL_NO
|
||||
self.access_token = None
|
||||
self.api_v3_key = settings.WECHAT_API_V3_KEY
|
||||
self.platform_cert = self._load_platform_cert()
|
||||
|
||||
def _load_private_key(self):
|
||||
"""加载商户私钥"""
|
||||
with open(settings.WECHAT_PRIVATE_KEY_PATH, 'rb') as f:
|
||||
return serialization.load_pem_private_key(
|
||||
f.read(),
|
||||
password=None
|
||||
)
|
||||
|
||||
def _load_platform_cert(self):
|
||||
"""加载微信支付平台证书"""
|
||||
with open(settings.WECHAT_PLATFORM_CERT_PATH, 'rb') as f:
|
||||
cert_data = f.read()
|
||||
return load_pem_x509_certificate(cert_data)
|
||||
|
||||
def sign(self, data: bytes) -> str:
|
||||
"""签名数据"""
|
||||
signature = self.private_key.sign(
|
||||
data,
|
||||
padding.PKCS1v15(),
|
||||
hashes.SHA256()
|
||||
)
|
||||
return base64.b64encode(signature).decode()
|
||||
|
||||
def post(self, path: str, **kwargs) -> requests.Response:
|
||||
"""发送 POST 请求到微信支付接口"""
|
||||
url = f"https://api.mch.weixin.qq.com{path}"
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = generate_random_string()
|
||||
|
||||
# 准备签名数据
|
||||
body = kwargs.get('json', '')
|
||||
body_str = json.dumps(body) if body else ''
|
||||
sign_str = f"POST\n{path}\n{timestamp}\n{nonce}\n{body_str}\n"
|
||||
|
||||
# 计算签名
|
||||
signature = self.sign(sign_str.encode())
|
||||
|
||||
# 设置认证信息
|
||||
auth = f'WECHATPAY2-SHA256-RSA2048 mchid="{self.mchid}",nonce_str="{nonce}",signature="{signature}",timestamp="{timestamp}",serial_no="{self.cert_serial_no}"'
|
||||
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': auth
|
||||
}
|
||||
|
||||
return requests.post(url, headers=headers, **kwargs)
|
||||
|
||||
async def get_access_token(self):
|
||||
"""获取接口调用凭证"""
|
||||
@ -112,37 +58,6 @@ class WeChatClient:
|
||||
return result.get("phone_info")
|
||||
raise Exception(result.get("errmsg", "获取手机号失败"))
|
||||
|
||||
def create_jsapi_payment(self, orderid: str, amount: int, openid: str, description: str) -> dict:
|
||||
"""创建 JSAPI 支付订单
|
||||
|
||||
Args:
|
||||
orderid: 订单号
|
||||
amount: 支付金额(分)
|
||||
openid: 用户openid
|
||||
description: 商品描述
|
||||
"""
|
||||
pay_data = {
|
||||
"appid": settings.WECHAT_APPID,
|
||||
"mchid": settings.WECHAT_MCH_ID,
|
||||
"description": description,
|
||||
"out_trade_no": orderid,
|
||||
"notify_url": f"{settings.API_BASE_URL}/api/wechat/pay/notify",
|
||||
"amount": {
|
||||
"total": amount,
|
||||
"currency": "CNY"
|
||||
},
|
||||
"payer": {
|
||||
"openid": openid
|
||||
}
|
||||
}
|
||||
|
||||
# 调用微信支付API
|
||||
resp = self.post("/v3/pay/transactions/jsapi", json=pay_data)
|
||||
|
||||
if resp.status_code != 200:
|
||||
raise Exception(f"微信支付下单失败: {resp.json().get('message')}")
|
||||
|
||||
return resp.json()
|
||||
|
||||
async def code2session(self, code: str) -> dict:
|
||||
"""通过 code 获取用户 openid
|
||||
@ -167,51 +82,4 @@ class WeChatClient:
|
||||
return result
|
||||
raise Exception(result.get("errmsg", "获取openid失败"))
|
||||
|
||||
def verify_signature(self, message: bytes, signature: str, serial_no: str) -> bool:
|
||||
"""验证微信支付回调签名
|
||||
|
||||
Args:
|
||||
message: 待验证的消息
|
||||
signature: 签名字符串
|
||||
serial_no: 证书序列号
|
||||
"""
|
||||
if serial_no != self.cert_serial_no:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 解码签名
|
||||
signature_bytes = base64.b64decode(signature)
|
||||
|
||||
# 使用公钥验证签名
|
||||
self.platform_cert.public_key().verify(
|
||||
signature_bytes,
|
||||
message,
|
||||
padding.PKCS1v15(),
|
||||
hashes.SHA256()
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def decrypt_callback(self, associated_data: str, nonce: str, ciphertext: str) -> str:
|
||||
"""解密回调数据
|
||||
|
||||
Args:
|
||||
associated_data: 附加数据
|
||||
nonce: 随机串
|
||||
ciphertext: 密文
|
||||
Returns:
|
||||
解密后的明文
|
||||
"""
|
||||
# 解码密文
|
||||
encrypted_data = base64.b64decode(ciphertext)
|
||||
|
||||
# 使用 AEAD_AES_256_GCM 算法解密
|
||||
aesgcm = AESGCM(base64.b64decode(self.api_v3_key))
|
||||
data = aesgcm.decrypt(
|
||||
nonce.encode(),
|
||||
encrypted_data,
|
||||
associated_data.encode()
|
||||
)
|
||||
|
||||
return data.decode()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user