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