br_pay/
wechat.rs

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