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::{info};
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 fn config(&mut self) -> JsonValue {
227 todo!()
228 }
229
230
231 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> {
232 let url = match types {
233 Types::Jsapi => "/v3/pay/partner/transactions/jsapi",
234 Types::Native => "/v3/pay/partner/transactions/native",
235 Types::H5 => "/v3/pay/partner/transactions/h5",
236 Types::MiniJsapi => "/v3/pay/partner/transactions/jsapi",
237 Types::App => "/v3/pay/partner/transactions/app",
238 Types::Micropay => "/pay/micropay"
239 };
240 let total = format!("{:.0}", total_fee * 100.0);
241 let mut body = object! {
242 "sp_appid" => self.appid.clone(),
243 "sp_mchid"=> self.sp_mchid.clone(),
244 "sub_mchid"=> sub_mchid,
245 "description"=>description,
246 "out_trade_no"=>out_trade_no,
247 "notify_url"=>self.notify_url.clone(),
248 "support_fapiao"=>true,
249 "amount"=>object! {
250 total: total.parse::<i64>().unwrap(),
251 currency:"CNY"
252 }
253 };
254 match types {
255 Types::Native => {}
256 _ => {
257 body["payer"] = object! {
258 sp_openid:sp_openid
259 };
260 }
261 };
262 match self.http(url, Method::POST, body) {
263 Ok(e) => {
264 match types {
265 Types::Native => {
266 if e.has_key("code_url") {
267 Ok(e["code_url"].clone())
268 } else {
269 Err(e["message"].to_string())
270 }
271 }
272 Types::Jsapi | Types::MiniJsapi => {
273 if e.has_key("prepay_id") {
274 let signinfo = self.paysign(format!("prepay_id={}", e["prepay_id"]).as_str())?;
275 Ok(signinfo)
276 } else {
277 Err(e["message"].to_string())
278 }
279 }
280 _ => {
281 Ok(e)
282 }
283 }
284 }
285 Err(e) => Err(e),
286 }
287 }
288
289 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> {
290 let url = "/pay/micropay";
291 let total = format!("{:.0}", total_fee * 100.0);
292
293 let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
294
295 let mut body = object! {
296 "appid": self.appid.clone(),
297 "mch_id"=> self.sp_mchid.clone(),
298 "sub_mch_id"=> sub_mchid,
299 "nonce_str"=>nonce_str,
300 "body"=> description,
301 "out_trade_no"=>out_trade_no,
302 "total_fee"=>total.parse::<i64>().unwrap(),
303 "fee_type":"CNY",
304 "spbill_create_ip":ip,
305 "device_info":org_openid,
306 "auth_code":auth_code
307 };
308 body["sign"] = self.sign_v2(body.clone())?.into();
309 let mut xml = vec!["<xml>".to_owned()];
310 for (key, value) in body.entries() {
311 let t = format!("<{}>{}</{00}>", key, value.clone().clone());
312 xml.push(t);
313 }
314 xml.push("</xml>".to_owned());
315 let xml = xml.join("");
316 let mut http = br_reqwest::Client::new();
317
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) => {
320 if e["return_code"].ne("SUCCESS") {
321 return Err(e.to_string());
322 }
323 if e["result_code"].eq("FAIL") {
324 if e["err_code_des"].ne("需要用户输入支付密码") {
325 return Err(e["err_code_des"].to_string());
326 }
327 let res = PayNotify {
328 trade_type: TradeType::MICROPAY,
329 out_trade_no: out_trade_no.to_string(),
330 sp_mchid: self.sp_mchid.clone(),
331 sub_mchid: sub_mchid.to_string(),
332 sp_appid: e["appid"].to_string(),
333 transaction_id: "".to_string(),
334 success_time: 0,
335 sp_openid: "".to_string(),
336 sub_openid: "".to_string(),
337 total: total_fee,
338 payer_total: total_fee,
339 currency: "CNY".to_string(),
340 payer_currency: "CNY".to_string(),
341 trade_state: TradeState::NOTPAY,
342 };
343 return Ok(res.json());
344 }
345 let res = PayNotify {
346 trade_type: TradeType::MICROPAY,
347 out_trade_no: out_trade_no.to_string(),
348 sp_mchid: self.sp_mchid.clone(),
349 sub_mchid: sub_mchid.to_string(),
350 sp_appid: e["appid"].to_string(),
351 transaction_id: e["transaction_id"].to_string(),
352 success_time: 0,
353 sp_openid: e["openid"].to_string(),
354 sub_openid: e["openid"].to_string(),
355 total: total_fee,
356 payer_total: total_fee,
357 currency: e["cash_fee_type"].to_string(),
358 payer_currency: "CNY".to_string(),
359 trade_state: TradeState::SUCCESS,
360 };
361 Ok(res.json())
362 }
363 Err(e) => Err(e),
364 }
365 }
366
367 fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
368 let url = format!("/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close");
369 let body = object! {
370 "sp_mchid"=> self.sp_mchid.clone(),
371 "sub_mchid"=> sub_mchid
372 };
373 match self.http(&url, Method::POST, body) {
374 Ok(_) => Ok(true.into()),
375 Err(e) => Err(e)
376 }
377 }
378
379 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
380 let url = format!(
381 "/v3/pay/partner/transactions/out-trade-no/{}?sub_mchid={}&sp_mchid={}",
382 out_trade_no, sub_mchid, self.sp_mchid
383 );
384 match self.http(&url, Method::GET, "".into()) {
385 Ok(e) => {
386 if e.has_key("message") {
387 return Err(e["message"].to_string());
388 }
389 let res = PayNotify {
390 trade_type: TradeType::from(e["trade_type"].to_string().as_str()),
391 out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
392 sp_mchid: e["sp_mchid"].as_str().unwrap().to_string(),
393 sub_mchid: e["sub_mchid"].as_str().unwrap().to_string(),
394 sp_appid: e["sp_appid"].as_str().unwrap().to_string(),
395 transaction_id: e["transaction_id"].to_string(),
396 success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
397 sp_openid: e["payer"]["sp_openid"].to_string(),
398 sub_openid: e["payer"]["sub_openid"].to_string(),
399 total: e["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
400 currency: e["amount"]["currency"].to_string(),
401 payer_total: e["amount"]["payer_total"].as_f64().unwrap_or(0.0) / 100.0,
402 payer_currency: e["amount"]["payer_currency"].to_string(),
403 trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
404 };
405 Ok(res.json())
406 }
407 Err(e) => Err(e),
408 }
409 }
410
411 fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, _channel: &str) -> Result<JsonValue, String> {
412 let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
413 let mut body = object! {
414 "appid": self.appid.clone(),
415 "mch_id"=> self.sp_mchid.clone(),
416 "sub_mch_id"=> sub_mchid,
417 "nonce_str"=>nonce_str,
418 "out_trade_no"=>out_trade_no
419 };
420 body["sign"] = self.sign_v2(body.clone())?.into();
421 let mut xml = vec!["<xml>".to_owned()];
422 for (key, value) in body.entries() {
423 let t = format!("<{}>{}</{00}>", key, value.clone().clone());
424 xml.push(t);
425 }
426 xml.push("</xml>".to_owned());
427 let xml = xml.join("");
428 let mut http = br_reqwest::Client::new();
429 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() {
430 Ok(e) => {
431 if e.has_key("result_code") && e["result_code"] != "SUCCESS" {
432 info!("pay_micropay_query: {e:#}");
433 return Err(e["return_msg"].to_string());
434 }
435 if e["trade_state"].eq("USERPAYING") {
436 let res = PayNotify {
437 trade_type: TradeType::MICROPAY,
438 out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
439 sp_mchid: e["mch_id"].as_str().unwrap().to_string(),
440 sub_mchid: e["sub_mch_id"].as_str().unwrap().to_string(),
441 sp_appid: e["appid"].as_str().unwrap().to_string(),
442 transaction_id: "".to_string(),
443 success_time: 0,
444 sp_openid: "".to_string(),
445 sub_openid: "".to_string(),
446 total: 0.0,
447 currency: "CNY".to_string(),
448 payer_total: 0.0,
449 payer_currency: "CNY".to_string(),
450 trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
451 };
452 return Ok(res.json());
453 }
454 let res = PayNotify {
455 trade_type: TradeType::from(e["trade_type"].to_string().as_str()),
456 out_trade_no: e["out_trade_no"].as_str().unwrap().to_string(),
457 sp_mchid: e["mch_id"].as_str().unwrap().to_string(),
458 sub_mchid: e["sub_mch_id"].as_str().unwrap().to_string(),
459 sp_appid: e["appid"].as_str().unwrap().to_string(),
460 transaction_id: e["transaction_id"].to_string(),
461 success_time: PayNotify::datetime_to_timestamp(e["time_end"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
462 sp_openid: e["device_info"].to_string(),
463 sub_openid: e["openid"].to_string(),
464 total: e["total_fee"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
465 currency: e["fee_type"].to_string(),
466 payer_total: e["cash_fee"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
467 payer_currency: e["cash_fee_type"].to_string(),
468 trade_state: TradeState::from(e["trade_state"].as_str().unwrap()),
469 };
470 Ok(res.json())
471 }
472 Err(e) => Err(e)
473 }
474 }
475 fn pay_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
476 if self.apikey.is_empty() {
477 return Err("apikey 不能为空".to_string());
478 }
479 #[allow(deprecated)]
480 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
481 let cipher = Aes256Gcm::new(key);
482 #[allow(deprecated)]
483 let nonce = Nonce::from_slice(nonce.as_bytes());
484 let data = match STANDARD.decode(ciphertext) {
485 Ok(e) => e,
486 Err(e) => return Err(format!("Invalid data received from API :{e}"))
487 };
488 let payload = Payload {
490 msg: &data,
491 aad: associated_data.as_bytes(),
492 };
493
494 let plaintext = match cipher.decrypt(nonce, payload) {
496 Ok(e) => e,
497 Err(e) => {
498 return Err(format!("解密 API:{e}"));
499 }
500 };
501 let rr = match String::from_utf8(plaintext) {
502 Ok(d) => d,
503 Err(_) => return Err("utf8 error".to_string())
504 };
505 let json = match json::parse(rr.as_str()) {
506 Ok(e) => e,
507 Err(_) => return Err("json error".to_string())
508 };
509 let res = PayNotify {
510 trade_type: TradeType::from(json["trade_type"].as_str().unwrap()),
511 out_trade_no: json["out_trade_no"].as_str().unwrap().to_string(),
512 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
513 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
514 sp_appid: json["sp_appid"].as_str().unwrap().to_string(),
515 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
516 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
517 sp_openid: json["payer"]["sp_openid"].as_str().unwrap().to_string(),
518 sub_openid: json["payer"]["sub_openid"].as_str().unwrap().to_string(),
519 total: json["amount"]["total"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
520 payer_total: json["amount"]["payer_total"].to_string().parse::<f64>().unwrap_or(0.0) / 100.0,
521 currency: json["amount"]["currency"].to_string(),
522 payer_currency: json["amount"]["payer_currency"].to_string(),
523 trade_state: TradeState::from(json["trade_state"].as_str().unwrap()),
524 };
525 Ok(res.json())
526 }
527
528 fn refund(
529 &mut self,
530 sub_mchid: &str,
531 out_trade_no: &str,
532 transaction_id: &str,
533 out_refund_no: &str,
534 amount: f64,
535 total: f64,
536 currency: &str,
537 ) -> Result<JsonValue, String> {
538 let url = "/v3/refund/domestic/refunds";
539
540 let refund = format!("{:.0}", amount * 100.0);
541 let total = format!("{:.0}", total * 100.0);
542
543 let body = object! {
544 "sub_mchid"=> sub_mchid,
545 "transaction_id"=>transaction_id,
546 "out_trade_no"=>out_trade_no,
547 "out_refund_no"=>out_refund_no,
548 "amount"=>object! {
549 refund: refund.parse::<i64>().unwrap(),
550 total: total.parse::<i64>().unwrap(),
551 currency:currency
552 }
553 };
554 match self.http(url, Method::POST, body) {
555 Ok(e) => {
556 if e.is_empty() {
557 return Err("已执行".to_string());
558 }
559 if e.has_key("message") {
560 return Err(e["message"].to_string());
561 }
562 let mut refund_time = 0.0;
563 if e.has_key("success_time") {
564 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
565 if !success_time.is_empty() {
566 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
567 refund_time = datetime.timestamp() as f64;
568 }
569 }
570
571 let status = match e["status"].as_str().unwrap() {
572 "PROCESSING" => "退款中",
573 "SUCCESS" => "已退款",
574 _ => "无退款",
575 };
576 let info = object! {
577 refund_id: e["refund_id"].clone(),
578 user_received_account:e["user_received_account"].clone(),
579 status:status,
580 refund_time:refund_time,
581 out_refund_no: e["out_refund_no"].clone(),
582 };
583 Ok(info)
584 }
585 Err(e) => Err(e)
586 }
587 }
588
589 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> {
590 let refund = format!("{:.0}", amount * 100.0);
591 let total = format!("{:.0}", total * 100.0);
592
593
594 let nonce_str: String = rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect();
595 let mut body = object! {
596 "appid": self.appid.clone(),
597 "mch_id"=> self.sp_mchid.clone(),
598 "sub_mch_id"=> sub_mchid,
599 "nonce_str"=>nonce_str,
600 "out_trade_no"=>out_trade_no,
601 "transaction_id"=>transaction_id,
602 "out_refund_no"=>out_refund_no,
603 "total_fee"=>total,
604 "refund_fee"=>refund,
605 "refund_fee_type"=> currency,
606 "refund_desc"=>refund_text
607 };
608 body["sign"] = self.sign_v2(body.clone())?.into();
609 let mut xml = vec!["<xml>".to_owned()];
610 for (key, value) in body.entries() {
611 let t = format!("<{}>{}</{00}>", key, value.clone().clone());
612 xml.push(t);
613 }
614 xml.push("</xml>".to_owned());
615 let xml = xml.join("");
616 let mut http = br_reqwest::Client::new();
617 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() {
618 Ok(e) => {
619 println!("{e:#}");
620 if !e.has_key("status") {
621 return Err(e.to_string());
622 }
623 if e.is_empty() {
624 return Err("已执行".to_string());
625 }
626 if e.has_key("message") {
627 return Err(e["message"].to_string());
628 }
629 let mut refund_time = 0.0;
630 if e.has_key("success_time") {
631 let success_time = e["success_time"].as_str().unwrap_or("").to_string();
632 if !success_time.is_empty() {
633 let datetime = DateTime::parse_from_rfc3339(success_time.as_str()).unwrap();
634 refund_time = datetime.timestamp() as f64;
635 }
636 }
637
638 let status = match e["status"].as_str().unwrap() {
639 "PROCESSING" => "退款中",
640 "SUCCESS" => "已退款",
641 _ => "无退款",
642 };
643 let info = object! {
644 refund_id: e["refund_id"].clone(),
645 user_received_account:e["user_received_account"].clone(),
646 status:status,
647 refund_time:refund_time,
648 out_refund_no: e["out_refund_no"].clone(),
649 };
650 Ok(info)
651 }
652 Err(e) => Err(e),
653 }
654 }
655
656 fn refund_notify(&mut self, nonce: &str, ciphertext: &str, associated_data: &str) -> Result<JsonValue, String> {
657 if self.apikey.is_empty() {
658 return Err("apikey 不能为空".to_string());
659 }
660 #[allow(deprecated)]
661 let key = Key::<Aes256Gcm>::from_slice(self.apikey.as_bytes());
662 let cipher = Aes256Gcm::new(key);
663 #[allow(deprecated)]
664 let nonce = Nonce::from_slice(nonce.as_bytes());
665 let data = match STANDARD.decode(ciphertext) {
666 Ok(e) => e,
667 Err(e) => return Err(format!("Invalid data received from API :{e}"))
668 };
669 let payload = Payload {
671 msg: &data,
672 aad: associated_data.as_bytes(),
673 };
674
675 let plaintext = match cipher.decrypt(nonce, payload) {
677 Ok(e) => e,
678 Err(e) => {
679 return Err(format!("解密 API:{e}"));
680 }
681 };
682 let rr = match String::from_utf8(plaintext) {
683 Ok(d) => d,
684 Err(_) => return Err("utf8 error".to_string())
685 };
686 let json = match json::parse(rr.as_str()) {
687 Ok(e) => e,
688 Err(_) => return Err("json error".to_string())
689 };
690 let res = RefundNotify {
691 out_trade_no: json["out_trade_no"].to_string(),
692 refund_no: json["out_refund_no"].to_string(),
693 refund_id: json["refund_id"].to_string(),
694 sp_mchid: json["sp_mchid"].as_str().unwrap().to_string(),
695 sub_mchid: json["sub_mchid"].as_str().unwrap().to_string(),
696 transaction_id: json["transaction_id"].as_str().unwrap().to_string(),
697 success_time: PayNotify::success_time(json["success_time"].as_str().unwrap_or("")),
698 total: json["amount"]["total"].as_f64().unwrap_or(0.0) / 100.0,
699 refund: json["amount"]["refund"].as_f64().unwrap_or(0.0) / 100.0,
700 payer_total: json["amount"]["payer_total"].as_f64().unwrap() / 100.0,
701 payer_refund: json["amount"]["payer_refund"].as_f64().unwrap() / 100.0,
702 status: RefundStatus::from(json["refund_status"].as_str().unwrap()),
703 };
704 Ok(res.json())
705 }
706
707 fn refund_query(&mut self, _trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
708 let url = format!("/v3/refund/domestic/refunds/{out_refund_no}?sub_mchid={sub_mchid}");
709 match self.http(&url, Method::GET, "".into()) {
710 Ok(e) => {
711 if e.is_empty() {
712 return Err("已执行".to_string());
713 }
714 if e.has_key("message") {
715 return Err(e["message"].to_string());
716 }
717
718
719 let res = RefundNotify {
720 out_trade_no: e["out_trade_no"].to_string(),
721 refund_no: e["out_refund_no"].to_string(),
722 sp_mchid: "".to_string(),
723 sub_mchid: sub_mchid.to_string(),
724 transaction_id: e["transaction_id"].to_string(),
725 refund_id: e["refund_id"].to_string(),
726 success_time: PayNotify::success_time(e["success_time"].as_str().unwrap_or("")),
727 total: e["amount"]["total"].to_string().parse::<f64>().unwrap()/100.0,
728 payer_total: e["amount"]["total"].to_string().parse::<f64>().unwrap()/100.0,
729 refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap()/100.0,
730 payer_refund: e["amount"]["refund"].to_string().parse::<f64>().unwrap()/100.0,
731 status: RefundStatus::from(e["status"].as_str().unwrap()),
732 };
733
734 Ok(res.json())
735 }
736 Err(e) => Err(e),
737 }
738 }
739
740 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> {
741 let contact_info_data = object! {
742 contact_type:contact_info["contact_type"].clone(),
744 contact_name:contact_info["contact_name"].clone(),
745 };
746
747 let body = object! {
748 business_code:business_code,
749 contact_info:contact_info_data
750 };
751 println!("{body:#}");
752 match self.http("/v3/applyment4sub/applyment/", Method::POST, body) {
753 Ok(e) => {
754 println!("{e:#}");
755 if e.is_empty() {
756 return Err("已执行".to_string());
757 }
758 if e.has_key("message") {
759 return Err(e["message"].to_string());
760 }
761 Ok(e)
762 }
763 Err(e) => Err(e),
764 }
765 }
766}