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