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(url, body)?;
481                if res.has_key("PAYURL") {
482                    let url = res["PAYURL"].to_string();
483                    let mut http = br_reqwest::Client::new();
484                    let re = match http.post(url.as_str()).send() {
485                        Ok(e) => e,
486                        Err(e) => {
487                            return Err(e.to_string());
488                        }
489                    };
490                    let re = re.body().to_string();
491                    let res = match json::parse(&re) {
492                        Ok(e) => e,
493                        Err(_) => return Err(re)
494                    };
495                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
496                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
497                    }
498                    Ok(res["jsapi"].clone())
499                } else {
500                    Err(res.to_string())
501                }
502            }
503            ("alipay", Types::H5) => {
504                let res = self.http_alipay(url, body)?;
505                if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
506                    return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
507                }
508                Ok(res["form_data"].clone())
509            }
510            ("alipay", Types::Jsapi) => {
511                let res = self.http(url, body)?;
512                if res.has_key("PAYURL") {
513                    let url = res["PAYURL"].to_string();
514                    if self.debug {
515                        debug!("{url:#}");
516                    }
517                    let mut http = br_reqwest::Client::new();
518
519                    let re = match http.post(url.as_str()).send() {
520                        Ok(e) => e,
521                        Err(e) => {
522                            return Err(e.to_string());
523                        }
524                    };
525                    let re = re.body().to_string();
526                    let res = match json::parse(&re) {
527                        Ok(e) => e,
528                        Err(_) => return Err(re)
529                    };
530                    if self.debug {
531                        debug!("{res:#}");
532                    }
533                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
534                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
535                    }
536                    let r = br_crypto::encoding::urlencoding_decode(res["QRURL"].as_str().unwrap());
537                    Ok(object! {
538                        url:r.clone()
539                    })
540                } else {
541                    Err(res.to_string())
542                }
543            }
544            _ => {
545                let res = self.http(url, body)?;
546                Ok(res)
547            }
548        }
549    }
550
551    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> {
552        let body = object! {
553            MERCHANTID:sub_mchid,
554            POSID:self.posid.clone(),
555            BRANCHID:self.branchid.clone(),
556            MERFLAG:"1",
557            TERMNO1:"",
558            TERMNO2:"",
559            ORDERID:out_trade_no,
560            QRCODE:auth_code,
561            AMOUNT:total_fee,
562            TXCODE:"PAY100",
563            PROINFO:description,
564            REMARK1:"",
565            REMARK2:"",
566            SMERID:"",SMERNAME:"",SMERTYPEID:"",SMERTYPE:"",TRADECODE:"",TRADENAME:"",SMEPROTYPE:"",PRONAME:""
567        };
568        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
569        let res = self.http_ccb_param(url, body)?;
570
571        match res["RESULT"].as_str().unwrap_or("N") {
572            "Y" => {
573                Ok(self.pay_micropay_query(out_trade_no, sub_mchid, channel)?)
574            }
575            "N" => {
576                Err(res["ERRMSG"].to_string())
577            }
578            "U" => {
579                Err(res.to_string())
580            }
581            "Q" => {
582                let res = PayNotify {
583                    trade_type: TradeType::MICROPAY,
584                    out_trade_no: out_trade_no.to_string(),
585                    sp_mchid: self.sp_mchid.clone(),
586                    sub_mchid: sub_mchid.to_string(),
587                    sp_appid: "".to_string(),
588                    transaction_id: res["TRACEID"].to_string(),
589                    success_time: 0,
590                    sp_openid: "".to_string(),
591                    sub_openid: "".to_string(),
592                    total: total_fee,
593                    payer_total: total_fee,
594                    currency: "CNY".to_string(),
595                    payer_currency: "CNY".to_string(),
596                    trade_state: TradeState::NOTPAY,
597                };
598                Ok(res.json())
599            }
600            _ => {
601                Err(res.to_string())
602            }
603        }
604    }
605
606    fn close(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
607        let crcode_type = match channel {
608            "wechat" => "2",
609            "alipay" => "3",
610            _ => "2"
611        };
612        let body = object! {
613            MERCHANTID:sub_mchid,
614            POSID:self.posid.clone(),
615            BRANCHID:self.branchid.clone(),
616            TXCODE:"PAY103",
617            MERFLAG:"1",
618            TERMNO1:"",
619            TERMNO2:"",
620            ORDERID:out_trade_no,
621            QRCODETYPE:crcode_type,
622            REMARK1:"",
623            REMARK2:"",
624        };
625        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
626        let res = self.http_ccb_param(url, body)?;
627        match res["RESULT"].as_str().unwrap_or("N") {
628            "Y" => Ok(true.into()),
629            _ => {
630                if res["ERRMSG"].to_string().contains("服务异常,@@该笔订单不存在支付记录!@@") {
631                    return Ok(true.into());
632                }
633                Err(res["ERRMSG"].to_string())
634            }
635        }
636    }
637
638    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
639        let today = Local::now().date_naive();
640        let date_str = today.format("%Y%m%d").to_string();
641
642        let order_date = &out_trade_no[0..8];
643
644        let kind = if date_str == order_date {
645            0
646        } else {
647            1
648        };
649
650        let body = object! {
651            MERCHANTID:sub_mchid,
652            BRANCHID:self.branchid.clone(),
653            POSID:self.posid.clone(),
654            ORDERDATE:order_date,
655            BEGORDERTIME:"00:00:00",
656            ENDORDERTIME:"23:59:59",
657            ORDERID:out_trade_no,
658            QUPWD:self.pass.clone(),
659            TXCODE:"410408",
660            TYPE:"0",
661            KIND:kind,
662            STATUS:"1",
663            SEL_TYPE:"3",
664            PAGE:"1",
665            OPERATOR:"",
666            CHANNEL:"",
667            MAC:""
668        };
669        let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
670        if res["RETURN_CODE"] != "000000" {
671            if res["RETURN_MSG"].eq("流水记录不存在") {
672                let res = PayNotify {
673                    trade_type: TradeType::None,
674                    out_trade_no: "".to_string(),
675                    sp_mchid: "".to_string(),
676                    sub_mchid: "".to_string(),
677                    sp_appid: "".to_string(),
678                    transaction_id: "".to_string(),
679                    success_time: 0,
680                    sp_openid: "".to_string(),
681                    sub_openid: "".to_string(),
682                    total: 0.0,
683                    payer_total: 0.0,
684                    currency: "".to_string(),
685                    payer_currency: "".to_string(),
686                    trade_state: TradeState::NOTPAY,
687                };
688                return Ok(res.json());
689            }
690            return Err(res["RETURN_MSG"].to_string());
691        }
692        let data = res["QUERYORDER"].clone();
693        let res = PayNotify {
694            trade_type: TradeType::None,
695            out_trade_no: data["ORDERID"].to_string(),
696            sp_mchid: "".to_string(),
697            sub_mchid: sub_mchid.to_string(),
698            sp_appid: "".to_string(),
699            transaction_id: data["ORDERID"].to_string(),
700            success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
701            sp_openid: "".to_string(),
702            sub_openid: "".to_string(),
703            total: data["AMOUNT"].as_f64().unwrap_or(0.0),
704            currency: "CNY".to_string(),
705            payer_total: data["AMOUNT"].as_f64().unwrap_or(0.0),
706            payer_currency: "CNY".to_string(),
707            trade_state: TradeState::from(data["STATUS"].as_str().unwrap()),
708        };
709        Ok(res.json())
710    }
711
712    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
713        let crcode_type = match channel {
714            "wechat" => "2",
715            "alipay" => "3",
716            _ => "5"
717        };
718
719        let body = object! {
720            MERCHANTID:sub_mchid,
721            POSID:self.posid.clone(),
722            BRANCHID:self.branchid.clone(),
723            TXCODE:"PAY102",
724            MERFLAG:"1",
725            TERMNO1:"",
726            TERMNO2:"",
727            ORDERID:out_trade_no,
728            QRYTIME:"1",
729            QRCODETYPE:crcode_type,
730            QRCODE:"",
731            REMARK1:"",
732            REMARK2:"",
733        };
734        let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
735        let res = self.http_ccb_param(url, body)?;
736
737        match res["RESULT"].as_str().unwrap_or("N") {
738            "Y" => {
739                let now = Local::now();
740                let formatted = now.format("%Y%m%d%H%M%S").to_string();
741                let transaction_id = match channel {
742                    "wechat" => res["WECHAT_NO"].to_string(),
743                    "alipay" => res["ZFB_NO"].to_string(),
744                    _ => "".to_string()
745                };
746                let res = PayNotify {
747                    trade_type: TradeType::MICROPAY,
748                    out_trade_no: out_trade_no.to_string(),
749                    sp_mchid: self.sp_mchid.clone(),
750                    sub_mchid: sub_mchid.to_string(),
751                    sp_appid: "".to_string(),
752                    transaction_id,
753                    success_time: PayNotify::datetime_to_timestamp(formatted.as_str(), "%Y%m%d%H%M%S"),
754                    sp_openid: "".to_string(),
755                    sub_openid: "".to_string(),
756                    total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
757                    payer_total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
758                    currency: "CNY".to_string(),
759                    payer_currency: "CNY".to_string(),
760                    trade_state: TradeState::SUCCESS,
761                };
762                Ok(res.json())
763            }
764            "N" => {
765                let crcode_type = match channel {
766                    "wechat" => res["WECHAT_STATE"].to_string(),
767                    "alipay" => res["ZFB_STATE"].to_string(),
768                    _ => "".to_string()
769                };
770                let res = PayNotify {
771                    trade_type: TradeType::MICROPAY,
772                    out_trade_no: out_trade_no.to_string(),
773                    sp_mchid: self.sp_mchid.clone(),
774                    sub_mchid: sub_mchid.to_string(),
775                    sp_appid: "".to_string(),
776                    transaction_id: "".to_string(),
777                    success_time: 0,
778                    sp_openid: "".to_string(),
779                    sub_openid: "".to_string(),
780                    total: 0.0,
781                    currency: "CNY".to_string(),
782                    payer_total: 0.0,
783                    payer_currency: "CNY".to_string(),
784                    trade_state: TradeState::from(crcode_type.as_str()),
785                };
786                Ok(res.json())
787            }
788            "U" => {
789                Err(res.to_string())
790            }
791            "Q" => {
792                let res = PayNotify {
793                    trade_type: TradeType::MICROPAY,
794                    out_trade_no: out_trade_no.to_string(),
795                    sp_mchid: self.sp_mchid.clone(),
796                    sub_mchid: sub_mchid.to_string(),
797                    sp_appid: "".to_string(),
798                    transaction_id: "".to_string(),
799                    success_time: 0,
800                    sp_openid: "".to_string(),
801                    sub_openid: "".to_string(),
802                    total: 0.0,
803                    payer_total: 0.0,
804                    currency: "CNY".to_string(),
805                    payer_currency: "CNY".to_string(),
806                    trade_state: TradeState::NOTPAY,
807                };
808                Ok(res.json())
809            }
810            _ => {
811                Err(res.to_string())
812            }
813        }
814    }
815
816    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
817        Err("暂未开通".to_string())
818    }
819
820    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> {
821        //
822        //let mut http = br_reqwest::Client::new();
823        //http.set_cert_p12("br-pay/examples/1822131-1.pfx", "1822131");
824        //let txt = fs::read_to_string("br-pay/examples/jsyh/退款/reund.xml").unwrap();
825        //http.post("https://merchant.ccb.com").form_urlencoded(object! {
826        //    requestXml:txt
827        //});
828        //let res = http.send()?;
829        Err("暂未开通".to_string())
830    }
831
832    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> {
833        Err("暂未开通".to_string())
834    }
835
836    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
837        Err("暂未开通".to_string())
838    }
839
840    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
841        let today = Local::now().date_naive();
842        let date_str = today.format("%Y%m%d").to_string();
843        let body = object! {
844            MERCHANTID:sub_mchid,
845            BRANCHID:self.branchid.clone(),
846            POSID:self.posid.clone(),
847            ORDERDATE:date_str,
848            BEGORDERTIME:"00:00:00",
849            ENDORDERTIME:"23:59:59",
850            ORDERID:out_refund_no,
851            QUPWD:self.pass.clone(),
852            TXCODE:"410408",
853            TYPE:"1",
854            KIND:"0",
855            STATUS:"1",
856            SEL_TYPE:"3",
857            PAGE:"1",
858            OPERATOR:"",
859            CHANNEL:"",
860            MAC:""
861        };
862        let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
863        if res["RETURN_CODE"] != "000000" {
864            if res["RETURN_MSG"].eq("流水记录不存在") {
865                let res = PayNotify {
866                    trade_type: TradeType::None,
867                    out_trade_no: "".to_string(),
868                    sp_mchid: "".to_string(),
869                    sub_mchid: "".to_string(),
870                    sp_appid: "".to_string(),
871                    transaction_id: "".to_string(),
872                    success_time: 0,
873                    sp_openid: "".to_string(),
874                    sub_openid: "".to_string(),
875                    total: 0.0,
876                    payer_total: 0.0,
877                    currency: "".to_string(),
878                    payer_currency: "".to_string(),
879                    trade_state: TradeState::NOTPAY,
880                };
881                return Ok(res.json());
882            }
883            return Err(res["RETURN_MSG"].to_string());
884        }
885        let data = res["QUERYORDER"].clone();
886        let res = RefundNotify {
887            out_trade_no: trade_no.to_string(),
888            refund_no: out_refund_no.to_string(),
889            sp_mchid: "".to_string(),
890            sub_mchid: sub_mchid.to_string(),
891            transaction_id: data["ORDERID"].to_string(),
892            refund_id: data["refund_id"].to_string(),
893            success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
894            total: data["AMOUNT"].as_f64().unwrap_or(0.0),
895            payer_total: data["amount"]["total"].to_string().parse::<f64>().unwrap(),
896            refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
897            payer_refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
898            status: RefundStatus::from(data["STATUS"].as_str().unwrap()),
899        };
900
901        Ok(res.json())
902    }
903
904    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> {
905        todo!()
906    }
907}
908
909fn xml_element_to_json(elem: &Element) -> JsonValue {
910    let mut obj = object! {};
911
912    for child in &elem.children {
913        if let xmltree::XMLNode::Element(e) = child {
914            obj[e.name.clone()] = xml_element_to_json(e);
915        }
916    }
917
918    match elem.get_text() {
919        None => obj,
920        Some(text) => JsonValue::from(text.to_string()),
921    }
922}