br_pay/
ccbc.rs

1use std::fs;
2use std::path::PathBuf;
3use base64::Engine;
4use base64::engine::general_purpose;
5use chrono::Local;
6use json::{object, JsonValue};
7use log::{debug, error, info, warn};
8use xmltree::Element;
9use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
10use cipher::{BlockEncryptMut, KeyInit};
11use cipher::block_padding::Pkcs7;
12use des::Des;
13use rand::{rng, Rng};
14
15/// 建设银行
16#[derive(Clone, Debug)]
17pub struct Ccbc {
18    /// 调试
19    pub debug: bool,
20    /// 微信小程序APPID
21    pub appid: String,
22    /// 微信公众号APPID
23    pub appid_subscribe: String,
24    /// 服务商号
25    pub sp_mchid: String,
26    /// 服务商账号ID
27    pub sp_user_id: String,
28    /// 服务商登录密码
29    pub sp_pass: String,
30    /// 通知地址
31    /// 服务商柜台代码
32    pub sp_posid: String,
33    pub notify_url: String,
34    /// 商户商柜台代码
35    pub sub_posid: String,
36    /// 分行代码
37    pub branchid: String,
38    /// 二级商户公钥
39    pub public_key: String,
40    pub client_ip: String,
41    /// 重试次
42    pub retry: usize,
43}
44
45impl Ccbc {
46    pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
47        let mut mac = vec![];
48        let mut path = vec![];
49        let fields = ["MAC"];
50        for (key, value) in body.entries() {
51            if value.is_empty() && fields.contains(&key) {
52                continue;
53            }
54            if key != "PUB" {
55                path.push(format!("{key}={value}"));
56            }
57            mac.push(format!("{key}={value}"));
58        }
59
60
61        let mac_text = mac.join("&");
62        let path = path.join("&");
63        if self.debug {
64            debug!("MERCHANTID=105000373721227&POSID=091864103&BRANCHID=530000000&ORDERID=96398&PAYMENT=0.01&CURCODE=01&TXCODE=530550&REMARK1=&REMARK2=&RETURNTYPE=3&TIMEOUT=&PUB=93bd9affe92c29dea12e5d79020111");
65            debug!("{mac_text:#}");
66        }
67        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
68        if self.debug {
69            debug!("{body:#}");
70        }
71        body.remove("PUB");
72        let mac = format!("{}&MAC={}", path, body["MAC"]);
73        if self.debug {
74            debug!("{mac:#}");
75        }
76        let urls = format!("{url}&{mac}");
77        if self.debug {
78            debug!("{urls:#}");
79        }
80        let mut http = br_reqwest::Client::new();
81
82        let res = http.post(urls.as_str()).raw_json(body.clone()).set_retry(3).send()?;
83        let res = res.body().to_string();
84        match json::parse(&res) {
85            Ok(e) => Ok(e),
86            Err(e) => Err(e.to_string())
87        }
88    }
89    pub fn http_alipay(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
90        let mut mac = vec![];
91        let mut path = vec![];
92        let fields = ["MAC", "SUBJECT", "AREA_INFO"];
93        for (key, value) in body.entries() {
94            if value.is_empty() && fields.contains(&key) {
95                continue;
96            }
97            if fields.contains(&key) {
98                continue;
99            }
100            if key != "PUB" {
101                path.push(format!("{key}={value}"));
102            }
103            mac.push(format!("{key}={value}"));
104        }
105
106
107        let mac_text = mac.join("&");
108        let path = path.join("&");
109        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
110        body.remove("PUB");
111        let mac = format!("{}&MAC={}", path, body["MAC"]);
112
113        let urls = format!("{url}&{mac}");
114
115        let mut http = br_reqwest::Client::new();
116        let res = match http.post(urls.as_str()).raw_json(body.clone()).send() {
117            Ok(e) => e,
118            Err(e) => {
119                if self.retry > 2 {
120                    return Err(e.to_string());
121                }
122                self.retry += 1;
123                warn!("建行接口重试: {}", self.retry);
124                body.remove("MAC");
125                let res = self.http_alipay(url, body.clone())?;
126                return Ok(res);
127            }
128        };
129        let res = res.body().to_string();
130        match json::parse(&res) {
131            Ok(e) => Ok(e),
132            Err(_) => Err(res)
133        }
134    }
135    fn escape_unicode(&mut self, s: &str) -> String {
136        s.chars().map(|c| {
137            if c.is_ascii() {
138                c.to_string()
139            } else {
140                format!("%u{:04X}", c as u32)
141            }
142        }).collect::<String>()
143    }
144    fn _unescape_unicode(&mut self, s: &str) -> String {
145        let mut output = String::new();
146        let mut chars = s.chars().peekable();
147        while let Some(c) = chars.next() {
148            if c == '%' && chars.peek() == Some(&'u') {
149                chars.next(); // consume 'u'
150                let codepoint: String = chars.by_ref().take(4).collect();
151                if let Ok(value) = u32::from_str_radix(&codepoint, 16) {
152                    if let Some(ch) = std::char::from_u32(value) {
153                        output.push(ch);
154                    }
155                }
156            } else {
157                output.push(c);
158            }
159        }
160        output
161    }
162    pub fn http_q(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
163        let mut path = vec![];
164        let fields = ["MAC"];
165        for (key, value) in body.entries() {
166            if value.is_empty() && fields.contains(&key) {
167                continue;
168            }
169            if key.contains("QUPWD") {
170                path.push(format!("{key}="));
171                continue;
172            }
173            path.push(format!("{key}={value}"));
174        }
175
176        let mac = path.join("&");
177        body["MAC"] = br_crypto::md5::encrypt_hex(mac.as_bytes()).into();
178
179        let mut map = vec![];
180        for (key, value) in body.entries() {
181            map.push((key, value.to_string()));
182        }
183
184        let mut http = br_reqwest::Client::new();
185
186        http.header("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36");
187
188        let res = match http.post(url).form_urlencoded(body.clone()).send() {
189            Ok(d) => d,
190            Err(e) => {
191                if self.retry > 2 {
192                    return Err(e.to_string());
193                }
194                self.retry += 1;
195                warn!("建行查询接口重试: {}", self.retry);
196                body.remove("MAC");
197                let res = self.http_q(url, body.clone())?;
198                return Ok(res);
199            }
200        };
201        let res = res.body().to_string().trim().to_string();
202        match Element::parse(res.as_bytes()) {
203            Ok(e) => Ok(xml_element_to_json(&e)),
204            Err(e) => Err(e.to_string())
205        }
206    }
207
208    pub fn http_ccb_param(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
209        let mer_info = format!("MERCHANTID={}&POSID={}&BRANCHID={}", body["MERCHANTID"], body["POSID"], body["BRANCHID"]);
210        let ccb_param = self.make_ccb_param(body.clone())?;
211        let urls = format!("{url}?{mer_info}&ccbParam={ccb_param}");
212        let mut http = br_reqwest::Client::new();
213        let res = match http.post(urls.as_str()).raw_json(body.clone()).send() {
214            Ok(e) => e,
215            Err(e) => {
216                if self.retry > 2 {
217                    return Err(e.to_string());
218                }
219                self.retry += 1;
220                warn!("建行接口重试: {}", self.retry);
221                body.remove("MAC");
222                let res = self.http_ccb_param(url, body.clone())?;
223                return Ok(res);
224            }
225        };
226        let res = res.body().to_string();
227        match json::parse(&res) {
228            Ok(e) => Ok(e),
229            Err(_) => Err(res)
230        }
231    }
232
233    pub fn make_ccb_param(&mut self, body: JsonValue) -> Result<String, String> {
234        if self.public_key.is_empty() || self.public_key.len() < 30 {
235            return Err(String::from("Public key is empty"));
236        }
237        let pubkey = self.public_key[self.public_key.len() - 30..].to_string();
238        if pubkey.len() < 8 {
239            return Err(String::from("Public key len 8"));
240        }
241        let pubkey = &pubkey[..8];
242        let mut mac = vec![];
243        let mut arr = vec![];
244        let mut params = vec![];
245        for (key, value) in body.entries() {
246            arr.push(key);
247            params.push(format!("{key}={value}"));
248        }
249        arr.sort();
250        for key in arr.iter() {
251            if body.has_key(key) && !body[key.to_string()].is_empty() {
252                mac.push(format!("{key}={}", body[key.to_string()]));
253            }
254        }
255        let ccb_param = mac.join("&");
256        let ccb_param = format!("{}20120315201809041004", ccb_param);
257        let ccb_param = br_crypto::md5::encrypt_hex(ccb_param.as_bytes());
258        let params = params.join("&");
259        let params = format!("{params}&SIGN={ccb_param}");
260        let bytes = self.utf16_bytes(params.as_str());
261        let encrypt = self.des_ecb_pkcs5_base64(&bytes, pubkey)?;
262        let encrypt = encrypt.replace("+", ",");
263        let url = br_crypto::encoding::urlencoding_encode(encrypt.as_str());
264        Ok(url)
265    }
266    fn utf16_bytes(&mut self, s: &str) -> Vec<u8> {
267        let mut out = Vec::with_capacity(2 + s.len() * 2);
268        out.push(0xFE);
269        out.push(0xFF);
270        for u in s.encode_utf16() {
271            out.push((u >> 8) as u8);
272            out.push((u & 0xFF) as u8);
273        }
274        out
275    }
276    fn des_ecb_pkcs5_base64(&mut self, plain: &[u8], key_str: &str) -> Result<String, &'static str> {
277        let kb = key_str.as_bytes();
278        if kb.len() < 8 {
279            return Err("DES key must be at least 8 bytes");
280        }
281        let mut key = [0u8; 8];
282        key.copy_from_slice(&kb[..8]);
283
284        let cipher = ecb::Encryptor::<Des>::new(&key.into());
285
286        // 预留 1 块给 PKCS#5/7 填充
287        let orig_len = plain.len();
288        let mut buf = vec![0u8; orig_len + 8];
289        buf[..orig_len].copy_from_slice(plain);
290
291        let ct = cipher.encrypt_padded_mut::<Pkcs7>(&mut buf, orig_len).map_err(|_| "encrypt error")?;
292
293        Ok(general_purpose::STANDARD.encode(ct))
294    }
295    fn https_cert(&mut self, text: &str) -> Result<JsonValue, String> {
296        let text = br_crypto::encoding::urlencoding_encode(text);
297        let mut http = br_reqwest::Client::new();
298        http.header("Connection", "close");
299        http.post("http://127.0.0.1:12345").form_urlencoded(object! {
300            requestXml:text
301        });
302        let res = http.send()?;
303        let ress = br_crypto::encoding::gb18030_to_utf8(res.stream())?;
304        let ress = ress.replace(r#"encoding="GB18030""#, r#"encoding="UTF-8""#);
305        match Element::parse(ress.trim().as_bytes()) {
306            Ok(e) => {
307                let json = xml_element_to_json(&e);
308                Ok(json)
309            }
310            Err(e) => Err(e.to_string())
311        }
312    }
313    fn get_xml_text(&mut self, name: &str) -> String {
314        let temp_plugin_mod_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
315        let temp_model_mod_path = temp_plugin_mod_path.join("xml").join("ccbc").join(name);
316        let xml_text = fs::read(temp_model_mod_path).unwrap();
317        let mut text = unsafe { String::from_utf8_unchecked(xml_text) };
318        let mut rng = rng();
319        let num: String = (0..14).map(|_| rng.random_range(0..10).to_string()).collect();
320        text = text.replace("{{REQUEST_SN}}", num.as_str());
321        text = text.replace("{{CUST_ID}}", self.sp_mchid.as_str());
322        text = text.replace("{{USER_ID}}", self.sp_user_id.as_str());
323        text = text.replace("{{PASSWORD}}", self.sp_pass.as_str());
324        text
325    }
326}
327impl PayMode for Ccbc {
328    fn check(&mut self) -> Result<bool, String> {
329        todo!()
330    }
331
332    fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
333        todo!()
334    }
335
336    fn config(&mut self) -> JsonValue {
337        todo!()
338    }
339
340
341    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> {
342        if self.public_key.is_empty() || self.public_key.len() < 30 {
343            return Err(String::from("Public key is empty"));
344        }
345        let pubtext = self.public_key[self.public_key.len() - 30..].to_string();
346
347        let url = match channel {
348            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
349            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
350            _ => return Err(format!("Invalid channel: {channel}")),
351        };
352        let body = match channel {
353            "wechat" => {
354                let mut body = object! {
355                    MERCHANTID:sub_mchid,
356                    POSID:self.sub_posid.clone(),
357                    BRANCHID:self.branchid.clone(),
358                    ORDERID:out_trade_no,
359                    PAYMENT:total_fee,
360                    CURCODE:"01",
361                    TXCODE:"530590",
362                    REMARK1:"",
363                    REMARK2:"",
364                    TYPE:"1",
365                    PUB:pubtext,
366                    GATEWAY:"0",
367                    CLIENTIP:self.client_ip.clone(),
368                    REGINFO:"",
369                    PROINFO: self.escape_unicode(description),
370                    REFERER:"",
371                    TRADE_TYPE:"",
372                    SUB_APPID: "",
373                    SUB_OPENID:sp_openid,
374                    MAC:"",
375                };
376                body["TRADE_TYPE"] = match types {
377                    Types::Jsapi => {
378                        body["SUB_APPID"] = self.appid_subscribe.clone().into();
379                        "JSAPI"
380                    }
381                    Types::MiniJsapi => {
382                        body["SUB_APPID"] = self.appid.clone().into();
383                        "MINIPRO"
384                    }
385                    _ => return Err(format!("Invalid types: {types:?}")),
386                }.into();
387                body
388            }
389            "alipay" => {
390                let body = match types {
391                    Types::MiniJsapi => object! {
392                        MERCHANTID:sub_mchid,
393                        POSID:self.sub_posid.clone(),
394                        BRANCHID:self.branchid.clone(),
395                        ORDERID:out_trade_no,
396                        PAYMENT:total_fee,
397                        CURCODE:"01",
398                        TXCODE:"530591",
399                        TRADE_TYPE:"JSAPI",
400                        USERID:sp_openid,
401                        PUB:pubtext,
402                        MAC:""
403                    },
404                    Types::H5 => object! {
405                        BRANCHID:self.branchid.clone(),
406                        MERCHANTID:sub_mchid,
407                        POSID:self.sub_posid.clone(),
408                        TXCODE:"ZFBWAP",
409                        ORDERID:out_trade_no,
410                        AMOUNT:total_fee,
411                        TIMEOUT:"",
412                        REMARK1:"",
413                        REMARK2:"",
414                        PUB:pubtext,
415                        MAC:"",
416                        SUBJECT:description,
417                        AREA_INFO:""
418                    },
419                    Types::Jsapi => object! {
420                        MERCHANTID:sub_mchid,
421                        POSID:self.sub_posid.clone(),
422                        BRANCHID:self.branchid.clone(),
423                        ORDERID:out_trade_no,
424                        PAYMENT:total_fee,
425                        CURCODE:"01",
426                        TXCODE:"530550",
427                        REMARK1:"",
428                        REMARK2:"",
429                        RETURNTYPE:"3",
430                        TIMEOUT:"",
431                        PUB:pubtext,
432                        MAC:""
433                    },
434                    _ => return Err(format!("Invalid types: {types:?}")),
435                };
436                body
437            }
438            _ => return Err(format!("Invalid channel: {channel}")),
439        };
440        match (channel, types) {
441            ("wechat", Types::Jsapi | Types::MiniJsapi) => {
442                let res = self.http(url, body.clone())?;
443                if res.has_key("PAYURL") {
444                    let url = res["PAYURL"].to_string();
445                    let mut http = br_reqwest::Client::new();
446
447                    let re = match http.post(url.as_str()).send() {
448                        Ok(e) => e,
449                        Err(e) => {
450                            return Err(e.to_string());
451                        }
452                    };
453                    let re = re.body().to_string();
454                    let res = match json::parse(&re) {
455                        Ok(e) => e,
456                        Err(_) => return Err(re)
457                    };
458                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
459                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
460                    }
461                    Ok(res)
462                } else {
463                    Err(res.to_string())
464                }
465            }
466            ("alipay", Types::MiniJsapi) => {
467                let res = self.http(url, body)?;
468                if res.has_key("PAYURL") {
469                    let url = res["PAYURL"].to_string();
470                    let mut http = br_reqwest::Client::new();
471                    let re = match http.post(url.as_str()).send() {
472                        Ok(e) => e,
473                        Err(e) => {
474                            return Err(e.to_string());
475                        }
476                    };
477                    let re = re.body().to_string();
478                    let res = match json::parse(&re) {
479                        Ok(e) => e,
480                        Err(_) => return Err(re)
481                    };
482                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
483                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
484                    }
485                    Ok(res["jsapi"].clone())
486                } else {
487                    Err(res.to_string())
488                }
489            }
490            ("alipay", Types::H5) => {
491                let res = self.http_alipay(url, body)?;
492                if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
493                    return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
494                }
495                Ok(res["form_data"].clone())
496            }
497            ("alipay", Types::Jsapi) => {
498                let res = self.http(url, body)?;
499                if res.has_key("PAYURL") {
500                    let url = res["PAYURL"].to_string();
501                    if self.debug {
502                        debug!("{url:#}");
503                    }
504                    let mut http = br_reqwest::Client::new();
505
506                    let re = match http.post(url.as_str()).send() {
507                        Ok(e) => e,
508                        Err(e) => {
509                            return Err(e.to_string());
510                        }
511                    };
512                    let re = re.body().to_string();
513                    let res = match json::parse(&re) {
514                        Ok(e) => e,
515                        Err(_) => return Err(re)
516                    };
517                    if self.debug {
518                        debug!("{res:#}");
519                    }
520                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
521                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
522                    }
523                    let r = br_crypto::encoding::urlencoding_decode(res["QRURL"].as_str().unwrap());
524                    Ok(object! {
525                        url:r.clone()
526                    })
527                } else {
528                    Err(res.to_string())
529                }
530            }
531            _ => {
532                let res = self.http(url, body)?;
533                Ok(res)
534            }
535        }
536    }
537
538    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> {
539        let body = object! {
540            MERCHANTID:sub_mchid,
541            POSID:self.sub_posid.clone(),
542            BRANCHID:self.branchid.clone(),
543            MERFLAG:"1",
544            TERMNO1:"",
545            TERMNO2:"",
546            ORDERID:out_trade_no,
547            QRCODE:auth_code,
548            AMOUNT:total_fee,
549            TXCODE:"PAY100",
550            PROINFO:description,
551            REMARK1:"",
552            REMARK2:"",
553            SMERID:"",SMERNAME:"",SMERTYPEID:"",SMERTYPE:"",TRADECODE:"",TRADENAME:"",SMEPROTYPE:"",PRONAME:""
554        };
555        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
556        let res = self.http_ccb_param(url, body)?;
557
558        match res["RESULT"].as_str().unwrap_or("N") {
559            "Y" => {
560                Ok(self.pay_micropay_query(out_trade_no, sub_mchid, channel)?)
561            }
562            "N" => {
563                Err(res["ERRMSG"].to_string())
564            }
565            "U" => {
566                Err(res.to_string())
567            }
568            "Q" => {
569                let res = PayNotify {
570                    trade_type: TradeType::MICROPAY,
571                    out_trade_no: out_trade_no.to_string(),
572                    sp_mchid: self.sp_mchid.clone(),
573                    sub_mchid: sub_mchid.to_string(),
574                    sp_appid: "".to_string(),
575                    transaction_id: res["TRACEID"].to_string(),
576                    success_time: 0,
577                    sp_openid: "".to_string(),
578                    sub_openid: "".to_string(),
579                    total: total_fee,
580                    payer_total: total_fee,
581                    currency: "CNY".to_string(),
582                    payer_currency: "CNY".to_string(),
583                    trade_state: TradeState::NOTPAY,
584                };
585                Ok(res.json())
586            }
587            _ => {
588                Err(res.to_string())
589            }
590        }
591    }
592
593    fn close(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
594        let crcode_type = match channel {
595            "wechat" => "2",
596            "alipay" => "3",
597            _ => "2"
598        };
599        let body = object! {
600            MERCHANTID:sub_mchid,
601            POSID:self.sub_posid.clone(),
602            BRANCHID:self.branchid.clone(),
603            TXCODE:"PAY103",
604            MERFLAG:"1",
605            TERMNO1:"",
606            TERMNO2:"",
607            ORDERID:out_trade_no,
608            QRCODETYPE:crcode_type,
609            REMARK1:"",
610            REMARK2:"",
611        };
612        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
613        let res = self.http_ccb_param(url, body)?;
614        match res["RESULT"].as_str().unwrap_or("N") {
615            "Y" => Ok(true.into()),
616            _ => {
617                if res["ERRMSG"].eq("服务异常,@@该笔订单不存在支付记录!@@") {
618                    return Ok(true.into());
619                }
620                if res["ERRMSG"].eq("未查询到该笔订单") {
621                    return Ok(true.into());
622                }
623                Err(res["ERRMSG"].to_string())
624            }
625        }
626    }
627
628    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
629        let today = Local::now().date_naive();
630        let date_str = today.format("%Y%m%d").to_string();
631        let order_date = &out_trade_no[0..8];
632        let kind = if date_str == order_date {
633            0
634        } else {
635            1
636        };
637
638        let mut text = self.get_xml_text("5W1002.xml");
639
640        text = text.replace("{{START}}", order_date);
641        text = text.replace("{{END}}", order_date);
642        text = text.replace("{{KIND}}", kind.to_string().as_str());
643        text = text.replace("{{ORDER}}", out_trade_no);
644
645        text = text.replace("{{POS_CODE}}", self.sp_posid.as_str());
646        text = text.replace("{{STATUS}}", "1");
647        text = text.replace("{{Mrch_No}}", sub_mchid);
648
649        let res = self.https_cert(&text)?;
650
651        if res.has_key("RETURN_MSG") {
652            if res["RETURN_MSG"].eq("流水记录不存在") {
653                let ttt = PayNotify {
654                    trade_type: TradeType::None,
655                    out_trade_no: out_trade_no.to_string(),
656                    sp_mchid: self.sp_mchid.to_string(),
657                    sub_mchid: sub_mchid.to_string(),
658                    sp_appid: "".to_string(),
659                    transaction_id: "".to_string(),
660                    success_time: 0,
661                    sp_openid: "".to_string(),
662                    sub_openid: "".to_string(),
663                    total: 0.0,
664                    payer_total: 0.0,
665                    currency: "CNY".to_string(),
666                    payer_currency: "CNY".to_string(),
667                    trade_state: TradeState::NOTPAY,
668                };
669                return Ok(ttt.json());
670            }
671            return Err(res["RETURN_MSG"].to_string());
672        }
673
674        let trade_state = match res["TX_INFO"]["LIST"]["ORDER_STATUS"].as_str().unwrap_or("") {
675            "0" => TradeState::PAYERROR,
676            "1" => TradeState::SUCCESS,
677            "2" | "5" => TradeState::USERPAYING,
678            "3" | "4" => TradeState::REFUND,
679            _ => return Err("未知状态".to_string()),
680        };
681        let data = res["TX_INFO"]["LIST"].clone();
682        let res = match trade_state {
683            TradeState::SUCCESS => {
684                PayNotify {
685                    trade_type: TradeType::None,
686                    out_trade_no: out_trade_no.to_string(),
687                    sp_mchid: self.sp_mchid.to_string(),
688                    sub_mchid: sub_mchid.to_string(),
689                    sp_appid: "".to_string(),
690                    transaction_id: data["OriOvrlsttnEV_Trck_No"].to_string(),
691                    success_time: PayNotify::datetime_to_timestamp(data["TRAN_DATE"].as_str().unwrap_or(""), "%Y-%m-%d %H:%M:%S"),
692                    sp_openid: "".to_string(),
693                    sub_openid: "".to_string(),
694                    total: data["Orig_Amt"].to_string().parse::<f64>().unwrap_or(0.0),
695                    payer_total: data["Txn_ClrgAmt"].to_string().parse::<f64>().unwrap_or(0.0),
696                    currency: "CNY".to_string(),
697                    payer_currency: "CNY".to_string(),
698                    trade_state,
699                }
700            }
701            _ => PayNotify {
702                trade_type: TradeType::None,
703                out_trade_no: out_trade_no.to_string(),
704                sp_mchid: self.sp_mchid.to_string(),
705                sub_mchid: sub_mchid.to_string(),
706                sp_appid: "".to_string(),
707                transaction_id: res["ORDER"].to_string(),
708                success_time: 0,
709                sp_openid: "".to_string(),
710                sub_openid: "".to_string(),
711                total: 0.0,
712                payer_total: 0.0,
713                currency: "CNY".to_string(),
714                payer_currency: "CNY".to_string(),
715                trade_state,
716            }
717        };
718        Ok(res.json())
719    }
720
721    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
722        let crcode_type = match channel {
723            "wechat" => "2",
724            "alipay" => "3",
725            _ => "5"
726        };
727
728        let body = object! {
729            MERCHANTID:sub_mchid,
730            POSID:self.sub_posid.clone(),
731            BRANCHID:self.branchid.clone(),
732            TXCODE:"PAY102",
733            MERFLAG:"1",
734            TERMNO1:"",
735            TERMNO2:"",
736            ORDERID:out_trade_no,
737            QRYTIME:"1",
738            QRCODETYPE:crcode_type,
739            QRCODE:"",
740            REMARK1:"",
741            REMARK2:"",
742        };
743        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
744        let res = self.http_ccb_param(url, body)?;
745
746        match res["RESULT"].as_str().unwrap_or("N") {
747            "Y" => {
748                let now = Local::now();
749                let formatted = now.format("%Y%m%d%H%M%S").to_string();
750                let transaction_id = match channel {
751                    "wechat" => res["WECHAT_NO"].to_string(),
752                    "alipay" => res["ZFB_NO"].to_string(),
753                    _ => "".to_string()
754                };
755                let res = PayNotify {
756                    trade_type: TradeType::MICROPAY,
757                    out_trade_no: out_trade_no.to_string(),
758                    sp_mchid: self.sp_mchid.clone(),
759                    sub_mchid: sub_mchid.to_string(),
760                    sp_appid: "".to_string(),
761                    transaction_id,
762                    success_time: PayNotify::datetime_to_timestamp(formatted.as_str(), "%Y%m%d%H%M%S"),
763                    sp_openid: "".to_string(),
764                    sub_openid: "".to_string(),
765                    total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
766                    payer_total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
767                    currency: "CNY".to_string(),
768                    payer_currency: "CNY".to_string(),
769                    trade_state: TradeState::SUCCESS,
770                };
771                Ok(res.json())
772            }
773            "N" => {
774                let crcode_type = match channel {
775                    "wechat" => res["WECHAT_STATE"].to_string(),
776                    "alipay" => res["ZFB_STATE"].to_string(),
777                    _ => "".to_string()
778                };
779                let res = PayNotify {
780                    trade_type: TradeType::MICROPAY,
781                    out_trade_no: out_trade_no.to_string(),
782                    sp_mchid: self.sp_mchid.clone(),
783                    sub_mchid: sub_mchid.to_string(),
784                    sp_appid: "".to_string(),
785                    transaction_id: "".to_string(),
786                    success_time: 0,
787                    sp_openid: "".to_string(),
788                    sub_openid: "".to_string(),
789                    total: 0.0,
790                    currency: "CNY".to_string(),
791                    payer_total: 0.0,
792                    payer_currency: "CNY".to_string(),
793                    trade_state: TradeState::from(crcode_type.as_str()),
794                };
795                Ok(res.json())
796            }
797            "U" => {
798                Err(res.to_string())
799            }
800            "Q" => {
801                let res = PayNotify {
802                    trade_type: TradeType::MICROPAY,
803                    out_trade_no: out_trade_no.to_string(),
804                    sp_mchid: self.sp_mchid.clone(),
805                    sub_mchid: sub_mchid.to_string(),
806                    sp_appid: "".to_string(),
807                    transaction_id: "".to_string(),
808                    success_time: 0,
809                    sp_openid: "".to_string(),
810                    sub_openid: "".to_string(),
811                    total: 0.0,
812                    payer_total: 0.0,
813                    currency: "CNY".to_string(),
814                    payer_currency: "CNY".to_string(),
815                    trade_state: TradeState::NOTPAY,
816                };
817                Ok(res.json())
818            }
819            _ => {
820                Err(res.to_string())
821            }
822        }
823    }
824
825    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
826        Err("暂未开通".to_string())
827    }
828
829    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> {
830        let mut text = self.get_xml_text("5W1024.xml");
831        text = text.replace("{{MONEY}}", amount.to_string().as_str());
832        text = text.replace("{{ORDER}}", out_trade_no);
833        text = text.replace("{{REFUND_CODE}}", out_refund_no);
834        text = text.replace("{{Mrch_No}}", sub_mchid);
835        let res = self.https_cert(&text)?;
836        if res.has_key("RETURN_MSG") {
837            error!("{:#}", res);
838            return Err(res["RETURN_MSG"].to_string());
839        }
840        info!("{:#}", res);
841        Ok(res)
842    }
843
844    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> {
845        Err("暂未开通".to_string())
846    }
847
848    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
849        Err("暂未开通".to_string())
850    }
851
852    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
853        let today = Local::now().date_naive();
854        let date_str = today.format("%Y%m%d").to_string();
855        let order_date = &out_refund_no[0..8];
856        let kind = if date_str == order_date {
857            0
858        } else {
859            1
860        };
861
862        let mut text = self.get_xml_text("5W1003.xml");
863
864        text = text.replace("{{START}}", order_date);
865        text = text.replace("{{END}}", order_date);
866        text = text.replace("{{KIND}}", kind.to_string().as_str());
867        text = text.replace("{{ORDER}}", out_refund_no);
868
869        text = text.replace("{{POS_CODE}}", self.sp_posid.as_str());
870        text = text.replace("{{STATUS}}", "1");
871        text = text.replace("{{Mrch_No}}", sub_mchid);
872
873        let res = self.https_cert(&text)?;
874
875        if res.has_key("RETURN_MSG") {
876            if res["RETURN_MSG"].eq("流水记录不存在") {
877                let res = RefundNotify {
878                    out_trade_no: trade_no.to_string(),
879                    refund_no: out_refund_no.to_string(),
880                    sp_mchid: self.sp_mchid.to_string(),
881                    sub_mchid: sub_mchid.to_string(),
882                    transaction_id: "".to_string(),
883                    refund_id: "".to_string(),
884                    success_time: 0,
885                    total: 0.0,
886                    refund: 0.0,
887                    payer_total: 0.0,
888                    payer_refund: 0.0,
889                    status: RefundStatus::None,
890                };
891                return Ok(res.json());
892            }
893            return Err(res["RETURN_MSG"].to_string());
894        }
895        Err("暂未开通".to_string())
896        //
897        //let today = Local::now().date_naive();
898        //let date_str = today.format("%Y%m%d").to_string();
899        //let body = object! {
900        //    MERCHANTID:sub_mchid,
901        //    BRANCHID:self.branchid.clone(),
902        //    POSID:self.sub_posid.clone(),
903        //    ORDERDATE:date_str,
904        //    BEGORDERTIME:"00:00:00",
905        //    ENDORDERTIME:"23:59:59",
906        //    ORDERID:out_refund_no,
907        //    QUPWD:self.sub_pass.clone(),
908        //    TXCODE:"410408",
909        //    TYPE:"1",
910        //    KIND:"0",
911        //    STATUS:"1",
912        //    SEL_TYPE:"3",
913        //    PAGE:"1",
914        //    OPERATOR:"",
915        //    CHANNEL:"",
916        //    MAC:""
917        //};
918        //let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
919        //if res["RETURN_CODE"] != "000000" {
920        //    if res["RETURN_MSG"].eq("流水记录不存在") {
921        //        let res = PayNotify {
922        //            trade_type: TradeType::None,
923        //            out_trade_no: "".to_string(),
924        //            sp_mchid: "".to_string(),
925        //            sub_mchid: "".to_string(),
926        //            sp_appid: "".to_string(),
927        //            transaction_id: "".to_string(),
928        //            success_time: 0,
929        //            sp_openid: "".to_string(),
930        //            sub_openid: "".to_string(),
931        //            total: 0.0,
932        //            payer_total: 0.0,
933        //            currency: "".to_string(),
934        //            payer_currency: "".to_string(),
935        //            trade_state: TradeState::NOTPAY,
936        //        };
937        //        return Ok(res.json());
938        //    }
939        //    return Err(res["RETURN_MSG"].to_string());
940        //}
941        //let data = res["QUERYORDER"].clone();
942        //let res = RefundNotify {
943        //    out_trade_no: trade_no.to_string(),
944        //    refund_no: out_refund_no.to_string(),
945        //    sp_mchid: "".to_string(),
946        //    sub_mchid: sub_mchid.to_string(),
947        //    transaction_id: data["ORDERID"].to_string(),
948        //    refund_id: data["refund_id"].to_string(),
949        //    success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
950        //    total: data["AMOUNT"].as_f64().unwrap_or(0.0),
951        //    payer_total: data["amount"]["total"].to_string().parse::<f64>().unwrap(),
952        //    refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
953        //    payer_refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
954        //    status: RefundStatus::from(data["STATUS"].as_str().unwrap()),
955        //};
956        //
957        //Ok(res.json())
958    }
959
960    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> {
961        todo!()
962    }
963}
964
965fn xml_element_to_json(elem: &Element) -> JsonValue {
966    let mut obj = object! {};
967    for child in &elem.children {
968        if let xmltree::XMLNode::Element(e) = child {
969            obj[e.name.clone()] = xml_element_to_json(e);
970        }
971    }
972    match elem.get_text() {
973        None => obj,
974        Some(text) => JsonValue::from(text.to_string()),
975    }
976}