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