br_pay/
ccbc.rs

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