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 _ => {
631 if res["ERRMSG"].to_string().contains("服务异常,@@该笔订单不存在支付记录!@@") {
632 return Ok(true.into());
633 }
634 Err(res["ERRMSG"].to_string())
635 }
636 }
637 }
638
639 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
640 let today = Local::now().date_naive();
641 let date_str = today.format("%Y%m%d").to_string();
642
643 let order_date = &out_trade_no[0..8];
644
645 let kind = if date_str == order_date {
646 0
647 } else {
648 1
649 };
650
651 let body = object! {
652 MERCHANTID:sub_mchid,
653 BRANCHID:self.branchid.clone(),
654 POSID:self.posid.clone(),
655 ORDERDATE:order_date,
656 BEGORDERTIME:"00:00:00",
657 ENDORDERTIME:"23:59:59",
658 ORDERID:out_trade_no,
659 QUPWD:self.pass.clone(),
660 TXCODE:"410408",
661 TYPE:"0",
662 KIND:kind,
663 STATUS:"1",
664 SEL_TYPE:"3",
665 PAGE:"1",
666 OPERATOR:"",
667 CHANNEL:"",
668 MAC:""
669 };
670 let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
671 if res["RETURN_CODE"] != "000000" {
672 if res["RETURN_MSG"].eq("流水记录不存在") {
673 let res = PayNotify {
674 trade_type: TradeType::None,
675 out_trade_no: "".to_string(),
676 sp_mchid: "".to_string(),
677 sub_mchid: "".to_string(),
678 sp_appid: "".to_string(),
679 transaction_id: "".to_string(),
680 success_time: 0,
681 sp_openid: "".to_string(),
682 sub_openid: "".to_string(),
683 total: 0.0,
684 payer_total: 0.0,
685 currency: "".to_string(),
686 payer_currency: "".to_string(),
687 trade_state: TradeState::NOTPAY,
688 };
689 return Ok(res.json());
690 }
691 return Err(res["RETURN_MSG"].to_string());
692 }
693 let data = res["QUERYORDER"].clone();
694 let res = PayNotify {
695 trade_type: TradeType::None,
696 out_trade_no: data["ORDERID"].to_string(),
697 sp_mchid: "".to_string(),
698 sub_mchid: sub_mchid.to_string(),
699 sp_appid: "".to_string(),
700 transaction_id: data["ORDERID"].to_string(),
701 success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
702 sp_openid: "".to_string(),
703 sub_openid: "".to_string(),
704 total: data["AMOUNT"].as_f64().unwrap_or(0.0),
705 currency: "CNY".to_string(),
706 payer_total: data["AMOUNT"].as_f64().unwrap_or(0.0),
707 payer_currency: "CNY".to_string(),
708 trade_state: TradeState::from(data["STATUS"].as_str().unwrap()),
709 };
710 Ok(res.json())
711 }
712
713 fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
714 let crcode_type = match channel {
715 "wechat" => "2",
716 "alipay" => "3",
717 _ => "5"
718 };
719
720 let body = object! {
721 MERCHANTID:sub_mchid,
722 POSID:self.posid.clone(),
723 BRANCHID:self.branchid.clone(),
724 TXCODE:"PAY102",
725 MERFLAG:"1",
726 TERMNO1:"",
727 TERMNO2:"",
728 ORDERID:out_trade_no,
729 QRYTIME:"1",
730 QRCODETYPE:crcode_type,
731 QRCODE:"",
732 REMARK1:"",
733 REMARK2:"",
734 };
735 let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
736 let res = self.http_ccb_param(url, body)?;
737
738 match res["RESULT"].as_str().unwrap_or("N") {
739 "Y" => {
740 let now = Local::now();
741 let formatted = now.format("%Y%m%d%H%M%S").to_string();
742 let transaction_id = match channel {
743 "wechat" => res["WECHAT_NO"].to_string(),
744 "alipay" => res["ZFB_NO"].to_string(),
745 _ => "".to_string()
746 };
747 let res = PayNotify {
748 trade_type: TradeType::MICROPAY,
749 out_trade_no: out_trade_no.to_string(),
750 sp_mchid: self.sp_mchid.clone(),
751 sub_mchid: sub_mchid.to_string(),
752 sp_appid: "".to_string(),
753 transaction_id,
754 success_time: PayNotify::datetime_to_timestamp(formatted.as_str(), "%Y%m%d%H%M%S"),
755 sp_openid: "".to_string(),
756 sub_openid: "".to_string(),
757 total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
758 payer_total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
759 currency: "CNY".to_string(),
760 payer_currency: "CNY".to_string(),
761 trade_state: TradeState::SUCCESS,
762 };
763 Ok(res.json())
764 }
765 "N" => {
766 let crcode_type = match channel {
767 "wechat" => res["WECHAT_STATE"].to_string(),
768 "alipay" => res["ZFB_STATE"].to_string(),
769 _ => "".to_string()
770 };
771 let res = PayNotify {
772 trade_type: TradeType::MICROPAY,
773 out_trade_no: out_trade_no.to_string(),
774 sp_mchid: self.sp_mchid.clone(),
775 sub_mchid: sub_mchid.to_string(),
776 sp_appid: "".to_string(),
777 transaction_id: "".to_string(),
778 success_time: 0,
779 sp_openid: "".to_string(),
780 sub_openid: "".to_string(),
781 total: 0.0,
782 currency: "CNY".to_string(),
783 payer_total: 0.0,
784 payer_currency: "CNY".to_string(),
785 trade_state: TradeState::from(crcode_type.as_str()),
786 };
787 Ok(res.json())
788 }
789 "U" => {
790 Err(res.to_string())
791 }
792 "Q" => {
793 let res = PayNotify {
794 trade_type: TradeType::MICROPAY,
795 out_trade_no: out_trade_no.to_string(),
796 sp_mchid: self.sp_mchid.clone(),
797 sub_mchid: sub_mchid.to_string(),
798 sp_appid: "".to_string(),
799 transaction_id: "".to_string(),
800 success_time: 0,
801 sp_openid: "".to_string(),
802 sub_openid: "".to_string(),
803 total: 0.0,
804 payer_total: 0.0,
805 currency: "CNY".to_string(),
806 payer_currency: "CNY".to_string(),
807 trade_state: TradeState::NOTPAY,
808 };
809 Ok(res.json())
810 }
811 _ => {
812 Err(res.to_string())
813 }
814 }
815 }
816
817 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
818 Err("暂未开通".to_string())
819 }
820
821 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> {
822 Err("暂未开通".to_string())
831 }
832
833 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> {
834 Err("暂未开通".to_string())
835 }
836
837 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
838 Err("暂未开通".to_string())
839 }
840
841 fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
842 let today = Local::now().date_naive();
843 let date_str = today.format("%Y%m%d").to_string();
844 let body = object! {
845 MERCHANTID:sub_mchid,
846 BRANCHID:self.branchid.clone(),
847 POSID:self.posid.clone(),
848 ORDERDATE:date_str,
849 BEGORDERTIME:"00:00:00",
850 ENDORDERTIME:"23:59:59",
851 ORDERID:out_refund_no,
852 QUPWD:self.pass.clone(),
853 TXCODE:"410408",
854 TYPE:"1",
855 KIND:"0",
856 STATUS:"1",
857 SEL_TYPE:"3",
858 PAGE:"1",
859 OPERATOR:"",
860 CHANNEL:"",
861 MAC:""
862 };
863 let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
864 if res["RETURN_CODE"] != "000000" {
865 if res["RETURN_MSG"].eq("流水记录不存在") {
866 let res = PayNotify {
867 trade_type: TradeType::None,
868 out_trade_no: "".to_string(),
869 sp_mchid: "".to_string(),
870 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 payer_total: 0.0,
878 currency: "".to_string(),
879 payer_currency: "".to_string(),
880 trade_state: TradeState::NOTPAY,
881 };
882 return Ok(res.json());
883 }
884 return Err(res["RETURN_MSG"].to_string());
885 }
886 let data = res["QUERYORDER"].clone();
887 let res = RefundNotify {
888 out_trade_no: trade_no.to_string(),
889 refund_no: out_refund_no.to_string(),
890 sp_mchid: "".to_string(),
891 sub_mchid: sub_mchid.to_string(),
892 transaction_id: data["ORDERID"].to_string(),
893 refund_id: data["refund_id"].to_string(),
894 success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
895 total: data["AMOUNT"].as_f64().unwrap_or(0.0),
896 payer_total: data["amount"]["total"].to_string().parse::<f64>().unwrap(),
897 refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
898 payer_refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
899 status: RefundStatus::from(data["STATUS"].as_str().unwrap()),
900 };
901
902 Ok(res.json())
903 }
904
905 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> {
906 todo!()
907 }
908}
909
910fn xml_element_to_json(elem: &Element) -> JsonValue {
911 let mut obj = object! {};
912
913 for child in &elem.children {
914 if let xmltree::XMLNode::Element(e) = child {
915 obj[e.name.clone()] = xml_element_to_json(e);
916 }
917 }
918
919 match elem.get_text() {
920 None => obj,
921 Some(text) => JsonValue::from(text.to_string()),
922 }
923}