"""付呗支付异步回调:验签后按订单/充值单履约(前端无感知)。""" 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