br_pay/
wechat.rs

1use std::collections::HashMap;
2use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
3use base64::engine::general_purpose::STANDARD;
4use base64::{Engine};
5use json::{object, JsonValue};
6use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
7use aes_gcm::aead::{Aead, Payload};
8use br_reqwest::Method;
9
10#[derive(Clone)]
11pub struct Wechat {
12    /// 服务商APPID
13    pub appid: String,
14    /// 密钥
15    pub secret: String,
16    /// 服务商户号
17    pub sp_mchid: String,
18    /// 证书号
19    pub serial_no: String,
20    /// API证书私钥
21    pub app_private: String,
22    /// APIv3密钥
23    pub apikey: String,
24    /// APIv2密钥
25    pub apiv2: String,
26    pub notify_url: String,
27}
28
29use chrono::{DateTime, Utc};
30use log::error;
31use openssl::hash::MessageDigest;
32use openssl::pkey::{PKey};
33use openssl::rsa::Rsa;
34use openssl::sign::Signer;
35use rand::distr::Alphanumeric;
36use rand::{rng, Rng};
37
38impl Wechat {
39    pub fn http(&mut self, url: &str, method: Method, body: JsonValue) -> Result<JsonValue, String> {
40        let sign = self.sign(method.to_str().to_uppercase().as_str(), url, body.to_string().as_str())?;
41        let mut http = br_reqwest::Client::new();
42        let url = format!("https://api.mch.weixin.qq.com{}", url);
43        let send = match method {
44            Method::GET => http.get(url.as_str()),
45            Method::POST => http.post(url.as_str()).raw_json(body),
46            _ => http.post(url.as_str()),
47        };
48        match send.header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
49            Ok(e) => Ok(e),
50            Err(e) => Err(e)
51        }
52    }
53
54    pub fn sign_v2(&mut self, body: JsonValue) -> Result<String, String> {
55        let mut map = HashMap::new();
56        for (key, value) in body.entries() {
57            if key == "sign" {
58                continue;
59            }
60            if value.is_empty() {
61                continue;
62            }
63            map.insert(key, value);
64        }
65        let mut keys: Vec<_> = map.keys().cloned().collect();
66        keys.sort();
67        let mut txt = vec![];
68        for key in keys {
69            txt.push(format!("{}={}", key, map.get(&key).unwrap()));
70        }
71        let txt = txt.join("&");
72        let string_sign_temp = format!("{}&key={}", txt, self.apiv2);
73        let sign = format!("{:x}", md5::compute(string_sign_temp.as_bytes())).to_uppercase();
74        Ok(sign)
75    }
76
77    pub fn sign(&mut self, method: &str, url: &str, body: &str) -> Result<String, String> {
78        let timestamp = Utc::now().timestamp(); // 秒级时间戳
79        let random_string: String = rng().sample_iter(&Alphanumeric) // 生成随机字母+数字
80                                         .take(10) // 指定长度
81                                         .map(char::from).collect();
82
83        let sign_txt = format!("{method}\n{url}\n{timestamp}\n{random_string}\n{body}\n");
84        // 加载 RSA 私钥
85        let rsa = match Rsa::private_key_from_pem(self.app_private.as_bytes()) {
86            Ok(e) => e,
87            Err(e) => {
88                return Err(e.to_string())
89            }
90        };
91        let pkey = match PKey::from_rsa(rsa) {
92            Ok(e) => e,
93            Err(e) => {
94                return Err(format!("Failed to create PKey: {}", e))
95            }
96        };
97        // 创建签名器
98        let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
99            Ok(e) => e,
100            Err(e) => {
101                return Err(format!("Failed to create signer:{}", e));
102            }
103        };
104        // 输入待签名数据
105        match signer.update(sign_txt.as_bytes()) {
106            Ok(_) => {}
107            Err(e) => {
108                return Err(e.to_string())
109            }
110        };
111        // 生成签名
112        let signature = match signer.sign_to_vec() {
113            Ok(e) => e,
114            Err(e) => {
115                return Err(format!("Failed to sign: {}", e));
116            }
117        };
118        let signature_b64 = STANDARD.encode(signature);
119        let sign = format!(
120            r#"WECHATPAY2-SHA256-RSA2048 mchid="{}",nonce_str="{random_string}",signature="{signature_b64}",timestamp="{timestamp}",serial_no="{}""#,
121            self.sp_mchid.as_str(),
122            self.serial_no
123        );
124        Ok(sign)
125    }
126
127    pub fn paysign(&mut self, prepay_id: &str) -> Result<JsonValue, String> {
128        let timestamp = Utc::now().timestamp(); // 秒级时间戳
129        let random_string: String = rng().sample_iter(&Alphanumeric) // 生成随机字母+数字
130                                         .take(10) // 指定长度
131                                         .map(char::from).collect();
132
133        let sign_txt = format!(
134            "{}\n{timestamp}\n{random_string}\n{prepay_id}\n",
135            self.appid
136        );
137
138        // 加载 RSA 私钥
139        let rsa = match Rsa::private_key_from_pem(self.app_private.as_bytes()) {
140            Ok(e) => e,
141            Err(e) => {
142                return Err(e.to_string())
143            }
144        };
145        let pkey = match PKey::from_rsa(rsa) {
146            Ok(e) => e,
147            Err(e) => {
148                return Err(format!("Failed to create PKey: {}", e))
149            }
150        };
151        // 创建签名器
152        let mut signer = match Signer::new(MessageDigest::sha256(), &pkey) {
153            Ok(e) => e,
154            Err(e) => {
155                return Err(format!("Failed to create signer:{}", e));
156            }
157        };
158        // 输入待签名数据
159        match signer.update(sign_txt.as_bytes()) {
160            Ok(_) => {}
161            Err(e) => {
162                return Err(e.to_string())
163            }
164        };
165        // 生成签名
166        let signature = match signer.sign_to_vec() {
167            Ok(e) => e,
168            Err(e) => {
169                return Err(format!("Failed to sign: {}", e));
170            }
171        };
172        let signature_b64 = STANDARD.encode(signature);
173        let sign = signature_b64;
174        Ok(object! {
175            timeStamp:timestamp,
176            nonceStr:random_string,
177            package:prepay_id,
178            signType:"RSA",
179            paySign:sign
180        })
181    }
182}
183impl PayMode for Wechat {
184    fn login(&mut self, code: &str) -> Result<JsonValue, String> {
185        let mut http = br_reqwest::Client::new();
186        match http.get(
187            "https://api.weixin.qq.com/sns/jscode2session".to_string().as_str(),
188        ).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").query(object! {
189                appid: self.appid.as_str(),
190                secret: self.secret.as_str(),
191                js_code:code,
192                grant_type:"authorization_code",
193            }).send()?.json() {
194            Ok(e) => Ok(e),
195            Err(e) => Err(e),
196        }
197    }
198
199    fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
200        let url = format!(
201            "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
202            out_trade_no, sub_mchid, self.sp_mchid
203        );
204        let sign = self.sign("GET", url.as_str(), "")?;
205        let mut http = br_reqwest::Client::new();
206        match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
207            Ok(e) => {
208                if e.has_key("message") {
209                    return Err(e["message"].to_string());
210                }
211                let res = PayNotify {
212                    trade_type: TradeType::from(e["trade_type"].to_string().as_str()),
213                    out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
214                    sp_mchid: e["sp_mchid"].as_str().unwrap().to_string(),
215                    sub_mchid: e["sub_mchid"].as_str().unwrap().to_string(),
216                    sp_appid: e["sp_appid"].as_str().unwrap().to_string(),
217                    transaction_id: e["transaction_id"].to_string(),
218                    success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
219                    sp_openid: e["payer"]["sp_openid"].to_string(),
220                    sub_openid: e["payer"]["sub_openid"].to_string(),
221                    total: e["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
222                    currency: e["amount"]["currency"].to_string(),
223                    payer_total: e["amount"]["payer_total"].as_f64().unwrap_or(0.0) / 100.0,
224                    payer_currency: e["amount"]["payer_currency"].to_string(),
225                    trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
226                };
227
228                Ok(res.json())
229            }
230            Err(e) => Err(e),
231        }
232    }
233
234    fn refund(
235        &mut self,
236        sub_mchid: &str,
237        out_trade_no: &str,
238        transaction_id: &str,
239        out_refund_no: &str,
240        amount: f64,
241        total: f64,
242        currency: &str,
243    ) -> Result<JsonValue, String> {
244        let url = "/v3/refund/domestic/refunds";
245
246        let refund = format!("{:.0}", amount * 100.0);
247        let total = format!("{:.0}", total * 100.0);
248
249        let body = object! {
250            "sub_mchid"=> sub_mchid,
251            "transaction_id"=>transaction_id,
252            "out_trade_no"=>out_trade_no,
253            "out_refund_no"=>out_refund_no,
254            "amount"=>object! {
255                refund: refund.parse::<i64>().unwrap(),
256                total: total.parse::<i64>().unwrap(),
257                currency:currency
258            }
259        };
260        let sign = self.sign("POST", url, body.to_string().as_str())?;
261        let mut http = br_reqwest::Client::new();
262        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).send()?.json() {
263            Ok(e) => {
264                if e.is_empty() {
265                    return Err("已执行".to_string());
266                }
267                if e.has_key("message") {
268                    return Err(e["message"].to_string());
269                }
270                let mut refund_time = 0.0;
271                if e.has_key("success_time") {
272                    let success_time = e["success_time"].as_str().unwrap_or("").to_string();
273                    if !success_time.is_empty() {
274                        let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
275                        refund_time = datetime.timestamp() as f64;
276                    }
277                }
278
279                let status = match e["status"].as_str().unwrap() {
280                    "PROCESSING" => "退款中",
281                    "SUCCESS" => "已退款",
282                    _ => "无退款",
283                };
284                let info = object! {
285                    refund_id: e["refund_id"].clone(),
286                    user_received_account:e["user_received_account"].clone(),
287                    status:status,
288                    refund_time:refund_time,
289                    out_refund_no: e["out_refund_no"].clone(),
290                };
291                Ok(info)
292            }
293            Err(e) => Err(e)
294        }
295    }
296
297    fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
298        if self.apikey.is_empty() {
299            return Err("apikey 不能为空".to_string());
300        }
301        let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
302        let cipher = Aes256Gcm::new(key);
303        let nonce = Nonce::from_slice(nonce.as_bytes());
304        let data = match STANDARD.decode(ciphertext) {
305            Ok(e) => e,
306            Err(e) => return Err(format!("Invalid data received from API :{}", e))
307        };
308        // 组合 Payload(带 aad)
309        let payload = Payload {
310            msg: &data,
311            aad: associated_data.as_bytes(),
312        };
313
314        // 解密
315        let plaintext = match cipher.decrypt(nonce, payload) {
316            Ok(e) => e,
317            Err(e) => {
318                return Err(format!("解密 API:{}", e));
319            }
320        };
321        let rr = match String::from_utf8(plaintext) {
322            Ok(d) => d,
323            Err(_) => return Err("utf8 error".to_string())
324        };
325        let json = match json::parse(rr.as_str()) {
326            Ok(e) => e,
327            Err(_) => return Err("json error".to_string())
328        };
329        let res = PayNotify {
330            trade_type: TradeType::from(json["trade_type"].as_str().unwrap()),
331            out_trade_no: json["out_trade_no"].as_str().unwrap().to_string(),
332            sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
333            sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
334            sp_appid: json["sp_appid"].as_str().unwrap().to_string(),
335            transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
336            success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
337            sp_openid: json["payer"]["sp_openid"].as_str().unwrap().to_string(),
338            sub_openid: json["payer"]["sub_openid"].as_str().unwrap().to_string(),
339            total: json["amount"]["total"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
340            payer_total: json["amount"]["payer_total"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
341            currency: json["amount"]["currency"].to_string(),
342            payer_currency: json["amount"]["payer_currency"].to_string(),
343            trade_state: TradeState::from(json["trade_state"].as_str().unwrap()),
344        };
345        Ok(res.json())
346    }
347
348    fn refund_query(&mut self, _trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
349        let url = format!("/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={}", sub_mchid);
350        let sign = self.sign("GET", url.as_str(), "")?;
351        let mut http = br_reqwest::Client::new();
352        match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
353            Ok(e) => {
354                if e.is_empty() {
355                    return Err("已执行".to_string());
356                }
357                if e.has_key("message") {
358                    return Err(e["message"].to_string());
359                }
360
361
362                let res = RefundNotify {
363                    out_trade_no: e["out_trade_no"].to_string(),
364                    refund_no: e["out_refund_no"].to_string(),
365                    sp_mchid: "".to_string(),
366                    sub_mchid: sub_mchid.to_string(),
367                    transaction_id: e["transaction_id"].to_string(),
368                    refund_id: e["refund_id"].to_string(),
369                    success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
370                    total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
371                    payer_total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
372                    refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
373                    payer_refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
374                    status: RefundStatus::from(e["status"].as_str().unwrap()),
375                };
376
377                Ok(res.json())
378            }
379            Err(e) => Err(e),
380        }
381    }
382    fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
383        if self.apikey.is_empty() {
384            return Err("apikey 不能为空".to_string());
385        }
386        let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
387        let cipher = Aes256Gcm::new(key);
388        let nonce = Nonce::from_slice(nonce.as_bytes());
389        let data = match STANDARD.decode(ciphertext) {
390            Ok(e) => e,
391            Err(e) => return Err(format!("Invalid data received from API :{}", e))
392        };
393        // 组合 Payload(带 aad)
394        let payload = Payload {
395            msg: &data,
396            aad: associated_data.as_bytes(),
397        };
398
399        // 解密
400        let plaintext = match cipher.decrypt(nonce, payload) {
401            Ok(e) => e,
402            Err(e) => {
403                return Err(format!("解密 API:{}", e));
404            }
405        };
406        let rr = match String::from_utf8(plaintext) {
407            Ok(d) => d,
408            Err(_) => return Err("utf8 error".to_string())
409        };
410        let json = match json::parse(rr.as_str()) {
411            Ok(e) => e,
412            Err(_) => return Err("json error".to_string())
413        };
414        let res = RefundNotify {
415            out_trade_no: json["out_trade_no"].to_string(),
416            refund_no: json["out_refund_no"].to_string(),
417            refund_id: json["refund_id"].to_string(),
418            sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
419            sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
420            transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
421            success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
422            total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
423            refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
424            payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
425            payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
426            status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
427        };
428        Ok(res.json())
429    }
430
431    fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
432        todo!()
433    }
434
435    fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
436        let url = format!("/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close");
437        let body = object! {
438            "sp_mchid"=> self.sp_mchid.clone(),
439            "sub_mchid"=> sub_mchid
440        };
441        let sign = self.sign("POST", url.as_str(), body.to_string().as_str())?;
442        let mut http = br_reqwest::Client::new();
443        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).send()?.json() {
444            Ok(_) => Ok(true.into()),
445            Err(e) => Err(e)
446        }
447    }
448
449    fn config(&mut self) -> JsonValue {
450        todo!()
451    }
452
453    fn pay(&mut self, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
454        let url = match types {
455            Types::Jsapi => "/v3/pay/partner/transactions/jsapi",
456            Types::Native => "/v3/pay/partner/transactions/native",
457            Types::H5 => "/v3/pay/partner/transactions/h5",
458            Types::MiniJsapi => "/v3/pay/partner/transactions/jsapi",
459            Types::App => "/v3/pay/partner/transactions/app",
460            Types::Micropay => "/pay/micropay"
461        };
462        let total = format!("{:.0}", total_fee * 100.0);
463        let mut body = object! {
464            "sp_appid" => self.appid.clone(),
465            "sp_mchid"=> self.sp_mchid.clone(),
466            "sub_mchid"=> sub_mchid,
467            "description"=>description,
468            "out_trade_no"=>out_trade_no,
469            "notify_url"=>self.notify_url.clone(),
470            "support_fapiao"=>true,
471            "amount"=>object! {
472                total: total.parse::<i64>().unwrap(),
473                currency:"CNY"
474            }
475        };
476        match types {
477            Types::Native => {}
478            _ => {
479                body["payer"] = object! {
480                sp_openid:sp_openid
481            };
482            }
483        };
484        let sign = self.sign("POST", url, body.to_string().as_str())?;
485        let mut http = br_reqwest::Client::new();
486        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", &sign).raw_json(body).send()?.json() {
487            Ok(e) => {
488                match types {
489                    Types::Native => {
490                        if e.has_key("code_url") {
491                            Ok(e["code_url"].clone())
492                        } else {
493                            Err(e["message"].to_string())
494                        }
495                    }
496                    Types::Jsapi | Types::MiniJsapi => {
497                        if e.has_key("prepay_id") {
498                            let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str())?;
499                            Ok(signinfo)
500                        } else {
501                            Err(e["message"].to_string())
502                        }
503                    }
504                    _ => {
505                        Ok(e)
506                    }
507                }
508            }
509            Err(e) => Err(e),
510        }
511    }
512    fn micropay(&mut self, auth_code: &str, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, org_openid: &str, ip: &str) -> Result<JsonValue, String> {
513        let url = "/pay/micropay";
514        let total = format!("{:.0}", total_fee * 100.0);
515
516        let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
517
518        let mut body = object! {
519            "appid": self.appid.clone(),
520            "mch_id"=> self.sp_mchid.clone(),
521            "sub_mch_id"=> sub_mchid,
522            "nonce_str"=>nonce_str,
523            "body"=> description,
524            "out_trade_no"=>out_trade_no,
525            "total_fee"=>total.parse::<i64>().unwrap(),
526            "fee_type":"CNY",
527            "spbill_create_ip":ip,
528            "device_info":org_openid,
529            "auth_code":auth_code
530        };
531        body["sign"] = self.sign_v2(body.clone())?.into();
532        let mut xml = vec!["<xml>".to_owned()];
533        for (key, value) in body.entries() {
534            let t = format!("<{}>{}</{00}>", key, value.clone().clone());
535            xml.push(t);
536        }
537        xml.push("</xml>".to_owned());
538        let xml = xml.join("");
539        let mut http = br_reqwest::Client::new();
540        match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Content-Type", "application/xml").raw_xml(xml.into()).send()?.xml() {
541            Ok(e) => Ok(e),
542            Err(e) => Err(e),
543        }
544    }
545
546    fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
547        todo!()
548    }
549
550    fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String> {
551        let url = format!("/v3/apply4sub/sub_merchants/{sub_mchid}/settlement");
552        let res = self.http(url.as_str(), Method::GET, "".into())?;
553        if res.has_key("verify_result") && res["verify_result"] == "VERIFY_SUCCESS" {
554            return Ok(true.into());
555        }
556        Err(res.to_string())
557    }
558
559    fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
560        let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
561        let mut body = object! {
562            "appid": self.appid.clone(),
563            "mch_id"=> self.sp_mchid.clone(),
564            "sub_mch_id"=> sub_mchid,
565            "nonce_str"=>nonce_str,
566            "out_trade_no"=>out_trade_no
567        };
568        body["sign"] = self.sign_v2(body.clone())?.into();
569        let mut xml = vec!["<xml>".to_owned()];
570        for (key, value) in body.entries() {
571            let t = format!("<{}>{}</{00}>", key, value.clone().clone());
572            xml.push(t);
573        }
574        xml.push("</xml>".to_owned());
575        let xml = xml.join("");
576        let mut http = br_reqwest::Client::new();
577        match http.post("https://api.mch.weixin.qq.com/pay/orderquery".to_string().as_str()).header("Content-Type", "application/xml").raw_xml(xml.into()).send()?.xml() {
578            Ok(e) => {
579                if e.has_key("result_code") && e["result_code"] != "SUCCESS" {
580                    error!("pay_micropay_query: {:#}", e);
581                    return Err(e["return_msg"].to_string());
582                }
583                let res = PayNotify {
584                    trade_type: TradeType::from(e["trade_type"].to_string().as_str()),
585                    out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
586                    sp_mchid: e["mch_id"].as_str().unwrap().to_string(),
587                    sub_mchid: e["sub_mch_id"].as_str().unwrap().to_string(),
588                    sp_appid: e["appid"].as_str().unwrap().to_string(),
589                    transaction_id: e["transaction_id"].to_string(),
590                    success_time: PayNotify::micropay_time(e["time_end"].as_str().unwrap_or("")),
591                    sp_openid: e["device_info"].to_string(),
592                    sub_openid: e["openid"].to_string(),
593                    total: e["total_fee"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
594                    currency: e["fee_type"].to_string(),
595                    payer_total: e["cash_fee"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
596                    payer_currency: e["cash_fee_type"].to_string(),
597                    trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
598                };
599                Ok(res.json())
600            }
601            Err(e) => Err(e),
602        }
603    }
604
605    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> {
606        let refund = format!("{:.0}", amount * 100.0);
607        let total = format!("{:.0}", total * 100.0);
608
609
610        let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
611        let mut body = object! {
612            "appid": self.appid.clone(),
613            "mch_id"=> self.sp_mchid.clone(),
614            "sub_mch_id"=> sub_mchid,
615            "nonce_str"=>nonce_str,
616            "out_trade_no"=>out_trade_no,
617            "transaction_id"=>transaction_id,
618            "out_refund_no"=>out_refund_no,
619            "total_fee"=>total,
620            "refund_fee"=>refund,
621            "refund_fee_type"=> currency,
622            "refund_desc"=>refund_text
623        };
624        body["sign"] = self.sign_v2(body.clone())?.into();
625        let mut xml = vec!["<xml>".to_owned()];
626        for (key, value) in body.entries() {
627            let t = format!("<{}>{}</{00}>", key, value.clone().clone());
628            xml.push(t);
629        }
630        xml.push("</xml>".to_owned());
631        let xml = xml.join("");
632        let mut http = br_reqwest::Client::new();
633        match http.post("https://api.mch.weixin.qq.com/secapi/pay/refund".to_string().as_str()).header("Content-Type", "application/xml").raw_xml(xml.into()).send()?.xml() {
634            Ok(e) => {
635                println!("{:#}",e);
636                if e.is_empty() {
637                    return Err("已执行".to_string());
638                }
639                if e.has_key("message") {
640                    return Err(e["message"].to_string());
641                }
642                let mut refund_time = 0.0;
643                if e.has_key("success_time") {
644                    let success_time = e["success_time"].as_str().unwrap_or("").to_string();
645                    if !success_time.is_empty() {
646                        let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
647                        refund_time = datetime.timestamp() as f64;
648                    }
649                }
650
651                let status = match e["status"].as_str().unwrap() {
652                    "PROCESSING" => "退款中",
653                    "SUCCESS" => "已退款",
654                    _ => "无退款",
655                };
656                let info = object! {
657                    refund_id: e["refund_id"].clone(),
658                    user_received_account:e["user_received_account"].clone(),
659                    status:status,
660                    refund_time:refund_time,
661                    out_refund_no: e["out_refund_no"].clone(),
662                };
663                Ok(info)
664            }
665            Err(e) => Err(e),
666        }
667    }
668}