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://127.0.0.1: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 return Err(res["RETURN_MSG"].to_string());
691 }
692 let trade_state = match res["TX_INFO"]["LIST"]["ORDER_STATUS"].as_str().unwrap_or("") {
693 "0" => TradeState::PAYERROR,
694 "1" => TradeState::SUCCESS,
695 "2" | "5" => TradeState::USERPAYING,
696 "3" | "4" => TradeState::REFUND,
697 _ => return Err("未知状态".to_string()),
698 };
699 match trade_state {
700 TradeState::SUCCESS => Ok(false.into()),
701 TradeState::REFUND => Ok(false.into()),
702 TradeState::NOTPAY => Ok(true.into()),
703 TradeState::CLOSED => Ok(true.into()),
704 TradeState::REVOKED => Ok(true.into()),
705 TradeState::USERPAYING => Ok(false.into()),
706 TradeState::PAYERROR => Ok(true.into()),
707 TradeState::None => Ok(true.into()),
708 }
709 }
710
711 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
712 let today = Local::now().date_naive();
713 let date_str = today.format("%Y%m%d").to_string();
714 let order_date = &out_trade_no[0..8];
715 let kind = if date_str == order_date {
716 0
717 } else {
718 1
719 };
720
721 let mut text = self.get_xml_text("5W1002.xml");
722
723 text = text.replace("{{START}}", order_date);
724 text = text.replace("{{END}}", order_date);
725 text = text.replace("{{KIND}}", kind.to_string().as_str());
726 text = text.replace("{{ORDER}}", out_trade_no);
727
728 text = text.replace("{{POS_CODE}}", self.sp_posid.as_str());
729 text = text.replace("{{STATUS}}", "1");
730 text = text.replace("{{Mrch_No}}", sub_mchid);
731
732 let res = self.https_cert(&text)?;
733 if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
734 return Err(res["RETURN_MSG"].to_string());
735 }
736 if res["RETURN_MSG"].eq("流水记录不存在") {
737 let ttt = PayNotify {
738 trade_type: TradeType::None,
739 out_trade_no: out_trade_no.to_string(),
740 sp_mchid: self.sp_mchid.to_string(),
741 sub_mchid: sub_mchid.to_string(),
742 sp_appid: "".to_string(),
743 transaction_id: "".to_string(),
744 success_time: 0,
745 sp_openid: "".to_string(),
746 sub_openid: "".to_string(),
747 total: 0.0,
748 payer_total: 0.0,
749 currency: "CNY".to_string(),
750 payer_currency: "CNY".to_string(),
751 trade_state: TradeState::NOTPAY,
752 };
753 return Ok(ttt.json());
754 }
755 let trade_state = match res["TX_INFO"]["LIST"]["ORDER_STATUS"].as_str().unwrap_or("") {
756 "0" => TradeState::PAYERROR,
757 "1" => TradeState::SUCCESS,
758 "2" | "5" => TradeState::USERPAYING,
759 "3" | "4" => TradeState::REFUND,
760 _ => return Err("未知状态".to_string()),
761 };
762 let data = res["TX_INFO"]["LIST"].clone();
763 let res = match trade_state {
764 TradeState::SUCCESS => {
765 PayNotify {
766 trade_type: TradeType::None,
767 out_trade_no: out_trade_no.to_string(),
768 sp_mchid: self.sp_mchid.to_string(),
769 sub_mchid: sub_mchid.to_string(),
770 sp_appid: "".to_string(),
771 transaction_id: data["OriOvrlsttnEV_Trck_No"].to_string(),
772 success_time: PayNotify::datetime_to_timestamp(data["TRAN_DATE"].as_str().unwrap_or(""), "%Y-%m-%d %H:%M:%S"),
773 sp_openid: "".to_string(),
774 sub_openid: "".to_string(),
775 total: data["Orig_Amt"].to_string().parse::<f64>().unwrap_or(0.0),
776 payer_total: data["Txn_ClrgAmt"].to_string().parse::<f64>().unwrap_or(0.0),
777 currency: "CNY".to_string(),
778 payer_currency: "CNY".to_string(),
779 trade_state,
780 }
781 }
782 _ => PayNotify {
783 trade_type: TradeType::None,
784 out_trade_no: out_trade_no.to_string(),
785 sp_mchid: self.sp_mchid.to_string(),
786 sub_mchid: sub_mchid.to_string(),
787 sp_appid: "".to_string(),
788 transaction_id: res["ORDER"].to_string(),
789 success_time: 0,
790 sp_openid: "".to_string(),
791 sub_openid: "".to_string(),
792 total: 0.0,
793 payer_total: 0.0,
794 currency: "CNY".to_string(),
795 payer_currency: "CNY".to_string(),
796 trade_state,
797 }
798 };
799 Ok(res.json())
800 }
801
802 fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
803 let crcode_type = match channel {
804 "wechat" => "2",
805 "alipay" => "3",
806 _ => "5"
807 };
808
809 let body = object! {
810 MERCHANTID:sub_mchid,
811 POSID:self.sub_posid.clone(),
812 BRANCHID:self.branchid.clone(),
813 TXCODE:"PAY102",
814 MERFLAG:"1",
815 TERMNO1:"",
816 TERMNO2:"",
817 ORDERID:out_trade_no,
818 QRYTIME:"1",
819 QRCODETYPE:crcode_type,
820 QRCODE:"",
821 REMARK1:"",
822 REMARK2:"",
823 };
824 let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
825 let res = self.http_ccb_param(url, body)?;
826
827 match res["RESULT"].as_str().unwrap_or("N") {
828 "Y" => {
829 let now = Local::now();
830 let formatted = now.format("%Y%m%d%H%M%S").to_string();
831 let transaction_id = match channel {
832 "wechat" => res["WECHAT_NO"].to_string(),
833 "alipay" => res["ZFB_NO"].to_string(),
834 _ => "".to_string()
835 };
836 let res = PayNotify {
837 trade_type: TradeType::MICROPAY,
838 out_trade_no: out_trade_no.to_string(),
839 sp_mchid: self.sp_mchid.clone(),
840 sub_mchid: sub_mchid.to_string(),
841 sp_appid: "".to_string(),
842 transaction_id,
843 success_time: PayNotify::datetime_to_timestamp(formatted.as_str(), "%Y%m%d%H%M%S"),
844 sp_openid: "".to_string(),
845 sub_openid: "".to_string(),
846 total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
847 payer_total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
848 currency: "CNY".to_string(),
849 payer_currency: "CNY".to_string(),
850 trade_state: TradeState::SUCCESS,
851 };
852 Ok(res.json())
853 }
854 "N" => {
855 let crcode_type = match channel {
856 "wechat" => res["WECHAT_STATE"].to_string(),
857 "alipay" => res["ZFB_STATE"].to_string(),
858 _ => "".to_string()
859 };
860 let res = PayNotify {
861 trade_type: TradeType::MICROPAY,
862 out_trade_no: out_trade_no.to_string(),
863 sp_mchid: self.sp_mchid.clone(),
864 sub_mchid: sub_mchid.to_string(),
865 sp_appid: "".to_string(),
866 transaction_id: "".to_string(),
867 success_time: 0,
868 sp_openid: "".to_string(),
869 sub_openid: "".to_string(),
870 total: 0.0,
871 currency: "CNY".to_string(),
872 payer_total: 0.0,
873 payer_currency: "CNY".to_string(),
874 trade_state: TradeState::from(crcode_type.as_str()),
875 };
876 Ok(res.json())
877 }
878 "U" => {
879 Err(res.to_string())
880 }
881 "Q" => {
882 let res = PayNotify {
883 trade_type: TradeType::MICROPAY,
884 out_trade_no: out_trade_no.to_string(),
885 sp_mchid: self.sp_mchid.clone(),
886 sub_mchid: sub_mchid.to_string(),
887 sp_appid: "".to_string(),
888 transaction_id: "".to_string(),
889 success_time: 0,
890 sp_openid: "".to_string(),
891 sub_openid: "".to_string(),
892 total: 0.0,
893 payer_total: 0.0,
894 currency: "CNY".to_string(),
895 payer_currency: "CNY".to_string(),
896 trade_state: TradeState::NOTPAY,
897 };
898 Ok(res.json())
899 }
900 _ => {
901 Err(res.to_string())
902 }
903 }
904 }
905
906 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
907 Err("暂未开通".to_string())
908 }
909
910 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> {
911 let mut text = self.get_xml_text("5W1024.xml");
912 text = text.replace("{{MONEY}}", amount.to_string().as_str());
913 text = text.replace("{{ORDER}}", out_trade_no);
914 text = text.replace("{{REFUND_CODE}}", out_refund_no);
915 text = text.replace("{{Mrch_No}}", sub_mchid);
916 let res = self.https_cert(&text)?;
917 if res.has_key("RETURN_CODE") && res["RETURN_CODE"].ne("000000") {
918 return Err(res["RETURN_MSG"].to_string());
919 }
920 if res.has_key("RETURN_MSG") {
921 error!("{:#}", res);
922 return Err(res["RETURN_MSG"].to_string());
923 }
924 info!("{:#}", res);
925 Ok(res)
926 }
927
928 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> {
929 Err("暂未开通".to_string())
930 }
931
932 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
933 Err("暂未开通".to_string())
934 }
935
936 fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
937 let today = Local::now().date_naive();
938 let date_str = today.format("%Y%m%d").to_string();
939 let order_date = &out_refund_no[0..8];
940 let kind = if date_str == order_date {
941 0
942 } else {
943 1
944 };
945
946 let mut text = self.get_xml_text("5W1003.xml");
947
948 text = text.replace("{{START}}", order_date);
949 text = text.replace("{{END}}", order_date);
950 text = text.replace("{{KIND}}", kind.to_string().as_str());
951 text = text.replace("{{ORDER}}", out_refund_no);
952
953 text = text.replace("{{POS_CODE}}", self.sp_posid.as_str());
954 text = text.replace("{{STATUS}}", "1");
955 text = text.replace("{{Mrch_No}}", sub_mchid);
956
957 let res = self.https_cert(&text)?;
958
959 if res.has_key("RETURN_MSG") {
960 if res["RETURN_MSG"].eq("流水记录不存在") {
961 let res = RefundNotify {
962 out_trade_no: trade_no.to_string(),
963 refund_no: out_refund_no.to_string(),
964 sp_mchid: self.sp_mchid.to_string(),
965 sub_mchid: sub_mchid.to_string(),
966 transaction_id: "".to_string(),
967 refund_id: "".to_string(),
968 success_time: 0,
969 total: 0.0,
970 refund: 0.0,
971 payer_total: 0.0,
972 payer_refund: 0.0,
973 status: RefundStatus::None,
974 };
975 return Ok(res.json());
976 }
977 return Err(res["RETURN_MSG"].to_string());
978 }
979 Err("暂未开通".to_string())
980 }
1043
1044 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> {
1045 todo!()
1046 }
1047}
1048
1049fn xml_element_to_json(elem: &Element) -> JsonValue {
1050 let mut obj = object! {};
1051 for child in &elem.children {
1052 if let xmltree::XMLNode::Element(e) = child {
1053 obj[e.name.clone()] = xml_element_to_json(e);
1054 }
1055 }
1056 match elem.get_text() {
1057 None => obj,
1058 Some(text) => JsonValue::from(text.to_string()),
1059 }
1060}