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