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