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 let sign = self.sign("GET", url.as_str(), "")?;
205 let mut http = br_reqwest::Client::new();
206 match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
207 Ok(e) => {
208 if e.has_key("message") {
209 return Err(e["message"].to_string());
210 }
211 let res = PayNotify {
212 trade_type: TradeType::from(e["trade_type"].to_string().as_str()),
213 out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
214 sp_mchid: e["sp_mchid"].as_str().unwrap().to_string(),
215 sub_mchid: e["sub_mchid"].as_str().unwrap().to_string(),
216 sp_appid: e["sp_appid"].as_str().unwrap().to_string(),
217 transaction_id: e["transaction_id"].to_string(),
218 success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
219 sp_openid: e["payer"]["sp_openid"].to_string(),
220 sub_openid: e["payer"]["sub_openid"].to_string(),
221 total: e["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
222 currency: e["amount"]["currency"].to_string(),
223 payer_total: e["amount"]["payer_total"].as_f64().unwrap_or(0.0) / 100.0,
224 payer_currency: e["amount"]["payer_currency"].to_string(),
225 trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
226 };
227
228 Ok(res.json())
229 }
230 Err(e) => Err(e),
231 }
232 }
233
234 fn refund(
235 &mut self,
236 sub_mchid: &str,
237 out_trade_no: &str,
238 transaction_id: &str,
239 out_refund_no: &str,
240 amount: f64,
241 total: f64,
242 currency: &str,
243 ) -> Result<JsonValue, String> {
244 let url = "/v3/refund/domestic/refunds";
245
246 let refund = format!("{:.0}", amount * 100.0);
247 let total = format!("{:.0}", total * 100.0);
248
249 let body = object! {
250 "sub_mchid"=> sub_mchid,
251 "transaction_id"=>transaction_id,
252 "out_trade_no"=>out_trade_no,
253 "out_refund_no"=>out_refund_no,
254 "amount"=>object! {
255 refund: refund.parse::<i64>().unwrap(),
256 total: total.parse::<i64>().unwrap(),
257 currency:currency
258 }
259 };
260 let sign = self.sign("POST", url, body.to_string().as_str())?;
261 let mut http = br_reqwest::Client::new();
262 match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).send()?.json() {
263 Ok(e) => {
264 if e.is_empty() {
265 return Err("已执行".to_string());
266 }
267 if e.has_key("message") {
268 return Err(e["message"].to_string());
269 }
270 let mut refund_time = 0.0;
271 if e.has_key("success_time") {
272 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
273 if !success_time.is_empty() {
274 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
275 refund_time = datetime.timestamp() as f64;
276 }
277 }
278
279 let status = match e["status"].as_str().unwrap() {
280 "PROCESSING" => "退款中",
281 "SUCCESS" => "已退款",
282 _ => "无退款",
283 };
284 let info = object! {
285 refund_id: e["refund_id"].clone(),
286 user_received_account:e["user_received_account"].clone(),
287 status:status,
288 refund_time:refund_time,
289 out_refund_no: e["out_refund_no"].clone(),
290 };
291 Ok(info)
292 }
293 Err(e) => Err(e)
294 }
295 }
296
297 fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
298 if self.apikey.is_empty() {
299 return Err("apikey 不能为空".to_string());
300 }
301 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
302 let cipher = Aes256Gcm::new(key);
303 let nonce = Nonce::from_slice(nonce.as_bytes());
304 let data = match STANDARD.decode(ciphertext) {
305 Ok(e) => e,
306 Err(e) => return Err(format!("Invalid data received from API :{}", e))
307 };
308 let payload = Payload {
310 msg: &data,
311 aad: associated_data.as_bytes(),
312 };
313
314 let plaintext = match cipher.decrypt(nonce, payload) {
316 Ok(e) => e,
317 Err(e) => {
318 return Err(format!("解密 API:{}", e));
319 }
320 };
321 let rr = match String::from_utf8(plaintext) {
322 Ok(d) => d,
323 Err(_) => return Err("utf8 error".to_string())
324 };
325 let json = match json::parse(rr.as_str()) {
326 Ok(e) => e,
327 Err(_) => return Err("json error".to_string())
328 };
329 let res = PayNotify {
330 trade_type: TradeType::from(json["trade_type"].as_str().unwrap()),
331 out_trade_no: json["out_trade_no"].as_str().unwrap().to_string(),
332 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
333 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
334 sp_appid: json["sp_appid"].as_str().unwrap().to_string(),
335 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
336 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
337 sp_openid: json["payer"]["sp_openid"].as_str().unwrap().to_string(),
338 sub_openid: json["payer"]["sub_openid"].as_str().unwrap().to_string(),
339 total: json["amount"]["total"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
340 payer_total: json["amount"]["payer_total"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
341 currency: json["amount"]["currency"].to_string(),
342 payer_currency: json["amount"]["payer_currency"].to_string(),
343 trade_state: TradeState::from(json["trade_state"].as_str().unwrap()),
344 };
345 Ok(res.json())
346 }
347
348 fn refund_query(&mut self, _trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
349 let url = format!("/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={}", sub_mchid);
350 let sign = self.sign("GET", url.as_str(), "")?;
351 let mut http = br_reqwest::Client::new();
352 match http.get(format!("https://api.mch.weixin.qq.com{}", url.clone()).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).send()?.json() {
353 Ok(e) => {
354 if e.is_empty() {
355 return Err("已执行".to_string());
356 }
357 if e.has_key("message") {
358 return Err(e["message"].to_string());
359 }
360
361
362 let res = RefundNotify {
363 out_trade_no: e["out_trade_no"].to_string(),
364 refund_no: e["out_refund_no"].to_string(),
365 sp_mchid: "".to_string(),
366 sub_mchid: sub_mchid.to_string(),
367 transaction_id: e["transaction_id"].to_string(),
368 refund_id: e["refund_id"].to_string(),
369 success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
370 total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
371 payer_total: e["amount"]["total"].to_string().parse::<f64>().unwrap(),
372 refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
373 payer_refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap(),
374 status: RefundStatus::from(e["status"].as_str().unwrap()),
375 };
376
377 Ok(res.json())
378 }
379 Err(e) => Err(e),
380 }
381 }
382 fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
383 if self.apikey.is_empty() {
384 return Err("apikey 不能为空".to_string());
385 }
386 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
387 let cipher = Aes256Gcm::new(key);
388 let nonce = Nonce::from_slice(nonce.as_bytes());
389 let data = match STANDARD.decode(ciphertext) {
390 Ok(e) => e,
391 Err(e) => return Err(format!("Invalid data received from API :{}", e))
392 };
393 let payload = Payload {
395 msg: &data,
396 aad: associated_data.as_bytes(),
397 };
398
399 let plaintext = match cipher.decrypt(nonce, payload) {
401 Ok(e) => e,
402 Err(e) => {
403 return Err(format!("解密 API:{}", e));
404 }
405 };
406 let rr = match String::from_utf8(plaintext) {
407 Ok(d) => d,
408 Err(_) => return Err("utf8 error".to_string())
409 };
410 let json = match json::parse(rr.as_str()) {
411 Ok(e) => e,
412 Err(_) => return Err("json error".to_string())
413 };
414 let res = RefundNotify {
415 out_trade_no: json["out_trade_no"].to_string(),
416 refund_no: json["out_refund_no"].to_string(),
417 refund_id: json["refund_id"].to_string(),
418 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
419 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
420 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
421 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
422 total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
423 refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
424 payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
425 payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
426 status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
427 };
428 Ok(res.json())
429 }
430
431 fn auth(&mut self, _code: &str) -> Result<JsonValue, String> {
432 todo!()
433 }
434
435 fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
436 let url = format!("/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close");
437 let body = object! {
438 "sp_mchid"=> self.sp_mchid.clone(),
439 "sub_mchid"=> sub_mchid
440 };
441 let sign = self.sign("POST", url.as_str(), body.to_string().as_str())?;
442 let mut http = br_reqwest::Client::new();
443 match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", sign.as_str()).raw_json(body).send()?.json() {
444 Ok(_) => Ok(true.into()),
445 Err(e) => Err(e)
446 }
447 }
448
449 fn config(&mut self) -> JsonValue {
450 todo!()
451 }
452
453 fn pay(&mut self, types: Types, sub_mchid: &str, out_trade_no: &str, description: &str, total_fee: f64, sp_openid: &str) -> Result<JsonValue, String> {
454 let url = match types {
455 Types::Jsapi => "/v3/pay/partner/transactions/jsapi",
456 Types::Native => "/v3/pay/partner/transactions/native",
457 Types::H5 => "/v3/pay/partner/transactions/h5",
458 Types::MiniJsapi => "/v3/pay/partner/transactions/jsapi",
459 Types::App => "/v3/pay/partner/transactions/app",
460 Types::Micropay => "/pay/micropay"
461 };
462 let total = format!("{:.0}", total_fee * 100.0);
463 let mut body = object! {
464 "sp_appid" => self.appid.clone(),
465 "sp_mchid"=> self.sp_mchid.clone(),
466 "sub_mchid"=> sub_mchid,
467 "description"=>description,
468 "out_trade_no"=>out_trade_no,
469 "notify_url"=>self.notify_url.clone(),
470 "support_fapiao"=>true,
471 "amount"=>object! {
472 total: total.parse::<i64>().unwrap(),
473 currency:"CNY"
474 }
475 };
476 match types {
477 Types::Native => {}
478 _ => {
479 body["payer"] = object! {
480 sp_openid:sp_openid
481 };
482 }
483 };
484 let sign = self.sign("POST", url, body.to_string().as_str())?;
485 let mut http = br_reqwest::Client::new();
486 match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Accept", "application/json").header("User-Agent", "api").header("Content-Type", "application/json").header("Authorization", &sign).raw_json(body).send()?.json() {
487 Ok(e) => {
488 match types {
489 Types::Native => {
490 if e.has_key("code_url") {
491 Ok(e["code_url"].clone())
492 } else {
493 Err(e["message"].to_string())
494 }
495 }
496 Types::Jsapi | Types::MiniJsapi => {
497 if e.has_key("prepay_id") {
498 let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str())?;
499 Ok(signinfo)
500 } else {
501 Err(e["message"].to_string())
502 }
503 }
504 _ => {
505 Ok(e)
506 }
507 }
508 }
509 Err(e) => Err(e),
510 }
511 }
512 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> {
513 let url = "/pay/micropay";
514 let total = format!("{:.0}", total_fee * 100.0);
515
516 let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
517
518 let mut body = object! {
519 "appid": self.appid.clone(),
520 "mch_id"=> self.sp_mchid.clone(),
521 "sub_mch_id"=> sub_mchid,
522 "nonce_str"=>nonce_str,
523 "body"=> description,
524 "out_trade_no"=>out_trade_no,
525 "total_fee"=>total.parse::<i64>().unwrap(),
526 "fee_type":"CNY",
527 "spbill_create_ip":ip,
528 "device_info":org_openid,
529 "auth_code":auth_code
530 };
531 body["sign"] = self.sign_v2(body.clone())?.into();
532 let mut xml = vec!["<xml>".to_owned()];
533 for (key, value) in body.entries() {
534 let t = format!("<{}>{}</{00}>", key, value.clone().clone());
535 xml.push(t);
536 }
537 xml.push("</xml>".to_owned());
538 let xml = xml.join("");
539 let mut http = br_reqwest::Client::new();
540 match http.post(format!("https://api.mch.weixin.qq.com{}", url).as_str()).header("Content-Type", "application/xml").raw_xml(xml.into()).send()?.xml() {
541 Ok(e) => Ok(e),
542 Err(e) => Err(e),
543 }
544 }
545
546 fn notify(&mut self, _data: JsonValue) -> Result<JsonValue, String> {
547 todo!()
548 }
549
550 fn get_sub_mchid(&mut self, sub_mchid: &str) -> Result<JsonValue, String> {
551 let url = format!("/v3/apply4sub/sub_merchants/{sub_mchid}/settlement");
552 let res = self.http(url.as_str(), Method::GET, "".into())?;
553 if res.has_key("verify_result") && res["verify_result"] == "VERIFY_SUCCESS" {
554 return Ok(true.into());
555 }
556 Err(res.to_string())
557 }
558
559 fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
560 let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
561 let mut body = object! {
562 "appid": self.appid.clone(),
563 "mch_id"=> self.sp_mchid.clone(),
564 "sub_mch_id"=> sub_mchid,
565 "nonce_str"=>nonce_str,
566 "out_trade_no"=>out_trade_no
567 };
568 body["sign"] = self.sign_v2(body.clone())?.into();
569 let mut xml = vec!["<xml>".to_owned()];
570 for (key, value) in body.entries() {
571 let t = format!("<{}>{}</{00}>", key, value.clone().clone());
572 xml.push(t);
573 }
574 xml.push("</xml>".to_owned());
575 let xml = xml.join("");
576 let mut http = br_reqwest::Client::new();
577 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() {
578 Ok(e) => {
579 if e.has_key("result_code") && e["result_code"] != "SUCCESS" {
580 error!("pay_micropay_query: {:#}", e);
581 return Err(e["return_msg"].to_string());
582 }
583 let res = PayNotify {
584 trade_type: TradeType::from(e["trade_type"].to_string().as_str()),
585 out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
586 sp_mchid: e["mch_id"].as_str().unwrap().to_string(),
587 sub_mchid: e["sub_mch_id"].as_str().unwrap().to_string(),
588 sp_appid: e["appid"].as_str().unwrap().to_string(),
589 transaction_id: e["transaction_id"].to_string(),
590 success_time: PayNotify::micropay_time(e["time_end"].as_str().unwrap_or("")),
591 sp_openid: e["device_info"].to_string(),
592 sub_openid: e["openid"].to_string(),
593 total: e["total_fee"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
594 currency: e["fee_type"].to_string(),
595 payer_total: e["cash_fee"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
596 payer_currency: e["cash_fee_type"].to_string(),
597 trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
598 };
599 Ok(res.json())
600 }
601 Err(e) => Err(e),
602 }
603 }
604
605 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> {
606 let refund = format!("{:.0}", amount * 100.0);
607 let total = format!("{:.0}", total * 100.0);
608
609
610 let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
611 let mut body = object! {
612 "appid": self.appid.clone(),
613 "mch_id"=> self.sp_mchid.clone(),
614 "sub_mch_id"=> sub_mchid,
615 "nonce_str"=>nonce_str,
616 "out_trade_no"=>out_trade_no,
617 "transaction_id"=>transaction_id,
618 "out_refund_no"=>out_refund_no,
619 "total_fee"=>total,
620 "refund_fee"=>refund,
621 "refund_fee_type"=> currency,
622 "refund_desc"=>refund_text
623 };
624 body["sign"] = self.sign_v2(body.clone())?.into();
625 let mut xml = vec!["<xml>".to_owned()];
626 for (key, value) in body.entries() {
627 let t = format!("<{}>{}</{00}>", key, value.clone().clone());
628 xml.push(t);
629 }
630 xml.push("</xml>".to_owned());
631 let xml = xml.join("");
632 let mut http = br_reqwest::Client::new();
633 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() {
634 Ok(e) => {
635 println!("{:#}",e);
636 if e.is_empty() {
637 return Err("已执行".to_string());
638 }
639 if e.has_key("message") {
640 return Err(e["message"].to_string());
641 }
642 let mut refund_time = 0.0;
643 if e.has_key("success_time") {
644 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
645 if !success_time.is_empty() {
646 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
647 refund_time = datetime.timestamp() as f64;
648 }
649 }
650
651 let status = match e["status"].as_str().unwrap() {
652 "PROCESSING" => "退款中",
653 "SUCCESS" => "已退款",
654 _ => "无退款",
655 };
656 let info = object! {
657 refund_id: e["refund_id"].clone(),
658 user_received_account:e["user_received_account"].clone(),
659 status:status,
660 refund_time:refund_time,
661 out_refund_no: e["out_refund_no"].clone(),
662 };
663 Ok(info)
664 }
665 Err(e) => Err(e),
666 }
667 }
668}