use crate::config::{AlipayConfig};
use crate::errors::PayError;
use crate::utils::{rsa_verify_sha256_pem};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::sync::Arc;
#[derive(Debug, Serialize, Deserialize)]
pub struct AlipayNotifyData {
pub app_id: String,
pub out_trade_no: String,
pub trade_no: String,
pub trade_status: String,
pub total_amount: String,
pub seller_id: Option<String>,
pub others: HashMap<String, String>,
}
pub struct AlipayNotify {
cfg: Arc<AlipayConfig>,
}
impl AlipayNotify {
pub fn new(cfg: Arc<AlipayConfig>) -> Self {
Self { cfg }
}
pub fn verify_notify(
&self,
params: &HashMap<String, String>,
) -> Result<AlipayNotifyData, PayError> {
let sign = params
.get("sign")
.ok_or_else(|| PayError::Other("missing sign".to_string()))?;
let mut kv: Vec<(&String, &String)> = params
.iter()
.filter(|&(k, _)| k != "sign" && k != "sign_type")
.collect();
kv.sort_by(|a, b| a.0.cmp(b.0));
let content = kv
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<String>>()
.join("&");
let mut pubkey_pem = String::new();
if let Some(cert_path) = &self.cfg.alipay_cert_path {
if let Ok(pem) = fs::read_to_string(cert_path) {
pubkey_pem = pem;
}
}
if pubkey_pem.is_empty() {
pubkey_pem = self.cfg.alipay_public_key.clone().unwrap_or_default();
}
if pubkey_pem.is_empty() {
return Err(PayError::Other("missing alipay public key".into()));
}
let verified = rsa_verify_sha256_pem(&pubkey_pem, &content, sign)
.map_err(|e| PayError::Crypto(format!("rsa verify error: {}", e)))?;
if !verified {
return Err(PayError::Other("alipay notify signature invalid".into()));
}
let app_id = params.get("app_id").cloned().unwrap_or_default();
let out_trade_no = params.get("out_trade_no").cloned().unwrap_or_default();
let trade_no = params.get("trade_no").cloned().unwrap_or_default();
let trade_status = params.get("trade_status").cloned().unwrap_or_default();
let total_amount = params.get("total_amount").cloned().unwrap_or_default();
let seller_id = params.get("seller_id").cloned();
if trade_status != "TRADE_SUCCESS" && trade_status != "TRADE_FINISHED" {
return Err(PayError::Other(format!(
"trade_status not success: {}",
trade_status
)));
}
let mut others = HashMap::new();
for (k, v) in params {
if k != "sign" && k != "sign_type" {
others.insert(k.clone(), v.clone());
}
}
Ok(AlipayNotifyData {
app_id,
out_trade_no,
trade_no,
trade_status,
total_amount,
seller_id,
others,
})
}
pub fn success_response(&self) -> &'static str {
"success"
}
}