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