//! 盛大数据模型 use crate::{ backend::{self}, config::Config, gmssl, Sign, Verify, }; use anyhow::Context; use axum::{response::IntoResponse, Json}; use md5::{Digest, Md5}; use serde::de::DeserializeOwned; #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PubReqHead { pub app_id: String, pub method: String, pub sign_type: String, pub sign: String, pub timestamp: String, // yyyy-MM-dd HH:mm:ss pub version: String, pub request_id: String, pub biz_content: String, } pub trait TryFromPubHeadConfig: Sized { fn try_from_pub_req_head_config(head: &PubReqHead, config: &Config) -> anyhow::Result; } impl TryFromPubHeadConfig for T where T: Sized + DeserializeOwned + crate::Verify, { fn try_from_pub_req_head_config(head: &PubReqHead, config: &Config) -> anyhow::Result { let req: T = head.decopy(config)?; req.verify(config).context("验证出错")?; Ok(req) } } impl crate::Verify for PubReqHead { #[tracing::instrument(skip_all, fields(PubReqHead=?self))] fn verify(&self, config: &Config) -> anyhow::Result<()> { if self.sign_type != "md5" || self.app_id != config.sd_config.app_id || self.method != "sd.tovc.recharge" { return Err(anyhow::Error::msg("PubReqHead Verify Value Error")); } match hex::decode(&self.sign) { Ok(o) => { let new_sign = self.sign_var(config); if o == new_sign { Ok(()) } else { Err(anyhow::Error::msg("sign 不匹配")) } } Err(e) => { tracing::error!("verify: {self:?} error: {e}"); Err(anyhow::Error::msg(format!( "PubReqHead Verify Value sign hex::decode Error: {e:#?}" ))) } } } } impl Default for PubReqHead { fn default() -> Self { let time_format = time::macros::format_description!( "[year]-[month padding:zero]-[day padding:zero] [hour padding:zero]:[minute padding:zero]:[second padding:zero]" ); let offset = time::UtcOffset::from_hms(8, 0, 0).unwrap(); let now = time::OffsetDateTime::now_utc().to_offset(offset); Self { app_id: "".to_string(), method: "".to_string(), sign_type: "md5".to_string(), sign: "".to_string(), timestamp: now.format(time_format).unwrap(), version: "v1.0".to_string(), request_id: "".to_string(), biz_content: "".to_string(), } } } macro_rules! str_per { ( $($p:ident).*) => { (str_per!(@internal $($p).*), $($p).*) }; (@internal $p:ident ) => { { let s = stringify!($p); let mut str = String::new(); let mut capitalize = false; for i in s.chars(){ if i == '_'{ capitalize = true; }else if capitalize { str.push(i.to_ascii_uppercase()); capitalize = false; }else{ str.push(i) } } str } }; (@internal $_p:ident.$($p:ident).*) => { str_per!(@internal $($p).*) }; } macro_rules! bt_map { ( $( ($k:expr, $v:expr) ),* $(,)?) => { { let mut map = std::collections::BTreeMap::new(); $( map.insert($k, $v) )* map } }; ($($k:expr),* $(,)?) => { { let mut map = std::collections::BTreeMap::new(); $( let entry: (_,_) = $k; map.insert(entry.0, entry.1); )* map } } } impl PubReqHead { pub fn encrypt_sign(&mut self, config: &Config, data: T) -> anyhow::Result<()> where T: serde::Serialize, { let data = serde_json::to_string(&data)?; let key = config .sd_config .get_pk() .context("PayReqHead::encrypt_sign")?; let r = key.encrypt(data, gmssl::sm2::PassOrder::C1C3C2)?; let data = hex::encode(r); self.biz_content = data; self.sign(config); Ok(()) } pub fn decopy(&self, config: &Config) -> anyhow::Result where T: for<'a> serde::Deserialize<'a>, { let key = config.sd_config.get_sk().context("PayReqHead::decopy")?; let data = hex::decode(&self.biz_content).context("hex::decode Error")?; let s = key .decrypt(data, gmssl::sm2::PassOrder::C1C3C2) .context("解密 biz_content")?; serde_json::from_slice(&s).context("反序列化 biz_content") } fn sign(&mut self, config: &Config) { self.sign = hex::encode_upper(self.sign_var(config)) } pub fn sign_var(&self, config: &Config) -> Vec { let mut aa = Md5::new(); let s = self.format_str(config); aa.update(s); let r = aa.finalize().to_vec(); tracing::debug!("{self:?} Sing:{}", hex::encode(&r)); r } pub fn format_str(&self, config: &Config) -> String { let Self { app_id, method, sign_type, sign, timestamp, version, request_id, biz_content, } = self; let map = bt_map!( str_per!(app_id), str_per!(method), str_per!(sign_type), str_per!(sign), str_per!(timestamp), str_per!(version), str_per!(request_id), str_per!(biz_content) ); let items: Vec<_> = map .into_iter() .map(|(k, v)| (k.trim().to_owned(), v.trim().to_owned())) .filter(|(k, v)| !v.is_empty() && k != "sign") .map(|(k, v)| format!("{}={}", k, v)) .collect(); let mut result = items.join("&"); result.push('&'); result.push_str(&format!("key={}", config.sd_config.sin_key)); result } } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PayReqBody { pub game_user_id: String, pub server_type: String, pub product_no: String, pub sp_order_id: String, pub source: String, } impl crate::Verify for PayReqBody { fn verify(&self, _: &Config) -> anyhow::Result<()> { if self.server_type != "tel" { return Err(anyhow::Error::msg("仅支持话费")); } Ok(()) } } impl PayReqBody { pub fn into_backend_pay_req( &self, head: &PubReqHead, config: &Config, ) -> backend::Body { let timestamp = head.timestamp.trim().replace( |c: char| c.is_whitespace() || c == '-' || c == '_' || c == ':', "", ); let mut body = backend::Body { header: backend::Header { timestamp, seqno: head.request_id.clone(), appid: config.fscg_config.app_id.clone(), ..Default::default() }, msgbody: backend::PayReqBody { content: Some(backend::PayReqContent { user: self.game_user_id.clone(), packageid: self.product_no.clone(), extorder: self.sp_order_id.clone(), note: Some(self.source.clone()), ..Default::default() }), resp: None, }, }; body.sign(config); body } } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PayRespBodyData { state: String, order_cash: String, order_id: String, sp_order_id: String, game_user_id: String, product_name: String, } pub type PayRespBody = PubRespBody; impl PayRespBody { pub fn from_backend_pay_resp( config: &Config, body: backend::Body, req: &PayReqBody, ) -> Self { if let Err(e) = body.verify(config) { // return Self::error("99", "服务响应验证错误"); return Self::error("99", format!("服务响应验证错误: {:#?}", e)); } let resp = match body.msgbody.resp { Some(resp) => resp, None => { return Self::error("99", "服务没有响应字段: resp"); } }; let content = match body.msgbody.content { Some(content) => content, None => { return Self::error( resp.rcode, resp.rmsg.unwrap_or("服务没有响应字段: content".to_string()), ); } }; let state = if resp.rcode == "00" { "0".to_string() } else { "9".to_string() }; Self::success(PayRespBodyData { state, order_cash: "".to_string(), order_id: content.orderid, sp_order_id: content.extorder, game_user_id: req.game_user_id.clone(), product_name: req.product_no.clone(), }) } } impl crate::Sign for PayRespBody { fn sign(&mut self, _: &Config) {} } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct BalanceRespBody { pub result_code: String, pub result_desc: String, pub balance: Option, } impl BalanceRespBody { pub fn from_backend_account_resp( config: &Config, body: backend::Body, ) -> Self { if let Err(e) = body.verify(config) { return Self::error("99", format!("服务响应验证错误: {:#?}", e)); } let resp = match body.msgbody.resp { Some(resp) => resp, None => { return Self::error("99", "服务没有响应字段: resp"); } }; let content = match body.msgbody.content { Some(content) => content, None => { return Self::error( resp.rcode, resp.rmsg.unwrap_or("服务没有响应字段: content".to_string()), ); } }; Self::success(content.balance) } pub fn success(data: impl Into>) -> Self { Self { result_code: "000".to_string(), result_desc: "success".to_string(), balance: data.into(), } } pub fn error(code: impl ToString, desc: impl ToString) -> Self { Self { result_code: code.to_string(), result_desc: desc.to_string(), balance: None, } } } impl crate::Sign for BalanceRespBody { fn sign(&mut self, _: &Config) {} } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct BalanceReqBody { source: String, } impl BalanceReqBody { pub fn into_backend_account_req( &self, config: &Config, head: &PubReqHead, ) -> backend::Body { let backend_req = backend::AccountReqBody::new(); let mut body = backend::Body { header: backend::Header { seqno: head.request_id.clone(), appid: config.fscg_config.app_id.clone(), ..Default::default() }, msgbody: backend_req, }; body.sign(config); body } } impl crate::Verify for BalanceReqBody { fn verify(&self, _: &Config) -> anyhow::Result<()> { if self.source == "SD" { Ok(()) } else { Err(anyhow::Error::msg("BalanceReqBody Verify False")) } } } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct SearchReqBody { sp_order_id: String, } impl SearchReqBody { pub fn into_backend_search_req( &self, head: &PubReqHead, config: &Config, ) -> backend::Body { let mut body = backend::Body { header: backend::Header { seqno: head.request_id.clone(), appid: config.fscg_config.app_id.clone(), ..Default::default() }, msgbody: backend::SearchReqBody { content: Some(backend::SearchReqContent { // orderid: Some(req.sp_order_id.clone()), extorderid: Some(self.sp_order_id.clone()), ..Default::default() }), resp: None, }, }; body.sign(config); body } } impl crate::Verify for SearchReqBody {} #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct SearchRespData { state: String, message: String, success_time: Option, order_id: String, sp_order_id: String, game_user_id: String, operator_order: Option, card_no: Option, // 卡密 card_pwd: Option, // 卡密 effective_time: Option, // 卡密 } pub type SearchRespBody = PubRespBody; impl SearchRespBody { pub fn from_backend_search_resp( config: &Config, body: backend::Body, ) -> Self { if let Err(e) = body.verify(config) { Self::error("99", format!("服务响应验证错误: {:#?}", e)); } let resp = match body.msgbody.resp { Some(resp) => resp, None => { return Self::error("99", "服务没有响应字段: resp".to_string()); } }; let content = match body.msgbody.content { Some(content) => content, None => { return Self::error( resp.rcode, resp.rmsg.unwrap_or("服务没有响应字段: content".to_string()), ); } }; let state = if content.status == "6" { "1" } else if content.status == "4" { "9" } else { "0" }; let time_str = if content.checktime.is_empty() { None } else { let time_format = time::macros::format_description!("[year]-[month padding:zero]-[day padding:zero] [hour padding:zero]:[minute padding:zero]:[second padding:zero]"); let backend_time = time::macros::format_description!("[year][month padding:zero][day padding:zero][hour padding:zero][minute padding:zero][second padding:zero][subsecond digits:1+]"); let time = time::PrimitiveDateTime::parse(&content.checktime, backend_time).unwrap(); let time_str = time.format(time_format).unwrap(); Some(time_str) }; let data = SearchRespData { state: state.to_string(), message: content.msg, success_time: time_str, order_id: content.orderid, sp_order_id: content.extorder, game_user_id: content.user, operator_order: content.attr1, card_no: None, card_pwd: None, effective_time: None, }; Self::success(data) } } impl crate::Sign for SearchRespBody { fn sign(&mut self, _: &Config) {} } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PubRespBody { pub result_code: String, pub result_desc: String, pub data: Option, } impl PubRespBody { pub fn success(data: T) -> Self { Self { result_code: "000".to_string(), result_desc: "success".to_string(), data: Some(data), } } pub fn error(code: impl ToString, desc: impl ToString) -> Self { Self { result_code: code.to_string(), result_desc: desc.to_string(), data: None, } } } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PubRespHead { #[serde(default = "String::new")] pub code: String, #[serde(default = "String::new")] pub msg: String, #[serde(default = "String::new")] pub data: String, #[serde(default = "String::new")] pub sign: String, #[serde(default = "String::new")] pub request_id: String, } impl IntoResponse for PubRespHead { fn into_response(self) -> axum::response::Response { Json(self).into_response() } } impl PubRespHead { pub fn new(config: &Config, data: impl ToString, request_id: impl ToString) -> Self { let mut result = Self { data: data.to_string(), request_id: request_id.to_string(), ..Default::default() }; result.sign(config); result } pub fn sign(&mut self, config: &Config) { self.sign = hex::encode_upper(self.sign_var(config)) } pub fn sign_var(&self, config: &Config) -> Vec { let mut aa = Md5::new(); let s = self.format_str(config); aa.update(&s); let r = aa.finalize().to_vec(); tracing::debug!(sign = hex::encode(&r), data = s, "签名"); r } pub fn format_str(&self, config: &Config) -> String { let Self { code, msg, data, sign, request_id, } = self; let map = bt_map!( str_per!(code), str_per!(msg), str_per!(data), str_per!(sign), str_per!(request_id), ); let items: Vec<_> = map .into_iter() .map(|(k, v)| (k.trim().to_owned(), v.trim().to_owned())) .filter(|(k, v)| !v.is_empty() && k != "sign") .map(|(k, v)| format!("{}={}", k, v)) .collect(); let mut result = items.join("&"); result.push('&'); result.push_str(&format!("key={}", config.sd_config.sin_key)); result } } impl Default for PubRespHead { fn default() -> Self { Self { code: "000".to_string(), msg: "success".to_string(), data: "".to_string(), sign: "".to_string(), request_id: "".to_string(), } } } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct CallbackReqBody { pub state: String, pub message: String, pub order_id: String, pub sp_order_id: String, pub game_user_id: String, pub operator_order: Option, pub card_no: Option, pub card_pwd: Option, pub effective_time: Option, } impl Default for CallbackReqBody { fn default() -> Self { Self { state: "".to_string(), message: "".to_string(), order_id: "".to_string(), sp_order_id: "".to_string(), game_user_id: "".to_string(), operator_order: None, card_no: None, card_pwd: None, effective_time: None, } } } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct CallbackRespBody { pub result_code: String, pub result_desc: String, } #[cfg(test)] #[allow(unused)] mod tests { use crate::config::SdConfig; use super::*; #[test] fn test_aaaa() { let s = "04f97e835ec530a95df4c999ab0184996b68464bf308239ebb692174812fb4c54349fc217ac5ab13ea8313e458e2cf37fa35345afc4290f8d25dc7c8bb37da1a0ff87a7752c8d03998067b3bf9fec1d7e397aba05fb016b3b48e269e9c87c22b6eef61508de4572c7dacd980530dac913f507ad9aa2a2a8c62edae35c21bf3aaa446168989a1350914499322ebb5b37038e432ed8f37e25083e35087b4fc1dbff3488c1e7cd7810712725a1fd78365a73892bf6b45352ca775b7b2e66a334ed93986728d2c2c78af2bbfaeb590d55a494163371c0c5df7"; let head = PubReqHead{biz_content: s.to_string(), ..Default::default()}; let cfg = Config::default(); let r = head.decopy::(&cfg); println!("{:#?}", r) } #[test] fn test_sign() { // 2C5A4C48A9BD07B79C250049A17C21C2 // 2C5A4C48A9BD07B79C250049A17C21C2 let mut a = PubReqHead { app_id: "asdaw".to_string(), method: "asdaw".to_string(), sign_type: "asdaw".to_string(), sign: "asdaw".to_string(), timestamp: "2023-03-23 16:25:00".to_string(), version: "asdaw".to_string(), request_id: "asdaw".to_string(), biz_content: "".to_string(), }; let c = Default::default(); a.sign(&c); println!("{a:?}"); } #[test] fn feature() { let r = PayReqBody { game_user_id: "13856023633".to_string(), server_type: "tel".to_string(), product_no: "LD_5477".to_string(), sp_order_id: "QHGHFBGF0235641202".to_string(), source: "SD".to_string(), }; let r = serde_json::to_string(&r).unwrap(); println!("{r}") } #[test] fn test_try_into_fscg_req() { // 470EF517335D444BD003098D6F6ABCE0 // 470EF517335D444BD003098D6F6ABCE0 let now = std::time::Instant::now(); let mut a = PubReqHead { app_id: "asdaw".to_string(), method: "asdaw".to_string(), sign_type: "asdaw".to_string(), sign: "asdaw".to_string(), timestamp: "2023-03-23 16:25:00".to_string(), version: "asdaw".to_string(), request_id: "asdaw".to_string(), biz_content: r#"{"gameUserId":"13856023633","serverType":"tel","productNo":"LD_5477","spOrderId":"QHGHFBGF0235641202","source":"SD"}"#.to_string(), }; let c: Config = Default::default(); // let r = c.sd_config.; let key = c.sd_config.get_sk().unwrap(); let r = key .encrypt(a.biz_content, gmssl::sm2::PassOrder::C1C2C3) .unwrap(); a.biz_content = hex::encode(r); a.sign(&c); println!("{a:?}"); let new = std::time::Instant::now(); println!("use Time{:?}", new - now); } }