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