Files
Django/jituan/services/fubei_notify.py
XingQue b19430213a fix: 付呗支付复查/轮询改查付呗单,避免一直显示支付中
下单已走付呗但复查仍查微信直连,导致付成功不入账;现按流水查付呗并履约,同时加强回调日志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 20:50:14 +08:00

171 lines
6.9 KiB
Python
Raw 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.
"""付呗支付异步回调:验签后按订单/充值单履约(前端无感知)。"""
from __future__ import annotations
import json
import logging
import traceback
from django.http import HttpResponse
from django.views import View
from jituan.constants import FUBEI_BIZ_CZJILU, FUBEI_BIZ_ORDER
from jituan.services.club_payment_channel import get_fubei_config
from jituan.services import fubei_client
from jituan.services.mini_pay_router import get_pay_ledger
from jituan.services.wechat_pay import club_id_from_czjilu, club_id_from_order
logger = logging.getLogger(__name__)
class FubeiPayNotifyView(View):
"""
付呗异步通知。
GET回调 URL 有效性验证,必须返回小写 success。
POST支付成功通知。
路径:/dingdan/fubei-notify/
"""
def get(self, request):
return HttpResponse('success', content_type='text/plain')
def post(self, request):
try:
raw_preview = ''
try:
raw_preview = (request.body or b'')[:800].decode('utf-8', errors='replace')
except Exception:
raw_preview = ''
post = request.POST.dict() if request.POST else {}
if not post and request.body:
# 偶发 JSON
try:
post = json.loads(request.body.decode('utf-8'))
except Exception:
post = {}
logger.info('[FUBEI_NOTIFY] raw=%s post_keys=%s', raw_preview, list(post.keys()))
params, data = fubei_client.parse_callback_form(post)
merchant_order_sn = str(data.get('merchant_order_sn') or '').strip()
result_code = str(params.get('result_code') or '')
logger.info(
'[FUBEI_NOTIFY] result_code=%s merchant_order_sn=%s trade_no=%s',
result_code, merchant_order_sn, data.get('trade_no') or data.get('platform_order_no'),
)
if not merchant_order_sn:
logger.warning('[FUBEI_NOTIFY] 缺少 merchant_order_sn')
return HttpResponse('fail', content_type='text/plain')
club_id = self._resolve_club_id(merchant_order_sn, data)
cfg = get_fubei_config(club_id)
if not cfg or not cfg.api_key:
logger.error('[FUBEI_NOTIFY] 无付呗密钥 club=%s order=%s', club_id, merchant_order_sn)
return HttpResponse('fail', content_type='text/plain')
if not fubei_client.verify_callback_sign(params, cfg.api_key):
# 验签失败仍记录,便于排查;同时尝试用 data 字符串原样参与(部分网关会改写)
logger.error(
'[FUBEI_NOTIFY] 验签失败 order=%s params=%s',
merchant_order_sn,
{k: params.get(k) for k in ('result_code', 'result_message', 'sign') if k in params},
)
return HttpResponse('fail', content_type='text/plain')
if result_code not in ('200', '200.0'):
logger.info('[FUBEI_NOTIFY] 非成功 result_code=%s order=%s', result_code, merchant_order_sn)
return HttpResponse('success', content_type='text/plain')
transaction_id = str(
data.get('trade_no')
or data.get('platform_order_no')
or data.get('order_sn')
or ''
)
total_fee = data.get('total_fee') or data.get('cash_fee') or data.get('order_price')
attach = str(data.get('attach') or '')
biz = self._biz_type(merchant_order_sn, attach)
if biz == FUBEI_BIZ_ORDER:
ok = self._fulfill_order(merchant_order_sn, transaction_id)
else:
ok = self._fulfill_czjilu(merchant_order_sn, total_fee, transaction_id)
return HttpResponse('success' if ok else 'fail', content_type='text/plain')
except Exception as exc:
logger.error('[FUBEI_NOTIFY] 异常: %s', exc)
logger.error(traceback.format_exc())
return HttpResponse('fail', content_type='text/plain')
def _resolve_club_id(self, merchant_order_sn: str, data: dict) -> str:
ledger = get_pay_ledger(merchant_order_sn)
if ledger and ledger.club_id:
return ledger.club_id
# 先试订单,再试充值单
try:
cid = club_id_from_order(merchant_order_sn)
if cid:
return cid
except Exception:
pass
try:
cid = club_id_from_czjilu(merchant_order_sn)
if cid:
return cid
except Exception:
pass
return 'xq'
def _biz_type(self, merchant_order_sn: str, attach: str) -> str:
ledger = get_pay_ledger(merchant_order_sn)
if ledger and ledger.biz_type:
return ledger.biz_type
if 'biz=order' in attach:
return FUBEI_BIZ_ORDER
if 'biz=czjilu' in attach:
return FUBEI_BIZ_CZJILU
# 启发式:点单 Order 表优先
try:
from orders.models import Order
if Order.query.filter(OrderID=merchant_order_sn).exists():
return FUBEI_BIZ_ORDER
except Exception:
pass
return FUBEI_BIZ_CZJILU
def _fulfill_order(self, out_trade_no: str, transaction_id: str) -> bool:
try:
from orders.views.payment import WechatPayNotifyView
view = WechatPayNotifyView()
view._handle_success(out_trade_no, transaction_id, None)
return True
except Exception as exc:
logger.error('[FUBEI_NOTIFY] 订单履约失败 %s: %s', out_trade_no, exc, exc_info=True)
return False
def _fulfill_czjilu(self, out_trade_no: str, total_fee, transaction_id: str) -> bool:
from products.models import Czjilu
from products.czjilu_types import (
CZJILU_PAID_STATUS,
is_czjilu_fulfillable_status,
)
from products.views.alipay_czjilu_notify import _fulfill_czjilu_by_leixing
order = Czjilu.query.filter(dingdan_id=out_trade_no).first()
if not order:
logger.error('[FUBEI_NOTIFY] 充值单不存在: %s', out_trade_no)
return False
if order.zhuangtai == CZJILU_PAID_STATUS:
return True
if not is_czjilu_fulfillable_status(order.zhuangtai):
logger.warning('[FUBEI_NOTIFY] 充值单终态不再履约 %s zhuangtai=%s', out_trade_no, order.zhuangtai)
return True
paid_amount = total_fee if total_fee not in (None, '') else order.jine
try:
_fulfill_czjilu_by_leixing(out_trade_no, order.leixing, paid_amount, source='fubei_callback')
return True
except Exception as exc:
logger.error('[FUBEI_NOTIFY] 充值履约失败 %s: %s', out_trade_no, exc, exc_info=True)
return False