1use base64::Engine;
2use base64::engine::general_purpose;
3use chrono::Local;
4use json::{object, JsonValue};
5use log::{debug, info, warn};
6use xmltree::Element;
7use crate::{PayMode, PayNotify, RefundNotify, RefundStatus, TradeState, TradeType, Types};
8use cipher::{BlockEncryptMut, KeyInit};
9use cipher::block_padding::Pkcs7;
10use des::Des;
11use rand::{rng, Rng};
12
13
14#[derive(Clone, Debug)]
16pub struct Ccbc {
17 pub debug: bool,
19 pub appid: String,
21 pub appid_subscribe: String,
23 pub sp_mchid: String,
25 pub sp_user_id: String,
27 pub sp_pass: String,
29 pub sp_posid: String,
32 pub notify_url: String,
33 pub sub_posid: String,
35 pub branchid: String,
37 pub public_key: String,
39 pub client_ip: String,
40 pub retry: usize,
42 pub query_url: String,
44}
45
46impl Ccbc {
47 pub fn http(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
48 let mut mac = vec![];
49 let mut path = vec![];
50 let fields = ["MAC"];
51 for (key, value) in body.entries() {
52 if value.is_empty() && fields.contains(&key) {
53 continue;
54 }
55 if key != "PUB" {
56 path.push(format!("{key}={value}"));
57 }
58 mac.push(format!("{key}={value}"));
59 }
60
61
62 let mac_text = mac.join("&");
63 let path = path.join("&");
64 if self.debug {
65 debug!("{mac_text:#}");
66 }
67 body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
68 if self.debug {
69 debug!("{body:#}");
70 }
71 body.remove("PUB");
72 let mac = format!("{}&MAC={}", path, body["MAC"]);
73 if self.debug {
74 debug!("{mac:#}");
75 }
76 let urls = format!("{url}&{mac}");
77 if self.debug {
78 debug!("{urls:#}");
79 }
80 let mut http = br_reqwest::Client::new();
81
82 let res = http.post(urls.as_str()).raw_json(body.clone()).set_retry(3).send()?;
83 let res = res.body().to_string();
84 match json::parse(&res) {
85 Ok(e) => Ok(e),
86 Err(e) => Err(e.to_string())
87 }
88 }
89 pub fn http_alipay(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
90 let mut mac = vec![];
91 let mut path = vec![];
92 let fields = ["MAC", "SUBJECT", "AREA_INFO"];
93 for (key, value) in body.entries() {
94 if value.is_empty() && fields.contains(&key) {
95 continue;
96 }
97 if fields.contains(&key) {
98 continue;
99 }
100 if key != "PUB" {
101 path.push(format!("{key}={value}"));
102 }
103 mac.push(format!("{key}={value}"));
104 }
105
106
107 let mac_text = mac.join("&");
108 let path = path.join("&");
109 body["MAC"] = br_crypto::md5::encrypt_hex(mac_text.as_bytes()).into();
110 body.remove("PUB");
111 let mac = format!("{}&MAC={}", path, body["MAC"]);
112
113 let urls = format!("{url}&{mac}");
114
115 let mut http = br_reqwest::Client::new();
116 let res = match http.post(urls.as_str()).raw_json(body.clone()).send() {
117 Ok(e) => e,
118 Err(e) => {
119 if self.retry > 2 {
120 return Err(e.to_string());
121 }
122 self.retry += 1;
123 warn!("建行接口重试: {}", self.retry);
124 body.remove("MAC");
125 let res = self.http_alipay(url, body.clone())?;
126 return Ok(res);
127 }
128 };
129 let res = res.body().to_string();
130 match json::parse(&res) {
131 Ok(e) => Ok(e),
132 Err(_) => Err(res)
133 }
134 }
135 fn escape_unicode(&mut self, s: &str) -> String {
136 s.chars().map(|c| {
137 if c.is_ascii() {
138 c.to_string()
139 } else {
140 format!("%u{:04X}", c as u32)
141 }
142 }).collect::<String>()
143 }
144 fn _unescape_unicode(&mut self, s: &str) -> String {
145 let mut output = String::new();
146 let mut chars = s.chars().peekable();
147 while let Some(c) = chars.next() {
148 if c == '%' && chars.peek() == Some(&'u') {
149 chars.next(); let codepoint: String = chars.by_ref().take(4).collect();
151 if let Ok(value) = u32::from_str_radix(&codepoint, 16) {
152 if let Some(ch) = std::char::from_u32(value) {
153 output.push(ch);
154 }
155 }
156 } else {
157 output.push(c);
158 }
159 }
160 output
161 }
162 pub fn http_q(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
163 let mut path = vec![];
164 let fields = ["MAC"];
165 for (key, value) in body.entries() {
166 if value.is_empty() && fields.contains(&key) {
167 continue;
168 }
169 if key.contains("QUPWD") {
170 path.push(format!("{key}="));
171 continue;
172 }
173 path.push(format!("{key}={value}"));
174 }
175
176 let mac = path.join("&");
177 body["MAC"] = br_crypto::md5::encrypt_hex(mac.as_bytes()).into();
178
179 let mut map = vec![];
180 for (key, value) in body.entries() {
181 map.push((key, value.to_string()));
182 }
183
184 let mut http = br_reqwest::Client::new();
185
186 http.header("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36");
187
188 let res = match http.post(url).form_urlencoded(body.clone()).send() {
189 Ok(d) => d,
190 Err(e) => {
191 if self.retry > 2 {
192 return Err(e.to_string());
193 }
194 self.retry += 1;
195 warn!("建行查询接口重试: {}", self.retry);
196 body.remove("MAC");
197 let res = self.http_q(url, body.clone())?;
198 return Ok(res);
199 }
200 };
201 let res = res.body().to_string().trim().to_string();
202 match Element::parse(res.as_bytes()) {
203 Ok(e) => Ok(xml_element_to_json(&e)),
204 Err(e) => Err(e.to_string())
205 }
206 }
207
208 pub fn http_ccb_param(&mut self, url: &str, mut body: JsonValue) -> Result<JsonValue, String> {
209 let mer_info = format!("MERCHANTID={}&POSID={}&BRANCHID={}", body["MERCHANTID"], body["POSID"], body["BRANCHID"]);
210 let ccb_param = self.make_ccb_param(body.clone())?;
211 let urls = format!("{url}?{mer_info}&ccbParam={ccb_param}");
212 let mut http = br_reqwest::Client::new();
213 let res = match http.post(urls.as_str()).raw_json(body.clone()).send() {
214 Ok(e) => e,
215 Err(e) => {
216 if self.retry > 2 {
217 return Err(e.to_string());
218 }
219 self.retry += 1;
220 warn!("建行接口重试: {}", self.retry);
221 body.remove("MAC");
222 let res = self.http_ccb_param(url, body.clone())?;
223 return Ok(res);
224 }
225 };
226 let res = res.body().to_string();
227 match json::parse(&res) {
228 Ok(e) => Ok(e),
229 Err(_) => Err(res)
230 }
231 }
232
233 pub fn make_ccb_param(&mut self, body: JsonValue) -> Result<String, String> {
234 if self.public_key.is_empty() || self.public_key.len() < 30 {
235 return Err(String::from("Public key is empty"));
236 }
237 let pubkey = self.public_key[self.public_key.len() - 30..].to_string();
238 if pubkey.len() < 8 {
239 return Err(String::from("Public key len 8"));
240 }
241 let pubkey = &pubkey[..8];
242 let mut mac = vec![];
243 let mut arr = vec![];
244 let mut params = vec![];
245 for (key, value) in body.entries() {
246 arr.push(key);
247 params.push(format!("{key}={value}"));
248 }
249 arr.sort();
250 for key in arr.iter() {
251 if body.has_key(key) && !body[key.to_string()].is_empty() {
252 mac.push(format!("{key}={}", body[key.to_string()]));
253 }
254 }
255 let ccb_param = mac.join("&");
256 let ccb_param = format!("{}20120315201809041004", ccb_param);
257 let ccb_param = br_crypto::md5::encrypt_hex(ccb_param.as_bytes());
258 let params = params.join("&");
259 let params = format!("{params}&SIGN={ccb_param}");
260 let bytes = self.utf16_bytes(params.as_str());
261 let encrypt = self.des_ecb_pkcs5_base64(&bytes, pubkey)?;
262 let encrypt = encrypt.replace("+", ",");
263 let url = br_crypto::encoding::urlencoding_encode(encrypt.as_str());
264 Ok(url)
265 }
266 fn utf16_bytes(&mut self, s: &str) -> Vec<u8> {
267 let mut out = Vec::with_capacity(2 + s.len() * 2);
268 out.push(0xFE);
269 out.push(0xFF);
270 for u in s.encode_utf16() {
271 out.push((u >> 8) as u8);
272 out.push((u & 0xFF) as u8);
273 }
274 out
275 }
276 fn des_ecb_pkcs5_base64(&mut self, plain: &[u8], key_str: &str) -> Result<String, &'static str> {
277 let kb = key_str.as_bytes();
278 if kb.len() < 8 {
279 return Err("DES key must be at least 8 bytes");
280 }
281 let mut key = [0u8; 8];
282 key.copy_from_slice(&kb[..8]);
283
284 let cipher = ecb::Encryptor::<Des>::new(&key.into());
285
286 let orig_len = plain.len();
288 let mut buf = vec![0u8; orig_len + 8];
289 buf[..orig_len].copy_from_slice(plain);
290
291 let ct = cipher.encrypt_padded_mut::<Pkcs7>(&mut buf, orig_len).map_err(|_| "encrypt error")?;
292
293 Ok(general_purpose::STANDARD.encode(ct))
294 }
295 fn https_cert(&mut self, text: &str) -> Result<JsonValue, String> {
296 let text = br_crypto::encoding::urlencoding_encode(text);
297 let mut http = br_reqwest::Client::new();
298 http.header("Connection", "close");
299 http.post(self.query_url.as_str()).form_urlencoded(object! {
300 requestXml:text
301 });
302 let res = http.send()?;
303 let ress = br_crypto::encoding::gb18030_to_utf8(res.stream())?;
304 let ress = ress.replace(r#"encoding="GB18030""#, r#"encoding="UTF-8""#);
305 match Element::parse(ress.trim().as_bytes()) {
306 Ok(e) => {
307 let json = xml_element_to_json(&e);
308 Ok(json)
309 }
310 Err(e) => Err(e.to_string())
311 }
312 }
313
314 fn get_xml_text(&mut self, name: &str) -> String {
315 let res = match name {
316 "5W1002.xml" => r#"<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
317<TX>
318 <REQUEST_SN>{{REQUEST_SN}}</REQUEST_SN>
319 <CUST_ID>{{CUST_ID}}</CUST_ID>
320 <USER_ID>{{USER_ID}}</USER_ID>
321 <PASSWORD>{{PASSWORD}}</PASSWORD>
322 <TX_CODE>5W1002</TX_CODE>
323 <LANGUAGE>CN</LANGUAGE>
324 <TX_INFO>
325 <START>{{START}}</START>
326 <STARTHOUR></STARTHOUR>
327 <STARTMIN></STARTMIN>
328 <END>{{END}}</END>
329 <ENDHOUR></ENDHOUR>
330 <ENDMIN></ENDMIN>
331 <KIND>{{KIND}}</KIND>
332 <ORDER>{{ORDER}}</ORDER>
333 <ACCOUNT></ACCOUNT>
334 <DEXCEL>1</DEXCEL>
335 <MONEY></MONEY>
336 <NORDERBY>2</NORDERBY>
337 <PAGE>1</PAGE>
338 <POS_CODE>{{POS_CODE}}</POS_CODE>
339 <STATUS>{{STATUS}}</STATUS>
340 <Mrch_No>{{Mrch_No}}</Mrch_No>
341 <TXN_TPCD></TXN_TPCD>
342 </TX_INFO>
343</TX>"#,
344 "5W1003.xml" => r#"<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
345<TX>
346 <REQUEST_SN>{{REQUEST_SN}}</REQUEST_SN>
347 <CUST_ID>{{CUST_ID}}</CUST_ID>
348 <USER_ID>{{USER_ID}}</USER_ID>
349 <PASSWORD>{{PASSWORD}}</PASSWORD>
350 <TX_CODE>5W1003</TX_CODE>
351 <LANGUAGE>CN</LANGUAGE>
352 <TX_INFO>
353 <START>{{START}}</START>
354 <STARTHOUR></STARTHOUR>
355 <STARTMIN></STARTMIN>
356 <END>{{END}}</END>
357 <ENDHOUR></ENDHOUR>
358 <ENDMIN></ENDMIN>
359 <KIND>{{KIND}}</KIND>
360 <ORDER>{{ORDER}}</ORDER>
361 <ACCOUNT></ACCOUNT>
362 <MONEY></MONEY>
363 <NORDERBY>2</NORDERBY>
364 <PAGE>1</PAGE>
365 <POS_CODE>{{POS_CODE}}</POS_CODE>
366 <STATUS>{{STATUS}}</STATUS>
367 <Mrch_No>{{Mrch_No}}</Mrch_No>
368 <TXN_TPCD></TXN_TPCD>
369 </TX_INFO>
370</TX>"#,
371 "5W1024.xml" => r#"<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
372<TX>
373 <REQUEST_SN>{{REQUEST_SN}}</REQUEST_SN>
374 <CUST_ID>{{CUST_ID}}</CUST_ID>
375 <USER_ID>{{USER_ID}}</USER_ID>
376 <PASSWORD>{{PASSWORD}}</PASSWORD>
377 <TX_CODE>5W1024</TX_CODE>
378 <LANGUAGE>CN</LANGUAGE>
379 <TX_INFO>
380 <MONEY>{{MONEY}}</MONEY>
381 <ORDER>{{ORDER}}</ORDER>
382 <REFUND_CODE>{{REFUND_CODE}}</REFUND_CODE>
383 <Mrch_No>{{Mrch_No}}</Mrch_No>
384 </TX_INFO>
385 <SIGN_INFO></SIGN_INFO>
386 <SIGNCERT></SIGNCERT>
387</TX>"#,
388 _ => r#""#
389 };
390
391 let xml_text = res.as_bytes().to_vec();
392 let mut text = unsafe { String::from_utf8_unchecked(xml_text) };
393 let mut rng = rng();
394 let num: String = (0..14).map(|_| rng.random_range(0..10).to_string()).collect();
395 text = text.replace("{{REQUEST_SN}}", num.as_str());
396 text = text.replace("{{CUST_ID}}", self.sp_mchid.as_str());
397 text = text.replace("{{USER_ID}}", self.sp_user_id.as_str());
398 text = text.replace("{{PASSWORD}}", self.sp_pass.as_str());
399 text
400 }
401}
402impl PayMode for Ccbc {
403 fn check(&mut self) -> Result<bool, String> {
404 todo!()
405 }
406
407 fn get_sub_mchid(&mut self, _sub_mchid: &str) -> Result<JsonValue, String> {
408 todo!()
409 }
410
411 fn config(&mut self) -> JsonValue {
412 todo!()
413 }
414
415
416 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> {
417 if self.public_key.is_empty() || self.public_key.len() < 30 {
418 return Err(String::from("Public key is empty"));
419 }
420 let pubtext = self.public_key[self.public_key.len() - 30..].to_string();
421
422 let url = match channel {
423 "wechat" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
424 "alipay" => "https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6",
425 _ => return Err(format!("Invalid channel: {channel}")),
426 };
427 let body = match channel {
428 "wechat" => {
429 let mut body = object! {
430 MERCHANTID:sub_mchid,
431 POSID:self.sub_posid.clone(),
432 BRANCHID:self.branchid.clone(),
433 ORDERID:out_trade_no,
434 PAYMENT:total_fee,
435 CURCODE:"01",
436 TXCODE:"530590",
437 REMARK1:"",
438 REMARK2:"",
439 TYPE:"1",
440 PUB:pubtext,
441 GATEWAY:"0",
442 CLIENTIP:self.client_ip.clone(),
443 REGINFO:"",
444 PROINFO: self.escape_unicode(description),
445 REFERER:"",
446 TRADE_TYPE:"",
447 SUB_APPID: "",
448 SUB_OPENID:sp_openid,
449 MAC:"",
450 };
451 body["TRADE_TYPE"] = match types {
452 Types::Jsapi => {
453 body["SUB_APPID"] = self.appid_subscribe.clone().into();
454 "JSAPI"
455 }
456 Types::MiniJsapi => {
457 body["SUB_APPID"] = self.appid.clone().into();
458 "MINIPRO"
459 }
460 _ => return Err(format!("Invalid types: {types:?}")),
461 }.into();
462 body
463 }
464 "alipay" => {
465 let body = match types {
466 Types::MiniJsapi => object! {
467 MERCHANTID:sub_mchid,
468 POSID:self.sub_posid.clone(),
469 BRANCHID:self.branchid.clone(),
470 ORDERID:out_trade_no,
471 PAYMENT:total_fee,
472 CURCODE:"01",
473 TXCODE:"530591",
474 TRADE_TYPE:"JSAPI",
475 USERID:sp_openid,
476 PUB:pubtext,
477 MAC:""
478 },
479 Types::H5 => object! {
480 BRANCHID:self.branchid.clone(),
481 MERCHANTID:sub_mchid,
482 POSID:self.sub_posid.clone(),
483 TXCODE:"ZFBWAP",
484 ORDERID:out_trade_no,
485 AMOUNT:total_fee,
486 TIMEOUT:"",
487 REMARK1:"",
488 REMARK2:"",
489 PUB:pubtext,
490 MAC:"",
491 SUBJECT:description,
492 AREA_INFO:""
493 },
494 Types::Jsapi => object! {
495 MERCHANTID:sub_mchid,
496 POSID:self.sub_posid.clone(),
497 BRANCHID:self.branchid.clone(),
498 ORDERID:out_trade_no,
499 PAYMENT:total_fee,
500 CURCODE:"01",
501 TXCODE:"530550",
502 REMARK1:"",
503 REMARK2:"",
504 RETURNTYPE:"3",
505 TIMEOUT:"",
506 PUB:pubtext,
507 MAC:""
508 },
509 _ => return Err(format!("Invalid types: {types:?}")),
510 };
511 body
512 }
513 _ => return Err(format!("Invalid channel: {channel}")),
514 };
515 match (channel, types) {
516 ("wechat", Types::Jsapi | Types::MiniJsapi) => {
517 let res = self.http(url, body.clone())?;
518 if res.has_key("PAYURL") {
519 let url = res["PAYURL"].to_string();
520 let mut http = br_reqwest::Client::new();
521
522 let re = match http.post(url.as_str()).send() {
523 Ok(e) => e,
524 Err(e) => {
525 return Err(e.to_string());
526 }
527 };
528 let re = re.body().to_string();
529 let res = match json::parse(&re) {
530 Ok(e) => e,
531 Err(_) => return Err(re)
532 };
533 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
534 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
535 }
536 Ok(res)
537 } else {
538 Err(res.to_string())
539 }
540 }
541 ("alipay", Types::MiniJsapi) => {
542 let res = self.http(url, body)?;
543 if res.has_key("PAYURL") {
544 let url = res["PAYURL"].to_string();
545 let mut http = br_reqwest::Client::new();
546 let re = match http.post(url.as_str()).send() {
547 Ok(e) => e,
548 Err(e) => {
549 return Err(e.to_string());
550 }
551 };
552 let re = re.body().to_string();
553 let res = match json::parse(&re) {
554 Ok(e) => e,
555 Err(_) => return Err(re)
556 };
557 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
558 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
559 }
560 Ok(res["jsapi"].clone())
561 } else {
562 Err(res.to_string())
563 }
564 }
565 ("alipay", Types::H5) => {
566 let res = self.http_alipay(url, body)?;
567 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
568 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
569 }
570 Ok(res["form_data"].clone())
571 }
572 ("alipay", Types::Jsapi) => {
573 let res = self.http(url, body)?;
574 if res.has_key("PAYURL") {
575 let url = res["PAYURL"].to_string();
576 if self.debug {
577 debug!("{url:#}");
578 }
579 let mut http = br_reqwest::Client::new();
580
581 let re = match http.post(url.as_str()).send() {
582 Ok(e) => e,
583 Err(e) => {
584 return Err(e.to_string());
585 }
586 };
587 let re = re.body().to_string();
588 let res = match json::parse(&re) {
589 Ok(e) => e,
590 Err(_) => return Err(re)
591 };
592 if self.debug {
593 debug!("{res:#}");
594 }
595 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
596 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
597 }
598 let r = br_crypto::encoding::urlencoding_decode(res["QRURL"].as_str().unwrap());
599 Ok(object! {
600 url:r.clone()
601 })
602 } else {
603 Err(res.to_string())
604 }
605 }
606 _ => {
607 let res = self.http(url, body)?;
608 Ok(res)
609 }
610 }
611 }
612
613 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> {
614 let body = object! {
615 MERCHANTID:sub_mchid,
616 POSID:self.sub_posid.clone(),
617 BRANCHID:self.branchid.clone(),
618 MERFLAG:"1",
619 TERMNO1:"",
620 TERMNO2:"",
621 ORDERID:out_trade_no,
622 QRCODE:auth_code,
623 AMOUNT:total_fee,
624 TXCODE:"PAY100",
625 PROINFO:description,
626 REMARK1:"",
627 REMARK2:"",
628 SMERID:"",SMERNAME:"",SMERTYPEID:"",SMERTYPE:"",TRADECODE:"",TRADENAME:"",SMEPROTYPE:"",PRONAME:""
629 };
630 let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
631 let res = self.http_ccb_param(url, body)?;
632
633 match res["RESULT"].as_str().unwrap_or("N") {
634 "Y" => {
635 Ok(self.pay_micropay_query(out_trade_no, sub_mchid, channel)?)
636 }
637 "N" => {
638 Err(res["ERRMSG"].to_string())
639 }
640 "U" => {
641 Err(res.to_string())
642 }
643 "Q" => {
644 let res = PayNotify {
645 trade_type: TradeType::MICROPAY,
646 out_trade_no: out_trade_no.to_string(),
647 sp_mchid: self.sp_mchid.clone(),
648 sub_mchid: sub_mchid.to_string(),
649 sp_appid: "".to_string(),
650 transaction_id: res["TRACEID"].to_string(),
651 success_time: 0,
652 sp_openid: "".to_string(),
653 sub_openid: "".to_string(),
654 total: total_fee,
655 payer_total: total_fee,
656 currency: "CNY".to_string(),
657 payer_currency: "CNY".to_string(),
658 trade_state: TradeState::NOTPAY,
659 };
660 Ok(res.json())
661 }
662 _ => {
663 Err(res.to_string())
664 }
665 }
666 }
667
668 fn close(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
669 let today = Local::now().date_naive();
670 let date_str = today.format("%Y%m%d").to_string();
671 let order_date = &out_trade_no[0..8];
672 let kind = if date_str == order_date {
673 0
674 } else {
675 1
676 };
677
678 let mut text = self.get_xml_text("5W1002.xml");
679
680 text = text.replace("{{START}}", order_date);
681 text = text.replace("{{END}}", order_date);
682 text = text.replace("{{KIND}}", kind.to_string().as_str());
683 text = text.replace("{{ORDER}}", out_trade_no);
684
685 text = text.replace("{{POS_CODE}}", "");
686 text = text.replace("{{STATUS}}", "1");
687 text = text.replace("{{Mrch_No}}", sub_mchid);
688
689 let res = self.https_cert(&text)?;
690 if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
691 if res["RETURN_MSG"].eq("流水记录不存在") {
692 return Ok(true.into());
693 }
694 return Err(res["RETURN_MSG"].to_string());
695 }
696 let trade_state = match res["TX_INFO"]["LIST"]["ORDER_STATUS"].as_str().unwrap_or("") {
697 "0" => TradeState::PAYERROR,
698 "1" => TradeState::SUCCESS,
699 "2" | "5" => TradeState::USERPAYING,
700 "3" | "4" => TradeState::REFUND,
701 _ => return Err("未知状态".to_string()),
702 };
703 match trade_state {
704 TradeState::SUCCESS => Ok(false.into()),
705 TradeState::REFUND => Ok(false.into()),
706 TradeState::NOTPAY => Ok(true.into()),
707 TradeState::CLOSED => Ok(true.into()),
708 TradeState::REVOKED => Ok(true.into()),
709 TradeState::USERPAYING => Ok(false.into()),
710 TradeState::PAYERROR => Ok(true.into()),
711 TradeState::None => Ok(true.into()),
712 }
713 }
714
715 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
716 let today = Local::now().date_naive();
717 let date_str = today.format("%Y%m%d").to_string();
718 let order_date = &out_trade_no[0..8];
719 let kind = if date_str == order_date {
720 0
721 } else {
722 1
723 };
724
725 let mut text = self.get_xml_text("5W1002.xml");
726
727 text = text.replace("{{START}}", order_date);
728 text = text.replace("{{END}}", order_date);
729 text = text.replace("{{KIND}}", kind.to_string().as_str());
730 text = text.replace("{{ORDER}}", out_trade_no);
731
732 text = text.replace("{{POS_CODE}}", "");
733 text = text.replace("{{STATUS}}", "1");
734 text = text.replace("{{Mrch_No}}", sub_mchid);
735 if self.debug{
736 info!("支付查询请求: {text}");
737 }
738 let res = self.https_cert(&text)?;
739 if self.debug{
740 info!("支付查询响应: {res}");
741 }
742 if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
743 if res["RETURN_MSG"].eq("流水记录不存在") {
744 let ttt = PayNotify {
745 trade_type: TradeType::None,
746 out_trade_no: out_trade_no.to_string(),
747 sp_mchid: self.sp_mchid.to_string(),
748 sub_mchid: sub_mchid.to_string(),
749 sp_appid: "".to_string(),
750 transaction_id: "".to_string(),
751 success_time: 0,
752 sp_openid: "".to_string(),
753 sub_openid: "".to_string(),
754 total: 0.0,
755 payer_total: 0.0,
756 currency: "CNY".to_string(),
757 payer_currency: "CNY".to_string(),
758 trade_state: TradeState::NOTPAY,
759 };
760 return Ok(ttt.json());
761 }
762 return Err(res["RETURN_MSG"].to_string());
763 }
764 let trade_state = match res["TX_INFO"]["LIST"]["ORDER_STATUS"].as_str().unwrap_or("") {
765 "0" => TradeState::PAYERROR,
766 "1" => TradeState::SUCCESS,
767 "2" | "5" => TradeState::USERPAYING,
768 "3" | "4" => TradeState::REFUND,
769 _ => return Err("未知状态".to_string()),
770 };
771 let data = res["TX_INFO"]["LIST"].clone();
772 let res = match trade_state {
773 TradeState::SUCCESS => {
774 PayNotify {
775 trade_type: TradeType::None,
776 out_trade_no: out_trade_no.to_string(),
777 sp_mchid: self.sp_mchid.to_string(),
778 sub_mchid: sub_mchid.to_string(),
779 sp_appid: "".to_string(),
780 transaction_id: data["OriOvrlsttnEV_Trck_No"].to_string(),
781 success_time: PayNotify::datetime_to_timestamp(data["TRAN_DATE"].as_str().unwrap_or(""), "%Y-%m-%d %H:%M:%S"),
782 sp_openid: "".to_string(),
783 sub_openid: "".to_string(),
784 total: data["Orig_Amt"].to_string().parse::<f64>().unwrap_or(0.0),
785 payer_total: data["Txn_ClrgAmt"].to_string().parse::<f64>().unwrap_or(0.0),
786 currency: "CNY".to_string(),
787 payer_currency: "CNY".to_string(),
788 trade_state,
789 }
790 }
791 _ => PayNotify {
792 trade_type: TradeType::None,
793 out_trade_no: out_trade_no.to_string(),
794 sp_mchid: self.sp_mchid.to_string(),
795 sub_mchid: sub_mchid.to_string(),
796 sp_appid: "".to_string(),
797 transaction_id: res["ORDER"].to_string(),
798 success_time: 0,
799 sp_openid: "".to_string(),
800 sub_openid: "".to_string(),
801 total: 0.0,
802 payer_total: 0.0,
803 currency: "CNY".to_string(),
804 payer_currency: "CNY".to_string(),
805 trade_state,
806 }
807 };
808 Ok(res.json())
809 }
810
811 fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
812 let crcode_type = match channel {
813 "wechat" => "2",
814 "alipay" => "3",
815 _ => "5"
816 };
817
818 let body = object! {
819 MERCHANTID:sub_mchid,
820 POSID:self.sub_posid.clone(),
821 BRANCHID:self.branchid.clone(),
822 TXCODE:"PAY102",
823 MERFLAG:"1",
824 TERMNO1:"",
825 TERMNO2:"",
826 ORDERID:out_trade_no,
827 QRYTIME:"1",
828 QRCODETYPE:crcode_type,
829 QRCODE:"",
830 REMARK1:"",
831 REMARK2:"",
832 };
833 let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
834 let res = self.http_ccb_param(url, body)?;
835
836 match res["RESULT"].as_str().unwrap_or("N") {
837 "Y" => {
838 let now = Local::now();
839 let formatted = now.format("%Y%m%d%H%M%S").to_string();
840 let transaction_id = match channel {
841 "wechat" => res["WECHAT_NO"].to_string(),
842 "alipay" => res["ZFB_NO"].to_string(),
843 _ => "".to_string()
844 };
845 let res = PayNotify {
846 trade_type: TradeType::MICROPAY,
847 out_trade_no: out_trade_no.to_string(),
848 sp_mchid: self.sp_mchid.clone(),
849 sub_mchid: sub_mchid.to_string(),
850 sp_appid: "".to_string(),
851 transaction_id,
852 success_time: PayNotify::datetime_to_timestamp(formatted.as_str(), "%Y%m%d%H%M%S"),
853 sp_openid: "".to_string(),
854 sub_openid: "".to_string(),
855 total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
856 payer_total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
857 currency: "CNY".to_string(),
858 payer_currency: "CNY".to_string(),
859 trade_state: TradeState::SUCCESS,
860 };
861 Ok(res.json())
862 }
863 "N" => {
864 let crcode_type = match channel {
865 "wechat" => res["WECHAT_STATE"].to_string(),
866 "alipay" => res["ZFB_STATE"].to_string(),
867 _ => "".to_string()
868 };
869 let res = PayNotify {
870 trade_type: TradeType::MICROPAY,
871 out_trade_no: out_trade_no.to_string(),
872 sp_mchid: self.sp_mchid.clone(),
873 sub_mchid: sub_mchid.to_string(),
874 sp_appid: "".to_string(),
875 transaction_id: "".to_string(),
876 success_time: 0,
877 sp_openid: "".to_string(),
878 sub_openid: "".to_string(),
879 total: 0.0,
880 currency: "CNY".to_string(),
881 payer_total: 0.0,
882 payer_currency: "CNY".to_string(),
883 trade_state: TradeState::from(crcode_type.as_str()),
884 };
885 Ok(res.json())
886 }
887 "U" => {
888 Err(res.to_string())
889 }
890 "Q" => {
891 let res = PayNotify {
892 trade_type: TradeType::MICROPAY,
893 out_trade_no: out_trade_no.to_string(),
894 sp_mchid: self.sp_mchid.clone(),
895 sub_mchid: sub_mchid.to_string(),
896 sp_appid: "".to_string(),
897 transaction_id: "".to_string(),
898 success_time: 0,
899 sp_openid: "".to_string(),
900 sub_openid: "".to_string(),
901 total: 0.0,
902 payer_total: 0.0,
903 currency: "CNY".to_string(),
904 payer_currency: "CNY".to_string(),
905 trade_state: TradeState::NOTPAY,
906 };
907 Ok(res.json())
908 }
909 _ => {
910 Err(res.to_string())
911 }
912 }
913 }
914
915 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
916 Err("暂未开通".to_string())
917 }
918
919 fn refund(&mut self, sub_mchid: &str, out_trade_no: &str, _transaction_id: &str, out_refund_no: &str, amount: f64, _total: f64, _currency: &str) -> Result<JsonValue, String> {
920 let mut text = self.get_xml_text("5W1024.xml");
921 text = text.replace("{{MONEY}}", amount.to_string().as_str());
922 text = text.replace("{{ORDER}}", out_trade_no);
923 text = text.replace("{{REFUND_CODE}}", out_refund_no);
924 text = text.replace("{{Mrch_No}}", sub_mchid);
925 let res = self.https_cert(&text)?;
926 if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
927 return Err(res["RETURN_MSG"].to_string());
928 }
929 if !res.has_key("TX_INFO") {
930 return Err(res["RETURN_MSG"].to_string());
931 }
932 let timestamp = Local::now().timestamp();
933 let info = object! {
934 refund_id:out_refund_no,
935 status:"已退款",
936 success_time:timestamp,
937 out_refund_no: out_refund_no,
938 };
939 Ok(info)
940 }
941
942 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> {
943 Err("暂未开通".to_string())
944 }
945
946 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
947 Err("暂未开通".to_string())
948 }
949
950 fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
951 let today = Local::now().date_naive();
952 let date_str = today.format("%Y%m%d").to_string();
953 let order_date = &out_refund_no[0..8];
954 let kind = if date_str == order_date {
955 0
956 } else {
957 1
958 };
959
960 let mut text = self.get_xml_text("5W1003.xml");
961
962 text = text.replace("{{START}}", order_date);
963 text = text.replace("{{END}}", order_date);
964 text = text.replace("{{KIND}}", kind.to_string().as_str());
965 text = text.replace("{{ORDER}}", out_refund_no);
966
967 text = text.replace("{{POS_CODE}}", "");
968 text = text.replace("{{STATUS}}", "1");
969 text = text.replace("{{Mrch_No}}", sub_mchid);
970
971 let res = self.https_cert(&text)?;
972
973 if res.has_key("RETURN_MSG") {
974 if res["RETURN_MSG"].eq("流水记录不存在") {
975 let res = RefundNotify {
976 out_trade_no: trade_no.to_string(),
977 refund_no: out_refund_no.to_string(),
978 sp_mchid: self.sp_mchid.to_string(),
979 sub_mchid: sub_mchid.to_string(),
980 transaction_id: "".to_string(),
981 refund_id: "".to_string(),
982 success_time: 0,
983 total: 0.0,
984 refund: 0.0,
985 payer_total: 0.0,
986 payer_refund: 0.0,
987 status: RefundStatus::None,
988 };
989 return Ok(res.json());
990 }
991 return Err(res["RETURN_MSG"].to_string());
992 }
993 Err("暂未开通".to_string())
994 }
1057
1058 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> {
1059 todo!()
1060 }
1061}
1062
1063fn xml_element_to_json(elem: &Element) -> JsonValue {
1064 let mut obj = object! {};
1065 for child in &elem.children {
1066 if let xmltree::XMLNode::Element(e) = child {
1067 obj[e.name.clone()] = xml_element_to_json(e);
1068 }
1069 }
1070 match elem.get_text() {
1071 None => obj,
1072 Some(text) => JsonValue::from(text.to_string()),
1073 }
1074}