fix: Alipay回调手动解析表单支持GBK/GB2312编码回退
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2341,6 +2341,7 @@ dependencies = [
|
|||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"encoding_rs",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"openssl",
|
"openssl",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ base64 = "0.22"
|
|||||||
rsa = { version = "0.9", features = ["pem", "sha2"] }
|
rsa = { version = "0.9", features = ["pem", "sha2"] }
|
||||||
pkcs8 = "0.10"
|
pkcs8 = "0.10"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
encoding_rs = "0.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|||||||
@@ -800,12 +800,86 @@ pub async fn alipay_pay_page(
|
|||||||
|
|
||||||
// ===== Handler: POST /payment/notify — 支付宝异步回调 =====
|
// ===== 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")]
|
#[post("/payment/notify")]
|
||||||
pub async fn alipay_notify(
|
pub async fn alipay_notify(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
body: web::Form<BTreeMap<String, String>>,
|
body_bytes: web::Bytes,
|
||||||
) -> HttpResponse {
|
) -> 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 out_trade_no = body.get("out_trade_no").cloned().unwrap_or_default();
|
||||||
let trade_status = body.get("trade_status").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 — 支付成功页面 =====
|
// ===== Handler: GET /payment/success — 支付成功页面 =====
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|||||||
Reference in New Issue
Block a user