测试转账通过,最后调整
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
|
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:
|
def _provider_error_from_exception(exc: Exception) -> AlipayTransferError | None:
|
||||||
"""Recover Alipay's structured business error from old SDK exceptions.
|
"""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
|
sub-code/message instead of treating a deterministic rejection as an
|
||||||
ambiguous network failure.
|
ambiguous network failure.
|
||||||
"""
|
"""
|
||||||
message = str(exc)
|
response = _provider_response_from_exception(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)
|
|
||||||
if not response or not response.get("code"):
|
if not response or not response.get("code"):
|
||||||
return None
|
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(
|
return AlipayTransferError(
|
||||||
response.get("sub_msg") or response.get("msg") or "支付宝请求失败",
|
response.get("sub_msg") or response.get("msg") or "支付宝请求失败",
|
||||||
response.get("sub_code") or response.get("code") or "ALIPAY_ERROR",
|
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:
|
except Exception as exc:
|
||||||
if isinstance(exc, AlipayTransferError):
|
if isinstance(exc, AlipayTransferError):
|
||||||
raise
|
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)
|
provider_error = _provider_error_from_exception(exc)
|
||||||
if provider_error:
|
if provider_error:
|
||||||
logger.error(
|
logger.error(
|
||||||
@ -342,6 +365,12 @@ def query(*, out_biz_no: str) -> dict:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if isinstance(exc, AlipayTransferError):
|
if isinstance(exc, AlipayTransferError):
|
||||||
raise
|
raise
|
||||||
|
if _unverified_success_response(exc):
|
||||||
|
raise AlipayTransferError(
|
||||||
|
"支付宝查询返回成功格式响应,但本地未能验证响应;请稍后再次查询",
|
||||||
|
"ALIPAY_RESPONSE_UNVERIFIED",
|
||||||
|
ambiguous=True,
|
||||||
|
) from exc
|
||||||
provider_error = _provider_error_from_exception(exc)
|
provider_error = _provider_error_from_exception(exc)
|
||||||
if provider_error:
|
if provider_error:
|
||||||
raise provider_error from exc
|
raise provider_error from exc
|
||||||
|
|||||||
@ -3661,6 +3661,8 @@ def _alipay_error_detail(exc: AlipayTransferError, *, querying: bool = False) ->
|
|||||||
return "支付宝根证书序列号无效,请检查证书模式配置后重试"
|
return "支付宝根证书序列号无效,请检查证书模式配置后重试"
|
||||||
if exc.code == "TRANSFER_SCENE_REPORT_INFO_ILLEGAL":
|
if exc.code == "TRANSFER_SCENE_REPORT_INFO_ILLEGAL":
|
||||||
return "支付宝转账失败(TRANSFER_SCENE_REPORT_INFO_ILLEGAL):佣金报酬场景必须上报“佣金报酬说明”,请确认服务器已部署最新代码并重启后端"
|
return "支付宝转账失败(TRANSFER_SCENE_REPORT_INFO_ILLEGAL):佣金报酬场景必须上报“佣金报酬说明”,请确认服务器已部署最新代码并重启后端"
|
||||||
|
if exc.code == "ALIPAY_RESPONSE_UNVERIFIED":
|
||||||
|
return "支付宝已返回成功格式响应,但本地未能验证转账结果;请先查询支付宝流水,勿重复发起转账"
|
||||||
if exc.ambiguous:
|
if exc.ambiguous:
|
||||||
return "支付宝请求超时,结果暂不明确;请稍后查询订单状态,勿重复创建转账"
|
return "支付宝请求超时,结果暂不明确;请稍后查询订单状态,勿重复创建转账"
|
||||||
action = "查询" if querying else "转账"
|
action = "查询" if querying else "转账"
|
||||||
@ -3696,6 +3698,13 @@ def _perform_alipay_refund(db: Session, record, kind: str, user: AdminUser, paye
|
|||||||
if recipient:
|
if recipient:
|
||||||
recipient.amount_fen = order.amount_fen
|
recipient.amount_fen = order.amount_fen
|
||||||
return order.alipay_trade_no
|
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":
|
if order and order.status == "pending":
|
||||||
try:
|
try:
|
||||||
from .alipay_provider import query
|
from .alipay_provider import query
|
||||||
@ -3718,6 +3727,7 @@ def _perform_alipay_refund(db: Session, record, kind: str, user: AdminUser, paye
|
|||||||
return trade_no
|
return trade_no
|
||||||
if provider_status in {"FAIL", "REFUND"}:
|
if provider_status in {"FAIL", "REFUND"}:
|
||||||
order.status = "failed"
|
order.status = "failed"
|
||||||
|
order.error_code = ""
|
||||||
order.error_message = str(result.get("fail_reason") or "支付宝确认转账失败")[:500]
|
order.error_message = str(result.get("fail_reason") or "支付宝确认转账失败")[:500]
|
||||||
db.commit()
|
db.commit()
|
||||||
raise HTTPException(status_code=409, detail="支付宝已确认本次转账失败;确认原因后可再次提交")
|
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
|
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):
|
def test_query_parses_processing_status(monkeypatch):
|
||||||
client = CapturingClient({
|
client = CapturingClient({
|
||||||
"code": "10000",
|
"code": "10000",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user