fix: 加固支付宝充值回调幂等履约,避免付成功不到账与误删单
禁止 Czjilu 回调地址回落到订单 notify;回调失败回 fail 重试;取消支付改关单不删;客服补单走同一履约防多加。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,22 +33,43 @@ def get_alipay_config(club_id=None):
|
||||
else:
|
||||
cfg = getattr(settings, 'ALIPAY_PROD', {})
|
||||
|
||||
# 注意:NOTIFY_URL_CZJILU 不得静默回落到订单 NOTIFY_URL,否则充值回调会打进订单回调被吞掉
|
||||
czjilu_notify = (cfg.get('NOTIFY_URL_CZJILU') or '').strip()
|
||||
order_notify = (cfg.get('NOTIFY_URL') or '').strip()
|
||||
return {
|
||||
'APP_ID': cfg.get('APP_ID', ''),
|
||||
'PID': cfg.get('PID', ''),
|
||||
'APP_PRIVATE_KEY': cfg.get('APP_PRIVATE_KEY', ''),
|
||||
'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', ''),
|
||||
'NOTIFY_URL': order_notify,
|
||||
'NOTIFY_URL_CZJILU': czjilu_notify,
|
||||
'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']
|
||||
"""获取 Czjilu 类充值的支付宝异步回调地址;未单独配置则抛错,禁止回落到订单回调。"""
|
||||
cfg = get_alipay_config(club_id)
|
||||
notify_url = (cfg.get('NOTIFY_URL_CZJILU') or '').strip()
|
||||
if not notify_url:
|
||||
raise Exception(
|
||||
'支付宝配置缺少 NOTIFY_URL_CZJILU(须指向 /shangpin/alipay-czjilu-notify/),'
|
||||
'禁止使用订单回调地址,否则充值到账会失败'
|
||||
)
|
||||
order_notify = (cfg.get('NOTIFY_URL') or '').strip()
|
||||
if order_notify and notify_url.rstrip('/') == order_notify.rstrip('/'):
|
||||
raise Exception(
|
||||
'NOTIFY_URL_CZJILU 不能与订单 NOTIFY_URL 相同,'
|
||||
'请配置为 .../shangpin/alipay-czjilu-notify/'
|
||||
)
|
||||
if 'alipay-czjilu-notify' not in notify_url:
|
||||
logger.warning(
|
||||
'NOTIFY_URL_CZJILU=%s 未包含 alipay-czjilu-notify,请确认是否配错',
|
||||
notify_url,
|
||||
)
|
||||
return notify_url
|
||||
|
||||
|
||||
def generate_alipay_czjilu_pay_params(dingdanid, jine, subject, club_id=None, expire_minutes=30):
|
||||
|
||||
@@ -11,6 +11,7 @@ from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.services.club_penalty import resolve_gsfenhong_club_id
|
||||
from orders.models import CommissionRate
|
||||
from products.models import Czjilu, Gsfenhong
|
||||
from products.czjilu_types import is_czjilu_fulfillable_status
|
||||
from users.models import UserDashou, UserGuanshi
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -28,13 +29,13 @@ class CzjiluFulfillError(Exception):
|
||||
|
||||
@transaction.atomic
|
||||
def fulfill_yajin_recharge(dingdan_id, paid_amount_yuan=None):
|
||||
"""押金充值幂等履约:仅 zhuangtai==9 时入账。"""
|
||||
"""押金充值幂等履约:仅待履约状态(9/用户关单2)时入账;已付(3)直接跳过防多加。"""
|
||||
order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first()
|
||||
if not order:
|
||||
raise CzjiluFulfillError('订单不存在', 'not_found')
|
||||
if order.leixing != 2:
|
||||
raise CzjiluFulfillError('订单类型不是押金', 'type_error')
|
||||
if order.zhuangtai != 9:
|
||||
if not is_czjilu_fulfillable_status(order.zhuangtai):
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(
|
||||
@@ -118,13 +119,13 @@ def _apply_yajin_fenhong(order, dashou, amount):
|
||||
|
||||
@transaction.atomic
|
||||
def fulfill_jifen_recharge(dingdan_id, paid_amount_yuan=None):
|
||||
"""积分补充幂等履约:仅 zhuangtai==9 时入账。"""
|
||||
"""积分补充幂等履约:仅待履约状态(9/2)时入账。"""
|
||||
order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first()
|
||||
if not order:
|
||||
raise CzjiluFulfillError('订单不存在', 'not_found')
|
||||
if order.leixing != 3:
|
||||
raise CzjiluFulfillError('订单类型不是积分', 'type_error')
|
||||
if order.zhuangtai != 9:
|
||||
if not is_czjilu_fulfillable_status(order.zhuangtai):
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(
|
||||
@@ -179,7 +180,7 @@ def fulfill_fakuan(dingdan_id, paid_amount_yuan=None):
|
||||
raise CzjiluFulfillError('订单不存在', 'not_found')
|
||||
if order.leixing != 6:
|
||||
raise CzjiluFulfillError('订单类型不是罚款', 'type_error')
|
||||
if order.zhuangtai != 9:
|
||||
if not is_czjilu_fulfillable_status(order.zhuangtai):
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(
|
||||
@@ -260,7 +261,7 @@ def fulfill_fakuan(dingdan_id, paid_amount_yuan=None):
|
||||
|
||||
@transaction.atomic
|
||||
def fulfill_kaohe(dingdan_id, paid_amount_yuan=None):
|
||||
"""考核支付幂等履约:仅 zhuangtai==9 时入账。"""
|
||||
"""考核支付幂等履约:仅待履约状态(9/2)时入账。"""
|
||||
from rank.models import KaohePayTemp
|
||||
from rank.utils import create_shenhe_jilu_from_temp
|
||||
from users.business_models import User
|
||||
@@ -270,7 +271,7 @@ def fulfill_kaohe(dingdan_id, paid_amount_yuan=None):
|
||||
raise CzjiluFulfillError('订单不存在', 'not_found')
|
||||
if order.leixing != 5:
|
||||
raise CzjiluFulfillError('订单类型不是考核', 'type_error')
|
||||
if order.zhuangtai != 9:
|
||||
if not is_czjilu_fulfillable_status(order.zhuangtai):
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(
|
||||
@@ -323,7 +324,7 @@ def fulfill_kaohe(dingdan_id, paid_amount_yuan=None):
|
||||
|
||||
@transaction.atomic
|
||||
def fulfill_shangjia(dingdan_id, paid_amount_yuan=None):
|
||||
"""商家充值幂等履约:仅 zhuangtai==9 时入账。"""
|
||||
"""商家充值幂等履约:仅待履约状态(9/2)时入账;已付(3)跳过防多加。"""
|
||||
from users.models import UserShangjia
|
||||
|
||||
order = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update().first()
|
||||
@@ -331,7 +332,7 @@ def fulfill_shangjia(dingdan_id, paid_amount_yuan=None):
|
||||
raise CzjiluFulfillError('订单不存在', 'not_found')
|
||||
if order.leixing != 4:
|
||||
raise CzjiluFulfillError('订单类型不是商家充值', 'type_error')
|
||||
if order.zhuangtai != 9:
|
||||
if not is_czjilu_fulfillable_status(order.zhuangtai):
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(
|
||||
@@ -394,7 +395,7 @@ def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing):
|
||||
logger.error('充值单补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
|
||||
return {'already_done': True, 'zhuangtai': 3}
|
||||
|
||||
if order.zhuangtai != 9:
|
||||
if not is_czjilu_fulfillable_status(order.zhuangtai):
|
||||
raise CzjiluFulfillError(f'订单状态异常: {order.zhuangtai}', 'status_error')
|
||||
|
||||
# 主动查单确认支付状态
|
||||
@@ -421,7 +422,7 @@ def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing):
|
||||
else:
|
||||
raise CzjiluFulfillError('微信侧尚未支付成功,请稍后再试', 'not_paid')
|
||||
|
||||
# 查单确认支付成功,执行履约
|
||||
# 查单确认支付成功,执行履约(fulfill_* 内行锁+仅 9/2→3,防多加)
|
||||
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')
|
||||
@@ -436,3 +437,12 @@ def confirm_czjilu_paid(dingdan_id, yonghuid, expected_leixing):
|
||||
if expected_leixing == 6:
|
||||
return fulfill_fakuan(dingdan_id, paid_amount_yuan=paid)
|
||||
raise CzjiluFulfillError('不支持的充值类型', 'type_error')
|
||||
|
||||
|
||||
def repair_czjilu_payment(dingdan_id):
|
||||
"""客服补单:官方查单确认已付后,走同一套幂等履约(绝不多加)。"""
|
||||
try:
|
||||
order = Czjilu.query.get(dingdan_id=dingdan_id)
|
||||
except Czjilu.DoesNotExist:
|
||||
raise CzjiluFulfillError('订单不存在', 'not_found')
|
||||
return confirm_czjilu_paid(dingdan_id, order.yonghuid, expected_leixing=order.leixing)
|
||||
|
||||
@@ -48,6 +48,9 @@ MEMBER_PAID_STATUS = 3
|
||||
MEMBER_PENDING_STATUS = 9
|
||||
MEMBER_FAILED_STATUS = 8
|
||||
MEMBER_CANCELLED_STATUS = 2
|
||||
# 与 czjilu_types.CZJILU_FULFILLABLE_STATUSES 一致:仅这些状态允许入账
|
||||
MEMBER_FULFILLABLE_STATUSES = (MEMBER_PENDING_STATUS, MEMBER_CANCELLED_STATUS)
|
||||
MEMBER_CANCELLED_STATUS = 2
|
||||
ENTITLEMENT_REVOKED_MARKER = '权益已撤销'
|
||||
MEMBER_JIFEN_CAP = 10
|
||||
|
||||
@@ -923,7 +926,7 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'
|
||||
if order.leixing != MEMBER_LEIXING:
|
||||
raise MemberRechargeError('订单类型不是会员充值', 'type_error')
|
||||
|
||||
if order.zhuangtai != MEMBER_PENDING_STATUS:
|
||||
if order.zhuangtai not in MEMBER_FULFILLABLE_STATUSES:
|
||||
logger.info(
|
||||
'会员订单已处理,跳过履约 dingdan=%s zhuangtai=%s source=%s',
|
||||
dingdan_id, order.zhuangtai, source,
|
||||
@@ -1093,7 +1096,7 @@ def confirm_member_recharge_paid(dingdan_id, yonghuid):
|
||||
'is_trial': goumai.is_trial,
|
||||
}
|
||||
|
||||
if order.zhuangtai != MEMBER_PENDING_STATUS:
|
||||
if order.zhuangtai != MEMBER_PENDING_STATUS and order.zhuangtai != MEMBER_CANCELLED_STATUS:
|
||||
raise MemberRechargeError(f'订单状态异常: {order.zhuangtai}', 'status_error')
|
||||
|
||||
# 主动查单确认支付状态
|
||||
|
||||
Reference in New Issue
Block a user