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