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