给所有支付接口加上了支付宝

This commit is contained in:
2026-07-09 22:06:46 +08:00
parent 914e8c70f2
commit 581295443a
11 changed files with 611 additions and 127 deletions

View File

@@ -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 跳转 URLGET 方式)。
参数:
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

View File

@@ -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')

View File

@@ -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),
)