fix: Alipay回调手动解析表单支持GBK/GB2312编码回退

This commit is contained in:
2026-06-15 11:17:43 +08:00
parent 776d318330
commit ac49ccb552
3 changed files with 79 additions and 2 deletions

1
Cargo.lock generated
View File

@@ -2341,6 +2341,7 @@ dependencies = [
"base64",
"chrono",
"dotenvy",
"encoding_rs",
"include_dir",
"jsonwebtoken",
"openssl",

View File

@@ -27,6 +27,7 @@ base64 = "0.22"
rsa = { version = "0.9", features = ["pem", "sha2"] }
pkcs8 = "0.10"
sha2 = "0.10"
encoding_rs = "0.8"
[dev-dependencies]
tokio = { version = "1", features = ["full"] }

View File

@@ -800,12 +800,86 @@ pub async fn alipay_pay_page(
// ===== Handler: POST /payment/notify — 支付宝异步回调 =====
/// 解析 x-www-form-urlencoded 请求体,支持 UTF-8 和 GBK/GB2312 编码回退
fn parse_alipay_form(body: &[u8]) -> BTreeMap<String, String> {
use std::collections::BTreeMap;
// 先尝试 UTF-8失败则回退到 GBK/GB2312
let body_str = match String::from_utf8(body.to_vec()) {
Ok(s) => s,
Err(_) => {
// GBK/GB2312 回退
if let Some(enc) = encoding_rs::Encoding::for_label(b"gbk") {
let (s, _enc, had_errors) = enc.decode(body);
if had_errors {
tracing::warn!("支付宝回调 GBK 解码有部分字节失败");
} else {
tracing::info!("支付宝回调使用 GBK 编码解码成功");
}
s.into_owned()
} else {
// 最后兜底lossy UTF-8
tracing::warn!("支付宝回调编码未知,使用 lossy UTF-8 兜底");
String::from_utf8_lossy(body).into_owned()
}
}
};
if body_str.is_empty() && !body.is_empty() {
tracing::warn!("支付宝回调解析结果为空(原始 {} 字节)", body.len());
}
// 手动解析 x-www-form-urlencoded兼容各种 charset
let mut map = BTreeMap::new();
for pair in body_str.split('&') {
if let Some((k, v)) = pair.split_once('=') {
let key = urlencoding(k);
let val = urlencoding(v);
map.insert(key, val);
}
}
map
}
/// 手动 URL 解码percent-decoding
fn urlencoding(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.bytes().peekable();
let mut had_escape = false;
while let Some(b) = chars.next() {
if b == b'%' {
had_escape = true;
let hi = chars.next().and_then(|c| hex_val(c));
let lo = chars.next().and_then(|c| hex_val(c));
if let (Some(h), Some(l)) = (hi, lo) {
result.push((h << 4 | l) as char);
} else {
result.push('%');
}
} else if b == b'+' && had_escape {
result.push(' ');
} else {
result.push(b as char);
}
}
result
}
fn hex_val(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
#[post("/payment/notify")]
pub async fn alipay_notify(
pool: web::Data<PgPool>,
body: web::Form<BTreeMap<String, String>>,
body_bytes: web::Bytes,
) -> HttpResponse {
let body = body.into_inner();
let body = parse_alipay_form(&body_bytes);
let out_trade_no = body.get("out_trade_no").cloned().unwrap_or_default();
let trade_status = body.get("trade_status").cloned().unwrap_or_default();
@@ -855,6 +929,7 @@ pub async fn alipay_notify(
}
}
// ===== Handler: GET /payment/success — 支付成功页面 =====
#[derive(Debug, Deserialize)]