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