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(url, body)?;
481 if res.has_key("PAYURL") {
482 let url = res["PAYURL"].to_string();
483 let mut http = br_reqwest::Client::new();
484 let re = match http.post(url.as_str()).send() {
485 Ok(e) => e,
486 Err(e) => {
487 return Err(e.to_string());
488 }
489 };
490 let re = re.body().to_string();
491 let res = match json::parse(&re) {
492 Ok(e) => e,
493 Err(_) => return Err(re)
494 };
495 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
496 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
497 }
498 Ok(res["jsapi"].clone())
499 } else {
500 Err(res.to_string())
501 }
502 }
503 ("alipay", Types::H5) => {
504 let res = self.http_alipay(url, body)?;
505 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
506 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
507 }
508 Ok(res["form_data"].clone())
509 }
510 ("alipay", Types::Jsapi) => {
511 let res = self.http(url, body)?;
512 if res.has_key("PAYURL") {
513 let url = res["PAYURL"].to_string();
514 if self.debug {
515 debug!("{url:#}");
516 }
517 let mut http = br_reqwest::Client::new();
518
519 let re = match http.post(url.as_str()).send() {
520 Ok(e) => e,
521 Err(e) => {
522 return Err(e.to_string());
523 }
524 };
525 let re = re.body().to_string();
526 let res = match json::parse(&re) {
527 Ok(e) => e,
528 Err(_) => return Err(re)
529 };
530 if self.debug {
531 debug!("{res:#}");
532 }
533 if res.has_key("ERRCODE") && res["ERRCODE"] != "000000" {
534 return Err(format!("获取支付参数: 错误码: [{}] {} 失败", res["ERRCODE"], res["ERRMSG"]));
535 }
536 let r = br_crypto::encoding::urlencoding_decode(res["QRURL"].as_str().unwrap());
537 Ok(object! {
538 url:r.clone()
539 })
540 } else {
541 Err(res.to_string())
542 }
543 }
544 _ => {
545 let res = self.http(url, body)?;
546 Ok(res)
547 }
548 }
549 }
550
551 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> {
552 let body = object! {
553 MERCHANTID:sub_mchid,
554 POSID:self.posid.clone(),
555 BRANCHID:self.branchid.clone(),
556 MERFLAG:"1",
557 TERMNO1:"",
558 TERMNO2:"",
559 ORDERID:out_trade_no,
560 QRCODE:auth_code,
561 AMOUNT:total_fee,
562 TXCODE:"PAY100",
563 PROINFO:description,
564 REMARK1:"",
565 REMARK2:"",
566 SMERID:"",SMERNAME:"",SMERTYPEID:"",SMERTYPE:"",TRADECODE:"",TRADENAME:"",SMEPROTYPE:"",PRONAME:""
567 };
568 let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
569 let res = self.http_ccb_param(url, body)?;
570
571 match res["RESULT"].as_str().unwrap_or("N") {
572 "Y" => {
573 Ok(self.pay_micropay_query(out_trade_no, sub_mchid, channel)?)
574 }
575 "N" => {
576 Err(res["ERRMSG"].to_string())
577 }
578 "U" => {
579 Err(res.to_string())
580 }
581 "Q" => {
582 let res = PayNotify {
583 trade_type: TradeType::MICROPAY,
584 out_trade_no: out_trade_no.to_string(),
585 sp_mchid: self.sp_mchid.clone(),
586 sub_mchid: sub_mchid.to_string(),
587 sp_appid: "".to_string(),
588 transaction_id: res["TRACEID"].to_string(),
589 success_time: 0,
590 sp_openid: "".to_string(),
591 sub_openid: "".to_string(),
592 total: total_fee,
593 payer_total: total_fee,
594 currency: "CNY".to_string(),
595 payer_currency: "CNY".to_string(),
596 trade_state: TradeState::NOTPAY,
597 };
598 Ok(res.json())
599 }
600 _ => {
601 Err(res.to_string())
602 }
603 }
604 }
605
606 fn close(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
607 let crcode_type = match channel {
608 "wechat" => "2",
609 "alipay" => "3",
610 _ => "2"
611 };
612 let body = object! {
613 MERCHANTID:sub_mchid,
614 POSID:self.posid.clone(),
615 BRANCHID:self.branchid.clone(),
616 TXCODE:"PAY103",
617 MERFLAG:"1",
618 TERMNO1:"",
619 TERMNO2:"",
620 ORDERID:out_trade_no,
621 QRCODETYPE:crcode_type,
622 REMARK1:"",
623 REMARK2:"",
624 };
625 let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
626 let res = self.http_ccb_param(url, body)?;
627 match res["RESULT"].as_str().unwrap_or("N") {
628 "Y" => Ok(true.into()),
629 _ => {
630 if res["ERRMSG"].to_string().contains("服务异常,@@该笔订单不存在支付记录!@@") {
631 return Ok(true.into());
632 }
633 Err(res["ERRMSG"].to_string())
634 }
635 }
636 }
637
638 fn pay_query(&mut self, out_trade_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
639 let today = Local::now().date_naive();
640 let date_str = today.format("%Y%m%d").to_string();
641
642 let order_date = &out_trade_no[0..8];
643
644 let kind = if date_str == order_date {
645 0
646 } else {
647 1
648 };
649
650 let body = object! {
651 MERCHANTID:sub_mchid,
652 BRANCHID:self.branchid.clone(),
653 POSID:self.posid.clone(),
654 ORDERDATE:order_date,
655 BEGORDERTIME:"00:00:00",
656 ENDORDERTIME:"23:59:59",
657 ORDERID:out_trade_no,
658 QUPWD:self.pass.clone(),
659 TXCODE:"410408",
660 TYPE:"0",
661 KIND:kind,
662 STATUS:"1",
663 SEL_TYPE:"3",
664 PAGE:"1",
665 OPERATOR:"",
666 CHANNEL:"",
667 MAC:""
668 };
669 let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
670 if res["RETURN_CODE"] != "000000" {
671 if res["RETURN_MSG"].eq("流水记录不存在") {
672 let res = PayNotify {
673 trade_type: TradeType::None,
674 out_trade_no: "".to_string(),
675 sp_mchid: "".to_string(),
676 sub_mchid: "".to_string(),
677 sp_appid: "".to_string(),
678 transaction_id: "".to_string(),
679 success_time: 0,
680 sp_openid: "".to_string(),
681 sub_openid: "".to_string(),
682 total: 0.0,
683 payer_total: 0.0,
684 currency: "".to_string(),
685 payer_currency: "".to_string(),
686 trade_state: TradeState::NOTPAY,
687 };
688 return Ok(res.json());
689 }
690 return Err(res["RETURN_MSG"].to_string());
691 }
692 let data = res["QUERYORDER"].clone();
693 let res = PayNotify {
694 trade_type: TradeType::None,
695 out_trade_no: data["ORDERID"].to_string(),
696 sp_mchid: "".to_string(),
697 sub_mchid: sub_mchid.to_string(),
698 sp_appid: "".to_string(),
699 transaction_id: data["ORDERID"].to_string(),
700 success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
701 sp_openid: "".to_string(),
702 sub_openid: "".to_string(),
703 total: data["AMOUNT"].as_f64().unwrap_or(0.0),
704 currency: "CNY".to_string(),
705 payer_total: data["AMOUNT"].as_f64().unwrap_or(0.0),
706 payer_currency: "CNY".to_string(),
707 trade_state: TradeState::from(data["STATUS"].as_str().unwrap()),
708 };
709 Ok(res.json())
710 }
711
712 fn pay_micropay_query(&mut self, out_trade_no: &str, sub_mchid: &str, channel: &str) -> Result<JsonValue, String> {
713 let crcode_type = match channel {
714 "wechat" => "2",
715 "alipay" => "3",
716 _ => "5"
717 };
718
719 let body = object! {
720 MERCHANTID:sub_mchid,
721 POSID:self.posid.clone(),
722 BRANCHID:self.branchid.clone(),
723 TXCODE:"PAY102",
724 MERFLAG:"1",
725 TERMNO1:"",
726 TERMNO2:"",
727 ORDERID:out_trade_no,
728 QRYTIME:"1",
729 QRCODETYPE:crcode_type,
730 QRCODE:"",
731 REMARK1:"",
732 REMARK2:"",
733 };
734 let url = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY";
735 let res = self.http_ccb_param(url, body)?;
736
737 match res["RESULT"].as_str().unwrap_or("N") {
738 "Y" => {
739 let now = Local::now();
740 let formatted = now.format("%Y%m%d%H%M%S").to_string();
741 let transaction_id = match channel {
742 "wechat" => res["WECHAT_NO"].to_string(),
743 "alipay" => res["ZFB_NO"].to_string(),
744 _ => "".to_string()
745 };
746 let res = PayNotify {
747 trade_type: TradeType::MICROPAY,
748 out_trade_no: out_trade_no.to_string(),
749 sp_mchid: self.sp_mchid.clone(),
750 sub_mchid: sub_mchid.to_string(),
751 sp_appid: "".to_string(),
752 transaction_id,
753 success_time: PayNotify::datetime_to_timestamp(formatted.as_str(), "%Y%m%d%H%M%S"),
754 sp_openid: "".to_string(),
755 sub_openid: "".to_string(),
756 total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
757 payer_total: res["AMOUNT"].to_string().parse::<f64>().unwrap_or(0.0),
758 currency: "CNY".to_string(),
759 payer_currency: "CNY".to_string(),
760 trade_state: TradeState::SUCCESS,
761 };
762 Ok(res.json())
763 }
764 "N" => {
765 let crcode_type = match channel {
766 "wechat" => res["WECHAT_STATE"].to_string(),
767 "alipay" => res["ZFB_STATE"].to_string(),
768 _ => "".to_string()
769 };
770 let res = PayNotify {
771 trade_type: TradeType::MICROPAY,
772 out_trade_no: out_trade_no.to_string(),
773 sp_mchid: self.sp_mchid.clone(),
774 sub_mchid: sub_mchid.to_string(),
775 sp_appid: "".to_string(),
776 transaction_id: "".to_string(),
777 success_time: 0,
778 sp_openid: "".to_string(),
779 sub_openid: "".to_string(),
780 total: 0.0,
781 currency: "CNY".to_string(),
782 payer_total: 0.0,
783 payer_currency: "CNY".to_string(),
784 trade_state: TradeState::from(crcode_type.as_str()),
785 };
786 Ok(res.json())
787 }
788 "U" => {
789 Err(res.to_string())
790 }
791 "Q" => {
792 let res = PayNotify {
793 trade_type: TradeType::MICROPAY,
794 out_trade_no: out_trade_no.to_string(),
795 sp_mchid: self.sp_mchid.clone(),
796 sub_mchid: sub_mchid.to_string(),
797 sp_appid: "".to_string(),
798 transaction_id: "".to_string(),
799 success_time: 0,
800 sp_openid: "".to_string(),
801 sub_openid: "".to_string(),
802 total: 0.0,
803 payer_total: 0.0,
804 currency: "CNY".to_string(),
805 payer_currency: "CNY".to_string(),
806 trade_state: TradeState::NOTPAY,
807 };
808 Ok(res.json())
809 }
810 _ => {
811 Err(res.to_string())
812 }
813 }
814 }
815
816 fn pay_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
817 Err("暂未开通".to_string())
818 }
819
820 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> {
821 Err("暂未开通".to_string())
830 }
831
832 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> {
833 Err("暂未开通".to_string())
834 }
835
836 fn refund_notify(&mut self, _nonce: &str, _ciphertext: &str, _associated_data: &str) -> Result<JsonValue, String> {
837 Err("暂未开通".to_string())
838 }
839
840 fn refund_query(&mut self, trade_no: &str, out_refund_no: &str, sub_mchid: &str) -> Result<JsonValue, String> {
841 let today = Local::now().date_naive();
842 let date_str = today.format("%Y%m%d").to_string();
843 let body = object! {
844 MERCHANTID:sub_mchid,
845 BRANCHID:self.branchid.clone(),
846 POSID:self.posid.clone(),
847 ORDERDATE:date_str,
848 BEGORDERTIME:"00:00:00",
849 ENDORDERTIME:"23:59:59",
850 ORDERID:out_refund_no,
851 QUPWD:self.pass.clone(),
852 TXCODE:"410408",
853 TYPE:"1",
854 KIND:"0",
855 STATUS:"1",
856 SEL_TYPE:"3",
857 PAGE:"1",
858 OPERATOR:"",
859 CHANNEL:"",
860 MAC:""
861 };
862 let res = self.http_q("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain", body)?;
863 if res["RETURN_CODE"] != "000000" {
864 if res["RETURN_MSG"].eq("流水记录不存在") {
865 let res = PayNotify {
866 trade_type: TradeType::None,
867 out_trade_no: "".to_string(),
868 sp_mchid: "".to_string(),
869 sub_mchid: "".to_string(),
870 sp_appid: "".to_string(),
871 transaction_id: "".to_string(),
872 success_time: 0,
873 sp_openid: "".to_string(),
874 sub_openid: "".to_string(),
875 total: 0.0,
876 payer_total: 0.0,
877 currency: "".to_string(),
878 payer_currency: "".to_string(),
879 trade_state: TradeState::NOTPAY,
880 };
881 return Ok(res.json());
882 }
883 return Err(res["RETURN_MSG"].to_string());
884 }
885 let data = res["QUERYORDER"].clone();
886 let res = RefundNotify {
887 out_trade_no: trade_no.to_string(),
888 refund_no: out_refund_no.to_string(),
889 sp_mchid: "".to_string(),
890 sub_mchid: sub_mchid.to_string(),
891 transaction_id: data["ORDERID"].to_string(),
892 refund_id: data["refund_id"].to_string(),
893 success_time: PayNotify::datetime_to_timestamp(data["ORDERDATE"].as_str().unwrap_or(""), "%Y%m%d%H%M%S"),
894 total: data["AMOUNT"].as_f64().unwrap_or(0.0),
895 payer_total: data["amount"]["total"].to_string().parse::<f64>().unwrap(),
896 refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
897 payer_refund: data["amount"]["refund"].to_string().parse::<f64>().unwrap(),
898 status: RefundStatus::from(data["STATUS"].as_str().unwrap()),
899 };
900
901 Ok(res.json())
902 }
903
904 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> {
905 todo!()
906 }
907}
908
909fn xml_element_to_json(elem: &Element) -> JsonValue {
910 let mut obj = object! {};
911
912 for child in &elem.children {
913 if let xmltree::XMLNode::Element(e) = child {
914 obj[e.name.clone()] = xml_element_to_json(e);
915 }
916 }
917
918 match elem.get_text() {
919 None => obj,
920 Some(text) => JsonValue::from(text.to_string()),
921 }
922}