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