br_pay/
ccbc.rs

1use base64::Engine;
2use base64::engine::general_purpose;
3use chrono::Local;
4use json::{object, JsonValue};
5use log::{debug, 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    pub query_url: String,
44}
45
46impl Ccbc {
47    pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
48        let mut mac = vec![];
49        let mut path = vec![];
50        let fields = ["MAC"];
51        for (key, value) in body.entries() {
52            if value.is_empty() && fields.contains(&key) {
53                continue;
54            }
55            if key != "PUB" {
56                path.push(format!("{key}={value}"));
57            }
58            mac.push(format!("{key}={value}"));
59        }
60
61
62        let mac_text = mac.join("&");
63        let path = path.join("&");
64        if self.debug {
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(self.query_url.as_str()).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
314    fn get_xml_text(&mut self, name: &str) -> String {
315        let res = match name {
316            "5W1002.xml" => r#"<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
317<TX>
318    <REQUEST_SN>{{REQUEST_SN}}</REQUEST_SN>
319    <CUST_ID>{{CUST_ID}}</CUST_ID>
320    <USER_ID>{{USER_ID}}</USER_ID>
321    <PASSWORD>{{PASSWORD}}</PASSWORD>
322    <TX_CODE>5W1002</TX_CODE>
323    <LANGUAGE>CN</LANGUAGE>
324    <TX_INFO>
325        <START>{{START}}</START>
326        <STARTHOUR></STARTHOUR>
327        <STARTMIN></STARTMIN>
328        <END>{{END}}</END>
329        <ENDHOUR></ENDHOUR>
330        <ENDMIN></ENDMIN>
331        <KIND>{{KIND}}</KIND>
332        <ORDER>{{ORDER}}</ORDER>
333        <ACCOUNT></ACCOUNT>
334        <DEXCEL>1</DEXCEL>
335        <MONEY></MONEY>
336        <NORDERBY>2</NORDERBY>
337        <PAGE>1</PAGE>
338        <POS_CODE>{{POS_CODE}}</POS_CODE>
339        <STATUS>{{STATUS}}</STATUS>
340        <Mrch_No>{{Mrch_No}}</Mrch_No>
341        <TXN_TPCD></TXN_TPCD>
342    </TX_INFO>
343</TX>"#,
344            "5W1003.xml" => r#"<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
345<TX>
346    <REQUEST_SN>{{REQUEST_SN}}</REQUEST_SN>
347    <CUST_ID>{{CUST_ID}}</CUST_ID>
348    <USER_ID>{{USER_ID}}</USER_ID>
349    <PASSWORD>{{PASSWORD}}</PASSWORD>
350    <TX_CODE>5W1003</TX_CODE>
351    <LANGUAGE>CN</LANGUAGE>
352    <TX_INFO>
353        <START>{{START}}</START>
354        <STARTHOUR></STARTHOUR>
355        <STARTMIN></STARTMIN>
356        <END>{{END}}</END>
357        <ENDHOUR></ENDHOUR>
358        <ENDMIN></ENDMIN>
359        <KIND>{{KIND}}</KIND>
360        <ORDER>{{ORDER}}</ORDER>
361        <ACCOUNT></ACCOUNT>
362        <MONEY></MONEY>
363        <NORDERBY>2</NORDERBY>
364        <PAGE>1</PAGE>
365        <POS_CODE>{{POS_CODE}}</POS_CODE>
366        <STATUS>{{STATUS}}</STATUS>
367        <Mrch_No>{{Mrch_No}}</Mrch_No>
368        <TXN_TPCD></TXN_TPCD>
369    </TX_INFO>
370</TX>"#,
371            "5W1024.xml" => r#"<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
372<TX>
373    <REQUEST_SN>{{REQUEST_SN}}</REQUEST_SN>
374    <CUST_ID>{{CUST_ID}}</CUST_ID>
375    <USER_ID>{{USER_ID}}</USER_ID>
376    <PASSWORD>{{PASSWORD}}</PASSWORD>
377    <TX_CODE>5W1024</TX_CODE>
378    <LANGUAGE>CN</LANGUAGE>
379    <TX_INFO>
380        <MONEY>{{MONEY}}</MONEY>
381        <ORDER>{{ORDER}}</ORDER>
382        <REFUND_CODE>{{REFUND_CODE}}</REFUND_CODE>
383        <Mrch_No>{{Mrch_No}}</Mrch_No>
384    </TX_INFO>
385    <SIGN_INFO></SIGN_INFO>
386    <SIGNCERT></SIGNCERT>
387</TX>"#,
388            _ => r#""#
389        };
390
391        let xml_text = res.as_bytes().to_vec();
392        let mut text = unsafe { String::from_utf8_unchecked(xml_text) };
393        let mut rng = rng();
394        let num: String = (0..14).map(|_| rng.random_range(0..10).to_string()).collect();
395        text = text.replace("{{REQUEST_SN}}", num.as_str());
396        text = text.replace("{{CUST_ID}}", self.sp_mchid.as_str());
397        text = text.replace("{{USER_ID}}", self.sp_user_id.as_str());
398        text = text.replace("{{PASSWORD}}", self.sp_pass.as_str());
399        text
400    }
401}
402impl PayMode for Ccbc {
403    fn check(&mut self) -> Result<bool, String> {
404        todo!()
405    }
406
407    fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
408        todo!()
409    }
410
411    fn config(&mut self) -> JsonValue {
412        todo!()
413    }
414
415
416    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> {
417        if self.public_key.is_empty() || self.public_key.len() < 30 {
418            return Err(String::from("Public key is empty"));
419        }
420        let pubtext = self.public_key[self.public_key.len() - 30..].to_string();
421
422        let url = match channel {
423            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
424            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
425            _ => return Err(format!("Invalid channel: {channel}")),
426        };
427        let body = match channel {
428            "wechat" => {
429                let mut body = object! {
430                    MERCHANTID:sub_mchid,
431                    POSID:self.sub_posid.clone(),
432                    BRANCHID:self.branchid.clone(),
433                    ORDERID:out_trade_no,
434                    PAYMENT:total_fee,
435                    CURCODE:"01",
436                    TXCODE:"530590",
437                    REMARK1:"",
438                    REMARK2:"",
439                    TYPE:"1",
440                    PUB:pubtext,
441                    GATEWAY:"0",
442                    CLIENTIP:self.client_ip.clone(),
443                    REGINFO:"",
444                    PROINFO: self.escape_unicode(description),
445                    REFERER:"",
446                    TRADE_TYPE:"",
447                    SUB_APPID: "",
448                    SUB_OPENID:sp_openid,
449                    MAC:"",
450                };
451                body["TRADE_TYPE"] = match types {
452                    Types::Jsapi => {
453                        body["SUB_APPID"] = self.appid_subscribe.clone().into();
454                        "JSAPI"
455                    }
456                    Types::MiniJsapi => {
457                        body["SUB_APPID"] = self.appid.clone().into();
458                        "MINIPRO"
459                    }
460                    _ => return Err(format!("Invalid types: {types:?}")),
461                }.into();
462                body
463            }
464            "alipay" => {
465                let body = match types {
466                    Types::MiniJsapi => object! {
467                        MERCHANTID:sub_mchid,
468                        POSID:self.sub_posid.clone(),
469                        BRANCHID:self.branchid.clone(),
470                        ORDERID:out_trade_no,
471                        PAYMENT:total_fee,
472                        CURCODE:"01",
473                        TXCODE:"530591",
474                        TRADE_TYPE:"JSAPI",
475                        USERID:sp_openid,
476                        PUB:pubtext,
477                        MAC:""
478                    },
479                    Types::H5 => object! {
480                        BRANCHID:self.branchid.clone(),
481                        MERCHANTID:sub_mchid,
482                        POSID:self.sub_posid.clone(),
483                        TXCODE:"ZFBWAP",
484                        ORDERID:out_trade_no,
485                        AMOUNT:total_fee,
486                        TIMEOUT:"",
487                        REMARK1:"",
488                        REMARK2:"",
489                        PUB:pubtext,
490                        MAC:"",
491                        SUBJECT:description,
492                        AREA_INFO:""
493                    },
494                    Types::Jsapi => object! {
495                        MERCHANTID:sub_mchid,
496                        POSID:self.sub_posid.clone(),
497                        BRANCHID:self.branchid.clone(),
498                        ORDERID:out_trade_no,
499                        PAYMENT:total_fee,
500                        CURCODE:"01",
501                        TXCODE:"530550",
502                        REMARK1:"",
503                        REMARK2:"",
504                        RETURNTYPE:"3",
505                        TIMEOUT:"",
506                        PUB:pubtext,
507                        MAC:""
508                    },
509                    _ => return Err(format!("Invalid types: {types:?}")),
510                };
511                body
512            }
513            _ => return Err(format!("Invalid channel: {channel}")),
514        };
515        match (channel, types) {
516            ("wechat", Types::Jsapi | Types::MiniJsapi) => {
517                let res = self.http(url, body.clone())?;
518                if res.has_key("PAYURL") {
519                    let url = res["PAYURL"].to_string();
520                    let mut http = br_reqwest::Client::new();
521
522                    let re = match http.post(url.as_str()).send() {
523                        Ok(e) => e,
524                        Err(e) => {
525                            return Err(e.to_string());
526                        }
527                    };
528                    let re = re.body().to_string();
529                    let res = match json::parse(&re) {
530                        Ok(e) => e,
531                        Err(_) => return Err(re)
532                    };
533                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
534                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
535                    }
536                    Ok(res)
537                } else {
538                    Err(res.to_string())
539                }
540            }
541            ("alipay", Types::MiniJsapi) => {
542                let res = self.http(url, body)?;
543                if res.has_key("PAYURL") {
544                    let url = res["PAYURL"].to_string();
545                    let mut http = br_reqwest::Client::new();
546                    let re = match http.post(url.as_str()).send() {
547                        Ok(e) => e,
548                        Err(e) => {
549                            return Err(e.to_string());
550                        }
551                    };
552                    let re = re.body().to_string();
553                    let res = match json::parse(&re) {
554                        Ok(e) => e,
555                        Err(_) => return Err(re)
556                    };
557                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
558                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
559                    }
560                    Ok(res["jsapi"].clone())
561                } else {
562                    Err(res.to_string())
563                }
564            }
565            ("alipay", Types::H5) => {
566                let res = self.http_alipay(url, body)?;
567                if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
568                    return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
569                }
570                Ok(res["form_data"].clone())
571            }
572            ("alipay", Types::Jsapi) => {
573                let res = self.http(url, body)?;
574                if res.has_key("PAYURL") {
575                    let url = res["PAYURL"].to_string();
576                    if self.debug {
577                        debug!("{url:#}");
578                    }
579                    let mut http = br_reqwest::Client::new();
580
581                    let re = match http.post(url.as_str()).send() {
582                        Ok(e) => e,
583                        Err(e) => {
584                            return Err(e.to_string());
585                        }
586                    };
587                    let re = re.body().to_string();
588                    let res = match json::parse(&re) {
589                        Ok(e) => e,
590                        Err(_) => return Err(re)
591                    };
592                    if self.debug {
593                        debug!("{res:#}");
594                    }
595                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
596                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
597                    }
598                    let r = br_crypto::encoding::urlencoding_decode(res["QRURL"].as_str().unwrap());
599                    Ok(object! {
600                        url:r.clone()
601                    })
602                } else {
603                    Err(res.to_string())
604                }
605            }
606            _ => {
607                let res = self.http(url, body)?;
608                Ok(res)
609            }
610        }
611    }
612
613    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> {
614        let body = object! {
615            MERCHANTID:sub_mchid,
616            POSID:self.sub_posid.clone(),
617            BRANCHID:self.branchid.clone(),
618            MERFLAG:"1",
619            TERMNO1:"",
620            TERMNO2:"",
621            ORDERID:out_trade_no,
622            QRCODE:auth_code,
623            AMOUNT:total_fee,
624            TXCODE:"PAY100",
625            PROINFO:description,
626            REMARK1:"",
627            REMARK2:"",
628            SMERID:"",SMERNAME:"",SMERTYPEID:"",SMERTYPE:"",TRADECODE:"",TRADENAME:"",SMEPROTYPE:"",PRONAME:""
629        };
630        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
631        let res = self.http_ccb_param(url, body)?;
632
633        match res["RESULT"].as_str().unwrap_or("N") {
634            "Y" => {
635                Ok(self.pay_micropay_query(out_trade_no, sub_mchid, channel)?)
636            }
637            "N" => {
638                Err(res["ERRMSG"].to_string())
639            }
640            "U" => {
641                Err(res.to_string())
642            }
643            "Q" => {
644                let res = PayNotify {
645                    trade_type: TradeType::MICROPAY,
646                    out_trade_no: out_trade_no.to_string(),
647                    sp_mchid: self.sp_mchid.clone(),
648                    sub_mchid: sub_mchid.to_string(),
649                    sp_appid: "".to_string(),
650                    transaction_id: res["TRACEID"].to_string(),
651                    success_time: 0,
652                    sp_openid: "".to_string(),
653                    sub_openid: "".to_string(),
654                    total: total_fee,
655                    payer_total: total_fee,
656                    currency: "CNY".to_string(),
657                    payer_currency: "CNY".to_string(),
658                    trade_state: TradeState::NOTPAY,
659                };
660                Ok(res.json())
661            }
662            _ => {
663                Err(res.to_string())
664            }
665        }
666    }
667
668    fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
669        let today = Local::now().date_naive();
670        let date_str = today.format("%Y%m%d").to_string();
671        let order_date = &out_trade_no[0..8];
672        let kind = if date_str == order_date {
673            0
674        } else {
675            1
676        };
677
678        let mut text = self.get_xml_text("5W1002.xml");
679
680        text = text.replace("{{START}}", order_date);
681        text = text.replace("{{END}}", order_date);
682        text = text.replace("{{KIND}}", kind.to_string().as_str());
683        text = text.replace("{{ORDER}}", out_trade_no);
684
685        text = text.replace("{{POS_CODE}}", "");
686        text = text.replace("{{STATUS}}", "1");
687        text = text.replace("{{Mrch_No}}", sub_mchid);
688
689        let res = self.https_cert(&text)?;
690        if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
691            if res["RETURN_MSG"].eq("流水记录不存在") {
692                return Ok(true.into());
693            }
694            return Err(res["RETURN_MSG"].to_string());
695        }
696        let trade_state = match res["TX_INFO"]["LIST"]["ORDER_STATUS"].as_str().unwrap_or("") {
697            "0" => TradeState::PAYERROR,
698            "1" => TradeState::SUCCESS,
699            "2" | "5" => TradeState::USERPAYING,
700            "3" | "4" => TradeState::REFUND,
701            _ => return Err("未知状态".to_string()),
702        };
703        match trade_state {
704            TradeState::SUCCESS => Ok(false.into()),
705            TradeState::REFUND => Ok(false.into()),
706            TradeState::NOTPAY => Ok(true.into()),
707            TradeState::CLOSED => Ok(true.into()),
708            TradeState::REVOKED => Ok(true.into()),
709            TradeState::USERPAYING => Ok(false.into()),
710            TradeState::PAYERROR => Ok(true.into()),
711            TradeState::None => Ok(true.into()),
712        }
713    }
714
715    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
716        let today = Local::now().date_naive();
717        let date_str = today.format("%Y%m%d").to_string();
718        let order_date = &out_trade_no[0..8];
719        let kind = if date_str == order_date {
720            0
721        } else {
722            1
723        };
724
725        let mut text = self.get_xml_text("5W1002.xml");
726
727        text = text.replace("{{START}}", order_date);
728        text = text.replace("{{END}}", order_date);
729        text = text.replace("{{KIND}}", kind.to_string().as_str());
730        text = text.replace("{{ORDER}}", out_trade_no);
731
732        text = text.replace("{{POS_CODE}}", "");
733        text = text.replace("{{STATUS}}", "1");
734        text = text.replace("{{Mrch_No}}", sub_mchid);
735        if self.debug{
736            info!("支付查询请求: {text}");
737        }
738        let res = self.https_cert(&text)?;
739        if self.debug{
740            info!("支付查询响应: {res}");
741        }
742        if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
743            if res["RETURN_MSG"].eq("流水记录不存在") {
744                let ttt = PayNotify {
745                    trade_type: TradeType::None,
746                    out_trade_no: out_trade_no.to_string(),
747                    sp_mchid: self.sp_mchid.to_string(),
748                    sub_mchid: sub_mchid.to_string(),
749                    sp_appid: "".to_string(),
750                    transaction_id: "".to_string(),
751                    success_time: 0,
752                    sp_openid: "".to_string(),
753                    sub_openid: "".to_string(),
754                    total: 0.0,
755                    payer_total: 0.0,
756                    currency: "CNY".to_string(),
757                    payer_currency: "CNY".to_string(),
758                    trade_state: TradeState::NOTPAY,
759                };
760                return Ok(ttt.json());
761            }
762            return Err(res["RETURN_MSG"].to_string());
763        }
764        let trade_state = match res["TX_INFO"]["LIST"]["ORDER_STATUS"].as_str().unwrap_or("") {
765            "0" => TradeState::PAYERROR,
766            "1" => TradeState::SUCCESS,
767            "2" | "5" => TradeState::USERPAYING,
768            "3" | "4" => TradeState::REFUND,
769            _ => return Err("未知状态".to_string()),
770        };
771        let data = res["TX_INFO"]["LIST"].clone();
772        let res = match trade_state {
773            TradeState::SUCCESS => {
774                PayNotify {
775                    trade_type: TradeType::None,
776                    out_trade_no: out_trade_no.to_string(),
777                    sp_mchid: self.sp_mchid.to_string(),
778                    sub_mchid: sub_mchid.to_string(),
779                    sp_appid: "".to_string(),
780                    transaction_id: data["OriOvrlsttnEV_Trck_No"].to_string(),
781                    success_time: PayNotify::datetime_to_timestamp(data["TRAN_DATE"].as_str().unwrap_or(""), "%Y-%m-%d %H:%M:%S"),
782                    sp_openid: "".to_string(),
783                    sub_openid: "".to_string(),
784                    total: data["Orig_Amt"].to_string().parse::<f64>().unwrap_or(0.0),
785                    payer_total: data["Txn_ClrgAmt"].to_string().parse::<f64>().unwrap_or(0.0),
786                    currency: "CNY".to_string(),
787                    payer_currency: "CNY".to_string(),
788                    trade_state,
789                }
790            }
791            _ => PayNotify {
792                trade_type: TradeType::None,
793                out_trade_no: out_trade_no.to_string(),
794                sp_mchid: self.sp_mchid.to_string(),
795                sub_mchid: sub_mchid.to_string(),
796                sp_appid: "".to_string(),
797                transaction_id: res["ORDER"].to_string(),
798                success_time: 0,
799                sp_openid: "".to_string(),
800                sub_openid: "".to_string(),
801                total: 0.0,
802                payer_total: 0.0,
803                currency: "CNY".to_string(),
804                payer_currency: "CNY".to_string(),
805                trade_state,
806            }
807        };
808        Ok(res.json())
809    }
810
811    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
812        let crcode_type = match channel {
813            "wechat" => "2",
814            "alipay" => "3",
815            _ => "5"
816        };
817
818        let body = object! {
819            MERCHANTID:sub_mchid,
820            POSID:self.sub_posid.clone(),
821            BRANCHID:self.branchid.clone(),
822            TXCODE:"PAY102",
823            MERFLAG:"1",
824            TERMNO1:"",
825            TERMNO2:"",
826            ORDERID:out_trade_no,
827            QRYTIME:"1",
828            QRCODETYPE:crcode_type,
829            QRCODE:"",
830            REMARK1:"",
831            REMARK2:"",
832        };
833        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
834        let res = self.http_ccb_param(url, body)?;
835
836        match res["RESULT"].as_str().unwrap_or("N") {
837            "Y" => {
838                let now = Local::now();
839                let formatted = now.format("%Y%m%d%H%M%S").to_string();
840                let transaction_id = match channel {
841                    "wechat" => res["WECHAT_NO"].to_string(),
842                    "alipay" => res["ZFB_NO"].to_string(),
843                    _ => "".to_string()
844                };
845                let res = PayNotify {
846                    trade_type: TradeType::MICROPAY,
847                    out_trade_no: out_trade_no.to_string(),
848                    sp_mchid: self.sp_mchid.clone(),
849                    sub_mchid: sub_mchid.to_string(),
850                    sp_appid: "".to_string(),
851                    transaction_id,
852                    success_time: PayNotify::datetime_to_timestamp(formatted.as_str(), "%Y%m%d%H%M%S"),
853                    sp_openid: "".to_string(),
854                    sub_openid: "".to_string(),
855                    total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
856                    payer_total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
857                    currency: "CNY".to_string(),
858                    payer_currency: "CNY".to_string(),
859                    trade_state: TradeState::SUCCESS,
860                };
861                Ok(res.json())
862            }
863            "N" => {
864                let crcode_type = match channel {
865                    "wechat" => res["WECHAT_STATE"].to_string(),
866                    "alipay" => res["ZFB_STATE"].to_string(),
867                    _ => "".to_string()
868                };
869                let res = PayNotify {
870                    trade_type: TradeType::MICROPAY,
871                    out_trade_no: out_trade_no.to_string(),
872                    sp_mchid: self.sp_mchid.clone(),
873                    sub_mchid: sub_mchid.to_string(),
874                    sp_appid: "".to_string(),
875                    transaction_id: "".to_string(),
876                    success_time: 0,
877                    sp_openid: "".to_string(),
878                    sub_openid: "".to_string(),
879                    total: 0.0,
880                    currency: "CNY".to_string(),
881                    payer_total: 0.0,
882                    payer_currency: "CNY".to_string(),
883                    trade_state: TradeState::from(crcode_type.as_str()),
884                };
885                Ok(res.json())
886            }
887            "U" => {
888                Err(res.to_string())
889            }
890            "Q" => {
891                let res = PayNotify {
892                    trade_type: TradeType::MICROPAY,
893                    out_trade_no: out_trade_no.to_string(),
894                    sp_mchid: self.sp_mchid.clone(),
895                    sub_mchid: sub_mchid.to_string(),
896                    sp_appid: "".to_string(),
897                    transaction_id: "".to_string(),
898                    success_time: 0,
899                    sp_openid: "".to_string(),
900                    sub_openid: "".to_string(),
901                    total: 0.0,
902                    payer_total: 0.0,
903                    currency: "CNY".to_string(),
904                    payer_currency: "CNY".to_string(),
905                    trade_state: TradeState::NOTPAY,
906                };
907                Ok(res.json())
908            }
909            _ => {
910                Err(res.to_string())
911            }
912        }
913    }
914
915    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
916        Err("暂未开通".to_string())
917    }
918
919    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> {
920        let mut text = self.get_xml_text("5W1024.xml");
921        text = text.replace("{{MONEY}}", amount.to_string().as_str());
922        text = text.replace("{{ORDER}}", out_trade_no);
923        text = text.replace("{{REFUND_CODE}}", out_refund_no);
924        text = text.replace("{{Mrch_No}}", sub_mchid);
925        let res = self.https_cert(&text)?;
926        if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
927            return Err(res["RETURN_MSG"].to_string());
928        }
929        if !res.has_key("TX_INFO") {
930            return Err(res["RETURN_MSG"].to_string());
931        }
932        let timestamp = Local::now().timestamp();
933        let info = object! {
934                    refund_id:out_refund_no,
935                    status:"已退款",
936                    success_time:timestamp,
937                    out_refund_no: out_refund_no,
938                };
939        Ok(info)
940    }
941
942    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> {
943        Err("暂未开通".to_string())
944    }
945
946    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
947        Err("暂未开通".to_string())
948    }
949
950    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
951        let today = Local::now().date_naive();
952        let date_str = today.format("%Y%m%d").to_string();
953        let order_date = &out_refund_no[0..8];
954        let kind = if date_str == order_date {
955            0
956        } else {
957            1
958        };
959
960        let mut text = self.get_xml_text("5W1003.xml");
961
962        text = text.replace("{{START}}", order_date);
963        text = text.replace("{{END}}", order_date);
964        text = text.replace("{{KIND}}", kind.to_string().as_str());
965        text = text.replace("{{ORDER}}", out_refund_no);
966
967        text = text.replace("{{POS_CODE}}", "");
968        text = text.replace("{{STATUS}}", "1");
969        text = text.replace("{{Mrch_No}}", sub_mchid);
970
971        let res = self.https_cert(&text)?;
972
973        if res.has_key("RETURN_MSG") {
974            if res["RETURN_MSG"].eq("流水记录不存在") {
975                let res = RefundNotify {
976                    out_trade_no: trade_no.to_string(),
977                    refund_no: out_refund_no.to_string(),
978                    sp_mchid: self.sp_mchid.to_string(),
979                    sub_mchid: sub_mchid.to_string(),
980                    transaction_id: "".to_string(),
981                    refund_id: "".to_string(),
982                    success_time: 0,
983                    total: 0.0,
984                    refund: 0.0,
985                    payer_total: 0.0,
986                    payer_refund: 0.0,
987                    status: RefundStatus::None,
988                };
989                return Ok(res.json());
990            }
991            return Err(res["RETURN_MSG"].to_string());
992        }
993        Err("暂未开通".to_string())
994        //
995        //let today = Local::now().date_naive();
996        //let date_str = today.format("%Y%m%d").to_string();
997        //let body = object! {
998        //    MERCHANTID:sub_mchid,
999        //    BRANCHID:self.branchid.clone(),
1000        //    POSID:self.sub_posid.clone(),
1001        //    ORDERDATE:date_str,
1002        //    BEGORDERTIME:"00:00:00",
1003        //    ENDORDERTIME:"23:59:59",
1004        //    ORDERID:out_refund_no,
1005        //    QUPWD:self.sub_pass.clone(),
1006        //    TXCODE:"410408",
1007        //    TYPE:"1",
1008        //    KIND:"0",
1009        //    STATUS:"1",
1010        //    SEL_TYPE:"3",
1011        //    PAGE:"1",
1012        //    OPERATOR:"",
1013        //    CHANNEL:"",
1014        //    MAC:""
1015        //};
1016        //let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
1017        //if res["RETURN_CODE"] != "000000" {
1018        //    if res["RETURN_MSG"].eq("流水记录不存在") {
1019        //        let res = PayNotify {
1020        //            trade_type: TradeType::None,
1021        //            out_trade_no: "".to_string(),
1022        //            sp_mchid: "".to_string(),
1023        //            sub_mchid: "".to_string(),
1024        //            sp_appid: "".to_string(),
1025        //            transaction_id: "".to_string(),
1026        //            success_time: 0,
1027        //            sp_openid: "".to_string(),
1028        //            sub_openid: "".to_string(),
1029        //            total: 0.0,
1030        //            payer_total: 0.0,
1031        //            currency: "".to_string(),
1032        //            payer_currency: "".to_string(),
1033        //            trade_state: TradeState::NOTPAY,
1034        //        };
1035        //        return Ok(res.json());
1036        //    }
1037        //    return Err(res["RETURN_MSG"].to_string());
1038        //}
1039        //let data = res["QUERYORDER"].clone();
1040        //let res = RefundNotify {
1041        //    out_trade_no: trade_no.to_string(),
1042        //    refund_no: out_refund_no.to_string(),
1043        //    sp_mchid: "".to_string(),
1044        //    sub_mchid: sub_mchid.to_string(),
1045        //    transaction_id: data["ORDERID"].to_string(),
1046        //    refund_id: data["refund_id"].to_string(),
1047        //    success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
1048        //    total: data["AMOUNT"].as_f64().unwrap_or(0.0),
1049        //    payer_total: data["amount"]["total"].to_string().parse::<f64>().unwrap(),
1050        //    refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
1051        //    payer_refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
1052        //    status: RefundStatus::from(data["STATUS"].as_str().unwrap()),
1053        //};
1054        //
1055        //Ok(res.json())
1056    }
1057
1058    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> {
1059        todo!()
1060    }
1061}
1062
1063fn xml_element_to_json(elem: &Element) -> JsonValue {
1064    let mut obj = object! {};
1065    for child in &elem.children {
1066        if let xmltree::XMLNode::Element(e) = child {
1067            obj[e.name.clone()] = xml_element_to_json(e);
1068        }
1069    }
1070    match elem.get_text() {
1071        None => obj,
1072        Some(text) => JsonValue::from(text.to_string()),
1073    }
1074}