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