Przelewy24 payment gateway 无法接收交易状态通知
结论
URL_STATUS 必须配置为 Przelewy24 服务器能够直接访问的公网 HTTPS 地址。状态通知是服务器向服务器发送的 POST 请求,不能把它当成用户支付完成后的跳转地址,也不能只按 JSON 解析。
接口收到通知后,应先读取表单或 JSON 数据,校验通知签名,再调用 Przelewy24 的 transaction verification 接口确认交易。只有验证成功,才能把订单标记为已支付。
正确配置 URL_STATUS
创建交易时传入完整的回调地址,例如:
https://api.example.com/payments/przelewy24/status/
根据所使用的 Przelewy24 接口版本,该参数可能叫:
urlStatus
旧版接口中可能叫:
p24_url_status
不要将它与用户支付后跳转使用的 urlReturn 混淆。
这个地址需要满足以下条件:
- 能从公网访问,不能使用
localhost、127.0.0.1或局域网地址。 - 建议使用有效证书的 HTTPS。
- 接受未经用户登录认证的
POST请求。 - 不经过登录页、WAF 验证、人机验证或 VPN。
- 最好不发生
301、302、307、308重定向。 - URL 路径与 Django 路由完全一致,包括末尾的
/。 - 正式环境和 Przelewy24 sandbox 使用各自正确的 API 配置。
例如 Django 路由可以写成:
from django.urls import path
urlpatterns = [
path(
"payments/przelewy24/status/",
PaymentVerify.as_view(),
name="przelewy24-status",
),
]
注册交易时也应传递相同地址:
payload = {
# 其他交易参数
"urlStatus": "https://api.example.com/payments/przelewy24/status/",
"urlReturn": "https://www.example.com/payment/result/",
}
接收通知
Przelewy24 的不同接口版本可能使用 application/x-www-form-urlencoded 或 JSON。Django REST Framework 的 request.data 通常可以解析这两种格式,但前提是没有把解析器限制为只接受 JSON。
可以这样实现:
import logging
from rest_framework import status
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
logger = logging.getLogger(__name__)
class PaymentVerify(APIView):
authentication_classes = []
permission_classes = [AllowAny]
parser_classes = [JSONParser, FormParser, MultiPartParser]
def post(self, request, *args, **kwargs):
data = request.data
logger.info(
"Przelewy24 notification received: content_type=%s data=%s",
request.content_type,
data,
)
if not data:
logger.warning(
"Empty Przelewy24 notification: content_type=%s body=%r",
request.content_type,
request.body,
)
return Response(
{"message": "Empty notification"},
status=status.HTTP_400_BAD_REQUEST,
)
required_fields = {
"merchantId",
"posId",
"sessionId",
"amount",
"currency",
"orderId",
"sign",
}
missing_fields = required_fields.difference(data.keys())
if missing_fields:
return Response(
{
"message": "Invalid notification",
"missing_fields": sorted(missing_fields),
},
status=status.HTTP_400_BAD_REQUEST,
)
session_id = str(data["sessionId"])
order_id = int(data["orderId"])
amount = int(data["amount"])
currency = str(data["currency"])
received_sign = str(data["sign"])
# 1. 根据当前 Przelewy24 API 文档校验 received_sign。
# 2. 使用相同的 sessionId、orderId、amount 和 currency
# 调用 Przelewy24 transaction verification 接口。
# 3. 验证成功后,以幂等方式更新本地订单。
verification_succeeded = verify_przelewy24_transaction(
session_id=session_id,
order_id=order_id,
amount=amount,
currency=currency,
received_sign=received_sign,
notification=dict(data),
)
if not verification_succeeded:
logger.warning(
"Przelewy24 verification failed: session_id=%s order_id=%s",
session_id,
order_id,
)
return Response(
{"message": "Transaction verification failed"},
status=status.HTTP_400_BAD_REQUEST,
)
mark_order_as_paid_once(
session_id=session_id,
provider_order_id=order_id,
amount=amount,
currency=currency,
)
return Response(status=status.HTTP_200_OK)
其中,验证函数需要按照正在使用的 Przelewy24 API 版本实现:
def verify_przelewy24_transaction(
*,
session_id,
order_id,
amount,
currency,
received_sign,
notification,
):
# 校验 notification 中的 sign。
# 然后调用 Przelewy24 transaction verification 接口。
#
# 签名字段、字段顺序、摘要算法和 verification endpoint
# 必须以当前接入版本的官方文档为准,不能混用 REST API 与旧版接口。
raise NotImplementedError
不要仅凭收到回调或 data 非空就认定付款成功。回调地址是公开接口,任何人都可以尝试向它发送请求。
原代码中的问题
下面这一行没有执行任何校验:
data.get
它只是取得 get 方法的引用,随后立即丢弃。若要读取字段,应实际调用:
session_id = data.get("sessionId")
此外,失败响应中的状态码写错了:
status=status.HTTP_400_OK
Django REST Framework 中应使用:
status=status.HTTP_400_BAD_REQUEST
原代码还存在几个逻辑问题:
- 假设通知一定是 JSON。
- 只判断请求体是否非空,没有验证签名。
- 没有调用 transaction verification 接口。
- 没有核对金额、币种和本地订单。
- 用
print()排查线上 webhook,容易遗漏进程日志,建议使用正式日志记录器。 - 返回
404表示 JSON 无效并不合适,格式错误通常返回400。
如果接口完全收不到请求
先在视图最前面记录请求,而不是只打印解析后的 request.data:
logger.info(
"Webhook request: method=%s path=%s content_type=%s body=%r",
request.method,
request.path,
request.content_type,
request.body,
)
然后依次检查:
- 创建交易时提交的
urlStatus是否确实是当前地址。 - Przelewy24 后台或注册交易响应中是否使用了预期环境。
- 域名能否从公网解析和访问。
- HTTPS 证书链是否有效。
- 反向代理是否允许该路径上的
POST。 - Django 路由末尾
/是否匹配,是否触发APPEND_SLASH重定向。 - 接口是否被登录认证、CSRF、中间件、WAF 或 IP 白名单拦截。
- Nginx、负载均衡器和应用服务器日志中是否出现请求。
- 是否意外把
urlReturn当成了urlStatus。
本地开发时可以临时使用能够提供公网 HTTPS 地址的隧道服务,并查看其请求记录;该地址必须同时更新到注册交易的 urlStatus 中。
处理通知时的注意事项
Przelewy24 可能因为超时或非成功响应重复发送通知,因此处理逻辑必须幂等。建议使用 sessionId 或 Przelewy24 的 orderId 建立唯一约束,避免重复入账。
在更新订单前至少核对:
sessionId对应本地存在的待支付订单。amount与创建交易时保存的最小货币单位金额一致。currency一致。- 商户信息一致。
sign校验通过。- transaction verification 调用成功。
- 同一交易此前没有被其他请求处理。
接口应尽快完成验证并返回 2xx。不要在 webhook 中执行耗时的邮件发送、报表生成等操作;这些后续任务可以在订单状态提交成功后交给任务队列处理。
备注:内容仅供参考。
