|
@@ -0,0 +1,694 @@
|
|
|
+//! 盛大数据模型
|
|
|
+
|
|
|
+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<Self>;
|
|
|
+}
|
|
|
+
|
|
|
+impl<T> TryFromPubHeadConfig for T
|
|
|
+where
|
|
|
+ T: Sized + DeserializeOwned + crate::Verify,
|
|
|
+{
|
|
|
+ fn try_from_pub_req_head_config(head: &PubReqHead, config: &Config) -> anyhow::Result<Self> {
|
|
|
+ let req: T = head.decopy(config)?;
|
|
|
+ req.verify(config).context("验证出错")?;
|
|
|
+ Ok(req)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl crate::Verify for PubReqHead {
|
|
|
+ fn verify(&self, config: &Config) -> anyhow::Result<()> {
|
|
|
+ if self.sign_type != "md5"
|
|
|
+ || self.app_id != config.sd_config.supplier_code
|
|
|
+ || 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 {
|
|
|
+ Self {
|
|
|
+ app_id: "".to_string(),
|
|
|
+ method: "".to_string(),
|
|
|
+ sign_type: "md5".to_string(),
|
|
|
+ sign: "".to_string(),
|
|
|
+ timestamp: "".to_string(),
|
|
|
+ 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 decopy<T>(&self, config: &Config) -> anyhow::Result<T>
|
|
|
+ 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::C1C2C3)
|
|
|
+ .context("解密 biz_content")?;
|
|
|
+ serde_json::from_slice(&s).context("反序列化 biz_content")
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn sign(&mut self, config: &Config) {
|
|
|
+ self.sign = hex::encode_upper(self.sign_var(config))
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn sign_var(&self, config: &Config) -> Vec<u8> {
|
|
|
+ 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<backend::PayReqBody> {
|
|
|
+ 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<PayRespBodyData>;
|
|
|
+
|
|
|
+impl PayRespBody {
|
|
|
+ pub fn from_backend_pay_resp(
|
|
|
+ config: &Config,
|
|
|
+ body: backend::Body<backend::PayRespBody>,
|
|
|
+ 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<String>,
|
|
|
+}
|
|
|
+
|
|
|
+impl BalanceRespBody {
|
|
|
+ pub fn from_backend_account_resp(
|
|
|
+ config: &Config,
|
|
|
+ body: backend::Body<backend::AccountRespBody>,
|
|
|
+ ) -> 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<Option<String>>) -> 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)]
|
|
|
+#[serde(rename_all = "camelCase")]
|
|
|
+pub struct BalanceReqBody {
|
|
|
+ source: String,
|
|
|
+}
|
|
|
+
|
|
|
+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<backend::SearchReqBody> {
|
|
|
+ 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);
|
|
|
+ let timestamp = now.format(time_format).unwrap();
|
|
|
+ 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::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<String>,
|
|
|
+ order_id: String,
|
|
|
+ sp_order_id: String,
|
|
|
+ game_user_id: String,
|
|
|
+ operator_order: Option<String>,
|
|
|
+ card_no: Option<String>, // 卡密
|
|
|
+ card_pwd: Option<String>, // 卡密
|
|
|
+ effective_time: Option<String>, // 卡密
|
|
|
+}
|
|
|
+
|
|
|
+pub type SearchRespBody = PubRespBody<SearchRespData>;
|
|
|
+
|
|
|
+impl SearchRespBody {
|
|
|
+ pub fn from_backend_search_resp(
|
|
|
+ config: &Config,
|
|
|
+ body: backend::Body<backend::SearchRespBody>,
|
|
|
+ ) -> 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<T> {
|
|
|
+ pub result_code: String,
|
|
|
+ pub result_desc: String,
|
|
|
+ pub data: Option<T>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<T> PubRespBody<T> {
|
|
|
+ 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 {
|
|
|
+ pub code: String,
|
|
|
+ pub msg: String,
|
|
|
+ pub data: String,
|
|
|
+ pub sign: String,
|
|
|
+ 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<u8> {
|
|
|
+ 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(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+#[allow(unused)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ #[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);
|
|
|
+ }
|
|
|
+}
|