From 581295443aad5a38cad9505de71e55a694895d47 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 9 Jul 2026 22:06:46 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=99=E6=89=80=E6=9C=89=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=8A=A0=E4=B8=8A=E4=BA=86=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=AE=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jituan/services/alipay_pay.py | 50 +++++- jituan/services/czjilu_fulfill.py | 202 ++++++++++++++++++++++++- jituan/services/member_recharge.py | 5 + products/urls.py | 5 +- products/views/__init__.py | 3 + products/views/alipay_czjilu_notify.py | 117 ++++++++++++++ products/views/chongzhi_payment.py | 56 ++++--- products/views/huiyuan_payment.py | 71 ++++++--- products/views/jifen_payment.py | 56 ++++--- products/views/yajin_payment.py | 56 ++++--- users/views/pay.py | 117 +++++++++----- 11 files changed, 611 insertions(+), 127 deletions(-) create mode 100644 products/views/alipay_czjilu_notify.py diff --git a/jituan/services/alipay_pay.py b/jituan/services/alipay_pay.py index f7ddfde..f8a05a1 100644 --- a/jituan/services/alipay_pay.py +++ b/jituan/services/alipay_pay.py @@ -40,11 +40,42 @@ def get_alipay_config(club_id=None): 'ALIPAY_PUBLIC_KEY': cfg.get('ALIPAY_PUBLIC_KEY', ''), 'GATEWAY': cfg.get('GATEWAY', 'https://openapi.alipay.com/gateway.do'), 'NOTIFY_URL': cfg.get('NOTIFY_URL', ''), + 'NOTIFY_URL_CZJILU': cfg.get('NOTIFY_URL_CZJILU', '') or cfg.get('NOTIFY_URL', ''), 'RETURN_URL': cfg.get('RETURN_URL', ''), 'sandbox': sandbox, } +def get_czjilu_notify_url(club_id=None): + """获取 Czjilu 类充值(押金/积分/会员/商家/考核/罚款)的支付宝异步回调地址。""" + return get_alipay_config(club_id)['NOTIFY_URL_CZJILU'] + + +def generate_alipay_czjilu_pay_params(dingdanid, jine, subject, club_id=None, expire_minutes=30): + """生成 Czjilu 类充值的支付宝 wap.pay 二维码参数。 + + 返回 {qr_code, qr_image, pay_method},并在 cache 标记该订单为支付宝订单 + (轮询接口据此跳过微信查单,直接等异步回调更新本地状态)。 + """ + from django.core.cache import cache + notify_url = get_czjilu_notify_url(club_id) + pay_url = build_wap_pay_url( + out_trade_no=dingdanid, + amount=jine, + subject=subject or '充值支付', + club_id=club_id, + expire_minutes=expire_minutes, + notify_url=notify_url, + ) + qr_image = qr_link_to_base64(pay_url) + cache.set(f'alipay_pay_url_{dingdanid}', pay_url, 60 * 30) + return { + 'qr_code': pay_url, + 'qr_image': qr_image, + 'pay_method': 'alipay', + } + + # ==================== 密钥加载 ==================== def _load_private_key(key_str): @@ -196,17 +227,18 @@ def verify_notify_params(params, club_id=None): # ==================== wap.pay 跳转 URL ==================== -def build_wap_pay_url(out_trade_no, amount, subject, body='', club_id=None, expire_minutes=30): +def build_wap_pay_url(out_trade_no, amount, subject, body='', club_id=None, expire_minutes=30, notify_url=None): """ 生成 alipay.trade.wap.pay 跳转 URL(GET 方式)。 参数: - out_trade_no: 商户订单号(对应 Order.OrderID) + out_trade_no: 商户订单号(对应 Order.OrderID 或 Czjilu.dingdan_id) amount: 订单金额(Decimal 或字符串,单位元) subject: 订单标题 body: 订单描述(可选) club_id: 预留多 club(暂未使用) expire_minutes: 超时关闭时间(分钟) + notify_url: 自定义异步回调地址(可选,默认用 cfg['NOTIFY_URL']) 返回: 完整的支付宝跳转 URL,前端 web-view 直接加载即可。 @@ -230,7 +262,7 @@ def build_wap_pay_url(out_trade_no, amount, subject, body='', club_id=None, expi 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'), 'version': '1.0', 'biz_content': json.dumps(biz_content, separators=(',', ':'), ensure_ascii=False), - 'notify_url': cfg['NOTIFY_URL'], + 'notify_url': notify_url or cfg['NOTIFY_URL'], 'return_url': cfg['RETURN_URL'], } @@ -343,3 +375,15 @@ def club_id_from_order(order_id): except Exception: pass return CLUB_ID_DEFAULT + + +def club_id_from_czjilu(dingdan_id): + """从 Czjilu 充值单反查 club_id(支付宝 czjilu 回调用)。""" + from products.models import Czjilu + try: + row = Czjilu.query.filter(dingdan_id=dingdan_id).first() + if row and getattr(row, 'club_id', None): + return row.club_id + except Exception: + pass + return CLUB_ID_DEFAULT diff --git a/jituan/services/czjilu_fulfill.py b/jituan/services/czjilu_fulfill.py index 25fb20d..dfa1110 100644 --- a/jituan/services/czjilu_fulfill.py +++ b/jituan/services/czjilu_fulfill.py @@ -167,9 +167,191 @@ def fulfill_jifen_recharge(dingdan_id, paid_amount_yuan=None): return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': 3, 'jifen': dashou.jifen} +@transaction.atomic +def fulfill_fakuan(dingdan_id, paid_amount_yuan=None): + """罚款支付幂等履约:仅 zhuangtai==9 时入账。""" + import re + from orders.models import Penalty + from users.fadan_fenhong_utils import process_fadan_fenhong + + order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first() + if not order: + raise CzjiluFulfillError('订单不存在', 'not_found') + if order.leixing != 6: + raise CzjiluFulfillError('订单类型不是罚款', 'type_error') + if order.zhuangtai != 9: + try: + from jituan.services.szjilu_accounting import repair_wechat_income_if_missing + repair_wechat_income_if_missing( + Decimal(str(order.jine)), + getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, + biz_ref=f'cz:{dingdan_id}', + ) + except Exception as exc: + logger.error('罚款补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + return {'already_done': True, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai} + + amount = Decimal(str(order.jine)) + if paid_amount_yuan is not None: + paid = Decimal(str(paid_amount_yuan)) + if abs(paid - amount) > Decimal('0.01'): + raise CzjiluFulfillError(f'支付金额{paid}与订单{amount}不一致', 'amount_mismatch') + + order.zhuangtai = 3 + order.save(update_fields=['zhuangtai']) + + # 更新罚单状态为已缴纳(2)并触发分红 + try: + fadan = None + m = re.search(r'#fadan:(\d+)', order.shuoming or '') + if m: + fadan = Penalty.query.filter( + id=int(m.group(1)), + PenalizedUserID=order.yonghuid, + Status__in=[1, 3], + ).first() + if not fadan: + fadan = Penalty.query.filter( + PenalizedUserID=order.yonghuid, + FineAmount=order.jine, + Status__in=[1, 3], + ).order_by('-CreateTime').first() + if fadan: + fadan.Status = 2 + fadan.save(update_fields=['Status']) + success, msg = process_fadan_fenhong(fadan.id) + if not success: + logger.error('罚单 %s 分红处理失败: %s', fadan.id, msg) + else: + logger.info('罚单 %s 分红处理完成: %s', fadan.id, msg) + except Exception as exc: + logger.error('罚款罚单状态更新失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + + try: + from jituan.services.szjilu_accounting import repair_wechat_income_if_missing + repair_wechat_income_if_missing(amount, getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}') + except Exception as exc: + logger.error('罚款收支统计失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + + logger.info('罚款履约完成 dingdan=%s amount=%s', dingdan_id, amount) + return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': 3} + + +@transaction.atomic +def fulfill_kaohe(dingdan_id, paid_amount_yuan=None): + """考核支付幂等履约:仅 zhuangtai==9 时入账。""" + from rank.models import KaohePayTemp + from rank.utils import create_shenhe_jilu_from_temp + from users.business_models import User + + order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first() + if not order: + raise CzjiluFulfillError('订单不存在', 'not_found') + if order.leixing != 5: + raise CzjiluFulfillError('订单类型不是考核', 'type_error') + if order.zhuangtai != 9: + try: + from jituan.services.szjilu_accounting import repair_wechat_income_if_missing + repair_wechat_income_if_missing( + Decimal(str(order.jine)), + getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, + biz_ref=f'cz:{dingdan_id}', + ) + except Exception as exc: + logger.error('考核补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + return {'already_done': True, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai} + + amount = Decimal(str(order.jine)) + if paid_amount_yuan is not None: + paid = Decimal(str(paid_amount_yuan)) + if abs(paid - amount) > Decimal('0.01'): + raise CzjiluFulfillError(f'支付金额{paid}与订单{amount}不一致', 'amount_mismatch') + + order.zhuangtai = 3 + order.save(update_fields=['zhuangtai']) + + # 从临时表取业务参数并创建审核记录 + try: + temp = KaohePayTemp.query.get(dingdan_id=dingdan_id, yonghuid=order.yonghuid) + user = User.query.get(UserUID=order.yonghuid) + success, msg, record = create_shenhe_jilu_from_temp(user, temp) + if not success: + logger.error('创建审核记录失败 dingdan=%s: %s', dingdan_id, msg) + order.zhuangtai = 10 # 支付成功但业务处理失败 + order.save(update_fields=['zhuangtai']) + else: + logger.info('考核审核记录创建成功 dingdan=%s jilu_id=%s', dingdan_id, getattr(record, 'jilu_id', None)) + except KaohePayTemp.DoesNotExist: + logger.error('考核临时表无记录 dingdan=%s', dingdan_id) + order.zhuangtai = 10 + order.save(update_fields=['zhuangtai']) + except Exception as exc: + logger.error('考核审核记录创建异常 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + order.zhuangtai = 10 + order.save(update_fields=['zhuangtai']) + + try: + from jituan.services.szjilu_accounting import repair_wechat_income_if_missing + repair_wechat_income_if_missing(amount, getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}') + except Exception as exc: + logger.error('考核收支统计失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + + logger.info('考核履约完成 dingdan=%s amount=%s', dingdan_id, amount) + return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai} + + +@transaction.atomic +def fulfill_shangjia(dingdan_id, paid_amount_yuan=None): + """商家充值幂等履约:仅 zhuangtai==9 时入账。""" + from users.models import UserShangjia + + order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first() + if not order: + raise CzjiluFulfillError('订单不存在', 'not_found') + if order.leixing != 4: + raise CzjiluFulfillError('订单类型不是商家充值', 'type_error') + if order.zhuangtai != 9: + try: + from jituan.services.szjilu_accounting import repair_wechat_income_if_missing + repair_wechat_income_if_missing( + Decimal(str(order.jine)), + getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, + biz_ref=f'cz:{dingdan_id}', + ) + except Exception as exc: + logger.error('商家充值补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + return {'already_done': True, 'dingdan_id': dingdan_id, 'zhuangtai': order.zhuangtai} + + amount = Decimal(str(order.jine)) + if paid_amount_yuan is not None: + paid = Decimal(str(paid_amount_yuan)) + if abs(paid - amount) > Decimal('0.01'): + raise CzjiluFulfillError(f'支付金额{paid}与订单{amount}不一致', 'amount_mismatch') + + try: + shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=order.yonghuid) + except UserShangjia.DoesNotExist: + raise CzjiluFulfillError('商家不存在,无法充值', 'shangjia_not_found') + + shangjia.yue += amount + shangjia.save(update_fields=['yue']) + + order.zhuangtai = 3 + order.save(update_fields=['zhuangtai']) + + try: + from jituan.services.szjilu_accounting import repair_wechat_income_if_missing + repair_wechat_income_if_missing(amount, getattr(order, 'club_id', None) or CLUB_ID_DEFAULT, biz_ref=f'cz:{dingdan_id}') + except Exception as exc: + logger.error('商家充值收支统计失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) + + logger.info('商家充值履约完成 dingdan=%s amount=%s yue=%s', dingdan_id, amount, shangjia.yue) + return {'already_done': False, 'dingdan_id': dingdan_id, 'zhuangtai': 3, 'yue': shangjia.yue} + + def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing): - """轮询确认:查微信 SUCCESS 后幂等履约。""" - from jituan.services.member_recharge import query_wechat_v2_trade_state + """轮询确认:支付宝订单直接看本地状态,微信订单查微信 SUCCESS 后幂等履约。""" + from django.core.cache import cache try: order = Czjilu.query.get(dingdan_id=dingdan_id, yonghuid=yonghuid) @@ -194,13 +376,29 @@ def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing): if order.zhuangtai != 9: raise CzjiluFulfillError(f'订单状态异常: {order.zhuangtai}', 'status_error') + # 支付宝订单:异步回调会更新状态,轮询时只看本地状态,未支付则提示稍后重试 + is_alipay = cache.get(f'alipay_pay_url_{dingdan_id}') is not None + if is_alipay: + raise CzjiluFulfillError('支付宝支付结果尚未同步,请稍后再试', 'not_paid') + + # 微信订单:主动查单 + from jituan.services.member_recharge import query_wechat_v2_trade_state trade_state, _ = query_wechat_v2_trade_state(dingdan_id, getattr(order, 'club_id', None)) if trade_state != 'SUCCESS': raise CzjiluFulfillError('微信侧尚未支付成功,请稍后再试', 'not_paid') paid = Decimal(str(order.jine)) + if expected_leixing == 1: + from jituan.services.member_recharge import fulfill_member_recharge + return fulfill_member_recharge(dingdan_id, paid_amount_yuan=paid, source='confirm') if expected_leixing == 2: return fulfill_yajin_recharge(dingdan_id, paid_amount_yuan=paid) if expected_leixing == 3: return fulfill_jifen_recharge(dingdan_id, paid_amount_yuan=paid) + if expected_leixing == 4: + return fulfill_shangjia(dingdan_id, paid_amount_yuan=paid) + if expected_leixing == 5: + return fulfill_kaohe(dingdan_id, paid_amount_yuan=paid) + if expected_leixing == 6: + return fulfill_fakuan(dingdan_id, paid_amount_yuan=paid) raise CzjiluFulfillError('不支持的充值类型', 'type_error') diff --git a/jituan/services/member_recharge.py b/jituan/services/member_recharge.py index d59efee..e244251 100644 --- a/jituan/services/member_recharge.py +++ b/jituan/services/member_recharge.py @@ -1088,6 +1088,11 @@ def confirm_member_recharge_paid(dingdan_id, yonghuid): if order.zhuangtai != MEMBER_PENDING_STATUS: raise MemberRechargeError(f'订单状态异常: {order.zhuangtai}', 'status_error') + # 支付宝订单:异步回调会更新状态,轮询时只看本地状态,未支付则提示稍后重试 + from django.core.cache import cache + if cache.get(f'alipay_pay_url_{dingdan_id}') is not None: + raise MemberRechargeError('支付宝支付结果尚未同步,请稍后再试', 'not_paid') + trade_state, _ = query_wechat_v2_trade_state( dingdan_id, getattr(order, 'club_id', None), ) diff --git a/products/urls.py b/products/urls.py index b5e6b55..654f192 100644 --- a/products/urls.py +++ b/products/urls.py @@ -8,7 +8,8 @@ from .views import ShangpinHuoquView, ShangpinXiangqingHuoquView, DashouHuiyuanL ShangpinZhuanquFabuView, ShangpinFabuView, GuizeTupianShangchuanView, AdHuiyuanHqView, HuiyuanXiugaiView, \ AdHuiyuanFbView, AdShangpinHuQu, AdLeixingXiuGai, AdLunboXiuGai, AdZhuanquXiuGai, AdGonggaoXiuGai, \ AdShangpinGuizeXiuGai, AdShangpinXiuGai, AdShangpinNeiRongHuoQu,\ - HuiyuanTypeListView, HuiyuanRecordListView, ZuzhangFenhongView, CzhqdyView, DsqrgmdhView + HuiyuanTypeListView, HuiyuanRecordListView, ZuzhangFenhongView, CzhqdyView, DsqrgmdhView, \ + AlipayCzjiluNotifyView urlpatterns = [ #path('shangpinhuoqu/', ShangpinHuoquView.as_view(), name='获取商品数据商品类型和专区'), @@ -34,6 +35,8 @@ urlpatterns = [ path('huiyuanlx', HuiyuanLunxun.as_view(), name='会员支付轮询'), path('sjcz', ShangjiaChongzhi.as_view(), name='商家充值支付参数获取'), # 商家充值支付参数 path('sjhuitiao', csrf_exempt(ShangjiaHuitiao.as_view()), name='商家支付官方回调'), + # 支付宝 Czjilu 充值统一回调(押金/积分/会员/商家/考核/罚款) + path('alipay-czjilu-notify/', csrf_exempt(AlipayCzjiluNotifyView.as_view()), name='支付宝Czjilu充值统一回调'), #path('sjhuitiao', ShangjiaHuitiao.as_view(), name='商家支付官方回调'), # 商家支付回调 path('sjshibai', ShangjiaShibai.as_view(), name='商家支付失败'), # 商家支付失败处理 path('sjcg', ShangjiaChenggong.as_view(), name='商家支付成功'), # 商家支付成功轮询 diff --git a/products/views/__init__.py b/products/views/__init__.py index 256c8bc..13c5edb 100644 --- a/products/views/__init__.py +++ b/products/views/__init__.py @@ -56,6 +56,7 @@ from .chongzhi_payment import ( MAX_INTEGRAL, IDENTITY_CONFIG, ) +from .alipay_czjilu_notify import AlipayCzjiluNotifyView from .shangpin_admin import ( ShangpinLeixingFabuView, ShangpinZhuanquFabuView, @@ -96,6 +97,8 @@ __all__ = [ 'ShangjiaChongzhi', 'ShangjiaHuitiao', 'ShangjiaShibai', 'ShangjiaChenggong', 'CzhqdyView', 'DsqrgmdhView', 'INTEGRAL_PRICE', 'INTEGRAL_INCREMENT', 'MAX_INTEGRAL', 'IDENTITY_CONFIG', + # alipay_czjilu_notify + 'AlipayCzjiluNotifyView', # shangpin_admin 'ShangpinLeixingFabuView', 'ShangpinZhuanquFabuView', 'ShangpinFabuView', 'GuizeTupianShangchuanView', diff --git a/products/views/alipay_czjilu_notify.py b/products/views/alipay_czjilu_notify.py new file mode 100644 index 0000000..84f855e --- /dev/null +++ b/products/views/alipay_czjilu_notify.py @@ -0,0 +1,117 @@ +"""支付宝 Czjilu 类充值统一异步回调视图。 + +所有基于 Czjilu 表的支付宝支付(押金/积分/会员/商家充值/考核/罚款) +共用一个回调入口,按 Czjilu.leixing 路由到对应履约函数。 +""" +import logging +import traceback + +from django.http import HttpResponse +from django.views import View + +from products.models import Czjilu +from products.czjilu_types import ( + CZJILU_LEIXING_HUIYUAN, CZJILU_LEIXING_YAJIN, CZJILU_LEIXING_JIFEN, + CZJILU_LEIXING_SHANGJIA, CZJILU_LEIXING_KAOHE, CZJILU_LEIXING_FAKUAN, +) + +logger = logging.getLogger(__name__) + + +class AlipayCzjiluNotifyView(View): + """支付宝 Czjilu 充值统一回调:验签 + 按 leixing 路由履约。 + + 路径:/shangpin/alipay-czjilu-notify/ + 不需要 JWT 认证,由支付宝服务器调用。 + """ + + def post(self, request): + try: + # 注意:必须用 .dict() 而不是 dict() + # dict(request.POST) 返回 {key: [value_list]}(值是列表) + # request.POST.dict() 返回 {key: value}(值是字符串),符合验签需求 + params = request.POST.dict() if request.POST else {} + if not params: + logger.warning('[ALIPAY_CZJILU_NOTIFY] 收到空数据') + return HttpResponse('fail', content_type='text/plain') + + out_trade_no = params.get('out_trade_no', '') + trade_no = params.get('trade_no', '') + trade_status = params.get('trade_status', '') + total_amount = params.get('total_amount', '') + + logger.info( + '[ALIPAY_CZJILU_NOTIFY] out_trade_no=%s, trade_no=%s, trade_status=%s, total_amount=%s', + out_trade_no, trade_no, trade_status, total_amount, + ) + + # 验签 + from jituan.services.alipay_pay import verify_notify_params, club_id_from_czjilu + notify_club_id = club_id_from_czjilu(out_trade_no) + if not verify_notify_params(params, notify_club_id): + logger.warning('[ALIPAY_CZJILU_NOTIFY] 验签失败: out_trade_no=%s', out_trade_no) + return HttpResponse('fail', content_type='text/plain') + + # 仅处理交易成功状态 + if trade_status not in ('TRADE_SUCCESS', 'TRADE_FINISHED'): + logger.info('[ALIPAY_CZJILU_NOTIFY] 非成功状态 %s: %s', trade_status, out_trade_no) + return HttpResponse('success', content_type='text/plain') + + # 查询充值单 + order = Czjilu.query.filter(dingdan_id=out_trade_no).first() + if not order: + logger.error('[ALIPAY_CZJILU_NOTIFY] 充值单不存在: %s', out_trade_no) + return HttpResponse('fail', content_type='text/plain') + + # 已支付:补记收支后直接返回成功(幂等) + if order.zhuangtai == 3: + logger.info('[ALIPAY_CZJILU_NOTIFY] 订单已支付,幂等返回: %s', out_trade_no) + return HttpResponse('success', content_type='text/plain') + + if order.zhuangtai != 9: + logger.warning( + '[ALIPAY_CZJILU_NOTIFY] 订单状态异常: %s, zhuangtai=%s', + out_trade_no, order.zhuangtai, + ) + return HttpResponse('success', content_type='text/plain') + + # 按 leixing 路由到对应履约函数 + paid_amount = total_amount or order.jine + leixing = order.leixing + try: + if leixing == CZJILU_LEIXING_HUIYUAN: + from jituan.services.member_recharge import fulfill_member_recharge + fulfill_member_recharge(out_trade_no, paid_amount_yuan=paid_amount, source='callback') + elif leixing == CZJILU_LEIXING_YAJIN: + from jituan.services.czjilu_fulfill import fulfill_yajin_recharge + fulfill_yajin_recharge(out_trade_no, paid_amount_yuan=paid_amount) + elif leixing == CZJILU_LEIXING_JIFEN: + from jituan.services.czjilu_fulfill import fulfill_jifen_recharge + fulfill_jifen_recharge(out_trade_no, paid_amount_yuan=paid_amount) + elif leixing == CZJILU_LEIXING_SHANGJIA: + from jituan.services.czjilu_fulfill import fulfill_shangjia + fulfill_shangjia(out_trade_no, paid_amount_yuan=paid_amount) + elif leixing == CZJILU_LEIXING_KAOHE: + from jituan.services.czjilu_fulfill import fulfill_kaohe + fulfill_kaohe(out_trade_no, paid_amount_yuan=paid_amount) + elif leixing == CZJILU_LEIXING_FAKUAN: + from jituan.services.czjilu_fulfill import fulfill_fakuan + fulfill_fakuan(out_trade_no, paid_amount_yuan=paid_amount) + else: + logger.error('[ALIPAY_CZJILU_NOTIFY] 未知 leixing=%s: %s', leixing, out_trade_no) + return HttpResponse('fail', content_type='text/plain') + except Exception as fulfill_exc: + logger.error( + '[ALIPAY_CZJILU_NOTIFY] 履约异常 out_trade_no=%s leixing=%s: %s', + out_trade_no, leixing, fulfill_exc, exc_info=True, + ) + # 履约异常返回 fail,让支付宝重试 + return HttpResponse('fail', content_type='text/plain') + + logger.info('[ALIPAY_CZJILU_NOTIFY] 履约完成: %s, leixing=%s', out_trade_no, leixing) + return HttpResponse('success', content_type='text/plain') + + except Exception as e: + logger.error('[ALIPAY_CZJILU_NOTIFY] 回调处理异常: %s', str(e)) + logger.error(traceback.format_exc()) + return HttpResponse('fail', content_type='text/plain') diff --git a/products/views/chongzhi_payment.py b/products/views/chongzhi_payment.py index 2e0a3b9..7509c74 100644 --- a/products/views/chongzhi_payment.py +++ b/products/views/chongzhi_payment.py @@ -156,26 +156,44 @@ class ShangjiaChongzhi(APIView): 'message': '订单创建失败,请重试' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - # 5. 生成微信支付参数 - try: - pay_openid, _ = get_payment_openid(request) - if not pay_openid: + # 5. 生成支付参数(支持支付宝/微信) + pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower() + if pay_method == 'alipay': + try: + from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params + pay_params = generate_alipay_czjilu_pay_params( + dingdanid=dingdanid, + jine=jine, + subject=settings.SHANGJIA_CZ_PAY_DESCRIPTION, + club_id=resolve_club_id_for_write(request, request.user), + ) + except Exception as e: chongzhi_jilu.delete() - return Response({'code': 400, 'message': '用户openid不存在'}, status=status.HTTP_400_BAD_REQUEST) - pay_params = self.generate_wechat_pay_params( - dingdanid=dingdanid, - jine=jine, - openid=pay_openid, - pay_type='shangjia' - ) - except Exception as e: - # 支付参数生成失败,删除订单记录 - chongzhi_jilu.delete() - logger.error(f"生成微信支付参数失败: {str(e)}") - return Response({ - 'code': 500, - 'message': f'支付参数生成失败: {str(e)}' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + logger.error(f"生成支付宝支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + try: + pay_openid, _ = get_payment_openid(request) + if not pay_openid: + chongzhi_jilu.delete() + return Response({'code': 400, 'message': '用户openid不存在'}, status=status.HTTP_400_BAD_REQUEST) + pay_params = self.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=jine, + openid=pay_openid, + pay_type='shangjia' + ) + except Exception as e: + # 支付参数生成失败,删除订单记录 + chongzhi_jilu.delete() + logger.error(f"生成微信支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 6. 返回支付参数和订单ID logger.info(f"商家{shangjia.nicheng}支付参数生成成功: {dingdanid}") diff --git a/products/views/huiyuan_payment.py b/products/views/huiyuan_payment.py index 4f56bc0..21f69f4 100644 --- a/products/views/huiyuan_payment.py +++ b/products/views/huiyuan_payment.py @@ -164,35 +164,56 @@ class HuiyuanGoumai(APIView): f"会员{huiyuanid}, 俱乐部{club_id}, 体验={is_trial}, " f"金额{jine}元, 天数{purchase_days}") - # 7. 生成微信支付参数 - try: - pay_openid, _ = get_payment_openid(request) - if not pay_openid: + # 7. 生成支付参数(支持支付宝/微信) + pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower() + if pay_method == 'alipay': + try: + from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params + pay_params = generate_alipay_czjilu_pay_params( + dingdanid=dingdanid, + jine=jine, + subject=settings.HUIYUAN_PAY_DESCRIPTION, + club_id=club_id, + ) + except Exception as e: from jituan.services.member_recharge import MEMBER_CANCELLED_STATUS chongzhi_jilu.zhuangtai = MEMBER_CANCELLED_STATUS - chongzhi_jilu.shuoming = f"{shuoming} | 下单失败:openid不存在"[:100] + chongzhi_jilu.shuoming = f"{shuoming} | 下单失败:{str(e)[:40]}"[:100] chongzhi_jilu.save(update_fields=['zhuangtai', 'shuoming']) + logger.error(f"生成会员支付宝参数失败: {str(e)}") return Response({ - 'code': 400, - 'message': '用户openid不存在', - 'dingdanid': dingdanid, - }, status=status.HTTP_400_BAD_REQUEST) - pay_params = self.generate_wechat_pay_params( - dingdanid=dingdanid, - jine=jine, - openid=pay_openid, - pay_type='huiyuan' - ) - except Exception as e: - from jituan.services.member_recharge import MEMBER_CANCELLED_STATUS - chongzhi_jilu.zhuangtai = MEMBER_CANCELLED_STATUS - chongzhi_jilu.shuoming = f"{shuoming} | 下单失败:{str(e)[:40]}"[:100] - chongzhi_jilu.save(update_fields=['zhuangtai', 'shuoming']) - logger.error(f"生成会员支付参数失败: {str(e)}") - return Response({ - 'code': 500, - 'message': f'支付参数生成失败: {str(e)}' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + try: + pay_openid, _ = get_payment_openid(request) + if not pay_openid: + from jituan.services.member_recharge import MEMBER_CANCELLED_STATUS + chongzhi_jilu.zhuangtai = MEMBER_CANCELLED_STATUS + chongzhi_jilu.shuoming = f"{shuoming} | 下单失败:openid不存在"[:100] + chongzhi_jilu.save(update_fields=['zhuangtai', 'shuoming']) + return Response({ + 'code': 400, + 'message': '用户openid不存在', + 'dingdanid': dingdanid, + }, status=status.HTTP_400_BAD_REQUEST) + pay_params = self.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=jine, + openid=pay_openid, + pay_type='huiyuan' + ) + except Exception as e: + from jituan.services.member_recharge import MEMBER_CANCELLED_STATUS + chongzhi_jilu.zhuangtai = MEMBER_CANCELLED_STATUS + chongzhi_jilu.shuoming = f"{shuoming} | 下单失败:{str(e)[:40]}"[:100] + chongzhi_jilu.save(update_fields=['zhuangtai', 'shuoming']) + logger.error(f"生成会员支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 8. 返回支付参数和订单ID return Response({ diff --git a/products/views/jifen_payment.py b/products/views/jifen_payment.py index b842343..dc5e525 100644 --- a/products/views/jifen_payment.py +++ b/products/views/jifen_payment.py @@ -132,26 +132,44 @@ class JifenBuchong(APIView): logger.info(f"创建积分充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, 金额{jine}元") - # 6. 生成微信支付参数(完整版) - try: - pay_openid, _ = get_payment_openid(request) - if not pay_openid: + # 6. 生成支付参数(支持支付宝/微信) + pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower() + if pay_method == 'alipay': + try: + from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params + pay_params = generate_alipay_czjilu_pay_params( + dingdanid=dingdanid, + jine=jine, + subject=settings.JIFEN_PAY_DESCRIPTION, + club_id=resolve_club_id_for_write(request, request.user), + ) + except Exception as e: chongzhi_jilu.delete() - return Response({'code': 400, 'message': '用户openid不存在'}, status=status.HTTP_400_BAD_REQUEST) - pay_params = self.generate_wechat_pay_params( - dingdanid=dingdanid, - jine=jine, - openid=pay_openid, - pay_type='jifen' - ) - except Exception as e: - # 支付参数生成失败,删除订单记录 - chongzhi_jilu.delete() - logger.error(f"生成积分支付参数失败: {str(e)}") - return Response({ - 'code': 500, - 'message': f'支付参数生成失败: {str(e)}' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + logger.error(f"生成支付宝支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + try: + pay_openid, _ = get_payment_openid(request) + if not pay_openid: + chongzhi_jilu.delete() + return Response({'code': 400, 'message': '用户openid不存在'}, status=status.HTTP_400_BAD_REQUEST) + pay_params = self.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=jine, + openid=pay_openid, + pay_type='jifen' + ) + except Exception as e: + # 支付参数生成失败,删除订单记录 + chongzhi_jilu.delete() + logger.error(f"生成积分支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 7. 返回支付参数和订单ID return Response({ diff --git a/products/views/yajin_payment.py b/products/views/yajin_payment.py index 7a5491b..40f460c 100644 --- a/products/views/yajin_payment.py +++ b/products/views/yajin_payment.py @@ -146,26 +146,44 @@ class YajinGoumai(APIView): logger.info(f"创建押金充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, 金额{jine}元") - # 5. 生成微信支付参数 - try: - pay_openid, _ = get_payment_openid(request) - if not pay_openid: + # 5. 生成支付参数(支持支付宝/微信) + pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower() + if pay_method == 'alipay': + try: + from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params + pay_params = generate_alipay_czjilu_pay_params( + dingdanid=dingdanid, + jine=jine, + subject=settings.YAJIN_PAY_DESCRIPTION, + club_id=resolve_club_id_for_write(request, request.user), + ) + except Exception as e: chongzhi_jilu.delete() - return Response({'code': 400, 'message': '用户openid不存在'}, status=status.HTTP_400_BAD_REQUEST) - pay_params = self.generate_wechat_pay_params( - dingdanid=dingdanid, - jine=jine, - openid=pay_openid, - pay_type='yajin' - ) - except Exception as e: - # 支付参数生成失败,删除订单记录 - chongzhi_jilu.delete() - logger.error(f"生成微信支付参数失败: {str(e)}") - return Response({ - 'code': 500, - 'message': f'支付参数生成失败: {str(e)}' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + logger.error(f"生成支付宝支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + try: + pay_openid, _ = get_payment_openid(request) + if not pay_openid: + chongzhi_jilu.delete() + return Response({'code': 400, 'message': '用户openid不存在'}, status=status.HTTP_400_BAD_REQUEST) + pay_params = self.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=jine, + openid=pay_openid, + pay_type='yajin' + ) + except Exception as e: + # 支付参数生成失败,删除订单记录 + chongzhi_jilu.delete() + logger.error(f"生成微信支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 6. 返回支付参数和订单ID return Response({ diff --git a/users/views/pay.py b/users/views/pay.py index 9e836c1..f4b92fd 100644 --- a/users/views/pay.py +++ b/users/views/pay.py @@ -131,13 +131,6 @@ class FaKuanPayView(APIView): from jituan.services.club_user import get_payment_openid from products.czjilu_types import CZJILU_LEIXING_FAKUAN, penalty_cz_shuoming - pay_openid, _ = get_payment_openid(request) - if not pay_openid: - return Response({ - 'code': 400, - 'message': '用户openid不存在,无法发起微信支付', - }, status=status.HTTP_400_BAD_REQUEST) - chongzhi_jilu = Czjilu.query.create( dingdan_id=dingdanid, zhuangtai=9, @@ -148,20 +141,46 @@ class FaKuanPayView(APIView): club_id=penalty_cz_club_id(fadan), ) - try: - pay_params = self.generate_wechat_pay_params( - dingdanid=dingdanid, - jine=jine, - openid=pay_openid, - pay_type='fakuan' - ) - except Exception as e: - chongzhi_jilu.delete() - logger.error(f"生成微信支付参数失败: {str(e)}") - return Response({ - 'code': 500, - 'message': f'支付参数生成失败: {str(e)}' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + # 生成支付参数(支持支付宝/微信) + pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower() + if pay_method == 'alipay': + try: + from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params + pay_params = generate_alipay_czjilu_pay_params( + dingdanid=dingdanid, + jine=jine, + subject=settings.FAKUAN_PAY_DESCRIPTION, + club_id=penalty_cz_club_id(fadan), + ) + except Exception as e: + chongzhi_jilu.delete() + logger.error(f"生成支付宝支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + pay_openid, _ = get_payment_openid(request) + if not pay_openid: + chongzhi_jilu.delete() + return Response({ + 'code': 400, + 'message': '用户openid不存在,无法发起微信支付', + }, status=status.HTTP_400_BAD_REQUEST) + try: + pay_params = self.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=jine, + openid=pay_openid, + pay_type='fakuan' + ) + except Exception as e: + chongzhi_jilu.delete() + logger.error(f"生成微信支付参数失败: {str(e)}") + return Response({ + 'code': 500, + 'message': f'支付参数生成失败: {str(e)}' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response({ 'code': 200, @@ -670,25 +689,45 @@ class KaohePayView(APIView): attach_str = json.dumps({'oid': dingdanid}, ensure_ascii=False) logger.info(f"attach内容: {attach_str},长度: {len(attach_str.encode('utf-8'))}字节") - # 11. 生成微信支付参数 - try: - pay_params = self.generate_wechat_pay_params( - dingdanid=dingdanid, - jine=float(fee), - openid=user.OpenID, - attach=attach_str, - pay_type='kaohe' - ) - logger.info(f"微信支付参数生成成功: {dingdanid}") - except Exception as e: - order.delete() # 下单失败,删除订单记录 - # 同时删除临时表记录 + # 11. 生成支付参数(支持支付宝/微信) + pay_method = (request.data.get('pay_method') or request.data.get('payMethod') or 'wechat').lower() + if pay_method == 'alipay': try: - KaohePayTemp.query.filter(dingdan_id=dingdanid).delete() - except Exception as del_e: - logger.error(f"删除临时表记录失败: {str(del_e)}") - logger.error(f"生成微信支付参数失败: {str(e)}", exc_info=True) - return Response({'code': 500, 'message': f'支付参数生成失败: {str(e)}'}) + from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params + pay_params = generate_alipay_czjilu_pay_params( + dingdanid=dingdanid, + jine=float(fee), + subject=settings.KAOHE_PAY_DESCRIPTION, + club_id=resolve_club_id_for_write(request, user), + ) + logger.info(f"支付宝支付参数生成成功: {dingdanid}") + except Exception as e: + order.delete() + try: + KaohePayTemp.query.filter(dingdan_id=dingdanid).delete() + except Exception as del_e: + logger.error(f"删除临时表记录失败: {str(del_e)}") + logger.error(f"生成支付宝支付参数失败: {str(e)}", exc_info=True) + return Response({'code': 500, 'message': f'支付参数生成失败: {str(e)}'}) + else: + try: + pay_params = self.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=float(fee), + openid=user.OpenID, + attach=attach_str, + pay_type='kaohe' + ) + logger.info(f"微信支付参数生成成功: {dingdanid}") + except Exception as e: + order.delete() # 下单失败,删除订单记录 + # 同时删除临时表记录 + try: + KaohePayTemp.query.filter(dingdan_id=dingdanid).delete() + except Exception as del_e: + logger.error(f"删除临时表记录失败: {str(del_e)}") + logger.error(f"生成微信支付参数失败: {str(e)}", exc_info=True) + return Response({'code': 500, 'message': f'支付参数生成失败: {str(e)}'}) return Response({ 'code': 200,