br_pay/
ccbc.rs

1use chrono::Local;
2use json::{object, JsonValue};
3use log::{debug, warn};
4use xmltree::Element;
5use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
6
7/// 建设银行
8#[derive(Clone, Debug)]
9pub struct Ccbc {
10    /// 调试
11    pub debug: bool,
12    /// 微信小程序APPID
13    pub appid: String,
14    /// 微信公众号APPID
15    pub appid_subscribe: String,
16    /// 登录密码
17    pub pass: String,
18    /// 银行服务商号
19    pub sp_mchid: String,
20    /// 通知地址
21    pub notify_url: String,
22    /// 商户柜台代码
23    pub posid: String,
24    /// 分行代码
25    pub branchid: String,
26    /// 二级商户公钥
27    pub public_key: String,
28    pub client_ip: String,
29    /// 微信服务商号
30    pub wechat_mchid: String,
31    /// 重试次
32    pub retry: usize,
33}
34
35impl Ccbc {
36    pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
37        let mut mac = vec![];
38        let mut path = vec![];
39        let fields = ["MAC"];
40        for (key, value) in body.entries() {
41            if value.is_empty() && fields.contains(&key) {
42                continue;
43            }
44            if key != "PUB" {
45                path.push(format!("{key}={value}"));
46            }
47            mac.push(format!("{key}={value}"));
48        }
49
50
51        let mac_text = mac.join("&");
52        let path = path.join("&");
53        if self.debug {
54            debug!("MERCHANTID=105000373721227&POSID=091864103&BRANCHID=530000000&ORDERID=96398&PAYMENT=0.01&CURCODE=01&TXCODE=530550&REMARK1=&REMARK2=&RETURNTYPE=3&TIMEOUT=&PUB=93bd9affe92c29dea12e5d79020111");
55            debug!("{mac_text:#}");
56        }
57        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
58        if self.debug {
59            debug!("{body:#}");
60        }
61        body.remove("PUB");
62        let mac = format!("{}&MAC={}", path, body["MAC"]);
63        if self.debug {
64            debug!("{mac:#}");
65        }
66        let urls = format!("{url}&{mac}");
67        if self.debug {
68            debug!("{urls:#}");
69        }
70        let mut http = br_reqwest::Client::new();
71
72        let res = match http.post(urls.as_str()).raw_json(body.clone()).send() {
73            Ok(e) => e,
74            Err(e) => {
75                if self.retry > 2 {
76                    return Err(e.to_string());
77                }
78                self.retry += 1;
79                warn!("建行接口重试: {}", self.retry);
80                body.remove("MAC");
81                let res = self.http(url, body.clone())?;
82                return Ok(res);
83            }
84        };
85        let res = res.body().to_string();
86        match json::parse(&res) {
87            Ok(e) => Ok(e),
88            Err(_) => Err(res)
89        }
90    }
91    pub fn http_alipay(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
92        let mut mac = vec![];
93        let mut path = vec![];
94        let fields = ["MAC", "SUBJECT", "AREA_INFO"];
95        for (key, value) in body.entries() {
96            if value.is_empty() && fields.contains(&key) {
97                continue;
98            }
99            if fields.contains(&key) {
100                continue;
101            }
102            if key != "PUB" {
103                path.push(format!("{key}={value}"));
104            }
105            mac.push(format!("{key}={value}"));
106        }
107
108
109        let mac_text = mac.join("&");
110        let path = path.join("&");
111        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
112        body.remove("PUB");
113        let mac = format!("{}&MAC={}", path, body["MAC"]);
114
115        let urls = format!("{url}&{mac}");
116
117        let mut http = br_reqwest::Client::new();
118        let res = match http.post(urls.as_str()).raw_json(body.clone()).send() {
119            Ok(e) => e,
120            Err(e) => {
121                if self.retry > 2 {
122                    return Err(e.to_string());
123                }
124                self.retry += 1;
125                warn!("建行接口重试: {}", self.retry);
126                body.remove("MAC");
127                let res = self.http_alipay(url, body.clone())?;
128                return Ok(res);
129            }
130        };
131        let res = res.body().to_string();
132        match json::parse(&res) {
133            Ok(e) => Ok(e),
134            Err(_) => Err(res)
135        }
136    }
137    fn escape_unicode(&mut self, s: &str) -> String {
138        s.chars().map(|c| {
139            if c.is_ascii() {
140                c.to_string()
141            } else {
142                format!("%u{:04X}", c as u32)
143            }
144        }).collect::<String>()
145    }
146    fn _unescape_unicode(&mut self, s: &str) -> String {
147        let mut output = String::new();
148        let mut chars = s.chars().peekable();
149        while let Some(c) = chars.next() {
150            if c == '%' && chars.peek() == Some(&'u') {
151                chars.next(); // consume 'u'
152                let codepoint: String = chars.by_ref().take(4).collect();
153                if let Ok(value) = u32::from_str_radix(&codepoint, 16) {
154                    if let Some(ch) = std::char::from_u32(value) {
155                        output.push(ch);
156                    }
157                }
158            } else {
159                output.push(c);
160            }
161        }
162        output
163    }
164    pub fn http_q(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
165        let mut path = vec![];
166        let fields = ["MAC"];
167        for (key, value) in body.entries() {
168            if value.is_empty() && fields.contains(&key) {
169                continue;
170            }
171            if key.contains("QUPWD") {
172                path.push(format!("{key}="));
173                continue;
174            }
175            path.push(format!("{key}={value}"));
176        }
177
178        let mac = path.join("&");
179        body["MAC"] = br_crypto::md5::encrypt_hex(mac.as_bytes()).into();
180
181        let mut map = vec![];
182        for (key, value) in body.entries() {
183            map.push((key, value.to_string()));
184        }
185
186        let mut http = br_reqwest::Client::new();
187
188        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");
189
190        let res = match http.post(url).form_urlencoded(body.clone()).send() {
191            Ok(d) => d,
192            Err(e) => {
193                if self.retry > 2 {
194                    return Err(e.to_string());
195                }
196                self.retry += 1;
197                warn!("建行查询接口重试: {}", self.retry);
198                body.remove("MAC");
199                let res = self.http_q(url, body.clone())?;
200                return Ok(res);
201            }
202        };
203        let res = res.body().to_string().trim().to_string();
204        match Element::parse(res.as_bytes()) {
205            Ok(e) => Ok(xml_element_to_json(&e)),
206            Err(e) => Err(e.to_string())
207        }
208    }
209
210    pub fn http_ccb_param(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
211        println!("url: {url}");
212        println!("public_key: {}", self.public_key);
213
214
215        let mut mac = vec![];
216        let mut path = vec![];
217        let fields = ["MAC", "ccbParam"];
218        for (key, value) in body.entries() {
219            if value.is_empty() && fields.contains(&key) {
220                continue;
221            }
222            if fields.contains(&key) {
223                continue;
224            }
225            if key != "PUB" {
226                path.push(format!("{key}={value}"));
227            }
228            mac.push(format!("{key}={value}"));
229        }
230
231
232        let mac_text = mac.join("&");
233        println!("mac: {mac_text}");
234        // MERCHANTID=105000373721227&POSID=091864103&BRANCHID=530000000&MERFLAG=1&TERMNO1=&TERMNO2=&ORDERID=202508041754271606105000373721227&QRCODE=131318016110834439&AMOUNT=0.01&TXCODE=PAY100&PROINFO=单人&REMARK1=&REMARK2=&SUB_APPID=wx2408d13eefae86
235        // MERCHANTID=105910100190000&POSID=000000000&BRANCHID=610000000&MERFLAG=1&TERMNO1=&TERMNO2=&ORDERID=202508041754271433105000373721227&QRCODE=134737690209713400&AMOUNT=0.01&TXCODE=PAY100&PROINFO=&REMARK1=&REMARK2=&SMERID=&SMERNAME=&SMERTYPEID=&SMERTYPE=&TRADECODE=&TRADENAME=&SMEPROTYPE=&PRONAME=
236        let path = path.join("&");
237        body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
238        body.remove("PUB");
239        let mac = format!("{}&MAC={}", path, body["MAC"]);
240
241        let urls = format!("{url}&{mac}");
242
243        let mut http = br_reqwest::Client::new();
244
245        let res = match http.post(urls.as_str()).raw_json(body.clone()).send() {
246            Ok(e) => e,
247            Err(e) => {
248                if self.retry > 2 {
249                    return Err(e.to_string());
250                }
251                self.retry += 1;
252                warn!("建行接口重试: {}", self.retry);
253                body.remove("MAC");
254                let res = self.http(url, body.clone())?;
255                return Ok(res);
256            }
257        };
258        let res = res.body().to_string();
259        match json::parse(&res) {
260            Ok(e) => Ok(e),
261            Err(_) => Err(res)
262        }
263    }
264}
265impl PayMode for Ccbc {
266    fn check(&mut self) -> Result<bool, String> {
267        todo!()
268    }
269
270    fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
271        todo!()
272    }
273
274    fn config(&mut self) -> JsonValue {
275        todo!()
276    }
277
278
279    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> {
280        if self.public_key.is_empty() || self.public_key.len() < 30 {
281            return Err(String::from("Public key is empty"));
282        }
283        let pubtext = self.public_key[self.public_key.len() - 30..].to_string();
284
285        let url = match channel {
286            "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
287            "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
288            _ => return Err(format!("Invalid channel: {channel}")),
289        };
290
291        let body = match channel {
292            "wechat" => {
293                let mut body = object! {
294                    MERCHANTID:sub_mchid,
295                    POSID:self.posid.clone(),
296                    BRANCHID:self.branchid.clone(),
297                    ORDERID:out_trade_no,
298                    PAYMENT:total_fee,
299                    CURCODE:"01",
300                    TXCODE:"530590",
301                    REMARK1:"",
302                    REMARK2:"",
303                    TYPE:"1",
304                    PUB:pubtext,
305                    GATEWAY:"0",
306                    CLIENTIP:self.client_ip.clone(),
307                    REGINFO:"",
308                    PROINFO: self.escape_unicode(description),
309                    REFERER:"",
310                    TRADE_TYPE:"",
311                    SUB_APPID: "",
312                    SUB_OPENID:sp_openid,
313                    MAC:"",
314                };
315                body["TRADE_TYPE"] = match types {
316                    Types::Jsapi => {
317                        body["SUB_APPID"] = self.appid_subscribe.clone().into();
318                        "JSAPI"
319                    }
320                    Types::MiniJsapi => {
321                        body["SUB_APPID"] = self.appid.clone().into();
322                        "MINIPRO"
323                    }
324                    _ => return Err(format!("Invalid types: {types:?}")),
325                }.into();
326
327                //body["WX_CHANNELID"] = self.sp_mchid.clone().into();
328
329                //body["SMERID"] = sub_mchid.into();
330                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
331                //body["SMERTYPEID"] = self.smertypeid.clone().into();
332                //body["SMERTYPE"] = self.escape_unicode(self.smertype.clone().as_str()).into();
333
334                //body["SMERNAME"] = self.escape_unicode(self.smername.clone().as_str()).into();
335                //body["SMERTYPEID"] = 1.into();
336                //body["SMERTYPE"] = self.escape_unicode("宾馆餐娱类").into();
337
338                //body["TRADECODE"] = "交易类型代码".into();
339                //body["TRADENAME"] = self.escape_unicode("消费").into();
340                //body["SMEPROTYPE"] = "商品类别代码".into();
341                //body["PRONAME"] = self.escape_unicode("商品").into();
342                body
343            }
344            "alipay" => {
345                let body = match types {
346                    Types::MiniJsapi => object! {
347                        MERCHANTID:sub_mchid,
348                        POSID:self.posid.clone(),
349                        BRANCHID:self.branchid.clone(),
350                        ORDERID:out_trade_no,
351                        PAYMENT:total_fee,
352                        CURCODE:"01",
353                        TXCODE:"530591",
354                        TRADE_TYPE:"JSAPI",
355                        USERID:sp_openid,
356                        PUB:pubtext,
357                        MAC:""
358                    },
359                    Types::H5 => object! {
360                        BRANCHID:self.branchid.clone(),
361                        MERCHANTID:sub_mchid,
362                        POSID:self.posid.clone(),
363                        TXCODE:"ZFBWAP",
364                        ORDERID:out_trade_no,
365                        AMOUNT:total_fee,
366                        TIMEOUT:"",
367                        REMARK1:"",
368                        REMARK2:"",
369                        PUB:pubtext,
370                        MAC:"",
371                        SUBJECT:description,
372                        AREA_INFO:""
373                    },
374                    Types::Jsapi => object! {
375                        MERCHANTID:sub_mchid,
376                        POSID:self.posid.clone(),
377                        BRANCHID:self.branchid.clone(),
378                        ORDERID:out_trade_no,
379                        PAYMENT:total_fee,
380                        CURCODE:"01",
381                        TXCODE:"530550",
382                        REMARK1:"",
383                        REMARK2:"",
384                        RETURNTYPE:"3",
385                        TIMEOUT:"",
386                        PUB:pubtext,
387                        MAC:""
388                    },
389                    _ => return Err(format!("Invalid types: {types:?}")),
390                };
391                body
392            }
393            _ => return Err(format!("Invalid channel: {channel}")),
394        };
395        match (channel, types) {
396            ("wechat", Types::Jsapi | Types::MiniJsapi) => {
397                let res = self.http(url, body.clone())?;
398                if res.has_key("PAYURL") {
399                    let url = res["PAYURL"].to_string();
400                    let mut http = br_reqwest::Client::new();
401
402                    let re = match http.post(url.as_str()).send() {
403                        Ok(e) => e,
404                        Err(e) => {
405                            return Err(e.to_string());
406                        }
407                    };
408                    let re = re.body().to_string();
409                    let res = match json::parse(&re) {
410                        Ok(e) => e,
411                        Err(_) => return Err(re)
412                    };
413                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
414                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
415                    }
416                    Ok(res)
417                } else {
418                    Err(res.to_string())
419                }
420            }
421            ("alipay", Types::MiniJsapi) => {
422                let res = self.http_alipay(url, body)?;
423                if res.has_key("PAYURL") {
424                    let url = res["PAYURL"].to_string();
425                    let mut http = br_reqwest::Client::new();
426
427                    let re = match http.post(url.as_str()).send() {
428                        Ok(e) => e,
429                        Err(e) => {
430                            return Err(e.to_string());
431                        }
432                    };
433                    let re = re.body().to_string();
434                    let res = match json::parse(&re) {
435                        Ok(e) => e,
436                        Err(_) => return Err(re)
437                    };
438                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
439                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
440                    }
441                    Ok(res)
442                } else {
443                    Err(res.to_string())
444                }
445            }
446            ("alipay", Types::H5) => {
447                let res = self.http_alipay(url, body)?;
448                if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
449                    return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
450                }
451                Ok(res["form_data"].clone())
452            }
453            ("alipay", Types::Jsapi) => {
454                let res = self.http(url, body)?;
455                if res.has_key("PAYURL") {
456                    let url = res["PAYURL"].to_string();
457                    if self.debug {
458                        debug!("{url:#}");
459                    }
460                    let mut http = br_reqwest::Client::new();
461
462                    let re = match http.post(url.as_str()).send() {
463                        Ok(e) => e,
464                        Err(e) => {
465                            return Err(e.to_string());
466                        }
467                    };
468                    let re = re.body().to_string();
469                    let res = match json::parse(&re) {
470                        Ok(e) => e,
471                        Err(_) => return Err(re)
472                    };
473                    if self.debug {
474                        debug!("{res:#}");
475                    }
476                    if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
477                        return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
478                    }
479                    let r = br_crypto::encoding::urlencoding_decode(res["QRURL"].as_str().unwrap());
480                    Ok(object! {
481                        url:r.clone()
482                    })
483                } else {
484                    Err(res.to_string())
485                }
486            }
487            _ => {
488                let res = self.http(url, body)?;
489                Ok(res)
490            }
491        }
492    }
493
494    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> {
495        let mut body = object! {
496            MERCHANTID:sub_mchid,
497            POSID:self.posid.clone(),
498            BRANCHID:self.branchid.clone(),
499            ccbParam:"",
500            MERFLAG:"1",
501            TERMNO1:"",
502            TERMNO2:"",
503            ORDERID:out_trade_no,
504            QRCODE:auth_code,
505            AMOUNT:total_fee,
506            TXCODE:"PAY100",
507            PROINFO:description,
508            REMARK1:"",
509            REMARK2:"",
510            SUB_APPID:"",
511        };
512        let url = "https://ebanking2.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
513
514        match channel {
515            "wechat" => {
516                body["SUB_APPID"] = self.appid.clone().into();
517            }
518            "alipay" => {}
519            _ => return Err(format!("Invalid channel: {channel}")),
520        }
521        let res = self.http_ccb_param(url, body)?;
522        Ok(res)
523    }
524
525    fn close(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
526        Ok(true.into())
527    }
528
529    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
530        let today = Local::now().date_naive();
531        let date_str = today.format("%Y%m%d").to_string();
532
533        let order_date = &out_trade_no[0..8];
534
535        let kind = if date_str == order_date {
536            0
537        } else {
538            1
539        };
540
541        let body = object! {
542            MERCHANTID:sub_mchid,
543            BRANCHID:self.branchid.clone(),
544            POSID:self.posid.clone(),
545            ORDERDATE:order_date,
546            BEGORDERTIME:"00:00:00",
547            ENDORDERTIME:"23:59:59",
548            ORDERID:out_trade_no,
549            QUPWD:self.pass.clone(),
550            TXCODE:"410408",
551            TYPE:"0",
552            KIND:kind,
553            STATUS:"1",
554            SEL_TYPE:"3",
555            PAGE:"1",
556            OPERATOR:"",
557            CHANNEL:"",
558            MAC:""
559        };
560        let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
561        if res["RETURN_CODE"] != "000000" {
562            if res["RETURN_MSG"].eq("流水记录不存在") {
563                let res = PayNotify {
564                    trade_type: TradeType::None,
565                    out_trade_no: "".to_string(),
566                    sp_mchid: "".to_string(),
567                    sub_mchid: "".to_string(),
568                    sp_appid: "".to_string(),
569                    transaction_id: "".to_string(),
570                    success_time: 0,
571                    sp_openid: "".to_string(),
572                    sub_openid: "".to_string(),
573                    total: 0.0,
574                    payer_total: 0.0,
575                    currency: "".to_string(),
576                    payer_currency: "".to_string(),
577                    trade_state: TradeState::NOTPAY,
578                };
579                return Ok(res.json());
580            }
581            return Err(res["RETURN_MSG"].to_string());
582        }
583        let data = res["QUERYORDER"].clone();
584        let res = PayNotify {
585            trade_type: TradeType::None,
586            out_trade_no: data["ORDERID"].to_string(),
587            sp_mchid: "".to_string(),
588            sub_mchid: sub_mchid.to_string(),
589            sp_appid: "".to_string(),
590            transaction_id: data["ORDERID"].to_string(),
591            success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
592            sp_openid: "".to_string(),
593            sub_openid: "".to_string(),
594            total: data["AMOUNT"].as_f64().unwrap_or(0.0),
595            currency: "CNY".to_string(),
596            payer_total: data["AMOUNT"].as_f64().unwrap_or(0.0),
597            payer_currency: "CNY".to_string(),
598            trade_state: TradeState::from(data["STATUS"].as_str().unwrap()),
599        };
600        Ok(res.json())
601    }
602
603    fn pay_micropay_query(&mut self, _out_trade_no: &str, _sub_mchid: &str) -> Result<JsonValue, String> {
604        Err("暂未开通".to_string())
605    }
606
607    fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
608        Err("暂未开通".to_string())
609    }
610
611    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> {
612        //
613        //let mut http = br_reqwest::Client::new();
614        //http.set_cert_p12("br-pay/examples/1822131-1.pfx", "1822131");
615        //let txt = fs::read_to_string("br-pay/examples/jsyh/退款/reund.xml").unwrap();
616        //http.post("https://merchant.ccb.com").form_urlencoded(object! {
617        //    requestXml:txt
618        //});
619        //let res = http.send()?;
620        Err("暂未开通".to_string())
621    }
622
623    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> {
624        Err("暂未开通".to_string())
625    }
626
627    fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
628        Err("暂未开通".to_string())
629    }
630
631    fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
632        let today = Local::now().date_naive();
633        let date_str = today.format("%Y%m%d").to_string();
634        let body = object! {
635            MERCHANTID:sub_mchid,
636            BRANCHID:self.branchid.clone(),
637            POSID:self.posid.clone(),
638            ORDERDATE:date_str,
639            BEGORDERTIME:"00:00:00",
640            ENDORDERTIME:"23:59:59",
641            ORDERID:out_refund_no,
642            QUPWD:self.pass.clone(),
643            TXCODE:"410408",
644            TYPE:"1",
645            KIND:"0",
646            STATUS:"1",
647            SEL_TYPE:"3",
648            PAGE:"1",
649            OPERATOR:"",
650            CHANNEL:"",
651            MAC:""
652        };
653        let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
654        if res["RETURN_CODE"] != "000000" {
655            if res["RETURN_MSG"].eq("流水记录不存在") {
656                let res = PayNotify {
657                    trade_type: TradeType::None,
658                    out_trade_no: "".to_string(),
659                    sp_mchid: "".to_string(),
660                    sub_mchid: "".to_string(),
661                    sp_appid: "".to_string(),
662                    transaction_id: "".to_string(),
663                    success_time: 0,
664                    sp_openid: "".to_string(),
665                    sub_openid: "".to_string(),
666                    total: 0.0,
667                    payer_total: 0.0,
668                    currency: "".to_string(),
669                    payer_currency: "".to_string(),
670                    trade_state: TradeState::NOTPAY,
671                };
672                return Ok(res.json());
673            }
674            return Err(res["RETURN_MSG"].to_string());
675        }
676        println!("refund_query: {res:#}");
677        let data = res["QUERYORDER"].clone();
678
679        let res = RefundNotify {
680            out_trade_no: trade_no.to_string(),
681            refund_no: out_refund_no.to_string(),
682            sp_mchid: "".to_string(),
683            sub_mchid: sub_mchid.to_string(),
684            transaction_id: data["ORDERID"].to_string(),
685            refund_id: data["refund_id"].to_string(),
686            success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
687            total: data["AMOUNT"].as_f64().unwrap_or(0.0),
688            payer_total: data["amount"]["total"].to_string().parse::<f64>().unwrap(),
689            refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
690            payer_refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
691            status: RefundStatus::from(data["STATUS"].as_str().unwrap()),
692        };
693
694        Ok(res.json())
695    }
696
697    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> {
698        todo!()
699    }
700}
701
702fn xml_element_to_json(elem: &Element) -> JsonValue {
703    let mut obj = object! {};
704
705    for child in &elem.children {
706        if let xmltree::XMLNode::Element(e) = child {
707            obj[e.name.clone()] = xml_element_to_json(e);
708        }
709    }
710
711    match elem.get_text() {
712        None => obj,
713        Some(text) => JsonValue::from(text.to_string()),
714    }
715}