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::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 sub_merchant_id: Option<String>,
#[serde(flatten)]
pub others: HashMap<String, String>,
}
pub struct AlipayNotify {
cfg: Arc<AlipayConfig>,
mode: crate::config::Mode,
}
impl AlipayNotify {
pub fn new(cfg: Arc<AlipayConfig>, mode: crate::config::Mode) -> Self {
Self { cfg, mode }
}
pub fn verify_notify(
&self,
params: &HashMap<String, String>,
) -> Result<AlipayNotifyData, PayError> {
let sign = params
.get("sign")
.ok_or(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 pubkey = &self.cfg.alipay_public_key_pem;
let verified = rsa_verify_sha256_pem(pubkey, &content, sign)
.map_err(|e| PayError::Crypto(format!("rsa verify error: {}", e)))?;
if !verified {
return Err(PayError::Other(
"alipay notify signature invalid".to_string(),
));
}
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();
let sub_merchant_id = params.get("sub_merchant_id").cloned();
if trade_status != "TRADE_SUCCESS" && trade_status != "TRADE_FINISHED" {
return Err(PayError::Other(format!(
"trade_status not success: {}",
trade_status
)));
}
if let crate::config::Mode::Service = self.mode {
if let Some(cfg_sub) = &self.cfg.sub_merchant_id {
if let Some(notify_sub) = &sub_merchant_id {
if notify_sub != cfg_sub {
return Err(PayError::Other(format!(
"sub_merchant_id mismatch notify={} cfg={}",
notify_sub, cfg_sub
)));
}
}
}
}
let mut others = HashMap::new();
for (k, v) in params.iter() {
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,
sub_merchant_id,
others,
})
}
pub fn success_response(&self) -> &'static str {
"success"
}
}