From 7a194251be853caaa39710144a9977789e71c887 Mon Sep 17 00:00:00 2001 From: Milky0217 Date: Thu, 7 May 2026 15:19:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=AF=E4=BB=98=E5=AE=9D=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E4=BD=BF=E7=94=A8=E5=8E=9F=E5=A7=8B=E5=80=BC=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E7=94=A8=E8=A1=A8=E5=8D=95=E8=87=AA=E5=8A=A8=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复签名源:使用原始参数值(不进行 URL 编码) - 改用 HTML 表单自动提交方式代替服务器端 API 调用 - 避免 nginx 代理大响应体的问题 --- src/handlers/payment.rs | 110 ++++++++++++---------------------------- 1 file changed, 33 insertions(+), 77 deletions(-) diff --git a/src/handlers/payment.rs b/src/handlers/payment.rs index 35de385..d142ced 100644 --- a/src/handlers/payment.rs +++ b/src/handlers/payment.rs @@ -128,9 +128,9 @@ fn rsa2_verify(content: &str, sign: &str, public_key_pem: &str) -> Result Result { - let client = reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(30)) - .build() - .map_err(|e| format!("HTTP 客户端创建失败: {}", e))?; - let biz_content = serde_json::json!({ "out_trade_no": out_trade_no, "total_amount": total_amount, @@ -166,79 +161,44 @@ async fn call_alipay_trade_page_pay( params.insert("notify_url", notify_url); params.insert("return_url", return_url); - // 构造待签名串 + // 构造待签名串(注意:签名源使用原始值,不做 URL 编码) let sign_source: String = params .iter() - .map(|(k, v)| format!("{}={}", k, urlencoding(v))) + .map(|(k, v)| format!("{}={}", k, *v)) .collect::>() .join("&"); let sign = rsa2_sign(&sign_source, &config.private_key)?; - // 构建 POST body - let query: String = params - .iter() - .map(|(k, v)| format!("{}={}", urlencoding(k), urlencoding(v))) - .chain(std::iter::once(format!("sign={}", urlencoding(&sign)))) - .collect::>() - .join("&"); - - let resp = client - .post(&config.gateway) - .header("Content-Type", "application/x-www-form-urlencoded") - .body(query) - .send() - .await - .map_err(|e| format!("请求支付宝失败: {}", e))?; - - let body = resp.text().await.map_err(|e| format!("读取响应失败: {}", e))?; - - // 支付宝返回格式: alipay_trade_page_pay_response={...}&sign=xxx - let parts: Vec<&str> = body.splitn(2, "&sign=").collect(); - if parts.len() != 2 { - // 如果直接返回 HTML 表单(沙箱环境可能直接返回表单),直接返回 - if body.contains(""#, + urlencoding(k), + urlencoding(v) )); } + form_fields.push_str(&format!( + r#""#, + urlencoding(&sign) + )); - let json_str = parts[0] - .strip_prefix("alipay_trade_page_pay_response=") - .unwrap_or(parts[0]); + let html = format!( + r##" + +正在跳转支付宝... + +
+ {} +
+ + +"##, + config.gateway, form_fields + ); - let sign_from_alipay = parts[1]; - - if !rsa2_verify(json_str, sign_from_alipay, &config.alipay_public_key)? { - return Err("支付宝响应验签失败".to_string()); - } - - let json: serde_json::Value = - serde_json::from_str(json_str).map_err(|e| format!("JSON 解析失败: {}", e))?; - - if json.get("code").and_then(|v| v.as_str()) != Some("10000") { - let sub_msg = json - .get("sub_msg") - .and_then(|v| v.as_str()) - .unwrap_or(""); - return Err(format!( - "支付宝接口错误: {} - {}", - json.get("msg") - .and_then(|v| v.as_str()) - .unwrap_or("未知"), - sub_msg - )); - } - - let form_html = json - .get("form_html") - .and_then(|v| v.as_str()) - .ok_or_else(|| "响应中缺少 form_html".to_string())?; - - Ok(form_html.to_string()) + Ok(html) } // ===== 提取 JWT token ===== @@ -663,16 +623,14 @@ function confirmMockPay() {{ let total_amount_str = format!("{:.2}", pkg.amount as f64 / 100.0); - match call_alipay_trade_page_pay( + match build_alipay_form_html( &config, &order_no, &total_amount_str, pkg.display_name, ¬ify_url, &return_url, - ) - .await - { + ) { Ok(form_html) => Ok(HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(form_html)), @@ -764,16 +722,14 @@ function confirmMockPay() {{ let total_amount_str = format!("{:.2}", pkg.amount as f64 / 100.0); - match call_alipay_trade_page_pay( + match build_alipay_form_html( &config, &query.order_no, &total_amount_str, pkg.display_name, ¬ify_url, &return_url, - ) - .await - { + ) { Ok(form_html) => Ok(HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(form_html)),