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