fix(huya): 支付二维码改用收银台页面里的扫码内容 qr.alipay.com
真机复现(2026-10-15):上一版把二维码内容放成了**收银台页面地址**
(excashier.alipay.com/standard/auth.htm?payOrderId=…),那是 PC 版页面
(页面里写死 qrcodeWidth=150)。有些安卓机是用系统相机/浏览器打开的,就停在
这张 PC 页上——只显示左上角一个很小的二维码,不触发付款。
收银台页面里真正的扫码内容是隐藏字段 `J_qrCode`:
<input name="qrCode" value="https://qr.alipay.com/upx03110ghym9vm37sx7255c" id="J_qrCode"/>
- huya_pay_link 新增 PayTargets / resolve_pay_targets() / extract_pay_qr_code():
跟随网关 302 取收银台页面并解析出 qr.alipay.com 扫码码(约 40 字符)。
- 出码时写入 pay_qr_code,保留 pay_cashier_url 兜底;拿不到扫码码会打 warning。
- 前端二维码优先级:pay_qr_code → pay_cashier_url → 本站短链 → 原始地址。
验证:全量 pytest 772 passed / 6 skipped;用真机那张收银台页面跑解析得到
https://qr.alipay.com/upx03110ghym9vm37sx7255c;typecheck / eslint / ruff 通过。
This commit is contained in:
@@ -16,7 +16,7 @@ from core.huya.frame_decoder import decode_struct_bytes
|
||||
from core.huya.http_client import HuyaHttpClient
|
||||
from core.huya.shop_structs import ShopRawRsp
|
||||
from web.backend.models import HuyaAccount
|
||||
from web.backend.services.huya_pay_link import pay_qr_path
|
||||
from web.backend.services.huya_pay_link import PayTargets, pay_qr_path
|
||||
from web.backend.services.huya_runner import HuyaBatchRunner
|
||||
|
||||
UID = 1199668595203
|
||||
@@ -382,11 +382,12 @@ def test_create_recharge_order_confirms_elite_open_after_payment(db, monkeypatch
|
||||
"web.backend.services.huya_runner_recharge.run_open_flow",
|
||||
lambda *args, **kwargs: outcome,
|
||||
)
|
||||
# 二维码内容优先用支付宝收银台直达地址(真机:网关 302 后的落点)
|
||||
# 二维码内容优先用收银台页面里的扫码内容(真机:支付宝扫码支付码)
|
||||
monkeypatch.setattr(
|
||||
"web.backend.services.huya_runner_recharge.resolve_pay_entry_url",
|
||||
lambda _pay_url, **_kwargs: (
|
||||
"https://excashier.alipay.com/standard/auth.htm?payOrderId=x.25"
|
||||
"web.backend.services.huya_runner_recharge.resolve_pay_targets",
|
||||
lambda _pay_url, **_kwargs: PayTargets(
|
||||
cashier_url="https://excashier.alipay.com/standard/auth.htm?payOrderId=x.25",
|
||||
qr_code="https://qr.alipay.com/upx03110ghym9vm37sx7255c",
|
||||
),
|
||||
)
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
@@ -410,7 +411,9 @@ def test_create_recharge_order_confirms_elite_open_after_payment(db, monkeypatch
|
||||
assert result["post_payment_points"] == 280
|
||||
# 二维码内容给前端两条短地址(原始支付地址 1626 字符,扫码扫不动)
|
||||
assert result["pay_qr_path"] == pay_qr_path(587)
|
||||
# 优先用支付宝收银台直达地址(公网域名,localhost 下客户也扫得开)
|
||||
# 优先用收银台页面里的扫码内容(支付宝 App 扫码即付款)
|
||||
assert result["pay_qr_code"] == "https://qr.alipay.com/upx03110ghym9vm37sx7255c"
|
||||
# 收银台直达地址作为兜底保留(PC 打开就是这个页面)
|
||||
assert result["pay_cashier_url"].startswith("https://excashier.alipay.com/")
|
||||
assert account.elite_opened is True
|
||||
assert account.elite_expire_time == 1798732799
|
||||
|
||||
@@ -21,10 +21,13 @@ from web.backend.routers.huya.pay import redirect_huya_pay
|
||||
from web.backend.services import huya_pay_link
|
||||
from web.backend.services.huya_pay_link import (
|
||||
PAY_PATH_PREFIX,
|
||||
PayTargets,
|
||||
extract_pay_qr_code,
|
||||
is_alipay_url,
|
||||
pay_qr_path,
|
||||
pay_token,
|
||||
resolve_pay_entry_url,
|
||||
resolve_pay_targets,
|
||||
verify_pay_token,
|
||||
)
|
||||
|
||||
@@ -39,6 +42,17 @@ CASHIER_URL = (
|
||||
"https://excashier.alipay.com/standard/auth.htm"
|
||||
"?payOrderId=6cb86f1d259642ac843522b85dab8a52.25"
|
||||
)
|
||||
# 收银台页面里真正的扫码内容(真机页面原样片段)
|
||||
QR_CODE_LITERAL = "https://qr.alipay.com/upx03110ghym9vm37sx7255c"
|
||||
CASHIER_HTML = (
|
||||
'<input name="timeoutUrl" type="hidden" value="" id="J_timeoutUrl"/>\n'
|
||||
f'<input name="qrCode" type="hidden" value="{QR_CODE_LITERAL}" id="J_qrCode"/>\n'
|
||||
'<input name="qrImgUrl" type="hidden" '
|
||||
'value="https://mobilecodec.alipay.com/show.htm?code=upx03110ghym9vm37sx7255c" '
|
||||
'id="J_qrImgUrl"/>\n'
|
||||
f'<input name="qrRenewalURL" type="hidden" value="{CASHIER_URL}" id="J_qrRenewalURL"/>\n'
|
||||
'<input type="hidden" name="qrcodeWidth" id="J_qrcodeWidth" value="150"/>'
|
||||
)
|
||||
|
||||
|
||||
def _create_pay_task(db, pay_url: str) -> HuyaTask:
|
||||
@@ -111,24 +125,27 @@ def test_pay_redirect_route_rejects_task_without_pay_url(db):
|
||||
|
||||
|
||||
class _FakeResponse:
|
||||
"""假的 requests 响应:只需要 ``url`` 和 ``close()``。"""
|
||||
"""假的 requests 响应:只需要 ``url`` / ``text`` / ``close()``。"""
|
||||
|
||||
def __init__(self, url: str):
|
||||
def __init__(self, url: str, text: str = ""):
|
||||
self.url = url
|
||||
self.text = text
|
||||
self.closed = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
def _patch_get(monkeypatch, *, final_url: str | None = None, error=None):
|
||||
def _patch_get(
|
||||
monkeypatch, *, final_url: str | None = None, body: str = "", error=None
|
||||
):
|
||||
calls: list[dict] = []
|
||||
|
||||
def fake_get(url, **kwargs):
|
||||
calls.append({"url": url, **kwargs})
|
||||
if error is not None:
|
||||
raise error
|
||||
return _FakeResponse(final_url or "")
|
||||
return _FakeResponse(final_url or "", body)
|
||||
|
||||
monkeypatch.setattr(huya_pay_link.requests, "get", fake_get)
|
||||
return calls
|
||||
@@ -167,13 +184,58 @@ def test_resolve_pay_entry_url_is_shorter_than_raw_url(monkeypatch):
|
||||
|
||||
resolved = resolve_pay_entry_url(ALIPAY_LONG_URL)
|
||||
|
||||
# 收银台直达地址约 74 字符 → 二维码模块数远低于原始地址
|
||||
# 收银台直达地址约 93 字符 → 二维码模块数远低于原始地址
|
||||
assert len(resolved) < 120
|
||||
assert len(resolved) < len(ALIPAY_LONG_URL)
|
||||
|
||||
|
||||
def test_extract_pay_qr_code_from_cashier_html():
|
||||
"""收银台页面里的 J_qrCode 才是手机该扫的内容(真机抓到的原样片段)。"""
|
||||
assert extract_pay_qr_code(CASHIER_HTML) == QR_CODE_LITERAL
|
||||
|
||||
|
||||
def test_extract_pay_qr_code_ignores_other_alipay_urls():
|
||||
"""只有 qr.alipay.com 才算扫码内容:qrImgUrl(mobilecodec)不算。"""
|
||||
html = (
|
||||
'<input name="qrImgUrl" type="hidden" '
|
||||
'value="https://mobilecodec.alipay.com/show.htm?code=upx03110ghym9vm37sx7255c" '
|
||||
'id="J_qrImgUrl"/>'
|
||||
)
|
||||
|
||||
assert extract_pay_qr_code(html) == ""
|
||||
assert extract_pay_qr_code("") == ""
|
||||
|
||||
|
||||
def test_resolve_pay_targets_returns_cashier_and_qr_code(monkeypatch):
|
||||
_patch_get(monkeypatch, final_url=CASHIER_URL, body=CASHIER_HTML)
|
||||
|
||||
targets = resolve_pay_targets(ALIPAY_LONG_URL)
|
||||
|
||||
assert targets.cashier_url == CASHIER_URL
|
||||
assert targets.qr_code == QR_CODE_LITERAL
|
||||
|
||||
|
||||
def test_resolve_pay_targets_keeps_cashier_when_qr_code_missing(monkeypatch):
|
||||
"""拿不到 J_qrCode(比如停在中间页)时只回落到收银台地址。"""
|
||||
_patch_get(monkeypatch, final_url=CASHIER_URL, body="<html>no qr here</html>")
|
||||
|
||||
targets = resolve_pay_targets(ALIPAY_LONG_URL)
|
||||
|
||||
assert targets.cashier_url == CASHIER_URL
|
||||
assert targets.qr_code == ""
|
||||
|
||||
|
||||
def test_resolve_pay_targets_empty_on_error(monkeypatch):
|
||||
_patch_get(monkeypatch, error=RuntimeError("boom"))
|
||||
|
||||
targets = resolve_pay_targets(ALIPAY_LONG_URL)
|
||||
|
||||
assert targets == PayTargets()
|
||||
|
||||
|
||||
def test_is_alipay_url_matches_only_alipay_domains():
|
||||
assert is_alipay_url(CASHIER_URL) is True
|
||||
assert is_alipay_url(QR_CODE_LITERAL) is True
|
||||
assert is_alipay_url("https://openapi.alipay.com/gateway.do?x=1") is True
|
||||
assert is_alipay_url("https://alipay.com/") is True
|
||||
assert is_alipay_url("https://alipay.com.evil.com/") is False
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
"""开通支付码的**二维码内容**:让手机一定扫得动、打得开。
|
||||
"""开通支付码的**二维码内容**:让手机一定扫得动、打得开、且能直接付款。
|
||||
|
||||
问题(生产实测 2026-10-15):`payOrderSubmitV5` 返回的 `payUrl` 是
|
||||
`https://openapi.alipay.com/gateway.do?...&sign=...`,长度 **1626** 字符。
|
||||
直接把它编成二维码是 version 29+(约 140 个模块),在 150px 的画面上每个模块
|
||||
只有约 1 像素 —— 手机怎么都扫不上。
|
||||
|
||||
两条出路,二维码里按顺序取第一个可用的:
|
||||
二维码里放什么,按顺序取第一个可用的:
|
||||
|
||||
1. **支付宝收银台直达地址**(首选):网关地址 302 两次后落到
|
||||
`https://excashier.alipay.com/standard/auth.htm?payOrderId=...`(约 74 字符),
|
||||
公网域名、支付宝扫一扫直接识别,后端跑在 localhost 时也能发给客户扫。
|
||||
见 `resolve_pay_entry_url`。
|
||||
2. **本站短链** `/api/huya/pay/<task_id>?k=<hmac>`(约 38 字符):手机打开后由
|
||||
后端 302 跳到上面的网关地址。要求「本页域名」手机能访问到(localhost 不行)。
|
||||
1. **支付宝扫码支付码** `https://qr.alipay.com/<code>`(首选,约 40 字符):
|
||||
就是收银台页面里 `J_qrCode` 那个隐藏字段。支付宝 App 扫它直接进付款;普通
|
||||
相机/浏览器扫它是支付宝的手机页面(会拉起支付宝 App)。
|
||||
2. **收银台直达地址** `https://excashier.alipay.com/standard/auth.htm?payOrderId=...`
|
||||
(约 93 字符,见 `resolve_pay_targets`)。⚠️ 这是**PC 版**收银台页面
|
||||
(`qrcodeWidth=150`,左上角一个小二维码):真机反馈有些安卓机扫完是用浏览器
|
||||
打开的,就停在这张 PC 页上不触发付款,所以只作为拿不到 qrCode 时的兜底。
|
||||
3. **本站短链** `/api/huya/pay/<task_id>?k=<hmac>`(约 38 字符):手机打开后由
|
||||
后端 302 跳到网关地址。要求「本页域名」手机能访问到(localhost 不行)。
|
||||
扫码的是没登录的手机,所以路由只校验 HMAC 签名(用 JWT 密钥派生,不额外存库)。
|
||||
"""
|
||||
|
||||
@@ -20,6 +23,8 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import requests
|
||||
@@ -29,12 +34,26 @@ from ..security import SECRET_KEY_TYPED
|
||||
TOKEN_LENGTH = 16
|
||||
PAY_PATH_PREFIX = "/api/huya/pay"
|
||||
|
||||
#: 解析收银台直达地址时冒充浏览器(支付宝对无 UA 的请求会走风控页)。
|
||||
#: 解析收银台页面时冒充浏览器(支付宝对无 UA 的请求会走风控页)。
|
||||
BROWSER_UA = (
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
#: 收银台页面里真正的扫码内容(`<input name="qrCode" ... id="J_qrCode">`)。
|
||||
QR_CODE_HOST = "qr.alipay.com"
|
||||
_QR_CODE_RE = re.compile(r"https://qr\.alipay\.com/[A-Za-z0-9]+")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PayTargets:
|
||||
"""支付码可用的两个地址(取不到就是空串)。"""
|
||||
|
||||
#: 收银台直达地址(PC 打开就是这个页面,也是「复制链接」给电脑用的地址)
|
||||
cashier_url: str = ""
|
||||
#: 收银台页面里真正的扫码内容 https://qr.alipay.com/xxx(手机扫码用这个)
|
||||
qr_code: str = ""
|
||||
|
||||
|
||||
def pay_token(task_id: int) -> str:
|
||||
"""按任务号派生短链签名(16 位十六进制,48 bit 抗枚举)。"""
|
||||
@@ -59,25 +78,42 @@ def pay_qr_path(task_id: int) -> str:
|
||||
|
||||
|
||||
def is_alipay_url(url: str) -> bool:
|
||||
"""是否支付宝域名(网关/收银台)。"""
|
||||
"""是否支付宝域名(网关/收银台/扫码短链)。"""
|
||||
host = (urlsplit(str(url or "")).hostname or "").lower()
|
||||
return bool(host) and (host == "alipay.com" or host.endswith(".alipay.com"))
|
||||
|
||||
|
||||
def resolve_pay_entry_url(pay_url: str, *, timeout: float = 6.0) -> str:
|
||||
"""跟随支付宝网关的 302,取**收银台直达地址**(约 74 字符,公网可达)。
|
||||
def extract_pay_qr_code(html: str) -> str:
|
||||
"""从收银台页面里取真正的扫码内容 `https://qr.alipay.com/xxx`。
|
||||
|
||||
真机取到的页面里有(`id="J_qrCode"` 那个隐藏 input):
|
||||
|
||||
<input name="qrCode" type="hidden" value="https://qr.alipay.com/upx03110ghym9vm37sx7255c" id="J_qrCode"/>
|
||||
|
||||
取不到就返回空串(调用方回落到收银台地址 / 本站短链)。
|
||||
"""
|
||||
match = _QR_CODE_RE.search(html or "")
|
||||
if match is None:
|
||||
return ""
|
||||
code = match.group(0)
|
||||
return code if is_alipay_url(code) else ""
|
||||
|
||||
|
||||
def resolve_pay_targets(pay_url: str, *, timeout: float = 6.0) -> PayTargets:
|
||||
"""跟随支付宝网关的 302,取**收银台页面**和页面里**真正的扫码内容**。
|
||||
|
||||
真机实测(2026-10-15):`openapi.alipay.com/gateway.do?...` 会 302 到
|
||||
`unitradeprod.alipay.com/appAssign.htm?...` 再 302 到
|
||||
`https://excashier.alipay.com/standard/auth.htm?payOrderId=<id>`。
|
||||
这个地址编二维码又小又好扫,且不依赖本服务的域名能不能被手机访问。
|
||||
`https://excashier.alipay.com/standard/auth.htm?payOrderId=<id>`(约 93 字符),
|
||||
页面里再带一个 `https://qr.alipay.com/<code>`(约 40 字符)——后者才是
|
||||
「扫码支付」该放进二维码的内容(见模块头说明)。
|
||||
|
||||
解析失败、超时、或最终落到非支付宝域名都返回空串 —— 调用方回落到本站短链,
|
||||
绝不能让解析失败影响支付码本身。
|
||||
解析失败、超时、最终落到非支付宝域名,都返回空 `PayTargets`:调用方回落到
|
||||
本站短链,绝不能让解析失败影响支付码本身。
|
||||
"""
|
||||
url = str(pay_url or "").strip()
|
||||
if not url.startswith(("http://", "https://")):
|
||||
return ""
|
||||
return PayTargets()
|
||||
try:
|
||||
response = requests.get(
|
||||
url,
|
||||
@@ -87,10 +123,22 @@ def resolve_pay_entry_url(pay_url: str, *, timeout: float = 6.0) -> str:
|
||||
)
|
||||
try:
|
||||
final_url = str(getattr(response, "url", "") or "").strip()
|
||||
html = (
|
||||
str(getattr(response, "text", "") or "")
|
||||
if is_alipay_url(final_url)
|
||||
else ""
|
||||
)
|
||||
finally:
|
||||
close = getattr(response, "close", None)
|
||||
if callable(close):
|
||||
close()
|
||||
except Exception: # noqa: BLE001 - 解析失败不能打断支付码生成
|
||||
return ""
|
||||
return final_url if is_alipay_url(final_url) else ""
|
||||
return PayTargets()
|
||||
if not is_alipay_url(final_url):
|
||||
return PayTargets()
|
||||
return PayTargets(cashier_url=final_url, qr_code=extract_pay_qr_code(html))
|
||||
|
||||
|
||||
def resolve_pay_entry_url(pay_url: str, *, timeout: float = 6.0) -> str:
|
||||
"""只要收银台直达地址(约 93 字符);解析不到返回空串。"""
|
||||
return resolve_pay_targets(pay_url, timeout=timeout).cashier_url
|
||||
|
||||
@@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
|
||||
from core.huya.wss_client import POINTS_REDEEM_GOODS_NUM, SHOP_APP_SCENE
|
||||
|
||||
from ..models import HuyaAccount, HuyaRechargeGoodsSnapshot, HuyaTask
|
||||
from .huya_pay_link import pay_qr_path, resolve_pay_entry_url
|
||||
from .huya_pay_link import pay_qr_path, resolve_pay_targets
|
||||
from .huya_shop_flow import run_open_flow
|
||||
|
||||
HUYA_RECHARGE_ACT_ID = 25135
|
||||
@@ -528,14 +528,21 @@ class RechargeMixin:
|
||||
order_result = outcome.order_raw
|
||||
pay_result = outcome.pay_raw
|
||||
amount = outcome.amount
|
||||
# 二维码内容:优先支付宝收银台直达地址(约 74 字符、公网可达,手机一定打得开),
|
||||
# 解析不到再回落到本站短链(要求本页域名手机能访问)。
|
||||
pay_cashier_url = resolve_pay_entry_url(outcome.pay_url)
|
||||
if pay_cashier_url:
|
||||
self._push_log("info", f"[{uid}] 支付二维码改用支付宝收银台直达地址")
|
||||
# 二维码内容优先级(见 huya_pay_link 模块头):
|
||||
# ① 支付宝扫码支付码 https://qr.alipay.com/xxx —— 手机扫码直接进付款;
|
||||
# ② 收银台直达地址(PC 版页面,拿不到 ① 时才用,有些安卓机扫开只看到小二维码);
|
||||
# ③ 本站短链(要求本页域名手机能访问)。
|
||||
pay_targets = resolve_pay_targets(outcome.pay_url)
|
||||
if pay_targets.qr_code:
|
||||
self._push_log("info", f"[{uid}] 支付二维码使用支付宝扫码支付码")
|
||||
elif pay_targets.cashier_url:
|
||||
self._push_log(
|
||||
"warning",
|
||||
f"[{uid}] 未取到支付宝扫码支付码,二维码回落到收银台地址(部分手机扫开不触发付款)",
|
||||
)
|
||||
else:
|
||||
self._push_log(
|
||||
"warning", f"[{uid}] 未解析到支付宝收银台直达地址,二维码回落到本站短链"
|
||||
"warning", f"[{uid}] 未解析到支付宝收银台地址,二维码回落到本站短链"
|
||||
)
|
||||
result = {
|
||||
"spu_id": spu_id,
|
||||
@@ -552,9 +559,11 @@ class RechargeMixin:
|
||||
"pay_order_id": outcome.pay_order_id,
|
||||
"pay_url": outcome.pay_url,
|
||||
# 支付地址 1600+ 字符,直接编成二维码扫不动(真机反馈);给前端两条更短的路径:
|
||||
# pay_cashier_url = 支付宝收银台直达地址(公网域名,任意手机可打开);
|
||||
# pay_qr_code = 收银台页面里的 https://qr.alipay.com/xxx(约 40 字符,扫码即付款);
|
||||
# pay_cashier_url = 收银台直达地址(PC 版页面);
|
||||
# pay_qr_path = 本站短链 /api/huya/pay/<task_id>?k=...(需本页域名手机可达)。
|
||||
"pay_cashier_url": pay_cashier_url,
|
||||
"pay_qr_code": pay_targets.qr_code,
|
||||
"pay_cashier_url": pay_targets.cashier_url,
|
||||
"pay_qr_path": pay_qr_path(task.id) if task is not None else "",
|
||||
# 复用未支付旧单时为那张单的原始信息(限购商品:旧单没付完就下不了新单)
|
||||
"reused_unpaid_order": bool(outcome.reused_order),
|
||||
|
||||
@@ -79,14 +79,17 @@ function qrDoneLabel(task: HuyaTaskItem): string | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码里放**短地址**:支付宝网关地址 1600+ 字符,直接编成二维码有 140+ 个模块,
|
||||
* 手机根本扫不上(生产实测)。优先级:
|
||||
* 1. `pay_cashier_url` —— 支付宝收银台直达地址(约 74 字符、公网域名,任意手机可扫码支付);
|
||||
* 2. `pay_qr_path` —— 本站短链 `/api/huya/pay/<id>?k=<签名>`,手机打开后 302 跳到真实支付地址,
|
||||
* 但要求当前页面域名手机能访问到(localhost 不行);
|
||||
* 3. `pay_url` —— 原始网关地址,兜底。
|
||||
* 二维码里放什么(优先级,见后端 huya_pay_link 模块头):
|
||||
* 1. `pay_qr_code` —— 收银台页面里真正的扫码内容 `https://qr.alipay.com/xxx`(约 40 字符),
|
||||
* 支付宝 App 扫它直接进付款,手机相机扫它也会拉起支付宝;
|
||||
* 2. `pay_cashier_url` —— 收银台直达地址,但那是 **PC 版**页面(真机反馈:有的安卓机
|
||||
* 扫开只看到左上角一个小二维码,不触发付款),只在拿不到 ① 时兜底;
|
||||
* 3. `pay_qr_path` —— 本站短链 `/api/huya/pay/<id>?k=<签名>`,要求本页域名手机能访问(localhost 不行);
|
||||
* 4. `pay_url` —— 原始网关地址 1600+ 字符,二维码太密扫不动,最后兜底。
|
||||
*/
|
||||
function payQrValue(task: HuyaTaskItem): string {
|
||||
const qrCode = resultText(task, 'pay_qr_code');
|
||||
if (qrCode) return qrCode;
|
||||
const cashier = resultText(task, 'pay_cashier_url');
|
||||
if (cashier) return cashier;
|
||||
const path = resultText(task, 'pay_qr_path');
|
||||
|
||||
Reference in New Issue
Block a user