br_pay/
ccbc.rs

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