测试转账通过,最后调整
This commit is contained in:
parent
b552ac56b0
commit
a25b774521
Binary file not shown.
Binary file not shown.
@ -235,6 +235,26 @@ def _parse_response(raw, response_cls):
|
||||
return response
|
||||
|
||||
|
||||
def _provider_response_from_exception(exc: Exception) -> dict | None:
|
||||
message = str(exc)
|
||||
json_start = message.find("{")
|
||||
if json_start < 0:
|
||||
return None
|
||||
try:
|
||||
payload = json.loads(message[json_start:])
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return next((
|
||||
value for key, value in payload.items()
|
||||
if key.endswith("_response") and isinstance(value, dict)
|
||||
), None)
|
||||
|
||||
|
||||
def _unverified_success_response(exc: Exception) -> bool:
|
||||
response = _provider_response_from_exception(exc)
|
||||
return bool(response and str(response.get("code")) == "10000" and not response.get("sub_code"))
|
||||
|
||||
|
||||
def _provider_error_from_exception(exc: Exception) -> AlipayTransferError | None:
|
||||
"""Recover Alipay's structured business error from old SDK exceptions.
|
||||
|
||||
@ -244,20 +264,14 @@ def _provider_error_from_exception(exc: Exception) -> AlipayTransferError | None
|
||||
sub-code/message instead of treating a deterministic rejection as an
|
||||
ambiguous network failure.
|
||||
"""
|
||||
message = str(exc)
|
||||
json_start = message.find("{")
|
||||
if json_start < 0:
|
||||
return None
|
||||
try:
|
||||
payload = json.loads(message[json_start:])
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
response = next((
|
||||
value for key, value in payload.items()
|
||||
if key.endswith("_response") and isinstance(value, dict)
|
||||
), None)
|
||||
response = _provider_response_from_exception(exc)
|
||||
if not response or not response.get("code"):
|
||||
return None
|
||||
# A code=10000 envelope is not a provider rejection. The SDK may still
|
||||
# raise because the response signature could not be verified; in that
|
||||
# case the transfer result is uncertain and must remain pending.
|
||||
if str(response.get("code")) == "10000" and not response.get("sub_code"):
|
||||
return None
|
||||
return AlipayTransferError(
|
||||
response.get("sub_msg") or response.get("msg") or "支付宝请求失败",
|
||||
response.get("sub_code") or response.get("code") or "ALIPAY_ERROR",
|
||||
@ -309,6 +323,15 @@ def transfer(*, out_biz_no: str, amount_fen: int, account: str, payee_name: str,
|
||||
except Exception as exc:
|
||||
if isinstance(exc, AlipayTransferError):
|
||||
raise
|
||||
if _unverified_success_response(exc):
|
||||
logger.error(
|
||||
"Alipay returned a success-code envelope, but the SDK could not verify/parse it; transfer outcome is unconfirmed"
|
||||
)
|
||||
raise AlipayTransferError(
|
||||
"支付宝返回成功格式响应,但本地未能验证响应;转账结果未确认,请查询支付宝流水,勿重复发起",
|
||||
"ALIPAY_RESPONSE_UNVERIFIED",
|
||||
ambiguous=True,
|
||||
) from exc
|
||||
provider_error = _provider_error_from_exception(exc)
|
||||
if provider_error:
|
||||
logger.error(
|
||||
@ -342,6 +365,12 @@ def query(*, out_biz_no: str) -> dict:
|
||||
except Exception as exc:
|
||||
if isinstance(exc, AlipayTransferError):
|
||||
raise
|
||||
if _unverified_success_response(exc):
|
||||
raise AlipayTransferError(
|
||||
"支付宝查询返回成功格式响应,但本地未能验证响应;请稍后再次查询",
|
||||
"ALIPAY_RESPONSE_UNVERIFIED",
|
||||
ambiguous=True,
|
||||
) from exc
|
||||
provider_error = _provider_error_from_exception(exc)
|
||||
if provider_error:
|
||||
raise provider_error from exc
|
||||
|
||||
@ -3661,6 +3661,8 @@ def _alipay_error_detail(exc: AlipayTransferError, *, querying: bool = False) ->
|
||||
return "支付宝根证书序列号无效,请检查证书模式配置后重试"
|
||||
if exc.code == "TRANSFER_SCENE_REPORT_INFO_ILLEGAL":
|
||||
return "支付宝转账失败(TRANSFER_SCENE_REPORT_INFO_ILLEGAL):佣金报酬场景必须上报“佣金报酬说明”,请确认服务器已部署最新代码并重启后端"
|
||||
if exc.code == "ALIPAY_RESPONSE_UNVERIFIED":
|
||||
return "支付宝已返回成功格式响应,但本地未能验证转账结果;请先查询支付宝流水,勿重复发起转账"
|
||||
if exc.ambiguous:
|
||||
return "支付宝请求超时,结果暂不明确;请稍后查询订单状态,勿重复创建转账"
|
||||
action = "查询" if querying else "转账"
|
||||
@ -3696,6 +3698,13 @@ def _perform_alipay_refund(db: Session, record, kind: str, user: AdminUser, paye
|
||||
if recipient:
|
||||
recipient.amount_fen = order.amount_fen
|
||||
return order.alipay_trade_no
|
||||
# Older builds incorrectly treated a valid Alipay code=10000 response as
|
||||
# a deterministic failure. Reconcile those rows before allowing any new
|
||||
# out_biz_no, otherwise a retry could pay the recipient twice.
|
||||
if order and order.status == "failed" and order.error_code == "10000":
|
||||
order.status = "pending"
|
||||
order.error_message = "旧版本未能确认支付宝响应验签结果,正在查询原转账单据;请勿重复发起"
|
||||
db.commit()
|
||||
if order and order.status == "pending":
|
||||
try:
|
||||
from .alipay_provider import query
|
||||
@ -3718,6 +3727,7 @@ def _perform_alipay_refund(db: Session, record, kind: str, user: AdminUser, paye
|
||||
return trade_no
|
||||
if provider_status in {"FAIL", "REFUND"}:
|
||||
order.status = "failed"
|
||||
order.error_code = ""
|
||||
order.error_message = str(result.get("fail_reason") or "支付宝确认转账失败")[:500]
|
||||
db.commit()
|
||||
raise HTTPException(status_code=409, detail="支付宝已确认本次转账失败;确认原因后可再次提交")
|
||||
|
||||
Binary file not shown.
@ -78,6 +78,42 @@ def test_old_sdk_error_envelope_is_recovered_from_response_exception():
|
||||
assert error.ambiguous is False
|
||||
|
||||
|
||||
def test_success_code_envelope_is_not_misreported_as_provider_rejection():
|
||||
raw = (
|
||||
'[request-id]response sign verify failed. '
|
||||
'{"alipay_fund_trans_uni_transfer_response":{'
|
||||
'"code":"10000","msg":"Success","out_biz_no":"RF004",'
|
||||
'"order_id":"ALI004","status":"SUCCESS"},"sign":"signature"}'
|
||||
)
|
||||
|
||||
assert alipay_provider._provider_error_from_exception(RuntimeError(raw)) is None
|
||||
assert alipay_provider._unverified_success_response(RuntimeError(raw)) is True
|
||||
|
||||
|
||||
def test_transfer_keeps_unverified_success_envelope_ambiguous(monkeypatch):
|
||||
class RaisingClient:
|
||||
def execute(self, _request):
|
||||
raise RuntimeError(
|
||||
'[request-id]response sign verify failed. '
|
||||
'{"alipay_fund_trans_uni_transfer_response":{'
|
||||
'"code":"10000","msg":"Success","out_biz_no":"RF005",'
|
||||
'"order_id":"ALI005","status":"SUCCESS"},"sign":"signature"}'
|
||||
)
|
||||
|
||||
monkeypatch.setattr(alipay_provider, "_client", lambda: RaisingClient())
|
||||
monkeypatch.setattr(alipay_provider.settings, "alipay_transfer_scene_name", "佣金报酬")
|
||||
|
||||
with pytest.raises(alipay_provider.AlipayTransferError) as error:
|
||||
alipay_provider.transfer(
|
||||
out_biz_no="RF005", amount_fen=300, account="buyer@example.com",
|
||||
payee_name="测试收款人", order_title="测试返款",
|
||||
)
|
||||
|
||||
assert error.value.code == "ALIPAY_RESPONSE_UNVERIFIED"
|
||||
assert error.value.ambiguous is True
|
||||
assert "勿重复发起" in str(error.value)
|
||||
|
||||
|
||||
def test_query_parses_processing_status(monkeypatch):
|
||||
client = CapturingClient({
|
||||
"code": "10000",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user