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