br-pay 0.0.52

This is an pay
Documentation
use std::collections::HashMap;
use json::{object, JsonValue};
use crate::{PayMode, Types};

/// 建设银行
#[derive(Clone, Debug)]
pub struct Ccbc {
    /// 服务商APPID
    pub appid: String,
    /// 密钥
    pub secret: String,
    /// 登录密码
    pub pass: String,
    /// 银行商户号
    pub mchid: String,
    /// 微信商户号
    pub sp_mchid: String,
    /// 通知地址
    pub notify_url: String,
    /// 商户柜台代码
    pub posid: String,
    /// 分行代码
    pub branchid: String,
    /// 二级商户名称
    pub smername: String,
    /// 二级商户类别代码
    pub smertypeid: String,
    /// 二级商户类别名称
    pub smertype: String,
    /// 二级商户公钥
    pub public_key: String,
    pub client_ip: String,

}

impl Ccbc {
    pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
        let mut mac = vec![];
        let mut path = vec![];
        let fields = ["MAC"];
        for (key, value) in body.entries() {
            if value.is_empty() && fields.contains(&key) {
                continue;
            }
            if key != "PUB" {
                path.push(format!("{key}={value}"));
            }
            mac.push(format!("{key}={value}"));
        }


        let mac_text = mac.join("&");
        let path = path.join("&");
        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
        body.remove("PUB");
        let mac = format!("{}&MAC={}", path, body["MAC"]);

        let url = format!("{url}&{mac}");


        let mut map = HashMap::new();
        for (key, value) in body.entries() {
            map.insert(key, value.to_string());
        }

        let http = match reqwest::blocking::Client::builder().danger_accept_invalid_certs(true).build() {
            Ok(e) => e,
            Err(e) => return Err(e.to_string())
        };

        let res = match http.post(url).json(&map).send() {
            Ok(e) => e,
            Err(e) => return Err(e.to_string())
        };
        let res = res.text().unwrap();
        match json::parse(&res) {
            Ok(e) => Ok(e),
            Err(_) => Err(res)
        }
    }
    fn escape_unicode(&mut self, s: &str) -> String {
        s.chars().map(|c| {
            if c.is_ascii() {
                c.to_string()
            } else {
                format!("%u{:04X}", c as u32)
            }
        }).collect::<String>()
    }
    fn _unescape_unicode(&mut self, s: &str) -> String {
        let mut output = String::new();
        let mut chars = s.chars().peekable();
        while let Some(c) = chars.next() {
            if c == '%' && chars.peek() == Some(&'u') {
                chars.next(); // consume 'u'
                let codepoint: String = chars.by_ref().take(4).collect();
                if let Ok(value) = u32::from_str_radix(&codepoint, 16) {
                    if let Some(ch) = std::char::from_u32(value) {
                        output.push(ch);
                    }
                }
            } else {
                output.push(c);
            }
        }
        output
    }
    pub fn http_q(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
        let mut path = vec![];
        let mut mac = vec![];
        let fields = ["MAC"];
        for (key, value) in body.entries() {
            if value.is_empty() && fields.contains(&key) {
                continue;
            }
            mac.push(format!("{key}={value}"));
            if key.contains("QUPWD") {
                path.push(format!("{key}="));
                continue;
            }
            path.push(format!("{key}={value}"));
        }

        let mac = mac.join("&");
        body["MAC"] = br_crypto::md5::encrypt_hex(mac.as_bytes()).into();
        let mac = format!("{}&MAC={}", path.join("&"), body["MAC"]);
        let url = format!("{url}?{mac}");
        let mut map = HashMap::new();
        for (key, value) in body.entries() {
            map.insert(key, value.to_string());
        }

        let http = match reqwest::blocking::Client::builder().danger_accept_invalid_certs(true).build() {
            Ok(e) => e,
            Err(e) => return Err(e.to_string())
        };
        let res = match http.post(url).json(&map).send() {
            Ok(e) => e,
            Err(e) => return Err(e.to_string())
        };
        let res = res.text().unwrap();
        match json::parse(&res) {
            Ok(e) => Ok(e),
            Err(_) => Err(res)
        }
    }
}
impl PayMode for Ccbc {
    fn check(&mut self) -> Result<bool, String> {
        todo!()
    }

    fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
        todo!()
    }

    fn config(&mut self) -> JsonValue {
        todo!()
    }


    fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn pay(&mut self, channel: &str, types: Types, _sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
        if self.public_key.is_empty() {
            return Err(String::from("Public key is empty"));
        }
        let pubtext = self.public_key[self.public_key.len() - 30..].to_string();
        let mut body = object! {
            MERCHANTID:self.mchid.clone(),
            POSID:self.posid.clone(),
            BRANCHID:self.branchid.clone(),
            ORDERID:out_trade_no,
            PAYMENT:total_fee,
            CURCODE:"01",
            TXCODE:"530590",
            REMARK1:"",
            REMARK2:"",
            TYPE:"1",
            PUB:pubtext,
            GATEWAY:"0",
            CLIENTIP:self.client_ip.clone(),
            REGINFO:self.escape_unicode(&self.smername.clone()),
            PROINFO: self.escape_unicode(description),
            REFERER:"",
            TRADE_TYPE:"",
            MAC:"",
        };

        let url = match channel {
            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
            _ => return Err(format!("Invalid channel: {channel}")),
        };

        match channel {
            "wechat" => {
                body["TRADE_TYPE"] = match types {
                    Types::Jsapi => "JSAPI",
                    Types::MiniJsapi => "MINIPRO",
                    _ => return Err(format!("Invalid channel: {types:?}")),
                }.into();
                body["SUB_APPID"] = self.appid.clone().into();
                body["SUB_OPENID"] = sp_openid.into();

                //body["WX_CHANNELID"] = self.sp_mchid.clone().into();

                //body["SMERID"] = sub_mchid.into();
                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
                //body["SMERTYPEID"] = self.smertypeid.clone().into();
                //body["SMERTYPE"] = self.escape_unicode(self.smertype.clone().as_str()).into();

                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
                //body["SMERTYPEID"] = 1.into();
                //body["SMERTYPE"] = self.escape_unicode("宾馆餐娱类").into();

                //body["TRADECODE"] = "交易类型代码".into();
                //body["TRADENAME"] = self.escape_unicode("消费").into();
                //body["SMEPROTYPE"] = "商品类别代码".into();
                //body["PRONAME"] = self.escape_unicode("商品").into();
            }
            "alipay" => {
                body["TXCODE"] = "530591".into();
                body["TRADE_TYPE"] = match types {
                    Types::Jsapi => "JSAPI",
                    Types::MiniJsapi => "JSAPI",
                    _ => return Err(format!("Invalid channel: {types:?}")),
                }.into();
                body["USERID"] = sp_openid.into();
            }
            _ => return Err(format!("Invalid channel: {channel}")),
        }
        let res = self.http(url, body)?;
        match types {
            Types::Jsapi | Types::MiniJsapi => {
                if res.has_key("PAYURL") {
                    let url = res["PAYURL"].to_string();

                    let http = match reqwest::blocking::Client::builder().danger_accept_invalid_certs(true).build() {
                        Ok(e) => e,
                        Err(e) => return Err(e.to_string())
                    };
                    let re = match http.post(url.as_str()).send() {
                        Ok(e) => e,
                        Err(e) => {
                            return Err(e.to_string());
                        }
                    };
                    let re = re.text().unwrap();
                    let res = match json::parse(&re) {
                        Ok(e) => e,
                        Err(_) => return Err(re)
                    };
                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
                    }
                    Ok(res)
                } else {
                    Err(res.to_string())
                }
            }
            _ => {
                Ok(res)
            }
        }
    }

    fn micropay(&mut self, channel: &str, auth_code: &str, _sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, _org_openid: &str, _ip: &str) -> Result<JsonValue, String> {
        let mut body = object! {
            MERCHANTID:self.sp_mchid.clone(),
            POSID:self.posid.clone(),
            BRANCHID:self.branchid.clone(),
            ccbParam:"",
            TXCODE:"PAY100",
            MERFLAG:"1",
            ORDERID:out_trade_no,
            QRCODE:auth_code,
            AMOUNT:total_fee,
            PROINFO:"商品名称",
            REMARK1:description
        };

        let url = match channel {
            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
            _ => return Err(format!("Invalid channel: {channel}")),
        };

        match channel {
            "wechat" => {
                body["SUB_APPID"] = self.appid.clone().into();
            }
            "alipay" => {}
            _ => return Err(format!("Invalid channel: {channel}")),
        }

        let res = self.http(url, body)?;
        Ok(res)
    }

    fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
        Ok(true.into())
    }

    fn pay_query(&mut self, out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
        let body = object! {
            MERCHANTID:self.mchid.clone(),
            BRANCHID:self.branchid.clone(),
            POSID:self.posid.clone(),
            ORDERDATE:"",
            BEGORDERTIME:"00:00:00",
            ENDORDERTIME:"23:59:59",
            ORDERID:out_trade_no,
            QUPWD:self.pass.clone(),
            TXCODE:"410408",
            TYPE:"0",
            KIND:"0",
            STATUS:"1",
            SEL_TYPE:"3",
            PAGE:"1",
            OPERATOR:"",
            CHANNEL:"",
            MAC:""
        };
        let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
        println!("{res:#}");
        Ok(object! {})
    }

    fn pay_micropay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn refund(&mut self, _sub_mchid: &str, _out_trade_no: &str, _transaction_id: &str, _out_refund_no: &str, _amount: f64, _total: f64, _currency: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn micropay_refund(&mut self, _sub_mchid: &str, _out_trade_no: &str, _transaction_id: &str, _out_refund_no: &str, _amount: f64, _total: f64, _currency: &str, _refund_text: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn refund_query(&mut self, _trade_no: &str, _out_refund_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
        todo!()
    }

    fn incoming(&mut self, _business_code: &str, _contact_info: JsonValue, _subject_info: JsonValue, _business_info: JsonValue, _settlement_info: JsonValue, _bank_account_info: JsonValue) -> Result<JsonValue, String> {
        todo!()
    }
}