diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py index 4134c24..db5a9ac 100644 --- a/a_long_dianjing/settings.py +++ b/a_long_dianjing/settings.py @@ -399,3 +399,6 @@ WECHAT_PAY_V3_CONFIG = { ALIPAY_SANDBOX = app_secrets.ALIPAY_SANDBOX ALIPAY_PROD = app_secrets.ALIPAY_PROD ALIPAY_SANDBOX_CFG = app_secrets.ALIPAY_SANDBOX_CFG +# 仅这些 club_id 禁止「发起」支付宝支付;回调/查单仍可用(提现收款不受影响)。 +# 小溪/小汐 = xx。恢复:改为 frozenset(),再重启 gunicorn。 +ALIPAY_DISABLED_CLUB_IDS = frozenset({'xx'}) diff --git a/jituan/services/alipay_pay.py b/jituan/services/alipay_pay.py index 105d8cb..1f66cb7 100644 --- a/jituan/services/alipay_pay.py +++ b/jituan/services/alipay_pay.py @@ -18,6 +18,32 @@ from jituan.constants import CLUB_ID_DEFAULT logger = logging.getLogger(__name__) +ALIPAY_DISABLED_MSG = '支付宝支付功能暂不可用,请使用微信支付' + + +def normalize_pay_club_id(club_id=None): + return (club_id or '').strip().lower() or CLUB_ID_DEFAULT + + +def is_alipay_enabled_for_club(club_id=None): + """ + 是否允许该俱乐部「新发起」支付宝支付。 + 仅 settings.ALIPAY_DISABLED_CLUB_IDS 中的俱乐部为 False;其余一律 True。 + 不影响异步回调验签与查单;不影响提现支付宝收款。 + """ + cid = normalize_pay_club_id(club_id) + disabled = getattr(settings, 'ALIPAY_DISABLED_CLUB_IDS', None) or () + try: + disabled_set = {str(x).strip().lower() for x in disabled if x is not None} + except TypeError: + disabled_set = set() + return cid not in disabled_set + + +def assert_alipay_allowed_for_club(club_id=None): + if not is_alipay_enabled_for_club(club_id): + raise Exception(ALIPAY_DISABLED_MSG) + # ==================== 配置读取 ==================== @@ -73,6 +99,7 @@ def generate_alipay_czjilu_pay_params(dingdanid, jine, subject, club_id=None, ex 返回 {qr_code, qr_image, pay_method},并在 cache 标记该订单为支付宝订单 (轮询接口据此跳过微信查单,直接等异步回调更新本地状态)。 """ + assert_alipay_allowed_for_club(club_id) from django.core.cache import cache notify_url = get_czjilu_notify_url(club_id) pay_url = build_wap_pay_url( @@ -82,6 +109,7 @@ def generate_alipay_czjilu_pay_params(dingdanid, jine, subject, club_id=None, ex club_id=club_id, expire_minutes=expire_minutes, notify_url=notify_url, + _skip_club_gate=True, ) qr_image = qr_link_to_base64(pay_url) cache.set(f'alipay_pay_url_{dingdanid}', pay_url, 60 * 30) @@ -243,7 +271,10 @@ 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, notify_url=None): +def build_wap_pay_url( + out_trade_no, amount, subject, body='', club_id=None, expire_minutes=30, + notify_url=None, _skip_club_gate=False, +): """ 生成 alipay.trade.wap.pay 跳转 URL(GET 方式)。 @@ -252,13 +283,16 @@ def build_wap_pay_url(out_trade_no, amount, subject, body='', club_id=None, expi amount: 订单金额(Decimal 或字符串,单位元) subject: 订单标题 body: 订单描述(可选) - club_id: 预留多 club(暂未使用) + club_id: 俱乐部;命中 ALIPAY_DISABLED_CLUB_IDS 时拒绝发起 expire_minutes: 超时关闭时间(分钟) notify_url: 自定义异步回调地址(可选,默认用 cfg['NOTIFY_URL']) + _skip_club_gate: 内部已校验过时跳过(避免双重校验) 返回: 完整的支付宝跳转 URL,前端 web-view 直接加载即可。 """ + if not _skip_club_gate: + assert_alipay_allowed_for_club(club_id) cfg = get_alipay_config(club_id) biz_content = { @@ -311,6 +345,7 @@ def build_precreate_qr(out_trade_no, amount, subject, body='', club_id=None, exp """ import requests + assert_alipay_allowed_for_club(club_id) cfg = get_alipay_config(club_id) biz_content = { diff --git a/jituan/services/club_payment_channel.py b/jituan/services/club_payment_channel.py index 5dd204d..29d1ba0 100644 --- a/jituan/services/club_payment_channel.py +++ b/jituan/services/club_payment_channel.py @@ -212,12 +212,14 @@ def list_payment_methods_for_miniapp(club_id: Optional[str] = None) -> List[Dict }) if alipay and alipay.app_id and alipay.private_key and alipay.public_key: - methods.append({ - 'code': PAY_METHOD_ALIPAY, - 'label': PAY_METHOD_LABELS[PAY_METHOD_ALIPAY], - 'channel': PAY_CHANNEL_ALIPAY_WAP, - 'sort_order': _sort_for(cid, PAY_CHANNEL_ALIPAY_WAP), - }) + from jituan.services.alipay_pay import is_alipay_enabled_for_club + if is_alipay_enabled_for_club(cid): + methods.append({ + 'code': PAY_METHOD_ALIPAY, + 'label': PAY_METHOD_LABELS[PAY_METHOD_ALIPAY], + 'channel': PAY_CHANNEL_ALIPAY_WAP, + 'sort_order': _sort_for(cid, PAY_CHANNEL_ALIPAY_WAP), + }) methods.sort(key=lambda x: x.get('sort_order', 0)) return methods