[fastadmin] 第三十九篇 FastAdmin easypay 支付宝支付 回调 验签名错误 bug解决
[fastadmin] 第三十九篇 FastAdmin easypay yansonga 支付宝支付 回调 验签名错误 bug解决
错误演示
- 配置好pay之后,支付都是好的
- 但是支付宝付款 回调 进行代码打印输出,发现是验签错误
一般这里是走的是v2,v3最新的,但是包肯定没普及那么多。要知道还有很多得支付,使用商户pid,以及key进行发起的呢?这种都是2007 时候常用的支付代码了。
点进去 verify
再点进去 support类
找到最终执行代码地方
3.验签错误,进行支付宝开放平台密钥工具,验签。
得到params 参数,注意这里参数,你可以通过 支付宝,查询trade_no,进行查询 人家发送给你的回调通知。
地址在这:
验签工具:opensupport.alipay.com/support/FAQ...
- 阿里支付转人工 提工单。
- 工单记录给你们看看,证明一下,总结就是阿里客服公钥是对的,我本地生成就是错的。找了4个客服,对了4次。
客服直接让我用它给我发的公钥。。。不要管证书解析,这是我就疑问,是不是我工具生成证书完全不对?!注意了,这是一个埋点!
开始思考,寻找解决办法
采用阿里官方sdk,发现还是不行
采用python ,写一个秒级 更新订单状态
py用的多的是下面这个包
github.com/fzlee/alipay/blob/maste...
阿里云官方pyhton 是这个包,但是我感觉写的太重了,肯定是pip的包好用。
github.com/fzlee/alipay/blob/maste...
python代码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import time
import logging
import mysql.connector
from datetime import datetime
from alipay import AliPay
import json
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler("alipay_order_check.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'user': 'test',
'password': 'test', # 替换为你的数据库密码
'database': 'test', # 替换为你的数据库名称
'port': '3306',
'pool_name': 'order_pool',
'pool_size': 10,
'use_pure': True,
}
# 支付宝配置
ALIPAY_CONFIG = {
'app_id': '202100testtest890', # 替换为你的应用ID
'app_private_key_string': """-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAtlryQ9iWi8UmTi9CJfTYzTvCA4urWTjf9CX+/esj+luP9P8R
n6iD2xJfBRdA7k4saDpNuELv7/pFkztIGkaBqag+5fQdrspK3A8+iOjB41ebRk2U
H3bpOKf
6VHYa+IQIeF+D/k4s2DJdi3H3nADNM6qp3XNCQIDAQABAoIBAQCRbojmORcfk3UQ
R7peoR5C19TMlRhryOM7SQ5LHbwMz/dIciBxD6lRDx6+5aPAKpJZ8Z3IRYIsBpJ0
cIP5iJ+iWe8H4B/IJcd8J6A8+jyffl/0uqxWR7SKnbjnevZTkRNxahvnjw6R9hyQ
4gBtdnSp0M6pQ0pdzfveC8pi7tr9U8WVtYIB7UpKV36+YfgjPpu8sYFl1a/8Iea1
5k4zyuKVQrmD4LutLjv8L/WXBeossnMSKsiY5E4HpyGvSVvTJD0RHFZ+5F0tGG1C
tLIZj+sLvx6gGlkrQq2pIMossKQBJeXro4v02BdiqDIiYPyiUXJGWeh/4FpefXJt
ZelEtvvBH8P6LGGjFHVXZ4Qk4oNg4hyxP
rMrRrynNLgL7Z9HTd20D+Sc97LugjMeeYzhCzru
zR9VWPm+OiAo1JuzbU7zwwghJuuyr05sShzJ2t9wdtBLv9c+oGZGR58=
-----END RSA PRIVATE KEY-----
""",
# 'alipay_public_key_path': '../addons/epay/certs/alipayCertPublicKey_RSA2.crt', # 替换为支付宝公钥路径
'alipay_public_key_string': """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtlryQ9iWi8UmTi9CJfTY
aB9n7cyTGgpJYd1FZwZybK5sTCBIP2ezNJJSFu2S/0BLR+1DxT/UYxnoAD45v18N
G1AHtqakLmervaOtB17XfFX8oQPn6VHYa+IQIeF+D/k4s2DJdi3H3nADNM6qp3XN
CQIDAQAB
-----END PUBLIC KEY-----
""", # 替换为支付宝公钥路径
'sign_type': 'RSA2',
'debug': False # 生产环境设为False
}
# 初始化支付宝客户端
def init_alipay_client():
try:
logger.info("初始化支付宝客户端...")
# 使用密钥字符串模式初始化
alipay_client = AliPay(
appid=ALIPAY_CONFIG['app_id'],
app_private_key_string=ALIPAY_CONFIG['app_private_key_string'],
alipay_public_key_string=ALIPAY_CONFIG['alipay_public_key_string'],
sign_type=ALIPAY_CONFIG['sign_type'],
debug=ALIPAY_CONFIG['debug']
)
logger.info("支付宝客户端初始化成功")
return alipay_client
except Exception as e:
logger.error(f"支付宝客户端初始化失败: {e}")
import traceback
logger.error(f"错误详情: {traceback.format_exc()}")
raise
# 获取未支付的订单
def get_unpaid_orders(db_conn):
try:
cursor = db_conn.cursor(dictionary=True)
query = """
SELECT id, order_sn, transactionid, paytype, method, amount, createtime, expiretime
FROM fa_shop_order
WHERE paystate = 0 AND orderstate = 0 AND paytype = 'alipay' AND deletetime IS NULL
AND goodsprice = 0.01
AND year IS NOT NULL
ORDER BY createtime DESC
LIMIT 100
"""
cursor.execute(query)
orders = cursor.fetchall()
cursor.close()
return orders
except Exception as e:
logger.error(f"获取未支付订单失败: {e}")
raise
# 查询支付宝订单状态 - 修改后的版本,增加详细日志
def query_alipay_order_status(order_sn, alipay_client):
try:
logger.info(f"准备查询订单状态: {order_sn}")
# 准备查询参数
query_params = {'out_trade_no': order_sn}
logger.info(f"查询参数: {query_params}")
# 执行查询
result = alipay_client.api_alipay_trade_query(out_trade_no="order_sn")
if result.get("trade_status", "") == "TRADE_SUCCESS":
paid = True
print("not paid...")
result = alipay_client.api_alipay_trade_query(**query_params)
logger.info(f"查询结果: {result}")
if result.get('code') == '10000':
return {
'success': True,
'trade_status': result.get('trade_status'),
'trade_no': result.get('trade_no'),
'buyer_id': result.get('buyer_user_id'),
'total_amount': result.get('total_amount')
}
else:
logger.warning(f"查询返回非成功状态: {result}")
return {
'success': False,
'error_code': result.get('code'),
'error_msg': result.get('msg', '未知错误')
}
except Exception as e:
import traceback
logger.error(f"查询订单 {order_sn} 状态异常: {e}")
logger.error(traceback.format_exc())
return {
'success': False,
'error_msg': str(e)
}
# 更新订单状态
def update_order_status(db_conn, order_id, trade_no, pay_time):
try:
cursor = db_conn.cursor()
query = """
UPDATE fa_shop_order
SET paystate = 1,
transactionid = %s,
paytime = %s,
updatetime = %s
WHERE id = %s
"""
current_time = int(time.time())
cursor.execute(query, (trade_no, pay_time, current_time, order_id))
db_conn.commit()
affected_rows = cursor.rowcount
cursor.close()
return affected_rows > 0
except Exception as e:
logger.error(f"更新订单 {order_id} 状态失败: {e}")
db_conn.rollback()
return False
# 主函数
def main():
logger.info("开始执行订单支付状态检查脚本")
try:
# 创建数据库连接
try:
logger.info("尝试连接数据库...")
db_conn = mysql.connector.connect(**DB_CONFIG)
logger.info("数据库连接成功")
except mysql.connector.Error as err:
logger.error(f"数据库连接失败: {err}")
import traceback
logger.error(f"错误详情: {traceback.format_exc()}")
return
# 初始化支付宝客户端
alipay_client = init_alipay_client()
# 获取未支付订单
unpaid_orders = get_unpaid_orders(db_conn)
order_count = len(unpaid_orders)
logger.info(f"获取到 {order_count} 个未支付订单")
# 如果没有订单需要处理,则结束脚本
if order_count == 0:
logger.info("没有需要处理的订单,脚本结束")
db_conn.close()
return
orders_processed = 0
orders_updated = 0
# 逐个处理订单
for order in unpaid_orders:
orders_processed += 1
order_sn = order['order_sn']
logger.info(f"检查订单 {order_sn} ({orders_processed}/{order_count})")
# 避免API调用过于频繁
time.sleep(1) # 增加到1秒,减少API调用频率
# 查询支付宝订单状态
query_result = query_alipay_order_status(order_sn, alipay_client)
if query_result['success']:
logger.info(f"订单 {order_sn} 支付状态: {query_result['trade_status']}")
# 判断交易是否成功
if query_result['trade_status'] in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
# 更新订单状态为已支付
pay_time = int(time.time())
updated = update_order_status(db_conn, order['id'], query_result['trade_no'], pay_time)
if updated:
orders_updated += 1
logger.info(f"订单 {order_sn} 状态已更新为已支付")
else:
logger.warning(f"订单 {order_sn} 状态更新失败")
else:
logger.warning(f"查询订单 {order_sn} 失败: {query_result.get('error_msg', '未知错误')}")
logger.info(f"脚本执行完毕,共处理 {orders_processed} 个订单,更新 {orders_updated} 个订单状态")
except Exception as e:
logger.error(f"脚本执行过程中发生错误: {e}")
import traceback
logger.error(f"错误详情: {traceback.format_exc()}")
finally:
# 关闭数据库连接
if 'db_conn' in locals() and db_conn.is_connected():
db_conn.close()
logger.info("数据库连接已关闭")
logger.info("脚本执行完成")
if __name__ == "__main__":
main()
这里py执行结果,也是一直报错。
于是我就想着,再看看pip包的说明吧。
结果我就发现了这么一句话。
重大突破: 支付宝windows生成签名工具可能bug,用openssl好用点。
1.采用openssl,需要进入linux环境生成。
[root@wSsTBB1016841 ~]# mkdir opensslalipay
[root@wSsTBB1016841 ~]# cd opensslalipay/
[root@wSsTBB1016841 opensslalipay]# ls
[root@wSsTBB1016841 opensslalipay]# genrsa -out app_private_key.pem
-bash: genrsa: command not found
[root@wSsTBB1016841 opensslalipay]# openssl
OpenSSL> genrsa -out app_private_key.pem
Generating RSA private key, 2048 bit long modulus
........................................................+++
.........+++
e is 65537 (0x10001)
OpenSSL> pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem
OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem
writing RSA key
OpenSSL> exit
[root@wSsTBB1016841 opensslalipay]# ls
app_private_key.pem app_private_key_pkcs8.pem app_public_key.pem
得到这三个证书
但是easypay,要的是cert格式的。那就需要转换。
2. 证书转换pem转crt
基础概念
- PEM (Privacy Enhanced Mail): 这是一种基于Base64编码的文件格式,通常用于存储加密相关的文件,如私钥、公钥和证书。PEM文件以
.pem
为扩展名,内容通常以-----BEGIN CERTIFICATE-----
和-----END CERTIFICATE-----
包裹。 - CRT (Certificate): 这通常是PEM格式的一个别名,特别是在某些操作系统或软件中,CRT文件实际上是PEM格式的证书文件,只是扩展名不同。
转换方法
通常,PEM到CRT的转换并不需要改变文件内容,只是重命名文件扩展名即可。例如,如果你有一个名为certificate.pem
的文件,你可以简单地将其重命名为certificate.crt
转换结果(手动复制改后缀)如下图
传上去。
3. 测试支付回调。成功了。。。
总结:这种涉及到windows工具的,都不可靠。 证书生成还是得用linux。这一直是php好大好大的坑啊。
本作品采用《CC 协议》,转载必须注明作者和本文链接