use crate::{app_cert_client, error::AlipayResult, AlipayParam, Client, FieldValue};
use openssl::{
base64,
hash::MessageDigest,
pkey::{PKey, Private},
rsa::Rsa,
sign::Signer,
};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::{cell::RefCell, collections::HashMap};
fn get_hour_min_sec(timestamp: u64) -> (i32, i32, i32) {
let hour = ((timestamp % (24 * 3600)) / 3600 + 8) % 24;
let min = (timestamp % 3600) / 60;
let sec = (timestamp % 3600) % 60;
(hour as i32, min as i32, sec as i32)
}
fn get_moth_day(is_leap_year: bool, mut days: i32) -> (i32, i32) {
let p_moth: Vec<i32> = if is_leap_year {
vec![31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
} else {
vec![31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
};
let mut day = 0;
let mut moth = 0;
for i in 0..12 {
let temp = days - p_moth[i];
if temp <= 0 {
moth = i + 1;
day = if temp == 0 { p_moth[i] } else { days };
break;
}
days = temp;
}
(moth as i32, day)
}
fn datetime() -> AlipayResult<String> {
use std::time::SystemTime;
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs();
let days = 24 * 3600;
let four_years = 365 * 3 + 366;
let days = timestamp / days + (if (timestamp % days) != 0 { 1 } else { 0 });
let year_4 = days / four_years;
let mut remain = days % four_years;
let mut year = 1970 + year_4 * 4;
let mut is_leap_year = false;
if 365 <= remain && remain < 365 * 2 {
year += 1;
remain -= 365;
} else if 365 * 2 <= remain && remain < 365 * 3 {
year += 2;
remain -= 365 * 2;
} else if 365 * 3 <= remain {
year += 3;
remain -= 365 * 3;
is_leap_year = true;
}
let (moth, day) = get_moth_day(is_leap_year, remain as i32);
let (h, m, s) = get_hour_min_sec(timestamp);
Ok(format!(
"{}-{:>02}-{:>02} {:>02}:{:>02}:{:>02}",
year, moth, day, h, m, s,
))
}
impl Client {
pub fn new<S: Into<String>>(
app_id: S,
private_key: S,
app_cert_sn: Option<&str>,
alipay_root_cert_sn: Option<&str>,
) -> Client {
let mut params: HashMap<String, String> = HashMap::from([
("app_id".to_owned(), app_id.into()),
("charset".to_owned(), "utf-8".to_owned()),
("sign_type".to_owned(), "RSA2".to_owned()),
("format".to_owned(), "json".to_owned()),
("version".to_owned(), "1.0".to_owned()),
]);
if let Some(cert_sn) = app_cert_sn {
let app_cert_sn = app_cert_client::get_cert_sn_from_content(cert_sn.to_owned())
.unwrap_or(String::from(""));
params.insert("app_cert_sn".to_owned(), app_cert_sn);
}
if let Some(root_cert_sn) = alipay_root_cert_sn {
let alipay_root_cert_sn =
app_cert_client::get_root_cert_sn_from_content(root_cert_sn.to_owned())
.unwrap_or(String::from(""));
params.insert("alipay_root_cert_sn".to_owned(), alipay_root_cert_sn);
}
Self {
request_params: RefCell::new(params),
private_key: private_key.into(),
other_params: RefCell::new(HashMap::new()),
}
}
pub fn neo<S: Into<String>>(
app_id: S,
private_key_path: &str,
app_cert_sn: Option<&str>,
alipay_root_cert_sn: Option<&str>,
) -> Client {
let private_key =
app_cert_client::get_private_key_from_file(private_key_path).unwrap_or("".to_string());
let mut cert_sn: String = String::from("");
if let Some(cert_sn_path) = app_cert_sn {
cert_sn = app_cert_client::get_cert_sn(cert_sn_path).unwrap_or(String::from(""));
}
let mut root_cert_sn: String = String::from("");
if let Some(root_cert_sn_path) = alipay_root_cert_sn {
root_cert_sn =
app_cert_client::get_root_cert_sn(root_cert_sn_path).unwrap_or(String::from(""));
}
Client::new(
app_id.into(),
private_key,
Some(&cert_sn),
Some(&root_cert_sn),
)
}
fn create_params(&mut self) -> AlipayResult<String> {
let mut request_params = self.request_params.borrow_mut();
let request_params_len = request_params.len();
let mut other_params = self.other_params.borrow_mut();
let other_params_len = other_params.len();
let mut params: Vec<(String, String)> =
Vec::with_capacity(request_params_len + other_params_len);
for (key, val) in request_params.iter() {
params.push((key.to_string(), val.to_string()));
}
request_params.clear();
for (key, val) in other_params.iter() {
params.push((key.to_string(), val.to_string()));
}
other_params.clear();
params.sort_by(|a, b| a.0.cmp(&b.0));
let mut temp = String::new();
for (key, val) in params.iter() {
temp.push_str(key);
temp.push('=');
temp.push_str(val);
temp.push('&');
}
temp.pop();
let private_key = self.get_private_key()?;
let mut signer = Signer::new(MessageDigest::sha256(), &private_key)?;
signer.update(temp.as_bytes())?;
let sign = base64::encode_block(signer.sign_to_vec()?.as_ref());
params.push(("sign".to_owned(), sign));
Ok(serde_urlencoded::to_string(params)?)
}
fn set_request_params<S: Into<String>>(&mut self, key: S, val: String) {
let key = key.into();
let mut request_params = self.request_params.borrow_mut();
if let Some(value) = request_params.get_mut(&key) {
*value = val;
} else {
request_params.insert(key, val);
}
}
pub fn set_public_params<T: AlipayParam>(&mut self, args: T) {
let params = args.to_map();
for (key, val) in params {
match val {
FieldValue::Null => continue,
_ => {
if let Some(value) = self.request_params.borrow_mut().get_mut(&key) {
*value = val.to_string();
}
}
}
}
}
pub fn add_public_params<T: AlipayParam>(&mut self, args: T) {
let params = args.to_map();
for (key, val) in params {
match val {
FieldValue::Null => continue,
_ => {
self.other_params.borrow_mut().insert(key, val.to_string());
}
}
}
}
pub async fn post<S: Into<String>, T: Serialize, R: DeserializeOwned>(
self,
method: S,
biz_content: T,
) -> AlipayResult<R> {
self.sync_post(method, biz_content)
}
pub async fn no_param_post<S: Into<String>, R: DeserializeOwned>(
self,
method: S,
) -> AlipayResult<R> {
self.alipay_post(method, None)
}
pub fn sync_post<S: Into<String>, T: Serialize, R: DeserializeOwned>(
self,
method: S,
biz_content: T,
) -> AlipayResult<R> {
self.alipay_post(method, Some(serde_json::to_string(&biz_content)?))
}
pub async fn post_file<'a, S: Into<String>, D: DeserializeOwned>(
self,
method: S,
key: &'a str,
file_name: &'a str,
file_content: &[u8],
) -> AlipayResult<D> {
let mut multi = multipart::client::lazy::Multipart::new();
multi.add_stream(key, file_content, Some(file_name), None);
let mdata = multi.prepare()?;
let mut url = "https://openapi.alipay.com/gateway.do".to_owned();
let params = self.build_params(method, None)?;
url.push('?');
url.push_str(params.as_str());
let res = ureq::post(url.as_str())
.set(
"Content-Type",
&format!("multipart/form-data; boundary={}", mdata.boundary()),
)
.send(mdata)?;
Ok(res.into_json::<D>()?)
}
fn build_params<S: Into<String>>(
mut self,
method: S,
biz_content: Option<String>,
) -> AlipayResult<String> {
let now = datetime()?;
self.set_request_params("timestamp", now);
self.set_request_params("method", method.into());
if let Some(biz_content) = biz_content {
self.other_params
.borrow_mut()
.insert("biz_content".to_owned(), biz_content);
}
Ok(self.create_params()?)
}
fn alipay_post<S: Into<String>, R: DeserializeOwned>(
self,
method: S,
biz_content: Option<String>,
) -> AlipayResult<R> {
let url = "https://openapi.alipay.com/gateway.do";
let params = self.build_params(method, biz_content)?;
let res = ureq::post(url)
.set(
"Content-Type",
"application/x-www-form-urlencoded;charset=utf-8",
)
.send_string(¶ms)?;
Ok(res.into_json::<R>()?)
}
fn get_private_key(&self) -> AlipayResult<PKey<Private>> {
let cert_content = base64::decode_block(self.private_key.as_str())?;
let rsa = Rsa::private_key_from_der(cert_content.as_slice())?;
Ok(PKey::from_rsa(rsa)?)
}
}