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